Skip to content

Commit b4f322a

Browse files
authored
feat(esbuild): add support for multiple entry points (#2663)
1 parent 768d352 commit b4f322a

File tree

7 files changed

+116
-12
lines changed

7 files changed

+116
-12
lines changed

packages/esbuild/esbuild.bzl

Lines changed: 26 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ esbuild rule
55
load("@build_bazel_rules_nodejs//:index.bzl", "nodejs_binary")
66
load("@build_bazel_rules_nodejs//:providers.bzl", "JSEcmaScriptModuleInfo", "JSModuleInfo", "NpmPackageInfo", "node_modules_aspect", "run_node")
77
load("@build_bazel_rules_nodejs//internal/linker:link_node_modules.bzl", "MODULE_MAPPINGS_ASPECT_RESULTS_NAME", "module_mappings_aspect")
8-
load(":helpers.bzl", "filter_files", "generate_path_mapping", "resolve_entry_point", "write_jsconfig_file")
8+
load(":helpers.bzl", "desugar_entry_point_names", "filter_files", "generate_path_mapping", "resolve_entry_point", "write_jsconfig_file")
99

1010
def _esbuild_impl(ctx):
1111
# For each dep, JSEcmaScriptModuleInfo is used if found, then JSModuleInfo and finally
@@ -38,18 +38,21 @@ def _esbuild_impl(ctx):
3838
package_name = key.split(":")[0]
3939
path_alias_mappings.update(generate_path_mapping(package_name, value[1].replace(ctx.bin_dir.path + "/", "")))
4040

41+
entry_points = desugar_entry_point_names(ctx.file.entry_point, ctx.files.entry_points)
42+
4143
deps_inputs = depset(transitive = deps_depsets).to_list()
42-
inputs = filter_files(ctx.files.entry_point) + ctx.files.srcs + deps_inputs
44+
inputs = filter_files(entry_points) + ctx.files.srcs + deps_inputs
4345

4446
metafile = ctx.actions.declare_file("%s_metadata.json" % ctx.attr.name)
4547
outputs = [metafile]
4648

47-
entry_point = resolve_entry_point(ctx.file.entry_point, inputs, ctx.files.srcs)
48-
4949
args = ctx.actions.args()
5050
args.use_param_file(param_file_arg = "--esbuild_flags=%s", use_always = True)
5151

52-
args.add("--bundle", entry_point.path)
52+
# the entry point files to bundle
53+
for entry_point in entry_points:
54+
args.add(resolve_entry_point(entry_point, inputs, ctx.files.srcs))
55+
args.add("--bundle")
5356

5457
if len(ctx.attr.sourcemap) > 0:
5558
args.add_joined(["--sourcemap", ctx.attr.sourcemap], join_with = "=")
@@ -126,7 +129,7 @@ def _esbuild_impl(ctx):
126129
inputs = depset(inputs),
127130
outputs = outputs,
128131
arguments = [launcher_args, args],
129-
progress_message = "%s Javascript %s [esbuild]" % ("Bundling" if not ctx.attr.output_dir else "Splitting", entry_point.short_path),
132+
progress_message = "%s Javascript %s [esbuild]" % ("Bundling" if not ctx.attr.output_dir else "Splitting", " ".join([entry_point.short_path for entry_point in entry_points])),
130133
execution_requirements = execution_requirements,
131134
mnemonic = "esbuild",
132135
env = env,
@@ -168,9 +171,19 @@ See https://esbuild.github.io/api/#define for more details
168171
doc = "A list of direct dependencies that are required to build the bundle",
169172
),
170173
"entry_point": attr.label(
171-
mandatory = True,
172174
allow_single_file = True,
173-
doc = "The bundle's entry point (e.g. your main.js or app.js or index.js)",
175+
doc = """The bundle's entry point (e.g. your main.js or app.js or index.js)
176+
177+
This is a shortcut for the `entry_points` attribute with a single entry.
178+
Specify either this attribute or `entry_point`, but not both.
179+
""",
180+
),
181+
"entry_points": attr.label_list(
182+
allow_files = True,
183+
doc = """The bundle's entry points (e.g. your main.js or app.js or index.js)
184+
185+
Specify either this attribute or `entry_point`, but not both.
186+
""",
174187
),
175188
"external": attr.string_list(
176189
default = [],
@@ -183,7 +196,7 @@ See https://esbuild.github.io/api/#external for more details
183196
values = ["iife", "cjs", "esm", ""],
184197
mandatory = False,
185198
doc = """The output format of the bundle, defaults to iife when platform is browser
186-
and cjs when platform is node. If performing code splitting, defaults to esm.
199+
and cjs when platform is node. If performing code splitting or multiple entry_points are specified, defaults to esm.
187200
188201
See https://esbuild.github.io/api/#format for more details
189202
""",
@@ -229,9 +242,9 @@ file is named 'foo.js', you should set this to 'foo.css'.""",
229242
),
230243
"output_dir": attr.bool(
231244
default = False,
232-
doc = """If true, esbuild produces an output directory containing all the output files from code splitting
245+
doc = """If true, esbuild produces an output directory containing all the output files from code splitting for multiple entry points
233246
234-
See https://esbuild.github.io/api/#splitting for more details
247+
See https://esbuild.github.io/api/#splitting and https://esbuild.github.io/api/#entry-points for more details
235248
""",
236249
),
237250
"output_map": attr.output(
@@ -308,7 +321,8 @@ def esbuild_macro(name, output_dir = False, **kwargs):
308321
entry_point = Label("@build_bazel_rules_nodejs//packages/esbuild:launcher.js"),
309322
)
310323

