Does Vite satisfy you?

The previous article explained how to configure your own projects using Vite, and with the exception of the Router section, which demonstrates how to use vuE-Router with Vue3, the rest of the configuration is off frame. At the end of the article said that Vite’s current supporting facilities are not particularly comprehensive, so sometimes it is hard to avoid the need to “move” “do it yourself, have enough food and clothing”. Use React with ANTD as an example to teach you how to write your own Vite plugin πŸ“

See what ha πŸ₯·

Use Vite to create a React + Typescript project and install ANTD. After that, we will introduce the Button component in app.tsx and display it on the page:

function App() {
	// ...

  return (
    <div className="App">{/ *... * /}<Button type="primary">Make your own food and clothing</Button>
    </div>
  );
}
Copy the code

πŸ—£ WDNMD, how about the primary style?

πŸ‘‰ do you have a P style without quoting the style file?

Once we’ve introduced styles in main.tsx, we’re done:

import 'antd/dist/antd.min.css';
Copy the code

But why don’t we use Webpack without manually importing styles? It’s not Webpack but the Babel plugin babel-plugin-import:

// babel.config.js
module.exports = {
  plugins: [
    // ...
    [
      'import',
      {
        libraryName: 'antd'.libraryDirectory: 'es'.style: true,},],],};Copy the code

Since ANTD supports tree shaking based on ES Module by default, the function of babel-plugin-import can be understood simply as adding styles to imported components. Next we’ll write a similar widget ☸️

Make a πŸš€

Vite is a rollup plug-in, and Vite’s API design specification is a reference to rollup, so Vite is actually a rollup plug-in.

Vite plugins extends Rollup’s well-designed plugin interface with a few extra vite-specific options. As a result, you can write a Vite plugin once and have it work for both dev and build.

Plugins also have their own rollup life cycle. If you are interested, you can learn by yourself: rollupjs.org/guide/en/#p… Vite plugin life cycle to explain πŸƒβ™‚οΈ

The life cycle of the invocation when the service is started

options

From this method we can get the configuration content in viet.config. ts. Let’s create a script for the plugin:

// plugin.ts
import { Plugin } from 'vite';

export default function myPlugin() :Plugin {
  return {
    name: 'my-plugin'.options: (options) = > {
      console.info(options);
      return null; }}; };Copy the code

And use the plugin we wrote in vite.config.ts with a few additional configurations, such as alias:

import { defineConfig } from 'vite';
import reactRefresh from '@vitejs/plugin-react-refresh';
import myPlugin from './my-plugin';

// https://vitejs.dev/config/
export default defineConfig({
  plugins: [reactRefresh(), plugin()],
  resolve: {
    alias: {
      The '@': './src',}}})Copy the code

Then we execute yarn Build and take a look at the output in Terminal:

