aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGravatar Jarred Sumner <jarred@jarredsumner.com> 2021-10-06 16:49:26 -0700
committerGravatar Jarred Sumner <jarred@jarredsumner.com> 2021-10-06 16:49:26 -0700
commit5370ea71c0b3a6759c481f96608ce855bd043bc8 (patch)
treecf75dd798d1109fc3d06cfc8621c8aefc8a93183
parent0afec7739b9f1df8d9cf565f3fed19e663162734 (diff)
downloadbun-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.js1
-rw-r--r--integration/snippets/custom-emotion-jsx/file.jsx15
-rw-r--r--integration/snippets/custom-emotion-jsx/tsconfig.json5
-rw-r--r--integration/snippets/package.json2
-rw-r--r--src/bundler.zig17
-rw-r--r--src/cli.zig2
-rw-r--r--src/cli/bun_command.zig4
-rw-r--r--src/http.zig3
-rw-r--r--src/javascript/jsc/javascript.zig1
-rw-r--r--src/options.zig15
-rw-r--r--src/resolver/resolver.zig14
-rw-r--r--src/resolver/tsconfig_json.zig38
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;
}
}