diff --git a/examples/BUILD.bazel b/examples/BUILD.bazel index 1b31481ce7..b5228ea44e 100644 --- a/examples/BUILD.bazel +++ b/examples/BUILD.bazel @@ -64,6 +64,9 @@ example_integration_test( name = "examples_react_webpack", # TODO: add some tests in the example bazel_commands = ["build ..."], + npm_packages = { + "//packages/typescript:npm_package": "@bazel/typescript", + }, # TODO(alexeagle): somehow this is broken by the new node-patches based node_patches script # ERROR: D:/temp/tmp-6900sejcsrcttpdb/BUILD.bazel:37:1: output 'app.bundle.js' was not created tags = ["no-bazelci-windows"], diff --git a/examples/react_webpack/BUILD.bazel b/examples/react_webpack/BUILD.bazel index ffeb3a3891..2a834eebd4 100644 --- a/examples/react_webpack/BUILD.bazel +++ b/examples/react_webpack/BUILD.bazel @@ -1,7 +1,7 @@ load("@npm//http-server:index.bzl", "http_server") load("@npm//sass:index.bzl", "sass") -load("@npm//typescript:index.bzl", "tsc") load("@npm//webpack-cli:index.bzl", webpack = "webpack_cli") +load("@npm_bazel_typescript//:index.bzl", "ts_project") sass( name = "styles", @@ -13,22 +13,8 @@ sass( data = ["styles.scss"], ) -tsc( - name = "compile", - outs = ["index.js"], - args = [ - "$(execpath index.tsx)", - "$(execpath types.d.ts)", - "--outDir", - "$(RULEDIR)", - "--lib", - "es2015,dom", - "--jsx", - "react", - ], - data = [ - "index.tsx", - "types.d.ts", +ts_project( + deps = [ "@npm//@types", "@npm//csstype", ], diff --git a/examples/react_webpack/WORKSPACE b/examples/react_webpack/WORKSPACE index 0022821781..a9086fba54 100644 --- a/examples/react_webpack/WORKSPACE +++ b/examples/react_webpack/WORKSPACE @@ -19,3 +19,7 @@ yarn_install( package_json = "//:package.json", yarn_lock = "//:yarn.lock", ) + +load("@npm//:install_bazel_dependencies.bzl", "install_bazel_dependencies") + +install_bazel_dependencies() diff --git a/examples/react_webpack/package.json b/examples/react_webpack/package.json index 97710aace2..add1df42c7 100644 --- a/examples/react_webpack/package.json +++ b/examples/react_webpack/package.json @@ -4,6 +4,7 @@ "@bazel/bazelisk": "^1.3.0", "@bazel/buildifier": "^0.29.0", "@bazel/ibazel": "^0.12.2", + "@bazel/typescript": "^1.4.1", "@types/react": "^16.9.5", "@types/react-dom": "^16.9.1", "css-loader": "^3.2.0", diff --git a/examples/react_webpack/tsconfig.json b/examples/react_webpack/tsconfig.json new file mode 100644 index 0000000000..0ece51d45f --- /dev/null +++ b/examples/react_webpack/tsconfig.json @@ -0,0 +1,6 @@ +{ + "compilerOptions": { + "jsx": "react", + "lib": ["ES2015", "DOM"] + } +} \ No newline at end of file diff --git a/packages/typescript/docs/install.md b/packages/typescript/docs/install.md index f6b0537e73..7f416cda33 100644 --- a/packages/typescript/docs/install.md +++ b/packages/typescript/docs/install.md @@ -2,47 +2,33 @@ The TypeScript rules integrate the TypeScript compiler with Bazel. -Looking for Karma rules `ts_web_test` and `karma_web_test`? -These are now documented in the README at http://npmjs.com/package/@bazel/karma - ## Alternatives -This package provides Bazel wrappers around the TypeScript compiler, and are how we compile TS code at Google. - -These rules are opinionated, for example: - -- Your TS code must compile under the `--declaration` flag so that downstream libraries depend only on types, not implementation. This makes Bazel faster by avoiding cascading rebuilds in cases where the types aren't changed. -- We control the output format and module syntax so that downstream rules can rely on them. +This package provides Bazel wrappers around the TypeScript compiler. -They are also fast and optimized: +At a high level, there are two alternatives provided: `ts_project` and `ts_library`. +This section describes the trade-offs between these rules. -- We keep a running TypeScript compile running as a daemon, using Bazel workers. This process avoids re-parse and re-JIT of the >1MB `typescript.js` and keeps cached bound ASTs for input files which saves time. +`ts_project` simply runs `tsc --project`, with Bazel knowing which outputs to expect based on the TypeScript compiler options, and with interoperability with other TypeScript rules via a Bazel Provider (DeclarationInfo) that transmits the type information. +It is intended as an easy on-boarding for existing TypeScript code and should be familiar if your background is in frontend ecosystem idioms. +Any behavior of `ts_project` should be reproducible outside of Bazel, with a couple of caveats noted in the rule documentation below. -We understand this is a tradeoff. If you want to use the plain TypeScript compiler provided by the TS team at Microsoft, you can do this by calling its CLI directly. For example, +> We used to recommend using the `tsc` rule directly from the `typescript` project, like +> `load("@npm//typescript:index.bzl", "tsc")` +> However `ts_project` is strictly better and should be used instead. -```python -load("@npm//typescript:index.bzl", "tsc") +`ts_library` is an open-sourced version of the rule we use to compile TS code at Google. +It should be familiar if your background is in Bazel idioms. +It is very complex, involving code generation of the `tsconfig.json` file, a custom compiler binary, and a lot of extra features. +It is also opinionated, and may not work with existing TypeScript code. For example: -srcs = glob(["*.ts"]) -deps = ["@npm//@types/node"] +- Your TS code must compile under the `--declaration` flag so that downstream libraries depend only on types, not implementation. This makes Bazel faster by avoiding cascading rebuilds in cases where the types aren't changed. +- We control the output format and module syntax so that downstream rules can rely on them. -tsc( - name = "compile", - data = srcs + deps, - outs = [s.replace(".ts", ext) for ext in [".js", ".d.ts"] for s in srcs], - args = [ - "--outDir", - "$(RULEDIR)", - "--lib", - "es2017,dom", - "--downlevelIteration", - "--declaration", - ] + [ - "$(location %s)" % s - for s in srcs - ], -) -``` +On the other hand, `ts_library` is also fast and optimized. +We keep a running TypeScript compile running as a daemon, using Bazel workers. +This process avoids re-parse and re-JIT of the >1MB `typescript.js` and keeps cached bound ASTs for input files which saves time. +We also produce JS code which can be loaded faster (using named AMD module format) and which can be consumed by the Closure Compiler (via integration with [tsickle](https://github.com/angular/tsickle)). ## Installation diff --git a/packages/typescript/src/index.bzl b/packages/typescript/src/index.bzl index d1b620ee73..e45c0c5cb0 100644 --- a/packages/typescript/src/index.bzl +++ b/packages/typescript/src/index.bzl @@ -19,6 +19,7 @@ Users should not load files under "/internal" load("//internal:build_defs.bzl", _ts_library = "ts_library_macro") load("//internal:ts_config.bzl", _ts_config = "ts_config") +load("//internal:ts_project.bzl", _ts_project = "ts_project_macro") load("//internal:ts_repositories.bzl", _ts_setup_workspace = "ts_setup_workspace") load("//internal/devserver:ts_devserver.bzl", _ts_devserver = "ts_devserver_macro") @@ -26,4 +27,5 @@ ts_setup_workspace = _ts_setup_workspace ts_library = _ts_library ts_config = _ts_config ts_devserver = _ts_devserver +ts_project = _ts_project # If adding rules here also add to index.docs.bzl diff --git a/packages/typescript/src/index.docs.bzl b/packages/typescript/src/index.docs.bzl index a44d119049..2ec770c3d7 100644 --- a/packages/typescript/src/index.docs.bzl +++ b/packages/typescript/src/index.docs.bzl @@ -20,12 +20,14 @@ So this is a copy of index.bzl with macro indirection removed. load("//internal:build_defs.bzl", _ts_library = "ts_library") load("//internal:ts_config.bzl", _ts_config = "ts_config") +load("//internal:ts_project.bzl", _ts_project = "ts_project_macro") load("//internal:ts_repositories.bzl", _ts_setup_workspace = "ts_setup_workspace") load("//internal/devserver:ts_devserver.bzl", _ts_devserver = "ts_devserver") ts_setup_workspace = _ts_setup_workspace ts_library = _ts_library ts_config = _ts_config +ts_project = _ts_project ts_devserver = _ts_devserver # DO NOT ADD MORE rules here unless they appear in the generated docsite. # Run yarn stardoc to re-generate the docsite. diff --git a/packages/typescript/src/internal/BUILD.bazel b/packages/typescript/src/internal/BUILD.bazel index daf761f170..839caa36a6 100644 --- a/packages/typescript/src/internal/BUILD.bazel +++ b/packages/typescript/src/internal/BUILD.bazel @@ -47,6 +47,7 @@ filegroup( srcs = [ "build_defs.bzl", "ts_config.bzl", + "ts_project.bzl", "ts_repositories.bzl", "//internal/devserver:package_contents", ], diff --git a/packages/typescript/src/internal/ts_project.bzl b/packages/typescript/src/internal/ts_project.bzl new file mode 100644 index 0000000000..a9df74a363 --- /dev/null +++ b/packages/typescript/src/internal/ts_project.bzl @@ -0,0 +1,310 @@ +"ts_project rule" + +load("@build_bazel_rules_nodejs//:providers.bzl", "DeclarationInfo", "NpmPackageInfo", "run_node") + +_DEFAULT_TSC = "@npm//typescript/bin:tsc" + +_ATTRS = { + # NB: no restriction on extensions here, because tsc sometimes adds type-check support + # for more file kinds (like require('some.json')) and also + # if you swap out the `compiler` attribute (like with ngtsc) + # that compiler might allow more sources than tsc does. + "srcs": attr.label_list(allow_files = True, mandatory = True), + "args": attr.string_list(), + "extends": attr.label_list(allow_files = [".json"]), + "tsc": attr.label(default = Label(_DEFAULT_TSC), executable = True, cfg = "host"), + "tsconfig": attr.label(mandatory = True, allow_single_file = [".json"]), + "deps": attr.label_list(providers = [DeclarationInfo]), +} + +# tsc knows how to produce the following kinds of output files. +# NB: the macro `ts_project_macro` will set these outputs based on user +# telling us which settings are enabled in the tsconfig for this project. +_OUTPUTS = { + "buildinfo_out": attr.output(), + "js_outs": attr.output_list(), + "map_outs": attr.output_list(), + "typing_maps_outs": attr.output_list(), + "typings_outs": attr.output_list(), +} + +_TsConfigInfo = provider( + doc = """Passes tsconfig.json files to downstream compilations so that TypeScript can read them. + This is needed to support Project References""", + fields = { + "tsconfigs": "depset of tsconfig.json files", + }, +) + +def _ts_project_impl(ctx): + arguments = ctx.actions.args() + + # Add user specified arguments *before* rule supplied arguments + arguments.add_all(ctx.attr.args) + + arguments.add_all([ + "--project", + ctx.file.tsconfig.short_path, + "--outDir", + "/".join([ctx.bin_dir.path, ctx.label.package]), + ]) + if len(ctx.outputs.typings_outs) > 0: + arguments.add_all([ + "--declarationDir", + "/".join([ctx.bin_dir.path, ctx.label.package]), + ]) + + # When users report problems, we can ask them to re-build with + # --define=VERBOSE_LOGS=1 + # so anything that's useful to diagnose rule failures belongs here + if "VERBOSE_LOGS" in ctx.var.keys(): + arguments.add_all([ + # What files were in the ts.Program + "--listFiles", + # Did tsc write all outputs to the place we expect to find them? + "--listEmittedFiles", + # Why did module resolution fail? + "--traceResolution", + # Why was the build slow? + "--diagnostics", + "--extendedDiagnostics", + ]) + + deps_depsets = [] + for dep in ctx.attr.deps: + if _TsConfigInfo in dep: + deps_depsets.append(dep[_TsConfigInfo].tsconfigs) + if NpmPackageInfo in dep: + # TODO: we could maybe filter these to be tsconfig.json or *.d.ts only + # we don't expect tsc wants to read any other files from npm packages. + deps_depsets.append(dep[NpmPackageInfo].sources) + if DeclarationInfo in dep: + deps_depsets.append(dep[DeclarationInfo].transitive_declarations) + + inputs = ctx.files.srcs + depset(transitive = deps_depsets).to_list() + [ctx.file.tsconfig] + if ctx.attr.extends: + inputs.extend(ctx.files.extends) + outputs = ctx.outputs.js_outs + ctx.outputs.map_outs + ctx.outputs.typings_outs + ctx.outputs.typing_maps_outs + if ctx.outputs.buildinfo_out: + outputs.append(ctx.outputs.buildinfo_out) + runtime_outputs = depset(ctx.outputs.js_outs + ctx.outputs.map_outs) + typings_outputs = ctx.outputs.typings_outs + [s for s in ctx.files.srcs if s.path.endswith(".d.ts")] + + if len(outputs) > 0: + run_node( + ctx, + inputs = inputs, + arguments = [arguments], + outputs = outputs, + executable = "tsc", + progress_message = "Compiling TypeScript project %s" % ctx.file.tsconfig.short_path, + ) + + return [ + DeclarationInfo( + declarations = depset(typings_outputs), + transitive_declarations = depset(typings_outputs, transitive = [ + dep[DeclarationInfo].transitive_declarations + for dep in ctx.attr.deps + ]), + ), + # DefaultInfo is what you see on the command-line for a built library, + # and determines what files are used by a simple non-provider-aware + # downstream library. + # Only the JavaScript outputs are intended for use in non-TS-aware + # dependents. + DefaultInfo( + files = runtime_outputs, + runfiles = ctx.runfiles( + transitive_files = runtime_outputs, + collect_default = True, + ), + ), + _TsConfigInfo(tsconfigs = depset([ctx.file.tsconfig], transitive = [ + dep[_TsConfigInfo].tsconfigs + for dep in ctx.attr.deps + if _TsConfigInfo in dep + ])), + ] + +ts_project = rule( + implementation = _ts_project_impl, + attrs = dict(_ATTRS, **_OUTPUTS), +) + +def _out_paths(srcs, ext): + return [f[:f.rindex(".")] + ext for f in srcs if not f.endswith(".d.ts")] + +def ts_project_macro( + name = "tsconfig", + tsconfig = None, + srcs = None, + args = [], + deps = [], + extends = None, + declaration = False, + source_map = False, + declaration_map = False, + composite = False, + incremental = False, + emit_declaration_only = False, + tsc = _DEFAULT_TSC, + **kwargs): + """Compiles one TypeScript project using `tsc --project` + + This is a drop-in replacement for the `tsc` rule automatically generated for the "typescript" + package, typically loaded from `@npm//typescript:index.bzl`. Unlike bare `tsc`, this rule understands + the Bazel interop mechanism (Providers) so that this rule works with others that produce or consume + TypeScript typings (`.d.ts` files). + + Unlike `ts_library`, this rule is the thinnest possible layer of Bazel interoperability on top + of the TypeScript compiler. It shifts the burden of configuring TypeScript into the tsconfig.json file. + See https://github.com/bazelbuild/rules_nodejs/blob/master/docs/TypeScript.md#alternatives + for more details about the trade-offs between the two rules. + + Some TypeScript options affect which files are emitted, and Bazel wants to know these ahead-of-time. + So several options from the tsconfig file must be mirrored as attributes to ts_project. + See https://www.typescriptlang.org/v2/en/tsconfig for a listing of the TypeScript options. + + Any code that works with `tsc` should work with `ts_project` with a few caveats: + + - Bazel requires that the `outDir` (and `declarationDir`) be set to + `bazel-out/[target architecture]/bin/path/to/package` + so we override whatever settings appear in your tsconfig. + - Bazel expects that each output is produced by a single rule. + Thus if you have two `ts_project` rules with overlapping sources (the same `.ts` file + appears in more than one) then you get an error about conflicting `.js` output + files if you try to build both together. + Worse, if you build them separately then the output directory will contain whichever + one you happened to build most recently. This is highly discouraged. + + > Note: in order for TypeScript to resolve relative references to the bazel-out folder, + > we recommend that the base tsconfig contain a rootDirs section that includes all + > possible locations they may appear. + > + > We hope this will not be needed in some future release of TypeScript. + > Follow https://github.com/microsoft/TypeScript/issues/37257 for more info. + > + > For example, if the base tsconfig file relative to the workspace root is + > `path/to/tsconfig.json` then you should configure like: + > + > ``` + > "compilerOptions": { + > "rootDirs": [ + > ".", + > "../../bazel-out/darwin-fastbuild/bin/path/to", + > "../../bazel-out/k8-fastbuild/bin/path/to", + > "../../bazel-out/x64_windows-fastbuild/bin/path/to", + > "../../bazel-out/darwin-dbg/bin/path/to", + > "../../bazel-out/k8-dbg/bin/path/to", + > "../../bazel-out/x64_windows-dbg/bin/path/to", + > ] + > } + > ``` + + ### Issues when running non-sandboxed + + When using a non-sandboxed spawn strategy (which is the default on Windows), you may + observe these problems which require workarounds: + + 1) Bazel deletes outputs from the previous execution before running `tsc`. + This causes a problem with TypeScript's incremental mode: if the `.tsbuildinfo` file + is not known to be an output of the rule, then Bazel will leave it in the output + directory, and when `tsc` runs, it may see that the outputs written by the prior + invocation are up-to-date and skip the emit of these files. This will cause Bazel + to intermittently fail with an error that some outputs were not written. + This is why we depend on `composite` and/or `incremental` attributes to be provided, + so we can tell Bazel to expect a `.tsbuildinfo` output to ensure it is deleted before a + subsequent compilation. + At present, we don't do anything useful with the `.tsbuildinfo` output, and this rule + does not actually have incremental behavior. Deleting the file is actually + counter-productive in terms of TypeScript compile performance. + Follow https://github.com/bazelbuild/rules_nodejs/issues/1726 + + 2) When using Project References, TypeScript will expect to verify that the outputs of referenced + projects are up-to-date with respect to their inputs. + (This is true even without using the `--build` option). + When using a non-sandboxed spawn strategy, `tsc` can read the sources from other `ts_project` + rules in your project, and will expect that the `tsconfig.json` file for those references will + indicate where the outputs were written. However the `outDir` is determined by this Bazel rule so + it cannot be known from reading the `tsconfig.json` file. + This problem is manifested as a TypeScript diagnostic like + `error TS6305: Output file '/path/to/execroot/a.d.ts' has not been built from source file '/path/to/execroot/a.ts'.` + As a workaround, you can give the Windows "fastbuild" output directory as the `outDir` in your tsconfig file. + On other platforms, the value isn't read so it does no harm. + See https://github.com/bazelbuild/rules_nodejs/tree/master/packages/typescript/test/ts_project as an example. + We hope this will be fixed in a future release of TypeScript; + follow https://github.com/microsoft/TypeScript/issues/37378 + + 3) When TypeScript encounters an import statement, it adds the source file resolved by that reference + to the program. However you may have included that source file in a different project, so this causes + the problem mentioned above where a source file is in multiple programs. + (Note, if you use Project References this is not the case, TS will know the referenced + file is part of the other program.) + This will result in duplicate emit for the same file, which produces an error + since the files written to the output tree are read-only. + Workarounds include using using Project References, or simply grouping the whole compilation + into one program (if this doesn't exceed your time budget). + + Args: + name: A name for the target. + + We recommend you use the basename (no `.json` extension) of the tsconfig file that should be compiled. + + srcs: List of labels of TypeScript source files to be provided to the compiler. + + If absent, defaults to `**/*.ts[x]` (all TypeScript files in the package). + + deps: List of labels of other rules that produce TypeScript typings (.d.ts files) + + tsconfig: Label of the tsconfig.json file to use for the compilation. + + By default, we add `.json` to the `name` attribute. + + extends: List of labels of tsconfig file(s) referenced in `extends` section of tsconfig. + + Must include any tsconfig files "chained" by extends clauses. + + args: List of strings of additional command-line arguments to pass to tsc. + + tsc: Label of the TypeScript compiler binary to run. + + Override this if your npm_install or yarn_install isn't named "npm" + For example, `tsc = "@my_deps//typescript/bin:tsc"` + Or you can pass a custom compiler binary instead. + + declaration: if the `declaration` bit is set in the tsconfig. + Instructs Bazel to expect a `.d.ts` output for each `.ts` source. + source_map: if the `sourceMap` bit is set in the tsconfig. + 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. + 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. + Instructs Bazel to expect a `.tsbuildinfo` output. + emit_declaration_only: if the `emitDeclarationOnly` bit is set in the tsconfig. + Instructs Bazel *not* to expect `.js` or `.js.map` outputs for `.ts` sources. + """ + + if srcs == None: + srcs = native.glob(["**/*.ts", "**/*.tsx"]) + + if tsconfig == None: + tsconfig = name + ".json" + + ts_project( + name = name, + srcs = srcs, + deps = deps, + args = args, + tsconfig = tsconfig, + extends = extends, + js_outs = _out_paths(srcs, ".js") if not emit_declaration_only else [], + map_outs = _out_paths(srcs, ".js.map") if source_map and not emit_declaration_only else [], + typings_outs = _out_paths(srcs, ".d.ts") if declaration or composite else [], + typing_maps_outs = _out_paths(srcs, ".d.ts.map") if declaration_map else [], + buildinfo_out = tsconfig[:-5] + ".tsbuildinfo" if composite or incremental else None, + tsc = tsc, + **kwargs + ) diff --git a/packages/typescript/test/ts_project/BUILD b/packages/typescript/test/ts_project/BUILD new file mode 100644 index 0000000000..96b6172fd3 --- /dev/null +++ b/packages/typescript/test/ts_project/BUILD @@ -0,0 +1 @@ +exports_files(["tsconfig-base.json"]) diff --git a/packages/typescript/test/ts_project/a/BUILD.bazel b/packages/typescript/test/ts_project/a/BUILD.bazel new file mode 100644 index 0000000000..ebbb07a282 --- /dev/null +++ b/packages/typescript/test/ts_project/a/BUILD.bazel @@ -0,0 +1,8 @@ +load("@npm_bazel_typescript//:index.bzl", "ts_project") + +ts_project( + composite = True, + extends = ["//packages/typescript/test/ts_project:tsconfig-base.json"], + source_map = True, + visibility = ["//packages/typescript/test:__subpackages__"], +) diff --git a/packages/typescript/test/ts_project/a/a.ts b/packages/typescript/test/ts_project/a/a.ts new file mode 100644 index 0000000000..a668b7e336 --- /dev/null +++ b/packages/typescript/test/ts_project/a/a.ts @@ -0,0 +1 @@ +export const a: string = 'hello'; diff --git a/packages/typescript/test/ts_project/a/tsconfig.json b/packages/typescript/test/ts_project/a/tsconfig.json new file mode 100644 index 0000000000..8fa4bcb5e0 --- /dev/null +++ b/packages/typescript/test/ts_project/a/tsconfig.json @@ -0,0 +1,20 @@ +{ + "extends": "../tsconfig-base.json", + "compilerOptions": { + "sourceMap": true, + // WORKAROUND https://github.com/microsoft/TypeScript/issues/37378 + // When running the action with standalone strategy (no sandbox), + // tsc will see the .ts source files + // from our dependencies. It then tries to do an up-to-date check on the outputs. + // We are forced to have outDir in the tsconfig in this case (on windows) + // so that tsc can know where to look for the .d.ts outputs + // Error looks like + // (05:54:49) ERROR: C:/b/bk-windows-b4qr/bazel/rules-nodejs-nodejs/packages/typescript/test/ts_project/b/BUILD.bazel:6:1: Compiling TypeScript project packages/typescript/test/ts_project/b/tsconfig.json failed (Exit 2): tsc.bat failed: error executing command + // cd C:/b/uuxnwop3/execroot/build_bazel_rules_nodejs + // SET COMPILATION_MODE=fastbuild + // bazel-out/host/bin/external/npm/typescript/bin/tsc.bat -p packages/typescript/test/ts_project/b/tsconfig.json --outDir bazel-out/x64_windows-fastbuild/bin/packages/typescript/test/ts_project/b --declarationDir bazel-out/x64_windows-fastbuild/bin/packages/typescript/test/ts_project/b --bazel_node_modules_manifest=bazel-out/x64_windows-fastbuild/bin/packages/typescript/test/ts_project/b/_tsconfig.module_mappings.json --nobazel_patch_module_resolver + // Execution platform: @local_config_platform//:host + // packages/typescript/test/ts_project/b/b.ts(1,17): error TS6305: Output file 'C:/b/uuxnwop3/execroot/build_bazel_rules_nodejs/packages/typescript/test/ts_project/a/a.d.ts' has not been built from source file 'C:/b/uuxnwop3/execroot/build_bazel_rules_nodejs/packages/typescript/test/ts_project/a/a.ts'. + "outDir": "../../../../../bazel-out/x64_windows-fastbuild/bin/packages/typescript/test/ts_project/a", + } +} \ No newline at end of file diff --git a/packages/typescript/test/ts_project/b/BUILD.bazel b/packages/typescript/test/ts_project/b/BUILD.bazel new file mode 100644 index 0000000000..22304b501c --- /dev/null +++ b/packages/typescript/test/ts_project/b/BUILD.bazel @@ -0,0 +1,33 @@ +load("@npm_bazel_jasmine//:index.from_src.bzl", "jasmine_node_test") +load("@npm_bazel_typescript//:index.bzl", "ts_project") + +package(default_visibility = ["//packages/typescript/test:__subpackages__"]) + +ts_project( + name = "tsconfig", # This will use ./tsconfig.json + srcs = [":b.ts"], + # just a test for the pass-through args attribute + args = ["--emitBOM"], + composite = True, + extends = ["//packages/typescript/test/ts_project:tsconfig-base.json"], + deps = ["//packages/typescript/test/ts_project/a:tsconfig"], +) + +ts_project( + name = "tsconfig-test", # This will use ./tsconfig-test.json + testonly = True, + srcs = [":b.spec.ts"], + composite = True, + extends = ["//packages/typescript/test/ts_project:tsconfig-base.json"], + deps = [ + ":tsconfig", + "@npm//@types/jasmine", + "@npm//@types/node", + ], +) + +jasmine_node_test( + name = "test", + srcs = ["b.spec.js"], + data = [":tsconfig"], +) diff --git a/packages/typescript/test/ts_project/b/b.spec.ts b/packages/typescript/test/ts_project/b/b.spec.ts new file mode 100644 index 0000000000..18fb545b46 --- /dev/null +++ b/packages/typescript/test/ts_project/b/b.spec.ts @@ -0,0 +1,13 @@ +import {sayHello} from './b'; + +describe('b', () => { + it('should say hello', () => { + let captured: string = ''; + console.log = (s: string) => captured = s; + sayHello(' world'); + expect(captured).toBe('hello world'); + }); + it('should include byte-order mark since that was passed in args attr', () => { + expect(require('fs').readFileSync(require.resolve('./b'), 'utf-8')[0]).toBe('\ufeff'); + }); +}); diff --git a/packages/typescript/test/ts_project/b/b.ts b/packages/typescript/test/ts_project/b/b.ts new file mode 100644 index 0000000000..1879b29fa3 --- /dev/null +++ b/packages/typescript/test/ts_project/b/b.ts @@ -0,0 +1,5 @@ +import {a} from '../a/a'; + +export function sayHello(f: string) { + console.log(a + f); +} diff --git a/packages/typescript/test/ts_project/b/tsconfig-test.json b/packages/typescript/test/ts_project/b/tsconfig-test.json new file mode 100644 index 0000000000..288ba2408c --- /dev/null +++ b/packages/typescript/test/ts_project/b/tsconfig-test.json @@ -0,0 +1,7 @@ +{ + "extends": "../tsconfig-base.json", + "references": [ + {"path": "./"} + ], + "include": ["*.spec.ts"] +} diff --git a/packages/typescript/test/ts_project/b/tsconfig.json b/packages/typescript/test/ts_project/b/tsconfig.json new file mode 100644 index 0000000000..4746a3c94f --- /dev/null +++ b/packages/typescript/test/ts_project/b/tsconfig.json @@ -0,0 +1,11 @@ +{ + "extends": "../tsconfig-base.json", + "references": [ + {"path": "../a"} + ], + "compilerOptions": { + // WORKAROUND - See comment in ../a/tsconfig.json + "outDir": "../../../../../bazel-out/x64_windows-fastbuild/bin/packages/typescript/test/ts_project/b" + }, + "exclude": ["*.spec.ts"] +} diff --git a/packages/typescript/test/ts_project/c/BUILD.bazel b/packages/typescript/test/ts_project/c/BUILD.bazel new file mode 100644 index 0000000000..71b03bd5c2 --- /dev/null +++ b/packages/typescript/test/ts_project/c/BUILD.bazel @@ -0,0 +1,10 @@ +load("@npm_bazel_typescript//:index.bzl", "ts_project") + +ts_project( + name = "compile", + srcs = [":c.ts"], + composite = True, + extends = ["//packages/typescript/test/ts_project:tsconfig-base.json"], + tsconfig = "tsconfig.json", + deps = ["//packages/typescript/test/ts_project/b:tsconfig"], +) diff --git a/packages/typescript/test/ts_project/c/c.ts b/packages/typescript/test/ts_project/c/c.ts new file mode 100644 index 0000000000..5c4b70280b --- /dev/null +++ b/packages/typescript/test/ts_project/c/c.ts @@ -0,0 +1,5 @@ +import {a} from '../a/a'; // SHOULD FAIL HERE per https://github.com/microsoft/TypeScript/issues/36743 +import {sayHello} from '../b/b'; + +sayHello('world'); +console.error(a); diff --git a/packages/typescript/test/ts_project/c/tsconfig.json b/packages/typescript/test/ts_project/c/tsconfig.json new file mode 100644 index 0000000000..90cd4e5d5e --- /dev/null +++ b/packages/typescript/test/ts_project/c/tsconfig.json @@ -0,0 +1,8 @@ +{ + "extends": "../tsconfig-base.json", + "references": [ + {"path": "../b/tsconfig.json"} + ], + // NB: we don't need the compilerOptions.outDir Windows workaround here + // because there is no project that references this one. +} \ No newline at end of file diff --git a/packages/typescript/test/ts_project/empty_intermediate/BUILD.bazel b/packages/typescript/test/ts_project/empty_intermediate/BUILD.bazel new file mode 100644 index 0000000000..0877687cb5 --- /dev/null +++ b/packages/typescript/test/ts_project/empty_intermediate/BUILD.bazel @@ -0,0 +1,18 @@ +load("@npm_bazel_typescript//:index.bzl", "ts_project") + +ts_project( + name = "tsconfig-a", + srcs = ["a.d.ts"], +) + +ts_project( + name = "tsconfig-b", + srcs = [], + deps = ["tsconfig-a"], +) + +ts_project( + name = "tsconfig-c", + srcs = ["c.ts"], + deps = ["tsconfig-b"], +) diff --git a/packages/typescript/test/ts_project/empty_intermediate/a.d.ts b/packages/typescript/test/ts_project/empty_intermediate/a.d.ts new file mode 100644 index 0000000000..106d610ab9 --- /dev/null +++ b/packages/typescript/test/ts_project/empty_intermediate/a.d.ts @@ -0,0 +1 @@ +export declare const a: string; diff --git a/packages/typescript/test/ts_project/empty_intermediate/c.ts b/packages/typescript/test/ts_project/empty_intermediate/c.ts new file mode 100644 index 0000000000..6431a4f84c --- /dev/null +++ b/packages/typescript/test/ts_project/empty_intermediate/c.ts @@ -0,0 +1,3 @@ +import {a} from './a'; + +console.log(a); diff --git a/packages/typescript/test/ts_project/empty_intermediate/tsconfig-a.json b/packages/typescript/test/ts_project/empty_intermediate/tsconfig-a.json new file mode 100644 index 0000000000..3b2c25a809 --- /dev/null +++ b/packages/typescript/test/ts_project/empty_intermediate/tsconfig-a.json @@ -0,0 +1,6 @@ +{ + "files": ["a.ts"], + "compilerOptions": { + "declaration": true + } +} \ No newline at end of file diff --git a/packages/typescript/test/ts_project/empty_intermediate/tsconfig-b.json b/packages/typescript/test/ts_project/empty_intermediate/tsconfig-b.json new file mode 100644 index 0000000000..6f5fba878a --- /dev/null +++ b/packages/typescript/test/ts_project/empty_intermediate/tsconfig-b.json @@ -0,0 +1,3 @@ +{ + "files": [] +} \ No newline at end of file diff --git a/packages/typescript/test/ts_project/empty_intermediate/tsconfig-c.json b/packages/typescript/test/ts_project/empty_intermediate/tsconfig-c.json new file mode 100644 index 0000000000..dcdbec4409 --- /dev/null +++ b/packages/typescript/test/ts_project/empty_intermediate/tsconfig-c.json @@ -0,0 +1,15 @@ +{ + "files": ["c.ts"], + "compilerOptions": { + // Help TypeScript locate the a.d.ts file from previous compilation. Needed when running in a sandbox or remote. + "rootDirs": [ + ".", + "../../../../../bazel-out/darwin-fastbuild/bin/packages/typescript/test/ts_project/empty_intermediate", + "../../../../../bazel-out/k8-fastbuild/bin/packages/typescript/test/ts_project/empty_intermediate", + "../../../../../bazel-out/x64_windows-fastbuild/bin/packages/typescript/test/ts_project/empty_intermediate", + "../../../../../bazel-out/darwin-dbg/bin/packages/typescript/test/ts_project/empty_intermediate", + "../../../../../bazel-out/k8-dbg/bin/packages/typescript/test/ts_project/empty_intermediate", + "../../../../../bazel-out/x64_windows-dbg/bin/packages/typescript/test/ts_project/empty_intermediate", + ], + } +} \ No newline at end of file diff --git a/packages/typescript/test/ts_project/simple/BUILD.bazel b/packages/typescript/test/ts_project/simple/BUILD.bazel new file mode 100644 index 0000000000..20631cfccc --- /dev/null +++ b/packages/typescript/test/ts_project/simple/BUILD.bazel @@ -0,0 +1,13 @@ +load("@build_bazel_rules_nodejs//internal/golden_file_test:golden_file_test.bzl", "golden_file_test") +load("@npm_bazel_typescript//:index.bzl", "ts_project") + +# This uses defaults for all attributes. +# It will find `index.ts` and produce `index.js` +ts_project() + +golden_file_test( + name = "test", + # Refers to the output from ts_project above + actual = "index.js", + golden = "index.golden.js", +) diff --git a/packages/typescript/test/ts_project/simple/index.golden.js b/packages/typescript/test/ts_project/simple/index.golden.js new file mode 100644 index 0000000000..8e6486da69 --- /dev/null +++ b/packages/typescript/test/ts_project/simple/index.golden.js @@ -0,0 +1,3 @@ +"use strict"; +exports.__esModule = true; +exports.a = 'hello'; diff --git a/packages/typescript/test/ts_project/simple/index.ts b/packages/typescript/test/ts_project/simple/index.ts new file mode 100644 index 0000000000..a668b7e336 --- /dev/null +++ b/packages/typescript/test/ts_project/simple/index.ts @@ -0,0 +1 @@ +export const a: string = 'hello'; diff --git a/packages/typescript/test/ts_project/simple/tsconfig.json b/packages/typescript/test/ts_project/simple/tsconfig.json new file mode 100644 index 0000000000..0967ef424b --- /dev/null +++ b/packages/typescript/test/ts_project/simple/tsconfig.json @@ -0,0 +1 @@ +{} diff --git a/packages/typescript/test/ts_project/tsconfig-base.json b/packages/typescript/test/ts_project/tsconfig-base.json new file mode 100644 index 0000000000..11a249c71d --- /dev/null +++ b/packages/typescript/test/ts_project/tsconfig-base.json @@ -0,0 +1,22 @@ +{ + "compilerOptions": { + "strict": true, + "lib": [ + "ES2015", + "ES2016.Array.Include", + "DOM" + ], + "module": "commonjs", + "target": "ES2015", + "composite": true, + "rootDirs": [ + ".", + "../../../../bazel-out/darwin-fastbuild/bin/packages/typescript/test/ts_project", + "../../../../bazel-out/k8-fastbuild/bin/packages/typescript/test/ts_project", + "../../../../bazel-out/x64_windows-fastbuild/bin/packages/typescript/test/ts_project", + "../../../../bazel-out/darwin-dbg/bin/packages/typescript/test/ts_project", + "../../../../bazel-out/k8-dbg/bin/packages/typescript/test/ts_project", + "../../../../bazel-out/x64_windows-dbg/bin/packages/typescript/test/ts_project", + ], + } +} \ No newline at end of file