头图

In 2022, I threw myself into the arms of Vite, began to participate in the Vite community, and developed some plug-ins one after another.

Vite adheres to the idea of out-of-the-box and simplified configuration, which has indeed significantly improved the front-end development experience.

However, there are some deficiencies in the construction of the class library mode. It can only handle the situation of single input and single input and output, the construction scene is single, and there is currently no tool that can be used directly in the Vite community, so it is necessary to develop a unified build plug-in idea.

At present , the vite-plugin-build plug-in can be used directly, and it has also been entered in the official awesome-vite of Vite. I hope it can just meet the needs of some people.

image.png

What is Unity Build?

Because there is no particularly good name, I call this a unified construction for the time being, and I summarize the unified construction as the following construction:

  • Bundle build

    That is, Vite (also Rollup) library packaging mode, single input file, single output bundle file, if no external dependencies (external) are set, all involved dependencies will be packaged into a bundle file.

    Advantages: It supports the umd format, can be used as an external dependency in the browser, is not affected by the business code bundle, and can use the browser cache mechanism to improve the loading performance.

    Disadvantages: The code that does not support Tree Shaking will also be loaded. Since it is packaged into a bundle file, the readability of the local source code is poor.

    image.png

  • Folder construction (file-to-file transformer, file-to-file transformer)

    文件夹所有的符合格式的文件( ['ts', 'tsx', 'js', 'jsx', 'vue', 'svelte'] )会.js文件,只支持commonjs es格式.

    When converting, all import dependent packages will not be packaged in, and converted to commonjs require es the format that needs to be converted. The import syntax.

    Advantages: es mode can support Tree Shaking , and the local source code is highly readable.

    Disadvantages: The code will be packaged into the bundle file together with the business code in the construction tools such as Webpack and Vite, and it is difficult to take advantage of cross-site caching.

    image.png

  • Generate TypeScript declaration file

    Supports the generation of native TypeScript, Vue TypeScript and Svelte TypeScript declaration files (if there are other types of frameworks, they can also be extended here).

vite-plugin-build with Vite @vitejs/plugin-react , @vitejs/plugin-vue , @sveltejs/vite-plugin-svelte support the above three construction methods.

Why develop a unified build plugin for Vite?

The first reason is that Vite has a single construction scenario and does not support the following scenarios:

  • Multiple Input Multiple Output (Multiple Input Multiple Bundle)
  • Convert folder (file-to-file conversion method, not packaged into a bundle file)
  • Generate TypeScript declaration file

The second reason is that the Vite community lacks directly replaceable tools.

The official plugin library on Vite Github uses unbuild , which is a unified build tool. Although it is very convenient, unbuild is more inclined to deal with pure JavaScript or TypeScript code. For React, Vue, Svelte and other browser UI types related Packaging lacks associated conversion processing.

The third reason is that the Vite system provides one-stop service.

Vite's unified construction plug-in can allow Vite and Vitest to form a closed-loop system in some scenarios, without the need to use other construction and unit testing tools, and a Vite configuration file to conquer the world.

idea

Vite Before the rise, the business components in the company group and some personal Github projects were built using Rollup , and Rollup could not be built right out of the box. It also requires various plug-in configurations, which is relatively cumbersome.

Later, using the library mode of Vite to replace the native Rollup can reduce a lot of plug-in configuration, but all the files in the entire folder are converted into commonjs and es format separately, or need to pass Vite provided build API implementation, the following is through all the target files in the specified folder, and then traverse and run build way to achieve (also requires one more Vite configuration file to configure):

 const fs = require('fs');
const path = require('path');
const spawn = require('cross-spawn');
const srcDir = path.resolve(__dirname, '../src');

// 所有 src 文件夹包括子文件夹的 js、ts、jsx、tsx 文件路径数组
const srcFilePaths = getTargetDirFilePaths(srcDir);
srcFilePaths.forEach((file) => {
  const fileRelativePath = path.relative(srcDir, file);
  spawn(
    'npm',
    ['run', 'vite', '--', 'build', '--mode', fileRelativePath, '--outDir', 'es', '--config', './vite.file.config.ts'],
    {
      stdio: 'inherit',
    },
  );
});

At the same time, you also need to configure npm run tsc to generate a declaration file, pacakge.json scripts fields are more complicated:

 {
  "scripts": {
    "tsc:es": "tsc --declarationDir es",
    "tsc:lib": "tsc --declarationDir lib",
    "tsc": "npm run tsc:lib && npm run tsc:es",
    "vite": "vite",
    "build:lib": "vite build",
    "build:file": "node ./scripts/buildFiles.js",
    "build": "npm run tsc && npm run build:file && npm run build:lib"
  }
}

For a new project, just copy and modify it directly. Although it can also achieve the purpose of construction, it is not convenient enough. I am lazy , so I still wonder if there is a simpler way? Can it be solved by using one as follows script ?

 {
  "scripts": {
    "build": "vite build"
  }
}

Realize ideas

First of all, the function of unified construction cannot be achieved through the Vite configuration file, that is, the function of unified construction cannot be achieved by running the Vite build service once normally.

Still have to run the Vite build service multiple times to achieve this functionality (ostensibly without user awareness).

Vite 统一构建流程.png

key points to achieve

Bundle build

The Vite library mode is the bundle build mode, but only one entry file and one bundle output file can be set.

The actual scene may require multiple entry files and multiple bundle output files. This function is not complicated. It can be realized by traversing multiple vite build builds. The code is roughly as follows:

 import { build } from 'vite';

const buildPs = lastBuildOptions.map((buildOption) => {
  return build({
    ...viteConfig, // 透传 vite.config.ts 或者 vite.config.js 的用户配置,插件需要过滤自身(vite-plugin-build)
    mode: 'production',
    configFile: false,
    logLevel: 'error',
    build: buildOption,
  });
});
await Promise.all(buildPs);

folder build

The implementation idea is not complicated, as follows:

  1. Get all matching file paths in a folder
  2. Traverse all file paths, run vite build build
  3. Since the import of Vue and Svelte needs a suffix name, it is necessary to additionally remove the .vue and .svelte suffix names in the file content.

The simple code implementation is roughly as follows:

 import { build } from 'vite';
import fg from 'fast-glob';

const {
  inputFolder = 'src',
  extensions = ['ts', 'tsx', 'js', 'jsx', 'vue', 'svelte'],
  ignoreInputs,
  ...restOptions,
} = options;
// 获取默认为项目根目录下 src 文件夹包括子文件夹的所有 js、ts、jsx、tsx、vue、sevele 文件路径,除开 .test.*、.spec.* 和 .d.ts 三种后缀名的文件
// 返回格式为 ['src/**/*.ts', 'src/**/*.tsx']
const srcFilePaths = fg.sync([`${inputFolder}/**/*.{${extensions.join(',')}}`], {
  ignore: ignoreInputs || [`**/*.spec.*`, '**/*.test.*', '**/*.d.ts', '**/__tests__/**'],
});
const buildPromiseAll = srcFilePaths.map((fileRelativePath) => build({
  ...viteConfig, // 透传 vite.config.ts 或者 vite.config.js 的用户配置,插件需要过滤自身(vite-plugin-build)
  mode: 'production',
  configFile: false,
  logLevel: 'error',
  build: {
        lib: {
      entry: fileRelativePath,
            ...
    },
    ...restOptions
  },
}));
await Promise.all(buildPromiseAll);
await removeSuffix(); // 移除生成文件内容中的 `.vue` 和 `.svelte` 后缀名

TypeScript declaration file generation

There are various front-end UI frameworks, such as Vue and Svlete, which have their own custom syntax. TypeScript syntax needs special support, and the generation of declaration files naturally requires special processing.

Native TypeScript

The native TypeScript officially provides tsc tools can be used directly, by directly running the tsc bin file, and passing the corresponding configuration to achieve.

The simple code implementation is as follows:

 import spawn from 'cross-spawn';

const { rootDir, outputDir } = options;
const tscPath = path.resolve(require.resolve('typescript').split('node_modules')[0], 'node_modules/.bin/tsc');
spawn.sync(
  tscPath,
  ['--rootDir', rootDir, '--declaration', '--emitDeclarationOnly', '--declarationDir', outputDir],
  {
    stdio: 'ignore',
  },
);

Vue TypeScript

After Vue 3 comes out, the support for TypeScript is relatively complete. The Vue community's vue-tsc can be used to replace tsc , and the usage remains the same as tsc .

One difference is that the declaration file generated by vue-tsc .vue.d.ts a suffix of ---a43524b59812bd9c621f5d506fb0afb4---, so it needs to be renamed to .d.ts suffix.

The simple code implementation is as follows:

 import spawn from 'cross-spawn';

const { rootDir, outputDir } = options;
const vueTscPath = path.resolve(
  require.resolve('vue-tsc/out/proxy').split('node_modules')[0],
  'node_modules/.bin/vue-tsc',
);
spawn.sync(
  vueTscPath,
  ['--rootDir', rootDir, '--declaration', '--emitDeclarationOnly', '--declarationDir', outputDir],
  {
    stdio: 'ignore',
  },
);

if (isVue) {
  renameVueTdsFileName(); // 重命名 .vue.d.ts 为 .d.ts
}

Svelte TypeScript

The Svelte community is not as powerful as Vue, and there is no tool like vue-tsc . Finally, svelte-type-generator can be found. Refer to the code of svelte-type-generator to realize the function of generating declaration files ( The function of tsc cli is temporarily not supported).

 const { compile } = require('svelte-tsc');
const { rootDir, outputDir } = options;

compile({
  rootDir,
  declaration: true,
  emitDeclarationOnly: true,
  declarationDir: outputDir,
});

if (isVue) {
  renameSvelteTdsFileName(); // 重命名 .svelte.d.ts 为 .svelte.d.ts
}

print build info

Since it is triggered to run multiple vite build , if the default build information is directly output, it will be confusing, and it will not be able to output build information like running one vite build .

Therefore, it is necessary to intercept and hide the original build information, and customize the output of new build information.

  1. Intercepting and hiding the original build information can be achieved by rewriting console.log and console.warn , the code is as follows:

     export const restoreConsole = { ...console };
    
    export class InterceptConsole {
      public log: typeof console.log;
      public warn: typeof console.warn;
      public clear: typeof console.clear;
    
      constructor() {
        const { log, warn } = console;
        this.log = log;
        this.warn = warn;
      }
    
      silent() {
        console.log = () => {};
        console.warn = () => {};
      }
    
      restore() {
        console.log = this.log;
        console.warn = this.warn;
      }
    }
  2. Custom output new build information

    For the function, refer to the built-in reporter plug-in of Vite, and realize the output of construction information through the corresponding hook function.

final effect

image.png

Github example

vanilla

vanilla-ts

react

react-ts

vue

vue-ts

svelte

svelte-ts

For all examples, run the following command

 $ npm install
$ npm run build

Codesandbox online example

follow-up

If you are interested, you can also join the construction together. Currently svelte-tsc It is a bit challenging to achieve the same usage as vue-tsc .

  1. Generate declaration file support to configure tsconfig configuration file path
  2. svelte-tsc support bin file, function refer to vue-tsc , support all cli options of tsc .

Samon
1.3k 声望92 粉丝