mirror of
https://github.com/oven-sh/bun
synced 2026-02-02 15:08:46 +00:00
Add support for reading JSX config from tsconfig.json
This commit is contained in:
@@ -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();
|
||||
|
||||
|
||||
15
integration/snippets/custom-emotion-jsx/file.jsx
Normal file
15
integration/snippets/custom-emotion-jsx/file.jsx
Normal file
@@ -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);
|
||||
}
|
||||
5
integration/snippets/custom-emotion-jsx/tsconfig.json
Normal file
5
integration/snippets/custom-emotion-jsx/tsconfig.json
Normal file
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"jsxImportSource": "@emotion/react"
|
||||
}
|
||||
}
|
||||
@@ -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",
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 = "".*;
|
||||
|
||||
@@ -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)});
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user