Currently, the front-end community is increasingly calling for Vite to replace Webpack. But for long-term maintenance business projects, many students may still have doubts about getting on the bus — is Vite really enough to support non-toy projects? To that end, this article shares a practical case study of how we (relatively easily) implemented Vite in our core business.

Draft plane editors in Web services have been around for more than five years. As a front end project that goes through multi-person-led maintenance, it has a few complexities:

  • The editor manages the source code using a macro repository based on Yarn Workspace and Lerna, with nearly 20 packages, over 400 modules loaded upon initialization, and 2GB or more of node_modules dependencies.
  • The editor module first used Vue 0.8 and AMD module syntax, and has been maintained through Vue 1.x and 2.x era. Webpack also started from scratch and has been upgraded from 1.x to the current 4.x version.
  • Some of the advanced rendering functions in the editor use the capabilities of Worker and WASM.
  • The editor as a whole is published as a single NPM package to the company’s private repository for business access, with a separate packaging and release process.

The editor was first submitted in 2016 and is based on Vue 0.8 and AMD syntax

We can’t say that this is a “large enterprise” project, but it certainly isn’t a toy project. Surprisingly, the cost of Vite migration is even lower than upgrading the larger versions of Webpack and Babel. In just one afternoon, the Vite based editor was up and running with the smallest available MVP. The following points introduce relevant practical experience:

  • How to plan the basic migration ideas, and some basic knowledge reserves.
  • How to solve some Webpack Loader problems by writing plug-ins.
  • How to migrate common Webpack configurations.
  • How to deal with upstream dependencies.

Knowledge background and ideas

We know that mainstream front-end Bundlers, such as Webpack, are slow because they must recursively package the dependency tree of the entire project when started cold and have throughput bottlenecks due to the nature of JavaScript (which explains execution and the single-threaded model). To address these two pain points, Vite switched paths:

  • For business modules in a project, Vite takes advantage of the ES Module support built into modern browsers, where the browser loads these modules directly to dev Server on a request-by-request basis — so you tend to see a flood of HTTP requests in your local environment, which is the most distinctive feature of Vite.
  • For node_modules dependencies in the project, Vite uses high performance Bundler developed in native languages such as ESBuild to package the library’s non-ESM standard modules (CommonJS or UMD) as an ESM. So-called Dependency pre-bundling. The packaging results from this process are cached, and cold start rebuilding the cache is extremely efficient.

The difference between Vite’s design and webpack-dev-server is also clear in the documentation, and a picture is worth a thousand words:

Webpack-style schematic of the classic Bundler

Vite no-Bundler schematic

Based on this difference, we can see that in order for Vite to support existing Webpack projects, there are two things we need to ensure:

  • Ensure that the source code of business modules complies with THE ESM specification.
  • Ensure that dependencies are handled correctly by esBuild.

Of course, this is just the simplest model. Actual front-end projects often introduce some strange things like CSS, JSON, Worker, WASM, HTML templates… While Vite has good built-in support for these requirements, there is no guarantee that they will work out of the box — this is not a problem with Vite or Webpack, but a common difficulty in porting code build environments. The hardest part of this kind of mission is always the “light” from zero to one. So here’s the advice: Familiarize yourself with the code (subtree) you go through between the entry of the project and the completion of the rendering of each component, and make sure that the smallest subset works in the new environment. The rest of the code can be drastically removed temporarily.

For architected software projects, it is generally easy to simplify and extend modules. In this editor, for example, we support element types that can be configured and loaded on demand. For more than 20 existing business elements, their corresponding modules already support on-demand loading, and import() only when the corresponding data is encountered. Therefore, only a few base element module implementations can be retained for testing when migrating. Similarly, in a business project, you can customize a minimum available version for routing through the main process, for example by streamlining routing configurations.

Custom plug-in implementation

The above code simplification process is nothing more than creating a clean example page to import the project, commenting out parts of the code, and repeatedly executing the vite command test. For Vite migration, many students may be most concerned about Webpack plug-in compatibility issues. We happened to encounter a similar problem, here to share a brief.

One detail in the previous screenshot of the code from the 2016 old version of the editor is the introduction of editor.html as an HTML template for the component. This behavior has been retained over the years — that is, instead of using the SFC single-file component, we put a text-element.html template for components such as text-element.js, like this:

// Import the HTML source
import TextElementTpl from './text-element.html'

// Vue 2.0 classic configuration
export default {
  template: TextElementTpl,
  methods: {
    // ...
  },
  created() {
    // ...}}Copy the code

In a Webpack configuration, we typically support this with an HTML Loader, but what about Vite? Such requirements do not seem to be built in, and the community’s plugin-html is now designed for EJS templates, and the number of stars does not seem to be large… But do you really have to wait for the community to make it for you?

In fact, Vite’s plug-in system relies directly on Rollup. For this requirement, just write a few lines of plugins in vite.config.js:

// Use the Plugin utils included with Rollup
const { createFilter, dataToEsm } = require('@rollup/pluginutils');

function createMyHTMLPlugin() {
  // Create a filter for the filter module
  const filter = createFilter(['**/*.html']);
  return {
    name: 'vite-plugin-my-html'.// Give a name
    // Filter modules by id and convert the source when a matching module is encountered
    transform(source, id) {
      if(! filter(id))return;
      // So that the HTML string can be exported default to other JS modules
      returndataToEsm(source); }}; }// This allows you to use the Vite standard API
module.exports = {
  plugins: [createMyHTMLPlugin()],
}
Copy the code

This createMyHTMLPlugin is just a very simple function, right? But it does solve a real problem. Personally, I think a user-friendly build system should work out of the box most of the time and extend itself with simple logic. In this regard, it can be said that Vite has done quite well. Another major difference between Vite and Snowpack is that its plug-in system is more integrated with Rollup, thus implementing a common plug-in API in both Dev and build modes. Therefore, there is an opportunity in the business to “shell” some mature Rollup plug-ins to fulfill requirements.

Common Webpack configuration migration

The Vite configuration used in this practice is quite few, it is worth mentioning that the main is the following:

  • throughresolve.aliasConfiguration, can override (or hijack) module paths. Note that it is best to keep this configuration as small as possible; abuse of it can easily make the code module structure less tool chain-friendly.
  • throughdefineConfiguration can be supportedprocess.env.__DEV__Such environment variable injection. Note that Vite injects strings directly into raw expression in the product code, so if you just want to pass themtrueThis simple constant, extraJSON.stringifyPack a layer.
  • throughvite-plugin-vue2Supports Vue 2.0 SFC. The reason for this is that although the main component in the editor does not use SFC, the Demo entry to the test page isapp.vue. With this plug-in, they can coexist nicely.
  • Less and CSS rely on Vite’s built-in support without introducing additional configurations. Another workaround, of course, is to first execute the command to package the CSS independently, and thenimport "./dist.css"Can.
  • throughimport Worker from "worker.js? worker"Can support Web workers. It can also be further combinedresolve.aliasConfiguration to continue Webpack compatibility.
  • For WASM, except for the formimport init from "./a.wasm"In addition to the built-in support for WASM, another practice is for WASM’s JS adapter layer to support passing in configurable WASM paths. A typical example of this can be seen hereCanvasKitSuch as package.

Upstream dependency problem handling

Based on the practices described above, it should be enough to solve the problem of Vite loading various business modules. But there’s one final headache: what if the dependencies in node_modules aren’t properly packaged by esbuild?

In this migration, we have encountered two such problems, each for different reasons:

  • Image resampling libraryPicaRelies on a simple Web Worker transformation library that reads directly at the top level of the module codeargumentsData, resulting in an ESbuild error.
  • Font parsing libraryOpenType.jsTo be compatible with both browser and Node, several are encapsulated in the ESM source coderequire('fs')The function. This will also result in an error.

For both problems, there is a common workaround technique: create a third_party directory, copy the upstream module in question, fix the problem and adjust module dependencies. For example, the code in Pica library require(‘./a.js’) can be copied to third_party directory, and the module import path can be changed to require(‘ Pica/SRC /a.js’), so that the entire upstream dependency does not need to be copied. For both CommonJS issues, it is easy to fix them, such as placing arguments reading in the export Default function and removing Node file reading logic that is not needed in browsers. This third_party pattern is not really a hack, and is widely used in projects in many languages, but there are a few things to note:

  • You are advised to add it at the changed location// FIXME“So that the recipient can confirm the changes.
  • If you need to integrate large upstream dependencies, it is not recommended to put them directly into the code base. Instead, use a git submodule or CDN.
  • Ideally, the patch should be reported to the upstream to remove the corresponding local version after the problem is solved.

That’s all the questions worth listing, and finally a screenshot of a successful local environment startup based on Vite:

The problem with the log above is that two different Vue versions are loaded. This is because the SFC section and the code that depends on the HTML template misuse different Vue dependencies. This problem was later solved by rewriting vue entirely to vue/dist/vue with the alias configuration.

Because the editor SDK was originally distributed independently using Babel, the original NPM distribution process was not affected, and Vite’s overall aggressiveness was not as high. The end result was nothing more than a little acceleration:

  • Webpack 40 seconds + dev Server cold start time reduced to 1.5 seconds under build.viteAfter directory caching, startviteThe command takes only about 300 milliseconds.
  • The incremental build time of around 2 seconds after modifying a single file is completely optimized, and there is no significant difference in page loading efficiency in the browser.

In this way, the historical project was able to recapture the immediate feedback level of development experience, while also enabling more efficient CI integration. There’s a lot of room for imagination here, and we’re looking forward to making Vite a bigger role in the future.

conclusion

  • Vite is expected to lead the next wave of front-end build tools by offering a significant improvement in the development experience at a low access costparadigm shiftThe wave. According to ROI,The potential benefits of landing far outweigh the costs.
  • Real-world code should be as standard compliant as possible, using fewer features that rely on the dark magic of the toolchain, in exchange for better backward compatibility.
  • There are plenty of (not necessarily obvious) tricks in practice for code migration, such as regular substitution, writing Codemod, and providing deprecated API detection scripts for downstream businesses — ask yourself, and copy the codevarI’m going to find all of them and I’m going to replace themletHave you ever done such a thing? There is no superior or inferior solution to the problem, as long as it is simple and convenient.
  • JavaScript itself, even as a compiled product, is easy to read, easy to modify, and easy to feed back to the upstream backport. Mainstream compiled languages have a hard time doing this – you can change the machine code for function symbols in a DLL or bytecode in a Java class file and immediately follow diff directly to the upstream library. This is the source of dark magic, perhaps also a kind of front-end “road confidence” it.

In fact, as the author of this article, I have personally tried some similar code migrations before. This kind of work is like cracking a dungeon escape game, which is very interesting. Personally, like this Vite migration, the practical means are actually quite common with the previous experience:

  • In 1995, the world’s earliest JS engine source code compiled back to JavaScript
  • Take the Dart VM out of the Flutter and use it as a standalone iOS native project
  • Build embedded Linux tool chain for domestic handheld, and transplant QuickJS engine to it

So at the end of the day, I really encourage you to do more interest-driven technology. Maybe one day, the experience of juggling them will help you find a grip, empower business, and create a one-two punch (

We are recruiting in hangzhou, Shenzhen, Xiamen and other bases. Welcome to contact us by email or xuebi at gaoding.com.

reference

  • Why Vite – Vite
  • Dependency Pre-Bundling – Vite
  • Plugin API – Vite
  • Features – Vite
  • Configuring Vite – Vite