FrankKai.github.io
FrankKai.github.io copied to clipboard
实用webpack插件之webpack-chain
通过阅读这篇文章,可以学习到如何使用webpack-chain插件使得前端项目更加工程化。vue-cli3.0的vue.config.js采用webpack-chain插件的方式进行链式配置,所以这个插件非常值得一学。
- 命令式编程->声明式编程
- 为什么要使用webpack-chain插件?
- webpack-plugin使用示例
- 一些常用的webpack-chain 缩写方法
- 引入webpack-chain后如何配置plugin?
- vue-cli3.0引入webpack-chain后,如何配置最常用的DefinePlugin呢?
命令式编程->声明式编程(函数式编程)
使用前的声明式编程:
const path = require('path');
const config = {
entry: './src/index.js',
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'my-first-webpack.bundle.js'
},
module: {
rules: [
{
test: /\.(js)$/,
use: 'babel-loader'
}
]
},
};
module.exports = config;
使用后的声明式编程(函数式编程):
const Config = require('webpack-chain');
const config = new Config();
config.
.entry('index')
.add('src/index.js')
.end()
.output
.path('dist')
filename('my-first-webpack.bundle.js');
config.module
.rule('compile')
.test(/\.js$/)
.use('babel')
.loader('babel-loader')
module.exports = config;
通过对比我们发现webpack-chain有以下优点:
- 声明式编程(函数式编程)更简洁,例如path('dist')这里,非常清爽,无需path.resolve(__dirname, 'dist'),webpack-chain对象主动为我们做这件事。
- 声明式编程(函数式编程)更为清晰,它通过对一个全局上下文的config实例的引用,很清爽的与其他配置分开,并且还指明了运行时期,例如config.module.rule('compile'),让我们对整个配置文件起作用的过程更加明确。
vue-cli3.0和webpack4.0时代,webpack-chain插件是必会插件,因为在vue.config.js中,configureWebpack和chainWebpack中的cb中注入的config对象,其实都是一个webpack-chain实例,如果想对webpack的一些插件的配置做修改,那么就必须先理解webpack-chain。
为什么要使用webpack-chain插件?
- webpack的核心配置基于创建并且修改一个笨重的JavaScript对象。这对于一个独立的项目的配置是OK的,但是如果在项目之间共享这些配置对象并且做一些随后的修改的时候,将会变得非常糟糕,因为你需要非常非常理解那些底层的对象的结构,然后才能做出改变。
- 那么如何在项目间共享webpack的配置呢?webpack-chain,这个插件通过提供一个链式的或者是流畅的API去创建webpack 的配置对象。API的关键部分可以通过用户custom的名字引用,这可以规范化跨项目的configuration对象修改。
webpack-plugin使用示例
- 使用chain API修改配置。
- ChainedMap 与JavaScript Map的操作类似,链式调用和生成配置文件很方便。(Map式配置对象)
- ChainedSet 与JavaScript Set的操作类似,链式调用和生成配置文件很方便。(Set式配置对象)
- ChainedMap和ChainedSet是两种独特的数据结构,或者说是2个专门用于webpack-chain的构造函数,可调用的API不同,与类型无关,比如config.output是ChainedMap类型,但是config.resolve.alias却是ChainedSet类型,二者都是对象的形式。
- 缩写大多数是对ChainedMap类型的缩写,而缩写大多为ChainedSet类型,可以说ChainedSet是ChainedMap的一个子集。
- 每个API调用追踪到一个已经存储的配置。
webpack.core.js
const Config = require('webpack-chain');
const config = new Config();
// 入口出口文件配置
config.
.entry('index')
.add('src/index.js')
.end()
.output
.path('dist')
filename('[name].bundle.js');
// 创建之后可以修改的命名规则
config.module
.rule('lint')
.test(/\.js$/)
.pre()
.include
.add('src')
end()
.use('eslint')
.loader('eslint-loader')
options({
rules: {
semi: 'off'
}
});
config.module
.rule('compile')
.test(/\.js$/)
.include
.add('src')
.add('test')
end()
.use('babel')
.loader('babel-loader')
.options([
presets: [
['@babel/preset-env', {modules: false }]
]
]);
config
.plugin('clean')
.use(cleanPlugin, [['dist'], { root: '/dir' }]);
module.exports = config;
webpack.dev.js
const config = require('./webpack.core');
// ...
module.exports = config.toConfig();
webpack.prod.js
const config = require('./webpack.core');
// ...
module.exports = config.toConfig();
一些常用的webpack-chain 缩写方法
ChainedMap的有些key,可以直接作为方法调用,这些缩写方法也同样会返回原始实例,方便后续的链式调用。
devServer.hot(true);
devServer.set('hot', true);
-
.end()
通过.end()
可以返回到更高层级的上下文,但是仅向上一个层级,并且返回一个mutate后的实例。或者是直接通过config
是获取的顶级上下文。 -
.entry()
config.entryPoints.get()的缩写 。可以通过config.entry(name).add(value)
的.entry()缩写方法配置
,也可以通过config.entryPoints.get(name).add(value)
配置。 -
.add()
这是一个ChainedSet方法,它可以将值添加在Set的尾部。 -
output
这是一个ChainedMap对象,有很多方法,例如path(),filename(),publicPath()等常用的方法。 -
module
也是一个ChinedMap,主要方法为rules(),配置loader的规则,config.module.rule(name).use(name).loader(loader).options(options),或者config.module.rule(name).use(name).tap(options => newOptions) -
plugin
也是ChinedMap,主要是对plugin配置,config.plugin(name).use(WebpackPlugin, args)。重点对plugin做深入学习。
引入webpack-chain后如何配置plugin?
- 新增插件 adding
- 修改参数 modify arguments
- 修改实例 modify instantiation
- 移除插件 removing
- 某个插件前调用插件odering before
- 某个插件后调用插件ordering after
新增插件
config
.plugin(name)
.use(WebpackPlugin, args)
// 直接引入
config
.plugin('hot')
.use(webpack.HotModuleReplacementPlugin);
// 可以通过requrire('')的方式引入插件。
config
.plugin('env')
.use(require.resolve('webpack/lib/EnvironmentPlugin'), [{ 'VAR': false }]);
修改参数
config
.plugin(name)
.tap(args => newArgs)
// 为arguments新增一个'SECRET_KEY'
config
.plugin('env')
.tap(args => [...args, 'SECRET_KEY'])
修改实例
config
.plugin(name)
.init((Plugin, args) => new Plugin(...args));
删除插件
config.plugins.delete(name)
某个插件前调用插件odering before
不能在同一个插件上既使用before又使用after。
config
.plugin(name)
.before(otherName)
// 例子
config
.plugin('html-template')
.use(HtmlWebpackTemplate)
.end()
.plugin('script-ext')
.use(ScriptExtWebpackPlugin)
before('html-template')
某个插件后调用插件ordering after
不能在同一个插件上既使用before又使用after。
config
.plugin(name)
.after(otherName)
// 例子
config
.plugin('html-template')
.use(HtmlWebpackTemplate)
.after('script-ext')
.end()
.plugin('script-ext')
.use(ScriptExtWebpackPlugin)
vue-cli3.0引入webpack-chain后,如何配置最常用的DefinePlugin呢?
vue.config.js
const apiConfig = require('./config/api');
module.exports = {
chainWebpack: config => {
config
.plugin('define')
.tap(args => {
args[0].API_CONFIG = JSON.stringify(apiConfig)
return args
})
}
}
需要注意的是,在vue-cli3.0中,我们不能直接SET NODE_ENV=production或者EXPORT NODE_ENV=production。 因为vue-cli-servive有3种模式,serve默认为development,build为production,若想修改vue-cli-service包中的NODE_ENV,需要通过vue-cli-service serve --mode production进行切换。 就像下面这样:
{
"scripts": {
"dev": "vue-cli-service serve", // mode默认为development
"production": "vue-cli-service serve --mode production",
},
}
注意:我们只能在development, production或者test 3个模式下进行切换,不能引入类似preproduction之类的自定义node环境,但是实际上这3个环境已经足以满足大多数的开发情况。
为什么vue-cli 3.0中的DefinePlugin可以用config.plugin('define')修改入参?
在源码文件base.js中,有下面的代码:
webpackConfig
.plugin('define')
.use(require('webpack/lib/DefinePlugin'), [
resolveClientEnv(options)
])
这一点很关键!我们在vue.config.js中拿到的config.plugin('define'),实际上时vue-service内部创建的webpack.DefinePlugin实例的引用 ! 明确了这一点,我们在以后增强webpack默认插件配置时,需要先到vue-service的源码中寻找一番,看看有没有对应plugin的引用,若有,必须根据vue-service定义的名字直接引用,否则会修改失败。