// vite v2.1.3 building for production...
{
  input: '... '.preserveEntrySignatures: false.plugins: [{name: 'alias'.buildStart: [Function: buildStart],
      resolveId: [Function: resolveId]
    },
    {
      name: 'react-refresh'.enforce: 'pre'.configResolved: [Function: configResolved],
      resolveId: [Function: resolveId],
      load: [Function: load],
      transform: [Function: transform],
      transformIndexHtml: [Function: transformIndexHtml]
    },
    {
      name: 'vite:resolve'.configureServer: [Function: configureServer],
      resolveId: [Function: resolveId],
      load: [Function: load]
    },
    {
      name: 'my-plugin'.options: [Function: options],
      buildStart: [Function: buildStart]
    },
    // ...],}Copy the code

You can see the alias, react-Refresh, and other configurations we used in the output above, and you can see that the alias configuration is also supported through the plug-in. Returning NULL during the life cycle indicates that no processing was done to the value passed in.

In the Options life cycle, we can replace or manipulate the options passed to the packaging tool, but if you just want to read the options content, it is more recommended that you do so in the buildStart life cycle below. This is because the Options in the buildStart cycle are transformed, and the Options hook has no access to most plug-in contexts because the cycle is executed before it is configured.

buildStart

Similarly, we print the options argument from the buildStart hook:

{
  acorn: {... },acornInjectPlugins: [...]. .context: 'undefined'.experimentalCacheExpiry: 10.external: [Function].inlineDynamicImports: undefined.input: [ '... '].manualChunks: undefined.moduleContext: [Function].onwarn: [Function].perf: false.plugins: [...]. .// ...
}
Copy the code

In addition to the few properties printed in options, you will find many more properties. This life cycle is performed at each build, where the configuration items in the Options hook are transformed and default values are configured for some options that are set.

The lifecycle of the call when an incoming module request is made

resolveId

The lifecycle receives three parameters: Source, importer, and options. Options has been explained in the first two hooks, so I will not elaborate on it. Now let’s look at the source and importer parameters. To be clear, this hook is used as a custom resolver, such as to locate third-party dependencies. The contents of the source are the paths written in the import statement, such as:

import { throttle } from '.. /lodash';
Copy the code

The source is… / lodash. The second parameter importer is the import module after resolve. When importing resolve, the value of importer will be undefined, so we can use this to define the proxy module of the import. For example, the following official 🌰 exposes the default export in the import file but keeps the named export for internal use:

async resolveId(source,importer) {
  if(! importer) {// Skip this plugin to avoid infinite loops
    const resolution = await this.resolve(source, undefined, { skipSelf: true });
    // If resolve cannot be resolved, null is returned to cause Rollup to fail
    if(! resolution)return null;
    return `${resolution.id}? entry-proxy`;
  }
  return null;
},
load(id) {
  if (id.endsWith('? entry-proxy')) {
    const importee = id.slice(0, -'? entry-proxy'.length);
    // An exception will be thrown if there is no default export
    return `export {default} from '${importee}'; `;
  }
  return null;
}
Copy the code

It’s important to note that in addition to returning NULL, the corresponding Resolved path, and the corresponding object, we can return a Boolean type during the life cycle. When we return false, the source is treated as an external dependency and not packaged.

The resolvedId lifecycle can perform many operations. For details, see rollupjs.org/guide/en/#r…

load

In the load hook, we can customize the loader. To prevent unnecessary parsing, the life cycle has generated an AST using this.parse, and we can optionally return {code, AST, map} objects. The return must be the standard ESTree AST, with each node containing the start and end attributes.

The load life cycle accepts only one ID parameter. For details, see resolveId in the previous life cycle.

transform

In the Transform cycle, the method takes two parameters: code and ID. The second parameter id is the same as the load cycle, and the first code is the converted code. We create a random file utils. Ts in the SRC directory and export a method like this:

// src/utils.ts
export const whatever = (a: number, b: number) = > a === 1 ? a + b : a - b;
Copy the code

Then we print the obtained code in the transform method:

// my-plugin.ts
export default function myPlugin() :Plugin {
  return {
    // ...
    transform: (code, id) = > {
      if (id.includes('utils')) {
        console.log(code);
      }
      return null; }}; }Copy the code

The print result in terminal is obtained:

export const whatever = (a, b) = > a === 1 ? a + b : a - b;
Copy the code

Then, we import the whatever method above in main. TSX and print the main. TSX code:

// main.tsx
import React from 'react';
import ReactDOM from 'react-dom';
import { Button } from 'antd';
import { whatever } from './utils';

ReactDOM.render(
  <>
    <Button type="primary">Make your own food and clothing</Button>
    <h1>Utils -&gt; whatever function result: 1 + 1 = {whatever(1, 1)}</h1>
  </>.document.getElementById('root'));// my-plugin.ts
export default function myPlugin() :Plugin {
  return {
    // ...
    transform: (code, id) = > {
      if (id.includes('main')) {
        console.log(code);
      }
      return null; }}; }// Print the result in terminal
import React from "react";
import ReactDOM from "react-dom";
import {Button} from "antd";
import {whatever} from "./utils";
ReactDOM.render(/* @__PURE__ */ React.createElement(React.Fragment, null./* @__PURE__ */ React.createElement(Button, {
  type: "primary"
}, "\u81EA\u5DF1\u52A8\u624B\uFF0C\u4E30\u8863\u8DB3\u98DF"), /* @__PURE__ */ React.createElement("h1".null."Utils -> whatever function result: 1 + 1 = ", whatever(1.1))), document.getElementById("root"));
Copy the code

Have you found a clue? We can get our transformed code in transform and return our own code. This solves the problem that we introduced antD components and Vite also introduced the corresponding component styles πŸ₯³

How can analysis wave achieve this function πŸ€”

First we can know the components introduced from ANTD, such as: Button, Card, DatePicker, etc., and observing the naming way of folder and style file in ANTD, we can find that we only need to introduce antd/lib/component-name/style/index.css file.

// Matches the incoming component's line of code re
const IMPORT_LINE_REG = `/import {[\w,\s]+} from (\'|\")antd(\'|\"); ? /g`;
// Change the component name to KababCase
const transformToKebabCase = (name: string) = > {
  return name.replace(/([^-])([A-Z])/g.'$1 - $2').toLocaleLowerCase();
};

export default function myPlugin() :Plugin {
  return {
    // ...
    transform: (code, id) = > {
      if (/\"antd\"; /.test(code)) {
        constimportLine = code.match(IMPORT_LINE_REG)! [0];
        const cssLines = importLine
          .match(/\w+/g)! ['import', 'Button', 'from', 'antd']
          .slice(1, -2)	// Result: ['Button']
          .map(name= > `import "antd/lib/${transformToKebabCase(name)}/style/index.css"; `)
        	/ / result: [' import "antd/lib/button/style/index. The CSS ']
          .join('\n');	

        return code.replace(IMPORT_LINE_REG, `${importLine}\n${cssLines}`)}return null; }}; }Copy the code

Take a look at Yarn Dev to start the local development environment and introduce two more components in main.tsx:

Needle no stamp!

Ha 😴 a break

In addition to the five life cycles above, there are two life cycles that are called when the service is closed: buildEnd and closeBundle. You can learn about them on the Rollup website. With the ability to customize plug-ins, we have unlimited possibilities to control the output of our native development and packaging. For example, we can also write a plugin similar to Webpack’s raw-loader. As mentioned in the previous article, we add? To the end of the file name when importing resource types. Raw can be imported as a string using raw-loader, and we can write a plugin to customize the extensions of some files and add? To the files that end with the specified type. Raw, so we don’t have to write it in locally developed code? Raw string up. The implementation of this plug-in is simpler than the above automatic introduction of CSS plug-ins, I believe you can achieve their own!

Is similar to the Babel – plugin – import Vite plugin, ant 🐜 personnel already at the end of 2020 was developed and released open source, called: Vite plugin – import (www.npmjs.com/package/vit)… , we go to see the source code, in fact, it is very simple, is to use Babel to convert it (github.com/meowtec/vit…

async transform(src) {
  if((onlyBuild && ! isBuild) || ! codeIncludesLibraryName(src)) {return undefined;
  }

  const result = await transformAsync(src, {
    plugins: babelImportPluginOptions.map((mod) = > ['import', mod, `import-${mod.libraryDirectory}`])});returnresult? .code; }Copy the code

If you want to write your own plug-ins, please follow the naming conventions for Vite plug-ins:

  • VuePlugin prefix:vite-plugin-vue-;
  • ReactPlugin prefix:vite-plugin-react-;
  • SveltePlugin prefix:vite-plugin-svelte-;
  • Unlimited frame, applicable toVitePlugin prefix:vite-plugin-;

Of course, there are some nice plugins for Vite. If you need to customize some plugins, you can go to awesome-vite first. If you don’t make your own wheels, it’s not too late: github.com/vitejs/awes…

It is also important to note that you need to package plug-ins ascommonjs !commonjs!commonjs!

— No chitchat today, end of story! πŸ•Š Peace & Love ❀️–

Welcome to pay attention to the public number: Refactor, reconstruction for a better yourself!