- 前端
【免费下载链接】prerender-spa-plugin
Prerenders static HTML in a single-page application.
本文以本仓库 examples/angular-cli-eject 示例为蓝本,讲解如何在 Angular CLI 项目中通过ng eject拿到自定义 webpack 配置后接入 prerender-spa-plugin,把 SPA 关键路由预渲染成纯静态 HTML。读完本文,你将掌握从构建、验证到深度定制(多路由、minify、renderer 参数)的完整流程,并理解插件在 webpackafterEmit阶段的底层工作原理。
示例项目概览:Ejected Angular CLI App 是什么
该示例项目由 Angular CLI 1.7.0 生成,并已执行ng eject将内部构建配置导出为独立的 webpack.config.js。这意味着项目的打包逻辑不再由 Angular CLI 隐式接管,而是完全交给 webpack——这正是接入 prerender-spa-plugin 的前提:插件本身就是 一个标准的 webpack plugin。
从 package.json 可以看到项目技术栈:
- 依赖
@angular/*(5.2.x)、rxjs、zone.js、core-js,入口在 src/main.ts(platformBrowserDynamic().bootstrapModule(AppModule)引导 app.module.ts); - 构建工具链为 webpack 3.11、
@ngtools/webpack、html-webpack-plugin 等; - 关键 devDependency:
prerender-spa-plugin: ^3.0.0; - 脚本:
build执行webpack,start执行webpack-dev-server --port=4200。
构建预渲染产物:两条命令拿到静态 HTML
原文档给出的构建流程非常简单,但每一步都对应真实的产物:
npm install npm run buildnpm run build实际执行的是webpack(见 package.json 的 scripts 段)。构建完成后,webpack 会把 Angular 应用的 JS bundle 输出到dist/目录(输出路径由 webpack.config.js 中的output.path = path.join(process.cwd(), "dist")决定)。
随后 prerender-spa-plugin 在afterEmit阶段启动 headless Chrome(Puppeteer),加载dist/中的index.html,渲染指定路由并把结果写回dist/目录。所以构建结束后,dist/目录里不仅有你熟悉的main.bundle.js、vendor.bundle.js,还会多出每个预渲染路由对应的静态 HTML 文件,这就是原文档中 "check the newbuilddirectory for your prerendered static files!" 的含义。
验证预渲染结果:无 JavaScript 也能看到页面内容
预渲染的核心价值是让爬虫和首屏用户不需要等待 JS 执行就能拿到页面 DOM。验证方法如下:
npm install -g http-server然后在dist/目录下启动静态服务:
http-server dist接着在浏览器访问(注意结尾的斜杠):
- http://localhost:8000/
一定要保留结尾的/:预渲染输出遵循outputDir/route/index.html的文件结构(例如路由/会生成dist/index.html)。若省略斜杠,静态服务器可能返回 404 或做 301 跳转,无法正确命中预渲染文件。
判断是否成功:用浏览器的"禁用 JavaScript"功能(或在 DevTools 中屏蔽 JS 后刷新)重新访问该地址,如果页面仍然显示 Angular 组件渲染出的内容(如Welcome to app!,来自 app.component.html 的插值模板),就说明预渲染 HTML 已包含完整 DOM,即 "If all went well, it should load without JavaScript"。
顺带说明:本示例配置的是routes: ['/'],只预渲染根路由;如果把路由列表扩展为多页,dist/下会对应生成dist/about/index.html、dist/contact/index.html等多级目录,配合http-server的 history fallback 即可直接以静态文件形式访问。
配置核心:webpack.config.js 中的 PrerenderSPAPlugin
原文档明确指出的开发要点是:修改预渲染配置时,定位到 webpack.config.js 的 plugins 段,找到new PrerenderSPAPlugin。该示例的真实配置位于 webpack.config.js:
const PrerenderSPAPlugin = require('prerender-spa-plugin') const Renderer = PrerenderSPAPlugin.PuppeteerRenderer // ...plugins 数组中: new PrerenderSPAPlugin({ // Index.html 位于 dist 根目录。 staticDir: path.join(__dirname, 'dist'), routes: [ '/' ], // 可选:对预渲染结果做 HTML 压缩。 minify: { collapseBooleanAttributes: true, collapseWhitespace: true, decodeEntities: true, keepClosingSlash: true, sortAttributes: true }, renderer: new Renderer({ renderAfterTime: 500 }) })配置项说明:
| 配置项 | 作用 |
|---|---|
staticDir | 必填。webpack 输出目录,即应用静态根目录,本示例为dist。 |
routes | 必填。需要预渲染的路由数组。示例只有['/'],多页应用在此追加,如['/', '/about', '/contact']。 |
minify | 可选。基于 html-minifier 对产物做压缩,常用项包括collapseWhitespace(合并空白)、decodeEntities、sortAttributes等。 |
renderer | 可选。渲染器实例,默认new PuppeteerRenderer({ headless: true })。这里通过renderAfterTime: 500让页面加载后额外等待 500ms 再截取 HTML,确保 Angular 完成首次渲染。 |
关于renderAfterTime需要留意:它属于 PuppeteerRenderer 的渲染等待机制。插件还支持renderAfterDocumentEvent(等待自定义事件)、renderAfterElementExists(等待某元素出现)两种更可控的方式,而且这些renderAfterX选项同一时间只能启用一个。本例选用定长时间等待,虽然简单,但对网络延迟敏感的场景可能产生不确定结果,生产环境建议优先使用事件或元素等待。
多路由如何扩展
原文档特别提到:"If you're using a router or have more than one page to prerender, edit theroutesarray"。也就是说,当项目引入@angular/router后,只需把路由表里需要被搜索引擎收录或希望秒开的路由加入数组即可。例如 Angular 路由/home、/about:
routes: [ '/', '/home', '/about' ],需要注意前置条件:预渲染只适用于基于HTML5 History API的路由(如https://example.com/about)。index.html#/hash/route这类 hash 路由无法被预渲染,因为对静态文件服务器而言 hash 不产生真实路径。本示例的 webpack.config.js 在 devServer 中开启了historyApiFallback: true,正是为 History 路由设计的。
源码视角:插件在 webpack 构建链中如何工作
要理解"为什么构建完 dist 里就出现了预渲染 HTML",可以阅读插件主实现 es6/index.js(Node 8+ 环境通过 index.js 的版本判断加载此文件,低版本 Node 则回退到 es5-autogenerated/index.js)。
核心流程如下:
- 挂载 hook:
PrerenderSPAPlugin.prototype.apply在 es6/index.js 中通过compiler.hooks.afterEmit.tapAsync(webpack 4+)或compiler.plugin('after-emit', ...)(webpack 3)挂载回调——这正是插件在构建产物写盘之后再补写预渲染 HTML 的关键点。 - 初始化渲染器:
afterEmit内创建new Prerenderer(this._options)并initialize(),随后调用renderRoutes(this._options.routes || [])(见 es6/index.js),逐路由在 headless Chrome 中加载应用。 - 执行 postProcess / minify:渲染结果依次经过
postProcessHtml(v2 兼容)、postProcess、minify三个可选的加工阶段(见 es6/index.js)。其中minify就是通过html-minifier的minify(route.html, this._options.minify)实现的。 - 计算输出路径并写盘:若
postProcess未显式设置outputPath,插件按path.join(outputDir || staticDir, route, 'index.html')计算(见 es6/index.js),再递归 mkdir 并写入文件(es6/index.js)。因此routes: ['/']最终生成的就是dist/index.html。
插件还内置了 v2 兼容层(es6/index.js):若检测到以(staticDir, routes, config)位置参数形式调用,会输出迁移提示并自动归一化;captureAfterX等旧命名会被映射为renderAfterX并给出警告。
开发模式:npm start 与预渲染的关系
原文档的最后一部分是开发流程:
npm install npm startnpm start实际运行webpack-dev-server --port=4200,在 http://localhost:4200 提供带热更新的开发服务。需要说明的是:预渲染发生在webpack build的 afterEmit 阶段,开发服务器模式下插件同样会执行预渲染逻辑,因此本地联调时也能直观检验插件行为;而日常改代码则依赖 webpack-dev-server 的增量编译与historyApiFallback,两者职责不同,互补使用。
常见坑位与注意事项
结合本示例与仓库主文档 README.md 的 Tips 部分,以下问题在 Angular 场景最容易踩到:
- 依赖
<body>的初始化代码:Angular 应用经platformBrowserDynamic引导,天然在 DOM 就绪后执行,一般无此问题;但如果业务里有在脚本顶层直接操作<body>的代码,应包进DOMContentLoaded回调,否则预渲染截取时可能拿到空 DOM。 - 样式重复注入:若未抽取 CSS 而依赖 bundle 内联样式,预渲染页面可能出现重复的
<style>标签,建议用 extract-text-webpack-plugin 抽取后由 html-webpack-plugin 注入模板。 - 动态内容:预渲染快照是构建时生成的一次性内容。若路由包含用户个性化数据或实时数据,应在客户端加载后替换占位组件,避免快照与真实数据不一致。
- 路由数量:每增加一个
routes条目,构建时就要多开一次 headless Chrome 渲染(受maxConcurrentRoutes控制并行度)。数百个静态路由是合理范围;动辄上千路由时预渲染耗时显著,需要评估是否改用@prerenderer/renderer-jsdom之类的轻量渲染器。
小结
通过 examples/angular-cli-eject 这个示例,可以完整走通"Angular CLI eject → webpack 接管 → prerender-spa-plugin 预渲染 → 静态产物验证"的链路。核心操作只有三步:npm install+npm run build产出带静态 HTML 的dist/,用http-server关闭 JS 验证渲染结果,需要多页面时扩展routes数组并调整 webpack.config.js 中的插件参数。理解 es6/index.js 中 afterEmit、renderRoutes、postProcess、minify 的执行顺序后,你就能按需定制自己的预渲染管线。
- 前端
【免费下载链接】prerender-spa-plugin
Prerenders static HTML in a single-page application.
相关推荐
游戏设备防火墙:HidHide让你的控制器不再互相干扰
游戏设备防火墙:HidHide让你的控制器不再互相干扰 你是否曾经遇到过这样的场景:当多个游戏手柄、方向盘或飞行摇杆同时连接到电脑时,系统突然变得混乱不堪?游戏
驱动开发游戏开发Prerender SPA Plugin终极配置指南:从静态目录到路由渲染完整教程
Prerender SPA Plugin终极配置指南:从静态目录到路由渲染完整教程 想要为你的单页应用提升SEO性能吗?prerender spa plugin
前端为什么每个Neovim用户都需要duck.nvim?10个让你爱上它的理由
为什么每个Neovim用户都需要duck.nvim?10个让你爱上它的理由 duck.nvim是一款为Neovim设计的趣味插件,它能让一只可爱的鸭子在你的代码
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考