loader 执行范围约束
使用 module.rules.include、module.rules.exclude 等配置项,限定 Loader 的执行范围 :
module.exports = {
// ...
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
use: ["babel-loader", "eslint-loader"],
},
],
},
};
上述配置就排除了node_modules。
exclude 与 include 还支持通过 and/not/or 属性配置组合过滤逻辑:
const path = require("path");
module.exports = {
// ...
module: {
rules: [{
test: /\.js$/,
exclude: {
and: [/node_modules/],
not: [/node_modules\/lodash/]
},
use: ["babel-loader", "eslint-loader"]
}],
}
};
上述配置就排除了node_modules,但是不排除node_modules/lodash。
noParse 跳过文件编译
很多NPM 库已经提前做好打包处理(文件合并、Polyfill、ESM 转 CJS 等),不需要二次编译就可以直接放在浏览器上运行,比如:React 的 node_modules/react/umd/react.production.min.js 文件。
module.exports = {
//...
module: {
noParse: /lodash|react/,
},
};
上述配置或略了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:
// node_module/react/index.js
'use strict';
if (process.env.NODE_ENV === 'production') {
// 此时,真正有效的代码被包含在 react.development.js
module.exports = require('./cjs/react.production.min.js');
} else {
module.exports = require('./cjs/react.development.js');
}
针对这个问题,我们可以先找到适用的代码文件,然后用 resolve.alias 配置项重定向到该文件:
// webpack.config.js
module.exports = {
// ...
module: {
noParse: /react$/,
},
resolve: {
alias: {
react: path.join(
__dirname,
process.env.NODE_ENV === "production"
? "./node_modules/react/cjs/react.production.min.js"
: "./node_modules/react/cjs/react.development.js"
),
},
},
};
watch 监控范围
watch 模式下(通过 npx webpack —watch 命令启动),webpack 会持续监听项目目录中所有代码文件,发生变化时执行 rebuild 命令。
module.exports = {
//...
watchOptions: {
ignored: /node_modules/
},
};
因为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 文件;
resolve.extensions
模块导入语句未携带文件后缀时,如 import ‘./a’ ,Webpack 会遍历 resolve.extensions 项定义的后缀名列表,尝试在 ‘./a’ 路径追加后缀名,搜索对应物理文件。
搜索的默认值是: [‘.js’, ‘.json’, ‘.wasm’]
那就是说,在针对不带后缀名的引入语句时,可能需要执行三次判断逻辑才能完成文件搜索。
所以,为了减少检索时间:
- 修改 resolve.extensions 配置项,减少匹配次数;比如:extensions: [“.jsx”, “.tsx”];
- 代码中尽量补齐文件后缀名;
resolve.modules
当 webpack 遇到 import ‘lodash’ 这样的 npm 包导入语句时:
- 先尝试在当前项目 node_modules 目录搜索资源;
- 如果找不到,则按目录层级尝试逐级向上查找 node_modules 目录;
- 若依然找不到,则最终尝试在全局 node_modules 中搜索;
通常会尽量将 NPM 包安装在有限层级内,因此 Webpack 这一逐层查找的逻辑实用性并不高,可以通过修改 resolve.modules 配置项,主动关闭逐层搜索功能。
const path = require('path');
module.exports = {
//...
resolve: {
// 就在这里找就完事了~
modules: [path.resolve(__dirname, 'node_modules')],
},
};
resolve.mainFiles
resolve.mainFiles 配置项用于定义文件夹默认文件名。
例如对于 import ‘./dir’ 请求,假设 resolve.mainFiles = [‘index’, ‘home’] ,Webpack 会按依次测试 ./dir/index 与 ./dir/home 文件是否存在。
实际项目中应控制 resolve.mainFiles 数组数量,减少匹配次数。