vue-cli4定制自己的模板

通过vue-cli插件方式定制自己的模板,cli插件包括以下功能:
1.修改webpack配置
2.与用户进行交互,用户选择所需的一些功能模块
3.扩展package.json
4.创建、修改生成的项目文件
5.添加新的 vue-cli-service 命令

插件文件结构

通常的 CLI 插件目录结构看起来像下面这样:

1
2
3
4
5
6
7
.
├── README.md
├── generator.js 或者generator文件夹 # 修改或者生成项目文件(可选)
├── index.js # service 插件
├── package.json
├── prompts.js # 交互文件(可选)
└── ui.js # Vue UI 集成(可选)

插件命名规范

为了让一个 CLI 插件在 Vue CLI 项目中被正常使用,它必须遵循 vue-cli-plugin-<name> 或者 @scope/vue-cli-plugin-<name> 这样的命名惯例。例如:vue-cli-plugin-template-preset,这样你的插件才能够:

  • @vue/cli-service 发现;
  • 被其他开发者通过搜索发现;
  • 通过 vue add <name> 或者 vue invoke <name> 安装

插件安装

  1. 本地安装

    1
    2
    3
    4
    5
    6
    // 1.如果没有项目就创建个新的
    vue create my-app
    // 2.安装本地插件
    npm install --save-dev file:/full/path/to/your/plugin
    vue invoke vue-cli-plugin-template-preset
  2. 使用preset自动在线安装插件

    1
    vue create --preset vue-cli-plugin-template-preset my-app
  3. 手动在线安装

    1
    2
    3
    vue create my-app
    cd my-app
    vue add vue-cli-plugin-template-preset

Generator修改文件

在 CLI 插件内部,generator 应该放在 generator.js 或者 generator/index.js 文件中。它将在以下两个场景被调用:

  • 项目初始创建期间,CLI 插件被作为项目创建 preset 的一部分被安装时。
  • 当插件在项目创建完成和通过 vue add 或者 vue invoke 单独调用被安装时。

  • 一个 generator 应该export一个接收三个参数的函数

    1
    2
    3
    4
    5
    6
    7
    8
    9
    /**
    * generator/index.js
    * api: https://cli.vuejs.org/dev-guide/generator-api.html
    * options: prompt用户交互结果或者 `~/.vuerc`中的option值
    * rootOptions: `~/.vuerc`中的该preset整个值,相当于下面例子的 foo 对象值
    */
    module.exports = (api, options, rootOptions) => {
    // 使用提供的api或者option进行一些定制
    }

    // ~/.vuerc文件格式
    {

    "presets" : {
      "foo": {
        "plugins": {
          "@vue/cli-plugin-foo": { "option": "bar" } // generator拿到的option值
        }
      }
    }
    

    }

一些常用的generator api

  1. api.render('./template') 渲染EJS模板
  2. api.extendPackage
  3. api.entryFile
  4. api.afterInvoke
  5. api.chainWebpack
  6. api.registerCommand
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// generator.js
module.exports.hooks = (api) => {
api.afterInvoke(() => {
const { EOL } = require('os')
const fs = require('fs')
const contentMain = fs.readFileSync(api.resolve(api.entryFile), { encoding: 'utf-8' })
const lines = contentMain.split(/\r?\n/g)
const renderIndex = lines.findIndex(line => line.match(/render/))
lines[renderIndex] += `${EOL} router,`
fs.writeFileSync(api.entryFile, lines.join(EOL), { encoding: 'utf-8' })
})
}

模板文件生成

  1. 新文件:

    1
    2
    3
    4
    // 放到template文件夹下,然后用api.render解析
    module.exports = api => {
    api.render('./template')
    }
  2. 编辑已存在的文件

    • 方式一:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      ---
      extend: '@vue/cli-service/generator/template/src/App.vue' // 目标文件
      replace: !!js/regexp /<script>[^]*?<\/script>/ // 正则匹配内容
      ---
      <script>
      export default {
      // Replace default script
      }
      </script>
    • 方式二:多处替换

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      ---
      extend: '@vue/cli-service/generator/template/src/App.vue'
      replace:
      - !!js/regexp /Welcome to Your Vue\.js App/
      - !!js/regexp /<script>[^]*?<\/script>
      ---
      <%# REPLACE %>
      替换欢迎信息
      <%# END_REPLACE %>
      <%# REPLACE %>
      <script>
      export default {
      // 替换默认脚本
      }
      </script>
      <%# END_REPLACE %>

3.扩展package.json

1
2
3
4
5
6
7
module.exports = api => {
api.extendPackage({
dependencies: {
'vue-router-layout': '^0.1.2'
}
})
}

4.主文件注入

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
module.exports.hooks = (api) => {
api.afterInvoke(() => {
const { EOL } = require('os')
const fs = require('fs')
// 读取文件
const contentMain = fs.readFileSync(api.resolve(api.entryFile), { encoding: 'utf-8' })
// 文件内容分行
const lines = contentMain.split(/\r?\n/g)
// 查找到render那一行
const renderIndex = lines.findIndex(line => line.match(/render/))
lines[renderIndex] += `${EOL} router,`
// 写入
fs.writeFileSync(api.entryFile, lines.join(EOL), { encoding: 'utf-8' })
})
}

5.修改webpack配置

对话

所有的对话逻辑都存储在 prompts.js 文件中,用于用户选择所需的插件。所有对话的答案都会传给generator的option

rootOptions

  • 对话的配置项(所有配置项

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    // prompts.js
    module.exports = [
    {
    name: `addExampleRoutes`, // option的key
    type: 'confirm', // 选项类型:checkbox、confirm
    message: 'Add example routes?', // 对话显示的提示信息
    default: false, // 默认值
    validate: input => !!input, // 校验用户的答案,true则通过,否则返回错误提示字符串
    }
    ]

参考:

1.插件开发指南 | Vue CLI

热评文章