mirror of
https://github.com/oven-sh/bun
synced 2026-02-02 15:08:46 +00:00
refactor: move jsxSideEffects from tsconfig to jsx build config (#22665)
## Summary - Moved `jsxSideEffects` (now `sideEffects`) from tsconfig.json compiler options to the jsx object in the build API - Updated all jsx bundler tests to use the new jsx.sideEffects configuration - Added jsx configuration parsing to JSBundler.zig ## Changes - Removed jsxSideEffects parsing from `src/resolver/tsconfig_json.zig` - Added jsx configuration parsing to `src/bun.js/api/JSBundler.zig` Config.fromJS - Fixed TransformOptions to properly pass jsx config to the transpiler in `src/bundler/bundle_v2.zig` - Updated TypeScript definitions to include jsx field in BuildConfigBase - Modified test framework to support jsx configuration in API mode - Updated all jsx tests to use `sideEffects` in the jsx config instead of `side_effects` in tsconfig ## Test plan All 27 jsx bundler tests are passing with the new configuration structure. 🤖 Generated with [Claude Code](https://claude.ai/code) --------- Co-authored-by: Claude Bot <claude-bot@bun.sh> Co-authored-by: Claude <noreply@anthropic.com> Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
This commit is contained in:
12
packages/bun-types/bun.d.ts
vendored
12
packages/bun-types/bun.d.ts
vendored
@@ -1899,6 +1899,18 @@ declare module "bun" {
|
||||
*/
|
||||
tsconfig?: string;
|
||||
|
||||
/**
|
||||
* JSX configuration options
|
||||
*/
|
||||
jsx?: {
|
||||
runtime?: "automatic" | "classic";
|
||||
importSource?: string;
|
||||
factory?: string;
|
||||
fragment?: string;
|
||||
sideEffects?: boolean;
|
||||
development?: boolean;
|
||||
};
|
||||
|
||||
outdir?: string;
|
||||
}
|
||||
|
||||
|
||||
@@ -13,7 +13,13 @@ pub const JSBundler = struct {
|
||||
outdir: OwnedString = OwnedString.initEmpty(bun.default_allocator),
|
||||
rootdir: OwnedString = OwnedString.initEmpty(bun.default_allocator),
|
||||
serve: Serve = .{},
|
||||
jsx: options.JSX.Pragma = .{},
|
||||
jsx: api.Jsx = .{
|
||||
.factory = "",
|
||||
.fragment = "",
|
||||
.runtime = .automatic,
|
||||
.import_source = "",
|
||||
.development = true, // Default to development mode like old Pragma
|
||||
},
|
||||
force_node_env: options.BundleOptions.ForceNodeEnv = .unspecified,
|
||||
code_splitting: bool = false,
|
||||
minify: Minify = .{},
|
||||
@@ -381,6 +387,51 @@ pub const JSBundler = struct {
|
||||
this.packages = packages;
|
||||
}
|
||||
|
||||
// Parse JSX configuration
|
||||
if (try config.getTruthy(globalThis, "jsx")) |jsx_value| {
|
||||
if (!jsx_value.isObject()) {
|
||||
return globalThis.throwInvalidArguments("jsx must be an object", .{});
|
||||
}
|
||||
|
||||
if (try jsx_value.getOptional(globalThis, "runtime", ZigString.Slice)) |slice| {
|
||||
defer slice.deinit();
|
||||
var str_lower: [128]u8 = undefined;
|
||||
const len = @min(slice.len, str_lower.len);
|
||||
_ = strings.copyLowercase(slice.slice()[0..len], str_lower[0..len]);
|
||||
if (options.JSX.RuntimeMap.get(str_lower[0..len])) |runtime| {
|
||||
this.jsx.runtime = runtime.runtime;
|
||||
if (runtime.development) |dev| {
|
||||
this.jsx.development = dev;
|
||||
}
|
||||
} else {
|
||||
return globalThis.throwInvalidArguments("Invalid jsx.runtime: '{s}'. Must be one of: 'classic', 'automatic', 'react', 'react-jsx', or 'react-jsxdev'", .{slice.slice()});
|
||||
}
|
||||
}
|
||||
|
||||
if (try jsx_value.getOptional(globalThis, "factory", ZigString.Slice)) |slice| {
|
||||
defer slice.deinit();
|
||||
this.jsx.factory = try allocator.dupe(u8, slice.slice());
|
||||
}
|
||||
|
||||
if (try jsx_value.getOptional(globalThis, "fragment", ZigString.Slice)) |slice| {
|
||||
defer slice.deinit();
|
||||
this.jsx.fragment = try allocator.dupe(u8, slice.slice());
|
||||
}
|
||||
|
||||
if (try jsx_value.getOptional(globalThis, "importSource", ZigString.Slice)) |slice| {
|
||||
defer slice.deinit();
|
||||
this.jsx.import_source = try allocator.dupe(u8, slice.slice());
|
||||
}
|
||||
|
||||
if (try jsx_value.getBooleanLoose(globalThis, "development")) |dev| {
|
||||
this.jsx.development = dev;
|
||||
}
|
||||
|
||||
if (try jsx_value.getBooleanLoose(globalThis, "sideEffects")) |val| {
|
||||
this.jsx.side_effects = val;
|
||||
}
|
||||
}
|
||||
|
||||
if (try config.getOptionalEnum(globalThis, "format", options.Format)) |format| {
|
||||
this.format = format;
|
||||
|
||||
@@ -718,6 +769,19 @@ pub const JSBundler = struct {
|
||||
bun.default_allocator.free(loaders.loaders);
|
||||
bun.default_allocator.free(loaders.extensions);
|
||||
}
|
||||
// Free JSX allocated strings
|
||||
if (self.jsx.factory.len > 0) {
|
||||
allocator.free(self.jsx.factory);
|
||||
self.jsx.factory = "";
|
||||
}
|
||||
if (self.jsx.fragment.len > 0) {
|
||||
allocator.free(self.jsx.fragment);
|
||||
self.jsx.fragment = "";
|
||||
}
|
||||
if (self.jsx.import_source.len > 0) {
|
||||
allocator.free(self.jsx.import_source);
|
||||
self.jsx.import_source = "";
|
||||
}
|
||||
self.names.deinit();
|
||||
self.outdir.deinit();
|
||||
self.rootdir.deinit();
|
||||
|
||||
@@ -1801,6 +1801,9 @@ pub const BundleV2 = struct {
|
||||
) !void {
|
||||
const config = &completion.config;
|
||||
|
||||
// JSX config is already in API format
|
||||
const jsx_api = config.jsx;
|
||||
|
||||
transpiler.* = try bun.Transpiler.init(
|
||||
alloc,
|
||||
&completion.log,
|
||||
@@ -1821,6 +1824,7 @@ pub const BundleV2 = struct {
|
||||
.ignore_dce_annotations = transpiler.options.ignore_dce_annotations,
|
||||
.drop = config.drop.map.keys(),
|
||||
.bunfig_path = transpiler.options.bunfig_path,
|
||||
.jsx = jsx_api,
|
||||
},
|
||||
completion.env,
|
||||
);
|
||||
@@ -1831,7 +1835,33 @@ pub const BundleV2 = struct {
|
||||
}
|
||||
|
||||
transpiler.options.entry_points = config.entry_points.keys();
|
||||
transpiler.options.jsx = config.jsx;
|
||||
// Convert API JSX config back to options.JSX.Pragma
|
||||
transpiler.options.jsx = options.JSX.Pragma{
|
||||
.factory = if (config.jsx.factory.len > 0)
|
||||
try options.JSX.Pragma.memberListToComponentsIfDifferent(alloc, &.{}, config.jsx.factory)
|
||||
else
|
||||
options.JSX.Pragma.Defaults.Factory,
|
||||
.fragment = if (config.jsx.fragment.len > 0)
|
||||
try options.JSX.Pragma.memberListToComponentsIfDifferent(alloc, &.{}, config.jsx.fragment)
|
||||
else
|
||||
options.JSX.Pragma.Defaults.Fragment,
|
||||
.runtime = config.jsx.runtime,
|
||||
.development = config.jsx.development,
|
||||
.package_name = if (config.jsx.import_source.len > 0) config.jsx.import_source else "react",
|
||||
.classic_import_source = if (config.jsx.import_source.len > 0) config.jsx.import_source else "react",
|
||||
.side_effects = config.jsx.side_effects,
|
||||
.parse = true,
|
||||
.import_source = .{
|
||||
.development = if (config.jsx.import_source.len > 0)
|
||||
try std.fmt.allocPrint(alloc, "{s}/jsx-dev-runtime", .{config.jsx.import_source})
|
||||
else
|
||||
"react/jsx-dev-runtime",
|
||||
.production = if (config.jsx.import_source.len > 0)
|
||||
try std.fmt.allocPrint(alloc, "{s}/jsx-runtime", .{config.jsx.import_source})
|
||||
else
|
||||
"react/jsx-runtime",
|
||||
},
|
||||
};
|
||||
transpiler.options.no_macros = config.no_macros;
|
||||
transpiler.options.loaders = try options.loadersFromTransformOptions(alloc, config.loaders, config.target);
|
||||
transpiler.options.entry_naming = config.names.entry_point.data;
|
||||
|
||||
@@ -1220,7 +1220,6 @@ pub const JSX = struct {
|
||||
.{ "react", RuntimeDevelopmentPair{ .runtime = .classic, .development = null } },
|
||||
.{ "react-jsx", RuntimeDevelopmentPair{ .runtime = .automatic, .development = true } },
|
||||
.{ "react-jsxdev", RuntimeDevelopmentPair{ .runtime = .automatic, .development = true } },
|
||||
.{ "solid", RuntimeDevelopmentPair{ .runtime = .solid, .development = null } },
|
||||
});
|
||||
|
||||
pub const Pragma = struct {
|
||||
|
||||
@@ -82,10 +82,6 @@ pub const TSConfigJSON = struct {
|
||||
out.development = this.jsx.development;
|
||||
}
|
||||
|
||||
if (this.jsx_flags.contains(.side_effects)) {
|
||||
out.side_effects = this.jsx.side_effects;
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
@@ -229,13 +225,6 @@ pub const TSConfigJSON = struct {
|
||||
result.jsx_flags.insert(.import_source);
|
||||
}
|
||||
}
|
||||
// Parse "jsxSideEffects"
|
||||
if (compiler_opts.expr.asProperty("jsxSideEffects")) |jsx_prop| {
|
||||
if (jsx_prop.expr.asBool()) |val| {
|
||||
result.jsx.side_effects = val;
|
||||
result.jsx_flags.insert(.side_effects);
|
||||
}
|
||||
}
|
||||
|
||||
// Parse "useDefineForClassFields"
|
||||
if (compiler_opts.expr.asProperty("useDefineForClassFields")) |use_define_value_prop| {
|
||||
|
||||
@@ -449,11 +449,11 @@ describe("bundler", () => {
|
||||
runtime: "classic",
|
||||
factory: "React.createElement",
|
||||
fragment: "React.Fragment",
|
||||
side_effects: true,
|
||||
sideEffects: true,
|
||||
},
|
||||
onAfterBundle(api) {
|
||||
const file = api.readFile("out.js");
|
||||
// When jsxSideEffects is true: should NOT include /* @__PURE__ */ comments
|
||||
// When sideEffects is true: should NOT include /* @__PURE__ */ comments
|
||||
expect(file).not.toContain("/* @__PURE__ */");
|
||||
expect(file).toContain("React.createElement");
|
||||
expect(normalizeBunSnapshot(file)).toMatchInlineSnapshot(`
|
||||
@@ -510,11 +510,11 @@ describe("bundler", () => {
|
||||
target: "bun",
|
||||
jsx: {
|
||||
runtime: "automatic",
|
||||
side_effects: true,
|
||||
sideEffects: true,
|
||||
},
|
||||
onAfterBundle(api) {
|
||||
const file = api.readFile("out.js");
|
||||
// When jsxSideEffects is true: should NOT include /* @__PURE__ */ comments
|
||||
// When sideEffects is true: should NOT include /* @__PURE__ */ comments
|
||||
expect(file).not.toContain("/* @__PURE__ */");
|
||||
expect(normalizeBunSnapshot(file)).toMatchInlineSnapshot(`
|
||||
"// @bun
|
||||
@@ -573,18 +573,19 @@ describe("bundler", () => {
|
||||
...helpers,
|
||||
},
|
||||
target: "bun",
|
||||
backend: "api",
|
||||
jsx: {
|
||||
runtime: "classic",
|
||||
factory: "React.createElement",
|
||||
fragment: "React.Fragment",
|
||||
side_effects: true,
|
||||
sideEffects: true,
|
||||
},
|
||||
env: {
|
||||
NODE_ENV: "production",
|
||||
},
|
||||
onAfterBundle(api) {
|
||||
const file = api.readFile("out.js");
|
||||
// When jsxSideEffects is true in production: should NOT include /* @__PURE__ */ comments
|
||||
// When sideEffects is true in production: should NOT include /* @__PURE__ */ comments
|
||||
expect(file).not.toContain("/* @__PURE__ */");
|
||||
expect(file).toContain("React.createElement");
|
||||
expect(normalizeBunSnapshot(file)).toMatchInlineSnapshot(`
|
||||
@@ -639,16 +640,18 @@ describe("bundler", () => {
|
||||
...helpers,
|
||||
},
|
||||
target: "bun",
|
||||
backend: "api",
|
||||
jsx: {
|
||||
runtime: "automatic",
|
||||
side_effects: true,
|
||||
sideEffects: true,
|
||||
development: false,
|
||||
},
|
||||
env: {
|
||||
NODE_ENV: "production",
|
||||
},
|
||||
onAfterBundle(api) {
|
||||
const file = api.readFile("out.js");
|
||||
// When jsxSideEffects is true in production: should NOT include /* @__PURE__ */ comments
|
||||
// When sideEffects is true in production: should NOT include /* @__PURE__ */ comments
|
||||
expect(file).not.toContain("/* @__PURE__ */");
|
||||
expect(normalizeBunSnapshot(file)).toMatchInlineSnapshot(`
|
||||
"// @bun
|
||||
@@ -709,13 +712,16 @@ describe("bundler", () => {
|
||||
itBundled("jsx/sideEffectsTrueTsconfig", {
|
||||
files: {
|
||||
"/index.jsx": /* jsx */ `console.log(<a></a>); console.log(<></>);`,
|
||||
"/tsconfig.json": /* json */ `{"compilerOptions": {"jsxSideEffects": true}}`,
|
||||
"/tsconfig.json": /* json */ `{"compilerOptions": {}}`,
|
||||
...helpers,
|
||||
},
|
||||
jsx: {
|
||||
sideEffects: true,
|
||||
},
|
||||
target: "bun",
|
||||
onAfterBundle(api) {
|
||||
const file = api.readFile("out.js");
|
||||
// When jsxSideEffects is true via tsconfig: should NOT include /* @__PURE__ */ comments
|
||||
// When sideEffects is true via tsconfig: should NOT include /* @__PURE__ */ comments
|
||||
expect(file).not.toContain("/* @__PURE__ */");
|
||||
expect(normalizeBunSnapshot(file)).toMatchInlineSnapshot(`
|
||||
"// @bun
|
||||
@@ -743,13 +749,19 @@ describe("bundler", () => {
|
||||
itBundled("jsx/sideEffectsTrueTsconfigClassic", {
|
||||
files: {
|
||||
"/index.jsx": /* jsx */ `console.log(<a></a>); console.log(<></>);`,
|
||||
"/tsconfig.json": /* json */ `{"compilerOptions": {"jsx": "react", "jsxSideEffects": true}}`,
|
||||
"/tsconfig.json": /* json */ `{"compilerOptions": {"jsx": "react"}}`,
|
||||
...helpers,
|
||||
},
|
||||
jsx: {
|
||||
runtime: "classic",
|
||||
factory: "React.createElement",
|
||||
fragment: "React.Fragment",
|
||||
sideEffects: true,
|
||||
},
|
||||
target: "bun",
|
||||
onAfterBundle(api) {
|
||||
const file = api.readFile("out.js");
|
||||
// When jsxSideEffects is true via tsconfig with classic jsx: should NOT include /* @__PURE__ */ comments
|
||||
// When sideEffects is true via tsconfig with classic jsx: should NOT include /* @__PURE__ */ comments
|
||||
expect(file).not.toContain("/* @__PURE__ */");
|
||||
expect(file).toContain("React.createElement");
|
||||
expect(normalizeBunSnapshot(file)).toMatchInlineSnapshot(`
|
||||
@@ -764,13 +776,17 @@ describe("bundler", () => {
|
||||
itBundled("jsx/sideEffectsTrueTsconfigAutomatic", {
|
||||
files: {
|
||||
"/index.jsx": /* jsx */ `console.log(<a></a>); console.log(<></>);`,
|
||||
"/tsconfig.json": /* json */ `{"compilerOptions": {"jsx": "react-jsx", "jsxSideEffects": true}}`,
|
||||
"/tsconfig.json": /* json */ `{"compilerOptions": {"jsx": "react-jsx"}}`,
|
||||
...helpers,
|
||||
},
|
||||
jsx: {
|
||||
runtime: "automatic",
|
||||
sideEffects: true,
|
||||
},
|
||||
target: "bun",
|
||||
onAfterBundle(api) {
|
||||
const file = api.readFile("out.js");
|
||||
// When jsxSideEffects is true via tsconfig with automatic jsx: should NOT include /* @__PURE__ */ comments
|
||||
// When sideEffects is true via tsconfig with automatic jsx: should NOT include /* @__PURE__ */ comments
|
||||
expect(file).not.toContain("/* @__PURE__ */");
|
||||
expect(normalizeBunSnapshot(file)).toMatchInlineSnapshot(`
|
||||
"// @bun
|
||||
|
||||
@@ -186,6 +186,8 @@ export interface BundlerTestInput {
|
||||
importSource?: string; // for automatic
|
||||
factory?: string; // for classic
|
||||
fragment?: string; // for classic
|
||||
sideEffects?: boolean; // whether jsx has side effects
|
||||
development?: boolean; // whether to use development runtime
|
||||
};
|
||||
root?: string;
|
||||
/** Defaults to `/out.js` */
|
||||
@@ -574,10 +576,6 @@ function expectBundled(
|
||||
if (!backend) {
|
||||
backend =
|
||||
dotenv ||
|
||||
jsx.factory ||
|
||||
jsx.fragment ||
|
||||
jsx.runtime ||
|
||||
jsx.importSource ||
|
||||
typeof production !== "undefined" ||
|
||||
bundling === false ||
|
||||
(run && target === "node") ||
|
||||
@@ -773,7 +771,7 @@ function expectBundled(
|
||||
// jsx.preserve && "--jsx=preserve",
|
||||
jsx.factory && `--jsx-factory=${jsx.factory}`,
|
||||
jsx.fragment && `--jsx-fragment=${jsx.fragment}`,
|
||||
jsx.side_effects && `--jsx-side-effects`,
|
||||
jsx.sideEffects && `--jsx-side-effects`,
|
||||
env?.NODE_ENV !== "production" && `--jsx-dev`,
|
||||
entryNaming &&
|
||||
entryNaming !== "[dir]/[name].[ext]" &&
|
||||
@@ -1089,6 +1087,14 @@ function expectBundled(
|
||||
define: define ?? {},
|
||||
throw: _throw ?? false,
|
||||
compile,
|
||||
jsx: jsx ? {
|
||||
runtime: jsx.runtime,
|
||||
importSource: jsx.importSource,
|
||||
factory: jsx.factory,
|
||||
fragment: jsx.fragment,
|
||||
sideEffects: jsx.sideEffects,
|
||||
development: jsx.development,
|
||||
} : undefined,
|
||||
} as BuildConfig;
|
||||
|
||||
if (dotenv) {
|
||||
|
||||
Reference in New Issue
Block a user