feat(compiler): 发布浏览器静态资源清单,构建期挡住产物漂移 - #190
Conversation
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: c0d256419d
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| .map(([file, output]) => { | ||
| const cut = Math.max(file.lastIndexOf('/'), file.lastIndexOf('\\')) | ||
| return { | ||
| name: cut < 0 ? file : file.slice(cut + 1), |
There was a problem hiding this comment.
Preserve output directories in the asset check
When an entry is moved into a subdirectory—for example, esbuild emits dist/assets/stage-worker.browser.js—discarding the directory makes that output indistinguishable from the required sibling dist/stage-worker.browser.js. The contract therefore passes even though resolveBrowserAssets() still returns a nonexistent root-level file; retain and validate the path relative to dist so directory drift is rejected.
AGENTS.md reference: AGENTS.md:L23-L24
Useful? React with 👍 / 👎.
| return { | ||
| name: cut < 0 ? file : file.slice(cut + 1), | ||
| staticImports: (output.imports || []) | ||
| .filter((entry) => entry.kind === 'import-statement') |
There was a problem hiding this comment.
Reject non-dynamic metafile import kinds
When a dependency is externalized from CommonJS code, esbuild records the remaining dependency as kind: 'require-call' rather than import-statement. This filter drops it, so the contract reports the bundle as self-contained even though the raw browser asset still attempts a runtime require() and fails. Treat every metafile import except the deliberately supported dynamic setup import as a violation.
AGENTS.md reference: AGENTS.md:L23-L24
Useful? React with 👍 / 👎.
| /** The browser bundles a host has to host as static assets. */ | ||
| export const COMPILER_BROWSER_ASSETS = /** @type {readonly CompilerBrowserAsset[]} */ ([ | ||
| { | ||
| name: 'stage-worker.browser.js', |
There was a problem hiding this comment.
Validate manifest names against package exports
When an asset is renamed and both the esbuild entry and this manifest are updated, the contract passes while package.json can still map ./browser, ./pool, or ./stage-worker to the old filename. That leaves normal imports—and the documented require.resolve('@dimina-kit/compiler/browser') bootstrap—pointing to a missing file, so the supposedly authoritative names should also generate or be checked against those export targets.
AGENTS.md reference: AGENTS.md:L12-L12
Useful? React with 👍 / 👎.
|
|
||
| ### 浏览器静态资源:哪三个文件必须原样托管 | ||
|
|
||
| 上表里带 `.browser.js` 的三个产物,宿主要**原样拷贝、原样托管**,不能再过一遍自己的打包器。stage worker 只被 `new Worker(url)` 引用,另外两个只被 fetch 下来从 Blob URL import——没有一处是打包器能看见的静态 import,所以打包器要么整个漏掉这些文件,要么把它们改写坏,两种情况都不报错,只在运行时 404 或行为异常。 |
There was a problem hiding this comment.
Scope the raw-hosting rule to static loading
This states that all three browser bundles must bypass the host bundler and that the pool/core are only fetched and Blob-imported, but the recommended path later statically imports @dimina-kit/compiler/pool (README.md:89-96) and explicitly supports a bundler-resolved stage worker (README.md:135-137). As written, users following the new rule must add unnecessary manual copying and are told that the documented primary integration is invalid; limit this requirement to hosts that choose runtime URL loading.
AGENTS.md reference: AGENTS.md:L23-L24
Useful? React with 👍 / 👎.
dist 里三个 .browser.js 必须由宿主原样托管:stage worker 只被 new Worker(url) 引用,另外两个只被 fetch 后从 Blob URL import。没有 一处是打包器看得见的静态 import,所以文件名一旦变了,宿主那边不会 有任何编译期报错,只在运行时 404。 新增 @dimina-kit/compiler/browser-assets:清单 COMPILER_BROWSER_ASSETS、 从 ./browser 入口推出资源路径的 resolveBrowserAssets,宿主不用再手抄 文件名。同时发 ESM 与 CJS,只做字符串拼接,不依赖 node:path。 browser 构建现在开 metafile,拿 esbuild 自己的产物记录对着清单自检: 清单里的产物没被生成、生成了清单外的新产物(比如拆出 chunk)、或某个 静态资源开始带静态 import(不再自包含),构建直接失败。 scripts/test-browser-assets.js 把上述失败模式直接驱动一遍,并接进 package.json 的 test(turbo run test 会跑到)。
评审指出三处会静默放行的漏洞:产物被挪进 dist 子目录后,只比对文件名的检查看不出 差别;externalize 的 CJS 依赖在 metafile 里记成 require-call,只筛 import-statement 的检查漏掉它;清单里的文件名和 package.json 的 exports 各写各的,改名只落一边不会 报错。 现在产物名保留 dist 以下的目录,任何 import 类型都算破坏自包含(stage worker 的 import(toolchainSetupURL) 不受影响:specifier 是运行时变量,esbuild 记不下来,真实 浏览器构建实测三个资源零 import),并新增 checkAssetsAgainstExports 双向对账。 README 里"必须原样托管"的说法收窄到自己按 URL 托管的宿主——通过包名 import pool、 用 new URL 解析 stage worker 的宿主,打包器看得见引用,本来就该由它处理。
c0d2564 to
041e8f2
Compare
|
合并进 #195,内容一致,关闭这个单独的 PR。 |
问题
dist/里的三个.browser.js必须由宿主原样拷贝、原样托管:stage worker 只被new Worker(url)引用,pool.browser.js和compile-core.browser.js只被 fetch 下来从 Blob URL import。没有一处是打包器能看见的静态 import。后果是这个契约完全靠口头维持:产物改个名、被 esbuild 拆出一个新 chunk、或者哪天某个 bundle 不再自包含(开始
import别的文件),本仓构建照样绿,宿主那边也不会有编译期报错——只在运行时 404 或行为异常。宿主目前只能把这三个文件名硬编码在自己的拷贝脚本里。改动
@dimina-kit/compiler/browser-assets(新导出)——清单本身是可被程序读取的:宿主只需要一个它本来就能拿到的路径(
./browser入口的解析结果),不用知道 dist 布局,也不用手抄文件名。模块只做字符串拼接,不依赖node:path;ESM 和 CJS 都发(require条件),带类型声明。构建期自检——browser 构建开
metafile,用 esbuild 自己的产物记录核对三件事,任一不满足直接 exit 1:COMPILER_BROWSER_ASSETS宿主要托管,BUNDLER_ONLY_BROWSER_OUTPUTS宿主用自己的打包器 import);import(toolchainSetupURL)正是约定的用法)。这个检查随
pnpm --filter @dimina-kit/compiler build:browser跑,而 devkit 的check-types依赖^build,所以 CI 里必经。验证
scripts/test-browser-assets.js(13 条断言,已接进package.json的test,turbo run test会跑到)把检查存在的意义——那几种失败模式——直接驱动一遍:产物改名同时报「少了」和「多了未分类的」、拆出新 chunk 被拦、静态资源出现静态 import 被拦、metafile 整形(丢 sourcemap、动态 import 不计入)、POSIX 与 Windows 路径解析、传裸文件名报错。端到端也真的验过一次:把
build-compiler.js里pool.browser的输出名临时改成pool-renamed.browser,构建报出改回来后构建恢复
✅ browser static-asset contract holds (3 assets, no static imports)。./scripts/gate.sh全绿(lint / typecheck / test / pawl:check)。说明
基于 #189,先合那个。这里只多了
types-fixture/consumer.ts里新导出的那几行类型断言。esbuild-wasm、oxc 那几个 wasm 静态资源没有一起放进清单:它们是 peer 依赖的产物,不由本包构建生成,写进来就成了构建无法自检的第二份真相,只会重复宿主已经在做的
require.resolve。