feat(build): support bytes and text import attributes#4431
Conversation
Implements the import bytes and import text proposals via a pre-transform
that rewrites `with { type: "bytes" | "text" }` imports to the internal raw
prefixes, since rolldown does not expose import attributes to plugins.
Resolved raw ids now carry a `.js` suffix so that plugins matching the
original extension (`@rollup/plugin-json`, vite json, ...) no longer try to
transform them again.
https://github.com/tc39/proposal-import-bytes
https://github.com/tc39/proposal-import-text
Co-Authored-By: Claude Opus 4.8 <[email protected]>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughNitro transforms ChangesImport attributes
Estimated code review effort: 3 (Moderate) | ~25 minutes 🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/build/plugins/raw.ts`:
- Around line 51-58: Restore the this.addWatchFile(path) call in the handler
method after parseRawId(id) and before reading the file, so raw text and binary
assets are tracked for watch-mode updates. Leave the HELPER_ID branch and
existing fsp.readFile behavior unchanged.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: a046af3a-d77c-4cad-b27d-dc7483f8c53a
⛔ Files ignored due to path filters (1)
test/fixture/server/files/bytes.binis excluded by!**/*.bin
📒 Files selected for processing (6)
docs/1.docs/50.assets.mdsrc/build/plugins.tssrc/build/plugins/import-attributes.tssrc/build/plugins/raw.tstest/fixture/server/routes/import-attributes.tstest/tests.ts
- warn instead of silently skipping when oxc cannot parse a module
- lazily import rolldown, matching the oxc and route-meta plugins
- derive raw prefix regexes from a single list of types
- test `raw:` json imports and `export ... from ... with { type }`
Co-Authored-By: Claude Opus 4.8 <[email protected]>
There was a problem hiding this comment.
♻️ Duplicate comments (1)
src/build/plugins/raw.ts (1)
51-54: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick winRestore
this.addWatchFile(path)for raw assets.The plugin reads the underlying file through a synthetic
virtual:nitro:*module, meaning the bundler won't track the original source asset automatically. By leaving this commented out, edits to the raw text or binary inputs won't trigger a rebuild and can stay stale in watch mode.🐛 Proposed fix
const { path, binary } = parseRawId(id); - // this.addWatchFile(path); + this.addWatchFile(path); return fsp.readFile(path, binary ? "binary" : "utf8");🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/build/plugins/raw.ts` around lines 51 - 54, Restore the this.addWatchFile(path) call in the raw asset loading hook immediately after parseRawId(id) and before fsp.readFile, so both text and binary source files are tracked for watch-mode rebuilds.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Duplicate comments:
In `@src/build/plugins/raw.ts`:
- Around line 51-54: Restore the this.addWatchFile(path) call in the raw asset
loading hook immediately after parseRawId(id) and before fsp.readFile, so both
text and binary source files are tracked for watch-mode rebuilds.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 18cb395b-18fb-4447-be9d-b728e21d07f5
📒 Files selected for processing (7)
src/build/plugins.tssrc/build/plugins/import-attributes.tssrc/build/plugins/raw.tstest/fixture/server/routes/_import-attributes-reexport.tstest/fixture/server/routes/import-attributes.tstest/fixture/server/routes/raw.tstest/tests.ts
🚧 Files skipped from review as they are similar to previous changes (4)
- src/build/plugins.ts
- test/fixture/server/routes/import-attributes.ts
- test/tests.ts
- src/build/plugins/import-attributes.ts
Raw ids end with a `.js` suffix, so the import attributes plugin matched the file contents loaded by the raw plugin and rewrote the attribute syntax inside source files imported as text. Exclude the raw plugin ids from its transform. Co-Authored-By: Claude Opus 4.8 <[email protected]>
Use the oxc ESTree types from rolldown instead of `any`, remove the dynamic import options via its own span, and re-check the id filter in the handler fallback. Co-Authored-By: Claude Opus 4.8 <[email protected]>
Co-Authored-By: Claude Opus 4.8 <[email protected]>
Windows runners check out with autocrlf, so a `text:` or `raw:` import of `test.json` returned CRLF and failed the exact-content assertions. Co-Authored-By: Claude Opus 4.8 <[email protected]>
commit: |
Enable watching of raw-imported files, skip query strings and external resolutions, and tolerate builders without hook filter support. Co-Authored-By: Claude Fable 5 <[email protected]>
Exclude raw modules from `@rollup/plugin-replace` and drop its chunk-level `renderChunk` pass, which rewrote keys like `import.meta.*` inside imported file contents after inlining (breaking the syntax with quoted values). Also skip transpiling raw modules in the oxc plugin. Co-Authored-By: Claude Fable 5 <[email protected]>
The `with { ... }` clause has no AST node, so its closing brace was taken as
the first `}` after the last attribute. A `}` inside a comment was mistaken
for it, silently emitting broken code.
Co-Authored-By: Claude Fable 5 <[email protected]>
Returning null left the prefixed id unresolved, which the bundler then treated as an external import, silently producing a broken bundle. Co-Authored-By: Claude Fable 5 <[email protected]>
Virtual modules (`nitro.vfs`, other plugins) have no file behind them, so `load` read them from disk and failed with a bare ENOENT. Inline their rendered source instead. Co-Authored-By: Claude Fable 5 <[email protected]>
This PR adds support for the import bytes (stage 2.7) and import text (stage 3) proposals:
As in the proposals, the file type is ignored: any file can be imported as either bytes or text.
Implementation
Rollup and rolldown both parse the syntax, but neither implements the semantics, and rolldown drops import attributes entirely before they reach plugins (
resolveIdonly receives{ isEntry, kind }). An attribute-drivenresolveIdwould therefore only ever work on the rollup path.Instead, a new
nitro:import-attributesplugin pre-transforms the syntax into the existing raw import machinery, which gives a single code path for rollup, rolldown and vite, in both dev and prod:import b from "./f.bin" with { type: "bytes" }→import b from "bytes:./f.bin"import("./f.bin", { with: { type: "bytes" } })→import("bytes:./f.bin"), so the namespacedefaultis theUint8Arrayas specifiedexport ... from ... with { type: "..." }is handled by the same pathParsing (oxc) and sourcemaps (MagicString) come from
rolldown, already a direct dependency, so no new dependencies are added. Modules are only parsed when the attribute is actually present.The raw plugin gains
bytes:andtext:prefixes next to the existingraw:one:raw:infers from the file mime, whilebytes:/text:always yield aUint8Array/string(required, as the attributes ignore the file type).Bug fix
Resolved raw ids now carry a
.jssuffix. Without it, plugins matching the original extension claim the virtual module after the raw plugin has already turned it into JS: both@rollup/plugin-json(id.slice(-5) === ".json") and vite's json plugin (/\.(json|json5)($|\?)/) then fail withCould not parse JSON file. This also affected the existingraw:prefix for.jsonfiles.Tests
Regression tests in the main fixture (
/import-attributes), running against all presets:.sqlimported as bytes and.jsonimported as text (the type-ignored cases).txtfile dynamically imported as both bytes and textVerified against the rollup, rolldown and vite builders, dev, and the cloudflare preset.
Notes
TypeScript implements neither proposal and types these imports by file extension, so user code still needs
// @ts-ignoreor a cast. This is noted in the docs.🤖 Generated with Claude Code