我美好的假期终于结束了,习惯了写BUG、运动,写BUG、看看综艺还是游戏直播的生活。
明天又要回去接受职场pua了,希望我今年找份好工作。
距离上一篇vue cli
源码分析已经过去了一周了哈哈哈(忙着整理我的UI库)。
上一节中提到了vue create
命令中调用了插件的generate
方法去新增依赖啊创建模板等,加上下一个研究的源码命令是vue add
,所以先来学习一下如何进行vue cli
相关插件的开发。
这次先站在前人的肩膀上去学,后期我再搞一个这样的插件给我的UI库!。
实际案例
在我们的vue
项目中使用element
时,有时候需要按需引入,此时我们还需要根据官网各种配置一波再自己验证一遍,然而使用vue add element
进行安装插件时,这个插件很智能地询问你是要全局安装啊还是按需引入啊,我都能帮你配置好哦。
vue-cli-plugin-element
这个插件就是帮你安装包的同时,还能根据你的需要初始化一些配置(比如按需引用时加入babel-plugin-component
并帮你配置好其他文件等)。
(还会询问你使用哪种语言)旋转按需引入之后,项目中的文件发生了改变,尤其是babel.config.js
配置好了。
CLI插件的作用
参考一下官网 插件开发指南
- 修改
webpack
配置; - 添加新的 vue-cli-service 命令 ,比如@vue/cli-plugin-unit-jest
添加了
test:unit` 命令,允许开发者运行单元测试; - 扩展
package.json
,比如vue add element时旋转按需引用时还新增了
babel-plugin-component`这个依赖包; - 在项目中新增、修改文件,比如
vue add element
时修改了App.vue
文件; - 提示用户选择一个特定的选项, 比如
vue add element
询问引用方式。
开发注意事项
命名
必须遵循 vue-cli-plugin-<name>
或者 @scope/vue-cli-plugin-<name>
。
文件目录
├── README.md # 说明
├── generator.js 或者 generator/index.js # generator(可选)
├── index.js # service 插件(入口文件)
├── package.json # 插件包一定要有package.json(便于发布)
├── prompts.js # prompt 文件(可选)
└── ui.js # Vue UI 集成(可选)
vue-cli-plugin-element解读
index.js
这个文件超级简单:
module.exports = () => {}
prompts.js
还记得Inquirer
吗,可以给用户提供好看的交互界面,这个文件主要是询问用户一些需求:
const localeList = require('./lang.js')
module.exports = [
{
type: 'list',
name: 'import',
message: 'How do you want to import Element?',
choices: [
{ name: 'Fully import', value: 'full' },
{ name: 'Import on demand', value: 'partial' }
],
default: 'full',
},
{
when: answers => answers.import === 'full',
type: 'confirm',
name: 'customTheme',
message: 'Do you wish to overwrite Element\'s SCSS variables?',
default: false,
},
{
type: 'list',
name: 'lang',
message: 'Choose the locale you want to load',
choices: localeList.map(locale => ({
name: locale,
value: locale
})),
default: 'zh-CN'
}
]
generator
这个文件有几个作用:
- 创建新的模板;
- 编辑已存在的模板;
- 扩展依赖包
extendPackage
; - 修改主文件
injectImports
。
在vue-cli-plugin-element
中的generator
的结构如下:
├── templates/sc # 这是模板文件
├── indes.js # 安装时会读取里面的方法
└── utils.js # 封装的工具函数
分析index.js
,里面使用了api
这个对象方法:
-
extendPackage
新增packages.json
中的依赖:module.exports = (api, opts, rootOptions) => { api.extendPackage({ dependencies: { 'element-ui': '^2.4.5' } }) // ... }
-
injectImports
修改主文件(main.js
):api.injectImports(utils.getMain(), `import './plugins/element.js'`)
判断是向
main.js
还是main.ts
新增import './plugins/element.js
。 -
render
使用 EJS 渲染./template
中的文件:api.render({ './src/plugins/element.js': './templates/src/plugins/element.js', './src/App.vue': './templates/src/App.vue' })
其中
./templates/src/plugins/element.js
中的文件会根据条件渲染,然后在你的项目中的./src/plugins
下新增一个element.js
文件。而./templates/src/App.vue
会直接替换你现有的App.vue
文件。 -
根据条件渲染不同的模板和新增依赖:
if (opts.import === 'partial') { api.extendPackage({ devDependencies: { 'babel-plugin-component': '^1.1.1' } }) } else if (opts.customTheme) { api.render({ './src/element-variables.scss': './templates/src/element-variables.scss' }) api.extendPackage({ devDependencies: { 'sass-loader': '^7.0.3', 'node-sass': '^4.9.2' } }) }
顺便提一下,这里的
opts
指的是prompts.js
文件中弹窗的选择条件。 -
在文件写入磁盘时调用
onCreateComplete
,根据需要创建并更新babel.config.jsapi.onCreateComplete(() => { if (opts.import === 'partial') { utils.updateBabelConfig(cfg => { const pluginComponent = ['component', { 'libraryName': 'element-ui', 'styleLibraryName': 'theme-chalk' }] cfg.plugins = cfg.plugins || [] cfg.plugins.push(pluginComponent) return cfg }) } })
-
补充
afterInvoke
:官网的栗子:
module.exports.hooks = (api) => { api.afterInvoke(() => { 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] += `\n router,` }) }
main.js
已经存在的情况下,找到render
这一行的行数,将该行写上router
。import Vue from 'vue' import App from './App.vue' import router from './router' new Vue({ router, // 新增 render: h => h(App) }).$mount('#app')
更多有关于Generator API
可以查看 GeneratorAPI。
总结
vue cli
插件开发需要遵循各种规范,其中generator
的作用是为了方便新增依赖、新增修改文件等操作。后期等我的UI库开发完善了,我也来抄一个!
参考
若有错误,欢迎指出,感谢~