Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(esbuild): add support for multiple entry points #2663

Merged
merged 1 commit into from
May 10, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 26 additions & 12 deletions packages/esbuild/esbuild.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ esbuild rule
load("@build_bazel_rules_nodejs//:index.bzl", "nodejs_binary")
load("@build_bazel_rules_nodejs//:providers.bzl", "JSEcmaScriptModuleInfo", "JSModuleInfo", "NpmPackageInfo", "node_modules_aspect", "run_node")
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_entry_point", "write_jsconfig_file")
load(":helpers.bzl", "desugar_entry_point_names", "filter_files", "generate_path_mapping", "resolve_entry_point", "write_jsconfig_file")

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

entry_points = desugar_entry_point_names(ctx.file.entry_point, ctx.files.entry_points)

deps_inputs = depset(transitive = deps_depsets).to_list()
inputs = filter_files(ctx.files.entry_point) + ctx.files.srcs + deps_inputs
inputs = filter_files(entry_points) + ctx.files.srcs + deps_inputs

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

entry_point = resolve_entry_point(ctx.file.entry_point, inputs, ctx.files.srcs)

args = ctx.actions.args()
args.use_param_file(param_file_arg = "--esbuild_flags=%s", use_always = True)

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

if len(ctx.attr.sourcemap) > 0:
args.add_joined(["--sourcemap", ctx.attr.sourcemap], join_with = "=")
Expand Down Expand Up @@ -126,7 +129,7 @@ def _esbuild_impl(ctx):
inputs = depset(inputs),
outputs = outputs,
arguments = [launcher_args, args],
progress_message = "%s Javascript %s [esbuild]" % ("Bundling" if not ctx.attr.output_dir else "Splitting", entry_point.short_path),
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])),
execution_requirements = execution_requirements,
mnemonic = "esbuild",
env = env,
Expand Down Expand Up @@ -168,9 +171,19 @@ See https://esbuild.github.io/api/#define for more details
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)",
doc = """The bundle's entry point (e.g. your main.js or app.js or index.js)

This is a shortcut for the `entry_points` attribute with a single entry.
Specify either this attribute or `entry_point`, but not both.
""",
),
"entry_points": attr.label_list(
allow_files = True,
doc = """The bundle's entry points (e.g. your main.js or app.js or index.js)

Specify either this attribute or `entry_point`, but not both.
""",
),
"external": attr.string_list(
default = [],
Expand All @@ -183,7 +196,7 @@ See https://esbuild.github.io/api/#external for more details
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.
and cjs when platform is node. If performing code splitting or multiple entry_points are specified, defaults to esm.

See https://esbuild.github.io/api/#format for more details
""",
Expand Down Expand Up @@ -229,9 +242,9 @@ file is named 'foo.js', you should set this to 'foo.css'.""",
),
"output_dir": attr.bool(
default = False,
doc = """If true, esbuild produces an output directory containing all the output files from code splitting
doc = """If true, esbuild produces an output directory containing all the output files from code splitting for multiple entry points

See https://esbuild.github.io/api/#splitting for more details
See https://esbuild.github.io/api/#splitting and https://esbuild.github.io/api/#entry-points for more details
""",
),
"output_map": attr.output(
Expand Down Expand Up @@ -308,7 +321,8 @@ def esbuild_macro(name, output_dir = False, **kwargs):
entry_point = Label("@build_bazel_rules_nodejs//packages/esbuild:launcher.js"),
)

if output_dir == True:
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will cause an error when output_dir is True as it will pass entry_points = False, where entry_points is expecting a label list, but will get a boolean.

Perhaps something like the following (None is falsy and the default on label_list is an empty list)

entry_points = kwargs.get("entry_points", None)
if output_dir == True or entry_points:
    esbuild(
            name = name,
            output_dir = True,
            launcher = _launcher,
            **kwargs
        )

It can then be removed from the macro's signature

entry_points = kwargs.get("entry_points", None)
if output_dir == True or entry_points:
esbuild(
name = name,
output_dir = True,
Expand Down
22 changes: 22 additions & 0 deletions packages/esbuild/helpers.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,28 @@ def resolve_entry_point(f, inputs, srcs):

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))

def desugar_entry_point_names(entry_point, entry_points):
"""Users can specify entry_point (sugar) or entry_points (long form).

This function allows our code to treat it like they always used the long form.

It also validates that exactly one of these attributes should be specified.

Args:
entry_point: the simple argument for specifying a single entry
entry_points: the long form argument for specifing one or more entry points

Returns:
the array of entry poitns
"""
if entry_point and entry_points:
fail("Cannot specify both entry_point and entry_points")
if not entry_point and not entry_points:
fail("One of entry_point or entry_points must be specified")
if entry_point:
return [entry_point]
return entry_points

def filter_files(input, endings = ALLOWED_EXTENSIONS):
"""Filters a list of files for specific endings

Expand Down
36 changes: 36 additions & 0 deletions packages/esbuild/test/entries/BUILD.bazel
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
load("//:index.bzl", "nodejs_test")
load("//packages/esbuild/test:tests.bzl", "esbuild")
load("//packages/typescript:index.bzl", "ts_library")

ts_library(
name = "index",
srcs = ["index.ts"],
module_name = "lib",
)

ts_library(
name = "lib",
srcs = [
"a.ts",
"b.ts",
],
deps = [":index"],
)

esbuild(
name = "bundle",
entry_points = [
"a.ts",
"b.ts",
],
deps = [":lib"],
)

nodejs_test(
name = "bundle_test",
data = [
"bundle.spec.js",
":bundle",
],
entry_point = ":bundle.spec.js",
)
3 changes: 3 additions & 0 deletions packages/esbuild/test/entries/a.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import {NAME} from 'lib';

console.log(`Hello ${NAME}`);
3 changes: 3 additions & 0 deletions packages/esbuild/test/entries/b.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import {NAME} from 'lib';

console.log(`Hello ${NAME} from b.ts`);
25 changes: 25 additions & 0 deletions packages/esbuild/test/entries/bundle.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
const {join} = require('path');
const {readFileSync, lstatSync} = require('fs');

const helper = require(process.env.BAZEL_NODE_RUNFILES_HELPER);
const location = helper.resolve('build_bazel_rules_nodejs/packages/esbuild/test/entries/bundle/');

const a = readFileSync(join(location, 'a.js'), {encoding: 'utf8'});
const b = readFileSync(join(location, 'b.js'), {encoding: 'utf8'});
const aHasImportOfChunk = a.match(/\/(chunk-[a-zA-Z0-9]+\.js)";/);
const bHasImportOfChunk = b.match(/\/(chunk-[a-zA-Z0-9]+\.js)";/);

if (!aHasImportOfChunk || !bHasImportOfChunk) {
console.error(`Expected entry_points 'a.js' and 'b.js' to have an import of './chunk-[hash].js'`);
}

if (aHasImportOfChunk[1] !== bHasImportOfChunk[1]) {
console.error(`Expected entry_points 'a.js' and 'b.js' to the same shared chunk`);
}

// throws if file does not exist
lstatSync(join(location, aHasImportOfChunk && aHasImportOfChunk[1]));

process.exit(
(aHasImportOfChunk && bHasImportOfChunk && aHasImportOfChunk[1] === bHasImportOfChunk[1]) ? 0 :
1);
1 change: 1 addition & 0 deletions packages/esbuild/test/entries/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export const NAME = 'bazel';