Skip to content

Commit f660d39

Browse files
committed
feat(rollup): support multiple entry points
1 parent cfef773 commit f660d39

7 files changed

Lines changed: 211 additions & 61 deletions

File tree

packages/rollup/src/rollup.config.js

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22

33
module.exports = {
44
output: {
5-
name: 'bundle',
65
},
76
plugins: [],
87
};

packages/rollup/src/rollup_bundle.bzl

Lines changed: 171 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,61 @@
22

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

5+
_DOC = """Runs the Rollup.js CLI under Bazel.
6+
7+
See https://rollupjs.org/guide/en/#command-line-reference
8+
9+
Typical example:
10+
```python
11+
load("@npm_bazel_rollup//:index.bzl", "rollup_bundle")
12+
13+
rollup_bundle(
14+
name = "bundle",
15+
srcs = ["dependency.js"],
16+
entry_point = "input.js",
17+
config_file = "rollup.config.js",
18+
)
19+
```
20+
21+
Note that the command-line options set by Bazel override what appears in the rollup config file.
22+
This means that typically a single `rollup.config.js` can contain settings for your whole repo,
23+
and multiple `rollup_bundle` rules can share the configuration.
24+
25+
Thus, setting options that Bazel controls will have no effect, e.g.
26+
27+
```javascript
28+
module.exports = {
29+
output: { file: 'this_is_ignored.js' },
30+
}
31+
```
32+
33+
The rollup_bundle rule always produces a directory output, because it isn't known until
34+
rollup runs whether the output has many chunks or is a single file.
35+
36+
To get multiple output formats, wrap the rule with a macro or list comprehension, e.g.
37+
38+
```python
39+
[
40+
rollup_bundle(
41+
name = "bundle.%s" % format,
42+
entry_point = "foo.js",
43+
format = format,
44+
)
45+
for format in [
46+
"cjs",
47+
"umd",
48+
]
49+
]
50+
```
51+
52+
This will produce one output directory per requested format.
53+
"""
54+
555
_ROLLUP_ATTRS = {
656
"srcs": attr.label_list(
7-
doc = """JavaScript source files from the workspace.
57+
doc = """Non-entry point JavaScript source files from the workspace.
858
9-
The file passed to entry_point is automatically added.
59+
You must not repeat file(s) passed to entry_point/entry_points.
1060
""",
1161
# Don't try to constrain the filenames, could be json, svg, whatever
1262
allow_files = True,
@@ -23,13 +73,65 @@ If not set, a default basic Rollup config is used.
2373
default = "@npm_bazel_rollup//:rollup.config.js",
2474
),
2575
"entry_point": attr.label(
26-
doc = """The bundle's entry point(s) (e.g. your main.js or app.js or index.js).
76+
doc = """The bundle's entry point (e.g. your main.js or app.js or index.js).
77+
78+
This is just a shortcut for the `entry_points` attribute with a single output chunk named the same as the entry_point attribute.
79+
80+
For example, these are equivalent:
81+
82+
```python
83+
rollup_bundle(
84+
name = "bundle",
85+
entry_point = "index.js",
86+
)
87+
```
88+
89+
```python
90+
rollup_bundle(
91+
name = "bundle",
92+
entry_points = {
93+
"index.js": "index"
94+
}
95+
)
96+
```
97+
98+
If the entry_point attribute is instead a label that produces a single .js file,
99+
this will work, but the resulting output will be named after the label,
100+
so these are equivalent:
27101
28-
Passed to the [`--input` option](https://github.com/rollup/rollup/blob/master/docs/999-big-list-of-options.md#input) in Rollup.
29-
If you provide an array of entry points or an object mapping names to entry points, they will be bundled to separate output chunks.
102+
```python
103+
# Outputs index.js
104+
produces_js(
105+
name = "producer",
106+
)
107+
rollup_bundle(
108+
name = "bundle",
109+
entry_point = "producer",
110+
)
111+
```
112+
113+
```python
114+
rollup_bundle(
115+
name = "bundle",
116+
entry_points = {
117+
"index.js": "producer"
118+
}
119+
)
120+
```
30121
""",
31-
mandatory = True,
32-
allow_single_file = True,
122+
allow_single_file = [".js"],
123+
),
124+
"entry_points": attr.label_keyed_string_dict(
125+
doc = """The bundle's entry points (e.g. your main.js or app.js or index.js).
126+
127+
Passed to the [`--input` option](https://github.com/rollup/rollup/blob/master/docs/999-big-list-of-options.md#input) in Rollup.
128+
129+
Keys in this dictionary are labels pointing to .js entry point files.
130+
Values are the name to be given to the corresponding output chunk.
131+
132+
Either this attribute or `entry_point` must be specified, but not both.
133+
""",
134+
allow_files = [".js"],
33135
),
34136
"format": attr.string(
35137
doc = """"Specifies the format of the generated bundle. One of the following:
@@ -79,12 +181,58 @@ Passed to the [`--sourcemap` option](https://github.com/rollup/rollup/blob/maste
79181
def _chunks_dir_out(output_dir, name):
80182
return output_dir if output_dir else name
81183

82-
def _rollup_outs(sourcemap, name, entry_point, output_dir):
83-
# TODO: is it okay that entry_point.name includes extension?
84-
# what if the label was blah.ts?
85-
result = {"entry_point_chunk": "/".join([_chunks_dir_out(output_dir, name), entry_point.name])}
86-
if sourcemap:
87-
result["sourcemap"] = "%s.map" % result["entry_point_chunk"]
184+
def _desugar_entry_point_names(entry_point, entry_points):
185+
"""Users can specify entry_point (sugar) or entry_points (long form).
186+
187+
This function allows our code to treat it like they always used the long form.
188+
189+
It also performs validation:
190+
- exactly one of these attributes should be specified
191+
"""
192+
if entry_point and entry_points:
193+
fail("Cannot specify both entry_point and entry_points")
194+
if not entry_point and not entry_points:
195+
fail("One of entry_point or entry_points must be specified")
196+
if entry_point:
197+
name = entry_point.name
198+
if name.endswith(".js"):
199+
name = name[:-3]
200+
if name.endswith(".mjs"):
201+
name = name[:-4]
202+
return [name]
203+
return entry_points.values()
204+
205+
def _desugar_entry_points(entry_point, entry_points):
206+
"""Like above, but used by the implementation function, where the types differ.
207+
208+
It also performs validation:
209+
- attr.label_keyed_string_dict doesn't accept allow_single_file
210+
so we have to do validation now to be sure each key is a label resulting in one file
211+
212+
It converts from dict[target: string] to dict[file: string]
213+
"""
214+
names = _desugar_entry_point_names(entry_point.label if entry_point else None, entry_points)
215+
216+
if entry_point:
217+
return {entry_point.files.to_list()[0]: names[0]}
218+
219+
result = {}
220+
for ep in entry_points.items():
221+
entry_point = ep[0]
222+
name = ep[1]
223+
f = entry_point.files.to_list()
224+
if len(f) != 1:
225+
fail("keys in rollup_bundle#entry_points must provide one file, but %s has %s" % (entry_point.label, len(f)))
226+
result[f[0]] = name
227+
return result
228+
229+
def _rollup_outs(sourcemap, name, entry_point, entry_points, output_dir):
230+
"""Supply some labelled outputs in the common case of a single entry point"""
231+
result = {}
232+
for out in _desugar_entry_point_names(entry_point, entry_points):
233+
result[out] = "/".join([_chunks_dir_out(output_dir, name), out + ".js"])
234+
if sourcemap:
235+
result[out + "_map"] = "%s.map" % result[out]
88236
return result
89237

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

96-
inputs = [ctx.file.entry_point] + ctx.files.srcs + ctx.files.deps
97-
outputs = [ctx.outputs.entry_point_chunk]
244+
inputs = ctx.files.entry_point + ctx.files.entry_points + ctx.files.srcs + ctx.files.deps
245+
outputs = [getattr(ctx.outputs, o) for o in dir(ctx.outputs)]
98246

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

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

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

132285
if (ctx.attr.sourcemap):
133286
args.add("--sourcemap")
134-
outputs.append(ctx.outputs.sourcemap)
135287

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

142294
ctx.actions.run(
143-
progress_message = "Bundling JavaScript %s [rollup]" % ctx.outputs.entry_point_chunk.short_path,
295+
progress_message = "Bundling JavaScript %s [rollup]" % out_dir.short_path,
144296
executable = ctx.executable.rollup_bin,
145297
inputs = inputs,
146298
outputs = outputs,
147299
arguments = [args],
148300
)
149301

150302
rollup_bundle = rule(
151-
doc = """Runs the Rollup.js CLI under Bazel.
152-
153-
See https://rollupjs.org/guide/en/#command-line-reference
154-
155-
Typical example:
156-
```python
157-
load("@npm_bazel_rollup//:index.bzl", "rollup_bundle")
158-
159-
rollup_bundle(
160-
name = "bundle",
161-
srcs = ["dependency.js"],
162-
entry_point = "input.js",
163-
config_file = "rollup.config.js",
164-
)
165-
```
166-
167-
Note that the command-line options set by Bazel override what appears in the rollup config file.
168-
This means that typically a single `rollup.config.js` can contain settings for your whole repo,
169-
and multiple `rollup_bundle` rules can share the configuration.
170-
171-
Thus, setting options that Bazel controls will have no effect, e.g.
172-
173-
```javascript
174-
module.exports = {
175-
output: { file: 'this_is_ignored.js' },
176-
}
177-
```
178-
""",
303+
doc = _DOC,
179304
implementation = _rollup_bundle,
180305
attrs = _ROLLUP_ATTRS,
181306
outputs = _rollup_outs,

packages/rollup/test/integration_e2e_rollup/BUILD.bazel

Lines changed: 9 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
load("@build_bazel_rules_nodejs//internal/golden_file_test:golden_file_test.bzl", "golden_file_test")
22
load("@npm_bazel_rollup//:index.from_src.bzl", "rollup_bundle")
3-
load("@npm_bazel_terser//:index.from_src.bzl", "terser_minified")
43

54
[
65
rollup_bundle(
@@ -10,6 +9,9 @@ load("@npm_bazel_terser//:index.from_src.bzl", "terser_minified")
109
entry_point = "foo.js",
1110
format = format,
1211
globals = {"some_global_var": "runtime_name_of_global_var"},
12+
# TODO(alexeagle): this tickles a bug in the linker
13+
# when packages come from execroot but not runfiles
14+
tags = ["fix-windows"],
1315
deps = [
1416
"//%s/fum:fumlib" % package_name(),
1517
"@npm//hello",
@@ -31,6 +33,9 @@ load("@npm_bazel_terser//:index.from_src.bzl", "terser_minified")
3133
srcs = ["//internal/rollup/test/rollup:bundle-%s_golden.js_" % format],
3234
outs = ["%s_golden.js_" % format],
3335
cmd = "sed s/bundle.%s.js.map/foo.js.map/ $< > $@" % format,
36+
# TODO(alexeagle): this tickles a bug in the linker
37+
# when packages come from execroot but not runfiles
38+
tags = ["fix-windows"],
3439
)
3540
for format in [
3641
"cjs",
@@ -43,24 +48,14 @@ load("@npm_bazel_terser//:index.from_src.bzl", "terser_minified")
4348
name = "test_%s" % format,
4449
actual = "bundle.%s/foo.js" % format,
4550
golden = "%s_golden.js_" % format,
51+
# TODO(alexeagle): this tickles a bug in the linker
52+
# when packages come from execroot but not runfiles
53+
tags = ["fix-windows"],
4654
)
4755
for format in [
4856
"cjs",
4957
"umd",
5058
]
5159
]
5260

53-
terser_minified(
54-
name = "bundle.debug.min",
55-
src = "bundle.umd/foo.js",
56-
debug = True,
57-
)
58-
59-
golden_file_test(
60-
name = "bundle-min-debug",
61-
actual = "bundle.debug.min.js",
62-
golden = "//internal/rollup/test/rollup:bundle-min-debug_golden.js_",
63-
tags = ["manual"], # not working yet, needs something to do transpilation to es5
64-
)
65-
6661
# TODO(alexeagle): verify against remaining golden files
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
load("@npm_bazel_jasmine//:index.from_src.bzl", "jasmine_node_test")
2+
load("@npm_bazel_rollup//:index.from_src.bzl", "rollup_bundle")
3+
4+
rollup_bundle(
5+
name = "bundle",
6+
entry_points = {
7+
"entry1.js": "one",
8+
"entry2.js": "two",
9+
},
10+
)
11+
12+
jasmine_node_test(
13+
name = "test",
14+
srcs = ["spec.js"],
15+
data = ["@npm//source-map"],
16+
deps = [":bundle"],
17+
)
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
const hello1 = document.createElement('span');
2+
hello1.innerText = 'hello from entry point 1';
3+
window.document.body.appendChild(hello1);
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
const hello2 = document.createElement('span');
2+
hello2.innerText = 'hello from entry point 2';
3+
window.document.body.appendChild(hello1);
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
const fs = require('fs');
2+
3+
describe('rollup multiple entry points', () => {
4+
it('should produce a chunk for each entry point', () => {
5+
expect(fs.existsSync(require.resolve(__dirname + '/bundle/one.js'))).toBeTruthy();
6+
expect(fs.existsSync(require.resolve(__dirname + '/bundle/two.js'))).toBeTruthy();
7+
});
8+
});

0 commit comments

Comments
 (0)