37

vue-cli 之 Preset

vue-cli 插件开发指南

TLDR

背景介绍

vue-cli 3 完全推翻了 vue-cli 2 的整体架构设计,所以当你需要给组里定制一份基于 vue-cli 的前端项目初始化模板时,就需要去思考:我该怎么做?

我们要做的事情很简单,就是当别人使用 vue create xxx 命令初始化一个前端项目时,可以从 git repo 去拉取项目初始化信息,好处有两点:

  1. 团队内部所有的新项目都是统一的目录结构代码组织方式,便于维护
  2. 后期可以开发公共插件服务于不同的项目,提高效率

因为 vue-cli 3 才出来不久,所以探索的过程中踩了很多坑,这里就来总结下。

整体设计

vue-cli 官网介绍到:

你可以通过发布 git repo 将一个 preset 分享给其他开发者。这个 repo 应该包含以下文件:

  • preset.json: 包含 preset 数据的主要文件(必需)。
  • generator.js: 一个可以注入或是修改项目中文件的 Generator。
  • prompts.js: 一个可以通过命令行对话为 generator 收集选项的 prompts 文件。
# 从 GitHub repo 使用 preset
vue create --preset username/repo my-project
GitLab 和 BitBucket 也是支持的。如果要从私有 repo 获取,请确保使用 --clone 选项:
vue create --preset gitlab:username/repo --clone my-project
vue create --preset bitbucket:username/repo --clone my-project

是不是看上去很简单,起码我在实践过程中还是遇到了一些问题,接下来就重点讲下。

git repo 参数

上面 --preset 后跟的参数 username/repo 实际是下图中的红框内部分(千万别以为是 git clone 后的地址):

clipboard.png

preset.json 文件

先说一点:当你直接用 vue create xxx 初始化项目时,如果你将初始化信息保存成一个本地模板后,会写入到你系统的 ~/.vuerc 文件中。该文件中的内容其实就是我们接下来需要配置的 present.json

此处直接 show code :

{
  "useConfigFiles": true,

  "cssPreprocessor": "less",

  "plugins": {

    "@vue/cli-plugin-babel": {
      "version": "^3.0.0"
    },

    "@vue/cli-plugin-eslint": {
      "version": "^3.0.0",

      "config": "recommended",

      "lintOn": ["save", "commit"]
    }
  },

  "configs": {

    "vue": {
      "baseUrl": "/",

      "outputDir": "dist",

      "assetsDir": "static",

      "filenameHashing": true,

      "lintOnSave": true,

      "runtimeCompiler": false,

      "transpileDependencies": [],

      "productionSourceMap": false,

      "pages": {
        "index": {
          "entry": "src/main.js",
          "template": "public/index.html",
          "filename": "index.html",
          "title": "首页",
          "chunks": ["chunk-vendors", "chunk-common", "index"]
        }
      },

      "devServer": {
        "open": true,

        "host": "127.0.0.1",

        "https": false,

        "hotOnly": false,

        "proxy": null
      },

      "pwa": {},

      "pluginOptions": {}
    },

    "postcss": {},

    "eslintConfig": {
    }
  },

  "router": true,

  "vuex": false,

  "routerHistoryMode": false
}

其中当 "useConfigFiles": true 时, configs 内的配置信息会直接覆盖初始化后项目中的 vue.config.js

prompts.js 文件

prompts.js 其实就是你在初始化项目时,系统会询问你的配置选项问题,比如你的项目需不需要安装 vuex? 需不需要安装 vue-router?

你的回答会直接影响后面初始化生成的项目文件。

这里最需要注意一点!!!

当你查看官方文档时,第一眼看到就是下图:

clipboard.png

只要你这样写,就一定会 报错 !!!

原因很简单:上图中 prompts.js 的写法是开发基于 vue-cli-service 插件的代码。而当你是要开发项目模板时,正确写法如下:

module.exports = [
  {
    name: "vuex",
    type: "confirm",
    message: `是否需要使用 vuex`,
    default: false
  },
  {
    name: "elementUI",
    type: "confirm",
    message: `element-ui`,
    default: false
  }
];

这一点其实官网也有提到,只是很不容易注意到。

此处再给大家安利下 vue-cli-plugin-vuetify 这个开源插件中 prompts.js 的写法。程序猿嘛,最爱的就是栗子。

generator.js 文件

接下来就是 generator.js,这个文件负责的就是 注入或是修改项目中文件

同样,我还是直接 show code :

module.exports = (api, options, rootOptions) => {
  // 安装一些基础公共库
  api.extendPackage({
    dependencies: {
      "axios": "^0.18.0",
      "lodash": "^4.17.10",
      "keymirror": "^0.1.1"
    },
    devDependencies: {
      "mockjs": "^1.0.1-beta3"
    }
  });

  // 安装 vuex
  if (options.vuex) {
    api.extendPackage({
      dependencies: {
        vuex: '^3.0.1'
      }
    });

    api.render('./template/vuex');
  }

  // 安装 element-ui 库
  if (options.elementUI) {
    api.extendPackage({
      devDependencies: {
        "element-ui": "^2.4.6"
      }
    });
  }

  // 公共基础目录和文件
  api.render('./template/default');

  // 配置文件
  api.render({
    './.eslintrc.js'     : './template/_eslintrc.js',
    './.gitignore'       : './template/_gitignore',
    './.postcssrc.js'    : './template/_postcssrc.js'
  });
}

核心 api:

  • api.extendPackage : 负责给初始化项目中的 package.json 添加额外依赖并安装;
  • api.render : 负责将模板项目中提前定义好的目录和文件拷贝到初始化的项目中;
  • api.postProcessFiles : 负责具体处理模板项目中的文件,关于它可以参考 How to build your own vue-cli 3 pluginGeneratorAPI.js 源码

对于 api.render 需要注意几点:

  1. 拷贝目录的话,直接传地址字符串,render 函数会将你所传目录内的所有文件覆盖初始化项目中 src 目录下的文件(我的测试结果是限于两层目录);
  2. 拷贝文件的话,直接传入一个 object,其中 key 对应初始化项目中的目标位置,value 对应模板项目中的文件位置;
  3. 当你需要创建一个以 . 开头的文件时,模板项目中需要用 _ 替代 .,这点官网有说明;

clipboard.png

最后再说个很重要点,vue-cli 3 在拷贝文件时使用的是 EJS 模板去实现的,所以开发者是可以在任意文件中使用 EJS 语法去做更细粒度的控制。比如我的 main.js:

import Vue from 'vue'
import App from './App.vue'
<%_ if (options.vuex) { _%>
import store from './store'
<%_ } _%>
<%_ if (options.elementUI) { _%>
import ElementUI from 'element-ui';
Vue.use(ElementUI);
<%_ } _%>

// simulation data
import './mock/index';

Vue.config.productionTip = false

new Vue({
  router,
  <%_ if (options.vuex) { _%>
  store,
  <%_ } _%>
  render: h => h(App)
}).$mount('#app')

其中 options.vuexoptions.elementUI 就是用户在处理 prompts.js 中设定的问题的回答值。正是基于这点,我没有再去使用 api.postProcessFiles 这个 api 。

今天就写到这里,后续有补充再写~


神一半的男人
868 声望18 粉丝

Perfect