利用 Node.js 中的 Module 類,執行字符串形式代碼的方法

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,傳入一些假的 exportsmodule 等方法,例如:github

很是容易看出來,這種方法最大的問題是使用了假的 require 方法。這裏直接傳入 Node.js 提供的 require 方法也是不行的,由於 require 可能寫相對路徑或者包名稱,而咱們傳入的 require 方法是以當前文件來查找的,會找不到對應的依賴。web

mini-css-extract-plugin 插件所用的方法

最近看 webpack 的 mini-css-extract-plugin 插件時,發現這個插件爲了提取 css-loader 輸出的樣式,實現了一個 evalModuleCode 函數,這個函數的位置在這裏:github.com/webpack-con…,內容是這樣的:api

使用起來大概是這樣的:babel

若是執行一下,咱們就能夠拿到代碼中輸出的內容了:

咱們來看下這個函數如何實現的:

  • 首先引入了 module 模塊。
  • 以後建立了一個 NativeModule 實例。
  • 接下來設置了模塊的 pathsfilename
  • 最後執行了 _compile 方法。

這個 module 模塊是一個 Node.js 提供的模塊,文檔地址在這裏:nodejs.org/api/modules…,注意這個跟咱們直接使用的 module.exports 並非同一個(module.exports 中的 moduleNativeModule 的一個實例)。

可是咱們若是看這個文檔,會發現文檔中的內容只有很是有限的幾個方法,並無說起到上面的任何操做😓。

因而咱們只能看代碼了。

Node.js 的 module 類

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 方法是如何實現的:

相關文章
相關標籤/搜索