追求卓越一诺千金

蓝蓝设计,2011年成立,主创清华团队,专注软件和互联网ui设计开发。擅长企业信息化管理、监控、大数据软件UIUE咨询和设计开发服务。立足UI,好好学习,天天进步!


vue-cli3插件初体验

2019-9-5 释然 前端及开发文章及欣赏


vue-cli3发布自2018年8月,距离现在还不是特别久,最好搭建项目刚好用到,所以写下这篇文章,记录一下踩坑经历。

vue的作者说过,vue-cli的本质是模

版的拉取,太多的配置导致了模版的难以维护,所以重构后的版本提供了插件的功能,一个插件对应一个功能,这样避免了多个模版,也使得cli维护性得到提高,这也是本篇文章的核心介绍内容。

我对cli3的理解是,一方面扩展了cli2的核心能力 ,一方面封装了webpack逻辑和配置。在过去要去做一个cli,通常需要阅读cli2的代码,cli2的核心模块分别是 generator,prompts,template,git-repo,并用同样的方法去做一个cli,这样其实成本不小,cli3的插件就是提供了这样一种能力,你用他的规则,去做一个npm包,并发到仓库,就可以获得这种能力。

首先,先介绍一下vue-cli3,他的发布带了哪些新功能呢?我总结一下,大概有以下5点:

1.功能丰富。这点相信大家都很好理解,他实在太好用了,模版里包含了大多数业界比较新的功能,可以一键集成typescripts等类型定义工具, eslint/tslint等语法检验工具,风格规范,prettier,husky等格式化工具,还有postcss、pwa、vue-cli-server等等。

2.提高封装性和扩展性。这点指的是vue-cli-server,在过去webpack的逻辑和配置在项目里独立维护,各个项目之间就像孤岛,vue-cli-server就像一个纽带,连接起了各个项目,为项目升级webpack提供便利性,并且通过一份配置,提供个性化配置。

这里顺带扯一下vue.config.js, 这个东西就是用来个性化配置webpack的,他提供了很多的配置项,具体可以看官方文档:

https://cli.vuejs.org/zh/config/

我们挑几个常用的来讲:

configureWebpack,这个几乎是用的比较多的,简单的方法,可以通过配置的方式配置,如下所示:

 
	
  1. module.exports = {
  2. configureWebpack: {
  3. plugins: [
  4. new MyAwesomeWebpackPlugin()
  5. ]
  6. }
  7. }复制代码


这份配置,最终会被合并到原来的配置里,不会覆盖。

复杂的方法,可以往这个字段传一个函数,如下所示:

 
  1. module.exports = {
  2. configureWebpack: config => {
  3. if (process.env.NODE_ENV === 'production') {
  4. // 为生产环境修改配置...
  5. } else {
  6. // 为开发环境修改配置...
  7. }
  8. }
  9. }复制代码

这样就可以在一个函数里做一些简单的逻辑,config是webpack config,你可以直接修改config对象,也可以返回一个对象,这个对象最终也会被合并会webpack对象。

第三种方式,是通过webpack-chain来链式调用,代码如下所示:

 
  1. module.exports = {
  2. chainWebpack: config => {
  3. config.module
  4. .rule('vue')
  5. .use('vue-loader')
  6. .loader('vue-loader')
  7. .tap(options => {
  8. // 修改它的选项...
  9. return options
  10. })
  11. }
  12. }复制代码

3.提供图形化管理界面,最所周知,gui易懂,cli高效,vue在降低学习门槛这方面,已经是非常好了。

通过vue ui指令,可以查看编译各个模块的情况,模块依赖情况,插件的情况,非常便于管理。

4.便捷性。vue-cli-server不仅支持项目的编译,也支持单文件的编译,具体的方法可以看官网介绍。

5.提供了cli的核心能力,也就是问询,模版渲染,文件生成这几大核心功能。具体的示意图可以看如下:


以上是cli2的核心模块,需要引用hbs,inquirer.js,metalsmit等基本模块,cli3的插件机制基本帮我们封装好了,我们只需要根据插件的规范,就可以获取这种能力。

由于有的读者可能对cli2的流程比较陌生,所以我想简单描述一下cli2的工作流程,如下图所示:


