溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

微信小程序打包插件開發(fā)的方法

發(fā)布時間:2022-03-11 10:57:45 來源:億速云 閱讀:419 作者:iii 欄目:開發(fā)技術(shù)

今天小編給大家分享一下微信小程序打包插件開發(fā)的方法的相關(guān)知識點,內(nèi)容詳細,邏輯清晰,相信大部分人都還太了解這方面的知識,所以分享這篇文章給大家參考一下,希望大家閱讀完這篇文章后有所收獲,下面我們一起來了解一下吧。

如果你看過文檔,相信你一定知道:

  • 每個插件必須要有 apply 方法,用于 webpack 引擎執(zhí)行你想要執(zhí)行的代碼。

  • 兩個重要的對象 Compiler 和 Compilation,你可以在上面綁定事件鉤子(webpack 執(zhí)行到該步驟的時候調(diào)用),具體有哪些事件鉤子可以閱讀Compiler hooks。

  • module 和 chunk 的關(guān)系,我們可以理解為每個文件都會有一個 module,而一個 chunk 則是由多個 module 來組成。

  • webpack 整個打包流程有那些事件

  • 如何寫一個簡單的 loader

如果感覺無從著手,可以繼續(xù)看看我是如何一步步開發(fā)并完善 mini-program-webpack-loader 來打包小程序的。

小程序有一個固定的套路,首先需要有一個 app.json 文件來定義所有的頁面路徑,然后每個頁面有四個文件組成:.js,.json,.wxml,.wxss。所以我以 app.json 作為 webpack entry,當 webpack 執(zhí)行插件的 apply 的時候,通過獲取 entry 來知道小程序都有哪些頁面。

 

這里使用了兩個插件 MultiEntryPlugin,SingleEntryPlugin。為什么要這樣做呢?因為 webpack 會根據(jù)你的 entry 配置(這里的 entry 不只是 webpack 配置里的 entry,import(), require.ensure() 都會生成一個 entry)來決定生成文件的個數(shù),我們不希望把所有頁面的 js 打包到一個文件,需要使用 SingleEntryPlugin 來生成一個新的 entry module;而那些靜態(tài)資源,我們可以使用 MultiEntryPlugin 插件來處理,把這些文件作為一個 entry module 的依賴,在 loader 中配置 file-loader 即可把靜態(tài)文件輸出。偽代碼如下:

