Webpack 如何解決 Node.js 動態載入 Cannot find module 的問題
JavaScript Dynamic Import
我公司最近遇到了一個需求是,我們需要有辦法 import 任一 JS 檔案
將其當作動態模組載入主程式當中運行
JavaScript 本身提供了相對應的功能,稱作 dynamic import
注意到,普通的 import 是沒辦法支援動態載入的功能的
To load modules in non-module contexts, use the dynamic import syntax instead.
Use Cases
以我們的例子來說,是希望能夠支援各種不同接口
為了實現該目標,以動態的方式載入對應的 module 並使用相同的 API interface 即可
那當然也有一些比較正常的 use case,比如說
- 靜態載入的資源過大,會拖慢整體的運行速度,進而降低使用者體驗
- 載入的資源只有在 runtime 的時候才會拿到(
我們的情況)
- 載入的資源位置只有在 runtime 的時候才會拿到(
我們的情況)
- 只有在需要使用該資源的時候才引入,避免 side effect
- 執行環境是 non-module 的
Restrictions
雖然你可以使用 dynamic import,但實務上還是不建議
只有在你真的需要的時候才去使用
原因在於 static import 的方式有助於一些靜態分析工具
如果使用 dynamic import 則沒有辦法提供良好的支援
另一個重點是,並不是所有環境都支援 dynamic import
比如說 service worker 就不支援
Cannot find Module
1
2
3
var e = new Error("Cannot find module '" + req + "'");
Error: Cannot find module 'xxx'
之後我就遇到一個奇怪的問題
明明我動態產生的路徑是正確的,檔案也是存在的
不管執行幾次仍會遇到 Cannot find module
的問題
更懸的是,如果是直接用 node 跑不會報錯
只有在 webpack bundle 完才會出現
Webpack Dynamic Import Support
Webpack 本身也支援動態載入的功能,但是有一些限制
你沒辦法完全依靠於變數,某種程度上你需要提供一些 “線索” 給 bundler
1
2
3
4
5
// imagine we had a method to get language from cookies or other storage
const language = detectVisitorLanguage();
import(`./locale/${language}.json`).then((module) => {
// do something with the translations
});
以上述例子來說,動態載入的檔案位置它一定在 locale
底下並且為 json 檔案
設定為 wildcard 或者完全依靠 runtime 數值決定,會導致他有可能會指向 系統內的任一位置
這顯然不太對
Webpack Chunk
動態載入的模組,會是一個獨立的區塊,稱為 chunk
由於其動態載入的特性,亦即需要的時候才會引入進來
那把它跟 application chunk 綁在一起屬實是沒什麼必要性
分開打包,需要在引入可以讓一開始的載入速度變得更快
不過同時它會造成一些問題
如果動態載入的路徑是相對路徑
那是不是 runtime 在 evaluate 的時候就會出錯了呢?
因為 webpack 在打包的時候並不知道 runtime 的路徑
而這正是造成 Cannot find module
的罪魁禍首
WebpackIgnore the Magic Comment
那有沒有辦法不要讓 webpack 解析我的 import 路徑呢?
我可以跟它掛保證說 runtime 我會自己處理,你就把它放著就好
你可以透過所謂的 Magic Comment 進行標注
以本例來說,你需要的是 webpackIgnore
1
new URL(/* webpackIgnore: true */ 'file1.css', import.meta.url);
透過簡單的類似註解的寫法註記在程式碼當中,webpack 就知道說不要嘗試去解析 import.meta.url
是什麼
到 runtime 的時候它就會變合法的路徑了(當然,執行出錯我會自己負責)
這樣做可以確保即使是使用相對路徑,node 也能夠正確的找到檔案
Magic Comment CJS Support
Magic Comment 目前有多個種類可以使用
如果你需要 CommonJS 的支援,可以在 webpack.config.js
底下新增 flag 就可以了
不過要注意的是,目前僅有 webpackIgnore
這個 attribute 支援 CJS
1
2
3
4
5
6
7
8
9
module.exports = {
module: {
parser: {
javascript: {
commonjsMagicComments: true,
},
},
},
};
Leave a comment