首先,cli2提供init指令,执行这个指令,会去远程拿模版文件,并拉到本地用户目录的.vue-template的文件夹,然后通过meta里问题,去问你,然后生成最终模版到你执行命令的目录。

说完vue-cli2,我们来看看vue-cli3的插件是怎么样的?

首先根据插件的位置,我们分成了cli插件,和service插件。cli的插件有完整的开发目录,所能做的事情也比较多一点,service插件是一份js文件,导出一个函数。

cli插件的目录如下所示:


具体的用发可以在官网了解到:

https://cli.vuejs.org/zh/dev-guide/plugin-dev.html#cli-%E6%8F%92%E4%BB%B6

那么他们是怎么工作的呢?

cli的代码主要在@vue/cli 下面,他的大概的流程如下图所示:


@vue/cli/bin/vue.js:

 
  1. program
  2. .command('add <plugin> [pluginOptions]')
  3. .description('install a plugin and invoke its generator in an already created project')
  4. .option('--registry <url>', 'Use specified npm registry when installing dependencies (only for npm)')
  5. .allowUnknownOption()
  6. .action((plugin) => {
  7. require('../lib/add')(plugin, minimist(process.argv.slice(3)))
  8. })
  9. program
  10. .command('invoke <plugin> [pluginOptions]')
  11. .description('invoke the generator of a plugin in an already created project')
  12. .option('--registry <url>', 'Use specified npm registry when installing dependencies (only for npm)')
  13. .allowUnknownOption()
  14. .action((plugin) => {
  15. require('../lib/invoke')(plugin, minimist(process.argv.slice(3)))
  16. })复制代码

首先,执行vue指令,会执行@vue/cli/bin/vue.js,这里定义了vue add , vue invoke,vue build,vue serve,等指令,可以看出,add指令其实是包含invoke指令的,add指令主要是安装一个包,并且触发generator.js,invoke主要是触发generator.js。

然后再来看@vue/cli/lib/add.js,

 
  1. const packageManager = loadOptions().packageManager || (hasProjectYarn(context) ? 'yarn' : 'npm')
  2. await installPackage(context, packageManager, options.registry, packageName)复制代码
 
  1. const generatorPath = resolveModule(`${packageName}/generator`, context)
  2. if (generatorPath) {
  3. invoke(pluginName, options, context)
  4. } else {
  5. log(`Plugin ${packageName} does not have a generator to invoke`)
  6. }复制代码

add.js主要安装包,然后执行invoke模块,我们再看看invoke模块做了什么。

@vue/cli/lib/invoke.js

 
	
  1. const generator = new Generator(context, {
  2. pkg,
  3. plugins: [plugin],
  4. files: await readFiles(context),
  5. completeCbs: createCompleteCbs,
  6. invoking: true
  7. })复制代码

invoke里主要实例化generator类,然后把你的插件当成参数传给类,generator类算是vue-cli的核心类了。

@vue/cli/lib/generator.js

 
  1. plugins.forEach(({ id, apply, options }) => {
  2. const api = new GeneratorAPI(id, this, options, rootOptions)
  3. apply(api, options, rootOptions, invoking)
  4. })复制代码

这个类主要负责执行你的插件,然后把generatorapi作为参数传入插件的generator.js导出的函数。

具体的vue-cli插件的开发是怎么样的呢,我写了一个demo,用在模拟多项目的ci模版管理,通常每个项目都有一份.gitlab-ci.yml模版,所以我们一般可以抽出一个公共的ci模版来管理,这里我用cli插件来管理,真正的项目可能不具备可行性,这里我只是用来写一个例子。



目录结构大概如上所示,执行vue add foo后,就会出现propmts对话框,然后选择答案后,就会根据配置生成模版到你的根目录下。


_gitlab-ci.yml ,根据问题选择是否用私有npm仓库:

 
  1. script:
  2. <%_ if (options.config === 'npm') { _%>
  3. - nrm add companynpm http://xxx.y.cn:XXXXX/
  4. - nrm use companynpm
  5. <%_ } _%>复制代码

