vite2

1. Initialize the project

// 1. Lerna was used to manage the Monorepo project
lerna init 
lerna create @ishoppe/vite

// 2. The way plug-ins are used is framework-independent
lerna create @ishoppe/vite-plugin-vue
lerna create @ishoppe/vite-plugin-vue-jsx
lerna create @ishoppe/vite-plugin-react-refresh 
Copy the code

2. Vitejs entrance

// Similar to vite1 through bin/vitejs
require('.. /dist/node/cli')
// Register commands on the CLI
cli
  .command("[root]")
  .alias("serve")
  .action(async (root, options) => {
    const server = await createServer({
      root,
      base: options.base,
      mode: options.mode,
      configFile: options.config,
      // server: cleanOptions(options)
      server: options
    })
    await server.listen()
  });


// build
cli.command("build [root]").action(async (root, options) => {
  await build({
    root,
    base: options.base,
    mode: options.mode,
    configFile: options.config,
    build: options
  })
});
Copy the code

3. service

async function createServer(inlineConfig) {
  // 1. Obtain config
  const config = resolveConfig(inlineConfig, 'serve'.'development')
  // ca cert key
  const httpsOptions = await resolveHttpsConfig(config)
  // 2. Check whether it is the middleware mode. The middleware mode does not create HTTP services
  // Vite2 replaces VITe1's KOA with HTTP + Connect
  // require('http').createServer(app) https http2
  const httpServer = middlewareMode ? null : await resolveHttpServer(serverConfig, connect(), httpsOptions)
  // 3. Create webSocket service new websocket. Server
  const ws = createWebSocketServer(httpServer, config, httpsOptions)
  // 4. Monitor file changes
  const watcher = chokidar.watch()
  // 5. Guess is a Graph module dependency similar to rollup
  const container = await createPluginContainer(config, watcher)
  const moduleGraph = new ModuleGraph(container)
  // 6. Server service
  const server = {
    listen(port) {
      // httpServer.listen
      return startServer(server, port)
    }
  }
  // 7. The service configuration hook of the application plug-in
  const postHooks = []
  for(const plugin of plugins) { // options.plugins
    // The plugins we provide all require configureServer
    plugin.configureServer && postHooks.push(await plugin.configureServer(server))
  }
  Load a set of middleware cORS proxy static
  middlewares.use(serveStaticMiddleware(root, config)) // static
  middlewares.use(corsMiddleware) // cors
  middlewares.use(servePublicMiddleware) // public
  // /node_modules/. Vite Cache /@vite/client /@vite/env
  // Vite2 handling will not be loaded from the.vite directory in a precompiled process that overwrites /@module
  middlewares.use(transformMiddleware(server)) // vite/dist/client
  Fallback connect-history-api-fallback in spa history mode
  middlewares.use(history({
    rewrites: [{from: / / / $/.to({ parsedUrl }) {
          const rewritten = parsedUrl.pathname + 'index.html'
          if (fs.existsSync(path.join(root, rewritten))) {
            return rewritten
          } else {
            return `/index.html`}}}]})// 10. Run hooks before HTML middleware external middleware can provide custom content instead of index.html
  postHooks.forEach(fn= > fn && fn())
  // 11. Process index.html
  middlewares.use(indexHtmlMiddleware(server)) // insert 
  / / 12. Optimization
  const runOptimize = async() = > {// Use esbuild instead of rollup in Vite1
    server._optimizeDepsMetadata = await optimizeDeps(config)
  }
  // 13. Override the listen method to execute runOptimize
  httpServer.listen = async(port, ... args) => {Plugins.map (plugin => plugin.buildstart ())) iterates through the plugin's buildStart method
    // rollup buildStart? The plug-in
    await container.buildStart({})
    await runOptimize()
  }
  / / back to the server
  return server
}

Copy the code

4 resolveConfig

function resolveConfig() {
  // 1. The configuration file vite.config.js
  const loadResult = await loadConfigFromFile()
  // 2. User plugins
  const rawUserPlugins = (config.plugins || []).flat().filter()
  // Sort according to enforce
  const [prePlugins, normalPlugins, postPlugins] = sortUserPlugins(rawUserPlugins);
  const userPlugins = [...prePlugins, ...normalPlugins, ...postPlugins]
  // Execute the plug-in
  for (const p of userPlugins) {
    if (p.config) {
      const res = await p.config(config, configEnv)
      if (res) {
        config = mergeConfig(config, res)
      }
    }
  }
  // 3. Handle aliases
  const resolvedAlias = mergeAlias()
  Env file VITE_ variable
  const userEnv = loadEnv()
  // 5. Create a plug-in parser to execute the plug-in
  const createResolver = options= >{}// 6. Plugins Some of the built-in plugins in Vite1 add various middleware
  ResolvePlugin cssPlugin aliasPlugin etc
  resolved.plugins = await resolvePlugins(
    // resolveId tryOptimizedResolve Add the cache path if the path is dependent
    isBuild ? null : preAliasPlugin(),
    aliasPlugin,
    resolved,
    prePlugins,
    normalPlugins,
    postPlugins
  )
  // 7. Execute hook function
  await Promise.all(userPlugins.map((p) = >p.configResolved? .(resolved))) }Copy the code

