Skip to content

Commit 70b63ad

Browse files
committed
Updates script bundling to use multiple entry points.
Refs #26. Previously, we collected all the scripts referenced by `includeScript()` and bundled them together into a single JavaScript file which was injected into _every_ HTML page. This is unnecessarily broad, as not all pages use all the JavaScript. Instead, we should bundle the JavaScript for each HTML page separately, with shared chunks for common functionality. This makes a few updates to support multiple entry points. 1. It updates annotation extractor to no longer generate a list of included scripts, but instead generate a map of HTML file -> list of included scripts. This provides enough information to know which scripts are associated with which page. 2. It updates script entry generator to generate a directory (`TreeArtifact`) of entry points instead of a single entry point. This directory contains an entry point for every HTML file which includes at least one script. 3. It replaces `rollup_bundle()` with a custom rule which invokes the `rollup` binary under the hood. This is necessary because `rollup_bundle()` supports multiple entry points _only_ if those entry points are known at analysis time. In this case, they are not because we can't know what HTML files will include scripts until execution time. 4. It updates resource injector to no longer accept a single JavaScript bundle, but instead accept a directory of JavaScript bundles. These bundles are directly copied to the output directory and also any matching HTML file gets its associated JavaScript file injected. In the process I also updated `rollup.config.ts` so it is authored in TypeScript and includes its own plugin dependenices.
1 parent 6609dc9 commit 70b63ad

19 files changed

Lines changed: 471 additions & 204 deletions

common/models/prerender_metadata.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
/** Metadata about a prerendered HTML file. */
22
export interface PrerenderMetadata {
3-
/** Scripts to include. */
4-
readonly scripts: readonly ScriptMetadata[];
3+
/** Map of HTML relative path to a list of scripts it includes. */
4+
readonly includedScripts: Record<string /* htmlRelPath */, ScriptMetadata[]>;
55
}
66

77
/** Metadata for a script to be included in a prerendered HTML file. */

common/models/prerender_metadata_mock.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,9 @@ export function mockPrerenderMetadata(
1010
overrides: Partial<PrerenderMetadata> = {},
1111
): PrerenderMetadata {
1212
return {
13-
scripts: [],
13+
includedScripts: {
14+
...overrides.includedScripts,
15+
},
1416
...overrides,
1517
};
1618
}

packages/rules_prerender/BUILD.bazel

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,23 @@
1+
load("@rules_prerender_npm//:rollup/package_json.bzl", "bin")
12
load("@aspect_bazel_lib//lib:copy_to_bin.bzl", "copy_to_bin")
23
load("@bazel_skylib//:bzl_library.bzl", "bzl_library")
34
load("//:index.bzl", "prerender_component")
45
load("//tools/jasmine:defs.bzl", "jasmine_node_test")
56
load("//tools/typescript:defs.bzl", "ts_project")
67

