shimming 预置依赖使用场景:
- 一些第三方库可能会创建一些需要导出的全局变量;
- 当希望扩展浏览器能力,来支持到更多用户时。
一、Shimming 预置全局变量
一般来说,我们是在一个文件里面单独引入一个第三方库,如:
// src/index.js
import _ from 'lodash'
console.log(_.join(['hello', 'webpack'], ' '))
如果可以把 _ 变成一个全局变量,那么我们就可以在所有文件中直接使用 _,而不用每个文件都单独引入 lodash。
使用 Webpack 自带的 ProvidePlugin 插件可以做到这一点:
// webpack.config.js
const webpack = require('webpack')
module.exports = {
plugins: [
new webpack.ProvidePlugin({
_: 'lodash' // 把 lodash 变为全局变量依赖,且取名为 _
})
],
}
现在就可以在其他文件中直接使用 _ 这个全局变量:
// src/test.js
console.log(_.join(['hello', 'world'], '-')) // 直接使用 _ 变量
// src/index.js
import './test'
// import _ from 'lodash'
console.log(_.join(['hello', 'webpack'], ' ')) // 直接使用 _ 变量
打包编译后,发现 lodash 仍然可以正常工作。
也可以使用数组语法,单独引入 lodash 中的某个方法,就像按需加载一样:
// webpack.config.js
const webpack = require('webpack')
module.exports = {
plugins: [
new webpack.ProvidePlugin({
join: ['lodash', 'join'] // 将 lodash 中的 join 方法注册为全局变量 join
})
],
}
这样就可以在文件中直接使用 join 方法:
// src/test.js
console.log(join(['hello', 'world'], '-'))
// src/index.js
import './test'
console.log(join(['hello', 'webpack'], ' '))
再次打包编译,发现代码仍然可以正常工作。
这样就能很好的与 Tree shaking 配合,将 lodash 中没有用到的方法移除掉。
二、细颗粒度 Shimming
当在某个模块中直接使用 this,如:
// src/index.js
console.log('this:', this)
this.alert('hello webpack')
打包后,浏览器报错:
这是因为,当模块运行在 CommonJS 上下文中,模块中的 this 指向的就不是 window 而是 module.exports。
这种情况下,可以通过使用 imports-loader 覆盖 this 指向:
npm i imports-loader -D
// webpack.config.js
module.exports = {
module: {
rules: [
{
// 使用 imports-loader 将 ./src/index.js 包装成 window
test: require.resolve('./src/index.js'),
use: 'imports-loader?wrapper=window'
},
]
}
}
再次打包,在浏览器查看,发现 this 已经变成了 window,并且正常执行了 alert:
三、全局 Exports
假设某个第三方库创建出一个全局变量,我们却不知道它是如何导出的,如:
// src/global.js
const file = 'example.txt'
const helpers = {
test: function () {
console.log('test something')
},
parse: function () {
console.log('parse something')
}
}
这种情况,我们可以使用 exports-loader 来处理:
npm i exports-loader -D
// webpack.config.js
module.exports = {
module: {
rules: [
{
test: require.resolve('./src/global.js'),
use: 'exports-loader?type=commonjs&exports=file,multiple|helpers.parse|parse'
}
]
}
}
此时就可以在其他文件中正常引入第三方库:
// src/index.js
const { file, parse } = require('./global')
console.log(file)
parse()
四、Polyfills
polyfill 在英文中有垫片的意思,意为兜底的东西。在计算机科学中,指的是对未能实现的客户端上进行的"兜底"操作。
1. 加载 Polyfills
下面以 @babel/polyfill 为例:
npm install --save @babel/polyfill
// src/index.js
import '@babel/polyfill'
console.log(Array.from([1, 2, 3], x => x + x)) // Array.from 方法不是所有的浏览器都支持的
打包后发现 bundle 体积很大,因为使用 import 引入了整个 polyfill 包:
注意,这种方式优先考虑正确性,而不考虑 bundle 体积大小。为了安全和可靠,polyfill/shim 必须运行于所有其他代码之前,而且需要同步加载,或者说,需要在所有 polyfill/shim 加载之后,再去加载所有应用程序代码。
2. 优化 Polyfills
不建议使用 import @babel/polyfilll,因为这种方式会全局引入整个 polyfill 包,不但包的体积大,而且还会污染全局环境。
可参考如下配置方法:
npm i babel-loader @babel/core @babel/preset-env core-js@3 -D
// webpack.config.js
module.exports = {
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader',
options: {
presets: [
[
'@babel/preset-env',
{
targets: [
'last 1 version',
'> 1%'
],
useBuiltIns: 'usage',
corejs: 3
}
]
]
}
}
}
]
}
}
当设置了 useBuiltIns: 'usage' 时,polyfills 会自动引入,不需要再使用 import 手动引入。
去掉 import 手动引入:
// src/index.js
// import '@babel/polyfill'
console.log(Array.from([1, 2, 3], x => x + x))
再次打包,bundle 体积就小了很多: