Recently, I explored the establishment of Bundless building tools in the company, and tried to migrate some existing business projects from Webpack to Vite. Since the middle and background projects generally do not have high requirements for browser compatibility, I can boldly introduce some cutting-edge and radical solutions. So in the company found a business background project to initially try to introduce Vite.

Of course, instead of using Vite directly during the migration process, there is a layer of encapsulation on the top of Vite to access the architecture of the build tool that the team is currently developing. The project build configuration will be different from the original Vite configuration. However, this does not affect the subsequent pit and principle introduction, I also try to restore the site to the original Vite configuration, so that you can understand.

preface

At present, the mainstream front-end packaging tools are mainly represented by Webpack, but with the development of the project scale, the pain points in construction are becoming more and more prominent. The biggest feeling is that it is too slow. On the one hand, the project must recursively package the dependency tree of the whole project when it is cold started. On the other hand, the limitations of the JavaScript language itself (interpreted execution, single-threaded execution) lead to build performance bottlenecks.

In this context, some build tools called Bundleless (or Unbundled) came into being, like Snowpack, Vite, which has been making headway in the community lately, star 30K + on GitHub, It has even surpassed the number of stars in vue3 warehouse (currently 20K +), which shows its great influence.

Vite has two main advantages over traditional packaging tools like Webpack:

  1. Taking advantage of the browser’s built-in ES Module support (the script tag with the attribute type=” Module “), the browser directly requests the dev Server for each Module individually, without having to pack all the files in advance.

  2. Pre-build third-party libraries with the help of esBuild’s super fast compilation speed, on the one hand, grouping scattered files together to reduce network requests, and on the other hand, fully converting ESM module syntax to fit the built-in ESM support of the browser.

As its name suggests, Bundleless’s biggest feature compared to traditional build tools is that it doesn’t have to package business code (although third-party libraries still have to be packaged, which is not an option), which can greatly improve the build efficiency and development experience, especially as the project grows larger. After the landing of the company’s business projects, the bottom layer was cut from Webpack to Vite, and the speed of cold start increased by more than 400%. The project started in 20 seconds can be cold start in 3 to 4 seconds now, and under the condition of no change in dependence, the second start can be directly started in seconds. I have to feel that it is really too fast!

Migration issues

The SVG component reported an error

Vite has no support for SVG component writing. By default, this will cause an error:

import Up from 'common/imgs/up.svg';

function Home() {
  return {
    <>// Omit other child components<Up className="admin-header-user-up" />
    </>}} Copy the codeCopy the code

To solve this problem, find the viet-plugin-react-SVG plug-in in the community’s existing ecosystem, add it to the plugins array of Vite, implement the ability to reference SVG resources as components, and import SVG files as follows:

import Up from 'common/imgs/up.svg? component'; Copy the codeCopy the code

There are internal bugs in third-party packages

When esBuild is pre-built, errors are detected regarding third-party package dependencies. We had the following errors on the GAMES and we had problems on THE REACT – Virtualized ESM production:

// This variable is not exported in Windowscroll.js!
import { bpfrpt_proptype_WindowScroller } from ".. /WindowScroller.js"; Copy the codeCopy the code

The same question can be found in the GitHub repository of the library (issue address :github.com/bvaughn/rea… Webpack /rollup does not catch this problem, but it does report errors in esbuilds, which are generally third-party library artifacts.

In general, there are two ways to resolve the node_modules third-party library bug:

The first approach is to fix the problem file by copying it from the third-party library, putting it in the project directory (non-node_modules), and then redirecting it to the fixed location using the build tool resolve.alias capability.

Another option is to log node_modules changes through patch-package, generate patches directory, and then synchronize the changes across the team through the project’s post-install script.

The latter is used here to realize the temporary patch of the third-party library, which is also suitable for the temporary repair of other third-party library problems.

/ / 1. Install
yarn add patch-package postinstall-postinstall
// 2. Modify the node_modules code and run:
yarn patch-package react-virtualized
// 3. Package. json add:
{
  "postinstall": "patch-package"} Duplicate codeCopy the code

The prebuild executes repeatedly

If that was just a trivial problem, the next bug is just torture. After basic configuration, the project does display normally, but every time the cache is cleared and rebuilt, something really weird happens during the pre-build phase:

  • The console keeps showingnew dependencies xxxSimilar to log, the service is reloaded frequently
  • New dependency cache files are constantly created in the pre-built cache directory.vite, and more dependencies are created again as the service is reloaded frequently and all the cache files are emptiedHeavy brushThe cache directory took about 20 seconds to stabilize.
  • During the 20-plus seconds of the prebuild repeatedly reworking the directory, the page became inaccessible and stuck

Here’s a picture of the scene of the accident to give you a taste of it:

1. Locate the fault

First, we tried a normal demo project. The normal pre-build scenario looks like this:

  • Output all build cache files at once
  • The terminal log is also compact, as shown below:

Search globally for pre-bundling in the Vite source code according to the normal log,Reverse backPrebuild part of the source code. The summary process is as follows:

  • During the Vite Server startup phase, runOptimize logic is executed in the callback of server.listen to enter the pre-build phase.
  • runOptimizeIn the calloptimizeDeps, internally call esbuild for construction, and pass the custom Scan plug-in into esbuild. During esbuild construction, perform dependency analysis, and assign dependencies to DEPS
  • After getting the DEPS, print out the above terminal log and finish the first pre-build.

The function call flow is as follows:

StartServer -> runOptimize -> optimizeDeps -> scanImports -> esbuild.build Copies the codeCopy the code

The dependency information output by the terminal comes from the DEPS variable, which is in theoptimizeDepsThrough the execution ofscanImportsTo:So we break intoscanImportsIn the middle:You can see that this is reading the input configuration in the configuration, and the project is configured with./src/index.tsxIf this is configured, it will be based onrootandinputThe spliced path acts as an entry point to search for dependencies. Unfortunately, the entry path is not found, as shown below:The reason is that the configuration file looks like this:

{
  input: './src/index.js'.root: path.resolve(__dirname, 'src'} copy the codeCopy the code

In order to place the HTML in the SRC directory, I set the root parameter, so that I can not find the path after concatenating with the inputsrc/srcObviously, there are too many layerssrcIn this case, an empty object will be returned without an entry. Vite will assume that the entry cannot be found and will not be able to rely on the prebuild.

2. Solutions

The core of the solution is to ensure that entry paths can be parsed by Vite in two ways:

  • Remove the root configuration so that process.cwd() is used as root by default and the last entry path can be found.
  • Without input, Vite’s behavior is to search for HTML files in the root directory, even if root is configured as SRC in the project root directory.

3. Question review

Now everything is fine, but back to the original question, why does the command line spawn so many logs like New Dependencies, the build cache directory is refreshed again and again, and the page keeps stuck? The problem is solved, but it’s worth digging into these weird questions.

After reading the source code, find the root cause of the problem is that Vite pre-built not only at the time of service start, when the request to enter may also trigger the pre-built, that is to say, the pre-built behavior does not trigger a just at the beginning, when the browser to access the project is likely to trigger again, and even trigger many times.

In the first pre-built process mentioned earlierrunOptimizeThe function is registeredRuntime optimizationAs shown in the arrow below:It returns a closure function, mainly runtime optimized logic, which is called againoptimizeDeps“And then follow the logicscanImports + esbuild.buildTo restart the Vite Server.

This closure function is called in the resolvePlugin of Vite. In simple terms, when the browser initiates a request and the request enters the Vite server, the first step is to execute a series of plugins (see the official website for the sequence). The first step is to go to the resolvePlugin, which analyzes the dependencies in the project. The _registerMissingImport is executed to prebuild the dependency and restart the Vite Server.

The _registerMissingImport call is followed by a second pre-build, but not immediately, which is equivalent to a batch collection every 100 ms and a build together. There is actually a process of intercepting the stream. This improves the efficiency of the pre-build without calling optimizeDeps for each dependency. Belongs to Vite inside the detailed optimization, and Vue inside the nextTick batch update has the same wonderful. Here is the code returned by _registerMissingImport:

return function registerMissingImport(id: string, resolved: string, ssr? : boolean) {
  // Not prebuilt
  if(! knownOptimized[id]) { currentMissing[id] = resolved/ / the closure
    if (handle) clearTimeout(handle)
    // Batch prebuild dependencies after 100 ms
    handle = setTimeout(() = > rerun(ssr), debounceMs)
    // Restart Vite Server
    server._pendingReload = new Promise((r) = >{pendingResolve = r})}} Copy the codeCopy the code

In fact, the secondary optimization process of stream interception can also explain the cause of the large number of logs mentioned above. Every 100 ms, new dependencies will be found and Vite Server pre-build will be restarted, which will inevitably lead to a large number of new Dependecies. At this time, the Server will be restarted frequently. It’s not unusual for pages to get stuck.

4. Some stretching

The above analysis process can be regarded as the root cause of the stomping problem. However, I also searched related issues in the Vite warehouse. In fact, such a secondary pre-build process also exists in normal projects, mainly to deal with some dynamic import scenarios in the project. When the dependency of dynamic import becomes too much, the build performance will be greatly affected. In this scenario, automatic optimization can also be implemented using the vite-plugin-optimized-persist plug-in developed by ANTfu students.

Subsequent thinking

The project is up and running after the migration, with a decent build efficiency gain, but there are two other issues that have got me thinking.

1. Landing prospects

First of all, the business project is relatively architecturally less complex. If Monorepo, SSR builds or more complex architectures are involved, can Vite still migrate over? Can Bundless be used on a large scale for current mid-back office businesses?

This point needs to be verified by recent continuous investment and practice, and will continue to share with you.

2. Check whether the Vite is installed in the production environment

On the other hand, should Vite take over the build of the production environment? You should know that it is very obvious to improve the efficiency of the development environment, but does it really fit the production environment?

Personally, I think development environment and production environment should be separated. The former’s pain point is efficiency, while the latter’s appeal is stability and quality. At this time, I think Webpack in production environment is a better choice than Vite’s Rollup.

But this creates a new problem – how to solve the problem of unified configuration when the two configurations are so different? I’ll talk more about this later, but I’m optimistic that it’s at least possible. Perhaps when the configuration differences between Webpack and Vite can be smootened out by some solution, Vite will be available for all projects that use Webpack, in other words, when the time comes to replace Webpack completely in the development environment.