ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

本地能跑真机就崩?前端部署三大环境坑深度拆解

本地能跑真机就崩?前端部署三大环境坑深度拆解 老实说这个项目看起来人畜无害一个index.html两三个 JS 文件本地浏览器一点就开。结果我为了在手机上真机预览前后踩进去三个坑每个坑都卡了不止一个晚上。这三个坑平时在开发机上完全隐身一旦连接真机用局域网地址访问部署产物全都冒出来了。我先把话放这儿大部分所谓真机才有的坑本质上不是手机怪而是你被localhost和桌面浏览器的开发环境伺候得太舒服对静态服务器、文件系统、网络协议的底层差异完全没概念。这篇就把我自己这三段翻车经历完整拆开每个坑都写到能复现的粒度给所有做前端打包、静态站点、博客部署的同学当个活教材。1. 真机调试前先搞明白本地能打开到底骗了你多少事1.1 双击 html 和开启 HTTP 服务是两种完全不同的加载模型很多人包括以前的我判断页面有没有问题的方式特别粗暴双击 index.html浏览器打开了肉眼看着没毛病就以为可以收工。这个习惯在只有静态展示时勉强能用但只要项目里碰了模块加载、字体、ES Module、Worker、剪贴板或者 Service Worker双击打开和 HTTP 服务就是两套完全不同的运行环境。双击打开走的是file://协议浏览器对本地文件的加载限制很宽松也很奇怪某些浏览器允许跨文件读脚本某些 API 直接被禁用。而真机连接时手机不可能访问你电脑上的file://路径它只能通过局域网 HTTP 访问。这一下就把你从本地文件环境拽进了真实服务器环境所有你平时没注意的规则全部生效。我在真机调试时用的方案其实很朴素电脑上和手机连同一个 Wi-Fi。项目目录下起一个静态服务器比如python3 -m http.server 8080或npx serve dist。手机浏览器直接访问http://电脑局域网IP:8080。听起来简单吧就是这套流程逼出了后面三个雷。每一个雷都不在代码逻辑里而在运行环境的缝隙处。1.2 三个坑的埋点全都在环境切换的接缝处我复盘下来这三个坑分布在三个阶段正好是全链路最容易被忽略的三道接缝坑位发生阶段表面现象根子在哪坑一构建阶段vite build 直接报错rollup 找不到 index.html入口配置与文件系统命名不一致坑二静态站生成阶段hexo 部署后 public 目录里没有 index.html首页生成链路静默失败坑三真机访问阶段手机能连上服务器但白屏、404 或脚本中断默认文档、路径基准、安全上下文差异记住这张表。你会发现这三个坑单个拎出来都不算难难的是它们在本地一切正常的假象下同时埋雷等你真机一跳连环炸。2. 坑一vite build 报错rollup 死磕入口 index.html2.1 事故现场构建脚本刚跑 3 秒就红屏先说第一个坑它发生在最不起眼的地方——打包。我的项目是一个纯前端工具页结构大概是project/ index.html src/ main.js vite.config.jsvite 配置也特别常规import { fileURLToPath, URL } from node:url export default { build: { rollupOptions: { input: fileURLToPath(new URL(./index.html, import.meta.url)) } } }本地npm run dev一切正常热更新嗖嗖的。但当我跑npm run build准备把产物丢到服务器上给手机访问时终端前 3 秒直接红屏error during build: RollupError: Could not resolve entry module index.html. at error (file:///.../node_modules/rollup/dist/es/shared/rollup.js:...) at ...index.html明明就在项目根目录躺着文件管理器里看得一清二楚为什么 rollup 就是解析不到2.2 定位过程从入口配置查到文件系统的大小写一开始我以为是 vite 版本问题或者 rollup 缓存坏了。我做了三件事症状都没变删掉node_modules和package-lock.json重装依赖。把vite.config.js里的input改成字符串./index.html。在项目根目录新建一个测试用的test.html把入口指向它——结果 build 竟然成功。问题很明确了不是配置语法问题是index.html这个具体文件在构建器眼里不存在。后来在终端里敲了一下这条命令ls -la | grep -i index然后我愣了两秒。文件系统里躺着的不是index.html而是Index.html是的I是大写。这个文件是从一个压缩包里解压出来的压缩包里的原始文件名就是Index.html解压工具在 Windows 上没给我任何提示Windows 文件系统默认大小写不敏感所以无论我在资源管理器里看还是用代码里写index.html去读系统都把它当同一个文件放行。但 rollup 在做模块解析时走的是严格模式它对文件名敏感得很。它拿着index.html去找文件磁盘上只有Index.html匹配不上直接报Could not resolve entry module。而 vite dev 服务器为什么没炸因为 dev server 在启动时对入口的查找走了另一套逻辑对大小写容错更宽松加上我是直接访问http://localhost:5173浏览器请求根路径服务器会自己找默认 html把这个隐患盖住了。这个现象在 Linux 服务器和真机调试场景下会被放得更大。你想想如果我在本地 Windows 上一直用Index.html开发辛辛苦苦写完代码推到 Linux CI 构建第一分钟就会失败——这不是真机坑而是发布前的隐形炸弹。2.3 修复与防护入口路径显式解析 构建前自检脚本修复本身特别简单把文件重命名成小写mv Index.html index.html但真正的教训是我不能再依赖文件管理器看起来没问题这个错觉。我在package.json里加了一个prebuild自检{ scripts: { prebuild: node scripts/check-index.js, build: vite build } }对应的scripts/check-index.jsconst fs require(fs) const expected index.html const files fs.readdirSync(process.cwd()) if (!files.includes(expected)) { console.error([prebuild] 缺少入口文件 ${expected}) console.error(实际目录内容, files.filter((f) /index/i.test(f))) process.exit(1) }这个脚本故意用readdirSync拿到精确文件名做includes判断而不是用existsSync因为existsSync在 Windows 上依然不区分大小写起不到拦截作用。从这次以后我养成了一个习惯所有涉及入口文件的路径一律统一小写命名index.html、main.js、app.css养成肌肉记忆。宁可让 CI 第一次就失败也不要让它在我手机访问时爆一个莫名其妙的 404。3. 坑二hexo 部署完public 目录里根本没有 index.html3.1 线上打开是目录列表那一刻我想把电脑扔了第二个坑来自我的博客站。我用 hexo 搭了个静态博客主题、插件都配好了本地hexo server预览也正常。按我往常的流程hexo clean hexo generate hexo deploy一气呵成。然后我掏出手机连真机访问部署后的站点浏览器里没有出现首页而是出现了一个文件列表就像一个 FTP 目录被直接扔到了浏览器上。我赶紧回到电脑上看本地生成目录ls public/果然public目录下一堆about/、archives/、categories/、tags/唯独少了index.html。这个现象比直接 404 更阴间服务器活着文件都在但整个网站的入口丢了。你访问根路径时服务器找不到默认文档干脆开启了目录索引给你看。3.2 排查链路别急着 clean先过这三道闸很多人遇到这种情况第一反应是怀疑构建器坏了马上hexo clean hexo generate再来一遍。我告诉你先别急clean会把现场破坏掉很多信息直接没了。我按下面的顺序排查每一道闸都对应一个具体的失败原因第一道闸source目录下有没有index.md。ls source/hexo 生成首页时索引生成器会把source/index.md渲染成public/index.html。如果这个文件不存在很多主题不会自动给你造一个首页入口。我那次的情况是source/下只有_posts/、about/、categories/压根没有index.md。第二道闸hexo-generator-index是不是被关了。grep -A 3 index_generator _config.ymlindex_generator是负责生成首页索引的插件。如果配置里写了enable: false或者插件被误删首页自然就没有。我检查了一下我的配置本身是开着的所以不是这个原因。第三道闸主题的 index 布局模板是否存在。如果是自用主题检查layout/index.ejs或者主题用的模板引擎对应文件。如果主题只有post.ejs、archive.ejs而没有index.ejshexo 在渲染首页时找不到布局模板会静默跳过不报错也不生成文件。这才是最坑的地方——构建过程全是绿字没有一个错误但输出就是缺了入口。为了看到真实错误一定要开 debug 模式npx hexo clean npx hexo generate --debug 21 | tee build.log然后看build.log里的INFO和WARN特别是渲染index布局那一段。静默失败在 hexo 里很常见不开 debug 你永远不知道它到底跳过了什么。3.3 修复让 index.html 的生成变成可验证的硬约束修复取决于根因。我的问题是source/index.md缺失那我就在source/下创建一个--- layout: index ---一个几乎空白的 markdown作用只是告诉 hexo 这里需要一个首页文档。生成器拿到它配合主题的index布局就会输出public/index.html。但我觉得更值得分享的是后面的习惯我再也不把public 里有 index.html当成 hexo 的自觉了。我在部署脚本里加了一道硬校验if [ ! -f public/index.html ]; then echo 部署中止public/index.html 不存在 exit 1 fi这条校验放在hexo generate之后、hexo deploy之前。只要入口文件没生成后续的部署动作一律不执行。别嫌这行脚本啰嗦它至少能帮我把线上首页是目录列表这种事故从源头掐死。4. 坑三手机能打开页面但白屏、404、脚本中断轮着来4.1 默认文档的幽灵文件叫 Index.html服务器就是不给脸第三个坑回到了那个工具页。构建问题解决之后我把dist目录放到了服务器上自己也用电脑浏览器打开了正常得不能再正常。然后我手机连上 Wi-Fi访问http://192.168.x.x:8080/。浏览器给我展示了一个文件和文件夹的列表assets/ Index.html是的又是大小写。Index.html在桌面浏览器上可以被直接访问是因为我访问的是完整路径http://192.168.x.x:8080/Index.html或者开发服务器自己做了容错。但我的手机访问的是裸域名根路径/服务器需要自己找默认文档它去找index.html结果磁盘上叫Index.html匹配不上。服务器没辙只能给你列出目录内容。桌面浏览器为什么没触发因为我在电脑上开发时用的是 vite dev server 或者双击文件根本走不到服务器找默认文档这步。手机一上立刻现出原形。修复mv Index.html index.html如果你控制不了服务器上的文件名也可以在静态服务器里配置index指令把Index.html一并列入默认文档候选。但说实话最省心的做法永远是所有静态入口统一小写index.html没有例外。4.2 资源路径基准vite base 的默认值会悄悄坑掉部署目录首页终于能打开了我以为结束了结果第二种白屏又来了。手机屏幕上能看到页面框架和文字但 CSS 全丢了JS 也不会执行。打开开发者工具一看/assets/app.js和/assets/app.css全部 404。问题出在 vite 的base配置。vite 默认base是/构建出来的index.html里资源引用长这样script typemodule src/assets/app.js/script link relstylesheet href/assets/app.css注意那个/assets/...这是绝对路径浏览器会把它解析到服务器域名根部。如果你把dist直接放在服务器根目录没问题。但只要你的应用被部署到了子路径比如http://192.168.x.x:8080/demo/或者像 GitHub Pages 项目页那样部署在/repo/下那/assets/app.js就会被请求到http://192.168.x.x:8080/assets/app.js而不是http://192.168.x.x:8080/demo/assets/app.js。这就是典型的本地一切正常真机访问就白屏——因为你在本机上习惯了直接访问localhost:5173根路径根本不使用http://192.168.x.x:8080/demo/这种带二级目录的地址。手机一上你被迫用完整 URL路径基准问题就藏不住了。修复方式是在vite.config.js里把base改成相对路径export default { base: ./ }这样构建出来的index.html里资源引用会变成script typemodule src./assets/app.js/script用相对路径不管你的页面被放在/demo/还是/任何/奇怪的/路径/下面只要index.html和assets/是相对关系资源就永远找得到。这里提醒一句base: ./会让某些依赖 HTML 5 history 路由的页面在二级路径下刷新时出现路由失效但纯静态页面、博客、工具页完全不受影响。如果你用的是createWebHistory这种依赖绝对路径的路由模式请改用 hash 路由或者确保你的服务器做了 history fallback。4.3 隐藏杀手非安全上下文里一个 API 报错毁掉整页脚本如果你以为白屏问题到此为止那就太天真了。第三人白屏是因为某个 API 在真机环境上直接抛异常导致整段脚本中断。我那个工具页里注册了一个 Service Worker代码如下navigator.serviceWorker.register(/sw.js).then(() { console.log(sw registered) })在本地localhost上跑得好好的因为localhost被视为安全上下文navigator.serviceWorker可用。但真机上我访问的是http://192.168.x.x:8080这是一个非安全上下文。在非安全上下文里navigator.serviceWorker是undefined于是undefined.register直接抛TypeError整段 JS 中断页面初始化逻辑全部作废白屏。这个报错在电脑上用localhost永远不会出现也正因为如此它是最有资格被称为真机才有的坑的一个。不只是 Service Workernavigator.clipboard、Notification、Geolocation、navigator.bluetooth这些 API 都对安全上下文有硬性要求。只要你的脚本在顶层调用它们且没有做容错局域网 IP 访问时就是一行红色报错然后全站雪崩。修复方案分两层第一层所有这类 API 调用前必须做特性检测if (serviceWorker in navigator) { navigator.serviceWorker.register(/sw.js) .then(() console.log(sw registered)) .catch((err) console.warn(sw register failed, err)) }第二层整个初始化逻辑外层包一层 try/catch宁可某个功能不可用也不能让一个 API 报错毁掉整页脚本try { initApp() } catch (err) { console.error(app init error, err) // 降级到最基础的页面渲染 }另外如果你确实需要在真机上完整调试这类 API最稳妥的办法是让本地静态服务器支持 HTTPS。mkcert加 Nginx 或者直接用能签本地证书的 dev server 都行手机安装信任证书后就能在局域网 HTTPS 环境下继续调试。当然正式部署时服务器必须挂合法证书这个是底线。4.4 真机诊断工具箱vConsole、远程调试和自建探针页连续两次白屏之后我学乖了。真机调试不能像以前那样打开页面看一眼必须引入可视化和可追踪的手段。我现在固定使用的三件套第一件是 vConsole 。在index.html里临时引入 vConsole 的 CDN 脚本手机浏览器右下角会出现一个绿色按钮点开能看到 console 日志、网络请求、LocalStorage 和 Cookie。真机上所有报错一目了然。调试完再把它从代码里摘掉。第二件是 Chrome 的远程调试。安卓手机用 USB 连接电脑在 Chrome 地址栏输入chrome://inspect可以把手机的页面挂到电脑 DevTools 里。这个对排查 service worker、安全上下文问题和网络请求都很有用。iPhone 用户就用 Safari 的开发菜单操作路径不同但逻辑一致。第三件是我自己写的一个探针页。我知道听起来有点笨但效果极好。把它放在页面的隐藏元素里或者直接在控制台输出一份 JSONconsole.log(JSON.stringify({ href: location.href, origin: location.origin, base: document.baseURI, isSecureContext: window.isSecureContext, serviceWorker: serviceWorker in navigator, clipboard: clipboard in navigator, userAgent: navigator.userAgent }, null, 2))这一看就知道路径基准、安全上下文、文件是否存在。很多时候不用猜跑一下就全明白了。5. 把教训沉淀成清单我现在的上线前自检流程5.1 本地四连把开发环境骗局先拆穿吃过这三个坑之后我给自己定了一套固定流程每次上线前先过一遍不放过任何一个本地能跑的错觉。第一步本地构建必须完整通过。npm run build或hexo generate任何 warning 和 error 都不能忽略。特别是RollupError之类的构建期报错它往往是你本地开发环境隐藏问题的第一个信号。第二步构建产物必须人肉检查。打开dist/或public/确认index.html存在并且注意看它的名字是否严格小写。用ls -la | grep -i index这种命令看别相信文件管理器的显示。第三步产物必须通过静态服务器访问一次。不要双击index.html要用python3 -m http.server 8080或者npx serve起一个服务然后访问http://localhost:8080模拟真实服务器环境。这一步能过滤掉大量file://协议下的假象。第四步产物里的资源路径必须抽查。打开index.html源码看src和href是不是./assets/...这种相对路径。如果是/assets/...开头想清楚你的部署路径是不是真的在服务器根目录。不确定就改成相对路径一劳永逸。5.2 构建产物检查人肉扫一眼 public/dist 的入口文件这个习惯是从 hexo 那个坑里长出来的。现在我每次hexo generate之后都会看一眼 public 目录确认index.html真的在。别嫌这一步重复它就是一道保险。如果你和我一样写过很多静态站建议直接把这道检查写进部署脚本。用这类代码if [ ! -f public/index.html ]; then echo 部署中止public/index.html 不存在 exit 1 fi脚本比人可靠人会在疲惫或者赶进度的时候跳过检查脚本不会。5.3 真机三连用手机完成最后一公里验证所有本地检查都通过之后才进入真正的真机验证。我的流程很机械但很有效第一步手机和电脑连接同一个 Wi-Fi。服务器起在0.0.0.0而不是localhost否则手机连不上。第二步手机浏览器访问http://电脑局域网IP:端口。注意不要访问完整文件名直接访问根路径/看看服务器能不能顺利返回默认文档。这一步能验证默认文档和大小写问题。第三步打开 vConsole 或者连接远程调试看 console 和 Network。没有红色报错、没有 404、所有资源都加载成功才算真正过关。有条件的话再跑一遍探针页脚本确认isSecureContext等关键字段。如果你要用安全敏感 API这一步尤其重要。5.4 说句实在话大部分灵异都是环境一致性不够现在的我已经不会因为一个页面在手机上打不开而怀疑玄学了。所谓真机才有的坑排到最后你会发现它们的共性是同一个本地开发环境和真实访问环境之间存在你看不到的差异。Index.html和index.html的差异/assets/和./assets/的差异localhost和192.168.x.x的差异每一个看着都小得离谱但每一个都足以让整个页面在真机上彻底不可用。我现在处理这类问题有一个原则能用相对路径就不用绝对路径能用小写就不用大写能用标准静态服务器验证就不用双击文件验证真机调试永远是最后一关但绝不是第一关。以后你要是也遇到本地好好的、手机一打开就废的页面先别急着怀疑手机按我这份清单过一遍大概率第一个循环就能把凶手揪出来。
返回列表