In-depth Vite core compilation capabilities

We know that Vite implements an on-demand loading server during the development phase. Every file request will go through a series of compilation processes, and then Vite will respond to the browser with the compilation results. In the production environment, Vite will also perform a series of compilation processes, and pass the compilation results to Rollup for module packaging. This series of compilation process refers to Vite's plug-in workflow (Pipeline), how is it implemented?

1. Plug-in container

Through the previous introduction "How Vite is implemented on the shoulders of giants", we know that Vite's plug-in mechanism is compatible with Rollup, but its implementation in the development and production environments is slightly different. You can review this architecture picture.

insert image description here

It can be seen that in the development environment, Vite simulates the plugin mechanism of Rollup and designs a PluginContainer object to schedule each plugin; in the production environment, Vite directly calls Rollup to package, so Rollup can schedule various plugins.

Therefore, in the development environment, PluginContainer is very important. In fact, the implementation of PluginContainer is based on the rollup-plugin-container.js in WMR, which is mainly divided into two parts:

  • Implement the scheduling of the Rollup plugin hook
  • Implement the Context context object inside the plug-in hook

First, we can look at the implementation of each Rollup hook through the definition of container. The following is the simplified code:

const container = {
  // 异步串行钩子
  options: await (async () => {
    let options = rollupOptions
    for (const plugin of plugins) {
      if (!plugin.options) continue
      options =
        (await plugin.options.call(minimalContext, options)) || options
    }
    return options;
  })(),
  // 异步并行钩子
  async buildStart() {
    await Promise.all(
      plugins.map((plugin) => {
        if (plugin.buildStart) {
          return plugin.buildStart.call(
            new Context(plugin) as any,
            container.options as NormalizedInputOptions
          )
        }
      })
    )
  },
  // 异步优先钩子
  async resolveId(rawId, importer) {
    // 上下文对象,后文介绍
    const ctx = new Context()


    let id: string | null = null
    const partial: Partial<PartialResolvedId> = {}
    for (const plugin of plugins) {
      const result = await plugin.resolveId.call(
        ctx as any,
        rawId,
        importer,
        { ssr }
      )
      if (!result) continue;
      return result;
    }
  }
  // 异步优先钩子
  async load(id, options) {
    const ctx = new Context()
    for (const plugin of plugins) {
      const result = await plugin.load.call(ctx as any, id, { ssr })
      if (result != null) {
        return result
      }
    }
    return null
  },
  // 异步串行钩子
  async transform(code, id, options) {
    const ssr = options?.ssr
    // 每次 transform 调度过程会有专门的上下文对象,用于合并 SourceMap,后文会介绍
    const ctx = new TransformContext(id, code, inMap as SourceMap)
    ctx.ssr = !!ssr
    for (const plugin of plugins) {
      let result: TransformResult | string | undefined
      try {
        result = await plugin.transform.call(ctx as any, code, id, { ssr })
      } catch (e) {
        ctx.error(e)
      }
      if (!result) continue;
      // 省略 SourceMap 合并的逻辑 
      code = result;
    }
    return {
      code,
      map: ctx._getCombinedSourcemap()
    }
  },
  // close 钩子实现省略
}

In the previous article "In-depth understanding of Rollup's plug-in mechanism", we have systematically studied the execution principles of asynchronous, serial, parallel and other hook types in Rollup. Now look at the principle of PluginContainer is very simple.

However, it is worth noting that when various hooks are called, Vite will force the this of the hook function to be bound as a context object:

const ctx = new Context()
const result = await plugin.load.call(ctx as any, id, { ssr })

We know that in the Rollup hook function, we can call many context methods such as this.emitFile and this.resolve. Therefore, in addition to simulating the execution process of each plug-in, Vite also needs to simulate the context object of the plug-in execution. The Context object in the code is used to accomplish this. Let's take a look at the specific implementation of the Context object:

import { RollupPluginContext } from 'rollup';
type PluginContext = Omit<
  RollupPluginContext,
  // not documented
  | 'cache'
  // deprecated
  | 'emitAsset'
  | 'emitChunk'
  | 'getAssetFileName'
  | 'getChunkFileName'
  | 'isExternal'
  | 'moduleIds'
  | 'resolveId'
  | 'load'
>


const watchFiles = new Set<string>()


class Context implements PluginContext {
  // 实现各种上下文方法
  // 解析模块 AST(调用 acorn)
  parse(code: string, opts: any = {}) {
    return parser.parse(code, {
      sourceType: 'module',
      ecmaVersion: 'latest',
      locations: true,
      ...opts
    })
  }
  // 解析模块路径
  async resolve(
    id: string,
    importer?: string,
    options?: { skipSelf?: boolean }
  ) {
    let skip: Set<Plugin> | undefined
    if (options?.skipSelf && this._activePlugin) {
      skip = new Set(this._resolveSkips)
      skip.add(this._activePlugin)
    }
    let out = await container.resolveId(id, importer, { skip, ssr: this.ssr })
    if (typeof out === 'string') out = { id: out }
    return out as ResolvedId | null
  }


  // 以下两个方法均从 Vite 的模块依赖图中获取相关的信息
  // 我们将在下一节详细介绍模块依赖图,本节不做展开
  getModuleInfo(id: string) {
    return getModuleInfo(id)
  }


  getModuleIds() {
    return moduleGraph
      ? moduleGraph.idToModuleMap.keys()
      : Array.prototype[Symbol.iterator]()
  }
  
  // 记录开发阶段 watch 的文件
  addWatchFile(id: string) {
    watchFiles.add(id)
    ;(this._addedImports || (this._addedImports = new Set())).add(id)
    if (watcher) ensureWatchedFile(watcher, id, root)
  }


  getWatchFiles() {
    return [...watchFiles]
  }
  
  warn() {
    // 打印 warning 信息
  }
  
  error() {
    // 打印 error 信息
  }
  
  // 其它方法只是声明,并没有具体实现,这里就省略了
}

Obviously, Vite has re-implemented Rollup's PluginContext object, because it is only used in the development phase, so some packaging-related method implementations have been removed. At the same time, the context object is combined with the ModuleGraph in the Vite development stage, that is, the module dependency graph, to realize the HMR during development.

In addition, the transform hook will also bind a plug-in context object, but this object is different from other hooks. The simplified implementation code is as follows:

class TransformContext extends Context {
  constructor(filename: string, code: string, inMap?: SourceMap | string) {
    super()
    this.filename = filename
    this.originalCode = code
    if (inMap) {
      this.sourcemapChain.push(inMap)
    }
  }


  _getCombinedSourcemap(createIfNull = false) {
    return this.combinedMap
  }


  getCombinedSourcemap() {
    return this._getCombinedSourcemap(true) as SourceMap
  }
}

It can be seen that the TransformContext inherits from the previously mentioned Context object, that is to say, the context object of the transform hook has only been extended compared to other hooks, adding the function of merging sourcemaps, and returning the sourcemap after executing the transform hooks of different plug-ins Merging is done to ensure the accuracy and completeness of the sourcemap.

2. Plug-in Workflow

Next, let's focus on the implementation of resolvePlugins, where all Vite plugins are collected. The specific implementation is as follows:

export async function resolvePlugins(
  config: ResolvedConfig,
  prePlugins: Plugin[],
  normalPlugins: Plugin[],
  postPlugins: Plugin[]
): Promise<Plugin[]> {
  const isBuild = config.command === 'build'
  // 收集生产环境构建的插件,后文会介绍
  const buildPlugins = isBuild
    ? (await import('../build')).resolveBuildPlugins(config)
    : { pre: [], post: [] }


  return [
    // 1. 别名插件
    isBuild ? null : preAliasPlugin(),
    aliasPlugin({ entries: config.resolve.alias }),
    // 2. 用户自定义 pre 插件(带有`enforce: "pre"`属性)
    ...prePlugins,
    // 3. Vite 核心构建插件
    // 数量比较多,暂时省略代码
    // 4. 用户插件(不带有 `enforce` 属性)
    ...normalPlugins,
    // 5. Vite 生产环境插件 & 用户插件(带有 `enforce: "post"`属性)
    definePlugin(config),
    cssPostPlugin(config),
    ...buildPlugins.pre,
    ...postPlugins,
    ...buildPlugins.post,
    // 6. 一些开发阶段特有的插件
    ...(isBuild
      ? []
      : [clientInjectionsPlugin(config), importAnalysisPlugin(config)])
  ].filter(Boolean) as Plugin[]
}

From the above code, we can conclude the specific execution sequence of the Vite plugin:

  • Alias ​​plugins include vite:pre-alias and @rollup/plugin-alias for path alias substitution.
  • User-defined pre plugins, that is, custom plugins with the enforce: "pre" attribute.
  • Vite core build plug-ins, this part of the plug-ins is the core compilation plug-ins of Vite, the number is relatively large, we will disassemble them one by one in the next part.
  • Common user-defined plug-ins, that is, custom plug-ins without the enforce attribute.
  • Plugins with enforce: "post" attribute in Vite production environment plugin and user plugin.
  • Some development-specific plug-ins include environment variable injection plug-in clientInjectionsPlugin and import statement analysis and rewriting plug-in importAnalysisPlugin.

So, what plugins does Vite apply during execution, and what do these plugins do internally? Next, let's sort it out one by one.

Three, plug-in function carding

In addition to user-defined plug-ins, the Vite built-in plug-ins we need to sort out are as follows:

  • alias plugin
  • core build plugin
  • Production-specific plugins
  • Development environment specific plug-ins

3.1 Alias ​​plugin

There are two alias plugins, namely vite:pre-alias and @rollup/plugin-alias. The former is mainly to redirect the bare import path to the path of pre-built dependencies, such as:

// 假设 React 已经过 Vite 预构建
import React from 'react';
// 会被重定向到预构建产物的路径
import React from '/node_modules/.vite/react.js'

The latter implements a more general path alias (namely resolve.alias configuration), using the official Rollup Alias ​​plug-in .

3.2 Core build plugin

3.2.1 Polyfill for module preload feature

When we enable the following configuration in the Vite configuration file, Vite will automatically apply the modulePreloadPolyfillPlugin plugin:

{
  build: {
    polyfillModulePreload: true
  }
}

In the above configuration, its function is to inject the Polyfill code of module preload. The specific implementation is taken from the es-module-shims library we mentioned before. The implementation principle is as follows:

  • Scan all the current modulepreload tags, get the address corresponding to the link tag, and implement preloading by executing fetch;
  • At the same time, it monitors the changes of DOM through MutationObserver. Once a link tag containing the modulepreload attribute is found, preloading is also realized through a fetch request.

It should be noted that since some browsers that support native ESM do not support module preload, in some cases it is necessary to inject the corresponding polyfill for downgrade.

3.2.2 Path resolution plug-in

The path resolution plugin (vite:resolve) is a core plugin in Vite. Almost all important Vite features are inseparable from the implementation of this plugin, such as relying on pre-build, HMR, SSR and so on. At the same time, it is also a fairly complex plugin. On the one hand, it implements the official resolve algorithm of Node.js , and on the other hand, it needs to support the above-mentioned features. It can be said that it implements a set of path resolution algorithms specifically for Vite.

3.2.3 Inline script loading plugin

For inline scripts in HTML, Vite will load them through the vite:html-inline-script-proxy plugin. For example, the following script tag:

<script type="module">
import React from 'react';
console.log(React)
</script>

These contents will be removed from the HTML code in the subsequent build-html plug-in, and the following line of code will be inserted into the code of the project entry module, as follows.

import '/User/xxx/vite-app/index.html?http-proxy&index=0.js'

And vite:html-inline-script-proxy is used to load such modules, the implementation is as follows:

const htmlProxyRE = /?html-proxy&index=(\d+).js$/


export function htmlInlineScriptProxyPlugin(config: ResolvedConfig): Plugin {
  return {
    name: 'vite:html-inline-script-proxy',
    load(id) {
      const proxyMatch = id.match(htmlProxyRE)
      if (proxyMatch) {
        const index = Number(proxyMatch[1])
        const file = cleanUrl(id)
        const url = file.replace(normalizePath(config.root), '')
        // 内联脚本的内容会被记录在 htmlProxyMap 这个表中
        const result = htmlProxyMap.get(config)!.get(url)![index]
        if (typeof result === 'string') {
          // 加载脚本的具体内容
          return result
        } else {
          throw new Error(`No matching HTML proxy module found from ${id}`)
        }
      }
    }
  }
}

3.2.4 CSS compilation plugin

That is, the plug-in named vite:css mainly realizes the following functions:

  • Compilation of CSS preprocessors
  • CSS Modules
  • Postcss compile
  • Record dependencies through @import, which is convenient for HMR

3.2.5 Esbuild plugin

That is, the vite:esbuild plugin is used for .js, .ts, .jsx and tsx, replacing the traditional Babel or TSC functions, which is one of the reasons why Vite has strong performance during the development stage. The main logic in the plugin is the transformWithEsbuild function, as the name suggests, you can use this function for code translation.

Of course, Vite itself also exports this function. As a general transform capability, we can use it in the following ways:

import { transformWithEsbuild } from 'vite';


// 传入两个参数: code, filename
transformWithEsbuild('<h1>hello</h1>', './index.tsx').then(res => {
  // {
  //   warnings: [],
  //   code: '/* @__PURE__ */ React.createElement("h1", null, "hello");\n',
  //   map: {/* sourcemap 信息 */}
  // }
  console.log(res);
})

3.2.6 Static resource loading plugin

Static resource loading plugins include the following:

  • vite:json is used to load JSON files. The dataToEsm method of @rollup/pluginutils can be used to import JSON by name. For specific implementation, see the json link ;
  • vite:wasm is used to load files in .wasm format, see wasm link for specific implementation ;
  • vite:worker is used for Web Worker scripts, and Rollup will be used inside the plug-in to package Worker scripts. For details, see the works link ;
  • vite:asset, in the development stage, the loading of static resources in other formats is realized, and the production environment will rewrite the address of the static resource to the file address of the product through the renderChunk hook, such as rewriting ./img.png to https://cdn.xxx .com/assets/img.91ee297e.png .

It is worth noting that Rollup itself has an asset cascade problem, that is, the hash of the static resource is updated, but the hash of the JS that references it is not updated (issue link). Therefore, when Vite processes static resources, it does not hand over to Rollup to generate resource hashes. Instead, it generates hashes based on resource content and manually rewrites paths to avoid asset-cascade problems.

3.3 Production environment plug-in

3.3.1 Global variable replacement plugin

Provide global variable replacement function, add the following configuration.

// vite.config.ts
const version = '2.0.0';


export default {
  define: {
    __APP_VERSION__: `JSON.stringify(${version})`
  }
}

The function of global variable replacement is similar to that of the @rollup/plugin-replace plugin we mentioned earlier. Of course, Vite will be different in implementation, mainly in the following two points:

  • In the development environment, Vite will save compilation overhead by mounting all global variables to the window object without going through the process of the define plug-in;
  • In the production environment, Vite will use the define plugin to perform string replacement and sourcemap generation.

As a special case, SSR builds will go through this plugin in the development environment and only replace strings.

3.3.2 CSS post-processing plugin

The CSS post-processing plug-in is the plug-in named vite:css-post. Its functions include CSS response result processing in the development stage and CSS file generation in the production environment.

First, in the development stage, this plug-in will package the result processed by the previous CSS compilation plug-in into an ESM module, return it to the browser, and click to view the implementation code .

Secondly, in the production environment, Vite will use this plug-in to split CSS code by default, that is, for each asynchronous chunk, Vite will package the CSS code it depends on separately into a file. The key code is as follows (source code link ) :

const fileHandle = this.emitFile({
  name: chunk.name + '.css',
  type: 'asset',
  source: chunkCSS
});

If the CSS code splitting function is turned off (configured through build.cssCodeSplit), then Vite will package all CSS codes into the same CSS file, click to view the implementation . Finally, the plug-in will call Esbuild to compress the CSS, which is implemented in the minifyCSS function, click to view the implementation .

