It has been more than one year since I got to know Vite and used it in my work. In the words of Anthony Fu, during this period, the Vite community has exploded and a large number of surrounding ecology has also been constantly increasing. That said, there will be some features and plugins that the community may not have kept up with. This situation is very easy to encounter at work, so learn to write your own Vite plugin is very necessary!

The purpose of this article is to supplement and implement the official introductory plug-in tutorial. I hope that by learning this article, you can write a plug-in of your own thinking, or solve the problems encountered in the current work.

Some friends may want to ask, the official plug-in so a few lines of code there is nothing to say! Are you sure? Don’t believe yourself to use the official code into their own Vite project to try it out. To be exact, the official Demo will work fine in Rollup, but not in Vite.

Vite vs Rollup

Before we do that, let’s take a superficial look at what Vite is made of

There are three important concepts in Vite: Server, build, and pre-building, which correspond to the built-in commands vit Serve, Vite build, and Vite Optimizer.

  • Build the Rollup plugin… A short 😢

  • The serve command is a Node.js httpServer. Vite uses connect to connect middleware to handle requests. For example, the important middleware – transformMiddleware and internally introduced transformRequest

Part of Vite source code

// src/node/server/middlewares/transform.ts
export function transformMiddleware(
  server: ViteDevServer
) :Connect.NextHandleFunction

// src/node/server/transformRequest.ts
export function transformRequest(url: string, server: ViteDevServer, options: TransformOptions = {}) :Promise<TransformResult | null> {// 🚧 important: internal implementationRollup pluginOperation mechanism}Copy the code

The transformRequest is responsible for making the request go through a set of plug-ins properly, where Vite executes a bunch of built-in plug-ins. Note that it mimics Rollup and does not use Rollup

Built-in plug-in source code

// src/node/plugins/index.ts

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 [
    isBuild ? metadataPlugin() : null,
    isBuild ? null : preAliasPlugin(),
    aliasPlugin({ entries: config.resolve.alias }),
    // 🚨 key ③ -- write it down. prePlugins, config.build.polyfillModulePreload ? modulePreloadPolyfillPlugin(config) :null.// 🚨 key ① -- Write it down, I'll take the exam laterresolvePlugin({ ... config.resolve,root: config.root,
      isProduction: config.isProduction,
      isBuild,
      packageCache: config.packageCache,
      ssrConfig: config.ssr,
      asSrc: true
    }),
    isBuild ? null: optimizedDepsPlugin(), htmlInlineProxyPlugin(config), cssPlugin(config), config.esbuild ! = =false ? esbuildPlugin(config.esbuild) : null,
    jsonPlugin(
      {
        namedExports: true. config.json }, isBuild ), wasmPlugin(config), webWorkerPlugin(config), workerImportMetaUrlPlugin(config), assetPlugin(config),// 🚨 key ② -- write it down, I'll take the exam later. normalPlugins, definePlugin(config), cssPostPlugin(config), config.build.ssr ? ssrRequireHookPlugin(config) :null. buildPlugins.pre, ... postPlugins, ... buildPlugins.post,// internal server-only plugins are always applied after everything else. (isBuild ? [] : [clientInjectionsPlugin(config), importAnalysisPlugin(config)]) ].filter(Boolean) as Plugin[]
}

Copy the code

It may seem like a lot of plug-ins, but Vite is the next level on the Rollup + built-in plugin set, but there is another important concept before Vite launches – pre-building

  • Optimizer is a self-proclaimed pre-building responsible for building all bare-packages. What is a bare module? Anything that does not start with an absolute path, a relative path, an alias, or a built-in identifier – is a bare module, which sounds like a very complicated subchild! The imprecise one-sentence summary is “NPM module”
// None of them are bare modules
import './mod1'
import '/User/project/src/mod1'
import '@/mod1'
import '/@fs/node_modules/npm-pkg'

// I am a bare module
import 'module-name'
Copy the code

For example, if you have a bare module called vue, the module will be pre-built into node_modules/.vite/vue.js. It is possible to write such a plug-in – it is common to import vue as an external module. Webpack is full of cases

// vite.config.ts
export default {
  plugin: [{name: 'external-vue-plugin'.resolveId(id) {
        if (id === 'vue') {
          return '\ 0' + 'vue'}},load(id) {
        if (id === '\ 0' + 'vue') {
          return `const vue=window.Vue; export default Vue; `}},},],}Copy the code

So easy an external plug-in, sent to NPM last week download a few k is not difficult, interview installation force no problem ~ 🤩

Remember the above 🚨 focus ①, and your plugin is listed at 🚨 focus ②; At runtime, your plugin won’t have a chance to handle the bare vue module. Instead, the built-in resolvePlugin will be passed to the Optimizer to be pre-built. Node_modules /.vite/vue.js prebuilt modules will be hit first, regardless of the load hook you wrote. This is why the official Demo may not work as expected

Implement a Demo that works as expected

Now that we’ve implemented it ourselves, don’t always call it Demo, just like calling someone else three li Four; Call it “vite-plugin-resolve”, which is a reference to the built-in resolvePlugin.

We can advance our plug-in to the key ③ at 🚨 to achieve our desired behavior.

// vite.config.ts
export default {
  plugin: [{name: 'vue-plugin-resolve'.// I want to go to the "🚨 key ③" location
      enforce: 'pre'.resolveId(id) { /* Same as the original logic */ },
      load(id) { /* Same as the original logic */ },
      config(config) {
        // Tell Vite not to pre-order Vite modules
        config.optimizeDeps.exclude.push('vue'); }}},],Copy the code

Well! As simple as that, two changes could make the official Vite Demo perfect. Isn’t it amazing!

Don’t underestimate this plugin, it can do almost anything you want, such as externals, loading predefined templates, and any executable JS code. Great space to play! For example, our plugin also supports custom returns:

import resolve from 'vite-plugin-resolve'

export default {
  plugins: [
    resolve({
      // externals for Webpack
      vue: `const vue = window.Vue; export { vue as default }`.// Use Promise to read a template file
      '@scope/name': () = > require('fs').promises.readFile('path'.'utf-8'),

      // Use in Electron
      electron: `const { ipcRenderer } = require('electron'); export { ipcRenderer }; `,})],}Copy the code

The complete code 👉 viet-plugin-resolve

NPM I vite-plugin-resolve -d

summary

The thinking triggered by a small Demo can be very powerful in real life. Hopefully this article has given you a better understanding of the Vite plugin, and you won’t be deterred by the fear that there aren’t enough community plugins out there. At the same time, I also hope that Vite ecology has more partners to join and build Vite this big ecology!