欢迎光临
我们一直在努力

remix 前端资源服务器深度指南:`@remix-run/assets` 的 `createAssetServer` 演进与完整实践

remix 前端资源服务器深度指南:@remix-run/assets 的 createAssetServer 演进与完整实践

【免费下载链接】remix The fully-stacked web framework 【免费下载链接】remix 项目地址: https://gitcode.com/GitHub_Trending/re/remix

导读

@remix-run/assets 是 remix 仓库(The fully-stacked web framework)中负责按需编译并托管浏览器端 JS/TS 脚本与 CSS 样式的核心包:它基于 fetch 接口工作,可以无缝挂载到 fetch-router 等路由系统中,同时提供目录挂载、访问控制、指纹缓存、文件资源托管、HMR 等一系列开箱即用的能力。本指南以 packages/assets/CHANGELOG.md 为骨架,完整梳理 createAssetServer 从 v0.1.0 到 v0.6.0 的 API 演进与破坏性变更,并结合 packages/assets/src 源码与仓库内多个 demo 的真实配置,帮助你掌握配置迁移路径、每个选项的默认值与边界约束,以及 getAssets() / getAssetDetails() 等诊断接口的实际用法。

一、包定位:一个「编译型」资源服务器

在深入配置之前,先明确这个包的职责边界。根据 packages/assets/package.json 中的描述,它是:

Fetch-based server for compiling browser JS/TS and CSS assets on demand

即:一个基于 fetch 的、按需编译浏览器 JS/TS 与 CSS 资源的服务器。入口位于 packages/assets/src/assets.ts,对外只暴露少数几个符号:

  • createAssetServer():核心工厂函数,创建一个 AssetServer 实例;
  • defineFileTransform():定义可用于文件资源 URL 的请求时变换(见第四节);
  • 若干类型导出:AssetServer、AssetServerOptions、AssetAccessDetails、AssetDetails、AssetKind、AssetStatus、ModuleLoader、BrowserHmrChannel 等。