prompts.js,主要一些问题的设计:

 
  1. module.exports = [
  2. {
  3. name: 'config',
  4. type: 'list',
  5. message: `是否需要切换内部源:`,
  6. choices: [
  7. {
  8. name: '需要',
  9. value: 'npm',
  10. short: ''
  11. },
  12. {
  13. name: '不需要',
  14. value: 'npm',
  15. short: ''
  16. }
  17. ]
  18. }
  19. ]复制代码

generator.js 这个模块很简单,直接渲染模版

 
  1. module.exports = (api, options, rootOptions) => {
  2. // 复制并用 ejs 渲染 `./template` 内所有的文件
  3. api.render('./template')
  4. }复制代码

service插件主要放在项目本地,是一份js代码,然后导出一个函数,通过package.json配置指向这个js文件的路径,


service主要依赖的代码在@vue/cli-service里,首先先执行@vue/cli-service/bin/vue-cli-service.js文件,


 
  1. const Service = require('../lib/Service')
  2. const service = new Service(process.env.VUE_CLI_CONTEXT || process.cwd())
  3. .....
  4. service.run(command, args, rawArgv).catch(err => {
  5. error(err)
  6. process.exit(1)
  7. })复制代码

该文件实例化Service类,这个类是service插件的核心类,然后再执行run方法。

再来看看@vue/cli-service/lib/Service.js的代码:

首先构造函数执行resolvePlugin,把官方提供的插件和项目里的插件都加载进来,

 
  1. constructor (context, { plugins, pkg, inlineOptions, useBuiltIn } = {}) {
  2. this.plugins = this.resolvePlugins(plugins, useBuiltIn)
  3. }复制代码

resolvePlugin这个函数主要在这里引入本地的插件:

 
  1. resolvePlugins (inlinePlugins, useBuiltIn) {
  2. // Local plugins
  3. if (this.pkg.vuePlugins && this.pkg.vuePlugins.service) {
  4. const files = this.pkg.vuePlugins.service
  5. if (!Array.isArray(files)) {
  6. throw new Error(`Invalid type for option 'vuePlugins.service', expected 'array' but got ${typeof files}.`)
  7. }
  8. plugins = plugins.concat(files.map(file => ({
  9. id: `local:${file}`,
  10. apply: loadModule(file, this.pkgContext)
  11. })))
  12. }
  13. return plugins
  14. }复制代码

run方法又会执行init方法,在该方法内部执行插件:

 
  1. init (mode = process.env.VUE_CLI_MODE) {
  2. // apply plugins.
  3. this.plugins.forEach(({ id, apply }) => {
  4. apply(new PluginAPI(id, this), this.projectOptions)
  5. }
  6. }复制代码


那么service插件要如何来实践,我们来看如下的例子:

首先在package.json配置一下,指向本地的插件my-service.js

 
  1. "vuePlugins": {
  2. "service": [
  3. "my-service.js"
  4. ]
  5. }复制代码

my-service.js的代码如下所示:

 
  1. const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
  2. const webpack = require('webpack');
  3. module.exports = (api, projectOptions) => {
  4. api.registerCommand(
  5. 'analyze',
  6. {
  7. description: 'start analyze server',
  8. },
  9. (args) => {
  10. // 注册 `vue-cli-service analyze`
  11. let options = projectOptions.pluginOptions.demoOptions
  12. console.log(options);
  13. // resolve webpack config
  14. const webpackConfig = api.resolveWebpackConfig();
  15. webpackConfig.plugins.push(new BundleAnalyzerPlugin());
  16. webpack(webpackConfig,(err,stats)=>{
  17. if(!err) console.log('打包成功')
  18. })
  19. },
  20. );
  21. };复制代码

该插件主要扩展vue-cli-service的指令,加了analyze指令,这个指令主要会向webpack的配置增加一个BundleAnalyzerPlugin的插件,用来分析包的大小,当然,这里也是没有太大的现实可行性的,vue-cli 提供了vue ui的Gui界面让你看到打包后各个模块的大小,或者cli的命令,vue-cli-service build --report,也能生一个报告,效果也是一样。


至此,vue-cli和service插件的使用和实现都介绍完了,如果有哪里写的不到位,希望各位大神能提出指正

标签: vue-cli3插件初体验 « 全球都在用的Booking,是如何做设计改版升级的? | 破熵而行-驱动产品增长»


订阅Rss