Vite 简介与核心理念 Vite(法语意为”快速”)是由 Vue.js 作者尤雨溪开发的下一代前端构建工具。它基于原生 ES 模块和现代浏览器特性,在开发环境中使用 esbuild 提供极快的冷启动和热更新体验,在生产环境中使用 Rollup 进行优化构建。
Vite 的革命性设计理念 Vite 代表了前端构建工具的范式转换,它摒弃了传统的”打包优先”思路,转而采用”按需编译”的策略。这种设计哲学的核心在于充分利用现代浏览器的原生 ES 模块支持和高性能的 esbuild 工具。
传统构建工具的痛点 :
冷启动慢 :需要预先打包整个应用
热更新慢 :修改文件后需要重新打包相关模块
开发体验差 :等待时间长,影响开发效率
Vite 的解决方案 :
即时启动 :开发服务器无需预打包,秒级启动
精确热更新 :只更新变化的模块,毫秒级响应
原生 ES 模块 :直接利用浏览器的模块加载能力
Vite 双引擎架构原理 开发引擎 (esbuild) :
目标 :极致的开发体验
特点 :速度优先,功能够用即可
技术 :Go 语言编写,原生性能
生产引擎 (Rollup) :
目标 :优化的生产构建
特点 :质量优先,输出最优代码
技术 :成熟的插件生态,强大的优化能力
Vite 核心运行流程详解 开发环境运行流程 1. 服务启动阶段 :
依赖预构建 :扫描项目依赖,使用 esbuild 预构建第三方库
服务器启动 :启动基于 Connect 的开发服务器
路由注册 :注册各种中间件处理不同类型的请求
2. 模块请求处理 :
路径解析 :将浏览器请求的路径解析为实际的文件路径
文件转换 :根据文件类型选择相应的转换器
依赖重写 :将裸导入重写为可访问的路径
响应返回 :将转换后的内容返回给浏览器
3. 热更新流程 :
文件监听 :监听项目文件的变化
影响分析 :分析文件变化对模块图的影响
精确更新 :只更新受影响的模块
状态保持 :尽可能保持应用的运行时状态
生产环境构建流程 1. 预构建阶段 :
依赖分析 :分析所有的模块依赖关系
代码分割 :根据配置和启发式规则进行代码分割
资源收集 :收集所有需要处理的静态资源
2. Rollup 构建阶段 :
模块解析 :使用 Rollup 解析模块依赖
Tree Shaking :移除未使用的代码
代码优化 :进行各种代码优化和压缩
资源处理 :处理图片、字体等静态资源
3. 输出生成阶段 :
文件生成 :生成最终的构建文件
Hash 计算 :为文件添加内容哈希
Manifest 生成 :生成资源映射表
部署准备 :整理输出目录结构
双引擎协调机制 配置统一 :
同一套配置文件同时控制开发和生产行为
插件系统确保转换逻辑的一致性
环境变量和构建选项的统一管理
插件兼容 :
Vite 插件既支持开发时的实时转换,也支持生产时的批量处理
Rollup 插件可以直接在 Vite 中使用
自动适配不同环境的插件行为
路径解析一致性 :
开发和生产使用相同的路径解析逻辑
别名配置在两个环境中保持一致
资源引用方式统一处理
双引擎设计 下面的代码展示了 Vite 如何在配置层面协调两个引擎:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 import { defineConfig } from "vite" ;import react from "@vitejs/plugin-react" ;export default defineConfig ({ plugins : [react ()], esbuild : { target : "es2020" , format : "esm" , platform : "browser" , }, build : { target : "es2015" , rollupOptions : { output : { manualChunks : { vendor : ["react" , "react-dom" ], utils : ["lodash" , "date-fns" ], }, }, }, }, });
模块解析机制 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 class ViteModuleResolver { constructor (config ) { this .config = config; this .moduleCache = new Map (); this .dependencyGraph = new Map (); } async resolveModule (id, importer ) { if (this .moduleCache .has (id)) { return this .moduleCache .get (id); } const resolved = await this .resolvePath (id, importer); if (this .needsPreBuild (resolved)) { return this .preBuildDependency (resolved); } const transformed = await this .transformModule (resolved); this .moduleCache .set (id, transformed); return transformed; } resolvePath (id, importer ) { if (id.startsWith ("./" ) || id.startsWith ("../" )) { return path.resolve (path.dirname (importer), id); } if (id.startsWith ("/" )) { return path.resolve (this .config .root , id.slice (1 )); } return this .resolveNodeModule (id); } needsPreBuild (modulePath ) { return modulePath.includes ("node_modules" ) && !modulePath.endsWith (".js" ); } async transformModule (modulePath ) { const code = await fs.readFile (modulePath, "utf-8" ); if (modulePath.endsWith (".ts" ) || modulePath.endsWith (".tsx" )) { return this .transformTypeScript (code, modulePath); } if (modulePath.endsWith (".vue" )) { return this .transformVue (code, modulePath); } if (modulePath.endsWith (".jsx" )) { return this .transformJSX (code, modulePath); } return code; } }
Vite 与 Rollup 的深度集成 Rollup 在 Vite 中的角色 Vite 在生产环境中完全基于 Rollup 进行构建,这种设计带来了以下优势:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 export default defineConfig ({ build : { rollupOptions : { input : { main : path.resolve (__dirname, "index.html" ), admin : path.resolve (__dirname, "admin.html" ), }, external : ["react" , "react-dom" ], output : { manualChunks : (id ) => { if (id.includes ("node_modules" )) { if (id.includes ("react" )) { return "react-vendor" ; } if (id.includes ("antd" )) { return "ui-vendor" ; } return "vendor" ; } }, entryFileNames : "assets/[name]-[hash].js" , chunkFileNames : "assets/[name]-[hash].js" , assetFileNames : "assets/[name]-[hash].[ext]" , }, plugins : [ ], }, }, });
Vite 插件与 Rollup 插件的兼容性 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 import { defineConfig } from "vite" ;import { resolve } from "path" ;import commonjs from "@rollup/plugin-commonjs" ;import { nodeResolve } from "@rollup/plugin-node-resolve" ;import typescript from "@rollup/plugin-typescript" ;import vue from "@vitejs/plugin-vue" ;import react from "@vitejs/plugin-react" ;export default defineConfig ({ plugins : [ vue (), react (), { ...commonjs (), apply : "build" , }, { ...nodeResolve (), apply : "build" , }, process.env .NODE_ENV === "production" && typescript (), ].filter (Boolean ), });
开发与生产环境的差异处理 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 class ViteEnvironmentHandler { constructor ( ) { this .isDev = process.env .NODE_ENV === "development" ; this .isProd = process.env .NODE_ENV === "production" ; } async serveDevelopment (moduleId ) { const module = await this .loadModule (moduleId); return { code : module .code , map : module .map , headers : { "Content-Type" : "application/javascript" , "Cache-Control" : "no-cache" , }, }; } async buildProduction (config ) { const rollupConfig = this .generateRollupConfig (config); const bundle = await rollup.rollup (rollupConfig); await bundle.generate ({ format : "es" , dir : "dist" , ...config.build .rollupOptions .output , }); return bundle; } generateRollupConfig (viteConfig ) { return { input : viteConfig.build .rollupOptions .input , external : viteConfig.build .rollupOptions .external , plugins : [ ...this .transformVitePlugins (viteConfig.plugins ), ...this .getRollupSpecificPlugins (), ], }; } }
快速开始与基础配置 项目初始化 1 2 3 4 5 6 7 8 npm create vite@latest my-project yarn create vite my-project pnpm create vite my-project
基础配置文件 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 import { defineConfig } from "vite" ;import vue from "@vitejs/plugin-vue" ;import { resolve } from "path" ;export default defineConfig ({ plugins : [vue ()], server : { port : 3000 , open : true , cors : true , proxy : { "/api" : { target : "http://localhost:8080" , changeOrigin : true , rewrite : (path ) => path.replace (/^\/api/ , "" ), }, }, }, build : { outDir : "dist" , sourcemap : true , minify : "terser" , rollupOptions : { output : { manualChunks : { vue : ["vue" ], vendor : ["lodash" , "axios" ], }, }, }, }, resolve : { alias : { "@" : resolve (__dirname, "src" ), "@components" : resolve (__dirname, "src/components" ), "@utils" : resolve (__dirname, "src/utils" ), }, }, css : { preprocessorOptions : { scss : { additionalData : `@import "@/styles/variables.scss";` , }, }, modules : { localsConvention : "camelCase" , }, }, define : { __APP_VERSION__ : JSON .stringify (process.env .npm_package_version ), __BUILD_TIME__ : JSON .stringify (new Date ().toISOString ()), }, });
开发服务器深度解析 模块请求处理流程 当浏览器请求一个模块时,Vite 开发服务器会经历以下处理步骤:
URL 解析 :解析请求的 URL,确定对应的文件路径
文件读取 :从文件系统读取原始文件内容
插件转换 :应用相应的插件进行代码转换
依赖重写 :重写 import 语句,使其指向正确的模块
缓存处理 :缓存转换结果,提高后续请求性能
响应返回 :将处理后的内容返回给浏览器
依赖重写机制 Vite 开发服务器的一个关键功能是依赖重写,它解决了浏览器无法直接解析 Node.js 风格的裸导入的问题:
1 2 3 4 5 6 7 import React from "react" ;import { lodash } from "lodash" ;import React from "/@fs/node_modules/react/index.js" ;import { lodash } from "/@fs/node_modules/lodash/index.js" ;
这种重写机制的优势:
兼容性 :保持与现有代码的完全兼容
透明性 :开发者无需修改任何代码
灵活性 :支持各种复杂的导入场景
开发服务器架构 下面的代码展示了 Vite 开发服务器的核心架构实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 class ViteDevServer { constructor (config ) { this .config = config; this .app = connect (); this .server = null ; this .moduleGraph = new ModuleGraph (); this .pluginContainer = new PluginContainer (config.plugins ); } async listen (port = 3000 ) { this .setupMiddlewares (); this .server = this .app .listen (port, () => { console .log (`Dev server running at http://localhost:${port} ` ); }); return this .server ; } setupMiddlewares ( ) { this .app .use (cors ()); this .app .use ("/public" , serveStatic (this .config .publicDir )); this .app .use (this .transformMiddleware .bind (this )); this .app .use (this .hmrMiddleware .bind (this )); this .app .use (this .htmlRewriteMiddleware .bind (this )); if (this .config .server .proxy ) { this .setupProxy (); } } async transformMiddleware (req, res, next ) { const url = req.url ; if (!this .shouldTransform (url)) { return next (); } try { const module = await this .resolveModule (url); const transformed = await this .transformModule (module ); res.setHeader ("Content-Type" , "application/javascript" ); res.setHeader ("Cache-Control" , "no-cache" ); res.end (transformed.code ); } catch (error) { this .handleError (error, req, res); } } async transformModule (module ) { let result = { code : module .code , map : null }; for (const plugin of this .pluginContainer .plugins ) { if (plugin.transform ) { result = await plugin.transform (result.code , module .id ); } } result.code = this .rewriteImports (result.code , module .id ); if (this .config .server .hmr ) { result.code = this .injectHMR (result.code , module .id ); } return result; } rewriteImports (code, moduleId ) { return code.replace ( /import\s+(.+)\s+from\s+['"]([^'"]+)['"]/g , (match, imports, source ) => { if (this .isBareImport (source)) { const resolved = this .resolvePreBuiltDep (source); return `import ${imports} from '${resolved} '` ; } return match; } ); } }
生产构建与优化 Vite 在生产环境中完全转换为基于 Rollup 的构建策略,这种设计确保了开发和生产环境的最佳平衡:开发时追求速度,生产时追求质量。
生产构建的设计原理 为什么生产环境选择 Rollup?
成熟的优化策略 :
Rollup 在代码优化方面经验丰富
原生支持 Tree Shaking
优秀的作用域提升能力
丰富的插件生态 :
大量成熟的 Rollup 插件可直接使用
社区经验积累深厚
插件质量普遍较高
输出质量优先 :
生产构建流程详解 1. 构建准备阶段 :
配置合并 :将 Vite 配置转换为 Rollup 配置
插件适配 :将 Vite 插件转换为 Rollup 插件
依赖解析 :分析所有的模块依赖关系
2. 代码分析阶段 :
入口点识别 :确定所有的构建入口
依赖图构建 :建立完整的模块依赖图
分割策略制定 :决定如何进行代码分割
3. 代码转换阶段 :
TypeScript 编译 :将 TypeScript 转换为 JavaScript
JSX 转换 :处理 React/Vue 等框架的语法
CSS 处理 :处理样式文件,包括预处理器
4. 优化处理阶段 :
Tree Shaking :移除未使用的代码
代码压缩 :使用 Terser 等工具压缩代码
资源优化 :压缩图片、字体等静态资源
5. 输出生成阶段 :
文件生成 :生成最终的构建文件
Hash 添加 :为文件添加内容哈希用于缓存
Manifest 生成 :生成资源映射表
Vite 的构建优化策略 智能代码分割 :
自动识别第三方依赖并分离
根据路由进行动态分割
提取公共模块避免重复
资源处理优化 :
小图片自动转换为 Base64
字体文件智能压缩
CSS 提取和压缩
缓存优化 :
文件内容哈希确保缓存有效性
合理的分包策略提高缓存命中率
支持浏览器缓存和 CDN 缓存
插件系统与生态 官方插件生态 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 import { defineConfig } from "vite" ;import vue from "@vitejs/plugin-vue" ;import react from "@vitejs/plugin-react" ;import preact from "@vitejs/plugin-preact" ;import svelte from "@vitejs/plugin-svelte" ;import legacy from "@vitejs/plugin-legacy" ;import { resolve } from "path" ;export default defineConfig ({ plugins : [ vue ({ include : [/\.vue$/ , /\.md$/ ], template : { compilerOptions : { isCustomElement : (tag ) => tag.startsWith ("my-" ), }, }, }), react ({ include : "**/*.{jsx,tsx}" , babel : { plugins : [["@babel/plugin-proposal-decorators" , { legacy : true }]], }, }), legacy ({ targets : ["defaults" , "not IE 11" ], }), VitePWA ({ registerType : "autoUpdate" , workbox : { globPatterns : ["**/*.{js,css,html,ico,png,svg}" ], }, }), ], });
社区插件示例 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 import { defineConfig } from "vite" ;import { createHtmlPlugin } from "vite-plugin-html" ;import { visualizer } from "rollup-plugin-visualizer" ;import viteCompression from "vite-plugin-compression" ;import { viteStaticCopy } from "vite-plugin-static-copy" ;export default defineConfig ({ plugins : [ createHtmlPlugin ({ minify : true , inject : { data : { title : "My Vite App" , description : "A modern web application" , }, }, }), visualizer ({ filename : "dist/stats.html" , open : true , gzipSize : true , }), viteCompression ({ algorithm : "gzip" , ext : ".gz" , }), viteStaticCopy ({ targets : [ { src : "assets/robots.txt" , dest : "." , }, ], }), viteMockServe ({ mockPath : "mock" , localEnabled : true , prodEnabled : false , }), ], });
模块热替换 (HMR) 原理 HMR 架构设计 Vite 的 HMR 系统采用了客户端-服务端协作的架构:
服务端职责 :
文件监听 :监控项目文件的变化
依赖分析 :分析文件变化对模块图的影响
更新决策 :决定哪些模块需要更新
通知推送 :通过 WebSocket 推送更新信息
客户端职责 :
连接维护 :维持与服务端的 WebSocket 连接
更新接收 :接收服务端的更新通知
模块替换 :执行实际的模块替换逻辑
状态恢复 :尽可能恢复或保持应用状态
HMR 更新策略 1. 精确影响分析 :
Vite 维护了一个完整的模块依赖图
当文件发生变化时,分析其对依赖图的影响
只标记真正受影响的模块进行更新
2. 边界检测 :
检测更新边界,确定更新的范围
如果模块接受自身更新,则在该模块停止传播
如果模块不接受更新,则继续向上传播
3. 状态保持策略 :
框架级别的状态保持(如 React Fast Refresh)
应用级别的状态保持(通过 HMR API)
自动状态恢复机制
HMR API 设计 Vite 提供了灵活的 HMR API,允许开发者精确控制模块的热更新行为:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 if (import .meta .hot ) { import .meta .hot .accept ((newModule ) => { }); import .meta .hot .accept (["./dependency.js" ], ([newDep] ) => { }); import .meta .hot .dispose (() => { }); }
框架集成的 HMR 不同框架的 HMR 集成策略各有特色:
React Fast Refresh :
Vue HMR :
支持模板、脚本、样式的独立更新
保持组件实例状态
支持 Composition API
Svelte HMR :
依赖预构建机制 依赖预构建是 Vite 架构中的一个关键组成部分,它解决了 ES 模块和 CommonJS 模块之间的兼容性问题,同时显著提升了开发服务器的性能。
依赖预构建的必要性 为什么需要预构建?
模块格式兼容性 :
大多数 npm 包仍然使用 CommonJS 格式
浏览器原生只支持 ES 模块
需要转换 CommonJS 为 ES 模块
性能优化 :
减少 HTTP 请求数量(将多个文件合并)
缓存第三方依赖(避免重复处理)
提高模块加载速度
兼容性处理 :
处理不同的导入导出语法
解决循环依赖问题
统一模块路径格式
预构建的工作流程 1. 依赖发现阶段 :
扫描项目入口文件和 HTML 文件
识别所有的裸导入(bare imports)
收集需要预构建的依赖列表
2. 依赖分析阶段 :
分析每个依赖的 package.json
确定依赖的入口文件
检测依赖的模块格式
3. 预构建执行阶段 :
使用 esbuild 进行快速转换
生成 ES 模块格式的代码
创建依赖映射表
4. 缓存管理阶段 :
存储预构建结果到缓存目录
生成缓存元数据
检测依赖变化以决定是否重新构建
esbuild 在预构建中的作用 Vite 选择 esbuild 进行依赖预构建的原因:
性能优势 :
Go 语言编写,原生性能
并行处理,充分利用多核 CPU
相比传统工具快 10-100 倍
功能完备 :
支持 TypeScript、JSX 转换
内置 CommonJS 到 ES 模块的转换
支持代码分割和树摇
稳定可靠 :
成熟的工具,bug 少
与 Vite 的需求完美匹配
活跃的社区支持
预构建缓存策略 Vite 采用了智能的缓存策略来优化预构建性能:
缓存触发条件 :
package.json 的 dependencies 发生变化
包管理器的锁文件发生变化
Vite 配置文件中的相关配置发生变化
缓存失效机制 :
自动检测依赖版本变化
支持手动清除缓存
开发时支持强制重新构建
预构建配置优化 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 export default defineConfig ({ optimizeDeps : { include : ["react" , "react-dom" , "lodash-es" , "@ant-design/icons" ], exclude : ["your-local-package" , "@some/esm-package" ], esbuildOptions : { target : "es2020" , plugins : [ { name : "custom-resolver" , setup (build ) { build.onResolve ({ filter : /^custom:/ }, (args ) => { return { path : args.path , namespace : "custom" , }; }); }, }, ], }, force : process.env .FORCE_OPTIMIZE === "true" , }, });
性能优化策略 开发环境优化 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 export default defineConfig ({ optimizeDeps : { include : ["react" , "react-dom" , "lodash-es" , "date-fns" , "antd" ], force : process.env .FORCE_OPTIMIZE === "true" , }, server : { warmup : { clientFiles : ["./src/components/*.vue" , "./src/utils/*.js" ], }, sourcemapIgnoreList : (sourcePath, sourcemapPath ) => { return sourcePath.includes ("node_modules" ); }, }, esbuild : { target : "es2020" , drop : process.env .NODE_ENV === "production" ? ["console" , "debugger" ] : [], }, });
生产环境优化 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 export default defineConfig ({ build : { target : "es2015" , chunkSizeWarningLimit : 500 , assetsInlineLimit : 4096 , cssCodeSplit : true , rollupOptions : { output : { manualChunks : (id ) => { if (id.includes ("node_modules" )) { if (id.includes ("react" ) || id.includes ("react-dom" )) { return "react-vendor" ; } if (id.includes ("echarts" ) || id.includes ("d3" )) { return "charts-vendor" ; } if (id.includes ("antd" ) || id.includes ("@ant-design" )) { return "ui-vendor" ; } if (id.includes ("lodash" ) || id.includes ("moment" )) { return "utils-vendor" ; } return "vendor" ; } if (id.includes ("src/pages/" )) { const pageName = id.split ("src/pages/" )[1 ].split ("/" )[0 ]; return `page-${pageName} ` ; } }, }, }, terserOptions : { compress : { drop_console : true , drop_debugger : true , pure_funcs : ["console.log" , "console.info" ], passes : 2 , }, mangle : { safari10 : true , }, format : { comments : false , safari10 : true , }, }, }, });
缓存策略优化 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 export class ViteCacheOptimizer { constructor (config ) { this .config = config; this .cacheStrategies = new Map (); } setupCacheStrategies ( ) { return { name : "cache-optimizer" , generateBundle (opts, bundle ) { Object .keys (bundle).forEach ((fileName ) => { const chunk = bundle[fileName]; if (chunk.type === "chunk" ) { const stability = this .analyzeStability (chunk); if (stability === "stable" ) { chunk.fileName = chunk.fileName .replace ( "[hash]" , this .generateContentHash (chunk.code ) ); } else { chunk.fileName = chunk.fileName .replace ( "[hash]" , Date .now ().toString (36 ) ); } } }); }, }; } analyzeStability (chunk ) { const modules = Object .keys (chunk.modules ); const vendorModules = modules.filter ((id ) => id.includes ("node_modules" )); if (vendorModules.length / modules.length > 0.8 ) { return "stable" ; } return "unstable" ; } generateContentHash (content ) { return crypto.createHash ("md5" ).update (content).digest ("hex" ).slice (0 , 8 ); } }