Skip to content

Commit

Permalink
feat(typescript): add support for "jsx: preserve" compiler option
Browse files Browse the repository at this point in the history
When tsc is instructed to preserve jsx, the emitted files have a .jsx
extension for .tsx and .jsx inputs, which means ts_project needs to be
aware of the option to properly define outputs.
  • Loading branch information
duarten committed Mar 30, 2021
1 parent c47b770 commit f6e5fbe
Show file tree
Hide file tree
Showing 7 changed files with 110 additions and 6 deletions.
37 changes: 31 additions & 6 deletions packages/typescript/internal/ts_project.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -269,6 +269,7 @@ def _validate_options_impl(ctx):
allow_js = ctx.attr.allow_js,
declaration = ctx.attr.declaration,
declaration_map = ctx.attr.declaration_map,
preserve_jsx = ctx.attr.preserve_jsx,
composite = ctx.attr.composite,
emit_declaration_only = ctx.attr.emit_declaration_only,
source_map = ctx.attr.source_map,
Expand Down Expand Up @@ -296,6 +297,7 @@ validate_options = rule(
"composite": attr.bool(),
"declaration": attr.bool(),
"declaration_map": attr.bool(),
"preserve_jsx": attr.bool(),
"emit_declaration_only": attr.bool(),
"extends": attr.label(allow_files = [".json"]),
"incremental": attr.bool(),
Expand All @@ -312,10 +314,20 @@ def _is_ts_src(src, allow_js):
return True
return allow_js and (src.endswith(".js") or src.endswith(".jsx"))

def _out_paths(srcs, outdir, rootdir, allow_js, ext):
def _replace_ext(f, ext_map):
cur_ext = f[f.rindex("."):]
new_ext = ext_map.get(cur_ext)
if new_ext != None:
return new_ext
new_ext = ext_map.get("*")
if new_ext != None:
return new_ext
return None

def _out_paths(srcs, outdir, rootdir, allow_js, ext_map):
rootdir_replace_pattern = rootdir + "/" if rootdir else ""
return [
_join(outdir, f[:f.rindex(".")].replace(rootdir_replace_pattern, "") + ext)
_join(outdir, f[:f.rindex(".")].replace(rootdir_replace_pattern, "") + _replace_ext(f, ext_map))
for f in srcs
if _is_ts_src(f, allow_js)
]
Expand All @@ -331,6 +343,7 @@ def ts_project_macro(
declaration = False,
source_map = False,
declaration_map = False,
preserve_jsx = False,
composite = False,
incremental = False,
emit_declaration_only = False,
Expand Down Expand Up @@ -551,6 +564,8 @@ def ts_project_macro(
Instructs Bazel to expect a `.js.map` output for each `.ts` source.
declaration_map: if the `declarationMap` bit is set in the tsconfig.
Instructs Bazel to expect a `.d.ts.map` output for each `.ts` source.
preserve_jsx: if the `jsx` value is set to "preserve" in the tsconfig.
Instructs Bazel to expect a `.jsx` or `.jsx.map` output for each `.tsx` source.
composite: if the `composite` bit is set in the tsconfig.
Instructs Bazel to expect a `.tsbuildinfo` output and a `.d.ts` output for each `.ts` source.
incremental: if the `incremental` bit is set in the tsconfig.
Expand Down Expand Up @@ -620,6 +635,7 @@ def ts_project_macro(
declaration = declaration,
source_map = source_map,
declaration_map = declaration_map,
preserve_jsx = preserve_jsx,
composite = composite,
incremental = incremental,
ts_build_info_file = ts_build_info_file,
Expand Down Expand Up @@ -660,13 +676,22 @@ def ts_project_macro(
typing_maps_outs = []

if not emit_declaration_only:
js_outs.extend(_out_paths(srcs, out_dir, root_dir, allow_js, ".js"))
exts = {
".tsx": ".jsx",
".jsx": ".jsx",
"*": ".js",
} if preserve_jsx else {"*": ".js"}
js_outs.extend(_out_paths(srcs, out_dir, root_dir, allow_js, exts))
if source_map and not emit_declaration_only:
map_outs.extend(_out_paths(srcs, out_dir, root_dir, False, ".js.map"))
exts = {
".tsx": ".jsx.map",
"*": ".js.map",
} if preserve_jsx else {"*": ".js.map"}
map_outs.extend(_out_paths(srcs, out_dir, root_dir, False, exts))
if declaration or composite:
typings_outs.extend(_out_paths(srcs, typings_out_dir, root_dir, allow_js, ".d.ts"))
typings_outs.extend(_out_paths(srcs, typings_out_dir, root_dir, allow_js, {"*": ".d.ts"}))
if declaration_map:
typing_maps_outs.extend(_out_paths(srcs, typings_out_dir, root_dir, allow_js, ".d.ts.map"))
typing_maps_outs.extend(_out_paths(srcs, typings_out_dir, root_dir, allow_js, {"*": ".d.ts.map"}))

if not len(js_outs) and not len(typings_outs):
fail("""ts_project target "//{}:{}" is configured to produce no outputs.
Expand Down
21 changes: 21 additions & 0 deletions packages/typescript/internal/ts_project_options_validator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,25 @@ function main([tsconfigPath, output, target, attrsStr]: string[]): 0|1 {
}
}

const jsxEmit: Record<ts.JsxEmit, string|undefined> =
{
[ts.JsxEmit.None]: 'none',
[ts.JsxEmit.Preserve]: 'preserve',
[ts.JsxEmit.React]: 'react',
[ts.JsxEmit.ReactNative]: 'react-native',
}

function
check_preserve_jsx() {
const attr = 'preserve_jsx'
const jsxVal = options['jsx'] as ts.JsxEmit
if ((jsxVal === ts.JsxEmit.Preserve) !== Boolean(attrs[attr])) {
failures.push(
`attribute ${attr}=${attrs[attr]} does not match compilerOptions.jsx=${jsxEmit[jsxVal]}`);
buildozerCmds.push(`set ${attr} ${jsxVal === ts.JsxEmit.Preserve ? 'True' : 'False'}`);
}
}

check('allowJs', 'allow_js');
check('declarationMap', 'declaration_map');
check('emitDeclarationOnly', 'emit_declaration_only');
Expand All @@ -72,6 +91,7 @@ function main([tsconfigPath, output, target, attrsStr]: string[]): 0|1 {
check('declaration');
check('incremental');
check('tsBuildInfoFile', 'ts_build_info_file');
check_preserve_jsx();

if (failures.length > 0) {
console.error(`ERROR: ts_project rule ${
Expand All @@ -98,6 +118,7 @@ function main([tsconfigPath, output, target, attrsStr]: string[]): 0|1 {
// source_map: ${attrs.source_map}
// emit_declaration_only: ${attrs.emit_declaration_only}
// ts_build_info_file: ${attrs.ts_build_info_file}
// preserve_jsx: ${attrs.preserve_jsx}
`,
'utf-8');
return 0;
Expand Down
38 changes: 38 additions & 0 deletions packages/typescript/test/ts_project/jsx/BUILD.bazel
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
load("@build_bazel_rules_nodejs//:index.bzl", "nodejs_test")
load("//packages/typescript:index.bzl", "ts_project")

# Ensure that a.js produces outDir/a.js, outDir/a.d.ts, and outDir/a.d.ts.map
SRCS = [
"a.tsx",
"b.jsx",
]

ts_project(
name = "tsconfig",
srcs = SRCS,
allow_js = True,
declaration = True,
declaration_map = True,
out_dir = "out",
preserve_jsx = True,
source_map = True,
)

filegroup(
name = "types",
srcs = [":tsconfig"],
output_group = "types",
)

nodejs_test(
name = "test",
data = [
":tsconfig",
":types",
],
entry_point = "verify-preserve.js",
templated_args = [
"$(locations :types)",
"$(locations :tsconfig)",
],
)
1 change: 1 addition & 0 deletions packages/typescript/test/ts_project/jsx/a.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export const A = <div>a</div>
1 change: 1 addition & 0 deletions packages/typescript/test/ts_project/jsx/b.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export const B = <div>b</div>
10 changes: 10 additions & 0 deletions packages/typescript/test/ts_project/jsx/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"compilerOptions": {
"allowJs": true,
"sourceMap": true,
"declaration": true,
"declarationMap": true,
"jsx": "preserve",
"types": []
}
}
8 changes: 8 additions & 0 deletions packages/typescript/test/ts_project/jsx/verify-preserve.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
const assert = require('assert');

const files = process.argv.slice(2);
assert.ok(files.some(f => f.endsWith('out/a.d.ts')), 'Missing a.d.ts');
assert.ok(files.some(f => f.endsWith('out/a.d.ts.map')), 'Missing a.d.ts.map');
assert.ok(files.some(f => f.endsWith('out/a.jsx.map'), 'Missing a.jsx.map'));
assert.ok(files.some(f => f.endsWith('out/a.jsx'), 'Missing a.jsx'));
assert.ok(files.some(f => f.endsWith('out/b.jsx'), 'Missing b.jsx'));

0 comments on commit f6e5fbe

Please sign in to comment.