anki/ts/esbuild/upstream.bzl
Damien Elmes f1780e6e24 move loader arg into esbuild() invocation
upstream.bzl should be kept as close to upstream as possible, so that
it's easier to send changes upstream and pull in future changes
2021-03-29 17:59:06 +10:00

270 lines
10 KiB
Python

"""
NOTE: this file was forked from the following repo (Apache2)
https://github.com/bazelbuild/rules_nodejs/blob/c47b770a122e9614516df2e3fdca6fe0bf6e3420/packages/esbuild/esbuild.bzl
Local changes not in upstream:
https://github.com/bazelbuild/rules_nodejs/pull/2545
https://github.com/bazelbuild/rules_nodejs/pull/2564
esbuild rule
"""
load("@build_bazel_rules_nodejs//:providers.bzl", "JSEcmaScriptModuleInfo", "JSModuleInfo", "NpmPackageInfo", "node_modules_aspect")
load("@build_bazel_rules_nodejs//internal/linker:link_node_modules.bzl", "MODULE_MAPPINGS_ASPECT_RESULTS_NAME", "module_mappings_aspect")
load(":helpers.bzl", "filter_files", "generate_path_mapping", "resolve_js_input", "write_jsconfig_file")
def _esbuild_impl(ctx):
# For each dep, JSEcmaScriptModuleInfo is used if found, then JSModuleInfo and finally
# the DefaultInfo files are used if the former providers are not found.
deps_depsets = []
# Path alias mapings are used to create a jsconfig with mappings so that esbuild
# how to resolve custom package or module names
path_alias_mappings = dict()
npm_workspaces = []
if (ctx.attr.link_workspace_root):
path_alias_mappings.update(generate_path_mapping(ctx.workspace_name, "."))
for dep in ctx.attr.deps:
if JSEcmaScriptModuleInfo in dep:
deps_depsets.append(dep[JSEcmaScriptModuleInfo].sources)
if JSModuleInfo in dep:
deps_depsets.append(dep[JSModuleInfo].sources)
elif hasattr(dep, "files"):
deps_depsets.append(dep.files)
if NpmPackageInfo in dep:
deps_depsets.append(dep[NpmPackageInfo].sources)
npm_workspaces.append(dep[NpmPackageInfo].workspace)
# Collect the path alias mapping to resolve packages correctly
if hasattr(dep, MODULE_MAPPINGS_ASPECT_RESULTS_NAME):
for key, value in getattr(dep, MODULE_MAPPINGS_ASPECT_RESULTS_NAME).items():
path_alias_mappings.update(generate_path_mapping(key, value[1].replace(ctx.bin_dir.path + "/", "")))
node_modules_mappings = [
"../../../external/%s/node_modules/*" % workspace
for workspace in depset(npm_workspaces).to_list()
]
path_alias_mappings.update({"*": node_modules_mappings})
deps_inputs = depset(transitive = deps_depsets).to_list()
inputs = filter_files(ctx.files.entry_point, [".mjs", ".js"]) + ctx.files.srcs + deps_inputs
metafile = ctx.actions.declare_file("%s_metadata.json" % ctx.attr.name)
outputs = [metafile]
entry_point = resolve_js_input(ctx.file.entry_point, inputs)
args = ctx.actions.args()
args.add("--bundle", entry_point.path)
args.add("--sourcemap")
args.add("--preserve-symlinks")
args.add_joined(["--platform", ctx.attr.platform], join_with = "=")
args.add_joined(["--target", ctx.attr.target], join_with = "=")
args.add_joined(["--log-level", "info"], join_with = "=")
args.add_joined(["--metafile", metafile.path], join_with = "=")
args.add_all(ctx.attr.define, format_each = "--define:%s")
args.add_all(ctx.attr.external, format_each = "--external:%s")
# disable the error limit and show all errors
args.add_joined(["--error-limit", "0"], join_with = "=")
if ctx.attr.minify:
args.add("--minify")
else:
# by default, esbuild will tree-shake 'pure' functions
# disable this unless also minifying
args.add_joined(["--tree-shaking", "ignore-annotations"], join_with = "=")
if ctx.attr.sources_content:
args.add("--sources-content=true")
else:
args.add("--sources-content=false")
if ctx.attr.output_dir:
js_out = ctx.actions.declare_directory("%s" % ctx.attr.name)
outputs.append(js_out)
args.add("--splitting")
args.add_joined(["--format", "esm"], join_with = "=")
args.add_joined(["--outdir", js_out.path], join_with = "=")
else:
js_out = ctx.outputs.output
js_out_map = ctx.outputs.output_map
outputs.extend([js_out, js_out_map])
if ctx.outputs.output_css:
outputs.append(ctx.outputs.output_css)
if ctx.attr.format:
args.add_joined(["--format", ctx.attr.format], join_with = "=")
args.add_joined(["--outfile", js_out.path], join_with = "=")
jsconfig_file = write_jsconfig_file(ctx, path_alias_mappings)
args.add_joined(["--tsconfig", jsconfig_file.path], join_with = "=")
inputs.append(jsconfig_file)
args.add_all([ctx.expand_location(arg) for arg in ctx.attr.args])
ctx.actions.run(
inputs = inputs,
outputs = outputs,
executable = ctx.executable.tool,
arguments = [args],
progress_message = "%s Javascript %s [esbuild]" % ("Bundling" if not ctx.attr.output_dir else "Splitting", entry_point.short_path),
execution_requirements = {
"no-remote-exec": "1",
},
)
return [
DefaultInfo(files = depset(outputs + [jsconfig_file])),
]
esbuild = rule(
attrs = {
"args": attr.string_list(
default = [],
doc = """A list of extra arguments that are included in the call to esbuild.
$(location ...) can be used to resolve the path to a Bazel target.""",
),
"define": attr.string_list(
default = [],
doc = """A list of global identifier replacements.
Example:
```python
esbuild(
name = "bundle",
define = [
"process.env.NODE_ENV=\\"production\\""
],
)
```
See https://esbuild.github.io/api/#define for more details
""",
),
"deps": attr.label_list(
default = [],
aspects = [module_mappings_aspect, node_modules_aspect],
doc = "A list of direct dependencies that are required to build the bundle",
),
"entry_point": attr.label(
mandatory = True,
allow_single_file = True,
doc = "The bundle's entry point (e.g. your main.js or app.js or index.js)",
),
"external": attr.string_list(
default = [],
doc = """A list of module names that are treated as external and not included in the resulting bundle
See https://esbuild.github.io/api/#external for more details
""",
),
"format": attr.string(
values = ["iife", "cjs", "esm", ""],
mandatory = False,
doc = """The output format of the bundle, defaults to iife when platform is browser
and cjs when platform is node. If performing code splitting, defaults to esm.
See https://esbuild.github.io/api/#format for more details
""",
),
"link_workspace_root": attr.bool(
doc = """Link the workspace root to the bin_dir to support absolute requires like 'my_wksp/path/to/file'.
If source files need to be required then they can be copied to the bin_dir with copy_to_bin.""",
),
"minify": attr.bool(
default = False,
doc = """Minifies the bundle with the built in minification.
Removes whitespace, shortens identifieres and uses equivalent but shorter syntax.
Sets all --minify-* flags
See https://esbuild.github.io/api/#minify for more details
""",
),
"output": attr.output(
mandatory = False,
doc = "Name of the output file when bundling",
),
"output_dir": attr.bool(
default = False,
doc = """If true, esbuild produces an output directory containing all the output files from code splitting
See https://esbuild.github.io/api/#splitting for more details
""",
),
"output_map": attr.output(
mandatory = False,
doc = "Name of the output source map when bundling",
),
"output_css": attr.output(
mandatory = False,
doc = "Name of the output css file when bundling",
),
"platform": attr.string(
default = "browser",
values = ["node", "browser", "neutral", ""],
doc = """The platform to bundle for.
See https://esbuild.github.io/api/#platform for more details
""",
),
"sources_content": attr.bool(
mandatory = False,
default = False,
doc = """If False, omits the `sourcesContent` field from generated source maps
See https://esbuild.github.io/api/#sources-content for more details
""",
),
"srcs": attr.label_list(
allow_files = True,
default = [],
doc = """Non-entry point JavaScript source files from the workspace.
You must not repeat file(s) passed to entry_point""",
),
"target": attr.string(
default = "es2015",
doc = """Environment target (e.g. es2017, chrome58, firefox57, safari11,
edge16, node10, default esnext)
See https://esbuild.github.io/api/#target for more details
""",
),
"tool": attr.label(
allow_single_file = True,
mandatory = True,
executable = True,
cfg = "exec",
doc = "An executable for the esbuild binary",
),
},
implementation = _esbuild_impl,
doc = """Runs the esbuild bundler under Bazel
For further information about esbuild, see https://esbuild.github.io/
""",
)
def esbuild_macro(name, output_dir = False, output_css = False, **kwargs):
"""esbuild helper macro around the `esbuild_bundle` rule
For a full list of attributes, see the `esbuild_bundle` rule
Args:
name: The name used for this rule and output files
output_dir: If `True`, produce a code split bundle in an output directory
output_css: If `True`, declare a .css file will be outputted, which is the
case when your code imports a css file.
**kwargs: All other args from `esbuild_bundle`
"""
if output_dir == True:
esbuild(
name = name,
output_dir = True,
**kwargs
)
else:
esbuild(
name = name,
output = "%s.js" % name,
output_map = "%s.js.map" % name,
output_css = None if not output_css else "%s.css" % name,
**kwargs
)