前言
不久前组内有大佬发布了一个 vue-cli3 的 dll 包,作为一个在 vue 项目内摸爬滚打的萌新,是时候该学习点儿新的技术了,于是在闲暇之余,我拷贝了一份代码,同时研究该如何从“零”开始编写一个 dll 包(该部分以 webpack 的 dllPlugin 作为例子)。
了解文档
通篇读完官网的文档,因为知识点比较多,并且没有详细的例子(指“傻瓜式教学式”),因此开发这第三方包的学习成本还是有一些的,特别是 webpack-chain 和 node 的部分知识,这里记录总结了一些关键的点。
插件命名
最开始命名文件夹时我并没有使用 vue-cli-plugin-
作为文件名前缀,结果可想而知,vue invoke 一直提示找不到该包的信息。于是我去看了看源码…… 在 @vue/cli/lib/invoke.js
内,其有一个关键的获取包 Id 的方法 resolvePluginId
,该方法在 @vue/cli-shared-utils/lib/pluginResolution.js
,源码如下:
exports.resolvePluginId = id => {
// already full id
// e.g. vue-cli-plugin-foo, @vue/cli-plugin-foo, @bar/vue-cli-plugin-foo
if (pluginRE.test(id)) {
return id
}
// scoped short
// e.g. @vue/foo, @bar/foo
if (id.charAt(0) === '@') {
const scopeMatch = id.match(scopeRE)
if (scopeMatch) {
const scope = scopeMatch[0]
const shortId = id.replace(scopeRE, '')
return `${scope}${scope === '@vue/' ? `` : `vue-`}cli-plugin-${shortId}`
}
}
// default short
// e.g. foo
return `vue-cli-plugin-${id}`
}
复制代码
很明显,使用 vue invoke 时其只会寻找含有 vue-cli-plugin-
作为前缀的包,官网内在文档的最后部分有做对应的说明(这个是后来才看到的),原文如下:
为了让一个 CLI 插件能够被其它开发者使用,你必须遵循
vue-cli-plugin-<name>
的命名约定将其发布到 npm 上。
因此 package.json
的 name
字段符合规则即可。
creator 和 service
官网开篇就介绍了两个主要的部分:@vue/cli
和 @vue/cli-service
,首先是 @vue/cli
部分,这里介绍了插件的目录结构,因此我们可以根据此来搭一个插件框架:
vue-cli-plugin-xxx
├── README.md
├── generator
| └── index.js
├── index.js
├── package.json
├── prompts
| └── index.js
├── service
| ├── config-file.js
| └── regist-command.js
└── yarn.lock
复制代码
接下来就详细分析一下各部分的作用。
generator
文档分析
文档中有提到,插件内的 generator 将会在两种场景下被调用:
- 在一个项目的初始化创建过程中,如果 CLI 插件作为项目创建 preset 的一部分被安装
- 插件在项目创建好之后通过
vue invoke
独立调用时被安装
由于开发的第三方插件使用场景多数在于更改已安装的项目配置,preset
使用场景不是很多(创建项目时一般还是手动配置,大多数情况不会选择去生成一个 ~/.vuerc),因此这边仅处理使用手动调用 generator 的情况。
触发 generator
的方法有两种:
vue invoke
vue add
下面就简单介绍一下这两个命令的区别。
vue invoke 指令
此指令的适用情况为已经通过 yarn 或者 npm 将包安装至项目内,此时仅需要调用 vue invoke 即可。
注意:这里的 packageName 为不包含
vue-cli-plugin-
部分的剩余包名,比如:发布的包名为vue-cli-plugin-xxx
,那么此时使用命令即vue invoke xxx
vue add
此指令的使用情况为项目内还没有安装对应的包,使用方式同 vue invoke
注:如果包的源不对的话,请自己在后面加上包所在的 npm 源地址( --registry )
内容编写
分析了这么多,重点还是 generator 内我们应该写点什么,它影响的是什么。好了,让我们来继续看文档(● ˃̶͈̀ロ˂̶͈́)੭ꠥ⁾⁾
generator 有三个参数,这里就不细赘了,因为这里不关注 preset 的配置,所以对我们来说,有用的部分就只有第一个参数 api。首先我们需要改动的部分便是项目内的 package.json 了,使用方法 extendPackage
即可,例子如下:
// 修改 `package.json` 里的字段
// vue 部分的内容可以不要
module.exports = (api, options, rootOptions) => {
// 修改 `package.json` 里的字段
api.extendPackage({
scripts: {
test: 'vue-cli-service test'
},
vue: {
pluginOptions: {
test: {
// 需要预打包的部分
vendors: [],
// 输出文件名
outputName: 'vendor.dll.js',
// 输出地址
outputPath: './public/vendor',
// 是否调用 cleanWebpackPlugin
cleanCache: true
}
}
}
});
}
复制代码
关于此处添加的 vue
字段在 invoke 后会自动补充至 vue.config.js 或者 package.json 内。
如果你配置了 promots 并且需要该部分的内容,那么可以使用第二个 options 参数去获取配置的内容。(配置 .vuerc 的方法没有尝试,因为解构 + 默认值 + prompts 已经足够了)
如果需要配置模板方面的参考官方源码,感觉配合 prompts 写个模板插件也不错。
友情提示:render() 函数内为你的 template 模板基于当前文件夹所在的路径。
prompts
该部分其实在这个项目内并没有涉及,但还是要提一下。官方文档对于此部分在内建插件有详细的说明(官方插件),第三方插件提到过一点:
这个文件应该导出一个用于 Inquirer.js 的问题的数组。这些被解析的答案对象会作为选项被传递给插件的 generator。
因此,如果需要的情况下,我们得通过数组的形式编写问答。例如:
module.exports = [
{
name: 'entry',
message: "What's the output file's name?",
type: 'input',
default: 'vendor'
}
]
复制代码
此部分的配置结果会在 generator 部分的第二个参数捕捉到。
service
这部分就是配置的重点了,还是根据官网来吧,官网有提到 3 个命令: chainWebpack
、configureWebpack
、registerCommand
。直接更改原有项目的配置并不是很好(除非你很有信心),因此我们可以使用 configureWebpack
来合并变更。
这部分总的来说做三件事:
- 更改用户的 webpack 配置文件(也就是 vue.config.js)
- 向 cli-service 内注册指令
- 为注册的指令指定模式
这里从简单的部分开始一一说明吧~(顺序:3、1、2)
指定模式
嗯,这个最简单了,毕竟官网文档内有,理由也就不赘述了,代码如下:
module.exports.defaultModes = {
<your direct>: <target mode>
}
复制代码
其中 your direct
部分即先前的 generator 部分注册的 script 脚本内,在 vue-cli-service
后面的那个指,比如,先前写的是 test
,那么这里注册的指令也是 direct
,mode 就根据实际情况处理即可,一般使用 production
生产模式就没啥问题。
更改用户的配置文件
这里就需要有 webpack-chain 的知识了。先看看 pluginAPI 内有些啥(传送门),可能会用到的一些方法包括:
- getCwd:获取当前的工作目录
- resolve:相当于 path.resolve
- registerCommand:注册指令(有三个参数!)
- chainWebpack:链式调用 webpack
- configureWebpack:用于合并 webpack
- resolveChainableWebpackConfig:用于解析 webpack
此处我们使用 configureWebpack
来更改配置,同时使用 registerCommand
来注册我们的命令。这里刚好对应的就是我们的两个文件 config-file
和 regist-command.js
了,对应开发即可。
configureWebpack
参考源码的写法,我们可以通过 options 参数获取 invoke 生成的 vue.config.js
里面的 pluginOptions
字段内的对应配置内容,配合 api.configureWebpack
来注入我们的 webpack 配置。粗略的写法为:
api.configureWebpack(config => {
// 增一个 plugins
config.plugins.push(
/* plugin 配置 */
)
});
复制代码
registerCommand
查看源码的写法,发现参数有三个,分别是:
- api: pluginAPI 实例
- options: 用来添加配置说明
- fn: 回调函数,用于触发执行的内容(比如运行一个 webpack 配置)
官方的写法如下,我们可以据此模仿
api.registerCommand(
"test",
{
description: "此为指令的说明", // 指令意义
usage: "vue-cli-service test", // 命令怎么用
options: {
/* 参数说明 */
}
},
async args => {
/* 可以写个 chain-webpack 然后调用,或者干点其它的 */
}
);
复制代码
部分问题总结
1. demo 内的 dll 打包位置问题
使用默认的打包位置: public/vendor
会产生一个问题,就是 build 文件内会包含此 vendor 文件夹,我们注入的一些配置不生效。
解决方法:将默认的 public/vendor
打包位置改改,只要不是生成在 public 文件夹内的都没问题,可看 vue-cli 官网的 public 部分的解释。
2. webpack 函数
目前第二个回调参数的使用场景不是很明确(其实不用回调也可以,外层关闭对应的 log 即可)
3. configureWebpack 只能用 push?
我尝试过自己新创建一个 webpack-chain 然后返回(源码上好像会对返回值进行判断,如果有那么会调用 merge 方法),按理来说返回一个 config.toConfig() 应该 没问题,但是行不通,目前仍然用的是 push 方法