This is the 105th original article without water. If you want to get more original articles, please search the official account and pay attention to us.
Vite features and partial source parsing
The characteristics of Vite
Vite’s main feature is Bundleless. JavaScript module is supported based on browser. JavaScript module relies on import and export features, which are basically supported by mainstream browsers at present.
To see which versions are supported, click here;
So what’s the advantage?
Remove the packing step
Packaging is a way for developers to use packaging tools to bundle together various application modules into bundles, and to read the code of the modules according to certain rules, so that it can be used in browsers that do not support modularity, and can reduce the number of HTTP requests. However, packaging in the local development process actually increased the difficulty of troubleshooting problems and increased the response time, and Vite eliminated the packaging step in the local development command, thus shortening the build time.
According to the need to load
To reduce the bundle size, there are two main ways to load on demand:
- Using dynamic introduction
import()
Asynchronous loading of modules, introduced modules still need to be compiled and packaged in advance; - Try your best to remove unreferenced modules using Tree Shaking, etc.
The way of VET is more direct. It only dynamically loads a module when it is imported, realizing real on-demand loading, reducing the size of the loading file and shortening the time.
VitE development environment main process
The following figure shows the main flow of the Vite file loading while the development environment is running.
<img src=”https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/6922f9b694324cb193acdbb482babadb~tplv-k3u1fbpfcp-zoom-1.image” style=”zoom:60%;” />
Vite part source code parsing
Overall directory structure
|-CHANGELOG.md |-LICENSE.md |-README.md |-bin | |-openChrome.applescript | |-vite.js |-client.d.ts |-package.json | - a rollup. Config. Js # packaging configuration file | - scripts | | - patchTypes. Js | - SRC | | - client # client | | | - client. Ts | | | - env. Ts | | | - overlay. Ts | | | - tsconfig. Json | | - node # server | | | - build. Ts | | | - cli. Ts # command entry file | | | - config. Ts | | | - the ts # constant | | | - importGlob. Ts | | | - index. Ts | | | - logger. Ts | | | - optimizer | | | | - esbuildDepPlugin. Ts | | | | - index. Ts | | . | | - registerMissing ts | | | | - scan. Ts | | | - plugin. Ts # rollup plugins | | | - plugins # plug-in documentation | | | | - asset. Ts | | | |-clientInjections.ts | | | |-css.ts | | | |-esbuild.ts | | | |-html.ts | | | |-index.ts | | | |-... | | | - preview. Ts | | | - server | | | | - HMR. Ts # hot update | | | | - HTTP. Ts | | | | - index. Ts | | | | - middlewares # middleware | | | | | -... | | | | - moduleGraph. Relationship between ts # module assembly (tree) | | | | - openBrowser. Ts # open a browser | | | | - pluginContainer. Ts | | | | - send. Ts | | | |-sourcemap.ts | | | |-transformRequest.ts | | | |-ws.ts | | |-ssr | | | |-__tests__ | | | | |-ssrTransform.spec.ts | | | |-ssrExternal.ts | | | |-ssrManifestPlugin.ts | | | |-ssrModuleLoader.ts | | | |-ssrStacktrace.ts | | | |-ssrTransform.ts | | |-tsconfig.json | | |-utils.ts |-tsconfig.base.json |-types | |-...
Server Core Method
From the entry file cli.ts, you can see that three commands correspond to three core file-methods;
- Dev command
File path:./server/index.ts;
Main methods: CreateServer;
Main functions: The local development command of the project, which starts the service based on HttpServer, Vite obtains the content of the resource by hijacking the request path and returns it to the browser, and the server rewrites the file path. Such as:
The project source code is as follows:
import { createApp } from 'vue';
import App from './index.vue';
After server-side overwrite, the code path of the three-party package in the node_modules folder will also be concatenated.
import __vite__cjsImport0_vue from "/node_modules/.vite/vue.js? v=ed69bae0"; const createApp = __vite__cjsImport0_vue["createApp"]; import App from '/src/pages/back-sky/index.vue';
2. Build command file path:./build.ts;
Main methods: build;
Main Function: Use Rollup to package and compile
3. Optimize the order
File path:./optimizer/index.ts;
Main methods: OptimizeDEPS;
Main functions: For the third party packages, Vite will use rollup to recompile the third party packages when executing runOptimize, and put the new packages compiled to conform to the ESM module specification into.vite under node_modules. Then cooperate with Resolver to handle the import of the three-party package: the compiled package content is used to replace the contents of the original package, so as to solve the problem that the CJS package cannot be used in ite.
Below is the _metadata.json file in the.vite folder, which was generated during the precompilation process and lists all the precompiled files and their paths. Such as:
{ "hash": "31d458ff", "browserHash": "ed69bae0", "optimized": { "element-plus/lib/utils/dom": { "file": "/Users/zcy/Documents/workspace/back-sky-front/node_modules/.vite/element-plus_lib_utils_dom.js", "src": "/Users/zcy/Documents/workspace/back-sky-front/node_modules/element-plus/lib/utils/dom.js", "needsInterop": true }, "element-plus": { "file": "/Users/zcy/Documents/workspace/back-sky-front/node_modules/.vite/element-plus.js", "src": "/Users/zcy/Documents/workspace/back-sky-front/node_modules/element-plus/lib/index.esm.js", "needsInterop": false }, "vue": { "file": "/Users/zcy/Documents/workspace/back-sky-front/node_modules/.vite/vue.js", "src": "/Users/zcy/Documents/workspace/back-sky-front/node_modules/vue/dist/vue.runtime.esm-bundler.js", "needsInterop": true }, ...... }}}
Module parse
Prebuild is used to speed up page reloads by converting CommonJS, UMD, and so on to ESM format. The pre-build step is performed by esbuild, which makes the cold start time of Vite much faster than any JavaScript-based package.
Why is ESbuild faster?
- Using the Go language
- Heavy parallelism, using CPU
- Efficient memory use
- Scratch to write, reduce the use of third-party libraries, to avoid uncontrollable performance
Rewrite the import to a valid URL, such as /node_modules/.vite/my-dep.js? V =f3sf2ebd so that the browser can import them correctly
Hot update
The main process of hot update is as follows:
- The server listens for file changes based on Watcher, determines the updating method according to the type, and compiles the resource
- The client listens for some updated message types through WebSocket
- The client receives the resource information and executes the hot update logic based on the message type
The following is part of the judgment logic in the core HMR.TS of server hot update;
If the configuration file or environment file changes, a service restart is triggered for the configuration to take effect.
If (the file = = = config. ConfigFile | | file. The endsWith (' env ")) {/ / auto restart server configuration file modification & environment automatically restart service debugHmr (` [config change] ${chalk.dim(shortFile)}`) config.logger.info( chalk.green('config or .env file changed, restarting server... '), { clear: true, timestamp: true } ) await restartServer(server) return }
When the HTML file is updated, a reload of the page is triggered.
If (file.endsWith('.html')) {// HTML file update config.logger.info(chalk. Green (' Page Reloading ') + chalk. Dim (shortFile), { clear: true, timestamp: true }) ws.send({ type: 'full-reload', path: config.server.middlewareMode ? '*' : '/' + normalizePath(path.relative(config.root, file)) }) } else { // loaded but not in the module graph, probably not js debugHmr(`[no modules matched] ${chalk.dim(shortFile)}`) }
When VUE and other files are updated, they will enter the updateModules method. Under normal circumstances, only UPDATE will be triggered to realize hot update and hot replacement.
function updateModules( file: string, modules: ModuleNode[], timestamp: number, { config, ws }: ViteDevServer ) { const updates: Update[] = [] const invalidatedModules = new Set<ModuleNode>() // For (const mod of modules) {const boundaries = new Set<{boundary: ModuleNode acceptedVia: ModuleNode}>() // Set the timestamp invalidate(mod, timestamp, invalidatedModules) // Determinate if you need to reload the page const hasDeadEnd = PROPAGATEUPDATE (mod, timestamp, Boundaries) // flush if (hasDeadend) {config.logger.info(chalk. Green (' Page Reload ') + chalk. Dim (file), { clear: true, timestamp: true }) ws.send({ type: 'full-reload' }) return } updates.push( ... [...boundaries].map(({ boundary, acceptedVia }) => ({ type: `${boundary.type}-update` as Update['type'], timestamp, path: boundary.url, acceptedPath: AcceptAcceptVa.url}))} // log output config.logger.info(updates. Map (({path}) => Chalk. Green (' HMR Update ') + Chalk. Dim (path)).join('\n'), {clear: true, timestamp: true}) // Send a message to the client for a hot update operation ws.send({type: 'update', updates }) }
The modules in the above code are the various plug-ins that need to be executed when the hot update occurs
for (const plugin of config.plugins) {
if (plugin.handleHotUpdate) {
const filteredModules = await plugin.handleHotUpdate(hmrContext)
if (filteredModules) {
hmrContext.modules = filteredModules
}
}
}
Vite combines module dependencies into a ModuleGraph, which has a tree-like structure and relies on hot updates to determine which files need to be updated. Its file reads something like this:
// ModuleGraph returns ModuleNode {id: ModuleNode; '/Users/zcy/Documents/workspace/back-sky-front/src/pages/back-sky/index.js', file: '/Users/zcy/Documents/workspace/back-sky-front/src/pages/back-sky/index.js', importers: Set {}, importedModules: Set { ModuleNode { id: '/Users/zcy/Documents/workspace/back-sky-front/node_modules/.vite/vue.js? v=32cfd30c', file: '/Users/zcy/Documents/workspace/back-sky-front/node_modules/.vite/vue.js', ...... lastHMRTimestamp: 0, url: '/node_modules/.vite/vue.js? v=32cfd30c', type: 'js' }, ModuleNode { id: '/Users/zcy/Documents/workspace/back-sky-front/src/pages/back-sky/index.vue', file: '/Users/zcy/Documents/workspace/back-sky-front/src/pages/back-sky/index.vue', ...... url: '/src/pages/back-sky/index.vue', type: 'js' }, ModuleNode { id: '/Users/zcy/Documents/workspace/back-sky-front/node_modules/element-plus/lib/theme-chalk/index.css', file: '/Users/zcy/Documents/workspace/back-sky-front/node_modules/element-plus/lib/theme-chalk/index.css', importers: [Set], importedModules: Set {}, acceptedHmrDeps: Set {}, isSelfAccepting: true, transformResult: [Object], ssrTransformResult: null, ssrModule: null, lastHMRTimestamp: 0, url: '/node_modules/element-plus/lib/theme-chalk/index.css', type: 'js' }, ...... }, acceptedHmrDeps: Set {}, isSelfAccepting: false, transformResult: { code: 'import __vite__cjsImport0_vue from ' + '"/node_modules/.vite/vue.js? v=32cfd30c"; const createApp = ' + '__vite__cjsImport0_vue["createApp"]; \nimport App from ' + "'/src/pages/back-sky/index.vue'; \nimport " + "'/node_modules/element-plus/lib/theme-chalk/index.css'; \n\nconst app = " + 'createApp(App); \n\nimport { addHistoryMethod } from ' + "'/src/pages/back-sky/api/index.js'; \nimport {\n ElButton,\n ElDropdown,\n " + 'ElDropdownMenu,\n ElDropdownItem,\n ElMenu,\n ElSubmenu,\n ElMenuItem,\n ' + 'ElMenuItemGroup,\n ElPopover,\n ElDialog,\n ElRow,\n ElInput,\n ' + "ElLoading,\n} from '/node_modules/.vite/element-plus.js? v=32cfd30c'; \n\n" + 'app.use(ElButton); \napp.use(ElLoading); \napp.use(ElDropdown); \n' + 'app.use(ElDropdownMenu); \napp.use(ElDropdownItem); \napp.use(ElMenu); \n' + 'app.use(ElSubmenu); \napp.use(ElMenuItem); \napp.use(ElMenuItemGroup); \n' + 'app.use(ElPopover); \napp.use(ElDialog); \napp.use(ElRow); \napp.use(ElInput); \n' + "\nconst f = ()=>{\n return app.mount('#app'); \n}; \n\nconst $backsky = " + "document.getElementById('back-sky'); \nif($backsky) {\n $backsky.innerHTML " + "= ''; \n $backsky.appendChild(f().$el); \n} else {\n window.onload = " + "function(){\n document.getElementById('back-sky') && " + "document.getElementById('back-sky').appendChild(f().$el); \n }; \n}\n\n" + "window.addHistoryListener = addHistoryMethod('historychange'); \n" + "history.pushState = addHistoryMethod('pushState'); \nhistory.replaceState " + "= addHistoryMethod('replaceState'); \n\n// Listen for hash route changes, \n" + 'addHashChange(()=>{\n setTimeout(() =>{\n const $backsky = '+ "document.getElementById('back-sky'); \n if($backsky && " + "$backsky.innerHTML === '') {\n $backsky.appendChild(f().$el); \n }\n " + " },0); \n}); \n\ nFunction addHashChange(callback) {\n if(' onHashChange 'in "+ 'window === false){// Browser does not support \n return false; \n }\n ' + 'if(window.addEventListener) {\n ' + "window.addEventListener('hashchange',function(e) {\n callback && " + 'callback(e); \n },false); \n}else if(window.attachEvent) {//IE 8 and earlier IE '+ "version \n window.attachEvent(' onHashChange ',function(e) {\n callback" + '&& callback(e); \n }); \n }\n ' + "window.addHistoryListener('history',function(e){\n callback && " + 'callback(e); \n }); \n}\n\n\n', map: null, etag: 'W/"846-Qa424gJKl3YCqHDWXXsM1mFHRqg"' }, ssrTransformResult: null, ssrModule: null, lastHMRTimestamp: 0, url: '/src/pages/back-sky/index.js', type: 'js' }
Original project switching
Finally, let’s look at how to package an old Vue project using Vite;
First we need to upgrade Vue3
npm install vue@next
Add the vite configuration file to the project, create vite.config.js in the root directory, and add some basic configuration for it.
// vite.config.js // vite2.1.5 const path = require('path'); import vue from '@vitejs/plugin-vue'; Export default {alias: {'@utils': path.resolve(__dirname, './ SRC /utils')},}, plugins: [vue()],};
Third-party component libraries referenced may also need to be upgraded, for example, by raising Element-UI to Element-Plus
npm install element-plus
When VUE3 imports, it needs to be initialized using the createApp method
import { createApp } from 'vue'; import App from './index.vue'; const app = createApp(App); import { ElInput, ElLoading, } from 'element-plus'; app.use(ElButton); app.use(ElLoading); .
This is where you can get the project running. Note: Vite officials do not allow to omit. Vue suffix, otherwise an error will be reported.
[plugin:vite:import-analysis] Failed to resolve import "./todoList" from "src/pages/back-sky/components/header/index.vue". Does the file exist?
/components/header/index.vue:2:23
1 |
2 | import todoList from './todoList';
import todoList from './todoList.vue';
Finally, let’s compare the time of the two construction methods of the project;
Webpack cold start, time 7513ms:
⚠ "WDM" : Hash: 1AD1DD54289CFAD8ECBE Version: Webpack 4.46.0 Time: 7513ms Built at: 2021-05-24 13:59:35
The cold start of VITE of the same project takes 924ms:
> vite
Pre-bundling dependencies:
vue
element-plus
@zcy/zcy-request
element-plus/lib/utils/dom
(this will be run only when your dependencies or config have changed)
vite v2.3.3 dev server running at:
> Local: http://localhost:3000/
> Network: use `--host` to expose
ready in 924ms.
Secondary startup (pre-compiled dependency already exists), 407ms;
> vite
vite v2.3.3 dev server running at:
> Local: http://localhost:3000/
> Network: use `--host` to expose
ready in 407ms.
conclusion
There are obvious benefits to using Vite for both local service startup and hot update, but what are the differences in compilation and packaging? How did it work? What other potholes have you stepped in? Leave me a message.
Recommended reading:
What are CJS, AMD, UMD, and ESM in Javascript?
Open source works
- Politics gathers cloud front tabloids
Open source address www.zoo.team/openweekly/ (there is WeChat communication group on the homepage of the tabloid official website)
, recruiting
ZooTeam, a young, passionate and creative front-end team, is affiliated to the R&D Department of ZooTeam, based in the picturesque city of Hangzhou. There are more than 40 front-end partners in the team, with an average age of 27. Nearly 30% of them are full stack engineers, no problem young storm group. The members are not only “old” soldiers from Ali and netease, but also fresh graduates from Zhejiang University, University of Science and Technology of China and Hangzhou Electric University. In addition to daily business connection, the team also carries out technical exploration and practical practice in the directions of material system, engineering platform, construction platform, performance experience, cloud application, data analysis and visualization, promotes and lands a series of internal technical products, and continues to explore the new boundary of the front-end technical system.
If you want to change what you’ve been doing, you want to start doing it. If you want to change the way you’ve been told you need to think a little bit, you can’t break it. If you want to change, you have the power to do it, but you don’t need to; If you want to change what you want to do, you need a team to support it, but there is no place for you to bring people. If you want to change the established pace, it will be “5 years and 3 years experience”; If you want to change the original savvy is good, but there is always a layer of window paper fuzzy… If you believe in the power of belief, the ability of ordinary people to do extraordinary things, to meet better people. If you want to be a part of the take-off process and personally drive the growth of a front end team that has a deep business understanding, a sound technology system, technology that creates value, and influence that spills over, I think we should talk. Anytime, waiting for you to write something down and send it to [email protected]