anki/ts/esbuild/upstream.bzl

319 lines
12 KiB
Python
Raw Normal View History

"""
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")
2021-04-07 07:19:23 +02:00
load(":toolchain.bzl", "TOOLCHAIN")
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)
2021-04-07 06:53:53 +02:00
if DefaultInfo in dep:
deps_depsets.append(dep[DefaultInfo].data_runfiles.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)
2021-04-07 06:53:53 +02:00
if len(ctx.attr.sourcemap) > 0:
args.add_joined(["--sourcemap", ctx.attr.sourcemap], join_with = "=")
else:
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")
2021-04-07 06:53:53 +02:00
# disable the log limit and show all logs
args.add_joined(["--log-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
2021-04-07 06:53:53 +02:00
outputs.append(js_out)
js_out_map = ctx.outputs.output_map
2021-04-07 06:53:53 +02:00
if ctx.attr.sourcemap != "inline":
if js_out_map == None:
fail("output_map must be specified if sourcemap is not set to 'inline'")
outputs.append(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])
2021-04-07 06:53:53 +02:00
env = {}
if ctx.attr.max_threads > 0:
env["GOMAXPROCS"] = str(ctx.attr.max_threads)
execution_requirements = {}
if "no-remote-exec" in ctx.attr.tags:
execution_requirements = {"no-remote-exec": "1"}
ctx.actions.run(
inputs = inputs,
outputs = outputs,
2021-04-07 07:19:23 +02:00
executable = ctx.toolchains[TOOLCHAIN].binary,
arguments = [args],
progress_message = "%s Javascript %s [esbuild]" % ("Bundling" if not ctx.attr.output_dir else "Splitting", entry_point.short_path),
2021-04-07 06:53:53 +02:00
execution_requirements = execution_requirements,
mnemonic = "esbuild",
env = env,
)
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\\""
],
)
```
2021-04-07 06:53:53 +02:00
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
2021-04-07 06:53:53 +02:00
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.
2021-04-07 06:53:53 +02:00
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.""",
),
2021-04-07 06:53:53 +02:00
"max_threads": attr.int(
mandatory = False,
doc = """Sets the `GOMAXPROCS` variable to limit the number of threads that esbuild can run with.
This can be useful if running many esbuild rule invocations in parallel, which has the potential to cause slowdown.
For general use, leave this attribute unset.
""",
),
"minify": attr.bool(
default = False,
doc = """Minifies the bundle with the built in minification.
Removes whitespace, shortens identifieres and uses equivalent but shorter syntax.
2021-04-07 06:53:53 +02:00
Sets all --minify-* flags
2021-04-07 06:53:53 +02:00
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
2021-04-07 06:53:53 +02:00
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.
2021-04-07 06:53:53 +02:00
See https://esbuild.github.io/api/#platform for more details
""",
),
2021-04-07 06:53:53 +02:00
"sourcemap": attr.string(
values = ["external", "inline", "both", ""],
mandatory = False,
doc = """Defines where sourcemaps are output and how they are included in the bundle. By default, a separate `.js.map` file is generated and referenced by the bundle. If 'external', a separate `.js.map` file is generated but not referenced by the bundle. If 'inline', a sourcemap is generated and its contents are inlined into the bundle (and no external sourcemap file is created). If 'both', a sourcemap is inlined and a `.js.map` file is created.
See https://esbuild.github.io/api/#sourcemap for more details
""",
),
"sources_content": attr.bool(
mandatory = False,
default = False,
doc = """If False, omits the `sourcesContent` field from generated source maps
2021-04-07 06:53:53 +02:00
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.
2021-04-07 06:53:53 +02:00
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)
2021-04-07 06:53:53 +02:00
See https://esbuild.github.io/api/#target for more details
""",
),
},
implementation = _esbuild_impl,
doc = """Runs the esbuild bundler under Bazel
2021-04-07 06:53:53 +02:00
For further information about esbuild, see https://esbuild.github.io/
""",
2021-04-07 07:19:23 +02:00
toolchains = [
TOOLCHAIN,
],
)
def esbuild_macro(name, output_dir = False, output_css = False, **kwargs):
"""esbuild helper macro around the `esbuild_bundle` rule
2021-04-07 06:53:53 +02:00
For a full list of attributes, see the `esbuild_bundle` rule
2021-04-07 06:53:53 +02:00
Args:
name: The name used for this rule and output files
output_dir: If `True`, produce a code split bundle in an output directory
2021-04-07 06:53:53 +02:00
output_css: If `True`, declare name.css as an output, 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:
2021-04-07 06:53:53 +02:00
output = "%s.js" % name
if "output" in kwargs:
output = kwargs.pop("output")
output_map = None
sourcemap = kwargs.get("sourcemap", None)
if sourcemap != "inline":
output_map = "%s.map" % output
esbuild(
name = name,
2021-04-07 06:53:53 +02:00
output = output,
output_map = output_map,
output_css = None if not output_css else "%s.css" % name,
**kwargs
)