blob: ceb67aa2b21732375052d56bc06f353a81901221 [file] [log] [blame]
Philipp Schrader175a93c2023-02-19 13:13:40 -08001load("@aspect_rules_js//js:providers.bzl", "JsInfo")
2load("@bazel_skylib//rules:write_file.bzl", "write_file")
3load("@aspect_rules_js//js:defs.bzl", "js_library")
4load("@aspect_rules_js//npm:defs.bzl", "npm_package")
5load("@aspect_bazel_lib//lib:copy_to_directory.bzl", "copy_to_directory")
6load("@aspect_bazel_lib//lib:copy_file.bzl", "copy_file")
7load("@aspect_rules_esbuild//esbuild:defs.bzl", "esbuild")
Philipp Schrader3de4dfc2023-02-15 20:18:25 -08008
Philipp Schrader175a93c2023-02-19 13:13:40 -08009#load("@npm//:history-server/package_json.bzl", history_server_bin = "bin")
10load("@npm//:html-insert-assets/package_json.bzl", html_insert_assets_bin = "bin")
11load("//tools/build_rules/js:ng.bzl", "ng_esbuild", "ng_project")
12load("//tools/build_rules/js:ts.bzl", _ts_project = "ts_project")
13load("@aspect_rules_rollup//rollup:defs.bzl", upstream_rollup_bundle = "rollup_bundle")
14load("@aspect_rules_terser//terser:defs.bzl", "terser_minified")
Philipp Schrader155e76c2023-02-25 18:42:31 -080015load("@aspect_rules_cypress//cypress:defs.bzl", "cypress_module_test")
Philipp Schrader3de4dfc2023-02-15 20:18:25 -080016
Philipp Schrader175a93c2023-02-19 13:13:40 -080017ts_project = _ts_project
18
19# Common dependencies of Angular applications
20APPLICATION_DEPS = [
21 "//:node_modules/@angular/common",
22 "//:node_modules/@angular/core",
23 #"//:node_modules/@angular/router",
24 "//:node_modules/@angular/platform-browser",
25 "//:node_modules/@types/node",
26 "//:node_modules/rxjs",
27 #"//:node_modules/tslib",
28]
29
30APPLICATION_HTML_ASSETS = ["styles.css", "favicon.ico"]
31
32# Common dependencies of Angular packages
33PACKAGE_DEPS = [
34 "//:node_modules/@angular/common",
35 "//:node_modules/@angular/core",
36 #"//:node_modules/@angular/router",
37 "//:node_modules/@types/node",
38 "//:node_modules/rxjs",
39 #"//:node_modules/tslib",
40]
41
42TEST_DEPS = APPLICATION_DEPS + [
43 "//:node_modules/@angular/compiler",
44 "//:node_modules/@types/jasmine",
45 "//:node_modules/jasmine-core",
46 "//:node_modules/@angular/platform-browser-dynamic",
47]
48
49NG_DEV_DEFINE = {
50 "process.env.NODE_ENV": "'development'",
51 "ngJitMode": "false",
52}
53NG_PROD_DEFINE = {
54 "process.env.NODE_ENV": "'production'",
55 "ngDevMode": "false",
56 "ngJitMode": "false",
57}
58
59def ng_application(
60 name,
61 deps = [],
62 test_deps = [],
63 extra_srcs = [],
64 assets = None,
65 html_assets = APPLICATION_HTML_ASSETS,
66 visibility = ["//visibility:public"],
67 **kwargs):
Philipp Schrader3de4dfc2023-02-15 20:18:25 -080068 """
Philipp Schrader175a93c2023-02-19 13:13:40 -080069 Bazel macro for compiling an Angular application. Creates {name}, test, serve targets.
Philipp Schrader87277f42022-01-01 07:45:12 -080070
Philipp Schrader175a93c2023-02-19 13:13:40 -080071 Projects structure:
72 main.ts
73 index.html
74 polyfills.ts
75 styles.css, favicon.ico (defaults, can be overriden)
76 app/
77 **/*.{ts,css,html}
78
79 Tests:
80 app/
81 **/*.spec.ts
82
83 Args:
84 name: the rule name
85 deps: direct dependencies of the application
86 test_deps: additional dependencies for tests
87 html_assets: assets to insert into the index.html, [styles.css, favicon.ico] by default
88 assets: assets to include in the file bundle
89 visibility: visibility of the primary targets ({name}, 'test', 'serve')
90 **kwargs: extra args passed to main Angular CLI rules
91 """
92 assets = assets if assets else native.glob(["assets/**/*"])
93 html_assets = html_assets if html_assets else []
94
95 test_spec_srcs = native.glob(["app/**/*.spec.ts"])
96
97 srcs = native.glob(
98 ["main.ts", "app/**/*", "package.json"],
99 exclude = test_spec_srcs,
100 ) + extra_srcs
101
102 # Primary app source
103 ng_project(
104 name = "_app",
105 srcs = srcs,
106 deps = deps + APPLICATION_DEPS,
107 visibility = ["//visibility:private"],
108 )
109
110 # App polyfills source + bundle.
111 ng_project(
112 name = "_polyfills",
113 srcs = ["polyfills.ts"],
114 deps = ["//:node_modules/zone.js"],
115 visibility = ["//visibility:private"],
116 )
117 esbuild(
118 name = "polyfills-bundle",
119 entry_point = "polyfills.js",
120 srcs = [":_polyfills"],
121 define = {"process.env.NODE_ENV": "'production'"},
122 config = {
123 "resolveExtensions": [".mjs", ".js"],
124 },
125 metafile = False,
126 format = "esm",
127 minify = True,
128 visibility = ["//visibility:private"],
129 )
130
131 _pkg_web(
132 name = "prod",
133 entry_point = "main.js",
134 entry_deps = [":_app"],
135 html_assets = html_assets,
136 assets = assets,
137 production = True,
138 visibility = ["//visibility:private"],
139 )
140
141 _pkg_web(
142 name = "dev",
143 entry_point = "main.js",
144 entry_deps = [":_app"],
145 html_assets = html_assets,
146 assets = assets,
147 production = False,
148 visibility = ["//visibility:private"],
149 )
150
151 # The default target: the prod package
152 native.alias(
153 name = name,
154 actual = "prod",
155 visibility = visibility,
156 )
157
158def _pkg_web(name, entry_point, entry_deps, html_assets, assets, production, visibility):
159 """ Bundle and create runnable web package.
160
161 For a given application entry_point, assets and defined constants... generate
162 a bundle using that entry and constants, an index.html referencing the bundle and
163 providated assets, package all content into a resulting directory of the given name.
164 """
165
166 bundle = "bundle-%s" % name
167
168 ng_esbuild(
169 name = bundle,
170 entry_points = [entry_point],
171 srcs = entry_deps,
172 define = NG_PROD_DEFINE if production else NG_DEV_DEFINE,
173 format = "esm",
174 output_dir = True,
175 splitting = True,
176 metafile = False,
177 minify = production,
178 visibility = ["//visibility:private"],
179 )
180
181 html_out = "_%s_html" % name
182
183 html_insert_assets_bin.html_insert_assets(
184 name = html_out,
185 outs = ["%s/index.html" % html_out],
186 args = [
187 # Template HTML file.
188 "--html",
189 "$(location :index.html)",
190 # Output HTML file.
191 "--out",
192 "%s/%s/index.html" % (native.package_name(), html_out),
193 # Root directory prefixes to strip from asset paths.
194 "--roots",
195 native.package_name(),
196 "%s/%s" % (native.package_name(), html_out),
197 ] +
198 # Generic Assets
199 ["--assets"] + ["$(execpath %s)" % s for s in html_assets] +
200 ["--scripts", "--module", "polyfills-bundle.js"] +
201 # Main bundle to bootstrap the app last
202 ["--scripts", "--module", "%s/main.js" % bundle],
203 # The input HTML template, all assets for potential access for stamping
204 srcs = [":index.html", ":%s" % bundle, ":polyfills-bundle"] + html_assets,
205 visibility = ["//visibility:private"],
206 )
207
208 copy_to_directory(
209 name = name,
210 srcs = [":%s" % bundle, ":polyfills-bundle", ":%s" % html_out] + html_assets + assets,
211 root_paths = [".", "%s/%s" % (native.package_name(), html_out)],
212 visibility = visibility,
213 )
214
215 # http server serving the bundle
216 # TODO(phil): Get this working.
217 #history_server_bin.history_server_binary(
218 # name = "serve" + ("-prod" if production else ""),
219 # args = ["$(location :%s)" % name],
220 # data = [":%s" % name],
221 # visibility = visibility,
222 #)
223
224def ng_pkg(name, generate_public_api = True, extra_srcs = [], deps = [], test_deps = [], visibility = ["//visibility:public"], **kwargs):
225 """
226 Bazel macro for compiling an npm-like Angular package project. Creates '{name}' and 'test' targets.
227
228 Projects structure:
229 src/
230 public-api.ts
231 **/*.{ts,css,html}
232
233 Tests:
234 src/
235 **/*.spec.ts
236
237 Args:
238 name: the rule name
239 deps: package dependencies
240 test_deps: additional dependencies for tests
241 visibility: visibility of the primary targets ('{name}', 'test')
242 """
243
244 test_spec_srcs = native.glob(["**/*.spec.ts"])
245
246 srcs = native.glob(
247 ["**/*.ts", "**/*.css", "**/*.html"],
248 exclude = test_spec_srcs + [
249 "public-api.ts",
250 ],
251 ) + extra_srcs
252
253 # An index file to allow direct imports of the directory similar to a package.json "main"
254 write_file(
255 name = "_index",
256 out = "index.ts",
257 content = ["export * from \"./public-api\";"],
258 visibility = ["//visibility:private"],
259 )
260
261 if generate_public_api:
262 write_file(
263 name = "_public_api",
264 out = "public-api.ts",
265 content = [
266 "export * from './%s.component';" % name,
267 "export * from './%s.module';" % name,
268 ],
269 visibility = ["//visibility:private"],
270 )
271 srcs.append(":_public_api")
272
273 ng_project(
274 name = "_lib",
275 srcs = srcs + [":_index"],
276 deps = deps + PACKAGE_DEPS,
277 #visibility = ["//visibility:private"],
278 visibility = ["//visibility:public"],
279 **kwargs
280 )
281
282 js_library(
283 name = name + "_js",
284 srcs = [":_lib"],
285 visibility = ["//visibility:public"],
286 )
287
288 npm_package(
289 name = name,
290 srcs = ["package.json", ":_lib"],
291 # This is a perf improvement; the default will be flipped to False in rules_js 2.0
292 include_runfiles = False,
293 visibility = ["//visibility:public"],
294 )
295
296def rollup_bundle(name, entry_point, deps = [], visibility = None, **kwargs):
Philipp Schrader87277f42022-01-01 07:45:12 -0800297 """Calls the upstream rollup_bundle() and exposes a .min.js file.
298
299 Legacy version of rollup_bundle() used to provide the .min.js file. This
300 wrapper provides the same interface by explicitly exposing a .min.js file.
301 """
Philipp Schrader175a93c2023-02-19 13:13:40 -0800302 copy_file(
303 name = name + "__rollup_config",
304 src = "//:rollup.config.js",
305 out = name + "__rollup_config.js",
306 )
307
Philipp Schrader87277f42022-01-01 07:45:12 -0800308 upstream_rollup_bundle(
309 name = name,
310 visibility = visibility,
311 deps = deps + [
Philipp Schrader175a93c2023-02-19 13:13:40 -0800312 "//:node_modules/@rollup/plugin-node-resolve",
Philipp Schrader87277f42022-01-01 07:45:12 -0800313 ],
Philipp Schrader175a93c2023-02-19 13:13:40 -0800314 sourcemap = "false",
315 config_file = ":%s__rollup_config.js" % name,
316 entry_point = entry_point,
Philipp Schrader87277f42022-01-01 07:45:12 -0800317 **kwargs
318 )
319
320 terser_minified(
321 name = name + "__min",
Philipp Schrader175a93c2023-02-19 13:13:40 -0800322 srcs = [name + ".js"],
323 sourcemap = False,
Philipp Schrader87277f42022-01-01 07:45:12 -0800324 )
325
326 # Copy the __min.js file (a declared output inside the rule) so that it's a
327 # pre-declared output and publicly visible. I.e. via attr.output() below.
Philipp Schrader175a93c2023-02-19 13:13:40 -0800328 _expose_file_with_suffix(
Philipp Schrader87277f42022-01-01 07:45:12 -0800329 name = name + "__min_exposed",
330 src = ":%s__min" % name,
331 out = name + ".min.js",
Philipp Schrader175a93c2023-02-19 13:13:40 -0800332 suffix = "__min.js",
Philipp Schrader87277f42022-01-01 07:45:12 -0800333 visibility = visibility,
334 )
335
Philipp Schrader175a93c2023-02-19 13:13:40 -0800336def _expose_file_with_suffix_impl(ctx):
Philipp Schrader87277f42022-01-01 07:45:12 -0800337 """Copies the .min.js file in order to make it publicly accessible."""
Philipp Schrader175a93c2023-02-19 13:13:40 -0800338 sources = ctx.attr.src[JsInfo].sources.to_list()
Philipp Schrader87277f42022-01-01 07:45:12 -0800339 min_js = None
340 for src in sources:
Philipp Schrader175a93c2023-02-19 13:13:40 -0800341 if src.basename.endswith(ctx.attr.suffix):
Philipp Schrader87277f42022-01-01 07:45:12 -0800342 min_js = src
343 break
344
345 if min_js == None:
346 fail("Couldn't find .min.js in " + str(ctx.attr.src))
347
348 ctx.actions.run(
349 inputs = [min_js],
350 outputs = [ctx.outputs.out],
351 executable = "cp",
352 arguments = [min_js.path, ctx.outputs.out.path],
353 )
354
Philipp Schrader175a93c2023-02-19 13:13:40 -0800355_expose_file_with_suffix = rule(
356 implementation = _expose_file_with_suffix_impl,
Philipp Schrader87277f42022-01-01 07:45:12 -0800357 attrs = {
Philipp Schrader175a93c2023-02-19 13:13:40 -0800358 "src": attr.label(providers = [JsInfo]),
Philipp Schrader87277f42022-01-01 07:45:12 -0800359 "out": attr.output(mandatory = True),
Philipp Schrader175a93c2023-02-19 13:13:40 -0800360 "suffix": attr.string(mandatory = True),
Philipp Schrader87277f42022-01-01 07:45:12 -0800361 },
362)
Philipp Schrader155e76c2023-02-25 18:42:31 -0800363
364def cypress_test(runner, data = None, **kwargs):
365 """Runs a cypress test with the specified runner.
366
367 Args:
368 runner: The runner that starts up any necessary servers and then
369 invokes Cypress itself. See the Module API documentation for more
370 information: https://docs.cypress.io/guides/guides/module-api
371 data: The spec files (*.cy.js) and the servers under test. Also any
372 other files needed at runtime.
373 kwargs: Arguments forwarded to the upstream cypress_module_test().
374 """
375
376 # Figure out how many directories deep this package is relative to the
377 # workspace root.
378 package_depth = len(native.package_name().split("/"))
379
380 # Chrome is located at the runfiles root. So we need to go up one more
381 # directory than the workspace root.
382 chrome_location = "../" * (package_depth + 1) + "chrome_linux/chrome"
383 config_location = "../" * package_depth + "tools/build_rules/js/cypress.config.js"
384
385 data = data or []
386 data.append("//tools/build_rules/js:cypress.config.js")
387 data.append("@xvfb_amd64//:wrapped_bin/Xvfb")
388
389 cypress_module_test(
390 args = [
391 "run",
392 "--config-file=" + config_location,
393 "--browser=" + chrome_location,
394 ],
395 browsers = ["@chrome_linux//:all"],
396 copy_data_to_bin = False,
397 cypress = "//:node_modules/cypress",
398 data = data,
399 runner = runner,
400 **kwargs
401 )