loader 执行范围约束

使用 module.rules.include、module.rules.exclude 等配置项,限定 Loader 的执行范围 :

  1. module.exports = {
  2. // ...
  3. module: {
  4. rules: [
  5. {
  6. test: /\.js$/,
  7. exclude: /node_modules/,
  8. use: ["babel-loader", "eslint-loader"],
  9. },
  10. ],
  11. },
  12. };

上述配置就排除了node_modules。
exclude 与 include 还支持通过 and/not/or 属性配置组合过滤逻辑:

  1. const path = require("path");
  2. module.exports = {
  3. // ...
  4. module: {
  5. rules: [{
  6. test: /\.js$/,
  7. exclude: {
  8. and: [/node_modules/],
  9. not: [/node_modules\/lodash/]
  10. },
  11. use: ["babel-loader", "eslint-loader"]
  12. }],
  13. }
  14. };

上述配置就排除了node_modules,但是不排除node_modules/lodash。

noParse 跳过文件编译

很多NPM 库已经提前做好打包处理(文件合并、Polyfill、ESM 转 CJS 等),不需要二次编译就可以直接放在浏览器上运行,比如:React 的 node_modules/react/umd/react.production.min.js 文件。

  1. module.exports = {
  2. //...
  3. module: {
  4. noParse: /lodash|react/,
  5. },
  6. };

上述配置或略了react和lodash的编译。更多参考这里:

使用 noParse 时需要注意:

  • 由于跳过了前置的 AST 分析动作,构建过程无法发现文件中可能存在的语法错误,需要到运行(或 Terser 做压缩)时才能发现问题,所以必须确保 noParse 的文件内容正确性;

  • 由于跳过了依赖分析的过程,所以文件中,建议不要包含 import/export/require/define 等模块导入导出语句 —— 换句话说,noParse 文件不能存在对其它文件的依赖,除非运行环境支持这种模块化方案;

  • 由于跳过了内容分析过程,Webpack 无法标记该文件的导出值,也就无法实现 Tree-shaking。

由于上述问题的存在,这个demo实践发现是无法运行的,为什么呢?
因为,react18的指定入口文件node_module/react/index.js 文件,包含了模块导入语句 require,webpack 只会打包这段 index.js 内容,也就造成了产物中实际上并没有真正包含 React:

  1. // node_module/react/index.js
  2. 'use strict';
  3. if (process.env.NODE_ENV === 'production') {
  4. // 此时,真正有效的代码被包含在 react.development.js
  5. module.exports = require('./cjs/react.production.min.js');
  6. } else {
  7. module.exports = require('./cjs/react.development.js');
  8. }

针对这个问题,我们可以先找到适用的代码文件,然后用 resolve.alias 配置项重定向到该文件:

  1. // webpack.config.js
  2. module.exports = {
  3. // ...
  4. module: {
  5. noParse: /react$/,
  6. },
  7. resolve: {
  8. alias: {
  9. react: path.join(
  10. __dirname,
  11. process.env.NODE_ENV === "production"
  12. ? "./node_modules/react/cjs/react.production.min.js"
  13. : "./node_modules/react/cjs/react.development.js"
  14. ),
  15. },
  16. },
  17. };

watch 监控范围

watch 模式下(通过 npx webpack —watch 命令启动),webpack 会持续监听项目目录中所有代码文件,发生变化时执行 rebuild 命令。

  1. module.exports = {
  2. //...
  3. watchOptions: {
  4. ignored: /node_modules/
  5. },
  6. };

因为node_modules不会频繁改动,所以设置 watchOptions.ignored 属性忽略node_modules文件。

resolve 缩小搜索范围

enhanced-resolve:是webpack 默认提供了一套同时兼容 CMD、AMD、ESM 等模块化方案的资源搜索规则。将各种模块导入语句准确定位到模块对应的物理资源路径:

  • import ‘lodash’ :这一类引入 NPM 包的语句会被定位到node_modules/lodash/index.js ;
  • import ‘./a’ :这类不带文件后缀名可能被定位到 ./a.js 文件;
  • import ‘@/a’ :这类别名路径的引用,则可能被定位到 $PROJECT_ROOT/src/a.js 文件;

resovle配置解决的就是文件定位的问题,常用的配置有:

resolve.extensions

模块导入语句未携带文件后缀时,如 import ‘./a’ ,Webpack 会遍历 resolve.extensions 项定义的后缀名列表,尝试在 ‘./a’ 路径追加后缀名,搜索对应物理文件。
搜索的默认值是: [‘.js’, ‘.json’, ‘.wasm’]
那就是说,在针对不带后缀名的引入语句时,可能需要执行三次判断逻辑才能完成文件搜索。
所以,为了减少检索时间:

  • 修改 resolve.extensions 配置项,减少匹配次数;比如:extensions: [“.jsx”, “.tsx”];
  • 代码中尽量补齐文件后缀名;
    resolve.modules
    当 webpack 遇到 import ‘lodash’ 这样的 npm 包导入语句时:
  1. 先尝试在当前项目 node_modules 目录搜索资源;
  2. 如果找不到,则按目录层级尝试逐级向上查找 node_modules 目录;
  3. 若依然找不到,则最终尝试在全局 node_modules 中搜索;

通常会尽量将 NPM 包安装在有限层级内,因此 Webpack 这一逐层查找的逻辑实用性并不高,可以通过修改 resolve.modules 配置项,主动关闭逐层搜索功能。

  1. const path = require('path');
  2. module.exports = {
  3. //...
  4. resolve: {
  5. // 就在这里找就完事了~
  6. modules: [path.resolve(__dirname, 'node_modules')],
  7. },
  8. };

resolve.mainFiles

resolve.mainFiles 配置项用于定义文件夹默认文件名。
例如对于 import ‘./dir’ 请求,假设 resolve.mainFiles = [‘index’, ‘home’] ,Webpack 会按依次测试 ./dir/index 与 ./dir/home 文件是否存在。
实际项目中应控制 resolve.mainFiles 数组数量,减少匹配次数。