W3Cschool
恭喜您成為首批注冊用戶
獲得88經(jīng)驗值獎勵
webpack compiler 能夠識別遵循 ES2015 模塊語法、CommonJS 或 AMD 規(guī)范編寫的模塊。然而,一些 third party(第三方庫) 可能會引用一些全局依賴(例如 jQuery 中的 $)。因此這些 library 也可能會創(chuàng)建一些需要導(dǎo)出的全局變量。這些 "broken modules(不符合規(guī)范的模塊)" 就是 shimming(預(yù)置依賴) 發(fā)揮作用的地方。
shim 另外一個極其有用的使用場景就是:當(dāng)你希望 polyfill 擴展瀏覽器能力,來支持到更多用戶時。在這種情況下,你可能只是想要將這些 polyfills 提供給需要修補(patch)的瀏覽器(也就是實現(xiàn)按需加載)。
下面的文章將向我們展示這兩種用例。
讓我們開始第一個 shimming 全局變量的用例。在此之前,先看下我們的項目:
project
webpack-demo
|- package.json
|- package-lock.json
|- webpack.config.js
|- /dist
|- index.html
|- /src
|- index.js
|- /node_modules
還記得我們之前用過的 lodash 嗎?出于演示目的,例如把這個應(yīng)用程序中的模塊依賴,改為一個全局變量依賴。要實現(xiàn)這些,我們需要使用 ProvidePlugin 插件。
使用 ProvidePlugin 后,能夠在 webpack 編譯的每個模塊中,通過訪問一個變量來獲取一個 package。如果 webpack 看到模塊中用到這個變量,它將在最終 bundle 中引入給定的 package。讓我們先移除 lodash 的 import 語句,改為通過插件提供它:
src/index.js
-import _ from 'lodash';
-
function component() {
const element = document.createElement('div');
- // Lodash, now imported by this script
element.innerHTML = _.join(['Hello', 'webpack'], ' ');
return element;
}
document.body.appendChild(component());
webpack.config.js
const path = require('path');
+const webpack = require('webpack');
module.exports = {
entry: './src/index.js',
output: {
filename: 'main.js',
path: path.resolve(__dirname, 'dist'),
},
+ plugins: [
+ new webpack.ProvidePlugin({
+ _: 'lodash',
+ }),
+ ],
};
我們本質(zhì)上所做的,就是告訴 webpack……
如果你遇到了至少一處用到 _ 變量的模塊實例,那請你將 lodash package 引入進(jìn)來,并將其提供給需要用到它的模塊。
運行我們的構(gòu)建腳本,將會看到同樣的輸出:
$ npm run build
..
[webpack-cli] Compilation finished
asset main.js 69.1 KiB [emitted] [minimized] (name: main) 1 related asset
runtime modules 344 bytes 2 modules
cacheable modules 530 KiB
./src/index.js 191 bytes [built] [code generated]
./node_modules/lodash/lodash.js 530 KiB [built] [code generated]
webpack 5.4.0 compiled successfully in 2910 ms
還可以使用 ProvidePlugin 暴露出某個模塊中單個導(dǎo)出,通過配置一個“數(shù)組路徑”(例如 [module, child, ...children?])實現(xiàn)此功能。所以,我們假想如下,無論 join 方法在何處調(diào)用,我們都只會獲取到 lodash 中提供的 join 方法。
src/index.js
function component() {
const element = document.createElement('div');
- element.innerHTML = _.join(['Hello', 'webpack'], ' ');
+ element.innerHTML = join(['Hello', 'webpack'], ' ');
return element;
}
document.body.appendChild(component());
webpack.config.js
const path = require('path');
const webpack = require('webpack');
module.exports = {
entry: './src/index.js',
output: {
filename: 'main.js',
path: path.resolve(__dirname, 'dist'),
},
plugins: [
new webpack.ProvidePlugin({
- _: 'lodash',
+ join: ['lodash', 'join'],
}),
],
};
這樣就能很好的與 tree shaking 配合,將 lodash library 中的其余沒有用到的導(dǎo)出去除。
一些遺留模塊依賴的 this 指向的是 window 對象。在接下來的用例中,調(diào)整我們的 index.js:
function component() {
const element = document.createElement('div');
element.innerHTML = join(['Hello', 'webpack'], ' ');
+ // 假設(shè)我們處于 `window` 上下文
+ this.alert('Hmmm, this probably isn\'t a great idea...')
+
return element;
}
document.body.appendChild(component());
當(dāng)模塊運行在 CommonJS 上下文中,這將會變成一個問題,也就是說此時的 this 指向的是 module.exports。在這種情況下,你可以通過使用 imports-loader 覆蓋 this 指向:
webpack.config.js
const path = require('path');
const webpack = require('webpack');
module.exports = {
entry: './src/index.js',
output: {
filename: 'main.js',
path: path.resolve(__dirname, 'dist'),
},
+ module: {
+ rules: [
+ {
+ test: require.resolve('./src/index.js'),
+ use: 'imports-loader?wrapper=window',
+ },
+ ],
+ },
plugins: [
new webpack.ProvidePlugin({
join: ['lodash', 'join'],
}),
],
};
讓我們假設(shè),某個 library 創(chuàng)建出一個全局變量,它期望 consumer(使用者) 使用這個變量。為此,我們可以在項目配置中,添加一個小模塊來演示說明:
project
webpack-demo
|- package.json
|- package-lock.json
|- webpack.config.js
|- /dist
|- /src
|- index.js
+ |- globals.js
|- /node_modules
src/globals.js
const file = 'blah.txt';
const helpers = {
test: function () {
console.log('test something');
},
parse: function () {
console.log('parse something');
},
};
你可能從來沒有在自己的源碼中做過這些事情,但是你也許遇到過一個老舊的 library,和上面所展示的代碼類似。在這種情況下,我們可以使用 exports-loader,將一個全局變量作為一個普通的模塊來導(dǎo)出。例如,為了將 file 導(dǎo)出為 file 以及將 helpers.parse 導(dǎo)出為 parse,做如下調(diào)整:
webpack.config.js
const path = require('path');
const webpack = require('webpack');
module.exports = {
entry: './src/index.js',
output: {
filename: 'main.js',
path: path.resolve(__dirname, 'dist'),
},
module: {
rules: [
{
test: require.resolve('./src/index.js'),
use: 'imports-loader?wrapper=window',
},
+ {
+ test: require.resolve('./src/globals.js'),
+ use:
+ 'exports-loader?type=commonjs&exports=file,multiple|helpers.parse|parse',
+ },
],
},
plugins: [
new webpack.ProvidePlugin({
join: ['lodash', 'join'],
}),
],
};
此時,在我們的 entry 入口文件中(即 src/index.js),可以使用 const { file, parse } = require('./globals.js');,可以保證一切將順利運行。
目前為止我們所討論的所有內(nèi)容都是處理那些遺留的 package,讓我們進(jìn)入到第二個話題:polyfill。
有很多方法來加載 polyfill。例如,想要引入 babel-polyfill 我們只需如下操作:
npm install --save babel-polyfill
然后,使用 import 將其引入到我們的主 bundle 文件:
src/index.js
+import 'babel-polyfill';
+
function component() {
const element = document.createElement('div');
element.innerHTML = join(['Hello', 'webpack'], ' ');
// Assume we are in the context of `window`
this.alert("Hmmm, this probably isn't a great idea...");
return element;
}
document.body.appendChild(component());
注意,這種方式優(yōu)先考慮正確性,而不考慮 bundle 體積大小。為了安全和可靠,polyfill/shim 必須運行于所有其他代碼之前,而且需要同步加載,或者說,需要在所有 polyfill/shim 加載之后,再去加載所有應(yīng)用程序代碼。 社區(qū)中存在許多誤解,即現(xiàn)代瀏覽器“不需要”polyfill,或者 polyfill/shim 僅用于添加缺失功能 - 實際上,它們通常用于修復(fù)損壞實現(xiàn)(repair broken implementation),即使是在最現(xiàn)代的瀏覽器中,也會出現(xiàn)這種情況。 因此,最佳實踐仍然是,不加選擇地和同步地加載所有 polyfill/shim,盡管這會導(dǎo)致額外的 bundle 體積成本。
如果你認(rèn)為自己已經(jīng)打消這些顧慮,并且希望承受損壞的風(fēng)險。那么接下來的這件事情,可能是你應(yīng)該要做的: 我們將會把 import 放入一個新文件,并加入 whatwg-fetch polyfill:
npm install --save whatwg-fetch
src/index.js
-import 'babel-polyfill';
-
function component() {
const element = document.createElement('div');
element.innerHTML = join(['Hello', 'webpack'], ' ');
// Assume we are in the context of `window`
this.alert("Hmmm, this probably isn't a great idea...");
return element;
}
document.body.appendChild(component());
project
webpack-demo
|- package.json
|- package-lock.json
|- webpack.config.js
|- /dist
|- /src
|- index.js
|- globals.js
+ |- polyfills.js
|- /node_modules
src/polyfills.js
import 'babel-polyfill';
import 'whatwg-fetch';
webpack.config.js
const path = require('path');
const webpack = require('webpack');
module.exports = {
- entry: './src/index.js',
+ entry: {
+ polyfills: './src/polyfills',
+ index: './src/index.js',
+ },
output: {
- filename: 'main.js',
+ filename: '[name].bundle.js',
path: path.resolve(__dirname, 'dist'),
},
module: {
rules: [
{
test: require.resolve('./src/index.js'),
use: 'imports-loader?wrapper=window',
},
{
test: require.resolve('./src/globals.js'),
use:
'exports-loader?type=commonjs&exports[]=file&exports[]=multiple|helpers.parse|parse',
},
],
},
plugins: [
new webpack.ProvidePlugin({
join: ['lodash', 'join'],
}),
],
};
如上配置之后,我們可以在代碼中添加一些邏輯,有條件地加載新的 polyfills.bundle.js 文件。根據(jù)需要支持的技術(shù)和瀏覽器來決定是否加載。我們將做一些簡單的試驗,來確定是否需要引入這些 polyfill:
dist/index.html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>Getting Started</title>
+ <script>
+ const modernBrowser = 'fetch' in window && 'assign' in Object;
+
+ if (!modernBrowser) {
+ const scriptElement = document.createElement('script');
+
+ scriptElement.async = false;
+ scriptElement.src = '/polyfills.bundle.js';
+ document.head.appendChild(scriptElement);
+ }
+ </script>
</head>
<body>
- <script src="main.js"></script>
+ <script src="index.bundle.js"></script>
</body>
</html>
現(xiàn)在,在 entry 入口文件中,可以通過 fetch 獲取一些數(shù)據(jù):
src/index.js
function component() {
const element = document.createElement('div');
element.innerHTML = join(['Hello', 'webpack'], ' ');
// Assume we are in the context of `window`
this.alert("Hmmm, this probably isn't a great idea...");
return element;
}
document.body.appendChild(component());
+
+fetch('https://jsonplaceholder.typicode.com/users')
+ .then((response) => response.json())
+ .then((json) => {
+ console.log(
+ "We retrieved some data! AND we're confident it will work on a variety of browser distributions."
+ );
+ console.log(json);
+ })
+ .catch((error) =>
+ console.error('Something went wrong when fetching this data: ', error)
+ );
執(zhí)行構(gòu)建腳本,可以看到,瀏覽器發(fā)送了額外的 polyfills.bundle.js 文件請求,然后所有代碼順利執(zhí)行。注意,以上的這些設(shè)定可能還會有所改進(jìn),這里我們向你提供一個很棒的想法:將 polyfill 提供給需要引入它的用戶。
babel-preset-env package 通過 browserslist 來轉(zhuǎn)譯那些你瀏覽器中不支持的特性。這個 preset 使用 useBuiltIns 選項,默認(rèn)值是 false,這種方式可以將全局 babel-polyfill 導(dǎo)入,改進(jìn)為更細(xì)粒度的 import 格式:
import 'core-js/modules/es7.string.pad-start';
import 'core-js/modules/es7.string.pad-end';
import 'core-js/modules/web.timers';
import 'core-js/modules/web.immediate';
import 'core-js/modules/web.dom.iterable';
See the babel-preset-env documentation for more information.
像 process 這種 Node 內(nèi)置模塊,能直接根據(jù)配置文件進(jìn)行正確的 polyfill,而不需要任何特定的 loader 或者 plugin。查看 node 配置頁面獲取更多信息。
還有一些其他的工具,也能夠幫助我們處理這些遺留模塊。
如果這些遺留模塊沒有 AMD/CommonJS 版本,但你也想將他們加入 dist 文件,則可以使用 noParse 來標(biāo)識出這個模塊。這樣就能使 webpack 將引入這些模塊,但是不進(jìn)行轉(zhuǎn)化(parse),以及不解析(resolve) require() 和 import 語句。這種用法還會提高構(gòu)建性能。
最后,一些模塊支持多種 模塊格式,例如一個混合有 AMD、CommonJS 和 legacy(遺留) 的模塊。在大多數(shù)這樣的模塊中,會首先檢查 define,然后使用一些怪異代碼導(dǎo)出一些屬性。在這些情況下,可以通過 imports-loader 設(shè)置 additionalCode=var%20define%20=%20false; 來強制 CommonJS 路徑。
Copyright©2021 w3cschool編程獅|閩ICP備15016281號-3|閩公網(wǎng)安備35020302033924號
違法和不良信息舉報電話:173-0602-2364|舉報郵箱:jubao@eeedong.com
掃描二維碼
下載編程獅App
編程獅公眾號
聯(lián)系方式:
更多建議: