Vite2.0 was released two weeks ago (February 17, 2020), and vite2.0 is more mature than 1.0 as a next-generation front-end tool that uses the browser’s native ESM. Before that, I began to pay attention to this kind of “new” front-end tools. This time, we successfully migrated an existing project based on VUE-CLI (-service) + vue2 to vite 2.0 release.

The migration went smoothly, taking less than half a day. But the whole process of migration also encountered some small problems, here is a summary, also convenient to meet similar problems with friends to exchange and reference.

Project background

Before introducing the specific migration work, let’s give a brief overview of the project. At present, the project has been online for less than a year, and there is little historical legacy debt related to the construction. The project contains 1,987 module files (including modules in node_modules), using vue2 + vuex + typescript stack, and using vuE-CLI (webpack) as the building tool. It is a relatively standard vUE stack. Since it is an internal system, the project has a low requirement for compatibility, and most users use the newer Chrome browser (Safari is used to a lesser extent).

The migration work

Let’s talk about what happens in the next migration.

1. Configuration files

First you need to install Vite and create a configuration file for Vite.

npm i -D vite
Copy the code

Vue.config. js is used as the configuration file in vue-cli-service. For Vite, you need to create a vite.config.ts configuration file by default. The basic configuration file is simple:

import { defineConfig } from 'vite';

export default defineConfig({
  plugins: [
    // ...],})Copy the code

When this configuration file is created, the previous vue.config.js is no longer used.

2. Entry and HTML files

You also need to specify entry files in Vite. But unlike WebPack, in Vite, instead of specifying JS/TS as the entry point, you specify the actual HTML file as the entry point.

In Webpack, the user specifies the js package entry file by setting entry to js (e.g. SRC /app.js). HtmlWebpackPlugin injects the generated JS file path into HTML. Vite uses HTML files directly, parsing script tags in HTML to find the entry JS file.

Therefore, we add a script tag reference to the js/ts file in the entry HTML:

<! DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta Name = "viewport" content = "width = device - width, initial - scale = 1.0" > < title > < % = htmlWebpackPlugin. Options. The title % > < / title > </head> <body> <noscript> We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. </noscript> <div id="app"></div>+ <script type="module" src="/src/main.ts"></script>
</body>

</html>
Copy the code

Note that uses the browser’s native ESM to load the script. / SRC /main.ts is the source of the entry js. When started in Vite Dev mode, it actually starts a server like a static server, with serve source directory, so there is no complex module packaging process like WebPack. Dependency loading of modules will depend entirely on the handling of import syntax in the browser, so you can see a long cascade of script loading:

You also need to pay attention to the project root setup. The default is process.cwd(), and index.html is also searched under project root. I moved./public/index.html to./index.html for convenience.

3. Use the Vue plugin

Vite 2.0 provides good support for VUE projects, but it does not have strong coupling to VUE itself, so it supports building projects on the VUE technology stack in the form of plug-ins. The vue plugin currently recommended on the vite 2.0 website (2021.2.28) will work better with vue3’s SFC. Therefore, a plugin-vue2 plug-in is used to support VUe2. It supports JSX, and the latest version also supports VitE2.

It’s also easy to use:

import { defineConfig } from 'vite';
+ import { createVuePlugin } from 'vite-plugin-vue2';

export default defineConfig({
  plugins: [
+ createVuePlugin(),]});Copy the code

4. Handle typescript path mapping

When building TS projects using Vite, special treatment is required if you use typescript pathmapping capabilities, otherwise the module will not be able to resolve (it cannot be found) :

You need to use the vite-tsconfig-Paths plug-in to parse and replace the path mapping. The principle is relatively simple, which is basically the resolveId hook stage of the Vite plug-in, using the tsconfig-Paths library to resolve the path map into the actual map return. Those interested can take a look at the implementation of the plug-in, which is relatively brief.

The specific usage is as follows:

import { defineConfig } from 'vite';
import { createVuePlugin } from 'vite-plugin-vue2';
+ import tsconfigPaths from 'vite-tsconfig-paths';

// https://vitejs.dev/config/
export default defineConfig({
  plugins: [
    createVuePlugin(),
+ tsconfigPaths(),]});Copy the code

Replace CommonJS

Vite uses ESM as a modularity solution, so it does not support importing modules using require. Otherwise, the runtime will report Uncaught ReferenceError: require is not defined (the browser does not support CJS, so there is no require method injection).

In addition, compatibility issues between ESM and CJS may be encountered. Of course this is not a problem caused by the Vite build, but it is important to note. To put it simply, ESM has the concept of default, while CJS does not. Any exported variable is a property of the module.exports object in the view of CJS, and the default export of ESM is just a property of module.exports.default in the view of CJS. For example, in typescript we use the esModuleInterop configuration to have TSC add compatible code to help parse imported modules, and we do the same in WebPack.

For example, the previous code:

module.exports = {
    SSO_LOGIN_URL: 'https://xxx.yyy.com'.SSO_LOGOUT_URL: 'https://xxx.yyy.com/cas/logout',}Copy the code
const config = require('./config');
Copy the code

You need to change the export and import to ESM, for example:

export default {
    SSO_LOGIN_URL: 'https://xxx.yyy.com'.SSO_LOGOUT_URL: 'https://xxx.yyy.com/cas/logout',}Copy the code
import config from './config';
Copy the code

6. Use of environment variables

Using vuE-CLI (webpack) we often use environment variables to make runtime code judgments, such as:

const REPORTER_HOST = process.env.REPORTER_TYPE === 'mock'
  ? 'http://mock-report.xxx.com'
  : 'http://report.xxx.com';
Copy the code

Vite still supports the use of environment variables, but no longer provides access like process.env. Instead, you need to access environment variables via import.meta.env:

-const REPORTER_HOST = process.env.REPORTER_TYPE === 'mock'
+const REPORTER_HOST = import.meta.env.REPORTER_TYPE === 'mock'
  ? 'http://mock-report.xxx.com'
  : 'http://report.xxx.com';
Copy the code

Like WebPack, Vite has some built-in environment variables that you can use directly.

7,import.meta.env types

Note: Vite provides the types definitions it requires, which can be introduced directly using Vite/Client rather than adding them yourself.

If you access environment variables in typescript through import.meta.env, you might get a TS error that the attribute “env” does not exist on the type “ImportMeta”.

This is because in the current version (V4.2.2) import.meta is defined as an empty interface:

interface ImportMeta {
}
Copy the code

However, we can extend typing support for import.meta.env by further defining ImportMeta types in the project through interface merge capabilities. For example, vue-shims.d.ts files generated by vue-cli will be generated in the SRC directory.

declare global {
  interface ImportMeta {
    env: Record<string, unknown>; }}Copy the code

So you don’t get an error.

Webpack require context

In Webpack we can parse modules “dynamically” with the require.context method. A common approach is to specify a directory to load certain modules by means of regular matching, so that when new modules are added, the effect of “dynamic automatic import” can be played.

For example, in the project, we dynamically match the route.ts file in the Modules folder and set the router configuration in the global vue-Router:

const routes = require.context('./modules'.true./([\w\d-]+)\/routes\.ts/)
    .keys()
    .map(id= > context(id))
    .map(mod= > mod.__esModule ? mod.default : mod)
    .reduce((pre, list) = > [...pre, ...list], []);

export default new VueRouter({ routes });
Copy the code

The file structure is as follows:

SRC/Modules ├─ admin │ ├─ pages │ ├─ routes. Exercises ── Alert │ ├─ Components │ ├── routes │ └ ─ ─ utils ├ ─ ─ the environment │ ├ ─ ─ store │ ├ ─ ─ types │ └ ─ ─ utils └ ─ ─ service ├ ─ ─ assets ├ ─ ─ pages ├ ─ ─ routes. The ts ├ ─ ─ store └ ─ ─ typesCopy the code

Require Context is a module method that is unique to Webpack and is not a language standard, so require Context can no longer be used in Vite. However, if the developer manually imports modules completely, on the one hand, it is easy to miss the import of modules because of the existing code changes. The second is to give up this “flexible” mechanism, the subsequent development model will also have certain changes. Fortunately, Vite2.0 provides module imports in Glob mode. This feature can achieve the above goals. Of course, some code changes will be required:

const routesModules = import.meta.globEager<{default: unknown[]}>('./modules/**/routes.ts');
const routes = Object
  .keys(routesModules)
  .reduce<any[] > ((pre, k) = > [...pre, ...routesMod[k].default], []);

export default new VueRouter({ routes });
Copy the code

Basically, change require.context to import.meta.globEager and adapt the return value type. Of course, to support types, we can add some types to the ImportMeta interface:

declare global {
  interface ImportMeta {
    env: Record<string, unknown>;
+ globEager
      
       (globPath: string): Record
       
        ;
       ,>
      }}Copy the code

Also, import.meta.globEager does static analysis at build time to replace the code with a static import statement. If you want to support dynamic import, use the import.meta.glob method.

API proxy

Vite2.0 native development (DEV mode) still provides an HTTP Server and also supports setting up proxies through the proxy TAB. It uses HTTP-proxy as well as WebPack, so proxy Settings for VUe-CLI can be migrated to Vite:

import { defineConfig } from 'vite';
import tsconfigPaths from 'vite-tsconfig-paths';
import { createVuePlugin } from 'vite-plugin-vue2';
+ import proxy from './src/tangram/proxy-table';

export default defineConfig({
  plugins: [
    tsconfigPaths(),
    createVuePlugin(),
  ],
+ server: {
+ proxy,
+}
});
Copy the code

10. HTML content insertion

In vue-CLI, we can use the HtmlWebpackPlugin of Webpack to replace the value in HTML. For example, < % = htmlWebpackPlugin. Options. The title % > this form to the department template variables at compile time, replace with the actual value of the title. It is also very simple to implement such functionality, such as viet-plugin-html. This plug-in implements template variable injection based on EJS. It receives the raw HTML string through the transformIndexHtml hook and returns the injected variable via EJS rendering.

The following is the configuration using viet-plugin-html after migration:

import { defineConfig } from 'vite';
import tsconfigPaths from 'vite-tsconfig-paths';
import { createVuePlugin } from 'vite-plugin-vue2';
+ import { injectHtml } from 'vite-plugin-html';

export default defineConfig({
  plugins: [
    tsconfigPaths(),
    createVuePlugin(),
+ injectHtml({
+ injectData: {
+ title: 'User management system ',
+},
    }),
  ],
  server: {
    proxy,
  },
});
Copy the code

Modify the HTML template variable writing method:

<! DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta Name = "viewport" content = "width = device - width, initial - scale = 1.0" >- <title><%= htmlWebpackPlugin.options.title %></title>
+ <title><%= title %></title>
</head>

<body>
  <noscript>
- We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled.
+ We're sorry but <%= title %> doesn't work properly without JavaScript enabled.
  </noscript>
  <div id="app"></div>
  <script type="module" src="/src/main.ts"></script>
</body>

</html>
Copy the code

11. Compatibility processing

In the background of the project, it was mentioned that the project had low requirements for compatibility, so this part was not actually involved in the migration.

Of course, for projects that require compatibility, you can use the @Vitejs/plugin-Legacy plug-in. The plug-in packages up two sets of code, one for modern browsers and one for older browsers with various polyfills and syntactic compatibility. Also use module/nomodule technology in HTML to implement “conditional loading” in old and new browsers.

conclusion

The project contains 1,987 module files (including modules in node_modules). The build (no cache) before and after migration takes as follows:

vue-cli vite 2
Dev mode ~8s ~400ms
Prod mode ~42s ~36s

As you can see, the efficiency of vite2 builds is significantly improved in DEV mode. This is also due to the fact that in DEV mode, it only does some lightweight module file processing and does not do heavy packaging work. In production mode, it still needs to use esbuild and rollup to build. Therefore, the efficiency improvement in this project is not obvious.


These are some of the problems I encountered when I migrated vite 2.0 from VUe-CLI. Are some relatively small points, the overall migration did not encounter too big obstacles, with less than half a day on the migration. Of course, it also depends on the standardization work of JavaScript and HTML in recent years to make the mainstream code we write also have certain uniformity. This is one of the great benefits of these front-end tools for future-oriented programming. I hope this article will be a reference for those of you who are trying to migrate to Vite 2.0.