IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> JavaScript知识库 -> vue-cli 原理分析 -> 正文阅读

[JavaScript知识库]vue-cli 原理分析

1、实现交互式命令行

const inquirer = require('inquirer')
const path = require('path')
const fs = require('fs')
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-app

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 = name
  if (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
]

async function create () {
  let answers = await inquirer.prompt(prompts);
  console.log(answers)
}

create()

处理 preset 以及里面的 plugins

async function create () {
  let answers = await inquirer.prompt(prompts);
  console.log(answers)

  let preset; // { plugins: { @vue/cli-service: {} } }

  // answers.preset = __manual__ | default | __default_vue_3__
  if (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)

}

构造一个package并且写入文件系统

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])
  })
}

async function create () {
  let answers = await inquirer.prompt(prompts);
  console.log(answers)

  let preset

  if (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)

  // 构造一个pkg
  const pkg = {
    name,
    version: '0.1.0',
    private: true,
    devDependencies: {}
  }
  // 往devDependencies加依赖
  const deps = Object.keys(preset.plugins)
  deps.forEach(dep => {
    pkg.devDependencies[dep] = 'latest'
  })

  // 把package.json写入文件系统
  await writeFileTree(context, {
    'package.json': JSON.stringify(pkg, null, 2)
  })
}

安装依赖解析插件

function run (command, args) {
  return execa(command, args, { cwd: context })
}

function loadModule (request, context) {
  Module.createRequire(path.resolve(context, 'package.json'))(request)
  // require('@vue/cli-service/generator')
}

async function resolvePlugins (rawPlugins, pkg) {
  const plugins = []
  for (const id of Object.keys(rawPlugins)) {
    // loadModule(`@vue/cli-service/generator`, context)
    const apply = loadModule(`${id}/generator`, context) || (() => {})
    let options = rawPlugins[id] || {}
    plugins.push({ id, apply, options })
  }
  // 返回了插件的数组,注意每个插件都有属性 id, apply, options
  return plugins
}

async function create () {
  let answers = await inquirer.prompt(prompts);
  console.log(answers)

  let preset

  if (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)

  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)
  })

  // 安装依赖
  await run('npm', ['install'])
  // 解析插件
  const plugins = await resolvePlugins(preset.plugins, pkg)
}

调用生成器函数

async function create () {
  let answers = await inquirer.prompt(prompts);
  console.log(answers)

  let preset

  if (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)

  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)
  })

  await run('npm', ['install'])
  const plugins = await resolvePlugins(preset.plugins, pkg)

  // 调用生成器函数
  const generator = new Generator(context, {
    pkg,
    plugins,
  })
  await generator.generate()
}

Generator 生成器

class Generator {
  constructor (context, {
    pkg = {},
    plugins = [],
    files = {}
  }) {
    this.context = context
    this.plugins = plugins
    this.pkg = Object.assign({}, pkg)
    this.files = files
    this.fileMiddlewares = []
    const cliService = plugins.find(p => p.id === '@vue/cli-service')
    this.rootOptions = cliService.options
  }
  async generate () {
    // 初始化插件
    await this.initPlugins()
  }
  async initPlugins () {
    for (const plugin of this.plugins) {
      const { id, apply, options } = plugin
      // 为每个插件创建一个生成器API对象
      const api = new GeneratorAPI(id, this, options, this.rootOptions)
      // 调用apply方法
      await apply(api, options, rootOptions)
    }
  }
}

为每个插件创建一个GeneratorAPI对象

function extractCallDir () {
  // extract api.render() callsite file location using error stack
  const obj = {}
  Error.captureStackTrace(obj)
  const callSite = obj.stack.split('\n')[3]

  // the regexp for the stack when called inside a named function
  const namedStackRegExp = /\s\((.*):\d+:\d+\)$/
  // the regexp for the stack when called inside an anonymous
  const anonymousStackRegExp = /at (.*):\d+:\d+$/

  let matchResult = callSite.match(namedStackRegExp)
  if (!matchResult) {
    matchResult = callSite.match(anonymousStackRegExp)
  }

  const fileName = matchResult[1]
  return path.dirname(fileName)
}

class GeneratorAPI {
  constructor (id, generator, options, rootOptions) {
    this.id = id
    this.generator = generator
    this.options = options
    this.rootOptions = rootOptions
    this.pluginsData = generator.plugins
  }
  // 往fileMiddlewares里面添加了函数
  _injectFileMiddleware (middleware) {
    this.generator.fileMiddlewares.push(middleware)
  }
  _resolveData (additionalData) {
    return Object.assign({
      options: this.options,
      rootOptions: this.rootOptions,
      plugins: this.pluginsData
    }, additionalData)
  }
  // render用来渲染模板的方法
  render (source, additionalData = {}, ejsOptions = {}) {
    const baseDir = extractCallDir()
    if (isObject(source)) {
      this._injectFileMiddleware(files => {
        const data = this._resolveData(additionalData)
        for (const targetPath in source) {
          const sourcePath = path.resolve(baseDir, source[targetPath])
          const content = renderFile(sourcePath, data, ejsOptions)
          if (Buffer.isBuffer(content) || content.trim()) {
            files[targetPath] = content
          }
        }
      })
    }
  }
}

真正执行中间件

class Generator {
  constructor (context, {
    pkg = {},
    plugins = [],
    files = {}
  }) {
    this.context = context
    this.plugins = plugins
    this.pkg = Object.assign({}, pkg)
    this.files = files
    this.fileMiddlewares = []
    const cliService = plugins.find(p => p.id === '@vue/cli-service')
    this.rootOptions = cliService.options
  }
  async generate () {
    await this.initPlugins()
    // 初始化之后得到了 fileMiddlewares
    await this.resolveFiles()
  }
  async resolveFiles () { // 真正执行中间件
    const files = this.files
    for (const middleware of this.fileMiddlewares) {
      await middleware(files, ejs.render)
    }
  }
  async initPlugins () {
    for (const plugin of this.plugins) {
      const { id, apply, options } = plugin
      const api = new GeneratorAPI(id, this, options, this.rootOptions)
      await apply(api, options, rootOptions)
    }
  }
}

最后写入文件系统

class Generator {
  constructor (context, {
    pkg = {},
    plugins = [],
    files = {}
  }) {
    this.context = context
    this.plugins = plugins
    this.pkg = Object.assign({}, pkg)
    this.files = files
    this.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 () {
    const files = this.files
    for (const middleware of this.fileMiddlewares) {
      await middleware(files, ejs.render)
    }
  }
  async initPlugins () {
    for (const plugin of this.plugins) {
      const { id, apply, options } = plugin
      const api = new GeneratorAPI(id, this, options, this.rootOptions)
      await apply(api, options, rootOptions)
    }
  }
}
  JavaScript知识库 最新文章
ES6的相关知识点
react 函数式组件 & react其他一些总结
Vue基础超详细
前端JS也可以连点成线(Vue中运用 AntVG6)
Vue事件处理的基本使用
Vue后台项目的记录 (一)
前后端分离vue跨域,devServer配置proxy代理
TypeScript
初识vuex
vue项目安装包指令收集
上一篇文章      下一篇文章      查看所有文章
加:2021-08-30 11:54:58  更:2021-08-30 11:55:55 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/23 13:07:35-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码