7-
copy_to_bin(
8+
bin.rollup_binary(
9+
name = "rollup",
10+
visibility = ["//visibility:public"],
11+
)
12+
13+
ts_project(
814
name = "rollup_config",
9-
srcs = ["rollup-default.config.js"],
15+
srcs = ["rollup.config.ts"],
1016
visibility = ["//visibility:public"],
17+
deps = [
18+
"//:node_modules/@rollup/plugin-node-resolve",
19+
"//:node_modules/rollup",
20+
],
1121
)
1222

1323
ts_project(
@@ -103,6 +113,7 @@ bzl_library(
103113
deps = [
104114
":multi_inject_resources",
105115
":prerender_pages_unbundled",
116+
":scripts_bundle",
106117
":web_resources",
107118
],
108119
)
@@ -119,6 +130,11 @@ bzl_library(
119130
],
120131
)
121132

133+
bzl_library(
134+
name = "scripts_bundle",
135+
srcs = ["scripts_bundle.bzl"],
136+
)
137+
122138
bzl_library(
123139
name = "web_resources",
124140
srcs = ["web_resources.bzl"],

packages/rules_prerender/multi_inject_resources.bzl

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,16 +20,16 @@ def _multi_inject_resources_impl(ctx):
2020
args = ctx.actions.args()
2121
args.add("--input-dir", ctx.file.input_dir.short_path)
2222
args.add("--config", config.short_path)
23-
if ctx.attr.bundle:
24-
args.add("--bundle", ctx.file.bundle.short_path)
23+
if ctx.attr.bundles:
24+
args.add("--bundles", ctx.file.bundles.short_path)
2525
args.add("--output-dir", output_dir.short_path)
2626
ctx.actions.run(
2727
mnemonic = "MultiInjectResources",
2828
progress_message = "Injecting resources into multiple pages",
2929
executable = ctx.executable._injector,
3030
arguments = [args],
3131
inputs = [ctx.file.input_dir, config] +
32-
([ctx.file.bundle] if ctx.file.bundle else []) +
32+
([ctx.file.bundles] if ctx.file.bundles else []) +
3333
ctx.files.styles,
3434
outputs = [output_dir],
3535
env = {
@@ -49,7 +49,7 @@ multi_inject_resources = rule(
4949
mandatory = True,
5050
allow_single_file = True,
5151
),
52-
"bundle": attr.label(allow_single_file = True),
52+
"bundles": attr.label(allow_single_file = True),
5353
"scripts": attr.string_list(),
5454
"styles": attr.label(),
5555
"_injector": attr.label(

packages/rules_prerender/prerender_pages.bzl

Lines changed: 6 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
"""Defines `prerender_pages()` functionality."""
22

3-
load("@aspect_rules_rollup//rollup:defs.bzl", "rollup_bundle")
43
load(":multi_inject_resources.bzl", "multi_inject_resources")
54
load(":prerender_pages_unbundled.bzl", "prerender_pages_unbundled")
5+
load(":scripts_bundle.bzl", "scripts_bundle")
66
load(":web_resources.bzl", "web_resources")
77

88
visibility(["//"])
@@ -124,25 +124,20 @@ def prerender_pages(
124124

125125
bundle = "%s_bundle" % name
126126
if bundle_js:
127-
# Bundle all client-side scripts at `%{name}_bundle.js`.
128-
rollup_bundle(
127+
# Bundle all client-side scripts in a `TreeArtifact` at `%{name}_bundle`.
128+
scripts_bundle(
129129
name = bundle,
130-
entry_point = ":%s_scripts.js" % prerender_name,
131-
config_file = Label("//packages/rules_prerender:rollup_config"),
132-
silent = True,
130+
entry_points = ":%s_scripts_entries" % prerender_name,
133131
testonly = testonly,
134-
deps = [
135-
":%s_scripts" % prerender_name,
136-
"//:node_modules/@rollup/plugin-node-resolve",
137-
],
132+
deps = [":%s_scripts" % prerender_name],
138133
)
139134

140135
# Inject bundled JS and CSS into the HTML.
141136
injected_dir = "%s_injected" % name
142137
multi_inject_resources(
143138
name = injected_dir,
144139
input_dir = ":%s" % prerender_name,
145-
bundle = ":%s" % bundle if bundle_js else None,
140+
bundles = ":%s" % bundle if bundle_js else None,
146141
styles = ":%s_styles" % prerender_name,
147142
testonly = testonly,
148143
)

packages/rules_prerender/prerender_pages_unbundled.bzl

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ load("@aspect_rules_js//js:defs.bzl", "js_library")
44
load("//common:label.bzl", "absolute", "file_path_of", "rel_path")
55
load(":prerender_component.bzl", "prerender_component")
66
load(":prerender_resources.bzl", "prerender_resources_internal")
7-
load(":script_entry_point.bzl", "script_entry_point")
7+
load(":script_entry_point.bzl", "script_entry_points")
88
load(":web_resources.bzl", "WebResourceInfo", "web_resources")
99

1010
visibility(["//"])
@@ -60,10 +60,12 @@ def prerender_pages_unbundled(
6060
Outputs:
6161
%{name}: A `web_resources()` target containing all the files generated
6262
by the `src` file at their corresponding locations.
63-
%{name}_scripts: A `js_library()` rule containing all the client-side
63+
%{name}_scripts: A `js_library()` target containing all the client-side
6464
scripts used by the page. This includes a generated file
6565
`%{name}_scripts.js` which acts as an entry point, importing all
6666
scripts that were included in the page via `includeScript()`.
67+
%{name}_scripts_entries: A `TreeArtifact` containing all the entry points
68+
used for bundling laid out in the generated directory format.
6769
%{name}_resources: A `web_resources()` target containing all the
6870
transitively used resources.
6971
%{name}_prerender_for_test: An alias to the `ts_project()` target which
@@ -144,11 +146,9 @@ def prerender_pages_unbundled(
144146

145147
# Generate the entry point importing all included scripts.
146148
client_scripts = "%s_scripts" % name
147-
script_entry = "%s.js" % client_scripts
148-
script_entry_point(
149-
name = "%s_entry" % client_scripts,
149+
script_entry_points(
150+
name = "%s_entries" % client_scripts,
150151
metadata = metadata,
151-
output_entry_point = script_entry,
152152
testonly = testonly,
153153
# Export this file so Rollup can have a direct, label reference to the
154154
# entry point, since including the file in a `depset()` with other files
@@ -157,10 +157,9 @@ def prerender_pages_unbundled(
157157
)
158158

159159
# Reexport all included scripts at `%{name}_scripts`.
160-
js_library(
160+
native.alias(
161161
name = client_scripts,
162-
srcs = [script_entry],
163-
deps = [":%s" % component_scripts],
162+
actual = ":%s" % component_scripts,
164163
testonly = testonly,
165164
visibility = visibility,
166165
)

packages/rules_prerender/rollup-default.config.js renamed to packages/rules_prerender/rollup.config.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { nodeResolve } from '@rollup/plugin-node-resolve';
2+
import { RollupOptions } from 'rollup';
23

34
export default {
45
plugins: [
@@ -19,4 +20,4 @@ export default {
1920

2021
throw new Error(warning.message);
2122
},
22-
};
23+
} as RollupOptions;
Lines changed: 58 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,40 +1,66 @@
11
"""Defines functionality to generate entry points for JS and CSS."""
22

3+
load("@aspect_bazel_lib//lib:paths.bzl", "to_output_relative_path")
34
load("@aspect_rules_js//js:defs.bzl", "js_run_binary")
45
load("//common:label.bzl", "absolute", "file_path_of")
56

67
visibility(["//"])
78

8-
def script_entry_point(
9-
name,
10-
metadata,
11-
output_entry_point,
12-
testonly = None,
13-
visibility = None,
14-
):
15-
"""Generates an entry point for scripts with the given metadata.
16-
17-
Args:
18-
name: Name of this target.
19-
metadata: A metadata JSON file containing information about the scripts
20-
to bundle.
21-
output_entry_point: Location to write the generated script entry point.
22-
testonly: https://bazel.build/reference/be/common-definitions#common-attributes
23-
visibility: https://bazel.build/reference/be/common-definitions#common-attributes
24-
"""
25-
package_depth = len(native.package_name().split("/")) if native.package_name() != "" else 0
26-
js_run_binary(
27-
name = name,
28-
mnemonic = "GenerateScriptEntryPoint",
29-
progress_message = "Generating script entry point %{label}",
30-
srcs = [metadata],
31-
outs = [output_entry_point],
32-
tool = Label("//tools/binaries/script_entry_generator"),
33-
args = [
34-
"--metadata", file_path_of(absolute(metadata)),
35-
"--import-depth", str(package_depth),
36-
"--output", file_path_of(absolute(output_entry_point)),
37-
],
38-
testonly = testonly,
39-
visibility = visibility,
9+
def _script_entry_points_impl(ctx):
10+
output = ctx.actions.declare_directory(ctx.label.name)
11+
12+
args = ctx.actions.args()
13+
args.add("--metadata", to_output_relative_path(ctx.file.metadata))
14+
args.add("--output-dir", to_output_relative_path(output))
15+
16+
ctx.actions.run(
17+
mnemonic = "GenerateScriptEntryPoints",
18+
progress_message = "Generating script entry points %{label}",
19+
executable = ctx.executable._script_entry_generator,
20+
arguments = [args],
21+
inputs = [ctx.file.metadata],
22+
outputs = [output],
23+
env = {"BAZEL_BINDIR": ctx.bin_dir.path},
4024
)
25+
26+
return DefaultInfo(files = depset([output]))
27+
28+
script_entry_points = rule(
29+
implementation = _script_entry_points_impl,
30+
attrs = {
31+
"metadata": attr.label(
32+
mandatory = True,
33+
allow_single_file = True,
34+
doc = """
35+
A metadata JSON file containing information about the scripts
36+
to bundle.
37+
"""
38+
),
39+
"_script_entry_generator": attr.label(
40+
default = "//tools/binaries/script_entry_generator",
41+
executable = True,
42+
cfg = "exec",
43+
),
44+
},
45+
doc = """
46+
Generates a `TreeArtifact` at `%{name}` with a `.js` file generated for
47+
every HTML file with included scripts as defined in the given `metadata`
48+
file. For example, if the user generates the following HTML files and
49+
includes the associated JavaScript files:
50+
51+
```
52+
* /index.html - [ path/to/pkg/foo.js, path/to/pkg/baz.js ]
53+
* /dir/other.html - [ path/to/pkg/bar.js, path/to/pkg/baz.js ]
54+
```
55+
56+
Then `script_entry_points` will generate a `TreeArtifact` with:
57+
58+
```
59+
* /index.js - `import './path/to/pkg/foo.js'; import './path/to/pkg/baz.js';`
60+
* /dir/other.js - `import './path/to/pkg/bar.js'; import './path/to/pkg/baz.js';`
61+
```
62+
63+
These can serve as entry points for the JavaScript bundles necessary for
64+
each HTML page.
65+
""",
66+
)

0 commit comments

Comments
 (0)