diff --git a/WORKSPACE b/WORKSPACE index 01388e13..60d09397 100644 --- a/WORKSPACE +++ b/WORKSPACE @@ -43,6 +43,20 @@ node_repositories( package_json = ["//:package.json"], preserve_symlinks = True) +# Closure compiler. +# This is is needed for the closure templates example, users should not need to add it unless they want to use it. +# Using a fork to get support for typescript and clutz. +http_archive( + name = "io_bazel_rules_closure", + sha256 = "cbdeac3e610982e60c1af5695191d46e4f432cdbb30ac8535820aaf1e285abcc", + strip_prefix = "rules_closure-2a78960a3c64df0bf1311e07269ea28be476e848", + url = "http://github.com/ribrdb/rules_closure/archive/2a78960a3c64df0bf1311e07269ea28be476e848.tar.gz", +) + +load("@io_bazel_rules_closure//closure:defs.bzl", "closure_repositories") + +closure_repositories() + http_archive( name = "io_bazel_rules_go", urls = [ diff --git a/defs.bzl b/defs.bzl index e7caea17..c3aeb83f 100644 --- a/defs.bzl +++ b/defs.bzl @@ -17,7 +17,7 @@ Users should not load files under "/internal" """ load("//internal:ts_repositories.bzl", _ts_setup_workspace = "ts_setup_workspace") -load("//internal:build_defs.bzl", _ts_library = "ts_library") +load("//internal:build_defs.bzl", _ts_library = "ts_library", _ts_declaration="ts_declaration") load("//internal:ts_config.bzl", _ts_config = "ts_config") load("//internal/devserver:ts_devserver.bzl", _ts_devserver = "ts_devserver_macro") load("//internal/karma:ts_web_test.bzl", @@ -27,6 +27,7 @@ load("//internal/protobufjs:ts_proto_library.bzl", _ts_proto_library = "ts_proto ts_setup_workspace = _ts_setup_workspace ts_library = _ts_library +ts_declaration = _ts_declaration ts_config = _ts_config ts_devserver = _ts_devserver # TODO(alexeagle): make ts_web_test && ts_web_test_suite work in google3 diff --git a/examples/BUILD.bazel b/examples/BUILD.bazel index 3d1bcbca..e44a3278 100644 --- a/examples/BUILD.bazel +++ b/examples/BUILD.bazel @@ -35,6 +35,7 @@ ts_library( name = "bar_ts_library", srcs = ["bar.ts"], tsconfig = ":tsconfig.json", + tsickle_typed = False, deps = [ ":foo_ts_library", "//examples/some_library:lib", diff --git a/examples/some_library/BUILD.bazel b/examples/some_library/BUILD.bazel index 43dbc2c6..a34e8608 100644 --- a/examples/some_library/BUILD.bazel +++ b/examples/some_library/BUILD.bazel @@ -23,4 +23,5 @@ ts_library( module_name = "some-lib", # The imported path should be the library.d.ts file module_root = "library", + tsickle_typed = False, ) diff --git a/examples/some_soy/BUILD.bazel b/examples/some_soy/BUILD.bazel new file mode 100644 index 00000000..0d2a8a78 --- /dev/null +++ b/examples/some_soy/BUILD.bazel @@ -0,0 +1,95 @@ +# Copyright 2017 The Bazel Authors. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +package(default_visibility = ["//visibility:public"]) + +load( + "@io_bazel_rules_closure//closure:defs.bzl", + "closure_js_template_library", + "closure_js_binary", + "closure_js_library", +) +load("@build_bazel_rules_nodejs//:defs.bzl", "nodejs_binary") +load("//:defs.bzl", "ts_library", "ts_declaration") + +closure_js_template_library( + name = "template", + srcs = ["some.soy"], +) + +genrule( + name="template_clutz", + outs=["template.d.ts"], + srcs=["some.soy.js", + "@io_angular_clutz//:src/resources/partial_goog_base.js", + "@io_bazel_rules_closure//third_party/clutz:externs.js" + ], + tools=["@io_angular_clutz//:clutz"], + cmd=""" +BASE=$(location @io_angular_clutz//:src/resources/partial_goog_base.js); +CLUTZ=$(location @io_angular_clutz//:clutz); +$$CLUTZ --partialInput -o $@ --skipEmitRegExp $$BASE $(SRCS) + """ +) + +ts_declaration( + name="template_ts", + srcs=["template.d.ts"], + generate_externs=False, + runtime_deps=[":template"], +) + +ts_library( + name = "main", + srcs = ["main.ts"], + deps = [ + ":mini_closure", + ":template_ts", + ], +) + +closure_js_library( + name = "main_js", + ts_lib = ":main", + suppress=["unusedLocalVariables"], +) + +closure_js_binary( + name = "render_soy_bin", + entry_points = ["goog:build_bazel_rules_typescript.examples.some_soy.main"], + deps = [":main_js"], +) + +nodejs_binary( + name = "some_soy", + data = [ + ":render_soy_bin", + ], + entry_point = "build_bazel_rules_typescript/examples/some_soy/render_soy_bin.js", +) + +sh_test( + name = "soy_render_test", + srcs = ["soy_render_test.sh"], + data = [":some_soy"], +) + +ts_library( + name="mini_closure", + srcs=[":closure.d.ts"], + runtime_deps = [ + "@io_bazel_rules_closure//closure/library", + ], + generate_externs = False, +) diff --git a/examples/some_soy/closure.d.ts b/examples/some_soy/closure.d.ts new file mode 100644 index 00000000..cde28f1d --- /dev/null +++ b/examples/some_soy/closure.d.ts @@ -0,0 +1,3 @@ +declare namespace goog { + function isString(x:any): x is string; +} \ No newline at end of file diff --git a/examples/some_soy/main.ts b/examples/some_soy/main.ts new file mode 100644 index 00000000..c03c3d3a --- /dev/null +++ b/examples/some_soy/main.ts @@ -0,0 +1,6 @@ +import {Template} from 'goog:some.templates'; + +const msg = Template({name:"World"}).getContent(); +if (goog.isString(msg)){ + console.log(msg); +} diff --git a/examples/some_soy/some.soy b/examples/some_soy/some.soy new file mode 100644 index 00000000..fdd911bb --- /dev/null +++ b/examples/some_soy/some.soy @@ -0,0 +1,5 @@ +{namespace some.templates} +{template .Template} +{@param name: string} +

