-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Rewrites
prerender_component
to use a new design based on `Prerende…
…rMetadataInfo`. See #40 (comment) for full design explanation. This doesn't actually change the existing `prerender_component`. Instead it creates a new `prerender_component2` with the new implementation. The same is done for `prerender_pages_unbundled2` and `prerender_pages2`. Once all existing examples have been migrated to the new implementation, the old one will be deleted, and these macros will be renamed.
- Loading branch information
Showing
4 changed files
with
898 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,268 @@ | ||
"""Defines `prerender_component()` functionality.""" | ||
|
||
load("@aspect_rules_js//js:defs.bzl", "js_library") | ||
load("@aspect_rules_js//js:providers.bzl", "JsInfo", "js_info") | ||
load("@aspect_rules_ts//ts:defs.bzl", "ts_project") | ||
load("//common:label.bzl", "absolute") | ||
load("//common:paths.bzl", "is_js_file", "is_ts_file", "is_ts_declaration_file") | ||
load("//packages/rules_prerender/css:css_binaries.bzl", "css_binaries") | ||
load("//packages/rules_prerender/css:css_group.bzl", "css_group") | ||
load("//packages/rules_prerender/css:css_library.bzl", "css_library") | ||
load( | ||
":prerender_metadata.bzl", | ||
"PrerenderMetadataInfo", | ||
"alias_with_metadata", | ||
"prerender_metadata", | ||
) | ||
load(":web_resources.bzl", "web_resources") | ||
|
||
visibility("public") | ||
|
||
def prerender_component( | ||
name, | ||
prerender, | ||
scripts = None, | ||
styles = None, | ||
resources = None, | ||
testonly = None, | ||
visibility = None, | ||
): | ||
"""Encapsulates an HTML/JS/CSS component for use in prerendering a web page. | ||
This rule encapsulates the HTML, JavaScript, CSS, and static resources used | ||
by a logical "component". A "component" is effectively a prerendered | ||
fragment of HTML which provides some functionality (via JavaScript) and | ||
styling (via CSS) with any required static files (ex. an image) at a | ||
specific path (ex. /my-logo.png). Components are reusable pieces of UI which | ||
can be composed together to build a complex static site. | ||
IMPORTANT: `prerender_component()` has some special rules about _how_ it can | ||
be used. It doesn't inherently _do_ anything special. It only collects all | ||
the various parts of a component (HTML, scripts, styles, resources) with | ||
some extra metadata for the bundling process and re-exports them at | ||
`%{name}_prerender`, `%{name}_scripts`, `%{name}_styles`, and | ||
`%{name}_resources`. | ||
A `prerender_component()` should _not_ be depended upon directly, instead | ||
you should depend on the re-exports for the specific parts of the component | ||
you need. The bundling process will bundle the entire component for you as | ||
expected. For example: | ||
```BUILD | ||
# my_component/BUILD.bazel | ||
prerender_component( | ||
name = "my_component", | ||
prerender = ":prerender", | ||
) | ||
# IMPORTANT! No target except `:my_component` should depend on this. If they | ||
# do, the generated site may be missing scripts, styles, resources, etc. | ||
ts_project( | ||
name = "prerender", | ||
srcs = ["my_component_prerender.mts"], | ||
) | ||
``` | ||
```BUILD | ||
# my_other_component/BUILD.bazel | ||
prerender_component( | ||
name = "my_other_component", | ||
prerender = ":my_other_component_prerender_lib", | ||
# No dependency on `//my_component/...` here. | ||
) | ||
ts_project( | ||
name = "prerender", | ||
srcs = ["my_other_component_prerender.mts"], | ||
# IMPORTANT! Depend on the `_prerender` alias generated by | ||
# `prerender_component()`. DON'T depend on `//my_component:prerender` | ||
# directly. | ||
deps = ["//my_component:my_component_prerender"], | ||
) | ||
``` | ||
The rules here are: | ||
1. Any direct dependency of a `prerender_component()` target should _only_ | ||
be used by that `prerender_component()`. | ||
2. Any additional desired dependencies, should go through the relevant | ||
`_prerender`, `_scripts`, `_styles`, `_resources` re-export generated by | ||
the `prerender_component()` macro. | ||
* Exception: Unit tests may directly depend on targets provided they | ||
do not use any `prerender_*` rules in the test. | ||
3. Never depend on a `prerender_component()` directly, always depend on the | ||
specific re-export you want. | ||
* Exception: You may `bazel build` a `prerender_component()` target or | ||
have a `build_test()` depend on it to verify that it is buildable. | ||
Args: | ||
name: The name of this rule. | ||
prerender: Required. A `ts_project()` target which acts as a library for | ||
prerendering at build time. | ||
scripts: A `ts_project()` holding client-side scripts for this | ||
component. | ||
styles: A `css_library()` holding the styles for this component. | ||
resources: A `web_resources()` target holding other static files needed | ||
by this component at runtime. | ||
testonly: See https://docs.bazel.build/versions/master/be/common-definitions.html. | ||
visibility: See https://docs.bazel.build/versions/master/be/common-definitions.html. | ||
Outputs: | ||
%{name}: A library which verifies that all the different aspects of the | ||
component are buildable and runs various sanity checks. | ||
%{name}_prerender: A reexport of the `prerender` attribute. | ||
%{name}_scripts: A reexport of the `scripts` attribute. | ||
%{name}_styles: A reexport of the `styles` attribute. | ||
%{name}_resources: A reexport of the `resources` attribute. | ||
""" | ||
styles_reexport = "%s_styles_reexport" % name | ||
if styles: | ||
_inline_css_reexport( | ||
name = styles_reexport, | ||
styles = styles, | ||
visibility = visibility, | ||
testonly = testonly, | ||
) | ||
|
||
# Metadata provider. | ||
metadata = "%s_metadata" % name | ||
prerender_metadata( | ||
name = metadata, | ||
prerender = prerender, | ||
scripts = scripts, | ||
styles = ":%s" % styles_reexport if styles else None, | ||
styles_import_map = ":%s" % styles_reexport if styles else None, | ||
resources = resources, | ||
testonly = testonly, | ||
) | ||
|
||
# Prerendering JavaScript. | ||
alias_with_metadata( | ||
name = "%s_prerender" % name, | ||
metadata = ":%s" % metadata, | ||
actual = prerender, | ||
visibility = visibility, | ||
testonly = testonly, | ||
) | ||
|
||
# Client-side JavaScript. | ||
scripts_target = "%s_scripts" % name | ||
if scripts: | ||
alias_with_metadata( | ||
name = scripts_target, | ||
metadata = ":%s" % metadata, | ||
actual = scripts, | ||
visibility = visibility, | ||
testonly = testonly, | ||
) | ||
else: | ||
js_library( | ||
name = scripts_target, | ||
srcs = [], | ||
visibility = visibility, | ||
testonly = testonly, | ||
) | ||
|
||
# CSS styles. | ||
styles_target = "%s_styles" % name | ||
if styles: | ||
alias_with_metadata( | ||
name = styles_target, | ||
metadata = metadata, | ||
actual = ":%s" % styles_reexport, | ||
testonly = testonly, | ||
visibility = visibility, | ||
) | ||
|
||
# Resources. | ||
resources_target = "%s_resources" % name | ||
if resources: | ||
alias_with_metadata( | ||
name = resources_target, | ||
metadata = ":%s" % metadata, | ||
actual = resources, | ||
visibility = visibility, | ||
testonly = testonly, | ||
) | ||
|
||
def _js_reexport_impl(ctx): | ||
merged_js_info = js_info( | ||
declarations = depset([], | ||
transitive = [src[JsInfo].declarations | ||
for src in ctx.attr.srcs], | ||
), | ||
npm_linked_package_files = depset([], | ||
transitive = [src[JsInfo].npm_linked_package_files | ||
for src in ctx.attr.srcs], | ||
), | ||
npm_linked_packages = depset([], | ||
transitive = [src[JsInfo].npm_linked_packages | ||
for src in ctx.attr.srcs], | ||
), | ||
npm_package_store_deps = depset([], | ||
transitive = [src[JsInfo].npm_package_store_deps | ||
for src in ctx.attr.srcs], | ||
), | ||
sources = depset([], | ||
transitive = [src[JsInfo].sources | ||
for src in ctx.attr.srcs], | ||
), | ||
transitive_declarations = depset([], | ||
transitive = [dep[JsInfo].transitive_declarations | ||
for dep in ctx.attr.srcs + ctx.attr.deps], | ||
), | ||
transitive_npm_linked_package_files = depset([], | ||
transitive = [dep[JsInfo].transitive_npm_linked_package_files | ||
for dep in ctx.attr.srcs + ctx.attr.deps], | ||
), | ||
transitive_npm_linked_packages = depset([], | ||
transitive = [dep[JsInfo].transitive_npm_linked_packages | ||
for dep in ctx.attr.srcs + ctx.attr.deps], | ||
), | ||
transitive_sources = depset([], | ||
transitive = [dep[JsInfo].transitive_sources | ||
for dep in ctx.attr.srcs + ctx.attr.deps], | ||
), | ||
) | ||
|
||
return [ | ||
DefaultInfo(files = merged_js_info.sources), | ||
merged_js_info, | ||
] | ||
|
||
_js_reexport = rule( | ||
implementation = _js_reexport_impl, | ||
attrs = { | ||
"srcs": attr.label_list( | ||
default = [], | ||
providers = [JsInfo], | ||
), | ||
"deps": attr.label_list( | ||
default = [], | ||
providers = [JsInfo], | ||
), | ||
}, | ||
doc = """ | ||
Re-exports the given `ts_project()` and `js_library()` targets. Targets | ||
in `srcs` have their direct sources re-exported as the direct sources of | ||
this target, while targets in `deps` are only included as transitive | ||
sources. | ||
This rule serves two purposes: | ||
1. It re-exports **both** `ts_project()` and `js_library()`. | ||
2. It merges multiple targets together, depending on all of them but | ||
only re-exporting direct sources from the `srcs` attribute. Even | ||
with `ts_project()` re-export it is not possible to re-export only | ||
some of the given targets. | ||
""", | ||
) | ||
|
||
def _inline_css_reexport(name, styles, testonly = None, visibility = None): | ||
css_binaries( | ||
name = name, | ||
testonly = testonly, | ||
deps = [styles], | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,92 @@ | ||
load("@aspect_rules_js//js:providers.bzl", "JsInfo") | ||
load( | ||
"//packages/rules_prerender/css:css_providers.bzl", | ||
"CssInfo", | ||
"CssImportMapInfo", | ||
) | ||
load("//packages/rules_prerender:web_resources.bzl", "WebResourceInfo") | ||
|
||
PrerenderMetadataInfo = provider( | ||
"Holds all the providers for each component \"slice\".", | ||
fields = { | ||
"prerender": "The JSInfo of the prerender target.", | ||
"scripts": "The JSInfo of the scripts target.", | ||
"styles": "The CssInfo of the styles target.", | ||
"styles_import_map": "The CssImportMapInfo of the styles target.", | ||
"resources": "The WebResourceInfo of the resources target.", | ||
}, | ||
) | ||
|
||
def _prerender_metadata_impl(ctx): | ||
return PrerenderMetadataInfo( | ||
prerender = _safe_get(ctx.attr.prerender, JsInfo), | ||
scripts = _safe_get(ctx.attr.scripts, JsInfo), | ||
styles = _safe_get(ctx.attr.styles, CssInfo), | ||
styles_import_map = _safe_get(ctx.attr.styles_import_map, CssImportMapInfo), | ||
resources = _safe_get(ctx.attr.resources, WebResourceInfo), | ||
) | ||
|
||
prerender_metadata = rule( | ||
implementation = _prerender_metadata_impl, | ||
attrs = { | ||
"prerender": attr.label( | ||
mandatory = True, | ||
providers = [JsInfo], | ||
), | ||
"scripts": attr.label(providers = [JsInfo]), | ||
"styles": attr.label(providers = [CssInfo]), | ||
"styles_import_map": attr.label(providers = [CssImportMapInfo]), | ||
"resources": attr.label(providers = [WebResourceInfo]), | ||
}, | ||
doc = """ | ||
Collects all the various "slices" of a component together into a single | ||
target and returns a `PrerenderMetadataInfo` linking to all their | ||
providers. | ||
""", | ||
) | ||
|
||
def _alias_with_metadata_impl(ctx): | ||
# Re-export the metadata provider. | ||
providers = [ctx.attr.metadata[PrerenderMetadataInfo]] | ||
|
||
# Re-export all additional known providers from the actual target. | ||
providers.extend(_safe_get_all(ctx.attr.actual, [ | ||
DefaultInfo, | ||
JsInfo, | ||
CssInfo, | ||
CssImportMapInfo, | ||
WebResourceInfo, | ||
])) | ||
|
||
return providers | ||
|
||
alias_with_metadata = rule( | ||
implementation = _alias_with_metadata_impl, | ||
attrs = { | ||
"metadata": attr.label( | ||
mandatory = True, | ||
providers = [PrerenderMetadataInfo], | ||
), | ||
"actual": attr.label( | ||
mandatory = True, | ||
), | ||
}, | ||
doc = """ | ||
Creates an alias to the given `actual` target additionally providing the | ||
`PrerenderMetadataInfo` from the `metadata` target. | ||
""", | ||
) | ||
|
||
def _safe_get(target, provider): | ||
if target and provider in target: return target[provider] | ||
|
||
return None | ||
|
||
def _safe_get_all(target, providers): | ||
output = [] | ||
|
||
for provider in providers: | ||
if provider in target: | ||
output.append(target[provider]) | ||
|
||
return output |
Oops, something went wrong.