3.3.3 HTML build plugin

The HTML build plugin is the build-html plugin. As we mentioned before in the inline script loading plugin, the html in the root directory of the project will be converted into a piece of JavaScript code, as follows:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body>
  // 普通方式引入
  <script src="./index.ts"></script>
  // 内联脚本
  <script type="module">
    import React from 'react';
    console.log(React)
  </script>
</body>
</html>

First, when Vite transforms this entry HTML in the production environment, it will do three things:

  • Execute the transformIndexHtml hook in each plugin with enforce: "pre" attribute on HTML;
  • Delete the content of the script tag and convert it into an import statement such as import './index.ts', and record it;
  • Return the recorded import content in the transform hook, and load the import statement as the module content.

In other words, although Vite processes an HTML file, the final packaged content is a piece of JS content. The simplified code is as follows:

export function buildHtmlPlugin() {
  name: 'vite:build',
  transform(html, id) {
    if (id.endsWith('.html')) {
      let js = '';
      // 省略 HTML AST 遍历过程(通过 @vue/compiler-dom 实现)
      // 收集 script 标签,转换成 import 语句,拼接到 js 字符串中
      return js;
    }
  }
}

Secondly, in the last step of generating the product, that is, in the generateBundle hook, the entry Chunk is obtained, the content of the entry Chunk is analyzed, and then processed according to the situation.

If there is only an import statement, first obtain all the dependent chunks of the entry chunk through the chunk and bundle objects provided by Rollup, and arrange these chunks in sequence, such as a depends on b, b depends on c, and the final dependent array is [c, b, a]. Then generate three script tags from c, b, and a in turn, and insert them into the HTML. Finally, Vite will remove the content of the entry chunk from the bundle product, so its content only needs to be an import statement, and the imported chunk has been inserted into the HTML as a script tag, so the existence of the entry chunk is meaningless.

If there are other contents besides the import statement, Vite will separately generate a script tag for the entry chunk, analyze the sequence of dependencies (same as the analysis method in the previous case), and then inject the dependency chunk of the entry file by injecting the tag to preload.

Finally, the plug-in will call the transformIndexHtml hook with the attribute enforce: “post” in the user plug-in to further process the HTML, and the source code link .

3.3.4 Commonjs conversion plugin

We know that in the development environment, Vite uses Esbuild to convert Commonjs to ESM, while in the production environment, Vite directly uses Rollup's official plugin @rollup/plugin-commonjs.

3.3.5 date-uri items

The date-uri plugin is used to support the case that the import module contains Base64 encoding, and the usage is as follows:

import batman from 'data:application/json;base64, eyAiYmF0bWFuIjogInRydWUiIH0=';

The relevant implementation source link is as follows: https://github.com/vitejs/vite/blob/2b7e836f84b56b5f3dc81e0f5f161a9b5f9154c0/packages/vite/src/node/plugins/dataUri.ts#L14

3.3.6 dynamic-import-vars plugin

It is used to support the function of using variables in dynamic import, as shown in the following sample code:

function importLocale(locale) {
  return import(`./locales/${locale}.js`);
}

Internally, Rollup's official plugin @rollup/plugin-dynamic-import-vars is used.

3.3.7 import-meta-url plugin

It is used to convert the resource URL in the following format, and the usage method is as follows:

new URL('./foo.png', import.meta.url)

Convert it to the URL format of the production environment, as follows:

