1. 简介
webpack 编译器(compiler)能够识别遵循 ES2015 模块语法、CommonJS 或 AMD 规范编写的模块。然而,一些第三方的库(library)可能会引用一些全局依赖(例如 jQuery 中的 $)。这些库也可能创建一些需要被导出的全局变量。这些“不符合规范的模块”就是 shimming 发挥作用的地方。
shimming 另外一个使用场景就是,当你希望 polyfill 浏览器功能以支持更多用户时。在这种情况下,你可能只想要将这些 polyfills 提供给到需要修补(patch)的浏览器(也就是实现按需加载)。
2. 处理遗留模块
这块以前比较多见,但随着各个库的规范升级,现在用的比较少了。我们来看一下。
2.1 shimming 全局变量
我们来看一个简单的例子:
// index.js
import { ui } from './ui';
ui();
// ui.js
import $ from 'jquery';
export function ui() {
$('body').css('background', 'green');
}
我们使用一个 ui 库,提供了一个方法 ui,依赖 jquery 实现。打包后,打开页面:
可以看到正常输出。
现在我们试着调整 ui, 去掉对 jquery 的引用,在 index 将其引入会如何呢?
// index.js
import $ from 'jquery';
import { ui } from './ui';
ui();
// ui.js
export function ui() {
$('body').css('background', 'green');
}
打包:
发现即使在入口 index 引入 jquery,但是 ui 找不到该变量,这还是因为模块引入变量的作用范围是模块内,正确的用法是哪里使用,就在哪里引用(虽然webpack 底层只会对相同模块加载一次,但是引用标识必须是多次的)。可是对一些老的三方库,并没有引用 jquery,怎么办呢,他们默认 jquery 是全局变量可以直接引用。
要解决这个问题,我们把 jquery 作为我们应用程序中的一个全局变量就可以了。要实现这些,我们需要使用
ProvidePlugin
插件。
使用 ProvidePlugin
后,能够在通过 webpack 编译的每个模块中,通过访问一个变量来获取到 package 包。如果 webpack 知道这个变量在某个模块中被使用了,那么 webpack 将在最终 bundle 中引入我们给定的 package。
我们恢复 index.js, 然后修改 webpack.common.js
plugins: [
...
new webpack.ProvidePlugin({
$: 'jquery'
})
],
打包,
本质上,我们所做的,就是告诉 webpack,如果你遇到了至少一处用到 lodash 变量的模块实例,那请你将 lodash package 包引入进来,并将其提供给需要用到它的模块。
我们还可以使用 ProvidePlugin 暴露某个模块中单个导出值,只需通过一个“数组路径”进行配置(例如 [module, child, ...children?])
如下:
plugins: [
...
new webpack.ProvidePlugin({
$: 'jquery',
_join: ['lodash', 'join']
})
],
告诉我们如果用到_join 的地方,实际上是使用 lodash.join 方法。
// ui.js
export function ui() {
$('body').css('background', _join(['dark', 'green'], ''));
}
打包后如下:
这样就能很好的与 tree shaking 配合,将
lodash
库中的其他没用到的部分去除。
2.2 细粒度 shimming
一些传统的模块依赖的 this 指向的是 window 对象。我们来看一下 webpack 模块中打印 this 指向哪里:
// index.js
console.log(this);
this.alert('hi');
本来模块设想运行在 window 下,如果当模块运行在 CommonJS 环境下这将会变成一个问题,也就是说此时的 this
指向的是 module.exports
。在这个例子中,可以通过使用 imports-loader
覆写 this
:
rules: [
...
{
test: require.resolve('../src/index.js'),
use: 'imports-loader?this=>window'
}
]
安装 imports-loader, 然后打包后如下:
2.3 全局 exports
让我们假设,某个库(library)创建出一个全局变量,它期望用户使用这个变量。
// index.js
import { file, parse } from './global.js';
console.log(file);
parse();
// global.js
const file = 'blah.txt';
const helpers = {
test: function() { console.log('test something'); },
parse: function() { console.log('parse something'); }
}
你可能从来没有在自己的源码中做过这些事情,但是你也许遇到过一个老旧的库(library),和上面所展示的代码类似。在这个用例中,我们可以使用 exports-loader
,将一个全局变量作为一个普通的模块来导出。例如,为了将 file
导出为 file
以及将 helpers.parse
导出为 parse
,做如下调整:
rules: [
...
{
test: require.resolve('../src/global.js'),
use: 'exports-loader?file,parse=helpers.parse'
}
]
打包后:
3. polyfills
除了处理那些遗留的 package 包,shimming 的另一个作用就是处理 polyfills。有很多方法来载入 polyfills。例如,要引入 babel-polyfill
我们只需要如下操作:
npm install --save babel-polyfill
// index.js
import 'babel-polyfill';
请注意,我们没有将 import 绑定到变量。这是因为只需在基础代码(code base)之外,再额外执行 polyfills,这样我们就可以假定代码中已经具有某些原生功能。
让我们把 import
放入一个新文件,并加入 whatwg-fetch
polyfill:
npm install --save whatwg-fetch
// pollyfills.js
import 'babel-polyfill';
import 'whatwg-fetch';
配置修改如下:
entry: {
polyfills: './src/polyfills.js',
index: "./src/index.js"
},
output: {
path: path.resolve(__dirname, '../dist'),
filename: "[name].bundle.js",
},
打包后正常运行:
当我们开始执行构建时,polyfills.bundle.js 文件将会被载入到浏览器中,然后所有代码将正确无误的在浏览器中执行。请注意,以上的这些设定可能还会有所改进,我们只是对于如何解决「将 polyfills 提供给那些需要引入它的用户」这个问题,向你提供一个很棒的想法。
4. 小结
shimming 说到底视为了解决兼容问题,对旧的库或者浏览器进行兼容。shim 是一个库(library),它将一个新的 API 引入到一个旧的环境中,而且仅靠旧的环境中已有的手段实现。polyfill 就是一个用在浏览器 API 上的 shim。我们通常的做法是先检查当前浏览器是否支持某个 API,如果不支持的话就加载对应的 polyfill。然后新旧浏览器就都可以使用这个 API 了。
参考
https://www.webpackjs.com/guides/shimming/
https://webpack.js.org/guides/shimming/