Skip to content

Commit

Permalink
Add a module mapping feature, so that a ts_library can be imported us…
Browse files Browse the repository at this point in the history
…ing a short name

(typically its npm package).

PiperOrigin-RevId: 156885857
  • Loading branch information
Typescript Team authored and alexeagle committed May 23, 2017
1 parent d707fa6 commit 51e3c34
Show file tree
Hide file tree
Showing 7 changed files with 203 additions and 27 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -26,5 +26,8 @@ ts_library(
name = "bar_ts_library",
srcs = ["bar.ts"],
tsconfig = ":tsconfig.json",
deps = [":foo_ts_library"],
deps = [
":foo_ts_library",
"//examples/some_library:lib",
],
)
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
import {greeter} from './foo';
import {greeter} from './foo';
import {cool} from 'some-lib';
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# 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("//:defs.bzl", "ts_library")

ts_library(
name = "lib",
srcs = ["library.ts"],
# Allow this library to be imported from `some-lib`
module_name = "some-lib",
# The imported path should be the library.d.ts file
module_root = "library",
tsconfig = "//examples:tsconfig.json",
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
/**
* @license
* 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.
*/

export const cool = 1;
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,11 @@
"""
# pylint: disable=unused-argument
# pylint: disable=missing-docstring
load("//internal:common/compilation.bzl", "compile_ts")
load("//internal:executables.bzl", "get_tsc", "get_node")
load("//internal:common/json_marshal.bzl", "json_marshal")
load("//internal:common/tsconfig.bzl", "create_tsconfig")
load(":common/compilation.bzl", "compile_ts")
load(":executables.bzl", "get_tsc", "get_node")
load(":common/json_marshal.bzl", "json_marshal")
load(":common/tsconfig.bzl", "create_tsconfig")
load(":common/module_mappings.bzl", "module_mappings_aspect")

def _compile_action(ctx, inputs, outputs, config_file_path):
externs_files = []
Expand Down Expand Up @@ -75,28 +76,29 @@ def _tsc_wrapped_tsconfig(ctx,
else:
runfiles = "external/io_bazel_rules_typescript/internal/tsc_wrapped.runfiles"

module_roots = {
"*": [
"/".join([host_bin, runfiles, "yarn/installed/node_modules/*"]),
"/".join([host_bin, runfiles, "yarn/installed/node_modules/@types/*"]),
],
# Workaround https://github.com/Microsoft/TypeScript/issues/15962
# Needed for Angular to build with Bazel.
# TODO(alexeagle): fix the bug upstream or find a better place for
# this workaround.
"zone.js": [
"/".join([host_bin, runfiles, "yarn/installed/node_modules/zone.js/dist/zone.js.d.ts"]),
]
}

config = create_tsconfig(ctx, files, srcs, tsconfig_json.dirname,
devmode_manifest=devmode_manifest)
devmode_manifest=devmode_manifest,
module_roots=module_roots)

config["compilerOptions"].update({
"typeRoots": ["/".join([
workspace_path, host_bin, runfiles,
"yarn/installed/node_modules/@types"]
)],
"baseUrl": workspace_path,
"paths": {
"*": [
"/".join([host_bin, runfiles, "yarn/installed/node_modules/*"]),
"/".join([host_bin, runfiles, "yarn/installed/node_modules/@types/*"]),
],
# Workaround https://github.com/Microsoft/TypeScript/issues/15962
# Needed for Angular to build with Bazel.
# TODO(alexeagle): fix the bug upstream or find a better place for
# this workaround.
"zone.js": [
"/".join([host_bin, runfiles, "yarn/installed/node_modules/zone.js/dist/zone.js.d.ts"]),
]
},
})

# If the user gives a tsconfig attribute, the generated file should extend
Expand Down Expand Up @@ -142,7 +144,11 @@ ts_library = rule(
]),
mandatory=True,),
"deps":
attr.label_list(),
attr.label_list(aspects = [module_mappings_aspect]),
# Used to determine module mappings, see below.
"module_name": attr.string(),
"module_root": attr.string(),

# TODO(evanm): make this the default and remove the option.
"runtime":
attr.string(default="browser"),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
# 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.

# Definitions for handling path re-mapping, to support short module names.
# See pathMapping doc: https://github.com/Microsoft/TypeScript/issues/5039
#
# This reads the module_root and module_name attributes from typescript rules in
# the transitive closure, rolling these up to provide a mapping to the
# TypeScript compiler and to editors.
#

def _get_deps(attrs, names):
return [d for n in names if hasattr(attrs, n)
for d in getattr(attrs, n)]

# Traverse 'srcs' in addition so that we can go across a genrule
_MODULE_MAPPINGS_DEPS_NAMES = (["deps", "srcs"]
)

_DEBUG = False

def debug(msg, values=()):
if _DEBUG:
print(msg % values)

def get_module_mappings(label, attrs, srcs = []):
"""Returns the module_mappings from the given attrs.
Collects a {module_name - module_root} hash from all transitive dependencies,
checking for collisions. If a module has a non-empty `module_root` attribute,
all sources underneath it are treated as if they were rooted at a folder
`module_name`.
"""
mappings = dict()
all_deps = _get_deps(attrs, names = _MODULE_MAPPINGS_DEPS_NAMES)
for dep in all_deps:
if not hasattr(dep, "es6_module_mappings"):
continue
for k, v in dep.es6_module_mappings.items():
if k in mappings and mappings[k] != v:
fail(("duplicate module mapping at %s: %s maps to both %s and %s" %
(label, k, mappings[k], v)), "deps")
mappings[k] = v
if ((hasattr(attrs, "module_name") and attrs.module_name) or
(hasattr(attrs, "module_root") and attrs.module_root)):
mn = attrs.module_name
if not mn:
mn = label.name
mr = label.package
if attrs.module_root and attrs.module_root != ".":
mr = "%s/%s" % (mr, attrs.module_root)
# Validate that sources are underneath the module root.
# module_roots ending in .ts are a special case, they are used to
# restrict what's exported from a build rule, e.g. only exports from a
# specific index.d.ts file. For those, not every source must be under the
# given module root.
if not attrs.module_root.endswith(".ts"):
for s in srcs:
if not s.short_path.startswith(mr):
fail(("all sources must be under module root: %s, but found: %s" %
(mr, s.short_path)))
if mn in mappings and mappings[mn] != mr:
fail(("duplicate module mapping at %s: %s maps to both %s and %s" %
(label, mn, mappings[mn], mr)), "deps")
mappings[mn] = mr
debug("Mappings at %s: %s", (label, mappings))
return mappings

def _module_mappings_aspect_impl(target, ctx):
mappings = get_module_mappings(target.label, ctx.rule.attr)
return struct(es6_module_mappings = mappings)

module_mappings_aspect = aspect(
_module_mappings_aspect_impl,
attr_aspects = _MODULE_MAPPINGS_DEPS_NAMES,
)
Original file line number Diff line number Diff line change
Expand Up @@ -16,18 +16,20 @@
"""
_DEBUG = False

def create_tsconfig(ctx, files, srcs, tsconfig_path,
load(":common/module_mappings.bzl", "get_module_mappings")

def create_tsconfig(ctx, files, srcs, tsconfig_dir,
devmode_manifest=None, tsickle_externs=None, type_blacklisted_declarations=[],
out_dir=None, disable_strict_deps=False, allowed_deps=set(),
extra_root_dirs=[]):
extra_root_dirs=[], module_path_prefixes=None, module_roots=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.
tsconfig_path: where the resulting config will be written; paths will be relative
tsconfig_dir: where the resulting config will be written; paths will be relative
to this folder
devmode_manifest: path to the manifest file to write for --target=es5
tsickle_externs: path to write tsickle-generated externs.js.
Expand All @@ -39,7 +41,32 @@ def create_tsconfig(ctx, files, srcs, tsconfig_path,
extra_root_dirs: Extra root dirs to be passed to tsc_wrapped.
"""
outdir_path = out_dir if out_dir != None else ctx.configuration.bin_dir.path
workspace_path = "/".join([".."] * len(tsconfig_path.split("/")))
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
]]
module_roots = {
"*": base_path_mappings,
}
module_mappings = get_module_mappings(ctx.label, ctx.attr, srcs = srcs)

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).
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.
module_roots["{}/*".format(name)] = ["%s%s/*" % (p, path) for p in module_path_prefixes]

perf_trace_path = "/".join([ctx.configuration.bin_dir.path, ctx.label.package,
ctx.label.name + ".trace"])
Expand Down Expand Up @@ -80,7 +107,6 @@ def create_tsconfig(ctx, files, srcs, tsconfig_path,
# 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.
# http://b/30709121
"skipDefaultLibCheck": True,

# Always produce commonjs modules (might get translated to goog.module).
Expand Down Expand Up @@ -109,6 +135,12 @@ def create_tsconfig(ctx, files, srcs, tsconfig_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": module_roots,

"traceResolution": _DEBUG,
"diagnostics": _DEBUG,

Expand All @@ -130,6 +162,8 @@ def create_tsconfig(ctx, files, srcs, tsconfig_path,
# Embed source maps and sources in .js outputs
"inlineSourceMap": True,
"inlineSources": True,
# Implied by inlineSourceMap: True
"sourceMap": False,

# Don't emit decorate/metadata helper code, we provide our own helpers.js.
"noEmitHelpers": ctx.attr.runtime == "browser",
Expand Down

0 comments on commit 51e3c34

Please sign in to comment.