const MultiEntryPlugin = require('webpack/lib/MultiEntryPlugin');
 const SingleEntryPlugin = require('webpack/lib/SingleEntryPlugin');
 
 class MiniPlugin {
    apply (compiler) {
        let options = compiler.options
        let context = compiler.rootContext
        let entry = options.entry
        let files = loadFiles(entry)
        let scripts = files.filter(file => /\.js$/.test(file))
        let assets = files.filter(file => !/\.js$/.test(file))
        
       new  MultiEntryPlugin(context, assets, '__assets__').apply(compiler)
        
        scripts.forEach((file => {
            let fileName = relative(context, file).replace(extname(file), '');
            new SingleEntryPlugin(context, file, fileName).apply(compiler);
        })
    }
 }
復(fù)制代碼

當然,如果像上面那樣做,你會發(fā)現(xiàn)最后會多出一個 main.js,xxx.js(使用 MultiEntryPlugin 時填的名字),main.js 對應(yīng)的是配置的 entry 生成的文件,xxx.js 則是 MultiEntryPlugin 生成的。這些文件不是我們需要的,所以需要去掉他。如果熟悉 webpack 文檔,我們有很多地方可以修改最終打包出來的文件,如 compiler 的 emit 事件,compilation 的 optimizeChunks 相關(guān)的事件都可以實現(xiàn)。其本質(zhì)上就是去修改 compilation.assets 對象。

在 mini-program-webpack-loader 中就使用了 emit 事件來處理這種不需要輸出的內(nèi)容。 

小程序打包當然沒這么簡單,還得支持wxml、wxss、wxs和自定義組件的引用,所以這個時候就需要一個 loader 來完成了,loader 需要做的事情也非常簡單 —— 解析依賴的文件,如 .wxml 需要解析 import 組件的 src,wxs 的 src,.wxss 需要解析 @import,wxs 的 require,最后在 loader 中使用 loadModule 方法添加即可。自定義組件一開始在 add entry 步驟的時候直接獲取了,所以不需要 loader 來完成。

這樣做也沒什么問題,可是開發(fā)體驗是比較差的,如再添加一個自定義組件,一個頁面,webpack 是無感知的,所以需要在頁面中的 .json 發(fā)生改變時檢查是不是新增了自定義組件或者新增了頁面。這個時候遇到一個問題,自定義組件的 js 是不能通過 addModule 的方式來添加的,因為自定義組件的 js 必須作為獨立的入口文件。在 loader 中是做不了,所以嘗試把文件傳到 plugin 中(因為 plugin 先于 loader 執(zhí)行,所以是可以建立 loader 和 plugin 通信的)。

簡單粗暴的方式:

// loader.js
class MiniLoader {}

module.exports = function (content) {
    new MiniLoader(this, content)
}
module.exports.$applyPluginInstance = function (plugin) {
  MiniLoader.prototype.$plugin = plugin
}

// plugin.js
const loader = require('./loader')
class MiniPlugin {
    apply (compiler) {
        loader.$applyPluginInstance(this);
    }
}
復(fù)制代碼

但是...。文件是傳到 plugin 了,可是再使用 SingleEntryPlugin 時你會發(fā)現(xiàn),沒效果。因為在 compiler make 之后 webpack 已經(jīng)不能感知新的 module 添加了,所以是沒有用的,這個時候就需要根據(jù)文檔猜,怎么樣才能讓 webpack 感知到新的 module,根據(jù)文檔中的事件做關(guān)鍵字查詢,可以發(fā)現(xiàn)在編譯完成的時候會調(diào)用 compilation needAdditionalPass 事件鉤子:

this.emitAssets(compilation, err => {
    	if (err) return finalCallback(err);
    
    	if (compilation.hooks.needAdditionalPass.call()) {
    		compilation.needAdditionalPass = true;
    
    		const stats = new Stats(compilation);
    		stats.startTime = startTime;
    		stats.endTime = Date.now();
    		this.hooks.done.callAsync(stats, err => {
    			if (err) return finalCallback(err);
    
    			this.hooks.additionalPass.callAsync(err => {
    				if (err) return finalCallback(err);
    				this.compile(onCompiled);
    			});
    		});
    		return;
    	}
    
    	this.emitRecords(err => {
    		if (err) return finalCallback(err);
    
    		const stats = new Stats(compilation);
    		stats.startTime = startTime;
    		stats.endTime = Date.now();
    		this.hooks.done.callAsync(stats, err => {
    			if (err) return finalCallback(err);
    			return finalCallback(null, stats);
    		});
    	});
    });
復(fù)制代碼

如果在這個事件鉤子返回一個 true 值,則可以使 webpack 調(diào)用 compiler additionalPass 事件鉤子,嘗試在這里添加文件,果然是可以的。

 

當然,小程序打包還有些不同的地方,比如分包,如何用好 splitchunk,就不在啰嗦了,當你開始以后你會發(fā)現(xiàn)有很多的方法來實現(xiàn)想要的效果。

插件開發(fā)到這里差不多了,總的來說,webpack 就是變著花樣的回調(diào),當你知道每個回調(diào)該做什么的時候,webpack 用起來就輕松了。明顯我不知道,因為在開發(fā)過程中遇到了一些問題。

遇到的問題

1.如何在小程序代碼中支持 resolve alias,node_modules?

既然是工具,當然需要做更多的事情,有贊的小程序那么復(fù)雜,如果支持 resolve alias,node_modules 可以使得項目更方便維護,或許你會說這不是 webpack 最基本的功能嗎,不是的,我們當然是希望可以在任何文件中使用 alias,node_modules 支持的不僅僅是 js。當然這樣做就意味著事情將變得復(fù)雜,首先就是獲取文件路徑,必須是異步的,因為在 webpack 4 中 resolve 不再支持 sync。其次就是小程序的目錄名不能是 node_modules,這時就需要一種計算相對路徑的規(guī)則,還是相對打包輸出的,而不是相對當前項目目錄。

2.多個小程序項目的合并

有贊從小程序來講,有微商城版,有零售版,以及公共版,其中大多基礎(chǔ)功能,業(yè)務(wù)都是相同的,當然不能再每個小程序在開發(fā)一次,所以這個工具具備合并多個小程序當然是必須的。這樣的合并稍微又要比從 node_modules 中取文件復(fù)雜一些,因為需要保證多個小程序合并后的頁面是正確的,而且要保證路徑不變。

這兩個問題的最終的解決方案既是以 webpack rootContext 的 src 目錄為基準目錄,以該目錄所在路徑計算打包文件的絕對路徑,然后根據(jù)入口文件的 app.json 所在目錄的路徑計算出最終輸出路徑。

exports.getDistPath = (compilerContext, entryContexts) => {
  /**
   * webpack 以 config 所在目錄的 src 為打包入口
   * 所以可以根據(jù)該目錄追溯源文件地址
   */
  return (path) => {
    let fullPath = compilerContext
    let npmReg = /node_modules/g
    let pDirReg = /^[_|\.\.]\//g

    if (isAbsolute(path)) {
      fullPath = path
    } else {
      // 相對路徑:webpack 最后生成的路徑,打包入口外的文件都以 '_' 表示上級目錄

      while (pDirReg.test(path)) {
        path = path.substr(pDirReg.lastIndex)
        fullPath = join(fullPath, '../')
      }

      if (fullPath !== compilerContext) {
        fullPath = join(fullPath, path)
      }
    }
    // 根據(jù) entry 中定義的 json 文件目錄獲取打包后所在目錄,如果不能獲取就返回原路徑
    let contextReg = new RegExp(entryContexts.join('|'), 'g')

    if (fullPath !== compilerContext && contextReg.exec(fullPath)) {
      path = fullPath.substr(contextReg.lastIndex + 1)
      console.assert(!npmReg.test(path), `文件${path}路徑錯誤:不應(yīng)該還包含 node_modules`)
    }

    /**
     * 如果有 node_modules 字符串,則去模塊名稱
     * 如果 app.json 在 node_modules 中,那 path 不應(yīng)該包含 node_modules 
     */

    if (npmReg.test(path)) {
      path = path.substr(npmReg.lastIndex + 1)
    }

    return path
  }
}
復(fù)制代碼

3.如何把子包單獨依賴的內(nèi)容打包到子包內(nèi)

解決這個問題的方法是通過 optimizeChunks 事件,在每個 chunk 的依賴的 module 中添加這個 chunk 的入口文件,然后在 splitChunk 的 test 配置中檢查 module 被依賴的數(shù)量。如果只有一個,并且是被子包依賴,則打包到子包內(nèi)。

4.webpack 支持單文件失敗

這是一個未解決的問題,當嘗試使用 webpack 來支持單文件的時候,好像沒那么方便:

  • 單文件拆分為四個文件后,可以使用 emitFile 和 addDependency 來創(chuàng)建文件,但是創(chuàng)建的文件不會執(zhí)行 loader

  • 使用 loadModule 會因為文件系統(tǒng)不存在該文件會報錯

以上就是“微信小程序打包插件開發(fā)的方法”這篇文章的所有內(nèi)容,感謝各位的閱讀!相信大家閱讀完這篇文章都有很大的收獲,小編每天都會為大家更新不同的知識,如果還想學(xué)習(xí)更多的知識,請關(guān)注億速云行業(yè)資訊頻道。

向AI問一下細節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI