资讯动态

Egg.js 静态资源服务插件 @eggjs/static 实战指南:默认配置、多目录映射与缓存策略

发布时间:2026/9/21 15:32:48 来源:尧图企业网站定制
后端Web框架【免费下载链接】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.js 内置的静态资源服务插件eggjs/static展开系统讲解其在 packages/koa-static-cache 之上的封装方式、六项默认配置与生产/非生产环境的差异行为、多目录dir数组与多前缀映射的完整写法并结合 插件中间件源码 与 测试用例 剖析其懒加载、LRU 缓存、Range 断点续传与中间件排序原理。读完本文你将掌握app/public目录的静态托管机制并能自主定制静态目录、缓存时长、内存缓冲与多前缀路由同时理解“生产环境更新静态文件必须重启进程”这一关键行为背后的原因。一、插件定位基于 koa-static-cache 的内置静态服务器eggjs/static是 Egg.js 的静态服务器插件其底层依赖 eggjs/koa-static-cache。从 插件包清单 可以看到它的定位与依赖关系{ name: eggjs/static, description: static server plugin for egg, keywords: [egg, egg-plugin, eggPlugin, static], dependencies: { eggjs/koa-static-cache: workspace:*, koa-compose: catalog:, koa-range: catalog:, ylru: catalog: } }四个直接依赖各有分工eggjs/koa-static-cache核心静态缓存中间件负责文件查找、缓存、ETag 与 MIME 处理koa-compose把“Range 中间件 若干目录的 staticCache 中间件”串联成一个整体koa-range为静态文件提供 HTTP Range 断点续传支持ylru提供 LRU 缓存数据结构用于dynamic: true时的动态文件缓存。与社区常见的 koa-static 相比eggjs/koa-static-cache在设计上刻意去掉了目录浏览与index.html支持见 koa-static-cache README更强调缓存能力与构建产物目录如dist的托管这也正是 Egg.js 把它作为默认静态方案的原因。二、安装与启用默认内置、开箱即用eggjs/static是 Egg.js 的内置插件默认即启用无需额外安装依赖也不需要手动开启。如果你使用的是通过 create-egg 脚手架创建的标准项目直接把静态文件放进app/public/目录即可通过/public前缀访问。对于需要显式声明插件或二次封装插件的场景可以参照 插件工厂定义 中的用法在config/plugin.ts中声明// config/plugin.ts import staticPlugin from eggjs/static; export default { ...staticPlugin(), };从 源码 可以看到definePluginFactory返回的插件描述为{ name: static, enable: true, path: import.meta.dirname }——enable: true印证了“默认开启”的声明。三、默认配置与运行环境差异eggjs/static的默认配置定义在 config.default.ts 与 config.prod.ts 两个文件中。汇总如下配置项默认值说明prefix/public/URL 访问前缀dirpath.join(appInfo.baseDir, app/public)静态文件存放目录即${baseDir}/app/publicdynamictrue动态加载文件不在初始化时全量缓存preloadfalse是否在初始化时预缓存所有文件始终与dynamic配合使用maxAge生产31536000其他环境0缓存有效期秒buffer生产true其他环境false是否把文件内容缓冲到内存maxFiles1000dynamic为 true 时 LRU 缓存的最大条目数本插件独有扩展项其中prefix、dir、dynamic、preload、maxFiles、buffer定义在 config.default.ts 的defineConfigFactory中static: { prefix: /public/, dir: path.join(appInfo.baseDir, app/public), dynamic: true, preload: false, buffer: false, maxFiles: 1000, },而maxAge与buffer的生产环境覆盖值位于 config.prod.tsstatic: { maxAge: 31536000, // 约 365 天 buffer: true, },这两个文件合起来说明了一个核心事实Egg.js 的config.default与config.prod会按环境合并因此开发环境默认maxAge 0、buffer false生产环境则自动切换为maxAge 31536000、buffer true开发者无需在部署脚本里做任何环境判断。3.1 各配置项的作用与取值建议prefix静态资源的 URL 前缀默认/public/。注意默认值自带首尾斜杠多个静态目录可配置不同前缀见下文第四节。dir静态文件根目录支持字符串或数组多目录映射。默认指向${baseDir}/app/public类型定义见 config.default.ts 中的StaticConfigdir: string | Arraystring | StaticDirOptions;dynamic是否动态加载文件。为true时文件在首次被请求后才进入缓存懒加载为false时配合preload: true可在启动阶段一次性把目录内所有文件加载进缓存。preload初始化时是否预加载全部文件始终与dynamic配合使用。preload: true时文件在进程启动时即缓存运行期更新磁盘文件不会生效需重启进程。maxAgeCache-Control的max-age时长秒。生产环境31536000即一年适合带指纹的构建产物开发环境为0保证改动即时可见。buffer为true时文件内容读入内存直接返回为false时每次请求通过流式方式从文件系统读取stream。生产环境用内存缓冲换取吞吐开发环境用流式换取修改即时生效。maxFiles本插件在 koa-static-cache 基础上额外提供的一个选项用于限制dynamic: true时 LRU 缓存的最大条目数默认1000。当缓存条目超过该值LRU 会淘汰最久未使用的文件避免内存无限增长。在config/config.default.ts中按需覆盖即可未覆盖项自动取默认值// {app_root}/config/config.default.ts export default { static: { // maxAge: 31536000, }, };四、核心使用规则app/public 与懒加载默认情况下$baseDir/app/public下的所有静态文件都可以通过/public前缀访问且所有文件均为懒加载lazy load。例如放入app/public/foo.js访问/public/foo.js即可命中。这一行为有两个直接后果README 中特别强调非生产环境资源不会被缓存maxAge: 0、buffer: false你修改文件后刷新页面即可看到效果生产环境资源在首次被访问后进入缓存更新静态文件需要重启进程preload/buffer生效磁盘变化不会反映到缓存。测试用例 static.test.ts 中 “serve dist” 一节专门验证了“缓存命中后修改磁盘文件、响应仍为旧内容”的行为it(should cache file, async () { await app.httpRequest().get(/static/app/a.js).expect(console.log(a)).expect(200); await fs.writeFile(jsFile, console.log(b)); await app.httpRequest().get(/static/app/a.js).expect(console.log(a)).expect(200); });两次请求均返回console.log(a)证明生产环境该用例启用了buffer: true等缓存配置下静态内容被缓存锁定磁盘更新不会热生效。4.1 边界行为目录请求与 404eggjs/static不提供目录浏览与index.html支持。测试用例明确覆盖了两个边界请求/public不带文件名的目录前缀返回404请求不存在的文件/public/foo404.js返回404。这说明插件只精确命中文件目录前缀本身不产生响应也不会自动回落到index.html。五、多目录与多前缀映射默认dir只指向app/public一个目录但 README 明确说明可以用数组形式定义多个目录有两种写法// 写法一纯字符串数组全部沿用全局 prefix dir: [dir1, dir2, ...] // 写法二对象形式可为每个目录单独指定 prefix dir: [ dir1, { prefix: /static2, dir: dir2 }, ]测试夹具 static-server-with-dirs/config/config.default.js 给出了一个可直接落地的完整示例const path require(path); module.exports (appInfo) { return { keys: aaa, static: { prefix: /public, dirs: [ path.join(appInfo.baseDir, /app/public), { prefix: /static, dir: path.join(appInfo.baseDir, /dist/static), }, ], buffer: true, }, }; };该示例同时演示了两种形式混用第一个元素是字符串复用全局prefix: /public第二个元素是对象自带prefix: /static。对应的测试断言验证了两个前缀均可独立访问/public/foo.js命中app/public/foo.js/static/app/a.js命中dist/static/app/a.js。从源码类型注释还可以看到dirs字段是dir的已废弃别名见 config.default.ts新代码应统一使用dir数组。5.1 对象形式的字段要求使用对象形式StaticDirOptions时每个对象必须包含dir字符串字段并可独立覆盖prefix、maxAge、buffer等任意 koa-static-cache 选项。中间件源码 static.ts 会对对象形式做断言校验assert( typeof dirObj.dir string, config.static.dirs should contains [].dir property when object style, );也就是说对象写法缺少dir字段会在启动时报错而不是静默失败。六、底层实现中间件组合、LRU 与自动建目录eggjs/static的中间件实现位于 app/middleware/static.ts它把“多目录解析 → Range 支持 → LRU 文件存储 → 目录自动创建 → 逐目录 staticCache”组装为一条组合中间件。核心流程如下export default (options: StaticConfig, app: Application): MiddlewareFunc { const dirs (options.dirs ?? []).concat(options.dir); const prefixes: string[] []; function rangeMiddleware(ctx: Context, next: Next) { const isMatch prefixes.some((p) ctx.path.startsWith(p)); if (isMatch) return range(ctx as any, next); return next(); } const middlewares [rangeMiddleware]; for (const dirObj of dirs) { // 1. 合并全局选项与当前目录的独立选项 const newOptions isString ? { ...options, dir: dirObj } : { ...options, ...dirObj }; // 2. dynamic 开启且未显式传入 files 时用 ylru 创建 LRU 缓存 if (newOptions.dynamic !newOptions.files) { newOptions.files new LRU(newOptions.maxFiles); } // 3. 记录前缀供 Range 中间件判断命中范围 if (newOptions.prefix) prefixes.push(newOptions.prefix); // 4. 目录不存在则自动递归创建默认 app/public 目录无需手动新建 if (!existsSync(newOptions.dir)) { mkdirSync(newOptions.dir, { recursive: true }); } middlewares.push(staticCache(newOptions)); app.coreLogger.info([eggjs/static] starting static serve %s - %s, newOptions.prefix, newOptions.dir); } return compose(middlewares); };几个值得注意的实现细节每个目录一份独立配置副本字符串形式时通过{ ...options, dir }复制全局选项对象形式通过{ ...options, ...dirObj }合并避免对象共享导致串改源码注释明确说明“copy origin options to new options ensure the safety of objects”。LRU 缓存即maxFiles的落地载体maxFiles: 1000并不是一个简单数字而是new LRU(newOptions.maxFiles)的容量参数由ylru提供淘汰策略。目录自动创建如果配置的静态目录尚不存在中间件启动时会自动mkdirSync(..., { recursive: true })因此app/public不提前建目录也不会报错。启动日志可观测每个目录生效都会输出[eggjs/static] starting static serve {prefix} - {dir}到coreLogger可用于排查“为什么这个目录没生效”。Range 中间件只对静态前缀生效rangeMiddleware先判断ctx.path是否以任一已注册前缀开头命中才进入koa-range处理普通业务路由不受影响。测试用例 “serve public” 中验证了/foo/bar业务路由即使带Range: bytes0-5头也返回完整内容200。七、Range 断点续传与 ETag 行为得益于koa-range与eggjs/koa-static-cache静态资源天然支持 HTTP Range 请求视频、断点下载、分块加载等场景。测试用例给出了精确的 206 断言it(should return 206 with partial content, () { return app .httpRequest() .get(/public/foo.js) .set(range, bytes0-10) .expect(Content-Length, 11) .expect(Accept-Ranges, bytes) .expect(Content-Range, bytes 0-10/20) .expect(206); });同时eggjs/koa-static-cache使用MD5 哈希作为 ETag见 koa-static-cache README 中 Uses MD5 hash sum as an ETag 的说明配合Cache-Control让浏览器与 CDN 层都能做条件请求与缓存校验。八、可继续深入的扩展配置项eggjs/static完整继承eggjs/koa-static-cache的全部选项koa-static-cache 源码 Options 定义以下选项在需要更精细控制时可直接写入static配置选项默认值说明cacheControlundefined自定义Cache-Control响应头字符串或按路径返回字符串的函数优先级高于maxAgegzipfalse请求Accept-Encoding含 gzip 时对文件做 gzip 压缩后返回usePrecompiledGzipfalse尝试使用磁盘上已存在的.gz预压缩文件类似 nginx 的gzip_static模块alias{}URL 别名映射对象filterundefined初始化扫描目录时过滤文件函数或字符串白名单数组可用于跳过源码、.map等非构建产物filesundefined外部文件缓存对象覆盖内部 LRU 存储例如生产构建场景下希望用预压缩.gz文件降低带宽同时用白名单只托管构建产物// config/config.prod.ts export default { static: { usePrecompiledGzip: true, filter: [.js, .css, .png, .svg], }, };九、中间件执行顺序确保 static 位于 bodyParser 之前插件还通过 app.ts 中的生命周期钩子configWillLoad调整了中间件顺序async configWillLoad(): Promisevoid { const app this.app; // make sure static middleware is before bodyParser const index app.config.coreMiddleware.indexOf(bodyParser); if (index -1) { app.config.coreMiddleware.push(static); } else { app.config.coreMiddleware.splice(index, 0, static); } }其意图是保证static中间件排在bodyParser之前静态文件请求应当直接命中文件响应避免先经过请求体解析带来的无谓开销与潜在干扰。这是静态服务器在 Egg 中间件栈中的既定位置也是通过代码保证的默认行为无需用户干预。十、总结一条可复用的静态托管决策路径综合 README 与源码使用eggjs/static时的决策路径可以概括为默认场景把文件放入app/public直接用/public/xxx访问零配置多目录场景用dir: [dir1, { prefix: /static, dir: dir2 }]映射多个目录与多个前缀注意对象形式必须带dir字段开发/生产差异开发环境无需关心缓存maxAge: 0生产环境记得“更新静态文件 重启进程”精细化控制按需开启usePrecompiledGzip、filter、cacheControl、alias等 koa-static-cache 选项用maxFiles控制 LRU 上限可观测启动日志中的[eggjs/static] starting static serve prefix - dir是排查目录未生效的第一手线索。更进一步你可以直接阅读 中间件源码 与 测试用例 理解每个行为背后的断言或深入到 eggjs/koa-static-cache 了解 ETag、gzip 与文件存储的具体实现。License本插件基于 MIT 协议开源详见 plugins/static/LICENSE。赞分享后端Web框架【免费下载链接】egg Born to build better enterprise frameworks and apps with Node.js Koa. https://307.run/eggcode项目地址https://gitcode.com/gh_mirrors/eg/egg点击查看免费下载相关推荐Mesop 静态资源Static Assets完整指南目录配置、URL 映射与实战用例Mesop 静态资源Static Assets完整指南目录配置、URL 映射与实战用例 本指南围绕 Mesop 的静态资源托管能力展开讲解如何通过 ME前端后端Web框架Home-AssistantConfig进阶技巧如何通过脚本实现复杂智能场景联动Home AssistantConfig进阶技巧如何通过脚本实现复杂智能场景联动 Home AssistantConfig是一套强大的智能家居配置系统通过脚Egg.js静态资源优化全攻略CDN加速与缓存策略实战Egg.js静态资源优化全攻略CDN加速与缓存策略实战 你还在为Egg.js应用的静态资源加载缓慢而烦恼吗用户抱怨页面打开要等3秒以上静态资源带宽成本居高后端Web框架上一篇AVeryComfyNerd进阶技巧Prompt Travel动画创作与IPAdapter应用指南下一篇Windows 性能优化实操从系统诊断到 4 类底层调优把延迟降下来创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价