21. Shimming 预置依赖

278 阅读3分钟

相关代码

shimming 预置依赖使用场景:

  1. 一些第三方库可能会创建一些需要导出的全局变量;
  2. 当希望扩展浏览器能力,来支持到更多用户时。

一、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')

打包后,浏览器报错:

image.png

这是因为,当模块运行在 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:

image.png

三、全局 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 在英文中有垫片的意思,意为兜底的东西。在计算机科学中,指的是对未能实现的客户端上进行的"兜底"操作。

来源:zhuanlan.zhihu.com/p/71640183

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 包:

image.png

注意,这种方式优先考虑正确性,而不考虑 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 手动引入。

image.png

去掉 import 手动引入:

// src/index.js

// import '@babel/polyfill'
console.log(Array.from([1, 2, 3], x => x + x))

再次打包,bundle 体积就小了很多:

image.png