mini-css-extract-plugin 中有一個直接執行字符串形式的 commonjs 代碼,在編譯階段獲取 css-loader 產物的方法。學習了一下這個方法所作的事情。css
在寫編譯工具的時候,咱們常常須要提取文件中的特定內容來完成後續的編譯。舉例來講假如咱們正在寫一個編譯插件,須要提取 .vue 文件中 data 的初始值,若是想要用 babel 之類的工具來解析文件經過 AST 來獲取,會是很麻煩的一件事。html
若是 .vue 文件寫的還比較簡單,例如這種:vue
或許比較好處理,咱們只須要提取 data 函數中 return 的對象就能夠了。可是隻要稍微複雜一點,咱們就無能爲力了,例如 data 初始值中若是執行了一個函數:node
因爲寫法很是靈活,想要繼續經過靜態分析來找到內容是很是困難的了。這時候一個更好的方法,就是在編譯階段直接執行一下這段 JS 文件,同時執行一下 data 方法,那麼就能夠直接拿到 data 的返回值了。如今問題來了,如何能在編譯階段執行一下這段代碼呢?webpack
以前我一直的作法是編譯成 commonjs 文件:git
以後 new 一個 Function,傳入一些假的 exports
、module
等方法,例如:github
很是容易看出來,這種方法最大的問題是使用了假的 require
方法。這裏直接傳入 Node.js 提供的 require
方法也是不行的,由於 require
可能寫相對路徑或者包名稱,而咱們傳入的 require
方法是以當前文件來查找的,會找不到對應的依賴。web
最近看 webpack 的 mini-css-extract-plugin 插件時,發現這個插件爲了提取 css-loader 輸出的樣式,實現了一個 evalModuleCode
函數,這個函數的位置在這裏:github.com/webpack-con…,內容是這樣的:api
使用起來大概是這樣的:babel
若是執行一下,咱們就能夠拿到代碼中輸出的內容了:
咱們來看下這個函數如何實現的:
module
模塊。NativeModule
實例。paths
和 filename
。這個 module
模塊是一個 Node.js 提供的模塊,文檔地址在這裏:nodejs.org/api/modules…,注意這個跟咱們直接使用的 module.exports
並非同一個(module.exports
中的 module
是 NativeModule
的一個實例)。
可是咱們若是看這個文檔,會發現文檔中的內容只有很是有限的幾個方法,並無說起到上面的任何操做😓。
因而咱們只能看代碼了。
require('module')
引入的文件在 Node.js 源文件中的 lib/module.js
。這個文件又 exports
出了 internal/modules/cjs/loader
文件中的內容。
咱們對照着 evalModuleCode
的實現,一步一步在 internal/modules/cjs/loader
文件中找對應的內容:
首先是構造函數,Module
的構造函數沒有作什麼特殊的內容,只是設置了一些屬性。其中 updateChildren
,是爲了設置不一樣 module
以前的對應關係,這裏其實咱們並無用到:
以後是 _nodeModulePaths
方法,這個方法有 window 和 posix 兩個版本,區別只是對路徑的處理方式不一樣,所作的工做其實就是從當前目錄開始,向上遍歷出全部的 node_modules 文件夾路徑,用於在這些目錄中查找對應的包 :
例如當前咱們的路徑是 /home/work/code/tmp
,那麼解析出的 paths
路徑就是這樣的:
再接下來是設置了 filename
屬性,以後就是調用 _compile
方法了:
其中重點關注上面這兩句,第一句 wrapSafe
作的事情是將 JS 源代碼,使用函數包裝一下,參數增長 requires、module 這些,相似這樣:
以後在 V8 中編譯執行,執行結果就獲得了包裝後的函數。
再執行這個方法,就能夠獲得最終輸出的結果了:
這裏比較有意思的是 module
參數,能夠看到傳入的是 this
,而 this
就是一個 Module
實例。
執行過程大概就是這樣了,其實 咱們直接用的 require
方法,也是在 Module
上定義的,evalModuleCode
是將 require
的過程簡化了,感興趣的同窗能夠本身看一下 require
方法是如何實現的: