构建与运行从源码到浏览器里的那条 JS一、导读代码块含// 语法要点、//正确示例、//错误示例配置以Vite为主当前生态主流webpack 仅在对比时出现。建议本地npm create vitelatest建一个空项目对照vite.config与package.json看脚本与产物目录dist。目标能说清开发时浏览器为什么能直接跑 TS/TSX、生产构建多了哪一步、import在磁盘与在浏览器里的含义差在哪看到base、publicDir、环境变量前缀知道各自解决什么问题。二、为什么需要「构建」浏览器只认什么本章开发体验与线上交付有时是两条路径。1 浏览器能直接执行什么定义浏览器原生执行JavaScript含ES Modules的import/export。TypeScript、JSX、Less、Vue SFC等不是浏览器内置语言——要么编译成 JS要么交给支持该语法的 dev server在开发时即时转译。// 语法要点生产环境最终应是标准 JS及静态资源//正确示例——源码 TSexportconstadd(a:number,b:number)ab;// 构建后 dist 里对应 emit 的 .js 不含类型注解//错误示例// 把 .ts 文件不经构建直接丢到 nginx 静态目录给用户访问—— 浏览器不理解类型语法啥时候用上线— 几乎总有「打包 / 转译 / 压缩 / 哈希文件名」之一或组合。2 开发时「热更新」靠什么定义开发服务器如 Vite在内存里做模块图与转译浏览器通过HTTP拉取单个模块文件改文件后HMR只替换变更模块边界整页刷新成本更高。啥时候用本地调 UI— HMR改了根入口或环境变量— 常需整页重载或重启 dev server。三、模块系统简史CJS 与 ESM本章读package.json的type: module与.mts/.cts时需要这段背景。1 CommonJSCJS定义require/module.exportsNode 传统默认同步加载语义运行时拼路径浏览器原生不支持require需打包器打包成 IIFE/ESM 等。// 语法要点常见于老包与 Node 脚本//正确示例Node// const fs require(fs);//错误示例// 在浏览器 script 里直接 require —— 无打包时不可用2 ES ModulesESM定义import/export静态结构利于tree-shaking浏览器script typemodule原生支持。文件级严格模式等语义与 CJS 不同。// 语法要点import 提升在模块顶层动态 import() 返回 Promise//正确示例import{ref}fromvue;constmodawaitimport(./heavy.ts);//错误示例if(Math.random()0.5){import./a.js;// 非法静态 import 须在顶层动态用 import()}啥时候用新前端工程— 默认 ESMNode 工具链逐步 ESM 化。四、Vite1 开发vite dev定义利用浏览器原生 ESM每个import对应一次或可缓存的HTTP 模块请求。对node_modules里依赖先用esbuild做依赖预打包预构建把多文件 CJS/分散 ESM 收成浏览器友好形态加快冷启动与解析。// 语法要点源码 TS → esbuild 转译 → 浏览器执行 JS啥时候用中大型依赖— 预构建避免「上千小文件直连浏览器」。2 生产vite build定义生产构建使用Rollup做tree-shaking、代码分割、资源处理产出dist/静态目录HTML、JS chunk、CSS、图片等。开发服务器不参与线上流量。// npm run build → rollup 产出 dist部署 把 dist 交给 CDN / 静态服务器 / 嵌入服务端模板啥时候用性能与缓存— chunk 分割、[hash]文件名由 Rollup/Vite 插件体系生成。五、入口、根路径与public本章index.html在项目根、script typemodule src/src/main.ts由 Vite 解析public/下文件原样拷贝到 dist 根不走打包管道适合robots.txt、已有固定名的第三方脚本。1base定义base是部署在子路径时的公共前缀如https://cdn.com/myapp/则base: /myapp/。错误 base会导致chunk 404、资源路径全错。// vite.config.ts 语法要点import{defineConfig}fromvite;//正确示例exportdefaultdefineConfig({base:/shop/,// 部署在子目录时});//错误示例// base 写成相对路径 ./ 在部分部署场景与 deep link 组合时易踩路径解析坑—— 查官方「Relative base」说明再选啥时候用GitHub Pages 子路径、企业门户子应用— 必配base。2publicDir定义默认public适合永不改名的静态 URL如favicon.ico。需要hash 缓存破坏的资源应import进源码让打包器处理。啥时候用第三方要求固定路径— 放public。六、路径别名resolve.alias本章把/components映射到src/components减少相对路径../../类型提示需在tsconfig的paths同步否则仅运行正确、IDE 报红。若项目为ESMtype: modulevite.config.ts里可能没有 Node 注入的__dirname需用import.meta.url推导当前配置文件目录。// vite.config.tsimportpathfromnode:path;import{fileURLToPath}fromnode:url;import{defineConfig}fromvite;const__dirnamefileURLToPath(newURL(.,import.meta.url));exportdefaultdefineConfig({resolve:{alias:{:path.resolve(__dirname,src),},},});// tsconfig.app.json 片段{compilerOptions:{baseUrl:.,paths:{/*:[src/*]}}}啥时候用组件超过百个— 别名 paths 几乎标配。七、环境变量谁看得见本章Vite只在构建时把以VITE_开头的环境变量注入客户端代码没有前缀的变量不会暴露给浏览器 bundle防泄漏。import.meta.env是 Vite 约定不是通用 Node 全局。// 语法要点import.meta.env.MODE / DEV / PROD / VITE_*//正确示例constapiBaseimport.meta.env.VITE_API_BASE??/api;//错误示例constsecretimport.meta.env.DB_PASSWORD;// 未加 VITE_ 前缀打包进浏览器里拿不到也不该拿定义.env.production、.env.development按mode加载永远不要把私钥写进任何会被打进前端的变量名前缀下。啥时候用多环境 API 根路径—VITE_API_BASE。八、开发代理server.proxy本章把/api转发到https://backend.test浏览器眼里仍是同源从而开发阶段绕过 CORS仅 dev server 生效生产环境需在网关或后端配置 CORS / BFF。// vite.config.tsexportdefaultdefineConfig({server:{proxy:{/api:{target:http://localhost:8080,changeOrigin:true,},},},});啥时候用本地前后端分离— 必备技巧上线勿依赖。九、Tree-shaking什么能被摇掉定义ESM 静态结构让打包器分析「从未被引用的导出」副作用模块顶层有注册、改原型等会标记sideEffects: false在package.json里帮助摇树乱标会导致运行时缺代码。// 正确示例——纯函数库易摇树exportconstunused()1;exportconstused()2;// 若别处只 import { used }unused 可被移除在无副作用前提下//错误示例// 某库入口文件顶层执行 window.X ... 仍被标 sideEffects:false —— 线上神秘 bug啥时候用发布 npm 包— 如实填写sideEffects字段。十、import()动态导入与代码分割定义import()返回Promisemodule打包器通常为每个动态导入创建async chunk实现路由级懒加载。//正确示例constpageawaitimport(./pages/Admin.vue);//错误示例constpath./pages/name.vue;awaitimport(path);// 多数打包器无法静态分析全部可能路径—— 需有限映射表或 glob 约定啥时候用重管理后台页— 不给首屏加载体积。十一、Source Map 与线上调试定义Source map把压缩后的列号映射回源码公开到生产会暴露源码结构——通常内联到错误监控平台私有上传或仅 CI 存档不随dist对用户公开。啥时候用SentrysourceMapUpload— 上传 map用户侧不下发。十二、常见踩坑清单现象常见原因白屏 chunk 404base与 CDN 子路径不一致环境变量 undefined未VITE_前缀或未重启 dev server类型找不到/只配了 vite alias 未配 tsconfig pathsHMR 失效循环依赖、或改了非 HMR 边界文件动态 import 全进主包路径非静态可分析十三、速查总表与结语1 速查表概念一句话Vite dev原生 ESM esbuild 预构建依赖Vite buildRollup 产出 distbase部署子路径公共前缀public原样拷贝、不进管道VITE_*唯一默认可进浏览器 bundle 的 envserver.proxy仅开发、治 CORS 体验tree-shakingESM 静态分析删未引用代码import()懒加载 分 chunk2 结语构建工具解决的是开发要快、上线要小、路径要对、秘密不漏。把Vite 的 dev/build 双模与import.meta.env规则记牢你就不会在「本地好好的、一上 CDN 全 404」里耗一整天。部署前 checklistbase、环境变量前缀、dist里有没有误带.map到公网。
构建与运行(从源码到浏览器里的那条 JS)
发布时间:2026/5/16 7:11:17
构建与运行从源码到浏览器里的那条 JS一、导读代码块含// 语法要点、//正确示例、//错误示例配置以Vite为主当前生态主流webpack 仅在对比时出现。建议本地npm create vitelatest建一个空项目对照vite.config与package.json看脚本与产物目录dist。目标能说清开发时浏览器为什么能直接跑 TS/TSX、生产构建多了哪一步、import在磁盘与在浏览器里的含义差在哪看到base、publicDir、环境变量前缀知道各自解决什么问题。二、为什么需要「构建」浏览器只认什么本章开发体验与线上交付有时是两条路径。1 浏览器能直接执行什么定义浏览器原生执行JavaScript含ES Modules的import/export。TypeScript、JSX、Less、Vue SFC等不是浏览器内置语言——要么编译成 JS要么交给支持该语法的 dev server在开发时即时转译。// 语法要点生产环境最终应是标准 JS及静态资源//正确示例——源码 TSexportconstadd(a:number,b:number)ab;// 构建后 dist 里对应 emit 的 .js 不含类型注解//错误示例// 把 .ts 文件不经构建直接丢到 nginx 静态目录给用户访问—— 浏览器不理解类型语法啥时候用上线— 几乎总有「打包 / 转译 / 压缩 / 哈希文件名」之一或组合。2 开发时「热更新」靠什么定义开发服务器如 Vite在内存里做模块图与转译浏览器通过HTTP拉取单个模块文件改文件后HMR只替换变更模块边界整页刷新成本更高。啥时候用本地调 UI— HMR改了根入口或环境变量— 常需整页重载或重启 dev server。三、模块系统简史CJS 与 ESM本章读package.json的type: module与.mts/.cts时需要这段背景。1 CommonJSCJS定义require/module.exportsNode 传统默认同步加载语义运行时拼路径浏览器原生不支持require需打包器打包成 IIFE/ESM 等。// 语法要点常见于老包与 Node 脚本//正确示例Node// const fs require(fs);//错误示例// 在浏览器 script 里直接 require —— 无打包时不可用2 ES ModulesESM定义import/export静态结构利于tree-shaking浏览器script typemodule原生支持。文件级严格模式等语义与 CJS 不同。// 语法要点import 提升在模块顶层动态 import() 返回 Promise//正确示例import{ref}fromvue;constmodawaitimport(./heavy.ts);//错误示例if(Math.random()0.5){import./a.js;// 非法静态 import 须在顶层动态用 import()}啥时候用新前端工程— 默认 ESMNode 工具链逐步 ESM 化。四、Vite1 开发vite dev定义利用浏览器原生 ESM每个import对应一次或可缓存的HTTP 模块请求。对node_modules里依赖先用esbuild做依赖预打包预构建把多文件 CJS/分散 ESM 收成浏览器友好形态加快冷启动与解析。// 语法要点源码 TS → esbuild 转译 → 浏览器执行 JS啥时候用中大型依赖— 预构建避免「上千小文件直连浏览器」。2 生产vite build定义生产构建使用Rollup做tree-shaking、代码分割、资源处理产出dist/静态目录HTML、JS chunk、CSS、图片等。开发服务器不参与线上流量。// npm run build → rollup 产出 dist部署 把 dist 交给 CDN / 静态服务器 / 嵌入服务端模板啥时候用性能与缓存— chunk 分割、[hash]文件名由 Rollup/Vite 插件体系生成。五、入口、根路径与public本章index.html在项目根、script typemodule src/src/main.ts由 Vite 解析public/下文件原样拷贝到 dist 根不走打包管道适合robots.txt、已有固定名的第三方脚本。1base定义base是部署在子路径时的公共前缀如https://cdn.com/myapp/则base: /myapp/。错误 base会导致chunk 404、资源路径全错。// vite.config.ts 语法要点import{defineConfig}fromvite;//正确示例exportdefaultdefineConfig({base:/shop/,// 部署在子目录时});//错误示例// base 写成相对路径 ./ 在部分部署场景与 deep link 组合时易踩路径解析坑—— 查官方「Relative base」说明再选啥时候用GitHub Pages 子路径、企业门户子应用— 必配base。2publicDir定义默认public适合永不改名的静态 URL如favicon.ico。需要hash 缓存破坏的资源应import进源码让打包器处理。啥时候用第三方要求固定路径— 放public。六、路径别名resolve.alias本章把/components映射到src/components减少相对路径../../类型提示需在tsconfig的paths同步否则仅运行正确、IDE 报红。若项目为ESMtype: modulevite.config.ts里可能没有 Node 注入的__dirname需用import.meta.url推导当前配置文件目录。// vite.config.tsimportpathfromnode:path;import{fileURLToPath}fromnode:url;import{defineConfig}fromvite;const__dirnamefileURLToPath(newURL(.,import.meta.url));exportdefaultdefineConfig({resolve:{alias:{:path.resolve(__dirname,src),},},});// tsconfig.app.json 片段{compilerOptions:{baseUrl:.,paths:{/*:[src/*]}}}啥时候用组件超过百个— 别名 paths 几乎标配。七、环境变量谁看得见本章Vite只在构建时把以VITE_开头的环境变量注入客户端代码没有前缀的变量不会暴露给浏览器 bundle防泄漏。import.meta.env是 Vite 约定不是通用 Node 全局。// 语法要点import.meta.env.MODE / DEV / PROD / VITE_*//正确示例constapiBaseimport.meta.env.VITE_API_BASE??/api;//错误示例constsecretimport.meta.env.DB_PASSWORD;// 未加 VITE_ 前缀打包进浏览器里拿不到也不该拿定义.env.production、.env.development按mode加载永远不要把私钥写进任何会被打进前端的变量名前缀下。啥时候用多环境 API 根路径—VITE_API_BASE。八、开发代理server.proxy本章把/api转发到https://backend.test浏览器眼里仍是同源从而开发阶段绕过 CORS仅 dev server 生效生产环境需在网关或后端配置 CORS / BFF。// vite.config.tsexportdefaultdefineConfig({server:{proxy:{/api:{target:http://localhost:8080,changeOrigin:true,},},},});啥时候用本地前后端分离— 必备技巧上线勿依赖。九、Tree-shaking什么能被摇掉定义ESM 静态结构让打包器分析「从未被引用的导出」副作用模块顶层有注册、改原型等会标记sideEffects: false在package.json里帮助摇树乱标会导致运行时缺代码。// 正确示例——纯函数库易摇树exportconstunused()1;exportconstused()2;// 若别处只 import { used }unused 可被移除在无副作用前提下//错误示例// 某库入口文件顶层执行 window.X ... 仍被标 sideEffects:false —— 线上神秘 bug啥时候用发布 npm 包— 如实填写sideEffects字段。十、import()动态导入与代码分割定义import()返回Promisemodule打包器通常为每个动态导入创建async chunk实现路由级懒加载。//正确示例constpageawaitimport(./pages/Admin.vue);//错误示例constpath./pages/name.vue;awaitimport(path);// 多数打包器无法静态分析全部可能路径—— 需有限映射表或 glob 约定啥时候用重管理后台页— 不给首屏加载体积。十一、Source Map 与线上调试定义Source map把压缩后的列号映射回源码公开到生产会暴露源码结构——通常内联到错误监控平台私有上传或仅 CI 存档不随dist对用户公开。啥时候用SentrysourceMapUpload— 上传 map用户侧不下发。十二、常见踩坑清单现象常见原因白屏 chunk 404base与 CDN 子路径不一致环境变量 undefined未VITE_前缀或未重启 dev server类型找不到/只配了 vite alias 未配 tsconfig pathsHMR 失效循环依赖、或改了非 HMR 边界文件动态 import 全进主包路径非静态可分析十三、速查总表与结语1 速查表概念一句话Vite dev原生 ESM esbuild 预构建依赖Vite buildRollup 产出 distbase部署子路径公共前缀public原样拷贝、不进管道VITE_*唯一默认可进浏览器 bundle 的 envserver.proxy仅开发、治 CORS 体验tree-shakingESM 静态分析删未引用代码import()懒加载 分 chunk2 结语构建工具解决的是开发要快、上线要小、路径要对、秘密不漏。把Vite 的 dev/build 双模与import.meta.env规则记牢你就不会在「本地好好的、一上 CDN 全 404」里耗一整天。部署前 checklistbase、环境变量前缀、dist里有没有误带.map到公网。