Skip to content

Commit

Permalink
feat(rollup): support multiple entry points
Browse files Browse the repository at this point in the history
  • Loading branch information
alexeagle committed Sep 10, 2019
1 parent cfef773 commit f660d39
Show file tree
Hide file tree
Showing 7 changed files with 211 additions and 61 deletions.
1 change: 0 additions & 1 deletion packages/rollup/src/rollup.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

module.exports = {
output: {
name: 'bundle',
},
plugins: [],
};
217 changes: 171 additions & 46 deletions packages/rollup/src/rollup_bundle.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,61 @@

load("@build_bazel_rules_nodejs//internal/linker:link_node_modules.bzl", "module_mappings_aspect", "register_node_modules_linker")

_DOC = """Runs the Rollup.js CLI under Bazel.
See https://rollupjs.org/guide/en/#command-line-reference
Typical example:
```python
load("@npm_bazel_rollup//:index.bzl", "rollup_bundle")
rollup_bundle(
name = "bundle",
srcs = ["dependency.js"],
entry_point = "input.js",
config_file = "rollup.config.js",
)
```
Note that the command-line options set by Bazel override what appears in the rollup config file.
This means that typically a single `rollup.config.js` can contain settings for your whole repo,
and multiple `rollup_bundle` rules can share the configuration.
Thus, setting options that Bazel controls will have no effect, e.g.
```javascript
module.exports = {
output: { file: 'this_is_ignored.js' },
}
```
The rollup_bundle rule always produces a directory output, because it isn't known until
rollup runs whether the output has many chunks or is a single file.
To get multiple output formats, wrap the rule with a macro or list comprehension, e.g.
```python
[
rollup_bundle(
name = "bundle.%s" % format,
entry_point = "foo.js",
format = format,
)
for format in [
"cjs",
"umd",
]
]
```
This will produce one output directory per requested format.
"""

