blob: a95d5f239acaeab44ef0fc54214a28040ee98855 [file] [log] [blame]
Austin Schuh8f99c822024-05-05 22:43:40 -07001load("@aspect_bazel_lib//lib:copy_file.bzl", "copy_file")
2load("@aspect_bazel_lib//lib:copy_to_directory.bzl", "copy_to_directory")
3load("@aspect_rules_cypress//cypress:defs.bzl", "cypress_module_test")
4load("@aspect_rules_esbuild//esbuild:defs.bzl", "esbuild")
Philipp Schrader175a93c2023-02-19 13:13:40 -08005load("@aspect_rules_js//js:providers.bzl", "JsInfo")
Philipp Schradere5d13942024-03-17 15:44:35 -07006load("@aspect_rules_js//npm:defs.bzl", "npm_package")
Austin Schuh8f99c822024-05-05 22:43:40 -07007load("@aspect_rules_rollup//rollup:defs.bzl", upstream_rollup_bundle = "rollup")
8load("@aspect_rules_terser//terser:defs.bzl", terser_minified = "terser")
Philipp Schrader175a93c2023-02-19 13:13:40 -08009load("@bazel_skylib//rules:write_file.bzl", "write_file")
Philipp Schrader3de4dfc2023-02-15 20:18:25 -080010
Philipp Schrader175a93c2023-02-19 13:13:40 -080011#load("@npm//:history-server/package_json.bzl", history_server_bin = "bin")
12load("@npm//:html-insert-assets/package_json.bzl", html_insert_assets_bin = "bin")
13load("//tools/build_rules/js:ng.bzl", "ng_esbuild", "ng_project")
14load("//tools/build_rules/js:ts.bzl", _ts_project = "ts_project")
Philipp Schrader3de4dfc2023-02-15 20:18:25 -080015
Philipp Schrader175a93c2023-02-19 13:13:40 -080016ts_project = _ts_project
17
18# Common dependencies of Angular applications
19APPLICATION_DEPS = [
20 "//:node_modules/@angular/common",
21 "//:node_modules/@angular/core",
22 #"//:node_modules/@angular/router",
23 "//:node_modules/@angular/platform-browser",
24 "//:node_modules/@types/node",
25 "//:node_modules/rxjs",
26 #"//:node_modules/tslib",
27]
28
29APPLICATION_HTML_ASSETS = ["styles.css", "favicon.ico"]
30
31# Common dependencies of Angular packages
32PACKAGE_DEPS = [
33 "//:node_modules/@angular/common",
34 "//:node_modules/@angular/core",
35 #"//:node_modules/@angular/router",
36 "//:node_modules/@types/node",
37 "//:node_modules/rxjs",
38 #"//:node_modules/tslib",
39]
40
41TEST_DEPS = APPLICATION_DEPS + [
42 "//:node_modules/@angular/compiler",
43 "//:node_modules/@types/jasmine",
44 "//:node_modules/jasmine-core",
45 "//:node_modules/@angular/platform-browser-dynamic",
46]
47
48NG_DEV_DEFINE = {
49 "process.env.NODE_ENV": "'development'",
50 "ngJitMode": "false",
51}
52NG_PROD_DEFINE = {
53 "process.env.NODE_ENV": "'production'",
54 "ngDevMode": "false",
55 "ngJitMode": "false",
56}
57
58def ng_application(
59 name,
60 deps = [],
Philipp Schrader175a93c2023-02-19 13:13:40 -080061 extra_srcs = [],
62 assets = None,
63 html_assets = APPLICATION_HTML_ASSETS,
Adam Snaider13d48d92023-08-03 12:20:15 -070064 visibility = ["//visibility:public"]):
Philipp Schrader3de4dfc2023-02-15 20:18:25 -080065 """
Philipp Schrader175a93c2023-02-19 13:13:40 -080066 Bazel macro for compiling an Angular application. Creates {name}, test, serve targets.
Philipp Schrader87277f42022-01-01 07:45:12 -080067
Philipp Schrader175a93c2023-02-19 13:13:40 -080068 Projects structure:
69 main.ts
70 index.html
71 polyfills.ts
72 styles.css, favicon.ico (defaults, can be overriden)
73 app/
74 **/*.{ts,css,html}
75
76 Tests:
77 app/
78 **/*.spec.ts
79
80 Args:
81 name: the rule name
82 deps: direct dependencies of the application
Philipp Schrader175a93c2023-02-19 13:13:40 -080083 html_assets: assets to insert into the index.html, [styles.css, favicon.ico] by default
84 assets: assets to include in the file bundle
85 visibility: visibility of the primary targets ({name}, 'test', 'serve')
Philipp Schrader175a93c2023-02-19 13:13:40 -080086 """
Philipp Schraderc70fc622024-08-14 20:50:13 -070087 assets = assets if assets != None else native.glob(["assets/**/*"])
88 html_assets = html_assets or []
Philipp Schrader175a93c2023-02-19 13:13:40 -080089
90 test_spec_srcs = native.glob(["app/**/*.spec.ts"])
91
92 srcs = native.glob(
93 ["main.ts", "app/**/*", "package.json"],
94 exclude = test_spec_srcs,
95 ) + extra_srcs
96
97 # Primary app source
98 ng_project(
99 name = "_app",
100 srcs = srcs,
101 deps = deps + APPLICATION_DEPS,
Austin Schuhf737d472023-07-29 17:35:59 -0700102 tags = [
103 "no-remote-cache",
104 ],
Philipp Schrader175a93c2023-02-19 13:13:40 -0800105 visibility = ["//visibility:private"],
106 )
107
108 # App polyfills source + bundle.
109 ng_project(
110 name = "_polyfills",
111 srcs = ["polyfills.ts"],
112 deps = ["//:node_modules/zone.js"],
Austin Schuhf737d472023-07-29 17:35:59 -0700113 tags = [
114 "no-remote-cache",
115 ],
Philipp Schrader175a93c2023-02-19 13:13:40 -0800116 visibility = ["//visibility:private"],
117 )
118 esbuild(
119 name = "polyfills-bundle",
120 entry_point = "polyfills.js",
121 srcs = [":_polyfills"],
122 define = {"process.env.NODE_ENV": "'production'"},
123 config = {
124 "resolveExtensions": [".mjs", ".js"],
125 },
126 metafile = False,
127 format = "esm",
128 minify = True,
Austin Schuhf737d472023-07-29 17:35:59 -0700129 tags = [
130 "no-remote-cache",
131 ],
Philipp Schrader175a93c2023-02-19 13:13:40 -0800132 visibility = ["//visibility:private"],
133 )
134
135 _pkg_web(
136 name = "prod",
137 entry_point = "main.js",
138 entry_deps = [":_app"],
139 html_assets = html_assets,
140 assets = assets,
141 production = True,
142 visibility = ["//visibility:private"],
143 )
144
145 _pkg_web(
146 name = "dev",
147 entry_point = "main.js",
148 entry_deps = [":_app"],
149 html_assets = html_assets,
150 assets = assets,
151 production = False,
152 visibility = ["//visibility:private"],
153 )
154
155 # The default target: the prod package
156 native.alias(
157 name = name,
158 actual = "prod",
159 visibility = visibility,
160 )
161
162def _pkg_web(name, entry_point, entry_deps, html_assets, assets, production, visibility):
163 """ Bundle and create runnable web package.
164
165 For a given application entry_point, assets and defined constants... generate
166 a bundle using that entry and constants, an index.html referencing the bundle and
167 providated assets, package all content into a resulting directory of the given name.
168 """
169
170 bundle = "bundle-%s" % name
171
172 ng_esbuild(
173 name = bundle,
174 entry_points = [entry_point],
175 srcs = entry_deps,
176 define = NG_PROD_DEFINE if production else NG_DEV_DEFINE,
Austin Schuh869bf092024-04-13 17:45:12 -0700177 deps = [
178 "//:node_modules/@babel/core",
179 "//:node_modules/@angular/compiler-cli",
180 ],
Philipp Schrader175a93c2023-02-19 13:13:40 -0800181 format = "esm",
182 output_dir = True,
183 splitting = True,
184 metafile = False,
185 minify = production,
186 visibility = ["//visibility:private"],
187 )
188
189 html_out = "_%s_html" % name
190
191 html_insert_assets_bin.html_insert_assets(
192 name = html_out,
193 outs = ["%s/index.html" % html_out],
194 args = [
195 # Template HTML file.
196 "--html",
197 "$(location :index.html)",
198 # Output HTML file.
199 "--out",
200 "%s/%s/index.html" % (native.package_name(), html_out),
201 # Root directory prefixes to strip from asset paths.
202 "--roots",
203 native.package_name(),
204 "%s/%s" % (native.package_name(), html_out),
205 ] +
206 # Generic Assets
207 ["--assets"] + ["$(execpath %s)" % s for s in html_assets] +
208 ["--scripts", "--module", "polyfills-bundle.js"] +
209 # Main bundle to bootstrap the app last
210 ["--scripts", "--module", "%s/main.js" % bundle],
211 # The input HTML template, all assets for potential access for stamping
212 srcs = [":index.html", ":%s" % bundle, ":polyfills-bundle"] + html_assets,
Austin Schuhf737d472023-07-29 17:35:59 -0700213 tags = [
214 "no-remote-cache",
215 ],
Philipp Schrader175a93c2023-02-19 13:13:40 -0800216 visibility = ["//visibility:private"],
217 )
218
219 copy_to_directory(
220 name = name,
221 srcs = [":%s" % bundle, ":polyfills-bundle", ":%s" % html_out] + html_assets + assets,
222 root_paths = [".", "%s/%s" % (native.package_name(), html_out)],
223 visibility = visibility,
224 )
225
226 # http server serving the bundle
227 # TODO(phil): Get this working.
228 #history_server_bin.history_server_binary(
229 # name = "serve" + ("-prod" if production else ""),
230 # args = ["$(location :%s)" % name],
231 # data = [":%s" % name],
232 # visibility = visibility,
233 #)
234
Adam Snaider13d48d92023-08-03 12:20:15 -0700235def ng_pkg(name, generate_public_api = True, extra_srcs = [], deps = [], visibility = ["//visibility:public"], **kwargs):
Philipp Schrader175a93c2023-02-19 13:13:40 -0800236 """
237 Bazel macro for compiling an npm-like Angular package project. Creates '{name}' and 'test' targets.
238
239 Projects structure:
240 src/
241 public-api.ts
242 **/*.{ts,css,html}
243
244 Tests:
245 src/
246 **/*.spec.ts
247
248 Args:
249 name: the rule name
250 deps: package dependencies
Philipp Schrader175a93c2023-02-19 13:13:40 -0800251 visibility: visibility of the primary targets ('{name}', 'test')
252 """
253
254 test_spec_srcs = native.glob(["**/*.spec.ts"])
255
256 srcs = native.glob(
257 ["**/*.ts", "**/*.css", "**/*.html"],
258 exclude = test_spec_srcs + [
Philipp Schrader3d7dedc2024-03-16 16:27:25 -0700259 "**/*.jinja2.*",
Philipp Schrader175a93c2023-02-19 13:13:40 -0800260 "public-api.ts",
261 ],
262 ) + extra_srcs
263
264 # An index file to allow direct imports of the directory similar to a package.json "main"
265 write_file(
266 name = "_index",
267 out = "index.ts",
268 content = ["export * from \"./public-api\";"],
269 visibility = ["//visibility:private"],
270 )
271
272 if generate_public_api:
273 write_file(
274 name = "_public_api",
275 out = "public-api.ts",
276 content = [
277 "export * from './%s.component';" % name,
278 "export * from './%s.module';" % name,
279 ],
280 visibility = ["//visibility:private"],
281 )
282 srcs.append(":_public_api")
283
284 ng_project(
Philipp Schradere5d13942024-03-17 15:44:35 -0700285 name = "_lib",
Philipp Schrader175a93c2023-02-19 13:13:40 -0800286 srcs = srcs + [":_index"],
287 deps = deps + PACKAGE_DEPS,
Philipp Schradere5d13942024-03-17 15:44:35 -0700288 visibility = ["//visibility:private"],
Philipp Schrader175a93c2023-02-19 13:13:40 -0800289 **kwargs
290 )
291
Philipp Schradere5d13942024-03-17 15:44:35 -0700292 npm_package(
293 name = name,
294 srcs = ["package.json", ":_lib"],
295 include_runfiles = False,
296 visibility = visibility,
297 )
298
Philipp Schraderba072d92024-02-21 17:00:37 -0800299def rollup_bundle(name, entry_point, node_modules = "//:node_modules", deps = [], visibility = None, **kwargs):
Philipp Schrader87277f42022-01-01 07:45:12 -0800300 """Calls the upstream rollup_bundle() and exposes a .min.js file.
301
302 Legacy version of rollup_bundle() used to provide the .min.js file. This
303 wrapper provides the same interface by explicitly exposing a .min.js file.
304 """
Philipp Schrader175a93c2023-02-19 13:13:40 -0800305 copy_file(
306 name = name + "__rollup_config",
307 src = "//:rollup.config.js",
Austin Schuh869bf092024-04-13 17:45:12 -0700308 out = name + "__rollup_config.mjs",
Philipp Schrader175a93c2023-02-19 13:13:40 -0800309 )
310
Philipp Schrader87277f42022-01-01 07:45:12 -0800311 upstream_rollup_bundle(
312 name = name,
313 visibility = visibility,
314 deps = deps + [
Philipp Schrader175a93c2023-02-19 13:13:40 -0800315 "//:node_modules/@rollup/plugin-node-resolve",
Philipp Schrader87277f42022-01-01 07:45:12 -0800316 ],
Philipp Schraderba072d92024-02-21 17:00:37 -0800317 node_modules = node_modules,
Philipp Schrader175a93c2023-02-19 13:13:40 -0800318 sourcemap = "false",
Austin Schuh869bf092024-04-13 17:45:12 -0700319 config_file = ":%s__rollup_config.mjs" % name,
Philipp Schrader175a93c2023-02-19 13:13:40 -0800320 entry_point = entry_point,
Philipp Schrader87277f42022-01-01 07:45:12 -0800321 **kwargs
322 )
323
324 terser_minified(
325 name = name + "__min",
Philipp Schrader175a93c2023-02-19 13:13:40 -0800326 srcs = [name + ".js"],
Philipp Schraderba072d92024-02-21 17:00:37 -0800327 node_modules = node_modules,
Austin Schuhf737d472023-07-29 17:35:59 -0700328 tags = [
329 "no-remote-cache",
330 ],
Philipp Schrader175a93c2023-02-19 13:13:40 -0800331 sourcemap = False,
Philipp Schrader87277f42022-01-01 07:45:12 -0800332 )
333
334 # Copy the __min.js file (a declared output inside the rule) so that it's a
335 # pre-declared output and publicly visible. I.e. via attr.output() below.
Philipp Schrader175a93c2023-02-19 13:13:40 -0800336 _expose_file_with_suffix(
Philipp Schrader87277f42022-01-01 07:45:12 -0800337 name = name + "__min_exposed",
338 src = ":%s__min" % name,
339 out = name + ".min.js",
Philipp Schrader175a93c2023-02-19 13:13:40 -0800340 suffix = "__min.js",
Philipp Schrader87277f42022-01-01 07:45:12 -0800341 visibility = visibility,
342 )
343
Philipp Schrader175a93c2023-02-19 13:13:40 -0800344def _expose_file_with_suffix_impl(ctx):
Philipp Schrader87277f42022-01-01 07:45:12 -0800345 """Copies the .min.js file in order to make it publicly accessible."""
Philipp Schrader175a93c2023-02-19 13:13:40 -0800346 sources = ctx.attr.src[JsInfo].sources.to_list()
Philipp Schrader87277f42022-01-01 07:45:12 -0800347 min_js = None
348 for src in sources:
Philipp Schrader175a93c2023-02-19 13:13:40 -0800349 if src.basename.endswith(ctx.attr.suffix):
Philipp Schrader87277f42022-01-01 07:45:12 -0800350 min_js = src
351 break
352
353 if min_js == None:
354 fail("Couldn't find .min.js in " + str(ctx.attr.src))
355
356 ctx.actions.run(
357 inputs = [min_js],
358 outputs = [ctx.outputs.out],
359 executable = "cp",
360 arguments = [min_js.path, ctx.outputs.out.path],
361 )
362
Philipp Schrader175a93c2023-02-19 13:13:40 -0800363_expose_file_with_suffix = rule(
364 implementation = _expose_file_with_suffix_impl,
Philipp Schrader87277f42022-01-01 07:45:12 -0800365 attrs = {
Philipp Schrader175a93c2023-02-19 13:13:40 -0800366 "src": attr.label(providers = [JsInfo]),
Philipp Schrader87277f42022-01-01 07:45:12 -0800367 "out": attr.output(mandatory = True),
Philipp Schrader175a93c2023-02-19 13:13:40 -0800368 "suffix": attr.string(mandatory = True),
Philipp Schrader87277f42022-01-01 07:45:12 -0800369 },
370)
Philipp Schrader155e76c2023-02-25 18:42:31 -0800371
Philipp Schraderba072d92024-02-21 17:00:37 -0800372def cypress_test(name, runner, data = None, **kwargs):
Philipp Schrader155e76c2023-02-25 18:42:31 -0800373 """Runs a cypress test with the specified runner.
374
375 Args:
376 runner: The runner that starts up any necessary servers and then
377 invokes Cypress itself. See the Module API documentation for more
378 information: https://docs.cypress.io/guides/guides/module-api
379 data: The spec files (*.cy.js) and the servers under test. Also any
380 other files needed at runtime.
381 kwargs: Arguments forwarded to the upstream cypress_module_test().
382 """
383
384 # Figure out how many directories deep this package is relative to the
385 # workspace root.
386 package_depth = len(native.package_name().split("/"))
387
388 # Chrome is located at the runfiles root. So we need to go up one more
389 # directory than the workspace root.
390 chrome_location = "../" * (package_depth + 1) + "chrome_linux/chrome"
Philipp Schraderba072d92024-02-21 17:00:37 -0800391
392 copy_file(
393 name = name + "_config",
Austin Schuh869bf092024-04-13 17:45:12 -0700394 out = name + "_cypress.config.mjs",
Philipp Schraderba072d92024-02-21 17:00:37 -0800395 src = "//tools/build_rules/js:cypress.config.js",
396 visibility = ["//visibility:private"],
397 )
Philipp Schrader155e76c2023-02-25 18:42:31 -0800398
399 data = data or []
Philipp Schraderba072d92024-02-21 17:00:37 -0800400 data.append(":%s_config" % name)
Philipp Schrader155e76c2023-02-25 18:42:31 -0800401 data.append("@xvfb_amd64//:wrapped_bin/Xvfb")
Philipp Schraderba072d92024-02-21 17:00:37 -0800402 data.append("//:node_modules")
Philipp Schrader155e76c2023-02-25 18:42:31 -0800403
404 cypress_module_test(
Philipp Schraderba072d92024-02-21 17:00:37 -0800405 name = name,
Philipp Schrader155e76c2023-02-25 18:42:31 -0800406 args = [
407 "run",
Austin Schuh869bf092024-04-13 17:45:12 -0700408 "--config-file=%s_cypress.config.mjs" % name,
Philipp Schrader155e76c2023-02-25 18:42:31 -0800409 "--browser=" + chrome_location,
410 ],
411 browsers = ["@chrome_linux//:all"],
412 copy_data_to_bin = False,
413 cypress = "//:node_modules/cypress",
414 data = data,
415 runner = runner,
416 **kwargs
417 )