5. Pre-bundling dependencies

async function optimizeDeps(config) {
  // node_modules/.vite
  const dataPath = path.join(cacheDir, '_metadata.json')
  // Generate hash" hash": "211e8ad4",
  const mainHash = getDepHash(root, config)
  const data = {hash: mainHash, optimized: {}, browserHash: mainHash,}
  // The force parameter can be passed to force
  if(! force) {let prevData = JSON.parse(fs.readFileSync(dataPath, 'utf-8'))}let deps
  if(! newDeps) {// Use esbuild to analyze dependencies; ({ deps, missing } =await scanImports(config))
  } else {
    deps = newDeps
  }
  constinclude = config.optimizeDeps? .includeif(include) {
    const resolve = config.createResolver({ asSrc: false})}// es-module-lexer
  await init
  for (const id in deps) {
    // Flatten dependency
    const flatId = flattenId(id)
    flatIdDeps[flatId] = deps[id]
  }
  // Call the build method of esbuild
  const result = await build({})
  for (const id in deps) {

  }
  // Modify the _metadata file
  writeFile(dataPath, JSON.stringify(data, null.2))
  return data
}
Copy the code

6. Plugin mechanism plugin

// Vite1 is strongly related to the Vue framework. Vite2 uses the plug-in mechanism. Plug-ins are the core of Vitejs
// Path substitution parsing JS CSS injection script code is done by plug-ins
// Vite makes use of rollup's plugin system
// Vitejs is a rollup plugin based on rollup.
Copy the code

6.1 resolvePlugins

