vuepress 有三套 webpack 配置:基础配置、dev 配置、build 配置,看似和普通的一个前端项目也没什么差异,但它使用 webpack-chain 生成配置而不是传统的写死配置。css
相关源码见 createBaseConfig.js、createClientConfig、createServerConfig。前端
引入 webpack-chain 后,咱们全部的 webpack 配置经过一个链式包装器即可生成了:vue
const Config = require('webpack-chain');
const config = new Config();
// 链式生成配置
...
// 导出 webpack 配置对象
export default config.toConfig();
复制代码
在引入详细的示例以前,先让咱们介绍一下 webpack-chain 中内置的两种数据结构:ChainMap、ChainSet。webpack
带链式方法的集合。git
很显然,它和 ES6 的 Set 相似,都拥有键值对,但值得一提的是:它经过链式方法来操做。github
在 webpack-chain 中,属于 ChainedSet 的有 config.entry(name)
、config.resolve.modules
等。web
假如咱们须要指定 webpack 配置的 enrty,咱们只须要这样作:npm
config
.entry('app')
.add('src/index.js')
复制代码
它等价于 webpack 配置对象的这部分:sass
entry: {
app: './src/index.js'
}
复制代码
固然,我想强调的 ChainedSet 真正强大的地方,在于 ChainedSet 提供的内置方法:add(value)、delete(value)、has(value) 等。bash
这能够帮助咱们增删改查整个 webpack 配置中的任意一个部分。
带链式方法的哈希表。
同上,它和 ES6 的 Map 相似,也经过链式方法来操做。
在 webpack-chain 中,属于 ChainedMap 的有 config
、config.resolve
等。
想了解更多 API 用法的读者能够前往文档。
咱们打开源码目录:
一共有三种类:Chainable、ChainedSet 或 ChainedMap、其它。
Chainable 实现了链式调用的功能,它的代码很简洁:
module.exports = class {
constructor(parent) {
this.parent = parent;
}
batch(handler) {
handler(this);
return this;
}
end() {
return this.parent;
}
};
复制代码
最常调用的 end 方法即是来源于这了,它会返回调用链中最前端的那个对象。
好比说,咱们在 vuepress 中有这样一段代码:
config
.use('cache-loader')
.loader('cache-loader')
.options({
cacheDirectory,
cacheIdentifier
})
.end()
.use('babel-loader')
.loader('babel-loader')
.options({
// do not pick local project babel config
babelrc: false,
presets: [
require.resolve('@vue/babel-preset-app')
]
})
复制代码
第八行结尾 end() 处返回的便又是 config
了。
ChainedSet 和 ChainedMap 都继承于 Chainable,其余类大多都继承于 ChainedSet 或 ChainedMap,除了 Use 和 Plugin 类使用 Orderable 这个高阶函数包装了一下(至关于装饰器),目的在于解决在使用 module.use 或 plugin 时调整顺序的问题。有兴趣的读者能够自行翻阅源码~
分红三个配置咱们就不赘述了,毕竟你们日常开发的项目中也可能这样作。在这里我须要特别提一下的地方即是编写函数生成 webpack 配置:
举个例子,在 createBaseConfig 里,有一个这样的函数:
function createCSSRule (lang, test, loader, options) {
const baseRule = config.module.rule(lang).test(test)
const modulesRule = baseRule.oneOf('modules').resourceQuery(/module/)
const normalRule = baseRule.oneOf('normal')
applyLoaders(modulesRule, true)
applyLoaders(normalRule, false)
function applyLoaders (rule, modules) {
if (!isServer) {
if (isProd) {
rule.use('extract-css-loader').loader(CSSExtractPlugin.loader)
} else {
rule.use('vue-style-loader').loader('vue-style-loader')
}
}
rule.use('css-loader')
.loader(isServer ? 'css-loader/locals' : 'css-loader')
.options({
modules,
localIdentName: `[local]_[hash:base64:8]`,
importLoaders: 1,
sourceMap: !isProd
})
rule.use('postcss-loader').loader('postcss-loader').options(Object.assign({
plugins: [require('autoprefixer')],
sourceMap: !isProd
}, siteConfig.postcss))
if (loader) {
rule.use(loader).loader(loader).options(options)
}
}
}
复制代码
它作了这样一件事:对特定的一种样式语言进行 css 模块化和非模块化的处理,顺序是 loader -> postcss-loader -> css-loader -> vue-style-loader 或 extract-css-loader。 使用方式是这样的:
createCSSRule('css', /\.css$/)
createCSSRule('postcss', /\.p(ost)?css$/)
createCSSRule('scss', /\.scss$/, 'sass-loader', siteConfig.scss)
createCSSRule('sass', /\.sass$/, 'sass-loader', Object.assign({ indentedSyntax: true }, siteConfig.sass))
createCSSRule('less', /\.less$/, 'less-loader', siteConfig.less)
createCSSRule('stylus', /\.styl(us)?$/, 'stylus-loader', Object.assign({
preferPathResolver: 'webpack'
}, siteConfig.stylus))
复制代码
是否是一下减小了配置的编写量?并且还很灵活的支持用户自定义 options 和后期的代码变动。
何时应该使用 webpack-chain 呢?毕竟它的引入增长了项目的成本,个人答案是: