blob: 55f440bf27fe5451a2905c78ef6789e57d9e15f0 [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")
11load("@aspect_rules_rollup//rollup:defs.bzl", upstream_rollup_bundle = "rollup_bundle")
12load("@aspect_rules_terser//terser:defs.bzl", "terser_minified")
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 = [],
60 test_deps = [],
61 extra_srcs = [],
62 assets = None,
63 html_assets = APPLICATION_HTML_ASSETS,
64 visibility = ["//visibility:public"],
65 **kwargs):
Philipp Schrader3de4dfc2023-02-15 20:18:25 -080066 """
Philipp Schrader175a93c2023-02-19 13:13:40 -080067 Bazel macro for compiling an Angular application. Creates {name}, test, serve targets.
Philipp Schrader87277f42022-01-01 07:45:12 -080068
Philipp Schrader175a93c2023-02-19 13:13:40 -080069 Projects structure:
70 main.ts
71 index.html
72 polyfills.ts
73 styles.css, favicon.ico (defaults, can be overriden)
74 app/
75 **/*.{ts,css,html}
76
77 Tests:
78 app/
79 **/*.spec.ts
80
81 Args:
82 name: the rule name
83 deps: direct dependencies of the application
84 test_deps: additional dependencies for tests
85 html_assets: assets to insert into the index.html, [styles.css, favicon.ico] by default
86 assets: assets to include in the file bundle
87 visibility: visibility of the primary targets ({name}, 'test', 'serve')
88 **kwargs: extra args passed to main Angular CLI rules
89 """
90 assets = assets if assets else native.glob(["assets/**/*"])
91 html_assets = html_assets if html_assets else []
92
93 test_spec_srcs = native.glob(["app/**/*.spec.ts"])
94
95 srcs = native.glob(
96 ["main.ts", "app/**/*", "package.json"],
97 exclude = test_spec_srcs,
98 ) + extra_srcs
99
100 # Primary app source
101 ng_project(
102 name = "_app",
103 srcs = srcs,
104 deps = deps + APPLICATION_DEPS,
Austin Schuhf737d472023-07-29 17:35:59 -0700105 tags = [
106 "no-remote-cache",
107 ],
Philipp Schrader175a93c2023-02-19 13:13:40 -0800108 visibility = ["//visibility:private"],
109 )
110
111 # App polyfills source + bundle.
112 ng_project(
113 name = "_polyfills",
114 srcs = ["polyfills.ts"],
115 deps = ["//:node_modules/zone.js"],
Austin Schuhf737d472023-07-29 17:35:59 -0700116 tags = [
117 "no-remote-cache",
118 ],
Philipp Schrader175a93c2023-02-19 13:13:40 -0800119 visibility = ["//visibility:private"],
120 )
121 esbuild(
122 name = "polyfills-bundle",
123 entry_point = "polyfills.js",
124 srcs = [":_polyfills"],
125 define = {"process.env.NODE_ENV": "'production'"},
126 config = {
127 "resolveExtensions": [".mjs", ".js"],
128 },
129 metafile = False,
130 format = "esm",
131 minify = True,
Austin Schuhf737d472023-07-29 17:35:59 -0700132 tags = [
133 "no-remote-cache",
134 ],
Philipp Schrader175a93c2023-02-19 13:13:40 -0800135 visibility = ["//visibility:private"],
136 )
137
138 _pkg_web(
139 name = "prod",
140 entry_point = "main.js",
141 entry_deps = [":_app"],
142 html_assets = html_assets,
143 assets = assets,
144 production = True,
145 visibility = ["//visibility:private"],
146 )
147
148 _pkg_web(
149 name = "dev",
150 entry_point = "main.js",
151 entry_deps = [":_app"],
152 html_assets = html_assets,
153 assets = assets,
154 production = False,
155 visibility = ["//visibility:private"],
156 )
157
158 # The default target: the prod package
159 native.alias(
160 name = name,
161 actual = "prod",
162 visibility = visibility,
163 )
164
165def _pkg_web(name, entry_point, entry_deps, html_assets, assets, production, visibility):
166 """ Bundle and create runnable web package.
167
168 For a given application entry_point, assets and defined constants... generate
169 a bundle using that entry and constants, an index.html referencing the bundle and
170 providated assets, package all content into a resulting directory of the given name.
171 """
172
173 bundle = "bundle-%s" % name
174
175 ng_esbuild(
176 name = bundle,
177 entry_points = [entry_point],
178 srcs = entry_deps,
179 define = NG_PROD_DEFINE if production else NG_DEV_DEFINE,
180 format = "esm",
181 output_dir = True,
182 splitting = True,
183 metafile = False,
184 minify = production,
185 visibility = ["//visibility:private"],
186 )
187
188 html_out = "_%s_html" % name
189
190 html_insert_assets_bin.html_insert_assets(
191 name = html_out,
192 outs = ["%s/index.html" % html_out],
193 args = [
194 # Template HTML file.
195 "--html",
196 "$(location :index.html)",
197 # Output HTML file.
198 "--out",
199 "%s/%s/index.html" % (native.package_name(), html_out),
200 # Root directory prefixes to strip from asset paths.
201 "--roots",
202 native.package_name(),
203 "%s/%s" % (native.package_name(), html_out),
204 ] +
205 # Generic Assets
206 ["--assets"] + ["$(execpath %s)" % s for s in html_assets] +
207 ["--scripts", "--module", "polyfills-bundle.js"] +
208 # Main bundle to bootstrap the app last
209 ["--scripts", "--module", "%s/main.js" % bundle],
210 # The input HTML template, all assets for potential access for stamping
211 srcs = [":index.html", ":%s" % bundle, ":polyfills-bundle"] + html_assets,
Austin Schuhf737d472023-07-29 17:35:59 -0700212 tags = [
213 "no-remote-cache",
214 ],
Philipp Schrader175a93c2023-02-19 13:13:40 -0800215 visibility = ["//visibility:private"],
216 )
217
218 copy_to_directory(
219 name = name,
220 srcs = [":%s" % bundle, ":polyfills-bundle", ":%s" % html_out] + html_assets + assets,
221 root_paths = [".", "%s/%s" % (native.package_name(), html_out)],
222 visibility = visibility,
223 )
224
225 # http server serving the bundle
226 # TODO(phil): Get this working.
227 #history_server_bin.history_server_binary(
228 # name = "serve" + ("-prod" if production else ""),
229 # args = ["$(location :%s)" % name],
230 # data = [":%s" % name],
231 # visibility = visibility,
232 #)
233
234def ng_pkg(name, generate_public_api = True, extra_srcs = [], deps = [], test_deps = [], visibility = ["//visibility:public"], **kwargs):
235 """
236 Bazel macro for compiling an npm-like Angular package project. Creates '{name}' and 'test' targets.
237
238 Projects structure:
239 src/
240 public-api.ts
241 **/*.{ts,css,html}
242
243 Tests:
244 src/
245 **/*.spec.ts
246
247 Args:
248 name: the rule name
249 deps: package dependencies
250 test_deps: additional dependencies for tests
251 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 + [
259 "public-api.ts",
260 ],
261 ) + extra_srcs
262
263 # An index file to allow direct imports of the directory similar to a package.json "main"
264 write_file(
265 name = "_index",
266 out = "index.ts",
267 content = ["export * from \"./public-api\";"],
268 visibility = ["//visibility:private"],
269 )
270
271 if generate_public_api:
272 write_file(
273 name = "_public_api",
274 out = "public-api.ts",
275 content = [
276 "export * from './%s.component';" % name,
277 "export * from './%s.module';" % name,
278 ],
279 visibility = ["//visibility:private"],
280 )
281 srcs.append(":_public_api")
282
283 ng_project(
Philipp Schrader89342e52023-03-03 20:47:21 -0800284 name = name,
Philipp Schrader175a93c2023-02-19 13:13:40 -0800285 srcs = srcs + [":_index"],
286 deps = deps + PACKAGE_DEPS,
287 #visibility = ["//visibility:private"],
288 visibility = ["//visibility:public"],
289 **kwargs
290 )
291
Philipp Schrader175a93c2023-02-19 13:13:40 -0800292def rollup_bundle(name, entry_point, deps = [], visibility = None, **kwargs):
Philipp Schrader87277f42022-01-01 07:45:12 -0800293 """Calls the upstream rollup_bundle() and exposes a .min.js file.
294
295 Legacy version of rollup_bundle() used to provide the .min.js file. This
296 wrapper provides the same interface by explicitly exposing a .min.js file.
297 """
Philipp Schrader175a93c2023-02-19 13:13:40 -0800298 copy_file(
299 name = name + "__rollup_config",
300 src = "//:rollup.config.js",
301 out = name + "__rollup_config.js",
302 )
303
Philipp Schrader87277f42022-01-01 07:45:12 -0800304 upstream_rollup_bundle(
305 name = name,
306 visibility = visibility,
307 deps = deps + [
Philipp Schrader175a93c2023-02-19 13:13:40 -0800308 "//:node_modules/@rollup/plugin-node-resolve",
Philipp Schrader87277f42022-01-01 07:45:12 -0800309 ],
Philipp Schrader175a93c2023-02-19 13:13:40 -0800310 sourcemap = "false",
311 config_file = ":%s__rollup_config.js" % name,
312 entry_point = entry_point,
Philipp Schrader87277f42022-01-01 07:45:12 -0800313 **kwargs
314 )
315
316 terser_minified(
317 name = name + "__min",
Philipp Schrader175a93c2023-02-19 13:13:40 -0800318 srcs = [name + ".js"],
Austin Schuhf737d472023-07-29 17:35:59 -0700319 tags = [
320 "no-remote-cache",
321 ],
Philipp Schrader175a93c2023-02-19 13:13:40 -0800322 sourcemap = False,
Philipp Schrader87277f42022-01-01 07:45:12 -0800323 )
324
325 # Copy the __min.js file (a declared output inside the rule) so that it's a
326 # pre-declared output and publicly visible. I.e. via attr.output() below.
Philipp Schrader175a93c2023-02-19 13:13:40 -0800327 _expose_file_with_suffix(
Philipp Schrader87277f42022-01-01 07:45:12 -0800328 name = name + "__min_exposed",
329 src = ":%s__min" % name,
330 out = name + ".min.js",
Philipp Schrader175a93c2023-02-19 13:13:40 -0800331 suffix = "__min.js",
Philipp Schrader87277f42022-01-01 07:45:12 -0800332 visibility = visibility,
333 )
334
Philipp Schrader175a93c2023-02-19 13:13:40 -0800335def _expose_file_with_suffix_impl(ctx):
Philipp Schrader87277f42022-01-01 07:45:12 -0800336 """Copies the .min.js file in order to make it publicly accessible."""
Philipp Schrader175a93c2023-02-19 13:13:40 -0800337 sources = ctx.attr.src[JsInfo].sources.to_list()
Philipp Schrader87277f42022-01-01 07:45:12 -0800338 min_js = None
339 for src in sources:
Philipp Schrader175a93c2023-02-19 13:13:40 -0800340 if src.basename.endswith(ctx.attr.suffix):
Philipp Schrader87277f42022-01-01 07:45:12 -0800341 min_js = src
342 break
343
344 if min_js == None:
345 fail("Couldn't find .min.js in " + str(ctx.attr.src))
346
347 ctx.actions.run(
348 inputs = [min_js],
349 outputs = [ctx.outputs.out],
350 executable = "cp",
351 arguments = [min_js.path, ctx.outputs.out.path],
352 )
353
Philipp Schrader175a93c2023-02-19 13:13:40 -0800354_expose_file_with_suffix = rule(
355 implementation = _expose_file_with_suffix_impl,
Philipp Schrader87277f42022-01-01 07:45:12 -0800356 attrs = {
Philipp Schrader175a93c2023-02-19 13:13:40 -0800357 "src": attr.label(providers = [JsInfo]),
Philipp Schrader87277f42022-01-01 07:45:12 -0800358 "out": attr.output(mandatory = True),
Philipp Schrader175a93c2023-02-19 13:13:40 -0800359 "suffix": attr.string(mandatory = True),
Philipp Schrader87277f42022-01-01 07:45:12 -0800360 },
361)
Philipp Schrader155e76c2023-02-25 18:42:31 -0800362
363def cypress_test(runner, data = None, **kwargs):
364 """Runs a cypress test with the specified runner.
365
366 Args:
367 runner: The runner that starts up any necessary servers and then
368 invokes Cypress itself. See the Module API documentation for more
369 information: https://docs.cypress.io/guides/guides/module-api
370 data: The spec files (*.cy.js) and the servers under test. Also any
371 other files needed at runtime.
372 kwargs: Arguments forwarded to the upstream cypress_module_test().
373 """
374
375 # Figure out how many directories deep this package is relative to the
376 # workspace root.
377 package_depth = len(native.package_name().split("/"))
378
379 # Chrome is located at the runfiles root. So we need to go up one more
380 # directory than the workspace root.
381 chrome_location = "../" * (package_depth + 1) + "chrome_linux/chrome"
382 config_location = "../" * package_depth + "tools/build_rules/js/cypress.config.js"
383
384 data = data or []
385 data.append("//tools/build_rules/js:cypress.config.js")
386 data.append("@xvfb_amd64//:wrapped_bin/Xvfb")
387
388 cypress_module_test(
389 args = [
390 "run",
391 "--config-file=" + config_location,
392 "--browser=" + chrome_location,
393 ],
394 browsers = ["@chrome_linux//:all"],
395 copy_data_to_bin = False,
396 cypress = "//:node_modules/cypress",
397 data = data,
398 runner = runner,
399 **kwargs
400 )