// 使用 self.location 来保证低版本浏览器和 Web Worker 环境的兼容性
new URL('./assets.a4b3d56d.png, self.location)

Of course, dynamic import can also be supported, as in the following writing method:

function getImageUrl(name) {
  return new URL(`./dir/${name}.png`, import.meta.url).href
}

Vite recognizes a template string like ./dir/${name}.png, and converts the entire line of code into the following:

function getImageUrl(name) {
    return import.meta.globEager('./dir/**.png')[`./dir/${name}.png`].default;
}

Click to view the specific implementation

3.3.8 Production environment import analysis plug-in

The vite:build-import-analysis plug-in will be used for import statement analysis and rewriting when packaging in the production environment. The main purpose is to preload the dynamically imported modules.

For a chunk with dynamic import, such a piece of tool code will be added to the tranform hook of the plug-in for module preloading. The logic is not complicated, and you can refer to the source code for implementation . The key code is simplified as follows:

function preload(importModule, deps) {
  return Promise.all(
    deps.map(dep => {
      // 如果异步模块的依赖还没有加载
      if (!alreadyLoaded(dep)) { 
        // 创建 link 标签加载,包括 JS 或者 CSS
        document.head.appendChild(createLink(dep))  
        // 如果是 CSS,进行特殊处理,后文会介绍
        if (isCss(dep)) {
          return new Promise((resolve, reject) => {
            link.addEventListener('load', resolve)
            link.addEventListener('error', reject)
          })
        }
      }
    })
  ).then(() => importModule())
}

We know that Vite has a built-in CSS code splitting capability. When a module is introduced through dynamic import, this module will be packaged into a chunk separately, and the style code in this module will also be packaged into a separate CSS file. If the CSS and JS of the asynchronous module are preloaded at the same time, FOUC problems will occur in some browsers (such as IE), and the page style will flicker, affecting user experience. However, Vite solves the FOUC problem by monitoring the load event of the link tag to ensure that the CSS is loaded before the JS. You can pay attention to the following key code:

if (isCss) {
  return new Promise((res, rej) => {
    link.addEventListener('load', res)
    link.addEventListener('error', rej)
  })
}

Now that we know how to implement preloading, how does Vite compile dynamic imports into preloading code? From the implementation of the transform hook in the source code, it is not difficult to find that Vite will transform the code of dynamic import, as shown in the following code:

// 转换前
import('a')
// 转换后
__vitePreload(() => 'a', __VITE_IS_MODERN__ ?"__VITE_PRELOAD__":void)

Among them, __vitePreload will be loaded as the preload tool function in the previous article, VITE_IS_MODERN will be replaced with true or false in renderChunk, indicating whether it is packaged in Modern mode, and for " VITE_PRELOAD ", Vite will analyze all modules of a module in the generateBundle stage Dependent files (including CSS), pass an array of dependent filenames as the second parameter of the preload utility function.

At the same time, Vite's unique import.meta.glob syntax will also be compiled in this plugin, such as:

const modules = import.meta.glob('./dir/*.js')

The above code will be transformed into the following code through the plugin:

const modules = {
  './dir/foo.js': () => import('./dir/foo.js'),
  './dir/bar.js': () => import('./dir/bar.js')
}

The specific implementation is in the transformImportGlob function. In addition to being used by the plug-in, this function is also used by core processes such as pre-build and development environment import analysis, which belongs to a relatively low-level logic.

3.3.9 JS compression plugin

Vite provides two tools for JS code compression, namely Esbuild and Terser, which are respectively implemented by two plug-ins:

  • vite:esbuild-transpile: In the renderChunk stage, call the transform API of Esbuild and specify the minify parameter to achieve JS compression.
  • vite:terser: Also in the renderChunk phase, Vite will call Terser in a separate Worker process to compress JS code.

3.3.10 Building the report plugin

Three plugins are mainly used to output build reports: vite:manifest, vite:ssr-manifest and vite:reporter.

  • vite:manifest: Provide packaged resource files and their associated information, as shown below:
// manifest.json
{
  "index.html": {
    "file": "assets/index.8edffa56.js",
    "src": "index.html",
    "isEntry": true,
    "imports": [
      // JS 引用
      "_vendor.71e8fac3.js"
    ],
    "css": [
      // 样式文件应用
      "assets/index.458f9883.css"
    ],
    "assets": [
      // 静态资源引用
      "assets/img.9f0de7da.png"
    ]
  },
  "_vendor.71e8fac3.js": {
    "file": "assets/vendor.71e8fac3.js"
  }
}
  • vite:ssr-manifest: Provides a mapping relationship between each module and chunks, so that during the SSR period, it is convenient to determine which chunks will be used through the rendered components, so as to preload on demand. The final plug-in output is as follows:
// ssr-manifest.json
{
  "node_modules/object-assign/index.js": [
    "/assets/vendor.71e8fac3.js"
  ],
  "node_modules/object-assign/index.js?commonjs-proxy": [
    "/assets/vendor.71e8fac3.js"
  ],
  // 省略其它模块信息
}
  • vite:reporter: mainly provides command line build logs when packaging:
    insert image description here

3.4 Development environment plug-ins

3.4.1 Environment variable injection plug-in

In the development environment, Vite will automatically inject a client script into the HTML:

<script type="module" src="/@vite/client"></script>

This script mainly provides functions such as injecting environment variables, processing HMR update logic, and providing an error reporting interface when an error occurs in the build, and the vite:client-inject we are going to introduce here is to complete the injection of environment variables, and the client script _ _MODE__, BASE , __DEFINE__ and other strings are replaced with runtime variables to realize the injection of environment variables and HMR-related context information, and code linking .

3.4.2 import analysis plugin

Vite will add an import analysis plug-in during the development phase, namely vite:import-analysis, which corresponds to the previously introduced vite:build-import-analysis, and mainly deals with the analysis and rewriting of import statements, but the vite:import-analysis plug-in The focus will be different, mainly around the various features of the Vite development stage, we can sort out what this plug-in needs to do:

  • For bare import, convert the path name to a real file path, such as:
// 转换前
import 'foo'
// 转换后
// tip: 如果是预构建的依赖,则会转换为预构建产物的路径
import '/@fs/project/node_modules/foo/dist/foo.js'

This code mainly calls the context object method of PluginContainer, namely this.resolve, which will call the resolveId method of all plug-ins, including vite:pre-alias and vite:resolve introduced earlier, to complete the core logic of path resolution.

  • For HMR's client API, that is, import.meta.hot, after Vite recognizes such an import statement, it will inject the implementation of import.meta.hot on the one hand, because the browser does not have such an API natively; on the other hand, It will recognize the accept method and judge whether accept is a type that accepts its own update. If it is, mark it as the flag of isSelfAccepting, which is convenient for HMR to search for HMR Boundary when updating on the server side.
  • For the global environment variable reading statement, namely import.meta.env, Vite will inject the implementation of import.meta.env, which is the following env string.
 // config 即解析完的配置
let env = `import.meta.env = ${JSON.stringify({
  ...config.env,
  SSR: !!ssr
})};`
// 对用户配置的 define 对象中,将带有 import.meta.env 前缀的全局变量挂到 import.meta.env 对象上
for (const key in config.define) {
  if (key.startsWith(`import.meta.env.`)) {
    const val = config.define[key]
    env += `${key} = ${
      typeof val === 'string' ? val : JSON.stringify(val)
    };`
  }
}
  • For import.meta.glob syntax, Vite will also call the previously mentioned transformImportGlob function to perform syntax transformation, but it is different from the processing in the production environment. After transformation, Vite will record the dependent modules imported by the module through glob in the server instance so that more accurate module dependency information can be obtained when HMR is updated.

Four. Summary

This article mainly focuses on the implementation mechanism of PluginContainer and the functions of Vite's built-in plugins. First of all, PluginContainer is mainly realized by two parts, including the scheduling of Rollup plug-in hook and the implementation of Context context object inside the plug-in hook, which generally simulates the plug-in mechanism of Rollup. In fact, we introduced the built-in plugins of Vite, including four categories: alias plugins, core build plugins, production-specific plugins and development-specific plugins.

In addition, in the process of learning these plug-ins, we should avoid getting involved in many cumbersome implementation details, and try our best to grasp the key implementation ideas to efficiently understand the principles behind the plug-ins, so that the learning efficiency will be higher. Further speaking, after you understand the implementation principles of each plug-in, if you need to debug the code of some plug-ins in certain scenarios, you can also do it in a targeted manner.

Guess you like

Origin blog.csdn.net/xiangzhihong8/article/details/131695341