blob: 0fe82547d7a398e2b713678e3d49e3ce067c2abb [file] [log] [blame]
# 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.
"""Helpers for configuring the TypeScript compiler.
"""
load(":common/module_mappings.bzl", "get_module_mappings")
_DEBUG = False
def create_tsconfig(
ctx,
files,
srcs,
devmode_manifest = None,
tsickle_externs = None,
type_blacklisted_declarations = [],
out_dir = None,
disable_strict_deps = False,
allowed_deps = depset(),
extra_root_dirs = [],
module_path_prefixes = None,
module_roots = None,
node_modules_root = None):
"""Creates an object representing the TypeScript configuration to run the compiler under Bazel.
Args:
ctx: the skylark execution context
files: Labels of all TypeScript compiler inputs
srcs: Immediate sources being compiled, as opposed to transitive deps.
devmode_manifest: path to the manifest file to write for --target=es5
tsickle_externs: path to write tsickle-generated externs.js.
type_blacklisted_declarations: types declared in these files will never be
mentioned in generated .d.ts.
out_dir: directory for generated output. Default is ctx.bin_dir
disable_strict_deps: whether to disable the strict deps check
allowed_deps: the set of files that code in srcs may depend on (strict deps)
extra_root_dirs: Extra root dirs to be passed to tsc_wrapped.
module_path_prefixes: additional locations to resolve modules
module_roots: standard locations to resolve modules
node_modules_root: the node_modules root path
Returns:
A nested dict that corresponds to a tsconfig.json structure
"""
if (type(files) != type([])):
fail("expected files argument to be a list, got " + type(files))
outdir_path = out_dir if out_dir != None else ctx.configuration.bin_dir.path
# Callers can choose the filename for the tsconfig, but it must always live
# in the output directory corresponding with the label where it's declared.
tsconfig_dir = "/".join([
p
for p in [
ctx.bin_dir.path,
ctx.label.workspace_root,
ctx.label.package,
] + ctx.label.name.split("/")[:-1]
# Skip empty path segments (eg. workspace_root when in same repo)
if p
])
workspace_path = "/".join([".."] * len(tsconfig_dir.split("/")))
if module_path_prefixes == None:
module_path_prefixes = [
"",
ctx.configuration.genfiles_dir.path + "/",
ctx.configuration.bin_dir.path + "/",
]
if module_roots == None:
base_path_mappings = ["%s/*" % p for p in [
".",
ctx.configuration.genfiles_dir.path,
ctx.configuration.bin_dir.path,
]]
node_modules_mappings = []
if (hasattr(ctx.attr, "node_modules")):
node_modules_mappings.append("/".join([p for p in [
node_modules_root,
"*",
] if p]))
# TypeScript needs to look up ambient types from a 'node_modules'
# directory, but when Bazel manages the dependencies, this directory
# isn't in the project so TypeScript won't find it.
# We can add it to the path mapping to make this lookup work.
# See https://github.com/bazelbuild/rules_typescript/issues/179
node_modules_mappings.append("/".join([p for p in [
node_modules_root,
"@types",
"*",
] if p]))
module_roots = {
"*": node_modules_mappings,
ctx.workspace_name + "/*": base_path_mappings,
}
module_mappings = get_module_mappings(ctx.label, ctx.attr, srcs = srcs)
# To determine the path for auto-imports, TypeScript's language service
# considers paths in the order they appear in tsconfig.json.
# We want explicit module mappings ("@angular/core") to take precedence over
# the general "*" mapping (which would create "third_party/javascript/..."),
# so we create a new hash that contains the module_mappings and insert the
# default lookup locations at the end.
mapped_module_roots = {}
for name, path in module_mappings.items():
# Each module name maps to the immediate path, to resolve "index(.d).ts",
# or module mappings that directly point to files (like index.d.ts).
mapped_module_roots[name] = [
"%s%s" % (p, path.replace(".d.ts", ""))
for p in module_path_prefixes
]
if not path.endswith(".d.ts"):
# If not just mapping to a single .d.ts file, include a path glob that
# maps the entire module root.
mapped_module_roots["{}/*".format(name)] = [
"%s%s/*" % (p, path)
for p in module_path_prefixes
]
for name, path in module_roots.items():
mapped_module_roots[name] = path
# Options for running the TypeScript compiler under Bazel.
# See javascript/typescript/compiler/tsc_wrapped.ts:BazelOptions.
# Unlike compiler_options, the paths here are relative to the rootDir,
# not the location of the tsconfig.json file.
# @unsorted-dict-items preserve historical order for golden tests
bazel_options = {
"workspaceName": ctx.workspace_name,
"target": str(ctx.label),
"package": ctx.label.package,
"tsickleGenerateExterns": getattr(ctx.attr, "generate_externs", True),
"tsickleExternsPath": tsickle_externs.path if tsickle_externs else "",
"untyped": not getattr(ctx.attr, "tsickle_typed", False),
"typeBlackListPaths": [f.path for f in type_blacklisted_declarations],
# This is overridden by first-party javascript/typescript/tsconfig.bzl
"ignoreWarningPaths": [],
"es5Mode": devmode_manifest != None,
"manifest": devmode_manifest if devmode_manifest else "",
# Explicitly tell the compiler which sources we're interested in (emitting
# and type checking).
"compilationTargetSrc": [s.path for s in srcs],
"addDtsClutzAliases": getattr(ctx.attr, "add_dts_clutz_aliases", False),
"typeCheckDependencies": getattr(ctx.attr, "internal_testing_type_check_dependencies", False),
"expectedDiagnostics": getattr(ctx.attr, "expected_diagnostics", []),
}
if hasattr(ctx.attr, "compile_angular_templates") and ctx.attr.compile_angular_templates:
bazel_options["compileAngularTemplates"] = True
if disable_strict_deps:
bazel_options["disableStrictDeps"] = disable_strict_deps
bazel_options["allowedStrictDeps"] = []
else:
bazel_options["allowedStrictDeps"] = [f.path for f in allowed_deps.to_list()]
if hasattr(ctx.attr, "module_name") and ctx.attr.module_name:
bazel_options["moduleName"] = ctx.attr.module_name
if hasattr(ctx.attr, "module_root") and ctx.attr.module_root:
bazel_options["moduleRoot"] = ctx.attr.module_root
if "TYPESCRIPT_WORKER_CACHE_SIZE_MB" in ctx.var:
max_cache_size_mb = int(ctx.var["TYPESCRIPT_WORKER_CACHE_SIZE_MB"])
if max_cache_size_mb < 0:
fail("TYPESCRIPT_WORKER_CACHE_SIZE_MB set to a negative value (%d)." % max_cache_size_mb)
bazel_options["maxCacheSizeMb"] = max_cache_size_mb
has_node_runtime = getattr(ctx.attr, "runtime", "browser") == "nodejs"
target_language_level = "es5" if devmode_manifest or has_node_runtime else "es2015"
# Keep these options in sync with those in playground/playground.ts.
# @unsorted-dict-items preserve historical order for golden tests
compiler_options = {
# De-sugar to this language level
"target": target_language_level,
# The "typescript.es5_sources" provider is expected to work
# in both nodejs and in browsers, so we use umd in devmode.
# NOTE: tsc-wrapped will always name the enclosed AMD modules
# For production mode, we leave the module syntax alone and let the
# bundler handle it (including dynamic import).
# Note, in google3 we override this option with "commonjs" since Tsickle
# will convert that to goog.module syntax.
"module": "umd" if devmode_manifest or has_node_runtime else "esnext",
# Has no effect in closure/ES2015 mode. Always true just for simplicity.
"downlevelIteration": True,
# Do not type-check the lib.*.d.ts.
# We think this shouldn't be necessary but haven't figured out why yet
# and builds are faster with the setting on.
# See http://b/30709121
"skipDefaultLibCheck": True,
"moduleResolution": "node",
"outDir": "/".join([workspace_path, outdir_path]),
# We must set a rootDir to avoid TypeScript emit paths varying
# due computeCommonSourceDirectory behavior.
# TypeScript requires the rootDir be a parent of all sources in
# files[], so it must be set to the workspace_path.
"rootDir": workspace_path,
# Path handling for resolving modules, see specification at
# https://github.com/Microsoft/TypeScript/issues/5039
# Paths where we attempt to load relative references.
# Longest match wins
#
# tsc_wrapped also uses this property to strip leading paths
# to produce a flattened output tree, see
# https://github.com/Microsoft/TypeScript/issues/8245
"rootDirs": ["/".join([workspace_path, e]) for e in extra_root_dirs] + [
workspace_path,
"/".join([workspace_path, ctx.configuration.genfiles_dir.path]),
"/".join([workspace_path, ctx.configuration.bin_dir.path]),
],
# Root for non-relative module names
"baseUrl": workspace_path,
# "short name" mappings for npm packages, such as "@angular/core"
"paths": mapped_module_roots,
# Inline const enums.
"preserveConstEnums": False,
# permit `@Decorator` syntax and allow runtime reflection on their types.
"experimentalDecorators": True,
"emitDecoratorMetadata": True,
# Interpret JSX as React calls (until someone asks for something different)
"jsx": "react",
# Print out full errors. By default TS truncates errors >100 chars. This can make it
# impossible to understand some errors.
"noErrorTruncation": True,
# Do not emit files if they had errors (avoid accidentally serving broken code).
"noEmitOnError": False,
# Create .d.ts files as part of compilation.
"declaration": True,
# We don't support this compiler option (See github #32), so
# always emit declaration files in the same location as outDir.
"declarationDir": "/".join([workspace_path, outdir_path]),
"stripInternal": True,
# Embed source maps and sources in .js outputs
"inlineSourceMap": True,
"inlineSources": True,
# Implied by inlineSourceMap: True
"sourceMap": False,
}
if hasattr(ctx.attr, "node_modules"):
compiler_options["typeRoots"] = ["/".join([p for p in [
workspace_path,
node_modules_root,
"@types",
] if p])]
if _DEBUG:
compiler_options["traceResolution"] = True
compiler_options["diagnostics"] = True
# @unsorted-dict-items preserve historical order for golden tests
return {
"compilerOptions": compiler_options,
"bazelOptions": bazel_options,
"files": [workspace_path + "/" + f.path for f in files],
"compileOnSave": False,
}