diff options
| -rw-r--r-- | src/bundler/bundle_v2.zig | 1 | ||||
| -rw-r--r-- | src/cli.zig | 4 | ||||
| -rw-r--r-- | src/js_parser.zig | 94 | ||||
| -rw-r--r-- | src/options.zig | 8 | ||||
| -rw-r--r-- | src/resolver/resolver.zig | 1 | ||||
| -rw-r--r-- | src/resolver/tsconfig_json.zig | 9 | ||||
| -rw-r--r-- | test/bundler/esbuild/tsconfig.test.ts | 206 |
7 files changed, 200 insertions, 123 deletions
diff --git a/src/bundler/bundle_v2.zig b/src/bundler/bundle_v2.zig index 0cbeec1f2..3873c6b12 100644 --- a/src/bundler/bundle_v2.zig +++ b/src/bundler/bundle_v2.zig @@ -666,7 +666,6 @@ pub const BundleV2 = struct { var task = try this.graph.allocator.create(ParseTask); task.* = ParseTask.init(&result, source_index, this); task.loader = loader; - task.jsx = this.bundler.options.jsx; task.task.node.next = null; task.tree_shaking = this.linker.options.tree_shaking; diff --git a/src/cli.zig b/src/cli.zig index 606e0a3cc..0ad948ac7 100644 --- a/src/cli.zig +++ b/src/cli.zig @@ -712,7 +712,7 @@ pub const Arguments = struct { .factory = constStrToU8(jsx_factory orelse &default_factory), .fragment = constStrToU8(jsx_fragment orelse &default_fragment), .import_source = constStrToU8(jsx_import_source orelse &default_import_source), - .runtime = if (jsx_runtime != null) try resolve_jsx_runtime(jsx_runtime.?) else Api.JsxRuntime.automatic, + .runtime = if (jsx_runtime) |runtime| try resolve_jsx_runtime(runtime) else Api.JsxRuntime.automatic, .development = false, .react_fast_refresh = react_fast_refresh, }; @@ -721,7 +721,7 @@ pub const Arguments = struct { .factory = constStrToU8(jsx_factory orelse opts.jsx.?.factory), .fragment = constStrToU8(jsx_fragment orelse opts.jsx.?.fragment), .import_source = constStrToU8(jsx_import_source orelse opts.jsx.?.import_source), - .runtime = if (jsx_runtime != null) try resolve_jsx_runtime(jsx_runtime.?) else opts.jsx.?.runtime, + .runtime = if (jsx_runtime) |runtime| try resolve_jsx_runtime(runtime) else opts.jsx.?.runtime, .development = false, .react_fast_refresh = react_fast_refresh, }; diff --git a/src/js_parser.zig b/src/js_parser.zig index 91631d1f6..a0f4defae 100644 --- a/src/js_parser.zig +++ b/src/js_parser.zig @@ -111,7 +111,7 @@ const JSXImport = enum { jsxs: ?LocRef = null, Fragment: ?LocRef = null, createElement: ?LocRef = null, - factory_name: []const u8 = "createElement", + factory_name: []const u8 = "React.createElement", fragment_name: []const u8 = "Fragment", pub fn get(this: *const Symbols, name: []const u8) ?Ref { @@ -133,67 +133,31 @@ const JSXImport = enum { }; } - const Runtime = struct { - pub const full: []const string = &[_]string{ "jsx", "jsxs" }; - pub const jsxs_: []const string = &[_]string{"jsxs"}; - pub const jsx_: []const string = &[_]string{"jsx"}; - }; - - const DevRuntime = struct { - pub const full: []const string = &[_]string{ "jsxDEV", "jsxs" }; - pub const jsxs_: []const string = &[_]string{"jsxs"}; - pub const jsx_: []const string = &[_]string{"jsxDEV"}; - }; - pub fn runtimeImportNames(this: *const Symbols) []const string { + pub fn runtimeImportNames(this: *const Symbols, buf: *[3]string) []const string { + var i: usize = 0; if (this.jsxDEV != null) { std.debug.assert(this.jsx == null); // we should never end up with this in the same file - - if (this.jsxs != null) - return DevRuntime.full; - - return DevRuntime.jsx_; + buf[0] = "jsxDEV"; + i += 1; } - if (this.jsx != null and this.jsxs != null) - return Runtime.full; - - if (this.jsxs != null) - return Runtime.jsxs_; - - if (this.jsx != null) - return Runtime.jsx_; - - return &[_]string{}; - } - - const Legacy = struct { - pub const full: []const string = &[_]string{ "createElement", "Fragment" }; - pub const createElement_: []const string = &[_]string{"createElement"}; - pub const Fragment_: []const string = &[_]string{"Fragment"}; - }; - - pub fn legacyImportNames(this: *const Symbols, jsx: *const options.JSX.Pragma, buf: *[2]string) []const string { - _ = jsx; - if (this.Fragment != null and this.createElement != null) { - buf[0..2].* = .{ - this.factory_name, - this.fragment_name, - }; - return buf[0..2]; + if (this.jsx != null) { + std.debug.assert(this.jsxDEV == null); // we should never end up with this in the same file + buf[0] = "jsx"; + i += 1; } - if (this.createElement != null) { - buf[0] = - this.factory_name; - return buf[0..1]; + if (this.jsxs != null) { + buf[i] = "jsxs"; + i += 1; } if (this.Fragment != null) { - buf[0] = this.fragment_name; - return buf[0..1]; + buf[i] = this.fragment_name; + i += 1; } - return &[_]string{}; + return buf[0..i]; } }; }; @@ -4199,10 +4163,9 @@ pub const Parser = struct { } // handle new way to do automatic JSX imports which fixes symbol collision issues - if (p.options.jsx.parse and p.options.features.auto_import_jsx) { - var legacy_import_names_buf = [2]string{ "", "" }; - const runtime_import_names = p.jsx_imports.runtimeImportNames(); - const legacy_import_names = p.jsx_imports.legacyImportNames(&p.options.jsx, &legacy_import_names_buf); + if (p.options.jsx.parse and p.options.features.auto_import_jsx and p.options.jsx.runtime == .automatic) { + var buf = [3]string{ "", "", "" }; + const runtime_import_names = p.jsx_imports.runtimeImportNames(&buf); if (runtime_import_names.len > 0) { p.generateImportStmt( @@ -4215,18 +4178,6 @@ pub const Parser = struct { false, ) catch unreachable; } - - if (legacy_import_names.len > 0) { - p.generateImportStmt( - p.options.jsx.classic_import_source, - legacy_import_names, - &before, - &p.jsx_imports, - null, - "", - false, - ) catch unreachable; - } } var parts_slice: []js_ast.Part = &([_]js_ast.Part{}); @@ -6466,7 +6417,7 @@ fn NewParser_( } if (p.lexer.jsx_pragma.jsxImportSource()) |import_source| { - p.options.jsx.classic_import_source = options.JSX.Pragma.parsePackageName(import_source.text); + p.options.jsx.classic_import_source = import_source.text; p.options.jsx.package_name = p.options.jsx.classic_import_source; p.options.jsx.setImportSource(p.allocator); } @@ -6557,8 +6508,11 @@ fn NewParser_( // "Foo.Bar.createElement" becomes: // import { Bar } from 'foo'; // Usages become Bar.createElement - if (p.options.jsx.fragment.len > 0) - p.jsx_imports.fragment_name = p.options.jsx.fragment[if (p.options.jsx.fragment.len > 1) 1 else 0]; + + if (p.options.jsx.runtime == .classic) { + if (p.options.jsx.fragment.len > 0) + p.jsx_imports.fragment_name = p.options.jsx.fragment[if (p.options.jsx.fragment.len > 1) 1 else 0]; + } if (p.options.jsx.factory.len > 0) p.jsx_imports.factory_name = p.options.jsx.factory[if (p.options.jsx.factory.len > 1) 1 else 0]; diff --git a/src/options.zig b/src/options.zig index 9f5d3c552..6953a448f 100644 --- a/src/options.zig +++ b/src/options.zig @@ -965,7 +965,7 @@ pub const JSX = struct { .{ "automatic", JSX.Runtime.automatic }, .{ "react", JSX.Runtime.classic }, .{ "react-jsx", JSX.Runtime.automatic }, - .{ "react-jsxDEV", JSX.Runtime.automatic }, + .{ "react-jsxdev", JSX.Runtime.automatic }, .{ "solid", JSX.Runtime.solid }, }); @@ -1054,8 +1054,8 @@ pub const JSX = struct { } pub const Defaults = struct { - pub const Factory = &[_]string{"createElement"}; - pub const Fragment = &[_]string{"Fragment"}; + pub const Factory = &[_]string{"React.createElement"}; + pub const Fragment = &[_]string{"React.Fragment"}; pub const ImportSourceDev = "react/jsx-dev-runtime"; pub const ImportSource = "react/jsx-runtime"; pub const JSXFunction = "jsx"; @@ -1116,7 +1116,7 @@ pub const JSX = struct { pragma.runtime = jsx.runtime; if (jsx.import_source.len > 0) { - pragma.package_name = parsePackageName(pragma.importSource()); + pragma.package_name = jsx.import_source; pragma.setImportSource(allocator); pragma.classic_import_source = pragma.package_name; } diff --git a/src/resolver/resolver.zig b/src/resolver/resolver.zig index 58f00c2af..068b22dc4 100644 --- a/src/resolver/resolver.zig +++ b/src/resolver/resolver.zig @@ -3758,6 +3758,7 @@ pub const Resolver = struct { merged_config.base_url_for_paths = parent_config.base_url_for_paths; } merged_config.jsx = parent_config.mergeJSX(merged_config.jsx); + merged_config.jsx_flags.setUnion(parent_config.jsx_flags); if (parent_config.preserve_imports_not_used_as_values) |value| { merged_config.preserve_imports_not_used_as_values = value; diff --git a/src/resolver/tsconfig_json.zig b/src/resolver/tsconfig_json.zig index 7121e8741..7ec6b3e0b 100644 --- a/src/resolver/tsconfig_json.zig +++ b/src/resolver/tsconfig_json.zig @@ -157,11 +157,14 @@ pub const TSConfigJSON = struct { if (compiler_opts.expr.asProperty("jsx")) |jsx_prop| { if (jsx_prop.expr.asString(allocator)) |str| { + var str_lower = allocator.alloc(u8, str.len) catch unreachable; + defer allocator.free(str_lower); + _ = strings.copyLowercase(str, str_lower); // we don't support "preserve" yet - if (options.JSX.RuntimeMap.get(str)) |runtime| { + if (options.JSX.RuntimeMap.get(str_lower)) |runtime| { result.jsx.runtime = runtime; if (runtime == .automatic) { - result.jsx.setProduction(!strings.contains(str, "jsxDEV")); + result.jsx.setProduction(!strings.contains(str_lower, "jsxdev")); is_jsx_development = result.jsx.development; result.jsx_flags.insert(.development); } @@ -179,7 +182,7 @@ pub const TSConfigJSON = struct { result.jsx_flags.insert(.runtime); } - result.jsx.package_name = options.JSX.Pragma.parsePackageName(str); + result.jsx.package_name = str; result.jsx.setImportSource(allocator); result.jsx_flags.insert(.import_source); } diff --git a/test/bundler/esbuild/tsconfig.test.ts b/test/bundler/esbuild/tsconfig.test.ts index b698ad07a..fc0072423 100644 --- a/test/bundler/esbuild/tsconfig.test.ts +++ b/test/bundler/esbuild/tsconfig.test.ts @@ -364,114 +364,234 @@ describe("bundler", () => { "/Users/user/project/src/entry.ts": [`Could not resolve: "#/test". Maybe you need to "bun install"?`], }, }); - return; - itBundled("tsconfig/PathsTypeOnly", { + itBundled("tsconfig/JSX", { // GENERATED files: { - "/Users/user/project/entry.ts": /* ts */ ` - import { fib } from "fib"; - - console.log(fib(10)); - `, - "/Users/user/project/node_modules/fib/index.js": /* js */ ` - export function fib(input) { - if (input < 2) { - return input; - } - return fib(input - 1) + fib(input - 2); + "/Users/user/project/entry.tsx": `console.log(<><div/><div/></>)`, + "/Users/user/project/node_modules/react/jsx-dev-runtime.ts": ` + export const Fragment = (props: { key?: string; children?: Child[] }): JSXNode => { + return new JSXFragmentNode('', {}, props.children || []) + } + export const jsx = (tag: string | JSXComponent, props: { key?: string; children?: Child[] }, ...children: Child[]): JSXNode => { + return new JSXNode(tag, props, children) } `, - "/Users/user/project/fib-local.d.ts": `export function fib(input: number): number;`, "/Users/user/project/tsconfig.json": /* json */ ` { "compilerOptions": { - "baseUrl": ".", - "paths": { - "fib": ["fib-local.d.ts"] - } + "jsx": "react", + "jsxFactory": "R.c", + "jsxFragmentFactory": "R.F" } } `, }, + outfile: "/Users/user/project/out.js", + external: ["react"], + onAfterBundle(api) { + api + .expectFile("/Users/user/project/out.js") + .toContain(`console.log(c(F, null, c(\"div\", null), c(\"div\", null)));\n`); + }, }); - itBundled("tsconfig/JSX", { + itBundled("tsconfig/ReactJSXNotReact", { // GENERATED files: { "/Users/user/project/entry.tsx": `console.log(<><div/><div/></>)`, + "/Users/user/project/node_modules/notreact/jsx-runtime.ts": ` + export const Fragment = (props: { key?: string; children?: Child[] }): JSXNode => { + return new JSXFragmentNode('', {}, props.children || []) + } + export const jsx = (tag: string | JSXComponent, props: { key?: string; children?: Child[] }, ...children: Child[]): JSXNode => { + return new JSXNode(tag, props, children) + } + `, "/Users/user/project/tsconfig.json": /* json */ ` { "compilerOptions": { - "jsxFactory": "R.c", - "jsxFragmentFactory": "R.F" + "jsx": "react-jsx", + "jsxImportSource": "notreact" } } `, }, + outfile: "/Users/user/project/out.js", + external: ["notreact"], + onAfterBundle(api) { + api.expectFile("/Users/user/project/out.js").toContain(`from "notreact/jsx-runtime`); + }, }); - itBundled("tsconfig/NestedJSX", { + itBundled("tsconfig/ReactJSXNotReactScoped", { // GENERATED files: { - "/Users/user/project/entry.ts": /* ts */ ` - import factory from './factory' - import fragment from './fragment' - import both from './both' - console.log(factory, fragment, both) + "/Users/user/project/entry.tsx": `console.log(<><div/><div/></>)`, + "/Users/user/project/node_modules/@notreact/jsx/jsx-runtime.ts": ` + export const Fragment = (props: { key?: string; children?: Child[] }): JSXNode => { + return new JSXFragmentNode('', {}, props.children || []) + } + export const jsx = (tag: string | JSXComponent, props: { key?: string; children?: Child[] }, ...children: Child[]): JSXNode => { + return new JSXNode(tag, props, children) + } `, - "/Users/user/project/factory/index.tsx": `export default <><div/><div/></>`, - "/Users/user/project/factory/tsconfig.json": /* json */ ` + "/Users/user/project/tsconfig.json": /* json */ ` { "compilerOptions": { - "jsxFactory": "h" + "jsx": "react-jsx", + "jsxImportSource": "@notreact/jsx" } } `, - "/Users/user/project/fragment/index.tsx": `export default <><div/><div/></>`, - "/Users/user/project/fragment/tsconfig.json": /* json */ ` + }, + outfile: "/Users/user/project/out.js", + external: ["@notreact/jsx"], + onAfterBundle(api) { + api.expectFile("/Users/user/project/out.js").toContain(`from "@notreact/jsx/jsx-runtime`); + }, + }); + itBundled("tsconfig/ReactJSXDevNotReact", { + // GENERATED + files: { + "/Users/user/project/entry.tsx": `console.log(<><div/><div/></>)`, + "/Users/user/project/node_modules/notreact/jsx-dev-runtime.ts": ` + export const Fragment = (props: { key?: string; children?: Child[] }): JSXNode => { + return new JSXFragmentNode('', {}, props.children || []) + } + export const jsx = (tag: string | JSXComponent, props: { key?: string; children?: Child[] }, ...children: Child[]): JSXNode => { + return new JSXNode(tag, props, children) + } + `, + "/Users/user/project/tsconfig.json": /* json */ ` { "compilerOptions": { - "jsxFragmentFactory": "a.b" + "jsx": "react-jsxdev", + "jsxImportSource": "notreact" } } `, - "/Users/user/project/both/index.tsx": `export default <><div/><div/></>`, - "/Users/user/project/both/tsconfig.json": /* json */ ` + }, + outfile: "/Users/user/project/out.js", + external: ["notreact"], + onAfterBundle(api) { + api.expectFile("/Users/user/project/out.js").toContain(`from "notreact/jsx-dev-runtime`); + }, + }); + itBundled("tsconfig/ReactJSXDev", { + // GENERATED + files: { + "/Users/user/project/entry.tsx": `console.log(<><div/><div/></>)`, + "/Users/user/project/node_modules/react/jsx-dev-runtime.ts": ` + export const Fragment = (props: { key?: string; children?: Child[] }): JSXNode => { + return new JSXFragmentNode('', {}, props.children || []) + } + export const jsx = (tag: string | JSXComponent, props: { key?: string; children?: Child[] }, ...children: Child[]): JSXNode => { + return new JSXNode(tag, props, children) + } + `, + "/Users/user/project/tsconfig.json": /* json */ ` { "compilerOptions": { - "jsxFactory": "R.c", - "jsxFragmentFactory": "R.F" + "jsx": "react-jsxdev" } } `, }, + external: ["react"], + outfile: "/Users/user/project/out.js", + onAfterBundle(api) { + api.expectFile("/Users/user/project/out.js").toContain(`from "react/jsx-dev-runtime`); + }, }); itBundled("tsconfig/ReactJSX", { // GENERATED files: { "/Users/user/project/entry.tsx": `console.log(<><div/><div/></>)`, + "/Users/user/project/node_modules/react/jsx-runtime.ts": ` + export const Fragment = (props: { key?: string; children?: Child[] }): JSXNode => { + return new JSXFragmentNode('', {}, props.children || []) + } + export const jsx = (tag: string | JSXComponent, props: { key?: string; children?: Child[] }, ...children: Child[]): JSXNode => { + return new JSXNode(tag, props, children) + } + `, "/Users/user/project/tsconfig.json": /* json */ ` { "compilerOptions": { - "jsx": "react-jsx", - "jsxImportSource": "notreact" + "jsx": "react-jsx" } } `, }, + external: ["react"], outfile: "/Users/user/project/out.js", + onAfterBundle(api) { + api.expectFile("/Users/user/project/out.js").toContain(`from "react/jsx-runtime`); + }, }); - itBundled("tsconfig/ReactJSXDev", { + return; + itBundled("tsconfig/PathsTypeOnly", { // GENERATED files: { - "/Users/user/project/entry.tsx": `console.log(<><div/><div/></>)`, + "/Users/user/project/entry.ts": /* ts */ ` + import { fib } from "fib"; + + console.log(fib(10)); + `, + "/Users/user/project/node_modules/fib/index.js": /* js */ ` + export function fib(input) { + if (input < 2) { + return input; + } + return fib(input - 1) + fib(input - 2); + } + `, + "/Users/user/project/fib-local.d.ts": `export function fib(input: number): number;`, "/Users/user/project/tsconfig.json": /* json */ ` { "compilerOptions": { - "jsx": "react-jsxdev" + "baseUrl": ".", + "paths": { + "fib": ["fib-local.d.ts"] + } + } + } + `, + }, + }); + itBundled("tsconfig/NestedJSX", { + // GENERATED + files: { + "/Users/user/project/entry.ts": /* ts */ ` + import factory from './factory' + import fragment from './fragment' + import both from './both' + console.log(factory, fragment, both) + `, + "/Users/user/project/factory/index.tsx": `export default <><div/><div/></>`, + "/Users/user/project/factory/tsconfig.json": /* json */ ` + { + "compilerOptions": { + "jsxFactory": "h" + } + } + `, + "/Users/user/project/fragment/index.tsx": `export default <><div/><div/></>`, + "/Users/user/project/fragment/tsconfig.json": /* json */ ` + { + "compilerOptions": { + "jsxFragmentFactory": "a.b" + } + } + `, + "/Users/user/project/both/index.tsx": `export default <><div/><div/></>`, + "/Users/user/project/both/tsconfig.json": /* json */ ` + { + "compilerOptions": { + "jsxFactory": "R.c", + "jsxFragmentFactory": "R.F" } } `, }, - outfile: "/Users/user/project/out.js", }); itBundled("tsconfig/ReactJSXWithDevInMainConfig", { // GENERATED |
