Hello, everyone, I’m Dreyman, and today I’m bringing you Vite Analysis.

Finally, provide a background project using Vite + React + Concent.

The words written in front

Vite, touted as the next generation of front-end development and build tools. The emergence of vite benefits from the browser’s support for module, which makes use of the new features of the browser to achieve a fast development experience; It can achieve hot overload (HMR) very quickly.

Development mode, using the browser module support, to achieve the ultimate development experience;

The compilation and packaging of the formal environment was built using the rollup which was first proposed for tree-shaking;

There are many configuration options for Vite, including Vite itself, ESbuild, Rollup, etc. Today we will take a look at Vite from a source point of view.

One is the client part of the development process; the other is the client part. One part is the server part of the development process; The other part of the package compilation is related to production. Since the Vite package compilation is actually using rollup, we will not do the parsing, just look at the first two parts.

vite-client

The vite client is handled as a separate module. Its source code is in Packages /vite/ SRC /client; There are four files in it:

  • Client. ts: The main file entry, described below;
  • Env.ts: Environment-related configuration, where we will process the vite. Config.js (vite configuration file) define configuration;
  • Overlay. ts: This is an error mask display that displays our error messages;
  • Tsconfig. json: This is the configuration file for ts.

Tool parts

Client provides a series of utility functions, mainly for HMR;

Websocket part

  • Establish a WebSocket connection
  • Call the overlay above to display the error
  • The Message communication

There are several types of events in the Message section, as shown in the following figure:

For example

Create a simple demo using the vite-app:

yarn create @vitejs/app my-react-ts-app --template react-ts

Using the above commands, it is easy to create a Vite application with React -ts.

npm install
npm run dev

Execute the above command, install, and then start the service, open a browser: http://localhost:3000/, a network interface, can see the following request:

I have divided these types of data into different categories:

Let’s analyze the contents of the HTML:

<! DOCTYPE html> <html lang="en"> <head> <script type="module" src="/@vite/client"></script> <script type="module"> import RefreshRuntime from "/@react-refresh" RefreshRuntime.injectIntoGlobalHook(window) window.$RefreshReg$ = () => {} window.$RefreshSig$ = () => (type) => type window.__vite_plugin_react_preamble_installed__ = true </script> <meta charset="UTF-8" /> <link rel="icon" type="image/svg+xml" href="favicon.svg" /> <meta name="viewport" content="width=device-width, Initial -scale=1.0" /> </title> </head> <body> <div id="root"></div> <script type="module" src="/src/main.tsx"></script> </body> </html>

As you can see, there are three pieces involved in JS:

  • Client, the request path is /@vite/client, please pay attention to this path, which is the dependent path of vite itself;
  • Module code for React-Refresh, which is the code injected by the plug-in React-Refresh; @React-Refresh is requested from the SDK of the plug-in React-Refresh.
  • Main, the request path is/SRC /main/ TSC, which is related to the real code in our project;

In addition to the above three, there is also a env, request path is / @ vite/env. Js, this is @ vite/client internal dependence on env request: import ‘/ node_modules/vite/dist/client/env. Js’;;

And of course the SDK request for @react-refresh;

In addition to the JS mentioned above, the other requests are actually requests in our project code;

The first thing the client does is set up the websocket channel. You can see the websocket type localhost request above. This is the channel through which the client communicates with the server side, makes hot updates, and so on.

# vite – server client, and we went back to the server, the entry documents for packages/vite/SRC/node/serve. Ts, The main logic is actually in the packages/vite/SRC/node/server/index. The ts; Let’s call the Server side the Node side for the moment. The Node side mainly contains the processing of several types of files. After all, this is just a proxy server.

Let’s look at these types of processing in several sections

node watcher

The main function of watcher is to listen for file changes and then communicate with the client side:

The monitoring directory is the root directory of the whole project. WatchOptions is the configuration of the server.watch in vite.config.js.

// use Chokidar to listen to the file directory, const watcher = Chokidar. Watch (path.resolve(root), {ignored: ['**/node_modules/**', '**/.git/**', ...ignored], ignoreInitial: true, ignorePermissionErrors: true, ... watchOptions }) as FSWatcher

Start listening for files:

// If this changes, call handleHMrupDate,  watcher.on('change', async (file) => { file = normalizePath(file) // invalidate module graph cache on file change moduleGraph.onFileChange(file) if (serverConfig.hmr ! == false) { try { await handleHMRUpdate(file, server) } catch (err) { ws.send({ type: 'error', err: PrepareError (Err)})}}); watcher.on(' Add ', (File) => {HandleFileAddUnLink (normalizePath(File), On ('unlink', (file) = bb0 {HandleFileAddUnlink (normalizePath(file), server, true)})

Monitoring of the corresponding event handlers in the packages/vite/SRC/node/server/HMR. Ts file inside. Then the details of the processing, we do not explain, in fact, the inside logic is not too much difference, finally is called WebSocket, sent to the client side.

Node Dependency Types

The dependency type, which is the dependency package under node_modules, for example:



These packages are of a type that doesn’t change at all, and what Vite does is put these dependencies in when the service starts.viteUnder the directory, requests received go directly to.vite and return.

Node Static Resources

Static resources are known to us as public/ under or static/ under. These resources are static files, such as:



Such data is returned without any processing by Vite.

node html

For the entry file index. HTML, we will only talk about the single entry file for the moment, the multi-entry file vite is also supported, details can be seen in the multi-page application;

/ / cuts have to code the following / / @ the file packages/vite/SRC/node/server/middlewares/indexHtml ts export function indexHtmlMiddleware (server) {  return async (req, res, next) => { const url = req.url && cleanUrl(req.url) const filename = getHtmlFilename(url, Let HTML = fs.readFileSync(fileName, fileName, fileName, fileName, fileName, fileName, fileName, fileName) 'utf-8') // dev mode calls create atedevhtmlTransformfn to convert the contents of the HTML, Insert two script HTML = await for server transformIndexHtml (url, HTML) / / return the HTML content. return send(req, res, html, 'html') } catch (e) { return next(e) } } }

For the entry file index.html, vite will first read the contents of the file from the hard drive, and after a series of operations, it will return the contents of the operation. Let’s take a look at this series of operations:

  • Call createDevHTMLTransformfn to get the handler:
// @file packages/vite/src/node/plugins/html.ts export function resolveHtmlTransforms(plugins: readonly Plugin[]) { const preHooks: IndexHtmlTransformHook[] = [] const postHooks: IndexHtmlTransformHook[] = [] for (const plugin of plugins) { const hook = plugin.transformIndexHtml if (hook) { if (typeof hook === 'function') { postHooks.push(hook) } else if (hook.enforce === 'pre') { preHooks.push(hook.transform) }  else { postHooks.push(hook.transform) } } } return [preHooks, postHooks] } // @file packages/vite/src/node/server/middlewares/indexHtml.ts export function createDevHtmlTransformFn(server: ViteDevServer) { const [preHooks, postHooks] = resolveHtmlTransforms(server.config.plugins) return (url: string, html: string): Promise<string> => { return applyHtmlTransforms( html, url, getHtmlFilename(url, server), [...preHooks, devHtmlHook, ...postHooks], server ) } }

Here again, let’s take the React project as an example. The React-Refresh plug-in is inserted into Posthooks; It actually returns an anonymous Promise function; This is the closure. Unnamed functions are called applyHTMLtransforms. Let’s take a look at the arguments:

  • HTML is the content of the index.html under the root directory
  • Url is/index. HTML,
  • The third parameter is executed as /index.html
  • The fourth parameter is a large array with empty prehooks, the second is the return function of vite’s own /@vite/client link, and the third is the plug-in that has a react-refresh in it
  • The fifth parameter is the current server

This is followed by the invocation of the ApplyHTMLTransforms, where the HTML content is overwritten and returned.

The final HTML content is the HTML content we saw above.

Node Other Types

For the moment, count all other types as other types, including /@vite/client and business-related requests starting with @vite; All of these requests will go to the same TransformMiddleware middleware. This middleware does the following:

// @file packages/vite/src/node/server/middlewares/transform.ts

If you miss a hit, you will return to the transform. If you miss a hit, you will go to the transform. Now let’s see how to call the transform:

/ / @ the file packages/vite/SRC/node/server/transformRequest ts / / call the plugin for current request id, such as / @ the react - refresh, of course, there are getting less than; const id = (await pluginContainer.resolveId(url))? . Id | | url / / the content of the call for plugin return, such as / @ the react - refresh, there must be not to return to the plugin, Const LoadResult = await PluginContainer. Load (id, SSR) // If no result is obtained, i.e. no plugin type request, If (loadResult == null) {code = await fs.readFile(file, file, file, file); 'utf-8') } else { if (typeof loadResult === 'object') { code = loadResult.code map = loadResult.map } else { code = LoadResult}}} // Start file listening, call watcher, EnsureWatchedFile (watcher, mod.file, root) // But code is still the source file, Is to write the content of the files / / the following transform is to replace const transformResult = await pluginContainer. Transform (code, id, map, ssr) code = transformResult.code! map = transformResult.map return (mod.transformResult = { code, map, etag: getEtag(code, { weak: true }) } as TransformResult)

The general process is as follows:

async transform(code, id, inMap, ssr) { const ctx = new TransformContext(id, code, inMap as SourceMap) ctx.ssr = !! ssr for (const plugin of plugins) { if (! plugin.transform) continue ctx._activePlugin = plugin ctx._activeId = id ctx._activeCode = code let result try { result = await plugin.transform.call(ctx as any, code, id, ssr) } catch (e) { ctx.error(e) } if (! result) continue if (typeof result === 'object') { code = result.code || '' if (result.map) ctx.sourcemapChain.push(result.map) } else { code = result } } return { code, map: ctx._getCombinedSourcemap() } },

In fact, by this point, we have basically understood the functions of the vite server, the proxy server, and then modified the reference to its own rules, and analyzed its own rules. Of particular importance is the vite:import-analysis plug-in.

vite + react

Before you start, include the address: github: vite-react-concent-pro; This project was changed by the github: webpack-react-concent-pro project. The business logic code module was not changed, only the compilation and packaging part was changed.

Here’s a look at the process of switching from Webpack to Vite and some of the issues encountered.

After clone the project, remove the Webpack dependencies and replace them with Vite. Remember to add the Vite plugin of React: @vitejs/plugin-react-refresh; After that, since the reference path in our project is under the SRC folder, we need to provide the following alias for vite:

Resolve: {alias: {// alias "configs": path.resolve(__dirname, 'SRC /configs'), "components": path.resolve(__dirname, 'src/components'), "services": path.resolve(__dirname, 'src/services'), "pages": path.resolve(__dirname, 'src/pages'), "types": path.resolve(__dirname, 'src/types'), "utils": path.resolve(__dirname, 'src/utils'), }, },

This way we can let Vite know where to look for which files without changing the references inside. *** is a reference to process.env.** is a reference to process.env.** is a reference to process.env.**.

Npm Run Start is a good way to run.

1 the pit

After executing NPM run build, we execute NPM run preview while previewing, and the following screen appears:



We have this kind of unseen error, and then what is our solution?

First, get rid of the compression, don’t compress, the compressed code is all ABCD, nothing can be seen; The way to kill it is to change the configuration of Vite:

Build: {whether minify: false, / / compression of Boolean | 'terser' | 'esbuild' default terser manifest: Json sourceMap: false, // sourceMap: false, // sourceMap outDir: 'build', // output directory},

We changed minify to false, and then re-executed build and preview commands, so we can see the exact line and where the error was reported.

What about the final solution? Tmd is an object-inspect library that references a util package, and there is no util package in node_modules.

After two or three hours, the solution was a command: NPM i-s util.

After re-executing Build and Preview, it works fine.

Pit 2

Start, Build + Preview are all OK for local development. Next, it’s time to try a single test. Execute NPM run test. Sure enough, an error is reported because there is no Babel configuration for Babel-Preset-React app.

Why don’t we just add the configuration?

We added the configuration of Babel to package.json:

"bable": {
    "presets": [
        "react-app"
    ],
}

Then we run NPM run test; Well, there you go.

Let’s test it again, execute NPM run start, it’s dead, it’s running!

**

Using babel-preset-react-app requires that you specify NODE_ENV or BABEL_ENV environment variables. Valid values are “development”, “test”, and “production”. Instead, received: undefined

**

What does this sentence mean? Our babel-preset-react-app package needs a process.env.node_env or process.env.babel_env variable to run. We are in line with the principle that vite does not work on the process, this problem is not to be solved, that is to say, can not be configured to achieve Babel’s configuration, then how to do?

If you look at the source code of the babel-preset-react-app package, you can pass it as an argument, so you have to start with what you do with test. For test, you run jest, which has a configuration file called jest. The JEST configuration file contains a transform object that contains the babel-jest library, which is also called Babel.

We had to do something here, and finally after a lot of debugging, the configuration looked like this:

// You can't configure the Babel transform in the project or package.json: // You can't configure the Babel transform in the project or package. {/ / vite react project inside a single measurement needs to pass the Babel - react - app in here, not in the project or package. The json configuration inside the Babel "^. + \ \. (js | JSX | | ts TSX) $" : ["<rootDir>/node_modules/babel-jest", {"presets": ['babel-preset-react-app'] }], "^.+\\.css$": "<rootDir>/config/jest/cssTransform.js", "^(? ! .*\\.(js|jsx|ts|tsx|css|json)$)": "<rootDir>/config/jest/fileTransform.js", },

This is the biggest pit, and it took me another two hours to do this.

Write in the last

Vite has released version 2, which can be used in the internal project of the company. Because online and offline running is not a set of code, Mr. You also provides a preview function, which is recommended for you to try.

In addition, the project mentioned above :github: vite-react-concent-pro, currently contains a relatively complete set of functions:

  • Start: Start development and debugging locally
  • Build: Pack
  • 12. preview: The code that has been packaged:
  • The test: a single measurement
  • Snap: Generate snapshots

The project integrates React, Concent (a very useful state management library), AnTD, React – Router – DOM, Axios, etc. It can be developed at zero cost.

Of course, if you want to change your existing project to Vite, it’s very simple:

  • Clone the project and delete the contents under SRC;
  • Move the files under SRC of your old project to the files under SRC of this project, then change alias and process.env;
  • Remember to change index. HTML to your entry file

What follows is a miracle

With VitE, the NPM run start was improved by about 80%; NPM Run Build improves by about 50%

! Mmm, it smells good

Finally, if you think it’s good, give it a thumbs up and follow me at github:draven