wuxianqiang / my-cli

vue-cli插件式开发
0 stars 0 forks source link

核心原理 #1

Open wuxianqiang opened 3 years ago

wuxianqiang commented 3 years ago
plugins.push({ id, apply, options })

id:插件的名字,如 @vue/cli-service apply:其实就是插件的函数

const apply = loadModule(`${id}/generator`, this.context) || (() => {})

loadModule 其实就是为了得到插件 generator 导出的一个下面这样的函数

module.exports = (api, options = {}, rootOptions = {}) => {
  const isVue3 = (rootOptions.vueVersion === '3')

  api.injectImports(api.entryFile, `import router from './router'`)

  if (isVue3) {
    api.transformScript(api.entryFile, require('./injectUseRouter'))
    api.extendPackage({
      dependencies: {
        'vue-router': '^4.0.3'
      }
    })
  } else {
    api.injectRootOptions(api.entryFile, `router`)

    api.extendPackage({
      dependencies: {
        'vue-router': '^3.5.1'
      }
    })
  }

  api.render('./template', {
    historyMode: options.historyMode,
    doesCompile: api.hasPlugin('babel') || api.hasPlugin('typescript'),
    hasTypeScript: api.hasPlugin('typescript')
  })

  if (isVue3) {
    api.render('./template-vue3', {
      historyMode: options.historyMode,
      doesCompile: api.hasPlugin('babel') || api.hasPlugin('typescript'),
      hasTypeScript: api.hasPlugin('typescript')
    })
  }

  if (api.invoking) {
    if (api.hasPlugin('typescript')) {
      /* eslint-disable-next-line node/no-extraneous-require */
      const convertFiles = require('@vue/cli-plugin-typescript/generator/convert')
      convertFiles(api)
    }
  }
}
wuxianqiang commented 3 years ago

loadModule 源码

exports.loadModule = function (request, context, force = false) {
  // createRequire doesn't work with jest mocked fs
  // (which we used in tests for cli-service)
  if (process.env.VUE_CLI_TEST && context === '/') {
    return require(request)
  }

  try {
    return createRequire(path.resolve(context, 'package.json'))(request)
  } catch (e) {
    const resolvedPath = exports.resolveModule(request, context)
    if (resolvedPath) {
      if (force) {
        clearRequireCache(resolvedPath)
      }
      return require(resolvedPath)
    }
  }
}

const createRequire = Module.createRequire || Module.createRequireFromPath || function (filename) {
  const mod = new Module(filename, null)
  mod.filename = filename
  mod.paths = Module._nodeModulePaths(path.dirname(filename))

  mod._compile(`module.exports = require;`, filename)

  return mod.exports
}

request 是 @vue/cli-service/generator,context 是输出目录,已 context 为出发点找 @vue/cli-service/generator

Module.createRequire(path.resolve(context, 'package.json'))(request)

目的就是为了改变加载的位置,要从 context 这个输出目录里面加载,不能直接写 require 为当前目录

wuxianqiang commented 3 years ago

核心属性 pkg 表示 package.json plugins 表示 [{id, apply, options}] files 表示 {文件名: 文件内容} fileMiddlewares rootOptions 其实就是 @vue/cli-service的options

//     // pligins = [{id: @vue/cli-plugin-babel, apply: @vue/cli-plugin-babel/generator等等插件导出的函数,options: {参数}}]
module.exports = class Generator {
  constructor (context, {
    pkg = {}, // 存放 package.json
    plugins = [], // 存放之前构造的插件
    afterInvokeCbs = [],
    afterAnyInvokeCbs = [],
    files = {}, // 存放 文件名:文件内容,这里放最后生成的所有内容
    invoking = false
  } = {}) {
    this.context = context
    this.plugins = sortPlugins(plugins)
    this.originalPkg = pkg
    this.pkg = Object.assign({}, pkg) // 注意这里 package.json
    this.pm = new PackageManager({ context })
    this.imports = {}
    this.rootOptions = {}
    this.afterInvokeCbs = afterInvokeCbs
    this.afterAnyInvokeCbs = afterAnyInvokeCbs
    this.configTransforms = {}
    this.defaultConfigTransforms = defaultConfigTransforms
    this.reservedConfigTransforms = reservedConfigTransforms
    this.invoking = invoking
    // for conflict resolution
    this.depSources = {}
    // virtual file tree
    this.files = Object.keys(files).length // 注意这里 {文件名: 文件内容}
      // when execute `vue add/invoke`, only created/modified files are written to disk
      ? watchFiles(files, this.filesModifyRecord = new Set())
      // all files need to be written to disk
      : files
    this.fileMiddlewares = [] // 这里是核心,每个插件都会往中间件里面插入中间件,中间件会负责往this.files里面写东西
    this.postProcessFilesCbs = []
    // exit messages
    this.exitLogs = []

    // load all the other plugins 开发依赖加上生产依赖,过滤得到vue-cli这些插件
    this.allPlugins = this.resolveAllPlugins() // @vue/cli-plugin-babel 找出这些的插件

    // 默认把 @vue/cli-service 的options作为 rootOptions
    const cliService = plugins.find(p => p.id === '@vue/cli-service')
    const rootOptions = cliService
      ? cliService.options
      : inferRootOptions(pkg)

    this.rootOptions = rootOptions
  }