Compare commits

...

1 Commits

Author SHA1 Message Date
Claude Bot
931b584820 feat: support WebAssembly import attributes with type "webassembly"
Changes bun to properly support WebAssembly imports using the web standard
"webassembly" type attribute instead of the non-standard "wasm" type.

## Changes

- Modified js_printer.zig to output `type: "webassembly"` for .wasm files
  (following web standards)
- Added "webassembly" -> .wasm mapping to options.zig to support both forms
- Restructured import attribute handling to always preserve web standard
  attributes (css, json, webassembly) while only preserving Bun-specific
  attributes when on Bun platform
- Added comprehensive tests for WebAssembly import attribute behavior

## Before
```javascript
import module from './module.wasm' with { type: 'webassembly' };
// Transpiled to:
import module from './module.wasm'; //  attribute stripped
```

## After
```javascript
import module from './module.wasm' with { type: 'webassembly' };
// Transpiled to:
import module from './module.wasm' with { type: 'webassembly' }; //  preserved
```

This ensures full compliance with WebAssembly ES module integration spec
while maintaining backward compatibility.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-08-15 00:35:15 +00:00
3 changed files with 114 additions and 17 deletions

View File

@@ -4460,25 +4460,30 @@ fn NewPrinter(
p.printImportRecordPath(record);
// backwards compatibility: previously, we always stripped type
if (comptime is_bun_platform) if (record.loader) |loader| switch (loader) {
.jsx => p.printWhitespacer(ws(" with { type: \"jsx\" }")),
.js => p.printWhitespacer(ws(" with { type: \"js\" }")),
.ts => p.printWhitespacer(ws(" with { type: \"ts\" }")),
.tsx => p.printWhitespacer(ws(" with { type: \"tsx\" }")),
if (record.loader) |loader| switch (loader) {
// Always preserve web standard import attributes
.css => p.printWhitespacer(ws(" with { type: \"css\" }")),
.file => p.printWhitespacer(ws(" with { type: \"file\" }")),
.json => p.printWhitespacer(ws(" with { type: \"json\" }")),
.jsonc => p.printWhitespacer(ws(" with { type: \"jsonc\" }")),
.toml => p.printWhitespacer(ws(" with { type: \"toml\" }")),
.wasm => p.printWhitespacer(ws(" with { type: \"wasm\" }")),
.napi => p.printWhitespacer(ws(" with { type: \"napi\" }")),
.base64 => p.printWhitespacer(ws(" with { type: \"base64\" }")),
.dataurl => p.printWhitespacer(ws(" with { type: \"dataurl\" }")),
.text => p.printWhitespacer(ws(" with { type: \"text\" }")),
.bunsh => p.printWhitespacer(ws(" with { type: \"sh\" }")),
// sqlite_embedded only relevant when bundling
.sqlite, .sqlite_embedded => p.printWhitespacer(ws(" with { type: \"sqlite\" }")),
.html => p.printWhitespacer(ws(" with { type: \"html\" }")),
.wasm => p.printWhitespacer(ws(" with { type: \"webassembly\" }")),
// Only preserve Bun-specific loaders when on Bun platform
else => if (comptime is_bun_platform) switch (loader) {
.jsx => p.printWhitespacer(ws(" with { type: \"jsx\" }")),
.js => p.printWhitespacer(ws(" with { type: \"js\" }")),
.ts => p.printWhitespacer(ws(" with { type: \"ts\" }")),
.tsx => p.printWhitespacer(ws(" with { type: \"tsx\" }")),
.file => p.printWhitespacer(ws(" with { type: \"file\" }")),
.jsonc => p.printWhitespacer(ws(" with { type: \"jsonc\" }")),
.toml => p.printWhitespacer(ws(" with { type: \"toml\" }")),
.napi => p.printWhitespacer(ws(" with { type: \"napi\" }")),
.base64 => p.printWhitespacer(ws(" with { type: \"base64\" }")),
.dataurl => p.printWhitespacer(ws(" with { type: \"dataurl\" }")),
.text => p.printWhitespacer(ws(" with { type: \"text\" }")),
.bunsh => p.printWhitespacer(ws(" with { type: \"sh\" }")),
// sqlite_embedded only relevant when bundling
.sqlite, .sqlite_embedded => p.printWhitespacer(ws(" with { type: \"sqlite\" }")),
.html => p.printWhitespacer(ws(" with { type: \"html\" }")),
.css, .json, .wasm => unreachable, // handled above
},
};
p.printSemicolonAfterStatement();
},

View File

@@ -770,6 +770,7 @@ pub const Loader = enum(u8) {
.{ "jsonc", .jsonc },
.{ "toml", .toml },
.{ "wasm", .wasm },
.{ "webassembly", .wasm },
.{ "napi", .napi },
.{ "node", .napi },
.{ "dataurl", .dataurl },

View File

@@ -0,0 +1,91 @@
import { describe, expect, test } from "bun:test";
// Tests for WebAssembly import attributes support
// Ensures bun supports standard "webassembly" import attributes per ES spec
describe("WebAssembly import attributes support", () => {
test("WebAssembly import attributes should be preserved", () => {
const transpiler = new Bun.Transpiler({ loader: "js" });
const input = `import module from "./module.wasm" with { type: "webassembly" };
console.log(module);`;
const result = transpiler.transformSync(input, "js");
// WebAssembly import attributes should be preserved (web standard)
expect(result).toContain('with { type: "webassembly" }');
expect(result).toContain('import module from "./module.wasm" with { type: "webassembly" };');
});
test("Legacy 'wasm' type should still work but output standard 'webassembly'", () => {
const transpiler = new Bun.Transpiler({ loader: "js" });
const input = `import module from "./module.wasm" with { type: "wasm" };
console.log(module);`;
const result = transpiler.transformSync(input, "js");
// Should normalize to standard webassembly attribute
expect(result).toContain('with { type: "webassembly" }');
expect(result).not.toContain('with { type: "wasm" }');
});
test("WebAssembly with other web standard attributes", () => {
const transpiler = new Bun.Transpiler({ loader: "js" });
const input = `import styles from "./styles.css" with { type: "css" };
import config from "./config.json" with { type: "json" };
import wasmModule from "./module.wasm" with { type: "webassembly" };
document.adoptedStyleSheets = [styles];
console.log(config, wasmModule);`;
const result = transpiler.transformSync(input, "js");
// All web standard attributes preserved
expect(result).toContain('with { type: "css" }');
expect(result).toContain('with { type: "json" }');
expect(result).toContain('with { type: "webassembly" }');
// All imports still present
expect(result).toContain('import styles from "./styles.css"');
expect(result).toContain('import config from "./config.json"');
expect(result).toContain('import wasmModule from "./module.wasm"');
});
test("WebAssembly vs Bun-specific attributes behavior", () => {
const transpiler = new Bun.Transpiler({ loader: "js" });
const input = `import wasmModule from "./module.wasm" with { type: "webassembly" };
import data from "./data.toml" with { type: "toml" };
console.log(wasmModule, data);`;
const result = transpiler.transformSync(input, "js");
// WebAssembly is web standard - should be preserved
expect(result).toContain('with { type: "webassembly" }');
// TOML is Bun-specific - should be stripped in transpiler mode
expect(result).not.toContain('with { type: "toml" }');
// Both imports still present
expect(result).toContain('import wasmModule from "./module.wasm"');
expect(result).toContain('import data from "./data.toml"');
});
test("Multiple WebAssembly imports work correctly", () => {
const transpiler = new Bun.Transpiler({ loader: "js" });
const input = `import mathModule from "./math.wasm" with { type: "webassembly" };
import cryptoModule from "./crypto.wasm" with { type: "webassembly" };
import imageModule from "./image.wasm" with { type: "webassembly" };
console.log(mathModule, cryptoModule, imageModule);`;
const result = transpiler.transformSync(input, "js");
// All WebAssembly imports should preserve attributes
const wasmMatches = result.match(/with \{ type: "webassembly" \}/g);
expect(wasmMatches).toHaveLength(3);
expect(result).toContain('import mathModule from "./math.wasm" with { type: "webassembly" };');
expect(result).toContain('import cryptoModule from "./crypto.wasm" with { type: "webassembly" };');
expect(result).toContain('import imageModule from "./image.wasm" with { type: "webassembly" };');
});
});