blob: 51fe9871c332a595f41d15fce7305b670d9190a1 [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")
Philipp Schrader175a93c2023-02-19 13:13:40 -08003load("@aspect_bazel_lib//lib:copy_to_directory.bzl", "copy_to_directory")
4load("@aspect_bazel_lib//lib:copy_file.bzl", "copy_file")
5load("@aspect_rules_esbuild//esbuild:defs.bzl", "esbuild")
Philipp Schrader3de4dfc2023-02-15 20:18:25 -08006
Philipp Schrader175a93c2023-02-19 13:13:40 -08007#load("@npm//:history-server/package_json.bzl", history_server_bin = "bin")
8load("@npm//:html-insert-assets/package_json.bzl", html_insert_assets_bin = "bin")
9load("//tools/build_rules/js:ng.bzl", "ng_esbuild", "ng_project")
10load("//tools/build_rules/js:ts.bzl", _ts_project = "ts_project")
Philipp Schraderba072d92024-02-21 17:00:37 -080011load("@aspect_rules_rollup//rollup:defs.bzl", upstream_rollup_bundle = "rollup")
12load("@aspect_rules_terser//terser:defs.bzl", terser_minified = "terser")
Philipp Schrader155e76c2023-02-25 18:42:31 -080013load("@aspect_rules_cypress//cypress:defs.bzl", "cypress_module_test")
Philipp Schrader3de4dfc2023-02-15 20:18:25 -080014
Philipp Schrader175a93c2023-02-19 13:13:40 -080015ts_project = _ts_project
16
17# Common dependencies of Angular applications
18APPLICATION_DEPS = [
19 "//:node_modules/@angular/common",
20 "//:node_modules/@angular/core",
21 #"//:node_modules/@angular/router",
22 "//:node_modules/@angular/platform-browser",
23 "//:node_modules/@types/node",
24 "//:node_modules/rxjs",
25 #"//:node_modules/tslib",
26]
27
28APPLICATION_HTML_ASSETS = ["styles.css", "favicon.ico"]
29
30# Common dependencies of Angular packages
31PACKAGE_DEPS = [
32 "//:node_modules/@angular/common",
33 "//:node_modules/@angular/core",
34 #"//:node_modules/@angular/router",
35 "//:node_modules/@types/node",
36 "//:node_modules/rxjs",
37 #"//:node_modules/tslib",
38]
39
40TEST_DEPS = APPLICATION_DEPS + [
41 "//:node_modules/@angular/compiler",
42 "//:node_modules/@types/jasmine",
43 "//:node_modules/jasmine-core",
44 "//:node_modules/@angular/platform-browser-dynamic",
45]
46
47NG_DEV_DEFINE = {
48 "process.env.NODE_ENV": "'development'",
49 "ngJitMode": "false",
50}
51NG_PROD_DEFINE = {
52 "process.env.NODE_ENV": "'production'",
53 "ngDevMode": "false",
54 "ngJitMode": "false",
55}
56
57def ng_application(
58 name,
59 deps = [],
Philipp Schrader175a93c2023-02-19 13:13:40 -080060 extra_srcs = [],
61 assets = None,
62 html_assets = APPLICATION_HTML_ASSETS,
Adam Snaider13d48d92023-08-03 12:20:15 -070063 visibility = ["//visibility:public"]):
Philipp Schrader3de4dfc2023-02-15 20:18:25 -080064 """
Philipp Schrader175a93c2023-02-19 13:13:40 -080065 Bazel macro for compiling an Angular application. Creates {name}, test, serve targets.
Philipp Schrader87277f42022-01-01 07:45:12 -080066
Philipp Schrader175a93c2023-02-19 13:13:40 -080067 Projects structure:
68 main.ts
69 index.html
70 polyfills.ts
71 styles.css, favicon.ico (defaults, can be overriden)
72 app/
73 **/*.{ts,css,html}
74
75 Tests:
76 app/
77 **/*.spec.ts
78
79 Args:
80 name: the rule name
81 deps: direct dependencies of the application
Philipp Schrader175a93c2023-02-19 13:13:40 -080082 html_assets: assets to insert into the index.html, [styles.css, favicon.ico] by default
83 assets: assets to include in the file bundle
84 visibility: visibility of the primary targets ({name}, 'test', 'serve')
Philipp Schrader175a93c2023-02-19 13:13:40 -080085 """
86 assets = assets if assets else native.glob(["assets/**/*"])
87 html_assets = html_assets if html_assets else []
88
89 test_spec_srcs = native.glob(["app/**/*.spec.ts"])
90
91 srcs = native.glob(
92 ["main.ts", "app/**/*", "package.json"],
93 exclude = test_spec_srcs,
94 ) + extra_srcs
95
96 # Primary app source
97 ng_project(
98 name = "_app",
99 srcs = srcs,
100 deps = deps + APPLICATION_DEPS,
Austin Schuhf737d472023-07-29 17:35:59 -0700101 tags = [
102 "no-remote-cache",
103 ],
Philipp Schrader175a93c2023-02-19 13:13:40 -0800104 visibility = ["//visibility:private"],
105 )
106
107 # App polyfills source + bundle.
108 ng_project(
109 name = "_polyfills",
110 srcs = ["polyfills.ts"],
111 deps = ["//:node_modules/zone.js"],
Austin Schuhf737d472023-07-29 17:35:59 -0700112 tags = [
113 "no-remote-cache",
114 ],
Philipp Schrader175a93c2023-02-19 13:13:40 -0800115 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,
Austin Schuhf737d472023-07-29 17:35:59 -0700128 tags = [
129 "no-remote-cache",
130 ],
Philipp Schrader175a93c2023-02-19 13:13:40 -0800131 visibility = ["//visibility:private"],
132 )
133
134 _pkg_web(
135 name = "prod",
136 entry_point = "main.js",
137 entry_deps = [":_app"],
138 html_assets = html_assets,
139 assets = assets,
140 production = True,
141 visibility = ["//visibility:private"],
142 )
143
144 _pkg_web(
145 name = "dev",
146 entry_point = "main.js",
147 entry_deps = [":_app"],
148 html_assets = html_assets,
149 assets = assets,
150 production = False,
151 visibility = ["//visibility:private"],
152 )
153
154 # The default target: the prod package
155 native.alias(
156 name = name,
157 actual = "prod",
158 visibility = visibility,
159 )
160
161def _pkg_web(name, entry_point, entry_deps, html_assets, assets, production, visibility):
162 """ Bundle and create runnable web package.
163
164 For a given application entry_point, assets and defined constants... generate
165 a bundle using that entry and constants, an index.html referencing the bundle and
166 providated assets, package all content into a resulting directory of the given name.
167 """
168
169 bundle = "bundle-%s" % name
170
171 ng_esbuild(
172 name = bundle,
173 entry_points = [entry_point],
174 srcs = entry_deps,
175 define = NG_PROD_DEFINE if production else NG_DEV_DEFINE,
176 format = "esm",
177 output_dir = True,
178 splitting = True,
179 metafile = False,
180 minify = production,
181 visibility = ["//visibility:private"],
182 )
183
184 html_out = "_%s_html" % name
185
186 html_insert_assets_bin.html_insert_assets(
187 name = html_out,
188 outs = ["%s/index.html" % html_out],
189 args = [
190 # Template HTML file.
191 "--html",
192 "$(location :index.html)",
193 # Output HTML file.
194 "--out",
195 "%s/%s/index.html" % (native.package_name(), html_out),
196 # Root directory prefixes to strip from asset paths.
197 "--roots",
198 native.package_name(),
199 "%s/%s" % (native.package_name(), html_out),
200 ] +
201 # Generic Assets
202 ["--assets"] + ["$(execpath %s)" % s for s in html_assets] +
203 ["--scripts", "--module", "polyfills-bundle.js"] +
204 # Main bundle to bootstrap the app last
205 ["--scripts", "--module", "%s/main.js" % bundle],
206 # The input HTML template, all assets for potential access for stamping
207 srcs = [":index.html", ":%s" % bundle, ":polyfills-bundle"] + html_assets,
Austin Schuhf737d472023-07-29 17:35:59 -0700208 tags = [
209 "no-remote-cache",
210 ],
Philipp Schrader175a93c2023-02-19 13:13:40 -0800211 visibility = ["//visibility:private"],
212 )
213
214 copy_to_directory(
215 name = name,
216 srcs = [":%s" % bundle, ":polyfills-bundle", ":%s" % html_out] + html_assets + assets,
217 root_paths = [".", "%s/%s" % (native.package_name(), html_out)],
218 visibility = visibility,
219 )
220
221 # http server serving the bundle
222 # TODO(phil): Get this working.
223 #history_server_bin.history_server_binary(
224 # name = "serve" + ("-prod" if production else ""),
225 # args = ["$(location :%s)" % name],
226 # data = [":%s" % name],
227 # visibility = visibility,
228 #)
229
Adam Snaider13d48d92023-08-03 12:20:15 -0700230def ng_pkg(name, generate_public_api = True, extra_srcs = [], deps = [], visibility = ["//visibility:public"], **kwargs):
Philipp Schrader175a93c2023-02-19 13:13:40 -0800231 """
232 Bazel macro for compiling an npm-like Angular package project. Creates '{name}' and 'test' targets.
233
234 Projects structure:
235 src/
236 public-api.ts
237 **/*.{ts,css,html}
238
239 Tests:
240 src/
241 **/*.spec.ts
242
243 Args:
244 name: the rule name
245 deps: package dependencies
Philipp Schrader175a93c2023-02-19 13:13:40 -0800246 visibility: visibility of the primary targets ('{name}', 'test')
247 """
248
249 test_spec_srcs = native.glob(["**/*.spec.ts"])
250
251 srcs = native.glob(
252 ["**/*.ts", "**/*.css", "**/*.html"],
253 exclude = test_spec_srcs + [
254 "public-api.ts",
255 ],
256 ) + extra_srcs
257
258 # An index file to allow direct imports of the directory similar to a package.json "main"
259 write_file(
260 name = "_index",
261 out = "index.ts",
262 content = ["export * from \"./public-api\";"],
263 visibility = ["//visibility:private"],
264 )
265
266 if generate_public_api:
267 write_file(
268 name = "_public_api",
269 out = "public-api.ts",
270 content = [
271 "export * from './%s.component';" % name,
272 "export * from './%s.module';" % name,
273 ],
274 visibility = ["//visibility:private"],
275 )
276 srcs.append(":_public_api")
277
278 ng_project(
Philipp Schrader89342e52023-03-03 20:47:21 -0800279 name = name,
Philipp Schrader175a93c2023-02-19 13:13:40 -0800280 srcs = srcs + [":_index"],
281 deps = deps + PACKAGE_DEPS,
Adam Snaider13d48d92023-08-03 12:20:15 -0700282 visibility = visibility,
Philipp Schrader175a93c2023-02-19 13:13:40 -0800283 **kwargs
284 )
285
Philipp Schraderba072d92024-02-21 17:00:37 -0800286def rollup_bundle(name, entry_point, node_modules = "//:node_modules", deps = [], visibility = None, **kwargs):
Philipp Schrader87277f42022-01-01 07:45:12 -0800287 """Calls the upstream rollup_bundle() and exposes a .min.js file.
288
289 Legacy version of rollup_bundle() used to provide the .min.js file. This
290 wrapper provides the same interface by explicitly exposing a .min.js file.
291 """
Philipp Schrader175a93c2023-02-19 13:13:40 -0800292 copy_file(
293 name = name + "__rollup_config",
294 src = "//:rollup.config.js",
295 out = name + "__rollup_config.js",
296 )
297
Philipp Schrader87277f42022-01-01 07:45:12 -0800298 upstream_rollup_bundle(
299 name = name,
300 visibility = visibility,
301 deps = deps + [
Philipp Schrader175a93c2023-02-19 13:13:40 -0800302 "//:node_modules/@rollup/plugin-node-resolve",
Philipp Schrader87277f42022-01-01 07:45:12 -0800303 ],
Philipp Schraderba072d92024-02-21 17:00:37 -0800304 node_modules = node_modules,
Philipp Schrader175a93c2023-02-19 13:13:40 -0800305 sourcemap = "false",
306 config_file = ":%s__rollup_config.js" % name,
307 entry_point = entry_point,
Philipp Schrader87277f42022-01-01 07:45:12 -0800308 **kwargs
309 )
310
311 terser_minified(
312 name = name + "__min",
Philipp Schrader175a93c2023-02-19 13:13:40 -0800313 srcs = [name + ".js"],
Philipp Schraderba072d92024-02-21 17:00:37 -0800314 node_modules = node_modules,
Austin Schuhf737d472023-07-29 17:35:59 -0700315 tags = [
316 "no-remote-cache",
317 ],
Philipp Schrader175a93c2023-02-19 13:13:40 -0800318 sourcemap = False,
Philipp Schrader87277f42022-01-01 07:45:12 -0800319 )
320
321 # Copy the __min.js file (a declared output inside the rule) so that it's a
322 # pre-declared output and publicly visible. I.e. via attr.output() below.
Philipp Schrader175a93c2023-02-19 13:13:40 -0800323 _expose_file_with_suffix(
Philipp Schrader87277f42022-01-01 07:45:12 -0800324 name = name + "__min_exposed",
325 src = ":%s__min" % name,
326 out = name + ".min.js",
Philipp Schrader175a93c2023-02-19 13:13:40 -0800327 suffix = "__min.js",
Philipp Schrader87277f42022-01-01 07:45:12 -0800328 visibility = visibility,
329 )
330
Philipp Schrader175a93c2023-02-19 13:13:40 -0800331def _expose_file_with_suffix_impl(ctx):
Philipp Schrader87277f42022-01-01 07:45:12 -0800332 """Copies the .min.js file in order to make it publicly accessible."""
Philipp Schrader175a93c2023-02-19 13:13:40 -0800333 sources = ctx.attr.src[JsInfo].sources.to_list()
Philipp Schrader87277f42022-01-01 07:45:12 -0800334 min_js = None
335 for src in sources:
Philipp Schrader175a93c2023-02-19 13:13:40 -0800336 if src.basename.endswith(ctx.attr.suffix):
Philipp Schrader87277f42022-01-01 07:45:12 -0800337 min_js = src
338 break
339
340 if min_js == None:
341 fail("Couldn't find .min.js in " + str(ctx.attr.src))
342
343 ctx.actions.run(
344 inputs = [min_js],
345 outputs = [ctx.outputs.out],
346 executable = "cp",
347 arguments = [min_js.path, ctx.outputs.out.path],
348 )
349
Philipp Schrader175a93c2023-02-19 13:13:40 -0800350_expose_file_with_suffix = rule(
351 implementation = _expose_file_with_suffix_impl,
Philipp Schrader87277f42022-01-01 07:45:12 -0800352 attrs = {
Philipp Schrader175a93c2023-02-19 13:13:40 -0800353 "src": attr.label(providers = [JsInfo]),
Philipp Schrader87277f42022-01-01 07:45:12 -0800354 "out": attr.output(mandatory = True),
Philipp Schrader175a93c2023-02-19 13:13:40 -0800355 "suffix": attr.string(mandatory = True),
Philipp Schrader87277f42022-01-01 07:45:12 -0800356 },
357)
Philipp Schrader155e76c2023-02-25 18:42:31 -0800358
Philipp Schraderba072d92024-02-21 17:00:37 -0800359def cypress_test(name, runner, data = None, **kwargs):
Philipp Schrader155e76c2023-02-25 18:42:31 -0800360 """Runs a cypress test with the specified runner.
361
362 Args:
363 runner: The runner that starts up any necessary servers and then
364 invokes Cypress itself. See the Module API documentation for more
365 information: https://docs.cypress.io/guides/guides/module-api
366 data: The spec files (*.cy.js) and the servers under test. Also any
367 other files needed at runtime.
368 kwargs: Arguments forwarded to the upstream cypress_module_test().
369 """
370
371 # Figure out how many directories deep this package is relative to the
372 # workspace root.
373 package_depth = len(native.package_name().split("/"))
374
375 # Chrome is located at the runfiles root. So we need to go up one more
376 # directory than the workspace root.
377 chrome_location = "../" * (package_depth + 1) + "chrome_linux/chrome"
Philipp Schraderba072d92024-02-21 17:00:37 -0800378
379 copy_file(
380 name = name + "_config",
381 out = "cypress.config.js",
382 src = "//tools/build_rules/js:cypress.config.js",
383 visibility = ["//visibility:private"],
384 )
Philipp Schrader155e76c2023-02-25 18:42:31 -0800385
386 data = data or []
Philipp Schraderba072d92024-02-21 17:00:37 -0800387 data.append(":%s_config" % name)
Philipp Schrader155e76c2023-02-25 18:42:31 -0800388 data.append("@xvfb_amd64//:wrapped_bin/Xvfb")
Philipp Schraderba072d92024-02-21 17:00:37 -0800389 data.append("//:node_modules")
Philipp Schrader155e76c2023-02-25 18:42:31 -0800390
391 cypress_module_test(
Philipp Schraderba072d92024-02-21 17:00:37 -0800392 name = name,
Philipp Schrader155e76c2023-02-25 18:42:31 -0800393 args = [
394 "run",
Philipp Schraderba072d92024-02-21 17:00:37 -0800395 "--config-file=cypress.config.js",
Philipp Schrader155e76c2023-02-25 18:42:31 -0800396 "--browser=" + chrome_location,
397 ],
398 browsers = ["@chrome_linux//:all"],
399 copy_data_to_bin = False,
400 cypress = "//:node_modules/cypress",
401 data = data,
402 runner = runner,
403 **kwargs
404 )