编写一个简单的 vue-cli 就可以轻松明白原理是怎么运行的了。理解 cli 的插件形式开发,并且可以选择配置,下载不同的模板内容。

const inquirer = require('inquirer')
const path = require('path')
const fs = require('fs-extra')
const execa = require('execa')
const Module = require('module')
const ejs = require('ejs')const isManualMode = answers => answers.preset === '__manual__'
const context = path.resolve(__dirname, 'my-app') // 假设要输出到 my-app 文件
const name = 'my-app' // vue create my-appconst isString = val => typeof val === 'string'
const isFunction = val => typeof val === 'function'
const isObject = val => val && typeof val === 'object'const promptCompleteCbs = [// (answers, options) => {//   if (answers.features.includes('vuex')) {//     options.plugins['@vue/cli-plugin-vuex'] = {}//   }// }
]const defaultPreset = {useConfigFiles: false,cssPreprocessor: undefined,plugins: {'@vue/cli-plugin-babel': {},'@vue/cli-plugin-eslint': {config: 'base',lintOn: ['save']}}
}const presets = {'default': Object.assign({ vueVersion: '2' }, defaultPreset),'__default_vue_3__': Object.assign({ vueVersion: '3' }, defaultPreset)
}const presetChoices = Object.entries(presets).map(([name, preset]) => {let displayName = nameif (name === 'default') {displayName = 'Default'} else if (name === '__default_vue_3__') {displayName = 'Default (Vue 3)'}return {name: `${displayName}`,value: name}
})const presetPrompt = {name: 'preset',type: 'list',message: `Please pick a preset:`,choices: [...presetChoices,{name: 'Manually select features',value: '__manual__'}]
}
let features = ['vueVersion','babel','typescript','pwa','router','vuex','cssPreprocessors','linter','unit','e2e'
]const featurePrompt = {name: 'features',when: isManualMode,type: 'checkbox',message: 'Check the features needed for your project:',choices: features,pageSize: 10
}const prompts = [presetPrompt,featurePrompt
]function run (command, args) {return execa(command, args, { cwd: context })
}function loadModule (request, context) {return Module.createRequire(path.resolve(context, 'package.json'))(request)
}async function resolvePlugins (rawPlugins, pkg) {const plugins = []for (const id of Object.keys(rawPlugins)) {const apply = loadModule(`${id}/generator`, context) || (() => {})let options = rawPlugins[id] || {}plugins.push({ id, apply, options })}return plugins
}function extractCallDir () {// extract api.render() callsite file location using error stackconst obj = {}Error.captureStackTrace(obj)const callSite = obj.stack.split('\n')[3]// the regexp for the stack when called inside a named functionconst namedStackRegExp = /\s\((.*):\d+:\d+\)$/// the regexp for the stack when called inside an anonymousconst anonymousStackRegExp = /at (.*):\d+:\d+$/let matchResult = callSite.match(namedStackRegExp)if (!matchResult) {matchResult = callSite.match(anonymousStackRegExp)}const fileName = matchResult[1]return path.dirname(fileName)
}function renderFile (name, data, ejsOptions) {const template = fs.readFileSync(name, 'utf-8')let finalTemplate = template.trim() + `\n`return ejs.render(finalTemplate, data, ejsOptions)
}async function writeFileTree (dir, files) {Object.keys(files).forEach((name) => {const filePath = path.join(dir, name)fs.ensureDirSync(path.dirname(filePath))fs.writeFileSync(filePath, files[name])})
}class GeneratorAPI {constructor (id, generator, options, rootOptions) {this.id = idthis.generator = generatorthis.options = optionsthis.rootOptions = rootOptionsthis.pluginsData = generator.plugins}_injectFileMiddleware (middleware) {this.generator.fileMiddlewares.push(middleware)}_resolveData (additionalData) {return Object.assign({options: this.options,rootOptions: this.rootOptions,plugins: this.pluginsData}, additionalData)}extendPackage (fields, options = {}) {// 合并两个package}render (source, additionalData = {}, ejsOptions = {}) {const baseDir = extractCallDir()console.log(source, 'source')if (isString(source)) {source = path.resolve(baseDir, source) // 找到了插件的tempalte目录// 放到 fileMiddlewares 数组里面去,并没有执行中间件this._injectFileMiddleware(async (files) => {const data = this._resolveData(additionalData)const globby = require('globby')const _files = await globby(['**/*'], { cwd: source, dot: true })// 模板里面是 _gitignore 要变 .gitignore 文件,防止被忽略for (const rawPath of _files) {const targetPath = rawPath.split('/').map(filename => {if (filename.charAt(0) === '_' && filename.charAt(1) !== '_') {return `.${filename.slice(1)}`}if (filename.charAt(0) === '_' && filename.charAt(1) === '_') {return `${filename.slice(1)}`}return filename}).join('/')const sourcePath = path.resolve(source, rawPath)const content = renderFile(sourcePath, data, ejsOptions)if (Buffer.isBuffer(content) || /[^\s]/.test(content)) {files[targetPath] = content}}})}}
}class Generator {constructor (context, {pkg = {},plugins = [],files = {}}) {this.context = contextthis.plugins = pluginsthis.pkg = Object.assign({}, pkg)this.files = filesthis.fileMiddlewares = []const cliService = plugins.find(p => p.id === '@vue/cli-service') || {}this.rootOptions = cliService.options || {}}async generate () {await this.initPlugins()await this.resolveFiles()this.files['package.json'] = JSON.stringify(this.pkg, null, 2) + '\n'// 写入文件系统await writeFileTree(this.context, this.files)}async resolveFiles () {// GeneratorAPI 里面的render方法修改了 fileMiddlewares,最后在这里执行了const files = this.filesfor (const middleware of this.fileMiddlewares) {await middleware(files, ejs.render)}}async initPlugins () {const rootOptions = this.rootOptionsfor (const plugin of this.plugins) {const { id, apply, options } = plugin// 插件generator文件导出的函数在这里执行const api = new GeneratorAPI(id, this, options, rootOptions)await apply(api, options, rootOptions)}}
}async function create () {let answers = await inquirer.prompt(prompts);console.log(answers)let presetif (answers.preset !== '__manual__') {preset = presets[answers.preset]} else {preset = {useConfigFiles: false,plugins: {}}}promptCompleteCbs.forEach(cb => cb(answers, preset))// preset.plugins['@vue/cli-service'] = Object.assign({//   projectName: name// }, preset)// 暂时用一个我自己写的cli插件preset.plugins['cli-plugin-demo'] = {}const pkg = {name,version: '0.1.0',private: true,devDependencies: {}}const deps = Object.keys(preset.plugins)deps.forEach(dep => {pkg.devDependencies[dep] = 'latest'})await writeFileTree(context, {'package.json': JSON.stringify(pkg, null, 2)})console.log(`⚙\u{fe0f}  Installing CLI plugins. This might take a while...`)await run('npm', ['install'])console.log(`????  Invoking generators...`)// [{ id, apply, options }] id 插件的名字, apply 插件generator文件导出的函数,options 是参数const plugins = await resolvePlugins(preset.plugins, pkg)const generator = new Generator(context, {pkg,plugins,})await generator.generate()console.log(`????  Installing additional dependencies...`)await run('npm', ['install'])console.log(`????  Successfully created project ${name}.`)
}create()
// 最后使用node运行

vue-cli 基本原理相关推荐

  1. cli vue 卸载,vue Cli 环境删除与重装教程 - 版本文档

    vue-cli 卸载,版本选择,安装 · 检测(图文教程:vue Cli 环境删除与重装) 重要说明: vue-cli 3.0+版本,使用的不是vue-cli,而是@vue/cli: 如果用以上的安装 ...

  2. [Vue CLI 3] 插件编写实战和源码分析

    当你看过了官方的几个插件之后,慢慢地,其实你也有需求了. 那如何编写一个 Vue CLI 3 的插件呢? 本文代码已经放到 github 上,地址:https://github.com/dailyno ...

  3. vue连线 插件_【Vue CLI】手把手教你撸插件

    现如今 Vue 作为主流的前端框架之一,其健全的配套工具,活跃的开源社区,让广发码农热衷追捧.Vue CLI 作为其官方的开发构建工具,目前已更新迭代到 4.x 版本,其内部集成了日常开发用到的打包压 ...

  4. vuecli启动的服务器位置,webpack – 在vue cli 3生成的项目中启动dev服务器

    我使用npm i -g @ vue / cli在我的 Windows系统上全局安装了vue cli 3. 然后我使用vue create vue-project生成了一个项目 我通过提示选择了所需的插 ...

  5. vue项目通过命令行传参实现多环境配置(基于@vue/cli)

    大多数项目都有生产环境和开发环境,一般情况下应该够了,但是有时候还需要sit,uat,本地等环境,这时候假如要通过注释的方式切换环境就相当麻烦了. 如果可以像下面这样切换环境就方便了 npm run ...

  6. VSCode 搭建Vue开发环境之Vue CLI

    2019独角兽企业重金招聘Python工程师标准>>> 一.简介说明 1.关于VS Code开发工具,安装和配置,更多可以参考以前文章 2.关于Vue.js,Vue是一个优秀的渐进式 ...

  7. 基于vue cli 3.0创建前端项目并安装cube-ui

    前提条件: 安装node.js. 国内的开发者最好先配置淘宝镜像. 之后用cnpm来代替npm命令. 项目创建过程: 打开cmd,输入命令进入目标工作空间,以本机项目为例: cd /d d: cd D ...

  8. vue搜不到c_vue不是内部命令、安装@vue/cli失败、找不到vue.cmd文件等问题的解决方法...

    最近打算用vue-cli写项目,发现用官网提供的安装方式npm install @vue/cli -g怎么都安装不上,一直报如下错误 EPERM: operation not permitted, r ...

  9. Vue CLI 3.0脚手架如何在本地配置mock数据

    前后端分离的开发模式已经是目前前端的主流模式,至于为什么会前后端分离的开发我们就不做过多的阐述,既然是前后端分离的模式开发肯定是离不开前端的数据模拟阶段. 我们在开发的过程中,由于后台接口的没有完成或 ...

  10. Vue CLI 3 多页应用项目的搭建

    在项目初期时,从零开始搭建和配置本地前端开发环境是一项很繁琐的工作,需要考虑到项目目录结构.项目基本配置.Webpack 配置等等.通过 Vue CLI 3 可以快速的生成一个项目,这样我们就可以专注 ...

最新文章

  1. DiskLruCache 源码解析
  2. [转]QT中QString与string的转化,解决中文乱码问题
  3. 区块链BaaS云服务(30) 字节方舟 ByteArk
  4. 2小时撸完代码之后,所有程序员都逃不过的一天... (强共鸣)
  5. struts2几种result type探究
  6. Apache Ant 1.10.6发布–用于junitlauncher的fork模式以及新的jmod和链接任务
  7. java swt designerpdf_eclipse学习笔记!(4) ----- SWT Designer 下 SWT常用组件
  8. 《掌握需求过程》阅读笔记三
  9. 数据结构—线索二叉树
  10. mongodb系列~mongodb的副本集搭建和原理
  11. linux学习笔记:Linux 文件的基本属性
  12. AD17入门简单教程(一)
  13. 黄永成-thinkphp讲解-个人博客讲解26集
  14. OSG开发笔记(二十三):Qt使用QOpenGLWidget渲染OSG和地球仪
  15. 如何理解数据质量中准确性和一致性的区别?
  16. oracle 求一年多少天,SQL 计算一年有多少天
  17. Python之ffmpeg:利用python编程基于ffmpeg将m4a格式音频文件转为mp3格式文件
  18. linux能做什么?
  19. mac安装pygraphviz找不到头文件
  20. 有参有返回值函数实现求s=a+aa+aaa+aaaa+..的值,其中a是数字如:a = 2; s = 2+22 a = 4; s = 4+44+444+4444,

热门文章

  1. 2020 OPPO开发者大会:融合共创,打造多终端、跨场景的智能化生活
  2. 马斯克脑机接口、BrainOS 相继发布,未来已来?
  3. 「AI原生」时代来临?百度智能云提出AI-Native,发布新一代云基础架构「太行」
  4. 华为否认启动“塔山计划”;中金:苹果中国区下架微信概率较小;Linux Lab发布v0.5 rc3| 极客头条...
  5. Excel弱爆了!这个工具30分钟完成了我一天的工作量,零基础也能学!
  6. 招聘数下降71%!程序员:你的努力正在毁掉自己!
  7. GitLab 公开拒收中国员工,你怎么看?!
  8. 首款搭载国产CPU的域名服务器发布;iPhone彻底淘汰Lightning接口?ChromeOS 75发布 | 极客头条...
  9. OPPO 推出 10 亿引力计划,全力构建智能化服务生态
  10. 跟 05 后拼年龄?算了,11 岁的他都成比特币专家了