Make writing a habit together! This is the third day of my participation in the “Gold Digging Day New Plan · April More text Challenge”. Click here for more details.

Hello everyone, I am Xiao Yu. In the introduction, we learned that when you type Vite (Vite dev, Vite Server) on a terminal, the result below is returned

What happens in between.

This article starts from the code to deepen the impression of the whole process. We look directly at the source entry:

import { cac } from 'cac'

const cli = cac('vite')

// ...

/** * Removes global arguments */ for specific subcommands
function cleanOptions<Options extends GlobalCLIOptions> (
  options: Options
) :Omit<Options.keyof GlobalCLIOptions> {
  constret = { ... options }delete ret[The '-']
  delete ret.c
  delete ret.config
  delete ret.base
  delete ret.l
  delete ret.logLevel
  delete ret.clearScreen
  delete ret.d
  delete ret.debug
  delete ret.f
  delete ret.filter
  delete ret.m
  delete ret.mode
  return ret
}

// Global public parameters
cli
  .option('-c, --config <file>'.`[string] use specified config file`)
  .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`)
  .option('-m, --mode <mode>'.`[string] set env mode`)

cli
  .command('[root]'.'start dev server') // default command
  .alias('serve') // the command is called 'serve' in Vite's API
  .alias('dev') // alias to align with the script name
  .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(
    '--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 {
      // options gets command line arguments
      const server = await createServer({
        root,
        base: options.base,
        mode: options.mode,
        configFile: options.config,
        logLevel: options.logLevel,
        clearScreen: options.clearScreen,
        server: cleanOptions(options)
      })

      if(! server.httpServer) {throw new Error('HTTP server not available')}await server.listen()

      const info = server.config.logger.info

      info(
        colors.cyan(`\n  vite vThe ${require('vite/package.json').version}`) +
          colors.green(` dev server running at:\n`),
        {
          clear: !server.config.logger.hasWarned
        }
      )

      server.printUrls()

      // @ts-ignore
      // Start performance
      if (global.__vite_start_time) {
        // @ts-ignore
        const startupDuration = performance.now() - global.__vite_start_time
        info(
          `\n  ${colors.cyan(`ready in The ${Math.ceil(startupDuration)}ms.`)}\n`)}}catch (e) {
      createLogger(options.logLevel).error(
        colors.red(`error when starting dev server:\n${e.stack}`),
        { error: e }
      )
      process.exit(1)}})// ...
Copy the code

In the above code, the CLI application is created through CAC. The package features are:

  • Super lightweight, no dependencies, just one file;
  • Easy to use, with only 4 APIs to develop a CLI application:cli.option cli.version cli.help cli.parse;
  • Small as a sparrow is, it has all the organs. Enable default commands, Git-like subcommands, required parameters and options for validation, variable parameters, nested options, and automatic help message generation.
  • Development-friendly, tools developed in TypeScript.

Defines the following global configurations:

// The interface type of TS is the most friendly way to learn parameters
interface GlobalCLIOptions {
  The '-'? :string[] c? :boolean | stringconfig? :stringbase? :stringl? : LogLevel logLevel? : LogLevel clearScreen? :booleand? :boolean | stringdebug? :boolean | stringf? :stringfilter? :stringm? :stringmode? :string
}
Copy the code

For specific subcommands, global parameters are removed through cleanOptions when not required. The function function can also be quickly understood through the TS declaration.

function cleanOptions<Options extends GlobalCLIOptions> (
  options: Options
) :Omit<Options.keyof GlobalCLIOptions> {
  // ...
}
Copy the code

Omit

utility Type functions are used to Omit Keys from Type and to Omit global options from the CLI.
,>

Finally, we focus on the Vite (equivalent to Vite dev, Vite Server) action.

Receive the configuration of the development server (host, port, HTTPS, open) from the CLI, then call createServer to create the service, and then go inside the function to see what is done. Function in the packages/vite/SRC/node/server/index. The ts:

// Receive the incoming configuration to create the service
export async function createServer(
  inlineConfig: InlineConfig = {}
) :Promise<ViteDevServer> {
  // Get the Development or server config from the CLI + default parameters, which will be resolved in the next section. Here we will focus on the createServer process as a whole
  const config = await resolveConfig(inlineConfig, 'serve'.'development')

  / / root directory
  const root = config.root  
  // Configure the development server
  const serverConfig = config.server
  / / HTTPS configuration
  const httpsOptions = await resolveHttpsConfig(
    config.server.https,
    config.cacheDir
  )
  Create a Vite server in middleware mode
  let { middlewareMode } = serverConfig
  
  // middlewareMode is equal to SSR, which disables Vite's own HTML service logic
  if (middlewareMode === true) {
    middlewareMode = 'ssr'
  }
  // Indirectly through connect initialization, which is also express's middleware dependency package
  const middlewares = connect() as Connect.Server
  / / using the HTTP | | HTTPS | | http2 create service
  const httpServer = middlewareMode
    ? null
    : await resolveHttpServer(serverConfig, middlewares, httpsOptions)
  // Create the WebSocket service
  const ws = createWebSocketServer(httpServer, config, httpsOptions)
  // Get the watch parameter
  const{ ignored = [], ... watchOptions } = serverConfig.watch || {}// Monitor file changes through chokidar
  const watcher = chokidar.watch(path.resolve(root), {
    ignored: [
      '**/node_modules/**'.'**/.git/**'. (Array.isArray(ignored) ? ignored : [ignored])
    ],
    ignoreInitial: true.ignorePermissionErrors: true.disableGlobbing: true. watchOptions })as FSWatcher
  
  // ⚠️ initializes the module diagram
  const moduleGraph: ModuleGraph = new ModuleGraph((url, ssr) = >
    container.resolveId(url, undefined, { ssr })
  )
  
  // ⚠️ create the plug-in container
  const container = await createPluginContainer(config, moduleGraph, watcher)
  
  // Close the callback function after the service
  const closeHttpServer = createServerCloseFn(httpServer)

  // eslint-disable-next-line prefer-const
  let exitProcess: () = > void

  // ⚠️ defines the server configuration as a literal, which can be retrieved from the plug-in's configureServer hook
  const server: ViteDevServer = {
    config,
    middlewares,
    get app() {
      config.logger.warn(
        `ViteDevServer.app is deprecated. Use ViteDevServer.middlewares instead.`
      )
      return middlewares
    },
    httpServer,
    watcher,
    pluginContainer: container,
    ws,
    moduleGraph,
    ssrTransform,
    transformWithEsbuild,
    transformRequest(url, options) {
      return transformRequest(url, server, options)
    },
    transformIndexHtml: null! .// to be immediately set
    ssrLoadModule(url, opts? : { fixStacktrace? :boolean }) {
      server._ssrExternals ||= resolveSSRExternal(
        config,
        server._optimizeDepsMetadata
          ? Object.keys(server._optimizeDepsMetadata.optimized)
          : []
      )
      return ssrLoadModule(
        url,
        server,
        undefined.undefined, opts? .fixStacktrace ) },ssrFixStacktrace(e) {
      if (e.stack) {
        const stacktrace = ssrRewriteStacktrace(e.stack, moduleGraph)
        rebindErrorStacktrace(e, stacktrace)
      }
    },
    listen(port? :number, isRestart? :boolean) {
      return startServer(server, port, isRestart)
    },
    async close() {
      process.off('SIGTERM', exitProcess)

      if(! middlewareMode && process.env.CI ! = ='true') {
        process.stdin.off('end', exitProcess)
      }

      await Promise.all([
        watcher.close(),
        ws.close(),
        container.close(),
        closeHttpServer()
      ])
    },
    printUrls() {
      if (httpServer) {
        printCommonServerUrls(httpServer, config.server, config)
      } else {
        throw new Error('cannot print server URLs in middleware mode.')}},async restart(forceOptimize: boolean) {
      if(! server._restartPromise) { server._forceOptimizeOnRestart = !! forceOptimize server._restartPromise = restartServer(server).finally(() = > {
          server._restartPromise = null
          server._forceOptimizeOnRestart = false})}return server._restartPromise
    },

    _optimizeDepsMetadata: null._ssrExternals: null._globImporters: Object.create(null),
    _restartPromise: null._forceOptimizeOnRestart: false._isRunningOptimizer: false._registerMissingImport: null._pendingReload: null._pendingRequests: new Map()}// the ⚠️ plugin's transformIndexHtml hook executes here to convert index.html
  server.transformIndexHtml = createDevHtmlTransformFn(server)
  // Exit the process handler
  exitProcess = async() = > {try {
      await server.close()
    } finally {
      process.exit(0)}}// ...
  // ⚠️ Triggers an event when a file changes
  watcher.on('change'.async (file) => {
   // ...
  })

  watcher.on('add'.(file) = > {
    // ...
  })

  watcher.on('unlink'.(file) = > {
    // ...
  })

  if(! middlewareMode && httpServer) { httpServer.once('listening'.() = > {
      // update actual port since this may be different from initial value
      serverConfig.port = (httpServer.address() as AddressInfo).port
    })
  }

  // apply server configuration hooks from plugins
  // Collect the configureServer hooks in the plug-in
  const postHooks: ((() = > void) | void=) [] []for (const plugin of config.plugins) {
    if (plugin.configureServer) {
      postHooks.push(await plugin.configureServer(server))
    }
  }

  // A set of internal middleware
  // request timer
  if (process.env.DEBUG) {
    middlewares.use(timeMiddleware(root))
  }

  // cors (enabled by default)
  const { cors } = serverConfig
  if(cors ! = =false) {
    middlewares.use(corsMiddleware(typeof cors === 'boolean' ? {} : cors))
  }

  // proxy
  const { proxy } = serverConfig
  if (proxy) {
    middlewares.use(proxyMiddleware(httpServer, config))
  }

  // base
  if(config.base ! = ='/') {
    middlewares.use(baseMiddleware(server))
  }

  // open in editor support
  middlewares.use('/__open-in-editor', launchEditorMiddleware())

  // hmr reconnect ping
  // Keep the named function. The name is visible in debug logs via `DEBUG=connect:dispatcher ... `
  middlewares.use('/__vite_ping'.function viteHMRPingMiddleware(_, res) {
    res.end('pong')})// serve static files under /public
  // this applies before the transform middleware so that these files are served
  // as-is without transforms.
  if (config.publicDir) {
    middlewares.use(servePublicMiddleware(config.publicDir))
  }

  ⚠️ main transform Middleware -> main transform middleware file
  middlewares.use(transformMiddleware(server))

  // serve static files
  middlewares.use(serveRawFsMiddleware(server))
  middlewares.use(serveStaticMiddleware(root, server))

  // spa fallback
  if(! middlewareMode || middlewareMode ==='html') {
    middlewares.use(spaFallbackMiddleware(root))
  }

  // run post config hooks
  // This is applied before the html middleware so that user middleware can
  // serve custom content instead of index.html.
  postHooks.forEach((fn) = > fn && fn())

  if(! middlewareMode || middlewareMode ==='html') {
    // transform index.html
    middlewares.use(indexHtmlMiddleware(server))
    // handle 404s
    // Keep the named function. The name is visible in debug logs via `DEBUG=connect:dispatcher ... `
    middlewares.use(function vite404Middleware(_, res) {
      res.statusCode = 404
      res.end()
    })
  }

  // error handlermiddlewares.use(errorMiddleware(server, !! middlewareMode))// ⚠️ prebuild
  const runOptimize = async () => {
    server._isRunningOptimizer = true
    try {
      server._optimizeDepsMetadata = await optimizeDeps(
        config,
        config.server.force || server._forceOptimizeOnRestart
      )
    } finally {
      server._isRunningOptimizer = false
    }
    server._registerMissingImport = createMissingImporterRegisterFn(server)
  }

  // Is not a Vite service created in middleware mode, it is an HTTP service
  if(! middlewareMode && httpServer) {let isOptimized = false
    // overwrite listen to run optimizer before server start
    // Override the listen function to perform a pre-build before the service starts
    const listen = httpServer.listen.bind(httpServer)
    httpServer.listen = (async (port: number. args:any[]) = > {if(! isOptimized) {try {
          // ⚠️ executes the plugin container's buildStart hook
          await container.buildStart({})
					// ⚠️ Perform precompilation
          await runOptimize()
          isOptimized = true
        } catch (e) {
          httpServer.emit('error', e)
          return}}returnlisten(port, ... args) })as any
  } else {
    await container.buildStart({})
    await runOptimize()
  }

  // Finally return to server
  return server
}
Copy the code

After reading the above code, combine it with the flowchart from the beginning:

Let’s go through the process again:

  1. When we type vite on the terminal, the Vite node reads and filters the command line parameters, and then calls createServer to create the server.

  2. The resolveConfig command is used to obtain the configuration information inlineConfig, subcommands (serve or build), and mode (CLI parameter If no mode parameter is passed, The default is development) to generate config throughout the vite; This process is detailed in the next section, but it is more interesting.

  3. Step 3 Create the server and call resolveHttpsConfig to get the HTTPS configuration:

    /** * parse HTTPS configuration **@export
     * @param {(boolean | HttpsServerOptions | undefined)} https
     * @param {string} cacheDir
     * @return {*}  {(Promise<HttpsServerOptions | undefined>)}* /
    export async function resolveHttpsConfig(
      https: boolean | HttpsServerOptions | undefined,
      cacheDir: string
    ) :Promise<HttpsServerOptions | undefined> {
      // if server. HTTPS is false or undefined, HTTPS is not enabled
      if(! https)return undefined
    
      const httpsOption = isObject(https) ? { ...https } : {}
    
      // Obtain HTTPS basic configuration
      const { ca, cert, key, pfx } = httpsOption
      Object.assign(httpsOption, {
        ca: readFileIfExists(ca),
        cert: readFileIfExists(cert),
        key: readFileIfExists(key),
        pfx: readFileIfExists(pfx)
      })
      if(! httpsOption.key || ! httpsOption.cert) { httpsOption.cert = httpsOption.key =await getCertificate(cacheDir)
      }
      return httpsOption
    }
    Copy the code

    Use connect to initialize the middleware, vite internal use of a large number of middleware, timeMiddleware, corsMiddleware… ; Finally, call resolveHttpServer and createWebSocketServer to create HTTP and WS servers:

    / / using the HTTP | | HTTPS | | http2 create service
    const httpServer = middlewareMode
    ? null
    : await resolveHttpServer(serverConfig, middlewares, httpsOptions)
    // Create the WebSocket service
    const ws = createWebSocketServer(httpServer, config, httpsOptions)
    
    export async function resolveHttpServer({ proxy }: CommonServerOptions, app: Connect.Server, httpsOptions? : HttpsServerOptions) :Promise<HttpServer> {
      // ...
      
      // No HTTPS configuration is defined
      if(! httpsOptions) {return require('http').createServer(app)
      }
    
      // Configure a custom proxy rule
      if (proxy) {
        // #484 fallback to http1 when proxy is needed.
        return require('https').createServer(httpsOptions, app)
      } else {
        return require('http2').createSecureServer( { ... httpsOptions,allowHTTP1: true
          },
          app
        )
      }
    }
    
    export function createWebSocketServer(
      server: Server | null, config: ResolvedConfig, httpsOptions? : HttpsServerOptions) :WebSocketServer {
      let wss: WebSocket
      let httpsServer: Server | undefined = undefined
    
      const hmr = isObject(config.server.hmr) && config.server.hmr
     	// No HMR service is specified, default is httpServer
      const wsServer = (hmr && hmr.server) || server
    
      // The ws service address is specified
      if (wsServer) {
        wss = new WebSocket({ noServer: true })
        // ...
      } else {
        const websocketServerOptions: ServerOptions = {}
        const port = (hmr && hmr.port) || 24678
        // HTTPS configuration exists
        if (httpsOptions) {
          httpsServer = createHttpsServer(httpsOptions, (req, res) = > {
            // ...
            res.end(body)
          })
    
          httpsServer.listen(port)
          websocketServerOptions.server = httpsServer
        } else {
          // we don't need to serve over https, just let ws handle its own server
          websocketServerOptions.port = port
        }
    
        // vite dev server in middleware mode
        wss = new WebSocket(websocketServerOptions)
      }
    
      wss.on('connection'.(socket) = > {
        // ...
      })
    
      wss.on('error'.(e: Error & { code: string }) = > {
        // ...
      })
    
    
      return {
        on: wss.on.bind(wss),
        off: wss.off.bind(wss),
        send(payload: HMRPayload) {
          // ...
        },
        close() {
      		// ...}}}Copy the code
  4. Use Chokidar to create a file monitor that triggers the listener function on Watcher if any files in the current directory move:

    // Monitor file changes through chokidar
    const watcher = chokidar.watch(path.resolve(root), {
      ignored: [
        '**/node_modules/**'.'**/.git/**'. (Array.isArray(ignored) ? ignored : [ignored])
      ],
      ignoreInitial: true.ignorePermissionErrors: true.disableGlobbing: true. watchOptions })as FSWatcher
    
    // ⚠️ the change event is triggered when the file is changed
    watcher.on('change'.async (file) => {
      // ...
    })
    
    // The add event is triggered when a file is added
    watcher.on('add'.(file) = > {
      // ...
    })
    
    // An unlink event is triggered when a file is deleted
    watcher.on('unlink'.(file) = > {
      // ...
    })
    Copy the code
  5. Initialize the ModuleGraph instance to generate a module-dependent graph where each node is an instance of the ModuleNode:

    /** * Information for each module node */
    export class ModuleNode {
      /** * Public served url path, starts with / */
      url: string
      /** * Resolved file system path + query */
      id: string | null = null
      file: string | null = null
      // Node type, script, or style
      type: 'js' | 'css'info? : ModuleInfo meta? : Record<string.any>
      / / reference
      importers = new Set<ModuleNode>()
      importedModules = new Set<ModuleNode>()
      // The current module is more hot dependent
      acceptedHmrDeps = new Set<ModuleNode>()
      // Whether the self "accepts"
      isSelfAccepting = false
      // The result after conversion
      transformResult: TransformResult | null = null
      ssrTransformResult: TransformResult | null = null
      ssrModule: Record<string.any> | null = null
      // The latest hot time stamp
      lastHMRTimestamp = 0
    
      constructor(url: string) {
        this.url = url
        this.type = isDirectCSSRequest(url) ? 'css' : 'js'}}export class ModuleGraph {
      // Map the url to the module
      urlToModuleMap = new Map<string, ModuleNode>()
      // Mapping between id and module
      idToModuleMap = new Map<string, ModuleNode>()
      // File and module mapping, a file corresponds to multiple modules, such as SFC corresponds to multiple modules
      fileToModulesMap = new Map<string.Set<ModuleNode>>()
      // /@fs module
      safeModulesPath = new Set<string> ()constructor(
        // The internal resolvceId is passed in via the constructor
        private resolveId: (
          url: string,
          ssr: boolean) = >Promise<PartialResolvedId | null>
      ) {}
    
      /** * Get module */ from url
      async getModuleByUrl(
        rawUrl: string, ssr? :boolean) :Promise<ModuleNode | undefined> {
        const [url] = await this.resolveUrl(rawUrl, ssr)
        return this.urlToModuleMap.get(url)
      }
    
      /** * Get module */ by id
      getModuleById(id: string): ModuleNode | undefined {
        return this.idToModuleMap.get(removeTimestampQuery(id))
      }
    
      /** * Get module */ from file
      getModulesByFile(file: string) :Set<ModuleNode> | undefined {
        return this.fileToModulesMap.get(file)
      }
    
      /** * File modification event */
      onFileChange(file: string) :void {
        // ..
      }
    
      /** * Handle failed modules */
      invalidateModule(mod: ModuleNode, seen: Set<ModuleNode> = new Set()) :void {
        // ..
      }
    
      /** * Delete all invalid modules */
      invalidateAll(): void {
        // ..
      }
    
      /** * Update the module graph based on a module's updated imports information * If there are dependencies that no longer Have any credit, they are * returned as a Set@param {ModuleNode} Mod Specifies module A *@param {Set<string | ModuleNode>} ImportedModules Module A Imported module *@param {Set<string | ModuleNode>} AcceptedModules module A hot module *@param {boolean} IsSelfAccepting itself has an ACCPET function *@param {boolean} ssr* /
      async updateModuleInfo(
        mod: ModuleNode,
        importedModules: Set<string | ModuleNode>,
        acceptedModules: Set<string | ModuleNode>,
        isSelfAccepting: boolean, ssr? :boolean) :Promise<Set<ModuleNode> | undefined> {
        // ..
      }
    
      /** * Generate module */ based on url
      async ensureEntryFromUrl(rawUrl: string, ssr? :boolean) :Promise<ModuleNode> {
        // ..
      }
    
      /**
       * some deps, like a css file referenced via @import. don't have its own * url because they are inlined into the main css import. But they still * need to be represented in The Module graph so that they can trigger * HMR in the importing CSS file. In CSS code, there are no urls * but they are also identified in the module diagram */
      createFileOnlyEntry(file: string): ModuleNode {
        // ...
      }
    
      /** * parse the URL to do two things: * 1. Remove the HMR timestamp * 2. Handle file suffixes to ensure that the same file name (even if the suffix is different) can be mapped to the same module */
      async resolveUrl(url: string, ssr? :boolean) :Promise<ResolvedUrl> {
        // ...}}Copy the code
  6. Call createPluginContainer to generate the plugin container, which explicitly specifies the connection between the Vite plugin and the Rollup plugin.

    // PluginContext comes from rollup, and vite hooks come mostly from rollup
    class Context implements PluginContext {}
    
    Conversion contexts are handled by Vite-specific sourcemap
    class TransformContext extends Context {}
    
    // Define the plug-in container
    const container: PluginContainer = {
      // The hook that reads the configuration when the service starts
    	options: await (async() = > {// ...}) (),// The hook to start the build
      async buildStart() {},
      // Customize the parser
      async resolveId(rawId, importer = join(root, 'index.html'), options) {
        // ...
      }
      // Custom loader hooks
      async load(id, options) {},
      // Converter hook
      async transform(code, id, options) {},
      // Service closes the hook
      async close(){}}return container
    Copy the code
  7. The server instance is defined literally, which contains all the information about the Vite development server. The configureServer hook can be used to get the server information when we develop the plug-in and can be used to extend the middleware on Middlewares.

    // Initialize the server configuration with a literal
    const server: ViteDevServer = {
      / / configuration
      config,
      // Middleware information
      middlewares,
      get app() {
        config.logger.warn(
          `ViteDevServer.app is deprecated. Use ViteDevServer.middlewares instead.`
        )
        return middlewares
      },
      // HTTP server information
      httpServer,
      // File monitoring instance
      watcher,
      // Plug-in container
      pluginContainer: container,
      // websocket server
      ws,
      // Module map
      moduleGraph,
      // SSR converter
      ssrTransform,
      // esbuild converter
      transformWithEsbuild,
      // Load and convert the file to which the specific URL points
      transformRequest(url, options) {
        return transformRequest(url, server, options)
      },
      / / transformIndexHtml hook
      transformIndexHtml: null! .// to be immediately set
      // SSR loading module function
      ssrLoadModule(url, opts? : { fixStacktrace? :boolean }) {
        server._ssrExternals ||= resolveSSRExternal(
          config,
          server._optimizeDepsMetadata
          ? Object.keys(server._optimizeDepsMetadata.optimized)
          : []
        )
        return ssrLoadModule(
          url,
          server,
          undefined.undefined, opts? .fixStacktrace ) },// SSR stack information
      ssrFixStacktrace(e) {
        if (e.stack) {
          const stacktrace = ssrRewriteStacktrace(e.stack, moduleGraph)
          rebindErrorStacktrace(e, stacktrace)
        }
      },
      // Start the service
      listen(port? :number, isRestart? :boolean) {
        return startServer(server, port, isRestart)
      },
      // Shut down the service
      async close() {
        process.off('SIGTERM', exitProcess)
    
        if(! middlewareMode && process.env.CI ! = ='true') {
          process.stdin.off('end', exitProcess)
        }
    
        await Promise.all([
          watcher.close(),
          ws.close(),
          container.close(),
          closeHttpServer()
        ])
      },
      // Prints the URL helper function
      printUrls() {
        if (httpServer) {
          printCommonServerUrls(httpServer, config.server, config)
        } else {
          throw new Error('cannot print server URLs in middleware mode.')}},// Restart the service
      async restart(forceOptimize: boolean) {
        if(! server._restartPromise) { server._forceOptimizeOnRestart = !! forceOptimize server._restartPromise = restartServer(server).finally(() = > {
            server._restartPromise = null
            server._forceOptimizeOnRestart = false})}return server._restartPromise
      },
    
      // ...
    }
    Copy the code
  8. Finally, the Server’s Listen method is overridden to execute containerPlugin’s buildStart hook and prebuild before the server starts. Listen is called to start the server, and the access link and startup time are printed. This brings us back to the diagram at the beginning of the article after the service runs successfully:

conclusion

Go back to the process overview diagram:

When we hit the vite command, vite does parsing configuration, creating HTTP Server, creating WS, creating file listeners, initializing module dependency diagrams, creating plug-ins, pre-building, starting services, and more in a short time.

In addition, we were exposed to tools that we might use at work:

  • Cac is used to create command-line tools
  • Chokidar is used to listen for file changes

The template of the Vite forest gradually emerges, and then it’s time to go inside the forest and see what’s going on. We will first enter the entry configuration parsing (resolveConfig) and learn how Vite handles configuration file (vite.config.ts) parameters and CLI parameters.