From f6c53185606420fcbd4e910ebcf1759cda51289a Mon Sep 17 00:00:00 2001 From: robobun Date: Mon, 1 Sep 2025 02:35:55 -0700 Subject: [PATCH] Implement jsxSideEffects option for JSX dead code elimination control (#22298) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary Implements the `jsxSideEffects` option to control whether JSX elements are marked as pure for dead code elimination, matching esbuild's behavior from their TestJSXSideEffects test case. ## Features Added - **tsconfig.json support**: `{"compilerOptions": {"jsxSideEffects": true}}` - **CLI flag support**: `--jsx-side-effects` - **Dual runtime support**: Works with both classic (`React.createElement`) and automatic (`jsx`/`jsxs`) JSX runtimes - **Production/Development modes**: Works in both production and development environments - **Backward compatible**: Default value is `false` (maintains existing behavior) ## Behavior - **Default (`jsxSideEffects: false`)**: JSX elements marked with `/* @__PURE__ */` comments (can be eliminated by bundlers) - **When `jsxSideEffects: true`**: JSX elements NOT marked as pure (always preserved) ## Example Usage ### tsconfig.json ```json { "compilerOptions": { "jsxSideEffects": true } } ``` ### CLI ```bash bun build --jsx-side-effects ``` ### Output Comparison ```javascript // Input: console.log(
test
); // Default (jsxSideEffects: false): console.log(/* @__PURE__ */ React.createElement("div", null, "test")); // With jsxSideEffects: true: console.log(React.createElement("div", null, "test")); ``` ## Implementation Details - Added `side_effects: bool = false` field to `JSX.Pragma` struct - Updated tsconfig.json parser to handle `jsxSideEffects` option - Added CLI argument parsing for `--jsx-side-effects` flag - Modified JSX element visiting logic to respect the `side_effects` setting - Updated API schema with proper encode/decode support - Enhanced test framework to support the new JSX option ## Comprehensive Test Coverage (12 Tests) ### Core Functionality (4 tests) - ✅ Classic JSX runtime with default behavior (includes `/* @__PURE__ */`) - ✅ Classic JSX runtime with `side_effects: true` (no `/* @__PURE__ */`) - ✅ Automatic JSX runtime with default behavior (includes `/* @__PURE__ */`) - ✅ Automatic JSX runtime with `side_effects: true` (no `/* @__PURE__ */`) ### Production Mode (4 tests) - ✅ Classic JSX runtime in production with default behavior - ✅ Classic JSX runtime in production with `side_effects: true` - ✅ Automatic JSX runtime in production with default behavior - ✅ Automatic JSX runtime in production with `side_effects: true` ### tsconfig.json Integration (4 tests) - ✅ Default tsconfig.json behavior (automatic runtime, includes `/* @__PURE__ */`) - ✅ tsconfig.json with `jsxSideEffects: true` (automatic runtime, no `/* @__PURE__ */`) - ✅ tsconfig.json with `jsx: "react"` and `jsxSideEffects: true` (classic runtime) - ✅ tsconfig.json with `jsx: "react-jsx"` and `jsxSideEffects: true` (automatic runtime) ### Snapshot Testing All tests include inline snapshots demonstrating the exact output differences, providing clear documentation of the expected behavior. ### Existing Compatibility - ✅ All existing JSX tests continue to pass - ✅ Cross-platform Zig compilation succeeds ## Closes Fixes #22295 🤖 Generated with [Claude Code](https://claude.ai/code) --------- Co-authored-by: Claude Bot Co-authored-by: Claude Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com> --- docs/bundler/vs-esbuild.md | 6 +- docs/runtime/jsx.md | 59 +++++ src/api/schema.zig | 5 + src/ast/visitExpr.zig | 4 +- src/cli/Arguments.zig | 4 + src/options.zig | 2 + src/resolver/tsconfig_json.zig | 11 + test/bundler/bundler_jsx.test.ts | 384 +++++++++++++++++++++++++++++++ test/bundler/expectBundled.ts | 2 + 9 files changed, 472 insertions(+), 5 deletions(-) diff --git a/docs/bundler/vs-esbuild.md b/docs/bundler/vs-esbuild.md index 2a0dcd9a12..1387950d61 100644 --- a/docs/bundler/vs-esbuild.md +++ b/docs/bundler/vs-esbuild.md @@ -245,8 +245,8 @@ In Bun's CLI, simple boolean flags like `--minify` do not accept an argument. Ot --- - `--jsx-side-effects` -- n/a -- JSX is always assumed to be side-effect-free +- `--jsx-side-effects` +- Controls whether JSX expressions are marked as `/* @__PURE__ */` for dead code elimination. Default is `false` (JSX marked as pure). --- @@ -617,7 +617,7 @@ In Bun's CLI, simple boolean flags like `--minify` do not accept an argument. Ot - `jsxSideEffects` - `jsxSideEffects` -- Not supported in JS API, configure in `tsconfig.json` +- Controls whether JSX expressions are marked as pure for dead code elimination --- diff --git a/docs/runtime/jsx.md b/docs/runtime/jsx.md index f5ad0dc271..abf611b8bb 100644 --- a/docs/runtime/jsx.md +++ b/docs/runtime/jsx.md @@ -246,6 +246,65 @@ The module from which the component factory function (`createElement`, `jsx`, `j {% /table %} +### `jsxSideEffects` + +By default, Bun marks JSX expressions as `/* @__PURE__ */` so they can be removed during bundling if they are unused (known as "dead code elimination" or "tree shaking"). Set `jsxSideEffects` to `true` to prevent this behavior. + +{% table %} + +- Compiler options +- Transpiled output + +--- + +- ```jsonc + { + "jsx": "react", + // jsxSideEffects is false by default + } + ``` + +- ```tsx + // JSX expressions are marked as pure + /* @__PURE__ */ React.createElement("div", null, "Hello"); + ``` + +--- + +- ```jsonc + { + "jsx": "react", + "jsxSideEffects": true, + } + ``` + +- ```tsx + // JSX expressions are not marked as pure + React.createElement("div", null, "Hello"); + ``` + +--- + +- ```jsonc + { + "jsx": "react-jsx", + "jsxSideEffects": true, + } + ``` + +- ```tsx + // Automatic runtime also respects jsxSideEffects + jsx("div", { children: "Hello" }); + ``` + +{% /table %} + +This option is also available as a CLI flag: + +```bash +$ bun build --jsx-side-effects +``` + ### JSX pragma All of these values can be set on a per-file basis using _pragmas_. A pragma is a special comment that sets a compiler option in a particular file. diff --git a/src/api/schema.zig b/src/api/schema.zig index 3da966b9c7..995ad25254 100644 --- a/src/api/schema.zig +++ b/src/api/schema.zig @@ -799,6 +799,9 @@ pub const api = struct { /// import_source import_source: []const u8, + /// side_effects + side_effects: bool = false, + pub fn decode(reader: anytype) anyerror!Jsx { var this = std.mem.zeroes(Jsx); @@ -807,6 +810,7 @@ pub const api = struct { this.fragment = try reader.readValue([]const u8); this.development = try reader.readValue(bool); this.import_source = try reader.readValue([]const u8); + this.side_effects = try reader.readValue(bool); return this; } @@ -816,6 +820,7 @@ pub const api = struct { try writer.writeValue(@TypeOf(this.fragment), this.fragment); try writer.writeInt(@as(u8, @intFromBool(this.development))); try writer.writeValue(@TypeOf(this.import_source), this.import_source); + try writer.writeInt(@as(u8, @intFromBool(this.side_effects))); } }; diff --git a/src/ast/visitExpr.zig b/src/ast/visitExpr.zig index 4c0f42fd56..d77ae9fe31 100644 --- a/src/ast/visitExpr.zig +++ b/src/ast/visitExpr.zig @@ -257,7 +257,7 @@ pub fn VisitExpr( .target = if (runtime == .classic) target else p.jsxImport(.createElement, expr.loc), .args = ExprNodeList.init(args[0..i]), // Enable tree shaking - .can_be_unwrapped_if_unused = if (!p.options.ignore_dce_annotations) .if_unused else .never, + .can_be_unwrapped_if_unused = if (!p.options.ignore_dce_annotations and !p.options.jsx.side_effects) .if_unused else .never, .close_paren_loc = e_.close_tag_loc, }, expr.loc); } @@ -362,7 +362,7 @@ pub fn VisitExpr( .target = p.jsxImportAutomatic(expr.loc, is_static_jsx), .args = ExprNodeList.init(args), // Enable tree shaking - .can_be_unwrapped_if_unused = if (!p.options.ignore_dce_annotations) .if_unused else .never, + .can_be_unwrapped_if_unused = if (!p.options.ignore_dce_annotations and !p.options.jsx.side_effects) .if_unused else .never, .was_jsx_element = true, .close_paren_loc = e_.close_tag_loc, }, expr.loc); diff --git a/src/cli/Arguments.zig b/src/cli/Arguments.zig index 48d5669523..ed5ac1be7c 100644 --- a/src/cli/Arguments.zig +++ b/src/cli/Arguments.zig @@ -71,6 +71,7 @@ pub const transpiler_params_ = [_]ParamType{ clap.parseParam("--jsx-fragment Changes the function called when compiling JSX fragments") catch unreachable, clap.parseParam("--jsx-import-source Declares the module specifier to be used for importing the jsx and jsxs factory functions. Default: \"react\"") catch unreachable, clap.parseParam("--jsx-runtime \"automatic\" (default) or \"classic\"") catch unreachable, + clap.parseParam("--jsx-side-effects Treat JSX elements as having side effects (disable pure annotations)") catch unreachable, clap.parseParam("--ignore-dce-annotations Ignore tree-shaking annotations such as @__PURE__") catch unreachable, }; pub const runtime_params_ = [_]ParamType{ @@ -1120,6 +1121,7 @@ pub fn parse(allocator: std.mem.Allocator, ctx: Command.Context, comptime cmd: C const jsx_fragment = args.option("--jsx-fragment"); const jsx_import_source = args.option("--jsx-import-source"); const jsx_runtime = args.option("--jsx-runtime"); + const jsx_side_effects = args.flag("--jsx-side-effects"); if (cmd == .AutoCommand or cmd == .RunCommand) { // "run.silent" in bunfig.toml @@ -1166,6 +1168,7 @@ pub fn parse(allocator: std.mem.Allocator, ctx: Command.Context, comptime cmd: C .import_source = (jsx_import_source orelse &default_import_source), .runtime = if (jsx_runtime) |runtime| try resolve_jsx_runtime(runtime) else Api.JsxRuntime.automatic, .development = false, + .side_effects = jsx_side_effects, }; } else { opts.jsx = Api.Jsx{ @@ -1174,6 +1177,7 @@ pub fn parse(allocator: std.mem.Allocator, ctx: Command.Context, comptime cmd: C .import_source = (jsx_import_source orelse opts.jsx.?.import_source), .runtime = if (jsx_runtime) |runtime| try resolve_jsx_runtime(runtime) else opts.jsx.?.runtime, .development = false, + .side_effects = jsx_side_effects, }; } } diff --git a/src/options.zig b/src/options.zig index 826a45467a..66e33273d9 100644 --- a/src/options.zig +++ b/src/options.zig @@ -1242,6 +1242,7 @@ pub const JSX = struct { /// - tsconfig.json's `compilerOptions.jsx` (`react-jsx` or `react-jsxdev`) development: bool = true, parse: bool = true, + side_effects: bool = false, pub const ImportSource = struct { development: string = "react/jsx-dev-runtime", @@ -1380,6 +1381,7 @@ pub const JSX = struct { } pragma.runtime = jsx.runtime; + pragma.side_effects = jsx.side_effects; if (jsx.import_source.len > 0) { pragma.package_name = jsx.import_source; diff --git a/src/resolver/tsconfig_json.zig b/src/resolver/tsconfig_json.zig index 89b8c1608f..39ab047c78 100644 --- a/src/resolver/tsconfig_json.zig +++ b/src/resolver/tsconfig_json.zig @@ -83,6 +83,10 @@ 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; } @@ -226,6 +230,13 @@ 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| { diff --git a/test/bundler/bundler_jsx.test.ts b/test/bundler/bundler_jsx.test.ts index 8afaf3c991..a121dad40b 100644 --- a/test/bundler/bundler_jsx.test.ts +++ b/test/bundler/bundler_jsx.test.ts @@ -1,4 +1,5 @@ import { describe, expect } from "bun:test"; +import { normalizeBunSnapshot } from "harness"; import { BundlerTestInput, itBundled } from "./expectBundled"; const helpers = { @@ -411,4 +412,387 @@ describe("bundler", () => { stdout: `{\n $$typeof: Symbol(hello_jsxDEV),\n type: \"div\",\n props: {\n children: \"Hello World\",\n },\n key: undefined,\n}`, }, }); + + // Test for jsxSideEffects option - equivalent to esbuild's TestJSXSideEffects + describe("jsxSideEffects", () => { + itBundled("jsx/sideEffectsDefault", { + files: { + "/index.jsx": /* jsx */ `console.log(); console.log(<>);`, + ...helpers, + }, + target: "bun", + jsx: { + runtime: "classic", + factory: "React.createElement", + fragment: "React.Fragment", + }, + onAfterBundle(api) { + const file = api.readFile("out.js"); + // Default behavior: should include /* @__PURE__ */ comments + expect(file).toContain("/* @__PURE__ */"); + expect(normalizeBunSnapshot(file)).toMatchInlineSnapshot(` + "// @bun + // index.jsx + console.log(/* @__PURE__ */ React.createElement("a", null)); + console.log(/* @__PURE__ */ React.createElement(React.Fragment, null));" + `); + }, + }); + + itBundled("jsx/sideEffectsTrue", { + files: { + "/index.jsx": /* jsx */ `console.log(); console.log(<>);`, + ...helpers, + }, + target: "bun", + jsx: { + runtime: "classic", + factory: "React.createElement", + fragment: "React.Fragment", + side_effects: true, + }, + onAfterBundle(api) { + const file = api.readFile("out.js"); + // When jsxSideEffects is true: should NOT include /* @__PURE__ */ comments + expect(file).not.toContain("/* @__PURE__ */"); + expect(file).toContain("React.createElement"); + expect(normalizeBunSnapshot(file)).toMatchInlineSnapshot(` + "// @bun + // index.jsx + console.log(React.createElement("a", null)); + console.log(React.createElement(React.Fragment, null));" + `); + }, + }); + + // Test automatic JSX runtime with side effects + itBundled("jsx/sideEffectsDefaultAutomatic", { + files: { + "/index.jsx": /* jsx */ `console.log(); console.log(<>);`, + ...helpers, + }, + target: "bun", + jsx: { + runtime: "automatic", + }, + onAfterBundle(api) { + const file = api.readFile("out.js"); + // Default behavior: should include /* @__PURE__ */ comments + expect(file).toContain("/* @__PURE__ */"); + expect(normalizeBunSnapshot(file)).toMatchInlineSnapshot(` + "// @bun + // node_modules/react/jsx-dev-runtime.js + var $$typeof = Symbol.for("jsxdev"); + function jsxDEV(type, props, key, source, self) { + return { + $$typeof, + type, + props, + key, + source, + self + }; + } + var Fragment = Symbol.for("jsxdev.fragment"); + + // index.jsx + console.log(/* @__PURE__ */ jsxDEV("a", {}, undefined, false, undefined, this)); + console.log(/* @__PURE__ */ jsxDEV(Fragment, {}, undefined, false, undefined, this));" + `); + }, + }); + + itBundled("jsx/sideEffectsTrueAutomatic", { + files: { + "/index.jsx": /* jsx */ `console.log(); console.log(<>);`, + ...helpers, + }, + target: "bun", + jsx: { + runtime: "automatic", + side_effects: true, + }, + onAfterBundle(api) { + const file = api.readFile("out.js"); + // When jsxSideEffects is true: should NOT include /* @__PURE__ */ comments + expect(file).not.toContain("/* @__PURE__ */"); + expect(normalizeBunSnapshot(file)).toMatchInlineSnapshot(` + "// @bun + // node_modules/react/jsx-dev-runtime.js + var $$typeof = Symbol.for("jsxdev"); + function jsxDEV(type, props, key, source, self) { + return { + $$typeof, + type, + props, + key, + source, + self + }; + } + var Fragment = Symbol.for("jsxdev.fragment"); + + // index.jsx + console.log(jsxDEV("a", {}, undefined, false, undefined, this)); + console.log(jsxDEV(Fragment, {}, undefined, false, undefined, this));" + `); + }, + }); + + // Test JSX production mode (non-development) with side effects + itBundled("jsx/sideEffectsDefaultProductionClassic", { + files: { + "/index.jsx": /* jsx */ `console.log(); console.log(<>);`, + ...helpers, + }, + target: "bun", + jsx: { + runtime: "classic", + factory: "React.createElement", + fragment: "React.Fragment", + }, + env: { + NODE_ENV: "production", + }, + onAfterBundle(api) { + const file = api.readFile("out.js"); + // Default behavior in production: should include /* @__PURE__ */ comments + expect(file).toContain("/* @__PURE__ */"); + expect(normalizeBunSnapshot(file)).toMatchInlineSnapshot(` + "// @bun + // index.jsx + console.log(/* @__PURE__ */ React.createElement("a", null)); + console.log(/* @__PURE__ */ React.createElement(React.Fragment, null));" + `); + }, + }); + + itBundled("jsx/sideEffectsTrueProductionClassic", { + files: { + "/index.jsx": /* jsx */ `console.log(); console.log(<>);`, + ...helpers, + }, + target: "bun", + jsx: { + runtime: "classic", + factory: "React.createElement", + fragment: "React.Fragment", + side_effects: true, + }, + env: { + NODE_ENV: "production", + }, + onAfterBundle(api) { + const file = api.readFile("out.js"); + // When jsxSideEffects is true in production: should NOT include /* @__PURE__ */ comments + expect(file).not.toContain("/* @__PURE__ */"); + expect(file).toContain("React.createElement"); + expect(normalizeBunSnapshot(file)).toMatchInlineSnapshot(` + "// @bun + // index.jsx + console.log(React.createElement("a", null)); + console.log(React.createElement(React.Fragment, null));" + `); + }, + }); + + itBundled("jsx/sideEffectsDefaultProductionAutomatic", { + files: { + "/index.jsx": /* jsx */ `console.log(); console.log(<>);`, + ...helpers, + }, + target: "bun", + jsx: { + runtime: "automatic", + }, + env: { + NODE_ENV: "production", + }, + onAfterBundle(api) { + const file = api.readFile("out.js"); + // Default behavior in production: should include /* @__PURE__ */ comments + expect(file).toContain("/* @__PURE__ */"); + expect(normalizeBunSnapshot(file)).toMatchInlineSnapshot(` + "// @bun + // node_modules/react/jsx-runtime.js + var $$typeof = Symbol.for("jsx"); + function jsx(type, props, key) { + return { + $$typeof, + type, + props, + key + }; + } + var Fragment = Symbol.for("jsx.fragment"); + + // index.jsx + console.log(/* @__PURE__ */ jsx("a", {})); + console.log(/* @__PURE__ */ jsx(Fragment, {}));" + `); + }, + }); + + itBundled("jsx/sideEffectsTrueProductionAutomatic", { + files: { + "/index.jsx": /* jsx */ `console.log(); console.log(<>);`, + ...helpers, + }, + target: "bun", + jsx: { + runtime: "automatic", + side_effects: true, + }, + env: { + NODE_ENV: "production", + }, + onAfterBundle(api) { + const file = api.readFile("out.js"); + // When jsxSideEffects is true in production: should NOT include /* @__PURE__ */ comments + expect(file).not.toContain("/* @__PURE__ */"); + expect(normalizeBunSnapshot(file)).toMatchInlineSnapshot(` + "// @bun + // node_modules/react/jsx-runtime.js + var $$typeof = Symbol.for("jsx"); + function jsx(type, props, key) { + return { + $$typeof, + type, + props, + key + }; + } + var Fragment = Symbol.for("jsx.fragment"); + + // index.jsx + console.log(jsx("a", {})); + console.log(jsx(Fragment, {}));" + `); + }, + }); + + // Test tsconfig.json parsing for jsxSideEffects option + itBundled("jsx/sideEffectsDefaultTsconfig", { + files: { + "/index.jsx": /* jsx */ `console.log(); console.log(<>);`, + "/tsconfig.json": /* json */ `{"compilerOptions": {}}`, + ...helpers, + }, + target: "bun", + onAfterBundle(api) { + const file = api.readFile("out.js"); + // Default behavior via tsconfig: should include /* @__PURE__ */ comments + expect(file).toContain("/* @__PURE__ */"); + expect(normalizeBunSnapshot(file)).toMatchInlineSnapshot(` + "// @bun + // node_modules/react/jsx-dev-runtime.js + var $$typeof = Symbol.for("jsxdev"); + function jsxDEV(type, props, key, source, self) { + return { + $$typeof, + type, + props, + key, + source, + self + }; + } + var Fragment = Symbol.for("jsxdev.fragment"); + + // index.jsx + console.log(/* @__PURE__ */ jsxDEV("a", {}, undefined, false, undefined, this)); + console.log(/* @__PURE__ */ jsxDEV(Fragment, {}, undefined, false, undefined, this));" + `); + }, + }); + + itBundled("jsx/sideEffectsTrueTsconfig", { + files: { + "/index.jsx": /* jsx */ `console.log(); console.log(<>);`, + "/tsconfig.json": /* json */ `{"compilerOptions": {"jsxSideEffects": true}}`, + ...helpers, + }, + target: "bun", + onAfterBundle(api) { + const file = api.readFile("out.js"); + // When jsxSideEffects is true via tsconfig: should NOT include /* @__PURE__ */ comments + expect(file).not.toContain("/* @__PURE__ */"); + expect(normalizeBunSnapshot(file)).toMatchInlineSnapshot(` + "// @bun + // node_modules/react/jsx-dev-runtime.js + var $$typeof = Symbol.for("jsxdev"); + function jsxDEV(type, props, key, source, self) { + return { + $$typeof, + type, + props, + key, + source, + self + }; + } + var Fragment = Symbol.for("jsxdev.fragment"); + + // index.jsx + console.log(jsxDEV("a", {}, undefined, false, undefined, this)); + console.log(jsxDEV(Fragment, {}, undefined, false, undefined, this));" + `); + }, + }); + + itBundled("jsx/sideEffectsTrueTsconfigClassic", { + files: { + "/index.jsx": /* jsx */ `console.log(); console.log(<>);`, + "/tsconfig.json": /* json */ `{"compilerOptions": {"jsx": "react", "jsxSideEffects": true}}`, + ...helpers, + }, + target: "bun", + onAfterBundle(api) { + const file = api.readFile("out.js"); + // When jsxSideEffects 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(` + "// @bun + // index.jsx + console.log(React.createElement("a", null)); + console.log(React.createElement(React.Fragment, null));" + `); + }, + }); + + itBundled("jsx/sideEffectsTrueTsconfigAutomatic", { + files: { + "/index.jsx": /* jsx */ `console.log(); console.log(<>);`, + "/tsconfig.json": /* json */ `{"compilerOptions": {"jsx": "react-jsx", "jsxSideEffects": true}}`, + ...helpers, + }, + target: "bun", + onAfterBundle(api) { + const file = api.readFile("out.js"); + // When jsxSideEffects is true via tsconfig with automatic jsx: should NOT include /* @__PURE__ */ comments + expect(file).not.toContain("/* @__PURE__ */"); + expect(normalizeBunSnapshot(file)).toMatchInlineSnapshot(` + "// @bun + // node_modules/react/jsx-dev-runtime.js + var $$typeof = Symbol.for("jsxdev"); + function jsxDEV(type, props, key, source, self) { + return { + $$typeof, + type, + props, + key, + source, + self + }; + } + var Fragment = Symbol.for("jsxdev.fragment"); + + // index.jsx + console.log(jsxDEV("a", {}, undefined, false, undefined, this)); + console.log(jsxDEV(Fragment, {}, undefined, false, undefined, this));" + `); + }, + }); + }); }); diff --git a/test/bundler/expectBundled.ts b/test/bundler/expectBundled.ts index cb140a18dd..3c63fb45c0 100644 --- a/test/bundler/expectBundled.ts +++ b/test/bundler/expectBundled.ts @@ -717,6 +717,7 @@ function expectBundled( jsx.factory && ["--jsx-factory", jsx.factory], jsx.fragment && ["--jsx-fragment", jsx.fragment], jsx.importSource && ["--jsx-import-source", jsx.importSource], + jsx.side_effects && ["--jsx-side-effects"], dotenv && ["--env", dotenv], // metafile && `--manifest=${metafile}`, sourceMap && `--sourcemap=${sourceMap}`, @@ -760,6 +761,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`, env?.NODE_ENV !== "production" && `--jsx-dev`, entryNaming && entryNaming !== "[dir]/[name].[ext]" &&