Add support for reading JSX config from tsconfig.json

This commit is contained in:
Jarred Sumner
2021-10-06 16:49:26 -07:00
parent 0afec7739b
commit 5370ea71c0
12 changed files with 98 additions and 19 deletions

View File

@@ -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();

View 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);
}

View File

@@ -0,0 +1,5 @@
{
"compilerOptions": {
"jsxImportSource": "@emotion/react"
}
}

View File

@@ -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",

View File

@@ -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;

View File

@@ -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 = "".*;

View File

@@ -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)});

View File

@@ -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 {

View File

@@ -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(

View File

@@ -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;

View File

@@ -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

View File

@@ -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;
}
}