224 lines
8.4 KiB
Python
224 lines
8.4 KiB
Python
# Copyright Google Inc. All Rights Reserved.
|
|
#
|
|
# Use of this source code is governed by an MIT-style license that can be
|
|
# found in the LICENSE file at https://angular.io/license
|
|
|
|
"""Provides ES5 syntax with ESModule import/exports.
|
|
|
|
This exposes another flavor of output JavaScript, which is ES5 syntax
|
|
with ES2015 module syntax (import/export).
|
|
All Bazel rules should consume the standard dev or prod mode.
|
|
However we need to publish this flavor on NPM, so it's necessary to be able
|
|
to produce it.
|
|
"""
|
|
|
|
load(":external.bzl", "DEFAULT_NG_COMPILER")
|
|
|
|
# The provider downstream rules use to access the outputs
|
|
ESM5Info = provider(
|
|
doc = "Typescript compilation outputs in ES5 syntax with ES Modules",
|
|
fields = {
|
|
"transitive_output": """Dict of [rootDir, .js depset] entries.
|
|
|
|
The value is a depset of the .js output files.
|
|
The key is the prefix that should be stripped off the files
|
|
when resolving modules, eg. for file
|
|
bazel-bin/[external/wkspc/]path/to/package/label.esm5/path/to/package/file.js
|
|
the rootdir would be
|
|
bazel-bin/[external/wkspc/]path/to/package/label.esm5""",
|
|
},
|
|
)
|
|
|
|
def _map_closure_path(file):
|
|
result = file.short_path[:-len(".mjs")]
|
|
|
|
# short_path is meant to be used when accessing runfiles in a binary, where
|
|
# the CWD is inside the current repo. Therefore files in external repo have a
|
|
# short_path of ../external/wkspc/path/to/package
|
|
# We want to strip the first two segments from such paths.
|
|
if (result.startswith("../")):
|
|
result = "/".join(result.split("/")[2:])
|
|
return result + ".js"
|
|
|
|
def _join(array):
|
|
return "/".join([p for p in array if p])
|
|
|
|
def _esm5_outputs_aspect(target, ctx):
|
|
if not hasattr(target, "typescript"):
|
|
return []
|
|
|
|
# Workaround for https://github.com/bazelbuild/rules_typescript/issues/211
|
|
# TODO(gmagolan): generate esm5 output from ts_proto_library and have that
|
|
# output work with esm5_outputs_aspect
|
|
if not hasattr(target.typescript, "replay_params"):
|
|
print("WARNING: no esm5 output from target %s//%s:%s available" % (target.label.workspace_root, target.label.package, target.label.name))
|
|
return []
|
|
elif not target.typescript.replay_params:
|
|
# In case there are "replay_params" specified but the compile action didn't generate any
|
|
# outputs (e.g. only "d.ts" files), we cannot create ESM5 outputs for this target either.
|
|
return []
|
|
|
|
# We create a new tsconfig.json file that will have our compilation settings
|
|
tsconfig = ctx.actions.declare_file("%s_esm5.tsconfig.json" % target.label.name)
|
|
|
|
workspace = target.label.workspace_root if target.label.workspace_root else ""
|
|
|
|
# re-root the outputs under a ".esm5" directory so the path don't collide
|
|
out_dir = ctx.label.name + ".esm5"
|
|
if workspace:
|
|
out_dir = out_dir + "/" + workspace
|
|
|
|
outputs = [
|
|
ctx.actions.declare_file(_join([out_dir, _map_closure_path(f)]))
|
|
for f in target.typescript.replay_params.outputs
|
|
if not f.short_path.endswith(".externs.js")
|
|
]
|
|
|
|
ctx.actions.run(
|
|
executable = ctx.executable._modify_tsconfig,
|
|
inputs = [target.typescript.replay_params.tsconfig],
|
|
outputs = [tsconfig],
|
|
arguments = [
|
|
target.typescript.replay_params.tsconfig.path,
|
|
tsconfig.path,
|
|
_join([workspace, target.label.package, ctx.label.name + ".esm5"]),
|
|
ctx.bin_dir.path,
|
|
],
|
|
)
|
|
|
|
replay_compiler_path = target.typescript.replay_params.compiler.short_path
|
|
replay_compiler_name = replay_compiler_path.split("/")[-1]
|
|
|
|
# in windows replay_compiler path end with '.exe'
|
|
if replay_compiler_name.startswith("tsc_wrapped"):
|
|
compiler = ctx.executable._tsc_wrapped
|
|
elif replay_compiler_name.startswith("ngc-wrapped"):
|
|
compiler = ctx.executable._ngc_wrapped
|
|
|
|
# BEGIN-INTERNAL
|
|
# If the "replay_compiler" path does not refer to "ngc_wrapped" from the "@npm" workspace,
|
|
# we use "ngc_wrapped" from within the Angular workspace. This is necessary because we
|
|
# don't have a "npm" workspace with the "@angular/bazel" NPM package installed.
|
|
if replay_compiler_path != ctx.executable._ngc_wrapped.short_path:
|
|
compiler = ctx.executable._internal_ngc_wrapped
|
|
|
|
# END-INTERNAL
|
|
else:
|
|
fail("Unknown replay compiler", target.typescript.replay_params.compiler.path)
|
|
|
|
inputs = [tsconfig]
|
|
if (type(target.typescript.replay_params.inputs) == type([])):
|
|
inputs.extend(target.typescript.replay_params.inputs)
|
|
else:
|
|
inputs.extend(target.typescript.replay_params.inputs.to_list())
|
|
|
|
ctx.actions.run(
|
|
progress_message = "Compiling TypeScript (ES5 with ES Modules) %s" % target.label,
|
|
inputs = inputs,
|
|
outputs = outputs,
|
|
arguments = [tsconfig.path],
|
|
executable = compiler,
|
|
execution_requirements = {
|
|
# TODO(alexeagle): enable worker mode for these compilations
|
|
"supports-workers": "0",
|
|
},
|
|
mnemonic = "ESM5",
|
|
)
|
|
|
|
root_dir = _join([
|
|
ctx.bin_dir.path,
|
|
workspace,
|
|
target.label.package,
|
|
ctx.label.name + ".esm5",
|
|
])
|
|
|
|
transitive_output = {root_dir: depset(outputs)}
|
|
for dep in ctx.rule.attr.deps:
|
|
if ESM5Info in dep:
|
|
transitive_output.update(dep[ESM5Info].transitive_output)
|
|
|
|
return [ESM5Info(
|
|
transitive_output = transitive_output,
|
|
)]
|
|
|
|
# Downstream rules can use this aspect to access the ESM5 output flavor.
|
|
# Only terminal rules (those which expect never to be used in deps[]) should do
|
|
# this.
|
|
esm5_outputs_aspect = aspect(
|
|
implementation = _esm5_outputs_aspect,
|
|
# Recurse to the deps of any target we visit
|
|
attr_aspects = ["deps"],
|
|
attrs = {
|
|
# This is only used if the replay_compiler refers to the "angular" workspace. In that
|
|
# case we need to use "ngc_wrapped" from its source location because we can't have
|
|
# the "npm" workspace that has the "@angular/bazel" NPM package installed.
|
|
"_internal_ngc_wrapped": attr.label(
|
|
default = Label("//packages/bazel/src/ngc-wrapped"),
|
|
executable = True,
|
|
cfg = "host",
|
|
),
|
|
"_modify_tsconfig": attr.label(
|
|
default = Label("//packages/bazel/src:modify_tsconfig"),
|
|
executable = True,
|
|
cfg = "host",
|
|
),
|
|
"_tsc_wrapped": attr.label(
|
|
default = Label("@npm//@bazel/typescript/bin:tsc_wrapped"),
|
|
executable = True,
|
|
cfg = "host",
|
|
),
|
|
# This is the default "ngc_wrapped" executable that will be used to replay the compilation
|
|
# for ESM5 mode. The default compiler consumes "ngc_wrapped" from the "@npm" workspace.
|
|
# This is needed for downstream Bazel users that can have a different TypeScript
|
|
# version installed.
|
|
"_ngc_wrapped": attr.label(
|
|
default = Label(DEFAULT_NG_COMPILER),
|
|
executable = True,
|
|
cfg = "host",
|
|
),
|
|
},
|
|
)
|
|
|
|
def esm5_root_dir(ctx):
|
|
return ctx.label.name + ".esm5"
|
|
|
|
def flatten_esm5(ctx):
|
|
"""Merge together the .esm5 folders from the dependencies.
|
|
|
|
Two different dependencies A and B may have outputs like
|
|
`bazel-bin/path/to/A.esm5/path/to/lib.js`
|
|
`bazel-bin/path/to/B.esm5/path/to/main.js`
|
|
|
|
In order to run rollup on this app, in case main.js contains `import from './lib'`
|
|
they need to be together in the same root directory, so if we depend on both A and B
|
|
we need the outputs to be
|
|
`bazel-bin/path/to/my_rule.esm5/path/to/lib.js`
|
|
`bazel-bin/path/to/my_rule.esm5/path/to/main.js`
|
|
|
|
Args:
|
|
ctx: the skylark rule execution context
|
|
|
|
Returns:
|
|
depset of flattened files
|
|
"""
|
|
esm5_sources = []
|
|
result = []
|
|
for dep in ctx.attr.deps:
|
|
if ESM5Info in dep:
|
|
transitive_output = dep[ESM5Info].transitive_output
|
|
esm5_sources.extend(transitive_output.values())
|
|
for f in depset(transitive = esm5_sources).to_list():
|
|
path = f.short_path[f.short_path.find(".esm5") + len(".esm5"):]
|
|
if (path.startswith("../")):
|
|
path = "external/" + path[3:]
|
|
rerooted_file = ctx.actions.declare_file("/".join([esm5_root_dir(ctx), path]))
|
|
result.append(rerooted_file)
|
|
|
|
# print("copy", f.short_path, "to", rerooted_file.short_path)
|
|
ctx.actions.expand_template(
|
|
output = rerooted_file,
|
|
template = f,
|
|
substitutions = {},
|
|
)
|
|
return depset(result)
|