美文网首页
[vite源码解析]cli篇

[vite源码解析]cli篇

作者: 秋名山车神12138 | 来源:发表于2021-07-04 23:20 被阅读0次

    首先我们来看入口文件:/vite/packages/vite/bin/vite.js

    第1步:

    判断当前目录是否包含node_module目录,如果不包含,需要source-map的支持

    // vite/packages/vite/bin/vite.js
    #!/usr/bin/env node
    
    if (!__dirname.includes('node_modules')) {
      try {
        // only available as dev dependency
        require('source-map-support').install()
      } catch (e) {}
    }
    

    第2步:

    在导入cli之前通过正则匹配process.argv检查下debug模式:

    // vite/packages/vite/bin/vite.js
    const debugIndex = process.argv.findIndex((arg) => /^(?:-d|--debug)$/.test(arg))
    const filterIndex = process.argv.findIndex((arg) =>
      /^(?:-f|--filter)$/.test(arg)
    )
    const profileIndex = process.argv.indexOf('--profile')
    
    if (debugIndex > 0) {
      let value = process.argv[debugIndex + 1]
      if (!value || value.startsWith('-')) {
        value = 'vite:*'
      } else {
        // support debugging multiple flags with comma-separated list
        value = value
          .split(',')
          .map((v) => `vite:${v}`)
          .join(',')
      }
      process.env.DEBUG = value
    
      if (filterIndex > 0) {
        const filter = process.argv[filterIndex + 1]
        if (filter && !filter.startsWith('-')) {
          process.env.VITE_DEBUG_FILTER = filter
        }
      }
    }
    

    第3步:

    // vite/packages/vite/bin/vite.js
    // 启动方式就是导入编译后的node cli
    function start() {
      require('../dist/node/cli')
    }
    
    // 如果配置了profile参数
    if (profileIndex > 0) {
      // 删除profile参数
      process.argv.splice(profileIndex, 1)
      // 获取下一个参数,如果下个参数还有但并没有以-开头,继续删除一个
      const next = process.argv[profileIndex]
      if (next && !next.startsWith('-')) {
        process.argv.splice(profileIndex, 1)
      }
      // 导入inspector库Inspector API,创建对应会话并连接
      const inspector = require('inspector')
      const session = (global.__vite_profile_session = new inspector.Session())
      session.connect()
      session.post('Profiler.enable', () => {
        session.post('Profiler.start', start)
      })
    } else {
    // 正常导入启动
      start()
    }
    

    第4步:进入node 的cli部分

    // packages/vite/src/node/cli.ts
    
    import { cac } from 'cac'
    import chalk from 'chalk'
    import { BuildOptions } from './build'
    import { ServerOptions } from './server'
    import { createLogger, LogLevel } from './logger'
    import { resolveConfig } from '.'
    import { preview } from './preview'
    
    // 利用cac生成cli实例
    const cli = cac('vite')
    

    第5步:全局选项定义及清理选项

    // packages/vite/src/node/cli.ts
    
    interface GlobalCLIOptions {
      '--'?: string[]
      debug?: boolean | string
      d?: boolean | string
      filter?: string
      f?: string
      config?: string
      c?: boolean | string
      root?: string
      base?: string
      r?: string
      mode?: string
      m?: string
      logLevel?: LogLevel
      l?: LogLevel
      clearScreen?: boolean
    }
    
    /**
     * removing global flags before passing as command specific sub-configs
     */
    function cleanOptions(options: GlobalCLIOptions) {
      const ret = { ...options }
      delete ret['--']
      delete ret.debug
      delete ret.d
      delete ret.filter
      delete ret.f
      delete ret.config
      delete ret.c
      delete ret.root
      delete ret.base
      delete ret.r
      delete ret.mode
      delete ret.m
      delete ret.logLevel
      delete ret.l
      delete ret.clearScreen
      return ret
    }
    

    第6步:cli 参数定义

    // packages/vite/src/node/cli.ts
    
    // 下面几个是核心参数,例如-c指定配置文件
    cli
      .option('-c, --config <file>', `[string] use specified config file`)
      .option('-r, --root <path>', `[string] use specified root directory`)
      .option('--base <path>', `[string] public base path (default: /)`)
      .option('-l, --logLevel <level>', `[string] info | warn | error | silent`)
      .option('--clearScreen', `[boolean] allow/disable clear screen when logging`)
      .option('-d, --debug [feat]', `[string | boolean] show debug logs`)
      .option('-f, --filter <filter>', `[string] filter debug logs`)
    
    // 开发参数,主要针对server的配置
    cli
      .command('[root]') // default command
      .alias('serve')
      .option('--host [host]', `[string] specify hostname`)
      .option('--port <port>', `[number] specify port`)
      .option('--https', `[boolean] use TLS + HTTP/2`)
      .option('--open [path]', `[boolean | string] open browser on startup`)
      .option('--cors', `[boolean] enable CORS`)
      .option('--strictPort', `[boolean] exit if specified port is already in use`)
      .option('-m, --mode <mode>', `[string] set env mode`)
      .option(
        '--force',
        `[boolean] force the optimizer to ignore the cache and re-bundle`
      )
      .action(async (root: string, options: ServerOptions & GlobalCLIOptions) => {
        // output structure is preserved even after bundling so require()
        // is ok here
        const { createServer } = await import('./server')
        try {
          const server = await createServer({
            root,
            base: options.base,
            mode: options.mode,
            configFile: options.config,
            logLevel: options.logLevel,
            clearScreen: options.clearScreen,
            server: cleanOptions(options) as ServerOptions
          })
          await server.listen()
        } catch (e) {
          createLogger(options.logLevel).error(
            chalk.red(`error when starting dev server:\n${e.stack}`)
          )
          process.exit(1)
        }
      })
    
    // 构建参数
    cli
      .command('build [root]')
      .option('--target <target>', `[string] transpile target (default: 'modules')`)
      .option('--outDir <dir>', `[string] output directory (default: dist)`)
      .option(
        '--assetsDir <dir>',
        `[string] directory under outDir to place assets in (default: _assets)`
      )
      .option(
        '--assetsInlineLimit <number>',
        `[number] static asset base64 inline threshold in bytes (default: 4096)`
      )
      .option(
        '--ssr [entry]',
        `[string] build specified entry for server-side rendering`
      )
      .option(
        '--sourcemap',
        `[boolean] output source maps for build (default: false)`
      )
      .option(
        '--minify [minifier]',
        `[boolean | "terser" | "esbuild"] enable/disable minification, ` +
          `or specify minifier to use (default: terser)`
      )
      .option('--manifest', `[boolean] emit build manifest json`)
      .option('--ssrManifest', `[boolean] emit ssr manifest json`)
      .option(
        '--emptyOutDir',
        `[boolean] force empty outDir when it's outside of root`
      )
      .option('-m, --mode <mode>', `[string] set env mode`)
      .option('-w, --watch', `[boolean] rebuilds when modules have changed on disk`)
      .action(async (root: string, options: BuildOptions & GlobalCLIOptions) => {
        const { build } = await import('./build')
        const buildOptions = cleanOptions(options) as BuildOptions
    
        try {
          await build({
            root,
            base: options.base,
            mode: options.mode,
            configFile: options.config,
            logLevel: options.logLevel,
            clearScreen: options.clearScreen,
            build: buildOptions
          })
        } catch (e) {
          createLogger(options.logLevel).error(
            chalk.red(`error during build:\n${e.stack}`)
          )
          process.exit(1)
        }
      })
    
    // 优化参数:这里主要配置是否忽略缓存,默认vite在依赖预构建会缓存公共依赖包,例如lodash
    cli
      .command('optimize [root]')
      .option(
        '--force',
        `[boolean] force the optimizer to ignore the cache and re-bundle`
      )
      .action(
        async (root: string, options: { force?: boolean } & GlobalCLIOptions) => {
          const { optimizeDeps } = await import('./optimizer')
          try {
            const config = await resolveConfig(
              {
                root,
                base: options.base,
                configFile: options.config,
                logLevel: options.logLevel
              },
              'build',
              'development'
            )
            await optimizeDeps(config, options.force, true)
          } catch (e) {
            createLogger(options.logLevel).error(
              chalk.red(`error when optimizing deps:\n${e.stack}`)
            )
            process.exit(1)
          }
        }
      )
    
    // 预览配置
    cli
      .command('preview [root]')
      .option('--host [host]', `[string] specify hostname`)
      .option('--port <port>', `[number] specify port`)
      .option('--https', `[boolean] use TLS + HTTP/2`)
      .option('--open [path]', `[boolean | string] open browser on startup`)
      .action(
        async (
          root: string,
          options: {
            host?: string
            port?: number
            https?: boolean
            open?: boolean | string
          } & GlobalCLIOptions
        ) => {
          try {
            const config = await resolveConfig(
              {
                root,
                base: options.base,
                configFile: options.config,
                logLevel: options.logLevel,
                server: {
                  open: options.open
                }
              },
              'serve',
              'development'
            )
            await preview(
              config,
              cleanOptions(options) as {
                host?: string
                port?: number
                https?: boolean
              }
            )
          } catch (e) {
            createLogger(options.logLevel).error(
              chalk.red(`error when starting preview server:\n${e.stack}`)
            )
            process.exit(1)
          }
        }
      )
    // 输出帮助信息
    cli.help()
    // 取当前package.json的版本作为cli的版本
    cli.version(require('../../package.json').version)
    // 开始执行解析
    cli.parse()
    

    总结

    vite在cli部分判断了包括profile,debugger等多类参数,并针对build,server,preview等不同类型提供对应的配置内容。cli本身借助cac的能力,相当简洁清晰。

    备注


    1. inspector库:WebKit inspector API的node实现
    2. cac:cli 参数识别工具包

    相关文章

      网友评论

          本文标题:[vite源码解析]cli篇

          本文链接:https://www.haomeiwen.com/subject/emwvultx.html