311-
if output_dir == True:
324+
entry_points = kwargs.get("entry_points", None)
325+
if output_dir == True or entry_points:
312326
esbuild(
313327
name = name,
314328
output_dir = True,

packages/esbuild/helpers.bzl

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,28 @@ def resolve_entry_point(f, inputs, srcs):
4040

4141
fail("Could not find corresponding entry point for %s. Add the %s.js to your deps or %s.ts to your srcs" % (f.path, no_ext, no_ext))
4242

43+
def desugar_entry_point_names(entry_point, entry_points):
44+
"""Users can specify entry_point (sugar) or entry_points (long form).
45+
46+
This function allows our code to treat it like they always used the long form.
47+
48+
It also validates that exactly one of these attributes should be specified.
49+
50+
Args:
51+
entry_point: the simple argument for specifying a single entry
52+
entry_points: the long form argument for specifing one or more entry points
53+
54+
Returns:
55+
the array of entry poitns
56+
"""
57+
if entry_point and entry_points:
58+
fail("Cannot specify both entry_point and entry_points")
59+
if not entry_point and not entry_points:
60+
fail("One of entry_point or entry_points must be specified")
61+
if entry_point:
62+
return [entry_point]
63+
return entry_points
64+
4365
def filter_files(input, endings = ALLOWED_EXTENSIONS):
4466
"""Filters a list of files for specific endings
4567
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
load("//:index.bzl", "nodejs_test")
2+
load("//packages/esbuild/test:tests.bzl", "esbuild")
3+
load("//packages/typescript:index.bzl", "ts_library")
4+
5+
ts_library(
6+
name = "index",
7+
srcs = ["index.ts"],
8+
module_name = "lib",
9+
)
10+
11+
ts_library(
12+
name = "lib",
13+
srcs = [
14+
"a.ts",
15+
"b.ts",
16+
],
17+
deps = [":index"],
18+
)
19+
20+
esbuild(
21+
name = "bundle",
22+
entry_points = [
23+
"a.ts",
24+
"b.ts",
25+
],
26+
deps = [":lib"],
27+
)
28+
29+
nodejs_test(
30+
name = "bundle_test",
31+
data = [
32+
"bundle.spec.js",
33+
":bundle",
34+
],
35+
entry_point = ":bundle.spec.js",
36+
)

packages/esbuild/test/entries/a.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
import {NAME} from 'lib';
2+
3+
console.log(`Hello ${NAME}`);

packages/esbuild/test/entries/b.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
import {NAME} from 'lib';
2+
3+
console.log(`Hello ${NAME} from b.ts`);
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
const {join} = require('path');
2+
const {readFileSync, lstatSync} = require('fs');
3+
4+
const helper = require(process.env.BAZEL_NODE_RUNFILES_HELPER);
5+
const location = helper.resolve('build_bazel_rules_nodejs/packages/esbuild/test/entries/bundle/');
6+
7+
const a = readFileSync(join(location, 'a.js'), {encoding: 'utf8'});
8+
const b = readFileSync(join(location, 'b.js'), {encoding: 'utf8'});
9+
const aHasImportOfChunk = a.match(/\/(chunk-[a-zA-Z0-9]+\.js)";/);
10+
const bHasImportOfChunk = b.match(/\/(chunk-[a-zA-Z0-9]+\.js)";/);
11+
12+
if (!aHasImportOfChunk || !bHasImportOfChunk) {
13+
console.error(`Expected entry_points 'a.js' and 'b.js' to have an import of './chunk-[hash].js'`);
14+
}
15+
16+
if (aHasImportOfChunk[1] !== bHasImportOfChunk[1]) {
17+
console.error(`Expected entry_points 'a.js' and 'b.js' to the same shared chunk`);
18+
}
19+
20+
// throws if file does not exist
21+
lstatSync(join(location, aHasImportOfChunk && aHasImportOfChunk[1]));
22+
23+
process.exit(
24+
(aHasImportOfChunk && bHasImportOfChunk && aHasImportOfChunk[1] === bHasImportOfChunk[1]) ? 0 :
25+
1);
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export const NAME = 'bazel';

0 commit comments

Comments
 (0)