Hello, {$name}

+{/template} \ No newline at end of file diff --git a/examples/some_soy/soy_render_test.sh b/examples/some_soy/soy_render_test.sh new file mode 100755 index 00000000..9b60bc96 --- /dev/null +++ b/examples/some_soy/soy_render_test.sh @@ -0,0 +1,8 @@ +#!/bin/bash +set -e + +readonly OUT=$($TEST_SRCDIR/build_bazel_rules_typescript/examples/some_soy/some_soy) +if [ "$OUT" != "

Hello, World

" ]; then + echo "Expected output '

Hello, World

' but was $OUT" + exit 1 +fi diff --git a/internal/build_defs.bzl b/internal/build_defs.bzl index b41d367f..9b3b0956 100644 --- a/internal/build_defs.bzl +++ b/internal/build_defs.bzl @@ -24,17 +24,11 @@ def _compile_action(ctx, inputs, outputs, tsconfig_file, node_opts, description externs_files = [] action_outputs = [] for output in outputs: - if output.basename.endswith(".externs.js"): - externs_files.append(output) - elif output.basename.endswith(".es5.MF"): + if output.basename.endswith(".es5.MF"): ctx.file_action(output, content="") else: action_outputs.append(output) - # TODO(plf): For now we mock creation of files other than {name}.js. - for externs_file in externs_files: - ctx.file_action(output=externs_file, content="") - # A ts_library that has only .d.ts inputs will have no outputs, # therefore there are no actions to execute if not action_outputs: @@ -196,3 +190,69 @@ ts_library = rule( It produces declarations files (`.d.ts`) which are used for compiling downstream TypeScript targets and JavaScript for the browser and Closure compiler. """ + +def _ts_declaration_impl(ctx): + """Implementation of ts_declaration. + + Args: + ctx: the context. + + Returns: + the struct returned by the call to compile_ts. + """ + ts_providers = compile_ts(ctx, is_library=False, + compile_action=_compile_action, + devmode_compile_action=_devmode_compile_action, + tsc_wrapped_tsconfig=tsc_wrapped_tsconfig) + return ts_providers_dict_to_struct(ts_providers) + +ts_declaration = rule( + _ts_declaration_impl, + attrs = dict(COMMON_ATTRIBUTES, **{ + "srcs": attr.label_list( + allow_files = FileType([ + ".ts", + ".tsx", + ]), + mandatory = True, + ), + + # TODO(alexeagle): reconcile with google3: ts_library rules should + # be portable across internal/external, so we need this attribute + # internally as well. + "tsconfig": attr.label( + doc = """A tsconfig.json file containing settings for TypeScript compilation. + Note that some properties in the tsconfig are governed by Bazel and will be + overridden, such as `target` and `module`.""", + allow_files = True, + single_file = True, + ), + "compiler": attr.label( + doc = """Intended for internal use only. + Sets a different TypeScript compiler binary to use for this library. + For example, we use the vanilla TypeScript tsc.js for bootstrapping, + and Angular compilations can replace this with `ngc`.""", + default = Label("//internal:tsc_wrapped_bin"), + single_file = False, + allow_files = True, + executable = True, + cfg = "host", + ), + "supports_workers": attr.bool( + doc = """Intended for internal use only. + Allows you to disable the Bazel Worker strategy for this library. + Typically used together with the "compiler" setting when using a + non-worker aware compiler binary.""", + default = True, + ), + "tsickle_typed": attr.bool(default = True), + "_tsc_wrapped_deps": attr.label(default = Label("@build_bazel_rules_typescript_tsc_wrapped_deps//:node_modules")), + # @// is special syntax for the "main" repository + # The default assumes the user specified a target "node_modules" in their + # root BUILD file. + "node_modules": attr.label(default = Label("@//:node_modules")), + }), + outputs = { + "tsconfig": "%{name}_tsconfig.json", + }, +) diff --git a/internal/common/compilation.bzl b/internal/common/compilation.bzl index 68624272..7278dc9b 100644 --- a/internal/common/compilation.bzl +++ b/internal/common/compilation.bzl @@ -41,7 +41,7 @@ COMMON_ATTRIBUTES = dict(BASE_ATTRIBUTES, **{ # any closure JS code. "runtime_deps": attr.label_list( default = [], - providers = ["js"], + providers = [["js"], ["closure_js_library"]], ), "_additional_d_ts": attr.label_list( allow_files = True, @@ -188,9 +188,11 @@ def compile_ts(ctx, transpiled_devmode_js = outs.devmode_js gen_declarations = outs.declarations - if has_sources and ctx.attr.runtime != "nodejs": - # Note: setting this variable controls whether tsickle is run at all. - tsickle_externs = [ctx.new_file(ctx.label.name + ".externs.js")] + if hasattr(ctx.attr,'tsickle_typed') and ctx.attr.tsickle_typed: + if has_sources and ctx.attr.runtime != "nodejs": + if is_library or ctx.attr.generate_externs: + # Note: setting this variable controls whether tsickle is run at all. + tsickle_externs = [ctx.new_file(ctx.label.name + ".externs.js")] dep_declarations = _collect_dep_declarations(ctx) input_declarations = dep_declarations.transitive + src_declarations @@ -344,6 +346,10 @@ def compile_ts(ctx, transitive_es5_sources = depset(transitive = [transitive_es5_sources, es5_sources]) transitive_es6_sources = depset(transitive = [transitive_es6_sources, es6_sources]) + transitive_runtime_deps = depset( + ctx.attr.runtime_deps, + transitive=[dep.typescript.runtime_deps for dep in ctx.attr.deps] ) + return { "files": files, "output_groups": { @@ -369,6 +375,7 @@ def compile_ts(ctx, "type_blacklisted_declarations": type_blacklisted_declarations, "tsickle_externs": tsickle_externs, "replay_params": replay_params, + "runtime_deps": transitive_runtime_deps, }, # Expose the tags so that a Skylark aspect can access them. "tags": ctx.attr.tags, diff --git a/internal/common/tsconfig.bzl b/internal/common/tsconfig.bzl index 525b6647..ac779ff9 100644 --- a/internal/common/tsconfig.bzl +++ b/internal/common/tsconfig.bzl @@ -124,6 +124,7 @@ def create_tsconfig(ctx, files, srcs, "target": str(ctx.label), "package": ctx.label.package, "tsickle": tsickle_externs != None, + "googmodule": tsickle_externs != None, "tsickleGenerateExterns": getattr(ctx.attr, "generate_externs", True), "tsickleExternsPath": tsickle_externs.path if tsickle_externs else "", "untyped": not getattr(ctx.attr, "tsickle_typed", False), @@ -244,6 +245,9 @@ def create_tsconfig(ctx, files, srcs, "sourceMap": False, } + if tsickle_externs != None: + compiler_options["module"] = "commonjs" + if hasattr(ctx.attr, "node_modules"): compiler_options["typeRoots"] = ["/".join([p for p in [ workspace_path, diff --git a/internal/protobufjs/ts_proto_library.bzl b/internal/protobufjs/ts_proto_library.bzl index 19957f79..f65dd75d 100644 --- a/internal/protobufjs/ts_proto_library.bzl +++ b/internal/protobufjs/ts_proto_library.bzl @@ -100,6 +100,7 @@ def _ts_proto_library(ctx): es6_sources = depset([js_es6]), transitive_es5_sources = depset(), transitive_es6_sources = depset([js_es6]), + runtime_deps = depset(), ), ) diff --git a/internal/tsc_wrapped/compiler_host.ts b/internal/tsc_wrapped/compiler_host.ts index 2649be4a..1fc2cc01 100644 --- a/internal/tsc_wrapped/compiler_host.ts +++ b/internal/tsc_wrapped/compiler_host.ts @@ -177,7 +177,7 @@ export class CompilerHost implements ts.CompilerHost, tsickle.TsickleHost { /** Allows suppressing warnings for specific known libraries */ shouldIgnoreWarningsForPath(filePath: string): boolean { - return this.bazelOpts.ignoreWarningPaths.some( + return this.bazelOpts.ignoreWarningPaths && this.bazelOpts.ignoreWarningPaths.some( p => !!filePath.match(new RegExp(p))); } diff --git a/internal/tsc_wrapped/tsc_wrapped.ts b/internal/tsc_wrapped/tsc_wrapped.ts index 286e6f8b..ac5e20da 100644 --- a/internal/tsc_wrapped/tsc_wrapped.ts +++ b/internal/tsc_wrapped/tsc_wrapped.ts @@ -1,5 +1,6 @@ import * as fs from 'fs'; import * as path from 'path'; +import * as tsickle from 'tsickle'; import * as ts from 'typescript'; import {PLUGIN as tsetsePlugin} from '../tsetse/runner'; @@ -141,15 +142,38 @@ function runOneBuild( return false; } - for (const sf of program.getSourceFiles().filter(isCompilationTarget)) { - const emitResult = program.emit( - sf, /*writeFile*/ undefined, - /*cancellationToken*/ undefined, /*emitOnlyDtsFiles*/ undefined, { - after: [fixUmdModuleDeclarations( - (sf: ts.SourceFile) => compilerHost.amdModuleName(sf))] - }); - diags.push(...emitResult.diagnostics); + const emitResults = []; + + if (bazelOpts.tsickle) { + const emitResults: tsickle.EmitResult[] = + program.getSourceFiles() + .filter(isCompilationTarget) + .map( + file => tsickle.emitWithTsickle( + program, compilerHost, (compilerHost as ts.CompilerHost), + options, file)); + + const emitResult = tsickle.mergeEmitResults(emitResults); + + if (bazelOpts.tsickleExternsPath) { + const externs = bazelOpts.tsickleGenerateExterns ? tsickle.getGeneratedExterns(emitResult.externs) : ''; + fs.writeFileSync(bazelOpts.tsickleExternsPath, externs); + } + } else { + for (const sf of program.getSourceFiles().filter(isCompilationTarget)) { + const emitResult = program.emit( + sf, /*writeFile*/ undefined, + /*cancellationToken*/ undefined, /*emitOnlyDtsFiles*/ undefined, { + after: [fixUmdModuleDeclarations( + (sf: ts.SourceFile) => compilerHost.amdModuleName(sf))] + }); + diags.push(...emitResult.diagnostics); + } + if (bazelOpts.tsickleExternsPath) { + fs.writeFileSync(bazelOpts.tsickleExternsPath, ''); + } } + if (diags.length > 0) { console.error(diagnostics.format(bazelOpts.target, diags)); return false; diff --git a/package.json b/package.json index ffe5684e..ee31006d 100644 --- a/package.json +++ b/package.json @@ -9,7 +9,7 @@ "homepage": "https://github.com/bazelbuild/rules_typescript", "license": "Apache-2.0", "peerDependencies": { - "typescript": ">=2.4.2" + "typescript": "2.6.2" }, "devDependencies": { "@bazel/ibazel": "^0.2.0", @@ -23,7 +23,7 @@ "http-server": "^0.11.1", "protobufjs": "5.0.0", "protractor": "^5.2.0", - "tsickle": "0.25.x", + "tsickle": "0.27.2", "tsutils": "2.20.0", "typescript": "2.7.x" }, @@ -38,4 +38,4 @@ "skylint": "find . -type f -name \"*.bzl\" ! -path \"*/node_modules/*\" | xargs $(bazel info bazel-bin)/external/io_bazel/src/tools/skylark/java/com/google/devtools/skylark/skylint/Skylint", "skydoc": "bazel build //:docs && unzip -o -d docs/api bazel-bin/docs-skydoc.zip" } -} +} \ No newline at end of file diff --git a/yarn.lock b/yarn.lock index 9c52425b..3a1f1d97 100644 --- a/yarn.lock +++ b/yarn.lock @@ -912,7 +912,13 @@ sntp@2.x.x: dependencies: hoek "4.x.x" -source-map-support@^0.4.2, source-map-support@~0.4.0: +source-map-support@^0.5.0: + version "0.5.3" + resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.3.tgz#2b3d5fff298cfa4d1afd7d4352d569e9a0158e76" + dependencies: + source-map "^0.6.0" + +source-map-support@~0.4.0: version "0.4.18" resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.4.18.tgz#0286a6de8be42641338594e97ccea75f0a2c585f" dependencies: @@ -922,6 +928,10 @@ source-map@^0.5.6: version "0.5.7" resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc" +source-map@^0.6.0: + version "0.6.1" + resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" + spawn-command@^0.0.2-1: version "0.0.2" resolved "https://registry.yarnpkg.com/spawn-command/-/spawn-command-0.0.2.tgz#9544e1a43ca045f8531aac1a48cb29bdae62338e" @@ -1002,14 +1012,14 @@ tree-kill@^1.1.0: version "1.2.0" resolved "https://registry.yarnpkg.com/tree-kill/-/tree-kill-1.2.0.tgz#5846786237b4239014f05db156b643212d4c6f36" -tsickle@0.25.x: - version "0.25.0" - resolved "https://registry.yarnpkg.com/tsickle/-/tsickle-0.25.0.tgz#4ba51e79e9333ab7baec6f374c789b0bc1dba36c" +tsickle@0.27.2: + version "0.27.2" + resolved "https://registry.yarnpkg.com/tsickle/-/tsickle-0.27.2.tgz#f33d46d046f73dd5c155a37922e422816e878736" dependencies: minimist "^1.2.0" mkdirp "^0.5.1" - source-map "^0.5.6" - source-map-support "^0.4.2" + source-map "^0.6.0" + source-map-support "^0.5.0" tslib@^1.8.1: version "1.9.0"