Egg 静态资源管理实战:egg-view-assets 插件、构建工具映射约定与 CDN 部署
【免费下载链接】egg🥚🥚🥚🥚 Born to build better enterprise frameworks and apps with Node.js & Koa. https://307.run/eggcode项目地址: https://gitcode.com/gh_mirrors/eg/egg
导读
本指南完整讲解 Egg 框架下前端静态资源的管理与集成方案:核心是egg-view-assets插件提供的通用静态资源管理能力,涵盖本地开发一体化方案、生产环境资源映射、与模板引擎的集成,以及 webpack / roadhog / umi 等构建工具的接入约定。读完本文,你将掌握「以静态资源文件作为模板入口」的渲染方式、helper.assets的两种用法、前后端上下文数据传递,以及从本地开发到应用自托管、再到 CDN 部署的完整落地路径。
功能总览
egg-view-assets提供的是「通用静态资源管理和本地开发方案」,它并非简单的静态文件托管,而是把构建工具产物与服务端模板渲染打通。官方定位的四大能力如下:
- 一体化的本地开发方案(开发时自动拉起构建工具 dev server 并等待其就绪);
- 生产环境下的静态资源映射(依据 manifest 把
index.js这类逻辑名映射到带 hash 的真实文件); - 与模板引擎集成(既能独立作为模板引擎,也能嵌入 nunjucks 等其它引擎);
- 根据约定可以使用多种构建工具,例如 webpack、roadhog、umi 等(构建工具只需要满足一套统一的映射约定即可接入)。
这套方案的典型使用场景是「前后端同仓库、同部署」:前端源码放在app/assets等目录,构建产物输出到app/public由框架内置的静态服务直接托管,生产环境再通过publicPath与 CDN 地址调整访问路径。
页面渲染:以静态资源文件为模板入口
渲染静态资源有两种方式:使用 assets 模板引擎,或结合其它模板引擎。两者的核心区别在于——assets 模板引擎把静态资源文件(如index.js)当作渲染入口,而其它模板引擎则仍以 HTML 文件为入口,通过helper.assets手动注入资源标签。
使用 assets 模板引擎
assets 模板引擎并非服务端渲染,而是「以一个静态资源文件作为入口,使用基础模板渲染出 HTML,并将这个文件(及其依赖的 CSS)插入到 HTML 中」的方法。
第一步:配置插件(config/plugin.js):
exports.assets = { enable: true, package: 'egg-view-assets', };第二步:配置 assets 模板引擎(config/config.default.js),把.js扩展名映射到assets引擎:
exports.view = { mapping: { '.js': 'assets', }, };这一步依赖框架内置的 view 插件机制——在 packages/egg/src/config/plugin.ts 中,框架通过viewPlugin()默认启用了 view 插件,view.mapping正是向该插件注册扩展名与引擎的映射关系。assets 引擎同样经由这一加载器工作,因此修改view.root会影响其资源目录(下文详述)。
第三步:添加静态资源入口文件并渲染。入口文件放在app/view/index.js,Controller 中调用this.ctx.render('index.js'):
// app/controller/home.js module.exports = class HomeController extends Controller { async render() { await this.ctx.render('index.js'); } };渲染结果如下(本地开发模式):
<!doctype html> <html> <head> <link rel="stylesheet" href="http://127.0.0.1:8000/index.css"></link> </head> <body> <div id="root"></div> <script src="http://127.0.0.1:8000/index.js"></script> </body> </html>注意:路径生成遵循一层映射规则,如index.js->http://127.0.0.1:8000/index.js。如果本地开发工具不支持这层映射(例如自定义了 entry 配置),则应改用其它模板引擎的方案。
全局自定义 HTML 模板
默认生成的 HTML 往往无法满足需求,可以通过assets.templatePath指定模板路径、assets.templateViewEngine指定渲染该模板所用的引擎:
// config/config.default.js module.exports = (appInfo) => ({ assets: { templatePath: path.join(appInfo.baseDir, 'app/view/template.html'), templateViewEngine: 'nunjucks', }, });对应的模板文件(app/view/template.html):
<!DOCTYPE html> <html> <head> {{ helper.assets.getStyle() | safe }} </head> <body> <div id="root"></div> {{ helper.assets.getScript() | safe }} </body> </html>egg-view-assets插件向模板上下文注入了helper.assets,可按需调用:helper.assets.getScript()可以不传参数,此时会将render函数传入的渲染数据透传给资源标签生成逻辑。
页面自定义 HTML 模板
支持为不同页面指定不同模板:在render的第三个参数中传入templatePath与templateViewEngine即可:
// app/controller/home.js module.exports = class HomeController extends Controller { async render() { await this.ctx.render( 'index.js', {}, { templatePath: path.join(this.app.config.baseDir, 'app/view/template.html'), templateViewEngine: 'nunjucks', }, ); } };这样即可实现「全局一个默认模板 + 个别页面使用专属模板」的灵活布局。
修改静态资源目录
默认例子把静态资源放在app/view目录下,但多数项目希望放到独立目录(如app/assets)。由于 assets 模板引擎复用egg-view的加载器,直接修改view.root即可:
// config/config.default.js module.exports = (appInfo) => ({ view: { // 如果还有其他模板引擎,需要合并多个目录 root: path.join(appInfo.baseDir, 'app/assets'), }, });注意注释里的要点:若同时存在 nunjucks 等其它引擎,root需传入数组合并多个资源目录,否则其它引擎将无法找到模板。
使用其他模板引擎
如果默认 assets 模板引擎无法满足需求,可以结合其它模板引擎使用(典型代表是基于 umi 的方案,umi 只有唯一入口umi.js,天然不满足逐文件映射)。此时无需配置 assets 模板引擎,而是把 HTML 模板交给 nunjucks 等引擎:
// config/config.default.js exports.view = { mapping: { '.html': 'nunjucks', }, };渲染模板:
// app/controller/home.js module.exports = class HomeController extends Controller { async render() { await this.ctx.render('index.html'); } };模板文件(此处为简化版 umi 模板):
<!DOCTYPE html> <html> <head> {{ helper.assets.getStyle('umi.css') | safe }} </head> <body> <div id="root"></div> {{ helper.assets.getScript('umi.js') | safe }} </body> </html>关键区别:在其它模板中,必须为getStyle/getScript显式传入资源名参数(如'umi.js'),因为此时不存在「渲染入口即资源」的隐式映射,需要手动指定生成哪些静态资源标签。
上下文数据:向前端注入服务端数据
前端经常需要获取服务端数据,通用的做法是在渲染页面时把数据挂到window全局对象上。本方案为两种引擎分别提供了传递通道。
使用 assets 模板引擎时,默认前端代码可以直接从window.context读取数据:
// app/controller/home.js module.exports = class HomeController extends Controller { async render() { await this.ctx.render('index.js', { data: 1 }); } };使用其它模板引擎时,需要调用helper.assets.getContext(__context__),并把上下文放在渲染数据的__context__字段中:
// app/controller/home.js module.exports = class HomeController extends Controller { async render() { await this.ctx.render('index.html', { __context__: { data: 1 }, }); } };默认挂载属性名为context,可以通过配置修改(例如与模板字段冲突时):
exports.assets = { contextKey: '__context__', };与构建工具的约定:映射关系是接入前提
这套模式能否成立,关键在于构建工具与框架之间的一层约定——它同时保证本地开发体验与自动部署。下面以 roadhog 为例展开。
映射关系
构建工具的 entry 配置决定了映射关系。基于 webpack 封装的 roadhog、umi 等工具内置了这层映射;如果单独使用 webpack,则需要根据映射来选择接入方式。映射关系包含三层:
文件源码
app/assets/index.js,对应的 entry 为index.js;本地静态服务接收以此为 entry 的请求,如请求
http://127.0.0.1:8000/index.js;构建生成的文件需要保持这层映射,如生成
index.{hash}.js,并生成 manifest 文件描述逻辑名与真实文件的对应关系:{ "index.js": "index.{hash}.js" }
roadhog 完全满足该映射关系,因此可直接使用 assets 模板引擎;而 umi 只有一个入口文件umi.js、不满足逐文件映射,因此应选择「使用其它模板引擎」的方案。其它构建工具接入时同样必须满足这层映射关系,这是整个方案的硬性前提。
本地开发
本地开发时,框架负责拉起构建工具的 dev server 并检查其是否就绪。roadhog 默认启动端口为 8000,因此示例中port也配置为 8000:
exports.assets = { devServer: { command: 'roadhog dev', port: 8000, }, };command指定 dev server 启动命令,port用于轮询探测服务是否启动完成——只有端口可访问后,页面请求才会被转发到该服务,从而保证「首次访问即拿到最新构建产物」。
部署:构建产物与 manifest
静态资源部署之前需要先构建,配置roadhog build命令,并执行npm run build:
{ "scripts": { "build": "SET_PUBLIC_PATH=true roadhog build" } }注意:此处添加了SET_PUBLIC_PATH环境变量,因为 roadhog 只有在设置该变量后才会开启 publicPath 相关能力。
构建结果的位置由.webpackrc的 output 配置决定,示例中产物输出到app/public目录,由@eggjs/static插件提供服务;同时根据.webpackrc的 manifest 配置生成manifest.json文件到config目录下——Egg 启动时读取该文件作为逻辑名到 hash 文件的映射关系。
应用提供服务
构建完成且 manifest 就绪后,应用启动即可通过http://127.0.0.1:7001/public/index.{hash}.js访问静态资源。由于这里多了一层public路径,生产环境需要补充 publicPath 配置:
// config/config.prod.js exports.assets = { publicPath: '/public/', };从框架源码可以印证这层访问链路:框架内置的@eggjs/static插件(在 packages/egg/src/config/plugin.ts 通过staticPlugin()默认启用)默认配置为prefix: '/public/'、dir: app/public(见 plugins/static/src/config/config.default.ts)。其中间件 static.ts 会在启动时确保目录存在,并基于@eggjs/koa-static-cache为每个 prefix 挂载静态缓存服务,还通过koa-range支持 Range 请求(对音视频等大文件场景友好)。因此app/public目录下的构建产物天然以/public/前缀对外提供。
使用 CDN
生产环境通常会把静态资源部署到 CDN。构建完成后,平台需要将构建产物发布到 CDN,例如https://cdn/myapp/index.{hash}.js。此时除了publicPath还需要修改静态资源的基础地址:
// config/config.prod.js exports.assets = { url: 'https://cdn', publicPath: '/myapp/', };这里的含义是:资源基础地址为https://cdn,路径前缀为/myapp/,最终生成的资源 URL 即https://cdn/myapp/index.{hash}.js。在本地开发环境(assets 模板引擎默认生成http://127.0.0.1:8000/...)与 CDN 生产环境之间,仅需切换devServer/url+publicPath两套配置即可无缝过渡。
仓库中的相关实现与参考
- 英文原版文档:site/docs/tutorials/assets.md(当前为翻译占位页,内容以中文版 site/docs/zh-CN/tutorials/assets.md 为准);
- 静态资源插件配置与中间件:plugins/static/src/config/config.default.ts、plugins/static/src/app/middleware/static.ts;
- 框架内置 view / static 插件的启用逻辑:packages/egg/src/config/plugin.ts。
总结
Egg 的静态资源管理方案本质上是一套「构建工具 <-> 框架」的契约式集成:通过 entry 映射、manifest 映射与 dev server 端口探测三层约定,把前端构建流程无缝嵌入 Egg 的应用生命周期。接入时只需回答三个问题:构建工具是否满足逐文件映射(决定用 assets 引擎还是其它引擎)、上下文数据走window.context还是helper.assets.getContext()、生产环境走应用自托管还是 CDN。明确这三点后,按本文的配置模板即可完成从本地开发到生产部署的完整闭环。
【免费下载链接】egg🥚🥚🥚🥚 Born to build better enterprise frameworks and apps with Node.js & Koa. https://307.run/eggcode项目地址: https://gitcode.com/gh_mirrors/eg/egg
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考