function resolvePlugins(config, prePlugins, normalPlugins,postPlugins ) {
  const isBuild = config.command === 'build';
  // A push of plugins
  return [
    isBuild ? null : preAliasPlugin(),
    aliasPlugin({ entries: config.resolve.alias }), ... prePlugins, dynamicImportPolyfillPlugin(config), resolvePlugin({ ... config.resolve,root: config.root,
      isProduction: config.isProduction,
      isBuild,
      ssrTarget: config.ssr? .target,asSrc: true}), htmlInlineScriptProxyPlugin(), cssPlugin(config), config.esbuild ! = =false ? esbuildPlugin(config.esbuild) : null,
    jsonPlugin(
      {
        namedExports: true. config.json }, isBuild ), wasmPlugin(config), webWorkerPlugin(config), assetPlugin(config), ... normalPlugins, definePlugin(config), cssPostPlugin(config), ... buildPlugins.pre, ... postPlugins, ... buildPlugins.post,// internal server-only plugins are always applied after everything else. (isBuild ? [] : [clientInjectionsPlugin(config), importAnalysisPlugin(config)]) ].filter(Boolean)}Copy the code

6.2 createPluginContainer

// Centrally manage plug-ins
async function createPluginContainer() {
  class Context {
    parse(code, opts) {
      // acorn.Parser
      return parse.parse(code, {})
    }
    getModuleInfo(){}}// Context for transform hooks
  class TransformContext {}const container = {
    options: {},
    async buildStart() {
      await Promise.all(
        plugins.map((plugin) = > {
          if (plugin.buildStart) {
            return plugin.buildStart.call(
              new Context(plugin),
              container.options
            )
          }
        })
      )
    },
    async resolveId() {
      await plugin.resolveId
    },
    async load() {
      // Perform the load of the plug-in
      await plugin.load()
    },
    async transform(code, id, inMap, ssr) {
      // Execute the plugin's transform
      await plugin.transform()
    }
  }
  return container
}
Copy the code

Vite 6.3 – the plugin – vue

// postHooks.push(await plugin.configureServer(server)
// postHooks.forEach((fn) => fn && fn())
function vuePlugin(rawOptions) {
 let options = {}
 return {
  name: 'vite:vue'.configureServer(server) {
    options.devServer = server
  },
  load() {},
  // Parse the module
  transform(code, id) {
    / / vite1
    transformMain()
    transformTemplateAsModule()
    transformStyle()
  }
 }
}
Copy the code

6.4 transformMiddleware

function transformMiddleware(server) {
  const { config: { root, logger, cacheDir }, moduleGraph } = server
  return async function viteTransformMiddleware(req, res, next) {
    // resolve, load and transform using the plugin container
    const result = await transformRequest()
    if(result) return RTCRtpSender(req, res, result.code)
  }
}

function transformRequest(url, server) {
  // resolve
  const id = (awaitpluginContainer.resolveId(url))? .id || url// load This is the load and transform methods that are executed to the plug-in
  const loadResult = await pluginContainer.load(id, ssr)
  // transform
  const transformResult = await pluginContainer.transform(code, id, map, ssr)
}
Copy the code

7.hmr

// Module hot replacement Vitejs is native ESM in browser request source code first converted in return HMR faster than WebPack

Create a WS service on the server
const ws = createWebSocketServer(httpServer, config, httpsOptions)
// 2. Create client WS with the script injected in index.html
// <script type="module" src="/@vite/client"></script> 
const socket = new WebSocket()
// 3. Listen for file changes
const watcher = chokidar.watch()
// 4. Send messages
Copy the code

7.1 createWebSocketServer

function createWebSocketServer(server, config) {
  // 1. Create the WS service noServer and enable the serverless mode
  let wss = new WebSocket.Server({ noServer: true })
  return {
    send(payload) {
      const stringified = JSON.stringify(payload)
      // Send a message to the client.
      wss.clients.forEach((client) = > {
        if (client.readyState === WebSocket.OPEN) {
          client.send(stringified)
        }
      })
    }
  }
}
Copy the code

7.2 the client

Create a WS client
const socket = new WebSocket(`${socketProtocol}: / /${socketHost}`.'vite-hmr')
// 2. Listen for information on the server
socket.addEventListener('message'.async ({ data }) => {
  handleMessage(JSON.parse(data))
})
// 3. HandleMessage handles different messages
function handleMessage(payload) {
  switch (payload.type) {
    case 'connected': / / the connection
      console.log(`[vite] connected.`)
      break
    case 'update': / / update
      // Focus on
      payload.updates.forEach(update= > {
        if(update.type === 'js-update') {
          // await Promise.all()
          queueUpdate(fetchUpdate(update))
        } else { // css-update
          let { path } = update
          const el = ([].slice.call(document.querySelectorAll(`link`)) )
            .find((e) = > e.href.includes(path))
          el.href = new URL(newPath, el.href).href
        }
      })
    case 'custom': {
      break
    }
    case 'full-reload':
      location.reload()
      break
    default: {
      const check = payload
      return check
    }
  }
}
Copy the code

7.3. watch

watcher.on('change'.async file => {
  moduleGraph.onFileChange(file)
  // ws.send()
  await handleHMRUpdate(file, server) 
})

watcher.on('add'.(file) = > {
  handleFileAddUnlink(normalizePath(file), server)
})

watcher.on('unlink'.(file) = > {
  handleFileAddUnlink(normalizePath(file), server, true)})Copy the code

7.4 handleHMRUpdate

async function handleHMRUpdate(file, server) {
  const { ws, config, moduleGraph } = server
  if (file.startsWith(normalizedClientDir)) {
    // Use ws.send to send messages
    ws.send({
      type: 'full-reload'.path: The '*'
    })
    return
  }
  // Get the corresponding module
  const mods = moduleGraph.getModulesByFile(file)
  // Update the module
  updateModules(shortFile, hmrContext.modules, timestamp, server)
}

function updateModules(shortFile, hmrContext.modules, timestamp, server) {
  const updates = [] / / update
  for (const mod of modules) {
    const boundaries  = new Set(a)// boundaries.add()propagateUpdate(mod, timestamp, boundaries) updates.push( ... [...boundaries].map(({ boundary, acceptedVia }) = > ({
        type: `${boundary.type}-update`,
        timestamp,
        path: boundary.url,
        acceptedPath: acceptedVia.url
      }))
    )
  }
  // Send a message
  ws.send({
    type: 'update',
    updates
  })
}
Copy the code

Summary of differences between Vite2 and Vite1

  1. HTTP + Connect replaces KOA
  2. Preoptimization uses esbuild instead of rollup
  3. Plug-in mechanism

Vite advantages

  1. Vite is a local service that does not have the packaging process to directly request the source code return process using middleware to transform the source code module partition provided by the browser
  2. Node_modules /.vite uses esBuild to cache dependencies
  3. Compiling TS using ESBuild is faster than TSC. Esbuild’s HMR API is faster
  4. CSS Preprocessor postCss cssModule
  5. Asynchronous chunk
  6. Plug-in convenient extension compatible with rollup plug-in
  7. Fast in a word