diff options
author | 2021-10-06 16:49:26 -0700 | |
---|---|---|
committer | 2021-10-06 16:49:26 -0700 | |
commit | 5370ea71c0b3a6759c481f96608ce855bd043bc8 (patch) | |
tree | cf75dd798d1109fc3d06cfc8621c8aefc8a93183 | |
parent | 0afec7739b9f1df8d9cf565f3fed19e663162734 (diff) | |
download | bun-5370ea71c0b3a6759c481f96608ce855bd043bc8.tar.gz bun-5370ea71c0b3a6759c481f96608ce855bd043bc8.tar.zst bun-5370ea71c0b3a6759c481f96608ce855bd043bc8.zip |
Add support for reading JSX config from tsconfig.json
-rw-r--r-- | integration/scripts/browser.js | 1 | ||||
-rw-r--r-- | integration/snippets/custom-emotion-jsx/file.jsx | 15 | ||||
-rw-r--r-- | integration/snippets/custom-emotion-jsx/tsconfig.json | 5 | ||||
-rw-r--r-- | integration/snippets/package.json | 2 | ||||
-rw-r--r-- | src/bundler.zig | 17 | ||||
-rw-r--r-- | src/cli.zig | 2 | ||||
-rw-r--r-- | src/cli/bun_command.zig | 4 | ||||
-rw-r--r-- | src/http.zig | 3 | ||||
-rw-r--r-- | src/javascript/jsc/javascript.zig | 1 | ||||
-rw-r--r-- | src/options.zig | 15 | ||||
-rw-r--r-- | src/resolver/resolver.zig | 14 | ||||
-rw-r--r-- | src/resolver/tsconfig_json.zig | 38 |
12 files changed, 98 insertions, 19 deletions
diff --git a/integration/scripts/browser.js b/integration/scripts/browser.js index 2d758e638..2328e0ede 100644 --- a/integration/scripts/browser.js +++ b/integration/scripts/browser.js @@ -124,6 +124,7 @@ async function main() { "/spread_with_key.tsx", "/styledcomponents-output.js", "/void-shouldnt-delete-call-expressions.js", + "/custom-emotion-jsx/file.jsx", ]; tests.reverse(); diff --git a/integration/snippets/custom-emotion-jsx/file.jsx b/integration/snippets/custom-emotion-jsx/file.jsx new file mode 100644 index 000000000..c00cb0543 --- /dev/null +++ b/integration/snippets/custom-emotion-jsx/file.jsx @@ -0,0 +1,15 @@ +import * as ReactDOM from "react-dom"; +export const Foo = () => <div css={{ content: '"it worked!"' }}></div>; + +export function test() { + const element = document.createElement("div"); + element.id = "custom-emotion-jsx"; + document.body.appendChild(element); + ReactDOM.render(<Foo />, element); + const style = window.getComputedStyle(element.firstChild); + if (!(style["content"] ?? "").includes("it worked!")) { + throw new Error('Expected "it worked!" but received: ' + style["content"]); + } + + return testDone(import.meta.url); +} diff --git a/integration/snippets/custom-emotion-jsx/tsconfig.json b/integration/snippets/custom-emotion-jsx/tsconfig.json new file mode 100644 index 000000000..7bb0f58a0 --- /dev/null +++ b/integration/snippets/custom-emotion-jsx/tsconfig.json @@ -0,0 +1,5 @@ +{ + "compilerOptions": { + "jsxImportSource": "@emotion/react" + } +} diff --git a/integration/snippets/package.json b/integration/snippets/package.json index 1e9e250b3..0f5dce714 100644 --- a/integration/snippets/package.json +++ b/integration/snippets/package.json @@ -4,6 +4,8 @@ "main": "index.js", "license": "MIT", "dependencies": { + "@emotion/core": "^11.0.0", + "@emotion/react": "^11.4.1", "lodash": "^4.17.21", "react": "^17.0.2", "react-dom": "^17.0.2", diff --git a/src/bundler.zig b/src/bundler.zig index f348230f9..93c920a7d 100644 --- a/src/bundler.zig +++ b/src/bundler.zig @@ -217,6 +217,16 @@ pub const Bundler = struct { bundler.resolve_results, bundler.fs, ); + + // If we don't explicitly pass JSX, try to get it from the root tsconfig + if (bundler.options.transform_options.jsx == null) { + // Most of the time, this will already be cached + if (bundler.resolver.readDirInfo(bundler.fs.top_level_dir) catch null) |root_dir| { + if (root_dir.tsconfig_json) |tsconfig| { + bundler.options.jsx = tsconfig.jsx; + } + } + } } pub fn runEnvLoader(this: *ThisBundler) !void { @@ -1533,7 +1543,7 @@ pub const Bundler = struct { .js, .ts, => { - var jsx = bundler.options.jsx; + var jsx = _resolve.jsx; jsx.parse = loader.isJSX(); var opts = js_parser.Parser.Options.init(jsx, loader); @@ -2150,6 +2160,7 @@ pub const Bundler = struct { .file_descriptor = file_descriptor, .file_hash = filepath_hash, .macro_remappings = resolve_result.getMacroRemappings(), + .jsx = resolve_result.jsx, }, client_entry_point, ) orelse { @@ -2241,6 +2252,7 @@ pub const Bundler = struct { .file_descriptor = null, .file_hash = null, .macro_remappings = resolve_result.getMacroRemappings(), + .jsx = resolve_result.jsx, }, client_entry_point_, ) orelse { @@ -2401,6 +2413,7 @@ pub const Bundler = struct { file_hash: ?u32 = null, path: Fs.Path, loader: options.Loader, + jsx: options.JSX.Pragma, macro_remappings: MacroRemap, }; @@ -2467,7 +2480,7 @@ pub const Bundler = struct { .ts, .tsx, => { - var jsx = bundler.options.jsx; + var jsx = this_parse.jsx; jsx.parse = loader.isJSX(); var opts = js_parser.Parser.Options.init(jsx, loader); opts.enable_bundling = false; diff --git a/src/cli.zig b/src/cli.zig index 3603a9c74..5dd9095a1 100644 --- a/src/cli.zig +++ b/src/cli.zig @@ -405,7 +405,7 @@ pub const Arguments = struct { jsx_fragment != null or jsx_import_source != null or jsx_runtime != null or - jsx_production or react_fast_refresh) + jsx_production or !react_fast_refresh) { var default_factory = "".*; var default_fragment = "".*; diff --git a/src/cli/bun_command.zig b/src/cli/bun_command.zig index 74e8e7654..53579b938 100644 --- a/src/cli/bun_command.zig +++ b/src/cli/bun_command.zig @@ -45,8 +45,10 @@ const ServerBundleGeneratorThread = struct { null, env_loader_, ); - server_bundler.options.jsx.supports_fast_refresh = false; server_bundler.configureLinker(); + + server_bundler.options.jsx.supports_fast_refresh = false; + server_bundler.router = router; server_bundler.configureDefines() catch |err| { Output.prettyErrorln("<r><red>{s}<r> loading --define or .env values for node_modules.server.bun\n", .{@errorName(err)}); diff --git a/src/http.zig b/src/http.zig index 0baa3b3b5..434207485 100644 --- a/src/http.zig +++ b/src/http.zig @@ -205,6 +205,7 @@ pub const RequestContext = struct { .loader = .js, .macro_remappings = .{}, .dirname_fd = 0, + .jsx = bundler_.options.jsx, }; if (bundler_.parse( @@ -764,6 +765,8 @@ pub const RequestContext = struct { .file_descriptor = fd, .file_hash = id, .macro_remappings = macro_remappings, + // TODO: make this work correctly when multiple tsconfigs define different JSX pragmas + .jsx = this.bundler.options.jsx, }, null, ) orelse { diff --git a/src/javascript/jsc/javascript.zig b/src/javascript/jsc/javascript.zig index d693bad68..7749b5182 100644 --- a/src/javascript/jsc/javascript.zig +++ b/src/javascript/jsc/javascript.zig @@ -858,6 +858,7 @@ pub const VirtualMachine = struct { .file_descriptor = fd, .file_hash = hash, .macro_remappings = macro_remappings, + .jsx = vm.bundler.options.jsx, }; var parse_result = vm.bundler.parse( diff --git a/src/options.zig b/src/options.zig index e734a59e6..1babfa194 100644 --- a/src/options.zig +++ b/src/options.zig @@ -644,8 +644,8 @@ pub const ESMConditions = struct { pub const JSX = struct { pub const Pragma = struct { // these need to be arrays - factory: []const string = &(Defaults.Factory), - fragment: []const string = &(Defaults.Fragment), + factory: []const string = Defaults.Factory, + fragment: []const string = Defaults.Fragment, runtime: JSX.Runtime = JSX.Runtime.automatic, /// Facilitates automatic JSX importing @@ -655,9 +655,9 @@ pub const JSX = struct { classic_import_source: string = "react", package_name: []const u8 = "react", refresh_runtime: string = "react-refresh/runtime", - supports_fast_refresh: bool = false, + supports_fast_refresh: bool = true, - jsx: string = Defaults.JSXFunction, + jsx: string = Defaults.JSXFunctionDev, jsx_static: string = Defaults.JSXStaticFunction, development: bool = true, @@ -686,8 +686,8 @@ pub const JSX = struct { } pub const Defaults = struct { - pub var Factory = [_]string{ "React", "createElement" }; - pub var Fragment = [_]string{ "React", "Fragment" }; + pub const Factory = &[_]string{"createElement"}; + pub const Fragment = &[_]string{"Fragment"}; pub const ImportSourceDev = "react/jsx-dev-runtime"; pub const ImportSource = "react/jsx-runtime"; pub const JSXFunction = "jsx"; @@ -746,16 +746,13 @@ pub const JSX = struct { if (jsx.import_source.len > 0) { pragma.import_source = jsx.import_source; pragma.package_name = parsePackageName(pragma.import_source); - pragma.supports_fast_refresh = pragma.development and pragma.isReactLike(); } else if (jsx.development) { pragma.import_source = Defaults.ImportSourceDev; pragma.jsx = Defaults.JSXFunctionDev; - pragma.supports_fast_refresh = true; pragma.package_name = "react"; } else { pragma.import_source = Defaults.ImportSource; pragma.jsx = Defaults.JSXFunction; - pragma.supports_fast_refresh = false; } pragma.development = jsx.development; diff --git a/src/resolver/resolver.zig b/src/resolver/resolver.zig index a1066dfb7..4b766b81e 100644 --- a/src/resolver/resolver.zig +++ b/src/resolver/resolver.zig @@ -689,6 +689,10 @@ pub const Resolver = struct { var dir: *DirInfo = (r.readDirInfo(path.name.dir) catch continue) orelse continue; result.package_json = result.package_json orelse dir.enclosing_package_json; + if (dir.enclosing_tsconfig_json) |tsconfig| { + result.jsx = tsconfig.mergeJSX(result.jsx); + } + if (dir.getEntries()) |entries| { if (entries.get(path.name.filename)) |query| { const symlink_path = query.entry.symlink(&r.fs.fs); @@ -758,7 +762,12 @@ pub const Resolver = struct { // This implements the module resolution algorithm from node.js, which is // described here: https://nodejs.org/api/modules.html#modules_all_together - var result: Result = Result{ .path_pair = PathPair{ .primary = Path.empty } }; + var result: Result = Result{ + .path_pair = PathPair{ + .primary = Path.empty, + }, + .jsx = r.opts.jsx, + }; // Return early if this is already an absolute path. In addition to asking // the file system whether this is an absolute path, we also explicitly check @@ -788,6 +797,7 @@ pub const Resolver = struct { .package_json = res.package_json, .dirname_fd = res.dirname_fd, .file_fd = res.file_fd, + .jsx = tsconfig.mergeJSX(result.jsx), }; } } @@ -1325,7 +1335,7 @@ pub const Resolver = struct { const source = logger.Source.initPathString(key_path.text, entry.contents); const file_dir = source.path.sourceDir(); - var result = (try TSConfigJSON.parse(r.allocator, r.log, source, @TypeOf(r.caches.json), &r.caches.json)) orelse return null; + var result = (try TSConfigJSON.parse(r.allocator, r.log, source, &r.caches.json, r.opts.jsx.development)) orelse return null; if (result.hasBaseURL()) { // this might leak diff --git a/src/resolver/tsconfig_json.zig b/src/resolver/tsconfig_json.zig index f61b48eb5..bbd58f900 100644 --- a/src/resolver/tsconfig_json.zig +++ b/src/resolver/tsconfig_json.zig @@ -34,6 +34,9 @@ pub const TSConfigJSON = struct { paths: PathsMap, jsx: options.JSX.Pragma = options.JSX.Pragma{}, + has_jsxFactory: bool = false, + has_jsxFragmentFactory: bool = false, + has_jsxImportSource: bool = false, use_define_for_class_fields: ?bool = null, @@ -56,12 +59,30 @@ pub const TSConfigJSON = struct { }); }; + pub fn mergeJSX(this: *const TSConfigJSON, current: options.JSX.Pragma) options.JSX.Pragma { + var out = current; + + if (this.has_jsxFactory) { + out.factory = this.jsx.factory; + } + + if (this.has_jsxFragmentFactory) { + out.fragment = this.jsx.fragment; + } + + if (this.has_jsxImportSource) { + out.import_source = this.jsx.import_source; + } + + return out; + } + pub fn parse( allocator: *std.mem.Allocator, log: *logger.Log, source: logger.Source, - comptime JSONCache: type, - json_cache: *JSONCache, + json_cache: *cache.Json, + is_jsx_development: bool, ) anyerror!?*TSConfigJSON { // Unfortunately "tsconfig.json" isn't actually JSON. It's some other // format that appears to be defined by the implementation details of the @@ -107,20 +128,29 @@ pub const TSConfigJSON = struct { if (compiler_opts.expr.asProperty("jsxFactory")) |jsx_prop| { if (jsx_prop.expr.asString(allocator)) |str| { result.jsx.factory = try parseMemberExpressionForJSX(log, &source, jsx_prop.loc, str, allocator); + result.has_jsxFactory = true; } } // Parse "jsxFragmentFactory" - if (compiler_opts.expr.asProperty("jsxFactory")) |jsx_prop| { + if (compiler_opts.expr.asProperty("jsxFragmentFactory")) |jsx_prop| { if (jsx_prop.expr.asString(allocator)) |str| { result.jsx.fragment = try parseMemberExpressionForJSX(log, &source, jsx_prop.loc, str, allocator); + result.has_jsxFragmentFactory = true; } } // Parse "jsxImportSource" if (compiler_opts.expr.asProperty("jsxImportSource")) |jsx_prop| { if (jsx_prop.expr.asString(allocator)) |str| { - result.jsx.import_source = str; + if (is_jsx_development) { + result.jsx.import_source = std.fmt.allocPrint(allocator, "{s}/jsx-dev-runtime", .{str}) catch unreachable; + } else { + result.jsx.import_source = std.fmt.allocPrint(allocator, "{s}/jsx-runtime", .{str}) catch unreachable; + } + + result.jsx.package_name = options.JSX.Pragma.parsePackageName(str); + result.has_jsxImportSource = true; } } |