_ROLLUP_ATTRS = {
"srcs": attr.label_list(
doc = """JavaScript source files from the workspace.
doc = """Non-entry point JavaScript source files from the workspace.
The file passed to entry_point is automatically added.
You must not repeat file(s) passed to entry_point/entry_points.
""",
# Don't try to constrain the filenames, could be json, svg, whatever
allow_files = True,
Expand All @@ -23,13 +73,65 @@ If not set, a default basic Rollup config is used.
default = "@npm_bazel_rollup//:rollup.config.js",
),
"entry_point": attr.label(
doc = """The bundle's entry point(s) (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 just a shortcut for the `entry_points` attribute with a single output chunk named the same as the entry_point attribute.
For example, these are equivalent:
```python
rollup_bundle(
name = "bundle",
entry_point = "index.js",
)
```
```python
rollup_bundle(
name = "bundle",
entry_points = {
"index.js": "index"
}
)
```
If the entry_point attribute is instead a label that produces a single .js file,
this will work, but the resulting output will be named after the label,
so these are equivalent:
Passed to the [`--input` option](https://github.com/rollup/rollup/blob/master/docs/999-big-list-of-options.md#input) in Rollup.
If you provide an array of entry points or an object mapping names to entry points, they will be bundled to separate output chunks.
```python
# Outputs index.js
produces_js(
name = "producer",
)
rollup_bundle(
name = "bundle",
entry_point = "producer",
)
```
```python
rollup_bundle(
name = "bundle",
entry_points = {
"index.js": "producer"
}
)
```
""",
mandatory = True,
allow_single_file = True,
allow_single_file = [".js"],
),
"entry_points": attr.label_keyed_string_dict(
doc = """The bundle's entry points (e.g. your main.js or app.js or index.js).
Passed to the [`--input` option](https://github.com/rollup/rollup/blob/master/docs/999-big-list-of-options.md#input) in Rollup.
Keys in this dictionary are labels pointing to .js entry point files.
Values are the name to be given to the corresponding output chunk.
Either this attribute or `entry_point` must be specified, but not both.
""",
allow_files = [".js"],
),
"format": attr.string(
doc = """"Specifies the format of the generated bundle. One of the following:
Expand Down Expand Up @@ -79,12 +181,58 @@ Passed to the [`--sourcemap` option](https://github.com/rollup/rollup/blob/maste
def _chunks_dir_out(output_dir, name):
return output_dir if output_dir else name

def _rollup_outs(sourcemap, name, entry_point, output_dir):
# TODO: is it okay that entry_point.name includes extension?
# what if the label was blah.ts?
result = {"entry_point_chunk": "/".join([_chunks_dir_out(output_dir, name), entry_point.name])}
if sourcemap:
result["sourcemap"] = "%s.map" % result["entry_point_chunk"]
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 performs validation:
- exactly one of these attributes should be specified
"""
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:
name = entry_point.name
if name.endswith(".js"):
name = name[:-3]
if name.endswith(".mjs"):
name = name[:-4]
return [name]
return entry_points.values()

def _desugar_entry_points(entry_point, entry_points):
"""Like above, but used by the implementation function, where the types differ.
It also performs validation:
- attr.label_keyed_string_dict doesn't accept allow_single_file
so we have to do validation now to be sure each key is a label resulting in one file
It converts from dict[target: string] to dict[file: string]
"""
names = _desugar_entry_point_names(entry_point.label if entry_point else None, entry_points)

if entry_point:
return {entry_point.files.to_list()[0]: names[0]}

result = {}
for ep in entry_points.items():
entry_point = ep[0]
name = ep[1]
f = entry_point.files.to_list()
if len(f) != 1:
fail("keys in rollup_bundle#entry_points must provide one file, but %s has %s" % (entry_point.label, len(f)))
result[f[0]] = name
return result

def _rollup_outs(sourcemap, name, entry_point, entry_points, output_dir):
"""Supply some labelled outputs in the common case of a single entry point"""
result = {}
for out in _desugar_entry_point_names(entry_point, entry_points):
result[out] = "/".join([_chunks_dir_out(output_dir, name), out + ".js"])
if sourcemap:
result[out + "_map"] = "%s.map" % result[out]
return result

def _no_ext(f):
Expand All @@ -93,13 +241,18 @@ def _no_ext(f):
def _rollup_bundle(ctx):
"Generate a rollup config file and run rollup"

inputs = [ctx.file.entry_point] + ctx.files.srcs + ctx.files.deps
outputs = [ctx.outputs.entry_point_chunk]
inputs = ctx.files.entry_point + ctx.files.entry_points + ctx.files.srcs + ctx.files.deps
outputs = [getattr(ctx.outputs, o) for o in dir(ctx.outputs)]

# See CLI documentation at https://rollupjs.org/guide/en/#command-line-reference
args = ctx.actions.args()

args.add_all(["--input", _no_ext(ctx.file.entry_point)])
# List entry point argument first to save some argv space
# Rollup doc says
# When provided as the first options, it is equivalent to not prefix them with --input
for entry_point in _desugar_entry_points(ctx.attr.entry_point, ctx.attr.entry_points).items():
args.add_joined([entry_point[1], _no_ext(entry_point[0])], join_with = "=")

args.add_all(["--format", ctx.attr.format])

# Assume we always want to generate chunked output, so supply output.dir rather than output.file
Expand Down Expand Up @@ -131,7 +284,6 @@ def _rollup_bundle(ctx):

if (ctx.attr.sourcemap):
args.add("--sourcemap")
outputs.append(ctx.outputs.sourcemap)

if ctx.attr.globals:
args.add("--external")
Expand All @@ -140,42 +292,15 @@ def _rollup_bundle(ctx):
args.add_joined(["%s:%s" % g for g in ctx.attr.globals.items()], join_with = ",")

ctx.actions.run(
progress_message = "Bundling JavaScript %s [rollup]" % ctx.outputs.entry_point_chunk.short_path,
progress_message = "Bundling JavaScript %s [rollup]" % out_dir.short_path,
executable = ctx.executable.rollup_bin,
inputs = inputs,
outputs = outputs,
arguments = [args],
)

rollup_bundle = rule(
doc = """Runs the Rollup.js CLI under Bazel.
See https://rollupjs.org/guide/en/#command-line-reference
Typical example:
```python
load("@npm_bazel_rollup//:index.bzl", "rollup_bundle")
rollup_bundle(
name = "bundle",
srcs = ["dependency.js"],
entry_point = "input.js",
config_file = "rollup.config.js",
)
```
Note that the command-line options set by Bazel override what appears in the rollup config file.
This means that typically a single `rollup.config.js` can contain settings for your whole repo,
and multiple `rollup_bundle` rules can share the configuration.
Thus, setting options that Bazel controls will have no effect, e.g.
```javascript
module.exports = {
output: { file: 'this_is_ignored.js' },
}
```
""",
doc = _DOC,
implementation = _rollup_bundle,
attrs = _ROLLUP_ATTRS,
outputs = _rollup_outs,
Expand Down
23 changes: 9 additions & 14 deletions packages/rollup/test/integration_e2e_rollup/BUILD.bazel
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
load("@build_bazel_rules_nodejs//internal/golden_file_test:golden_file_test.bzl", "golden_file_test")
load("@npm_bazel_rollup//:index.from_src.bzl", "rollup_bundle")
load("@npm_bazel_terser//:index.from_src.bzl", "terser_minified")

[
rollup_bundle(
Expand All @@ -10,6 +9,9 @@ load("@npm_bazel_terser//:index.from_src.bzl", "terser_minified")
entry_point = "foo.js",
format = format,
globals = {"some_global_var": "runtime_name_of_global_var"},
# TODO(alexeagle): this tickles a bug in the linker
# when packages come from execroot but not runfiles
tags = ["fix-windows"],
deps = [
"//%s/fum:fumlib" % package_name(),
"@npm//hello",
Expand All @@ -31,6 +33,9 @@ load("@npm_bazel_terser//:index.from_src.bzl", "terser_minified")
srcs = ["//internal/rollup/test/rollup:bundle-%s_golden.js_" % format],
outs = ["%s_golden.js_" % format],
cmd = "sed s/bundle.%s.js.map/foo.js.map/ $< > $@" % format,
# TODO(alexeagle): this tickles a bug in the linker
# when packages come from execroot but not runfiles
tags = ["fix-windows"],
)
for format in [
"cjs",
Expand All @@ -43,24 +48,14 @@ load("@npm_bazel_terser//:index.from_src.bzl", "terser_minified")
name = "test_%s" % format,
actual = "bundle.%s/foo.js" % format,
golden = "%s_golden.js_" % format,
# TODO(alexeagle): this tickles a bug in the linker
# when packages come from execroot but not runfiles
tags = ["fix-windows"],
)
for format in [
"cjs",
"umd",
]
]

terser_minified(
name = "bundle.debug.min",
src = "bundle.umd/foo.js",
debug = True,
)

golden_file_test(
name = "bundle-min-debug",
actual = "bundle.debug.min.js",
golden = "//internal/rollup/test/rollup:bundle-min-debug_golden.js_",
tags = ["manual"], # not working yet, needs something to do transpilation to es5
)

# TODO(alexeagle): verify against remaining golden files
17 changes: 17 additions & 0 deletions packages/rollup/test/multiple_entry_points/BUILD.bazel
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
load("@npm_bazel_jasmine//:index.from_src.bzl", "jasmine_node_test")
load("@npm_bazel_rollup//:index.from_src.bzl", "rollup_bundle")

rollup_bundle(
name = "bundle",
entry_points = {
"entry1.js": "one",
"entry2.js": "two",
},
)

jasmine_node_test(
name = "test",
srcs = ["spec.js"],
data = ["@npm//source-map"],
deps = [":bundle"],
)
3 changes: 3 additions & 0 deletions packages/rollup/test/multiple_entry_points/entry1.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
const hello1 = document.createElement('span');
hello1.innerText = 'hello from entry point 1';
window.document.body.appendChild(hello1);
3 changes: 3 additions & 0 deletions packages/rollup/test/multiple_entry_points/entry2.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
const hello2 = document.createElement('span');
hello2.innerText = 'hello from entry point 2';
window.document.body.appendChild(hello1);
8 changes: 8 additions & 0 deletions packages/rollup/test/multiple_entry_points/spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
const fs = require('fs');

describe('rollup multiple entry points', () => {
it('should produce a chunk for each entry point', () => {
expect(fs.existsSync(require.resolve(__dirname + '/bundle/one.js'))).toBeTruthy();
expect(fs.existsSync(require.resolve(__dirname + '/bundle/two.js'))).toBeTruthy();
});
});

0 comments on commit f660d39

Please sign in to comment.