第六章 扩展知识

110 阅读5分钟

不确定的动态依赖

无法确定是否应该导入

// 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会将每个模块中的代码放置在一个函数环境中,这样是为了保证模块的作用域互不干扰,但这样也会带来两个方面的问题:

  1. 打包结果体积增加

    每个模块都会成为一个函数,函数需要定义,函数也有参数,这些都会导致文件中的代码有所增加

  2. 运行效率降低

    每个函数在运行时,都需要创建一个执行上下文,函数运行结束后还需要销毁执行上下文

当开启scope hoisting后,webpack就会把多个模块的代码合并到一个函数环境中执行,而不在是一个模块对应一个函数

在此期间,webpack会按照正确的顺序合并多个模块的代码,同时还会对合并后名称发生冲突的标识符进行处理

将多个模块合并到一个函数的好处有:

  1. 减少了函数调用,运行效率有一定提升
  2. 降低了打包体积

并非所有模块都能够被合并到一个函数中,比如被多次引用的模块或动态导入的模块

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]"
                }
            }
        ]
    }
}

更多配置详见:webpack.docschina.org/guides/asse…