AssetServer 实例的核心接口(定义在 packages/assets/src/lib/asset-server.ts#L289-L319)非常精简:

方法作用
fetch(request) 处理脚本/样式/文件资源请求,返回 Response;无法处理时返回 null,让外层路由继续向下匹配(最终可落到 404)
getHref(filePath) 返回某个已服务资源文件对应的请求 URL
getPreloads(filePath) 返回一个或多个资源文件的 preload URL,按“浅层优先”排序
getAssetDetails(input) 返回某个公开 URL 或文件路径的诊断信息(v0.6.0 新增)
getAssets() 返回当前可被浏览器访问的全部资源清单(v0.6.0 新增)
close() 关闭文件系统 watcher 与浏览器 HMR channel

一个最小可用的示例(取自 asset-server.ts 源码注释):

let assetServer = createAssetServer({
basePath: '/assets',
allowFiles: ['app/routes.ts', 'app/**/public/**'],
allowPackages: ['remix'],
denyFiles: ['app/**/*.test.*'],
})

route('/assets/*path', ({ request }) => assetServer.fetch(request))

二、v0.3.0:basePath 成为必填项

变更内容

v0.3.0 是一个破坏性变更:createAssetServer() 现在必须提供 basePath,且 fileMap 中的 URL 模式改为相对于该 base path 解析。

// Before:
createAssetServer({
fileMap: {
'/assets/app/*path': 'app/*path',
'/assets/npm/*path': 'node_modules/*path',
},
allow: ['app/**', 'node_modules/**'],
})

// After:
createAssetServer({
basePath: '/assets',
fileMap: {
'/app/*path': 'app/*path',
'/npm/*path': 'node_modules/*path',
},
allow: ['app/**', 'node_modules/**'],
})

源码佐证

在 resolveAssetServerOptions 中,basePath 会经过 normalizeBasePath 归一化(asset-server.ts#L1137-L1143):必须是字符串,basePath || '/' 后去除尾部斜杠,空值回落为 /。随后 basePath 会连同 mounts 一起被传入 compileRoutes,生成 URL 与文件系统路径之间的双向映射(asset-server.ts#L1065-L1071)。

同版本附带修复

  • @oxc-project/runtime(为面向旧浏览器生成的代码提供运行时辅助函数)现在由资源服务器自动托管,无需手动安装——在 package.json 中可见其作为直接依赖存在。

三、v0.6.0:fileMap → mounts 的目录化演进

变更内容

v0.6.0 用可选的目录式 mounts 替换了 fileMap 选项。mounts 会递归保留每个 public 根与文件系统根之下的路径,从而使模块 URL 与用于包解析的文件系统层级保持一致。

  • 省略 mounts 时,默认值为 { app: 'app', npm: 'node_modules' };
  • 若应用原有的 fileMap 恰好等价于新默认值,直接删除 fileMap 即可;
  • 若原来是自定义 fileMap 规则,则去掉两侧的尾部通配符,并把 fileMap 改名为 mounts。

迁移示例一:等价于默认值,直接删除

// before
createAssetServer({
basePath: '/assets',
fileMap: {
'/app/*path': 'app/*path',
'/npm/*path': 'node_modules/*path',
},
// …
})

// after
createAssetServer({
basePath: '/assets',
// …
})

迁移示例二:自定义层级

// before
createAssetServer({
basePath: '/assets',
fileMap: {
'/source/*path': 'app/*path',
'/vendor/*path': 'node_modules/*path',
},
// …
})

// after
createAssetServer({
basePath: '/assets',
mounts: {
source: 'app',
vendor: 'node_modules',
},
// …
})

源码佐证与约束

在 asset-server.ts#L176-L179 中可以看到默认值定义:

const defaultMounts = {
app: 'app',
npm: 'node_modules',
} as const

而 routes.ts 则完整实现了 mounts 的编译与校验逻辑:

  • mounts 的 key 是公开 URL 路径,value 是相对 rootDir 的目录(asset-server.ts#L190-L194);
  • value 不允许是绝对路径(compileMount 中会抛出 mounts values must be relative to rootDir);
  • key 不允许包含 query string、fragment 或编码的点段(routes.ts#L123-L138);
  • file root 之间不允许重叠、URL root 之间也不允许重叠(routes.ts#L168-L207);
  • mounts 不能为空对象,否则抛出 mounts must include at least one entry(asset-server.ts#L1046-L1048);
  • URL → 文件路径解析时会对 URL 路径做 decodeURIComponent(routes.ts#L69),因此 v0.4.4 修复的 percent-encoded 字符(如 scoped 包名 %40remix-run)在此得以正确解析;反向 matchFilePath 则对每个路径段做 encodeURIComponent(routes.ts#L89)。

仓库内的真实用法

在 demos/bookstore/app/utils/assets.ts 中可以看到一个完整的 monorepo 级 mounts 配置:

export const assets = createAssetServer({
basePath: assetsBase,
rootDir: path.resolve(import.meta.dirname, '../../../..'),
allowFiles: ['demos/bookstore/app/routes.ts', 'demos/bookstore/app/**/public/**'],
allowPackages: ['remix'],
denyFiles: ['demos/bookstore/app/**/*.test.*'],
mounts: {
app: 'demos/bookstore/app',
packages: 'packages',
},
sourceMaps: isDevelopment ? 'external' : undefined,
minify: !isDevelopment,
fingerprint: isDevelopment
? undefined
: { buildId: process.env.GITHUB_SHA || String(Date.now()) },
watch: isDevelopment,
hmr: isHmr
? async () => (await import('remix/node-hmr/runtime')).createBrowserHmrChannel()
: undefined,
scripts: {
loaders: isHmr ? [uiHmr()] : undefined,
},
})

注意:由于该 demo 位于仓库子目录 demos/bookstore,rootDir 指向仓库根,allowFiles/denyFiles 与 mounts 的 value 均以仓库根为基准。你自己的应用通常更简单——把 rootDir 指向应用根目录(默认是 process.cwd()),直接用 app、node_modules 这类相对目录即可。同样的配置模式也出现在 demos/frame-navigation/app/utils/assets.ts、demos/frames/app/utils/assets.ts、demos/sse/app/utils/assets.ts 中,可交叉参考。

四、v0.5.0:访问控制重构与三大新能力

1. allow/deny → allowFiles/denyFiles

v0.5.0 起,文件路径访问规则从 allow/deny 改名为 allowFiles/denyFiles:

import { createAssetServer } from 'remix/assets'

// Before:
export const assetServer = createAssetServer({
allow: ['app/routes.ts', 'app/**/public/**'],
deny: ['app/**/*.test.*'],
/* … */
})

// After:
export const assetServer = createAssetServer({
allowFiles: ['app/routes.ts', 'app/**/public/**'],
denyFiles: ['app/**/*.test.*'],
/* … */
})

从 access.ts 的实现看,访问策略遵循「先 allow 后 deny」:inspect() 先依次用 allowFiles 的 glob 匹配器寻找允许规则,未命中时再尝试 allowPackages 命名的包根路径;若仍无命中则直接 allowed: false;随后若命中任一 denyFiles 规则,则最终判定为拒绝(access.ts#L90-L114)。v0.2.0 还顺带修复了 allow/deny glob 对点前缀文件与目录的匹配问题,升级后该行为同样适用于 allowFiles/denyFiles。

2. 新增 allowPackages:包级访问控制

allowPackages 允许按包名放行资源,被允许的包及其依赖、已安装的 optional 依赖都会被自动允许。例如 allowPackages: ['remix'] 即可放行整个 remix 包及其依赖树。注意两个约束:

  • 包文件仍必须位于某个已配置的 mount 之内(asset-server.ts#L204-L207);
  • 包名必须合法:支持普通包名与 scoped 包名(@scope/name 两段式),拒绝 .、.. 及空段(access.ts#L437-L449)。

实现上,createAccessPolicy 会从每个 search root(默认是 rootDir 加上各 mount 的 file root,见 getPackageSearchRoots)向上逐层查找 package.json,找到后递归收集其 dependencies 与已安装的 optionalDependencies,把所有包根路径组织成一棵 Trie 用于 O(段数) 的命中判定(access.ts#L181-L291)。若在 watch 模式下 package-lock.json、pnpm-lock.yaml、yarn.lock、bun.lock 等包状态文件发生变化,Trie 会自动标记为 dirty 并在下次判定前重建(access.ts#L34-L42、access.ts#L125-L130)。测试用例可参考 packages/assets/src/lib/asset-server.test.ts 中围绕 allowPackages: ['@remix-run/__example'] 的用例(约 L4821、L4873)。

3. HMR 支持

createAssetServer 增加 hmr 选项:为 JS 资源提供 import.meta.hot API。相关要点:

  • hmr 是工厂函数,签名见 BrowserHmrChannelFactory:返回 BrowserHmrChannel | undefined | Promise<…>;返回 undefined 表示 HMR 不激活;
  • HMR 要求开启 watch 模式:源码在 asset-server.ts#L1043-L1045 强制校验 hmr requires watch mode;
  • channel 必须提供 url、close()、onFileEvents()、updateWatchedFiles() 四个成员(asset-server.ts#L1119-L1135);
  • 资源服务器会为浏览器注入一个 HMR client 脚本,其路径形如 ${basePath}/__remix_hmr/client.js,事件端点位于 ${basePath}/__remix_hmr/events(asset-server.ts#L903-L909);
  • assetServer.close() 会同时关闭该 channel 与文件 watcher(asset-server.ts#L846-L858)。

bookstore demo 展示了标准接线方式(demos/bookstore/app/utils/assets.ts):

const isHmr = Boolean(isDevelopment && process.env.REMIX_NODE_HMR)

hmr: isHmr
? async () => (await import('remix/node-hmr/runtime')).createBrowserHmrChannel()
: undefined,
scripts: {
loaders: isHmr ? [uiHmr()] : undefined,
},

4. 新增 scripts.loaders:同步 JS 后处理

scripts.loaders 使用 Node 同步 load hook 签名对编译后的 JavaScript 做后处理。关键语义(源码注释见 asset-server.ts#L164-L173):

  • 多个 loader 会互相包裹:[first, second] 中 second 先进入,委托给 first,最终落到默认行为;因此 nextLoad() 之后执行的变换按数组顺序生效;
  • 执行时机:TS/JS 变换之后、HMR 分析与 minify 之前;
  • 只支持 format: 'module',不支持 import attributes;
  • 每个 loader 必须委托给 nextLoad 或返回 shortCircuit: true。

ModuleLoader 的完整类型定义在 packages/assets/src/lib/loaders.ts(含 ModuleLoadContext 与 ModuleLoadResult)。一个独特之处是 context 中提供了 moduleUrl——即稳定的公开 URL,而不是 Node 标准 load hook 中的私有 file: URL,便于 loader 使用浏览器侧模块身份(loaders.ts#L64-L70)。

5. v0.6.0 新增诊断 API:getAssets() 与 getAssetDetails()

这两个 API 用于列出浏览器可达的文件与检查 URL 映射、文件类型、访问规则与可达性状态。它们复用的是资源服务器自身配置的 mapping 与访问策略,因此诊断结果与真实请求处理一致(CHANGELOG 注明了对应 issue #11726)。

实现位于 packages/assets/src/lib/inspection.ts:

  • getAssetDetails(input):接受 file:// URL、绝对/相对文件路径或公开 URL(含指纹后缀),返回 AssetDetails(inspection.ts#L92-L113);
  • getAssets():扫描 allowFiles 静态前缀目录、allowPackages 包根与注入包根,收集所有文件,逐一映射到 URL 并过滤出 reachable 项,按 URL 再按文件路径排序(inspection.ts#L151-L209)。

AssetDetails 结构(inspection.ts#L30-L45):

interface AssetDetails {
access?: AssetAccessDetails // 访问判定与命中的规则(allowedBy/deniedBy)
filePath?: string // 绝对映射文件路径
fileRoot?: string // 命中的文件系统 mount 根
status: AssetStatus // 'reachable' | 'denied' | 'not-allowed' | 'missing' | 'unmapped' | 'unsupported'
type?: AssetKind // 'script' | 'style' | 'file' | 'unsupported'
url?: string // 稳定的公开 URL pathname
urlRoot?: string // 命中的公开 mount 根
}

测试中展示了典型断言方式(asset-server.test.ts):可达资源返回 status: 'reachable';denyFiles 命中的资源返回 status: 'denied';不存在的文件返回 status: 'missing';不在任何 mount 内的 URL 返回 status: 'unmapped';未被 allowFiles/allowPackages 覆盖但存在的文件返回 status: 'not-allowed';扩展名不支持的返回 status: 'unsupported'。

五、v0.4.x 系列:符号链接与包解析的真实路径

v0.4.x 的几个 patch 都围绕符号链接与真实路径展开:

  • v0.4.1:从符号链接包解析 bare imports 时使用包的真实文件系统路径,使 pnpm virtual-store 依赖能够通过资源服务器被服务(对应 issue #11438);
  • v0.4.2:重写脚本 import 时使用 canonical realpath 资产 URL,避免符号链接路径与其真实路径产生重复的浏览器模块;同时 Windows 上默认使用轮询式文件监听,避免原生文件系统 watcher 崩溃,同时仍允许显式 watch.poll 覆盖;
  • v0.4.4:修复包含 percent-encoded 文件路径字符的 URL 的资产路由解析,包括 %40remix-run 这类 scoped 包名。

Windows 轮询默认值的源码佐证位于 packages/assets/src/lib/watch.ts#L95-L106:

return {
awaitWriteFinish: {
pollInterval: 10,
stabilityThreshold: 10,
},
depth: 0,
ignored: ['**/.git/**', …(options.ignore ?? [])],
interval: options.pollInterval ?? 100,
usePolling: options.poll ?? process.platform === 'win32',
}

即:usePolling 的默认值是 process.platform === 'win32',轮询间隔默认 100ms。v0.4.2 同时升级了 file-storage@0.13.6、headers@0.21.1、route-pattern@0.22.0。

六、v0.4.0:files 选项与 CSS url() 解析

v0.4.0 新增 files 选项,用于直接托管配置的叶文件资产,并且:

  • 相对 CSS url() 引用现在通过资源服务器解析,将受支持的文件资产重写为资源服务器 URL;
  • 对缺失或不受支持的文件抛出错误(而非静默失败)。

files 选项的完整结构(packages/assets/src/lib/files/config.ts#L94-L118):

字段说明默认值/约束
extensions 作为叶资产暴露的文件扩展名列表,须带前导点,如 ['.png', '.svg', '.woff2'] 必填;格式须匹配 ^\\.[A-Za-z0-9_-]+$
transforms 可从资产 URL 请求的命名变换 key 须为 transform-name 格式
maxRequestTransforms 单个资产 URL 允许的最大请求变换数 默认 5,须为正整数
globalTransforms 对每个被服务文件资产按序运行的变换,可返回 null 跳过自身 可为函数或对象
cache 可选变换产物缓存,须实现 FileStorage 接口 可选

注意约束:files.extensions 不允许包含编译型资产扩展名(脚本模块扩展名、.css、.map 均被保留拦截,见 config.ts#L167 与 config.ts#L202-L206)。

一个真实且完整的 files 配置在 demos/assets/app/utils/assets.ts 中:它把 .svg 暴露为叶资产,用 globalTransforms 对每个 SVG 执行 svgo 多轮优化,并定义了带必选参数的 recolor 请求变换(把 SVG 中的 currentColor 替换为指定十六进制色值):

export const assetServer = createAssetServer({
basePath: assetsBase,
rootDir: path.resolve(import.meta.dirname, '../..'),
allowFiles: ['app/routes.ts', 'app/**/public/**'],
denyFiles: ['app/**/*.test.*'],
files: {
cache: createFsFileStorage(path.resolve(import.meta.dirname, '../../.tmp/assets-cache')),
extensions: ['.svg'],
globalTransforms: [
{
extensions: ['.svg'],
async transform(bytes) {
let svg = new TextDecoder().decode(bytes)
return optimizeSvg(svg, { multipass: true }).data
},
},
],
transforms: {
recolor: defineFileTransform({
extensions: ['.svg'],
param: true,
async transform(bytes, { param }) {
if (!/^#?(?:[\\da-f]{3,4}|[\\da-f]{6}(?:[\\da-f]{2})?)$/i.test(param)) {
throw new TypeError('Expected a hex color, with or without a leading #')
}
let svg = new TextDecoder().decode(bytes)
return svg.replaceAll('currentColor', `${!param.startsWith('#') ? '#' : ''}${param}`)
},
}),
},
},
watch: isDevelopment,
fingerprint: isDevelopment
? undefined
: { buildId: process.env.GITHUB_SHA || String(Date.now()) },
})

请求变换通过 URL query 传递(如 ?transform=recolor:%23ff0000),单个 URL 最多 maxRequestTransforms(默认 5)个,序列化与解析逻辑见 config.ts#L349-L479。defineFileTransform 用于获得强类型推导:param: true 表示必须提供参数、'optional' 表示可选、不设置表示不接受参数。

七、v0.2.0:target、共享编译选项与 CSS 支持

v0.2.0 是另一个重要的破坏性版本:

1. target 提升为顶层对象格式

target 现在支持 es 版本目标 + 浏览器版本目标的组合:

  • 浏览器目标用字符串版本,如 target: { chrome: '109', safari: '16.4' };
  • 脚本可指定 es 为 2015 及以上年份,如 target: { es: '2020' };
  • 迁移:把 scripts: { target: 'es2020' } 改为 target: { es: '2020' }。

支持的浏览器键与取值约束(packages/assets/src/lib/target.ts):

  • 浏览器键:chrome、edge、firefox、ie、ios、opera、safari、samsung;
  • 版本格式:X、X.Y 或 X.Y.Z,每个分量 0~255;
  • es 必须是四位年份数字且 ≥ 2015,最终序列化为 esYYYY 形式;
  • 未知键会抛出 target.<key> is not a supported target。

脚本侧,es 与浏览器目标被合并为 oxc-transform 的目标数组;样式侧,浏览器目标被映射为 lightningcss 目标格式(ios → ios_saf),es 不影响样式(target.ts#L51-L86)。es 目标在样式配置中会被显式跳过(target.ts#L129-L131)。

2. 共享编译选项提升到顶层

sourceMaps、sourceMapSourcePaths、minify 从 scripts 下移到 createAssetServer() 顶层,从而同时作用于脚本与样式:

  • sourceMaps:'external'(输出独立 .map 文件)或 'inline'(以 base64 data URL 内嵌);
  • sourceMapSourcePaths:'url'(默认,使用稳定服务端路径如 /assets/app/entry.ts)或 'absolute'(使用磁盘上的原始文件系统路径);
  • minify:布尔值。

迁移方式:把 scripts.minify、scripts.sourceMaps、scripts.sourceMapSourcePaths 移到顶层。在 resolveAssetServerOptions 中可以看到它们最终分别解析为 scriptsTarget 与 stylesTarget 两条独立管线。

3. CSS 编译与服务

createAssetServer() 现在与脚本一起编译和服务 .css 文件,包括:

  • 本地 @import 重写;
  • 指纹(fingerprinting);
  • 共享编译选项(minify、source maps、浏览器兼容目标)。

请求处理中,.css 文件走 styleCompiler 管线(asset-server.ts#L633-L664),支持 If-None-Match/ETag 协商缓存与 304 响应;脚本与样式管线都支持 If-None-Match(asset-server.ts#L631-L727)。

八、v0.1.0 与依赖基线

  • v0.1.0 是 @remix-run/assets 的初始发布,首个依赖基线为 route-pattern@0.20.1;
  • 此后每次发版都会 bump 依赖:v0.3.0 引入 @oxc-project/runtime;v0.4.2 升级 file-storage@0.13.6、headers@0.21.1、route-pattern@0.22.0;v0.4.4 升级 file-storage@0.13.7、mime@0.4.2、route-pattern@0.23.0;v0.5.0 升级 route-pattern@0.24.0。这些依赖在 packages/assets/package.json 中均有体现。

当前版本为 0.6.0,语义化版本(semver)管理:Minor Changes 中的 BREAKING CHANGE 意味着对应主版本升级时需要按上文迁移。

九、演进脉络速查表

版本类型核心变化
v0.1.0 Minor @remix-run/assets 初始发布,依赖 route-pattern@0.20.1
v0.2.0 Breaking target 改为顶层对象(es + 浏览器版本);sourceMaps/sourceMapSourcePaths/minify 提升到顶层;新增 CSS 编译与服务
v0.3.0 Breaking basePath 必填;fileMap URL 模式相对 basePath;自动托管 @oxc-project/runtime
v0.4.0 Minor 新增 files 叶文件资产选项;CSS url() 经资源服务器解析重写
v0.4.1 Patch 符号链接包 bare import 使用真实路径(pnpm 支持)
v0.4.2 Patch canonical realpath 资产 URL 避免重复模块;Windows 默认轮询监听
v0.4.3 Patch 依赖 bump(route-pattern@0.22.1)
v0.4.4 Patch 修复 percent-encoded 路径字符的路由解析(含 scoped 包名)
v0.5.0 Breaking allowFiles/denyFiles 更名;新增 allowPackages、hmr、scripts.loaders
v0.6.0 Breaking fileMap → mounts(默认 { app: 'app', npm: 'node_modules' });新增 getAssets()/getAssetDetails() 诊断 API

十、升级检查清单

结合上文,从旧版本升级到 v0.6.0 时可依次核对:

  • basePath(v0.3.0 起必填):确认 createAssetServer 传入了 basePath;
  • fileMap → mounts(v0.6.0):若仍在使用 fileMap,按第二节迁移为 mounts;确认 mount 的 key/value 不重叠、value 为相对 rootDir 的目录;
  • allow/deny → allowFiles/denyFiles(v0.5.0):重命名访问规则,并检查 glob 是否匹配点前缀文件;
  • scripts.target → target(v0.2.0):把脚本目标改为顶层 target: { es: '2020' } 或浏览器版本对象;
  • scripts.minify / scripts.sourceMaps / scripts.sourceMapSourcePaths → 顶层(v0.2.0):移出 scripts 作用域;
  • 新增能力按需启用:需要包级放行时配置 allowPackages;需要开发热更新时开启 watch + hmr + scripts.loaders;需要托管图片/字体等叶资产时配置 files;需要诊断时使用 getAssets()/getAssetDetails();
  • 依赖基线:确保 route-pattern、file-storage、headers、mime 等 @remix-run/* 依赖处于 changelog 所列版本之上,@oxc-project/runtime 无需手动安装。
  • 十一、进一步阅读

    • 包入口与导出:packages/assets/src/assets.ts
    • 核心工厂与全部选项定义:packages/assets/src/lib/asset-server.ts
    • 访问策略(allowFiles/denyFiles/allowPackages):packages/assets/src/lib/access.ts
    • mounts 编译与 URL/文件系统双向映射:packages/assets/src/lib/routes.ts
    • 文件资产与请求变换配置:packages/assets/src/lib/files/config.ts
    • 诊断 API 实现:packages/assets/src/lib/inspection.ts
    • target 解析与校验:packages/assets/src/lib/target.ts
    • 同步 loader 类型契约:packages/assets/src/lib/loaders.ts
    • 测试用例(覆盖 mounts/allowPackages/诊断 API):packages/assets/src/lib/asset-server.test.ts
    • 实战配置示例一(含 files、transforms):demos/assets/app/utils/assets.ts
    • 实战配置示例二(含 mounts、hmr、loaders):demos/bookstore/app/utils/assets.ts </output文章>

    【免费下载链接】remix The fully-stacked web framework 【免费下载链接】remix 项目地址: https://gitcode.com/GitHub_Trending/re/remix

    创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

    赞(0)
    未经允许不得转载:171主机测评 » remix 前端资源服务器深度指南:`@remix-run/assets` 的 `createAssetServer` 演进与完整实践
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址