Skip to content

Commit

Permalink
Accepts JS sources in prerender_component() and a js_library() as…
Browse files Browse the repository at this point in the history
… a scripts dependency.

This chooses to generate either a `ts_library()` or `js_library()` based on the sources provided to `prerender_component()`. Whatever is given, is re-exported via `js_reexport()` which propagates both `JSModuleInfo` and `JSEcmaScriptModuleInfo`. This needs to be custom because `js_library()` only returns `JSModuleInfo` while `ts_library()` returns both, but a `js_library()` that depends on a `ts_library()` will drop its dependency's `JSEcmaScriptModuleInfo`, meaning there is no clean way of re-exporting a target which may be a `js_library()` or a `ts_library()`.
  • Loading branch information
dgp1130 committed Aug 28, 2021
1 parent 1c8a283 commit 656bf98
Show file tree
Hide file tree
Showing 9 changed files with 213 additions and 26 deletions.
15 changes: 15 additions & 0 deletions common/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ load("@npm//@bazel/typescript:index.bzl", "ts_library")
load("//tools:jasmine.bzl", "jasmine_node_test")
load("//tools:publish.bzl", "publish_files")
load(":label_test.bzl", "label_test_suite")
load(":paths_test.bzl", "paths_test_suite")

publish_files(
name = "publish_files",
Expand Down Expand Up @@ -118,6 +119,20 @@ bzl_library(

label_test_suite(name = "label_test")

bzl_library(
name = "paths",
srcs = ["paths.bzl"],
visibility = ["//visibility:public"],
)

bzl_library(
name = "paths_test_lib",
srcs = ["paths_test.bzl"],
deps = [":paths"],
)

paths_test_suite(name = "paths_test")

ts_library(
name = "runfiles",
srcs = ["runfiles.ts"],
Expand Down
5 changes: 5 additions & 0 deletions common/paths.bzl
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
"""Utilities based around file paths."""

def is_js_file(src):
"""Returns whether or not the given src path is a JavaScript file."""
return src.endswith(".js") or src.endswith(".mjs") or src.endswith(".cjs")
22 changes: 22 additions & 0 deletions common/paths_test.bzl
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
"""Tests for `paths.bzl`."""

load("@bazel_skylib//lib:unittest.bzl", "asserts", "unittest")
load(":paths.bzl", "is_js_file")

def _is_js_file_impl(ctx):
env = unittest.begin(ctx)

asserts.equals(env, True, is_js_file("foo/bar/baz.js"))
asserts.equals(env, True, is_js_file("foo/bar/baz.mjs"))
asserts.equals(env, True, is_js_file("foo/bar/baz.cjs"))

asserts.equals(env, False, is_js_file("foo/bar/baz.ts"))
asserts.equals(env, False, is_js_file("foo/bar/baz.d.ts"))
asserts.equals(env, False, is_js_file("foo/bar/baz.test"))

return unittest.end(env)

_is_js_file_test = unittest.make(_is_js_file_impl)

def paths_test_suite(name):
unittest.suite(name, _is_js_file_test)
4 changes: 3 additions & 1 deletion examples/custom_bundling/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,9 @@ ts_library(
# Manually bundle all scripts.
rollup_bundle(
name = "bundle",
entry_point = ":page_scripts.ts",
# TODO: Need to use `_esm_sources()` to pick out the `.mjs` file and depend
# on it directly.
entry_point = ":page_scripts.mjs",
config_file = "rollup.config.js",
link_workspace_root = True,
silent = True,
Expand Down
2 changes: 2 additions & 0 deletions packages/rules_prerender/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ bzl_library(
deps = [
":web_resources",
"//common:label",
"//common:paths",
],
)

Expand Down Expand Up @@ -96,6 +97,7 @@ bzl_library(
visibility = ["//:__pkg__"],
deps = [
":web_resources",
"//common:paths",
"//packages/renderer:build_vars",
],
)
Expand Down
125 changes: 110 additions & 15 deletions packages/rules_prerender/prerender_component.bzl
Original file line number Diff line number Diff line change
@@ -1,7 +1,14 @@
"""Defines `prerender_component()` functionality."""

load("@build_bazel_rules_nodejs//:index.bzl", "js_library")
load(
"@build_bazel_rules_nodejs//:providers.bzl",
"JSModuleInfo",
"JSEcmaScriptModuleInfo",
)
load("@npm//@bazel/typescript:index.bzl", "ts_library")
load("//common:label.bzl", "absolute")
load("//common:paths.bzl", "is_js_file")
load(":web_resources.bzl", "web_resources")

def prerender_component(
Expand Down Expand Up @@ -31,11 +38,14 @@ def prerender_component(
Args:
name: The name of this rule.
srcs: The TypeScript source files for use in prerendering.
srcs: The source files for use in prerendering. May be `*.ts` files or
`*.js`/`*.mjs`/`*.cjs`. All source files must be JavaScript, or all
source files must be TypeScript. However, mixing the two is not
allowed. `*.d.ts` files are also allowed in either case.
tsconfig: A label referencing a tsconfig.json file or `ts_config()`
target. Will be used to compile files in `srcs`.
target. Will be used to compile `*.ts` files in `srcs`.
data: See https://docs.bazel.build/versions/master/be/common-definitions.html.
lib_deps: `ts_library()` dependencies for the TypeScript source files.
lib_deps: Dependencies for the source files.
scripts: List of client-side JavaScript libraries which can be included
in the prerendered HTML.
styles: List of CSS files or `filegroup()`s of CSS files which can be
Expand Down Expand Up @@ -66,26 +76,42 @@ def prerender_component(
"""

prerender_lib = "%s_prerender" % name
ts_library(
name = prerender_lib,
srcs = srcs,
tsconfig = tsconfig,
data = data,
deps = lib_deps + ["%s_prerender" % absolute(dep) for dep in deps],
testonly = testonly,
visibility = visibility,
)
if all([src.endswith(".ts") or src.endswith(".d.ts") for src in srcs]):
ts_library(
name = prerender_lib,
srcs = srcs,
tsconfig = tsconfig,
data = data + styles,
deps = lib_deps + ["%s_prerender" % absolute(dep) for dep in deps],
testonly = testonly,
visibility = visibility,
)
elif all([is_js_file(src) or src.endswith(".d.ts") for src in srcs]):
js_library(
name = prerender_lib,
srcs = srcs + data + styles, # `data` is included in `srcs`.
deps = lib_deps + ["%s_prerender" % absolute(dep) for dep in deps],
testonly = testonly,
visibility = visibility,
)
else:
fail(" ".join("""
All sources must be TypeScript (`*.ts`) or all sources must be JavaScript
(`*.js` / `*.mjs` / `*.cjs`). It is not possible to use some JavaScript sources
and some TypeScript sources in the same component (excluding `*.d.ts` files,
which are always allowed).
""".strip().split("\n")))

native.alias(
name = "%s_prerender_for_test" % name,
actual = ":%s" % prerender_lib,
testonly = True,
)

ts_library(
js_reexport(
name = "%s_scripts" % name,
srcs = [],
deps = scripts + ["%s_scripts" % absolute(dep) for dep in deps],
srcs = scripts,
deps = ["%s_scripts" % absolute(dep) for dep in deps],
testonly = testonly,
visibility = visibility,
)
Expand All @@ -103,3 +129,72 @@ def prerender_component(
visibility = visibility,
deps = resources + ["%s_resources" % absolute(dep) for dep in deps],
)

def _js_reexport_impl(ctx):
# TODO
# for dep in ctx.attr.deps:
# if JSEcmaScriptModuleInfo in dep:
# print("%s - %s" % (ctx.label, dep[JSEcmaScriptModuleInfo]))
js_srcs = [src for src in ctx.files.srcs if is_js_file(src.path)]

merged_js_module_info = JSModuleInfo(
direct_sources = depset(js_srcs,
transitive = [src[JSModuleInfo].direct_sources
for src in ctx.attr.srcs
if JSModuleInfo in src],
),
sources = depset(js_srcs,
transitive = [dep[JSModuleInfo].sources
for dep in ctx.attr.srcs + ctx.attr.deps
if JSModuleInfo in dep],
),
)

merged_js_ecma_script_module_info = JSEcmaScriptModuleInfo(
direct_sources = depset(js_srcs,
transitive = [src[JSEcmaScriptModuleInfo].direct_sources
for src in ctx.attr.srcs
if JSEcmaScriptModuleInfo in src],
),
sources = depset(js_srcs,
transitive = [dep[JSEcmaScriptModuleInfo].sources
for dep in ctx.attr.srcs + ctx.attr.deps
if JSEcmaScriptModuleInfo in dep],
),
)

# TODO: Remove
output_group_info = OutputGroupInfo(
js_module_info_direct_sources = depset(js_srcs,
transitive = [src[JSModuleInfo].direct_sources
for src in ctx.attr.srcs
if JSModuleInfo in src],
),
js_ecma_script_module_info_direct_sources = depset(js_srcs,
transitive = [src[JSEcmaScriptModuleInfo].direct_sources
for src in ctx.attr.srcs
if JSEcmaScriptModuleInfo in src],
),
)

return [
merged_js_module_info,
merged_js_ecma_script_module_info,
output_group_info,
]

# TODO: Rename? It's not really a reexport?
# TODO: Move to own file?
js_reexport = rule(
implementation = _js_reexport_impl,
attrs = {
"srcs": attr.label_list(
default = [],
allow_files = True,
),
"deps": attr.label_list(
mandatory = True,
providers = [JSModuleInfo],
),
},
)
45 changes: 43 additions & 2 deletions packages/rules_prerender/prerender_pages.bzl
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
"""Defines `prerender_pages()` functionality."""

load(
"@build_bazel_rules_nodejs//:providers.bzl",
"JSEcmaScriptModuleInfo",
"JSModuleInfo",
)
load("@npm//@bazel/postcss:index.bzl", "postcss_binary")
load("@npm//@bazel/rollup:index.bzl", "rollup_bundle")
load(":multi_inject_resources.bzl", "multi_inject_resources")
Expand Down Expand Up @@ -122,18 +127,24 @@ def prerender_pages(
testonly = True,
)

_esm_sources(
name = "%s_scripts" % name,
dep = "%s_scripts" % prerender_name,
testonly = testonly,
)

bundle = "%s_bundle" % name
if bundle_js:
# Bundle all client-side scripts at `%{name}_bundle.js`.
rollup_bundle(
name = bundle,
entry_point = ":%s_scripts.ts" % prerender_name,
entry_point = ":%s_scripts" % name,
config_file = "//packages/rules_prerender:rollup-default.config.js",
link_workspace_root = True,
silent = True,
testonly = testonly,
deps = [
":%s_scripts" % prerender_name,
":%s_scripts" % name,
"@npm//@rollup/plugin-node-resolve",
],
)
Expand Down Expand Up @@ -175,3 +186,33 @@ def prerender_pages(
":%s_resources" % prerender_name,
],
)

def _esm_sources_impl(ctx):
providers = []

# Propagate providers of dependencies.
if JSEcmaScriptModuleInfo in ctx.attr.dep:
providers.append(ctx.attr.dep[JSEcmaScriptModuleInfo])
if JSModuleInfo in ctx.attr.dep:
providers.append(ctx.attr.dep[JSModuleInfo])

# Use direct sources as `DefaultInfo`, prefering `JSEcmaScriptModuleInfo`.
if JSEcmaScriptModuleInfo in ctx.attr.dep:
providers.append(DefaultInfo(
files = ctx.attr.dep[JSEcmaScriptModuleInfo].direct_sources,
))
elif JSModuleInfo in ctx.attr.dep:
providers.append(
DefaultInfo(files = ctx.attr.dep[JSModuleInfo].direct_sources),
)
else:
fail("Dependency (%s) does provides neither JSEcmaScriptModuleInfo nor JSModuleInfo. It must provide at least one." % ctx.attr.dep.label)

return providers

_esm_sources = rule(
implementation = _esm_sources_impl,
attrs = {
"dep": attr.label(mandatory = True),
},
)
18 changes: 11 additions & 7 deletions packages/rules_prerender/prerender_pages_unbundled.bzl
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
"""Defines `prerender_pages_unbundled()` functionality."""

load("@build_bazel_rules_nodejs//:index.bzl", "nodejs_binary")
load("@build_bazel_rules_nodejs//:index.bzl", "js_library", "nodejs_binary")
load("@npm//@bazel/typescript:index.bzl", "ts_library")
load("//common:label.bzl", "absolute", "file_path_of")
load(":entry_points.bzl", "script_entry_point", "style_entry_point")
load(":prerender_component.bzl", "prerender_component")
load(":prerender_component.bzl", "prerender_component", "js_reexport")
load(":prerender_resources.bzl", "prerender_resources")
load(":web_resources.bzl", "WebResourceInfo", "web_resources")

Expand Down Expand Up @@ -58,9 +58,9 @@ def prerender_pages_unbundled(
Outputs:
%{name}: A `web_resources()` target containing all the files generated
by the `src` file at their corresponding locations.
%{name}_scripts: A `ts_library()` rule containing all the client-side
%{name}_scripts: A `js_library()` rule containing all the client-side
scripts used by the page. This includes a generated file
`%{name}_scripts.ts` which acts as an entry point, importing all
`%{name}_scripts.js` which acts as an entry point, importing all
scripts that were included in the page via `includeScript()`.
%{name}_styles: A `filegroup()` containing all the CSS styles used by
the page.
Expand Down Expand Up @@ -116,7 +116,11 @@ def prerender_pages_unbundled(

# Execute the runner to generate annotated resources.
annotated = "%s_annotated" % name
js_src = ".ts".join(src.split(".ts")[:-1]) + ".js"
js_src = (
".ts".join(src.split(".ts")[:-1]) + ".js"
if src.endswith(".ts")
else src
)
prerender_resources(
name = annotated,
entry_point = file_path_of(absolute(js_src)),
Expand All @@ -136,7 +140,7 @@ def prerender_pages_unbundled(

# Generate the entry point importing all included scripts.
client_scripts = "%s_scripts" % name
script_entry = "%s.ts" % client_scripts
script_entry = "%s.js" % client_scripts
script_entry_point(
name = "%s_entry" % client_scripts,
metadata = metadata,
Expand All @@ -145,7 +149,7 @@ def prerender_pages_unbundled(
)

# Reexport all included scripts at `%{name}_scripts`.
ts_library(
js_reexport(
name = client_scripts,
srcs = [script_entry],
deps = [":%s" % component_scripts],
Expand Down
3 changes: 2 additions & 1 deletion packages/rules_prerender/prerender_resources.bzl
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
"""Defines `prerender_resources()` functionality."""

load("@build_bazel_rules_nodejs//:index.bzl", "nodejs_binary")
load("//common:paths.bzl", "is_js_file")
load("//packages/renderer:build_vars.bzl", "RENDERER_RUNTIME_DEPS")
load(":web_resources.bzl", "WebResourceInfo")

Expand Down Expand Up @@ -60,7 +61,7 @@ def prerender_resources(
visibility: See https://docs.bazel.build/versions/master/be/common-definitions.html.
"""
# Validate `entry_point`.
if "/" not in entry_point or not entry_point.endswith(".js"):
if "/" not in entry_point or not is_js_file(entry_point):
fail(("`entry_point` (%s) *must* be a workspace-relative path of the"
+ " format: \"path/to/pkg/file.js\"") % entry_point)

Expand Down

0 comments on commit 656bf98

Please sign in to comment.