本文要搞清楚的几个问题点:
1. 搞清楚webpack loader的规则设置函数的意义;
2. 再反过来看webpack-chain的配置修改逻辑;
3. 再分析现有vue.config.js如何实战处理;
从webpack 的模块说起
Node.js 从最一开始就支持模块化编程。 然而,web 的模块化支持正缓慢到来。 在 web 存在多种支持 JavaScript 模块化的工具,这些工具各有优势和限制。 webpack 基于从这些系统获得的经验教训,并将_模块_的概念应用于项目中的任何文件。
什么是webpack 模块?
-
ES2015 import语句
-
CommonJS require()语句
-
AMD define 和 require 语句
-
css/sass/less 文件中的 @import 语句
-
样式 url() 或 html 文件 <img src=""> 中的图片链接
webpack 除了能支持 javascript 语法的模块,还能支持其他类型,只需要他们有 loader 即可
-
coffee script
-
typescript
-
ESNext(Babel)
-
Sass
-
Less
-
Stylus 等等
webapck loader
loader 让 webpack 能够去处理那些非 JavaScript 文件(webpack 自身只理解 JavaScript)。loader 可以将所有类型的文件转换为 webpack 能够处理的有效模块
loader 有两个目标
-
test 属性,用于标识出应该被对应的 loader 进行转换的某个或某些文件;
-
use 属性,表示进行转换时,应该使用那个 loader;
-
loader 有前置,内联,正常,后置的四种,因此 webpack-chain 中 pre()代表的就是前置 loader
loader 的配置
-
loader 配置是从右到左的(或从下到上)的执行,loader 有 preLoader 和 postLoader 三种;
-
通过 Rule.enforce 可能的值有:“pre” | "post",按照 pre、normal、inline、post
-
不同 loader 的执行细节解读,参考:
plugins 的概念
插件可以理解为,在 webpack 的生命周期中,通过 hook 函数,来插入的一系列的动作的集合,并不能简单的归类为某一个 loader,有可能在多个 loader 中该插件都会被调用。
module.rules
创建模块时,匹配请求的规则数组,这些规则能够修改模块的创建方式。这些规则能够对模块应用 loader,或者修改解析器 parser
每个规则分为三部分: 条件,结果和嵌套规则。
条件有两种输入值:
-
resource 资源文件的绝对路径。
-
属性 test,include,exclude 和 resource 对 resource 进行匹配
-
-
issuer 请求者的文件绝对路径。
-
issuer 对 issuerj 进行匹配
-
结果有两种输入值
-
应用 loader,应用在 resource 上的 loader 数组;
-
loader, options,use 影响 loader;
-
enforce (执行)会影响 loader 种类
-
-
parser,可选,用于为模块创建解析器的选项对象;
-
parser 影响 parser
-
嵌套
略
webpack-chain 的核心
官方文档:
细节解读文档:
-
ChainedMap 类似于 JavaScript 的 Map ,对应 webpack 配置中的对象类型
-
ChainedSet 类似于 JavaScript 的 Set ,对应 webpack 配置中的数组类型
对于任意一个节点配置项,如果是一个对象,那么在 webpack-chain 中就对应一个 ChainedMap,如果这个节点配置项是一个数组,那么在 webpack-chain 中就对应一个 ChainedSet。
也就是说,在 webpack-chain 中,config 是由很多个不同层级的 ChainedMap 和 ChainedSet 组成的。
ChainedMap
clear()
delete(key)
get(key)
getOrCompute(key,fn)
set(key, value)
has(key)
values()
entries()
merge(obj, omit)
batch(handler)
when(condition, whenTruthy, whenFalsy)
ChainedSet
add(value)
prepend(value)
clear()
delete(value)
has(value)
valuse()
merge(arr)
batch(hanlder)
when(condition, whenTruthy, whenFalsy)
pre() 和 add()
关于 webpack-chain 中的 rules 的配置的 pre() 就是前置 loader,关于 add()就是往 include 中添加一个 path
// webpack.config.js
const path = require("path");
module.exports = {
module: {
rules: [
{
test: /\.txt$/i,
use: ["a-loader"],
enforce: "post", // post loader
},
{
test: /\.txt$/i,
use: ["b-loader"], // normal loader
},
{
test: /\.txt$/i,
use: ["c-loader"],
enforce: "pre", // pre loader
},
],
},
};
webpack-chain 的 rule 对象的 pre() 函数
位置:node_modules/webpack-chain/src/Rule.js/Rule
use(name) {
return this.uses.getOrCompute(name, () => new Use(this, name));
}
rule(name) {
return this.rules.getOrCompute(name, () => new Rule(this, name, 'rule'));
}
oneOf(name) {
return this.oneOfs.getOrCompute(
name,
() => new Rule(this, name, 'oneOf'),
);
}
pre() {
return this.enforce('pre'); // 最后还是设置enforce这个关键词的值
}
post() {
return this.enforce('post');
}
add() 实际上是向 include 追加 path
// webpack-chain 中涉及add() rules.include案例
config.module
.rule('compile')
.test(/\.js$/)
.include
.add('src') // 这里的意思src 和 test 文件夹的js文件被引入时执行eslint
.add('test')
.end()
.use('babel')
.loader('babel-loader')
.options({
presets: [
['@babel/preset-env', { modules: false }]
]
});
webpack 原配置形式,include 就是一个数组
resolve 模块解析
配置模块如何解析。例如,当在 ES2015 中调用 import 'lodash',resolve 选项能够对 webpack 查找 'lodash' 的方式去做修改。
具体参考链接:
对 resolve 的掌握,能够明白"@src" 路径的配置方式
const path = require('path');
module.exports = {
//...
module: {
rules: [
{
test: /\.css$/,
include: [
// will include any paths relative to the current directory starting with `app/styles`
// e.g. `app/styles.css`, `app/styles/styles.css`, `app/stylesheet.css`
path.resolve(__dirname, 'app/styles'),
// add an extra slash to only include the content of the directory `vendor/styles/`
path.join(__dirname, 'vendor/styles/'),
],
},
],
},
};
而 include 因为是数组所以是 ChainedSet 类型,ChainedMap 就是继承 TypedChainedSet 的
class Rule<T = Module> extends ChainedMap<T> implements Orderable {
rules: TypedChainedMap<this, Rule<Rule>>;
oneOfs: TypedChainedMap<this, Rule<Rule>>;
uses: TypedChainedMap<this, Use>;
include: TypedChainedSet<this, webpack.Condition>;
exclude: TypedChainedSet<this, webpack.Condition>;
resolve: Resolve<Rule<T>>;
class ChainedMap<Parent> extends __Config.TypedChainedMap<Parent, any> {}
class TypedChainedSet<Parent, Value> extends __Config.TypedChainedSet<
Parent,
Value
> {}
修改插件参数
// 可以使用tap方式,修改插件参数
config
.plugin(name)
.tap(args => newArgs)
// 一个例子
config
.plugin('env')
//使用tag修改参数
.tap(args => [...args, 'SECRET_KEY']);
webpack-chain 都是使用具名的
-
跟 webpack 配置匿名有点不一样,所以 rule("compile"),use("vue-loader"), plugin("clean-plugin") 都是起一个具体的名字,不要太深究跟具体的类有啥关系
-
因此 use("babel").loader('babel-loader') 这里的 loader 才是真实的引入库,可以直接 require,new 之后放在这里
-
plugin('clean').use(CleanPlugin, [['dist'], {root: "/dir"}]) use() 第一个参数才是插件的具体对象,后面是紧跟的参数值
vue.config.js configureWebpack 的讲解
这个配置,有两重功效,当值为一个函数是,函数参数为 config,你可以直接修改 config,也可以返回一个一个 config,这两件事不是互斥的,是互补的,一定要注意;
chainWebpack 的讲解
这个可以将 webpack-chain 发挥的更好,所以如果想对 config 进行更细的处理,建议直接在 chainWebpack 中进行修改。
注意
有些配置,要在 vue.config.js 根目录配置,因为 vue-cli 底层跟 webpack 公用了一些配置,如果直接修改 webpack 的配置,而没有在 vue.config.js 级别进行修改,会造成 vue 打包时和 webpack 编译时不一致,所以对于 vue.config.js 指定的某些公共配置,还是在 vue.config.js 根路径下配置。