不确定的动态依赖
无法确定是否应该导入
// index.js
if(Math.random() < 0.5){
require("./other");
}
对于上面这种依赖关系,只有在代码运行过后才能知道index.js会不会使用到other.js,而webpack在打包过程中又不会运行代码
webpack在面对这种情况时,会将other.js中的代码加入到打包结果中,避免代码在实际运行中出现问题
无法确定导入模块的具体模块路径
// index.js
var module = document.getElementById("txt").value;
require("./" + module);
在上面的代码中,模块的文件名来自于外界的输入,index.js可能会使用到工程中的其他任何js文件
默认情况下,webpack在面对这种情况时,会将所有可能会使用到的文件的代码全部加入到打包结果中
原理
实际上,webpack在编译过程中如果遇到了这种无法确定导入模块的具体路径的情况,会临时将require()转换为require.context()
require.context()有三个参数:
-
参数1
目录路径字符串,表示webpack要将哪个目录下的模块的内容添加到打包结果中
-
参数2
布尔值,表示是否需要递归寻找参数1目录的子目录
-
参数3
正则表达式,表示匹配哪些模块,匹配成功的才会被加入到打包结果中
而在上面的例子中,require("./", module)就会被临时转换成下面的形式:
require.context("./", true, /^\.\/.*$/);
之后,webpack会将require.context()进一步被转换为webpack_require(),该webpack_require()不同于直接从require()转换而来的webpack_require(),前者在执行后会返回一个函数(而后者执行后返回的是模块的导出结果),为了方便描述将webpack_require()所返回的函数记为context
context函数需要传入一个相对路径(该路径是以require.context()中的参数1为基准的),调用context函数,就可以得到相应模块的导出内容
此外,context函数还有一个方法keys(),keys()会返回require.context()所匹配到的所有模块的模块相对路径,这些模块路径就可以作为context函数的参数传入
webpack scope hoisting
scope hoisting是webpack的内置优化,它是针对模块的优化,在生产环境打包时会自动开启
在未开启scope hoisting时,webpack会将每个模块中的代码放置在一个函数环境中,这样是为了保证模块的作用域互不干扰,但这样也会带来两个方面的问题:
-
打包结果体积增加
每个模块都会成为一个函数,函数需要定义,函数也有参数,这些都会导致文件中的代码有所增加
-
运行效率降低
每个函数在运行时,都需要创建一个执行上下文,函数运行结束后还需要销毁执行上下文
当开启scope hoisting后,webpack就会把多个模块的代码合并到一个函数环境中执行,而不在是一个模块对应一个函数
在此期间,webpack会按照正确的顺序合并多个模块的代码,同时还会对合并后名称发生冲突的标识符进行处理
将多个模块合并到一个函数的好处有:
- 减少了函数调用,运行效率有一定提升
- 降低了打包体积
并非所有模块都能够被合并到一个函数中,比如被多次引用的模块或动态导入的模块
webpack5更新了什么
清除输出目录
webpack5中内置了清除输出目录的功能,无须开发者安装clean-webpack-plugin,而只需要简单配置即可:
// webpack.config.js
module.exports = {
output: {
clean: true
}
}
top-level-await
webpack5允许在模块的顶级作用域下使用await
// index.js
var resp = await fetch("http://www.mysite.com");
var body = await resp.json();
export default body;
topLevelAwait功能需要手动进行开启,具体配置如下:
// webpack.config.js
module.exports = {
experiments: {
topLevelAwait: true,
}
};
目前,top-level-await还未成为正式标准,因此,对于webpack5而言,该功能是作为experiments发布的,需要在webpack.config.js中通过配置后才能使用
experiments:实验
打包体积优化
webpack5对模块的合并、scope hoisting、tree shaking等处理更加智能
打包缓存
在webpack4中,经常需要使用cache-loader缓存打包结果以优化打包性能
而在webpack5中,默认就已经开启了打包缓存,并且无须安装cache-loader
不过,webpack5默认是将模块的打包结果缓存到内存中,可以通过cache配置进行更改:
const path = require("path");
module.exports = {
cache: {
// 缓存类型,支持memory(内存),filesystem(文件系统)
type: "filesystem",
// 存放缓存的目录路径,仅在type为filesystem时有效
cacheDirectory: path.resolve(__dirname, "node_modules/.cache/webpack"),
},
};
关于
cache的更多配置参考:webpack.docschina.org/configurati…
资源模块
在webpack4中,对于资源型文件需要使用file-loader、url-loader等进行转换,将内容转换成为js代码后才能被webpack处理
由于大部分前端项目都会用到资源型文件,因此webpack5原生就支持了对资源型模块的转换,而无需安装这些loader
具体配置如下:
// webpack.config.js
module.exports = {
output: {
// 设置资源文件在输出目录中的存放位置
assetModuleFilename: "assets/[hash:5].[ext]"
},
module: {
rules: [
// 在模块中导入此类资源时,所得到的是资源在输出目录中的存放路径
// 作用类似于file-loader
{
test: /\.png/,
type: "asset/resource"
},
// 在模块中导入此类资源时,所得到的是data url格式的字符串
// 作用类似于url-loader
{
test: /\.jpg/,
type: "asset/inline"
},
// 在模块中导入此类资源时,所得到的是资源的原始数据
// 作用类似于raw-loader
{
test: /\.txt/,
type: "asset/source"
},
// 对于此类资源,若资源体积小于等于4kB则使用data url,否则将单独形成一个文件到输出目录
{
test: /\.gif/,
type: "asset",
parser: {
dataUrlCondition: {
maxSize: 4 * 1024
}
},
generator: {
// generator.filename用于覆盖output.assetModuleFilename
filename: "gif/[hash:5].[ext]"
}
}
]
}
}