blob: eb95510300739e4e87e0bb60067f36386a4b1341 [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,
105 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"],
113 visibility = ["//visibility:private"],
114 )
115 esbuild(
116 name = "polyfills-bundle",
117 entry_point = "polyfills.js",
118 srcs = [":_polyfills"],
119 define = {"process.env.NODE_ENV": "'production'"},
120 config = {
121 "resolveExtensions": [".mjs", ".js"],
122 },
123 metafile = False,
124 format = "esm",
125 minify = True,
126 visibility = ["//visibility:private"],
127 )
128
129 _pkg_web(
130 name = "prod",
131 entry_point = "main.js",
132 entry_deps = [":_app"],
133 html_assets = html_assets,
134 assets = assets,
135 production = True,
136 visibility = ["//visibility:private"],
137 )
138
139 _pkg_web(
140 name = "dev",
141 entry_point = "main.js",
142 entry_deps = [":_app"],
143 html_assets = html_assets,
144 assets = assets,
145 production = False,
146 visibility = ["//visibility:private"],
147 )
148
149 # The default target: the prod package
150 native.alias(
151 name = name,
152 actual = "prod",
153 visibility = visibility,
154 )
155
156def _pkg_web(name, entry_point, entry_deps, html_assets, assets, production, visibility):
157 """ Bundle and create runnable web package.
158
159 For a given application entry_point, assets and defined constants... generate
160 a bundle using that entry and constants, an index.html referencing the bundle and
161 providated assets, package all content into a resulting directory of the given name.
162 """
163
164 bundle = "bundle-%s" % name
165
166 ng_esbuild(
167 name = bundle,
168 entry_points = [entry_point],
169 srcs = entry_deps,
170 define = NG_PROD_DEFINE if production else NG_DEV_DEFINE,
171 format = "esm",
172 output_dir = True,
173 splitting = True,
174 metafile = False,
175 minify = production,
176 visibility = ["//visibility:private"],
177 )
178
179 html_out = "_%s_html" % name
180
181 html_insert_assets_bin.html_insert_assets(
182 name = html_out,
183 outs = ["%s/index.html" % html_out],
184 args = [
185 # Template HTML file.
186 "--html",
187 "$(location :index.html)",
188 # Output HTML file.
189 "--out",
190 "%s/%s/index.html" % (native.package_name(), html_out),
191 # Root directory prefixes to strip from asset paths.
192 "--roots",
193 native.package_name(),
194 "%s/%s" % (native.package_name(), html_out),
195 ] +
196 # Generic Assets
197 ["--assets"] + ["$(execpath %s)" % s for s in html_assets] +
198 ["--scripts", "--module", "polyfills-bundle.js"] +
199 # Main bundle to bootstrap the app last
200 ["--scripts", "--module", "%s/main.js" % bundle],
201 # The input HTML template, all assets for potential access for stamping
202 srcs = [":index.html", ":%s" % bundle, ":polyfills-bundle"] + html_assets,
203 visibility = ["//visibility:private"],
204 )
205
206 copy_to_directory(
207 name = name,
208 srcs = [":%s" % bundle, ":polyfills-bundle", ":%s" % html_out] + html_assets + assets,
209 root_paths = [".", "%s/%s" % (native.package_name(), html_out)],
210 visibility = visibility,
211 )
212
213 # http server serving the bundle
214 # TODO(phil): Get this working.
215 #history_server_bin.history_server_binary(
216 # name = "serve" + ("-prod" if production else ""),
217 # args = ["$(location :%s)" % name],
218 # data = [":%s" % name],
219 # visibility = visibility,
220 #)
221
222def ng_pkg(name, generate_public_api = True, extra_srcs = [], deps = [], test_deps = [], visibility = ["//visibility:public"], **kwargs):
223 """
224 Bazel macro for compiling an npm-like Angular package project. Creates '{name}' and 'test' targets.
225
226 Projects structure:
227 src/
228 public-api.ts
229 **/*.{ts,css,html}
230
231 Tests:
232 src/
233 **/*.spec.ts
234
235 Args:
236 name: the rule name
237 deps: package dependencies
238 test_deps: additional dependencies for tests
239 visibility: visibility of the primary targets ('{name}', 'test')
240 """
241
242 test_spec_srcs = native.glob(["**/*.spec.ts"])
243
244 srcs = native.glob(
245 ["**/*.ts", "**/*.css", "**/*.html"],
246 exclude = test_spec_srcs + [
247 "public-api.ts",
248 ],
249 ) + extra_srcs
250
251 # An index file to allow direct imports of the directory similar to a package.json "main"
252 write_file(
253 name = "_index",
254 out = "index.ts",
255 content = ["export * from \"./public-api\";"],
256 visibility = ["//visibility:private"],
257 )
258
259 if generate_public_api:
260 write_file(
261 name = "_public_api",
262 out = "public-api.ts",
263 content = [
264 "export * from './%s.component';" % name,
265 "export * from './%s.module';" % name,
266 ],
267 visibility = ["//visibility:private"],
268 )
269 srcs.append(":_public_api")
270
271 ng_project(
Philipp Schrader89342e52023-03-03 20:47:21 -0800272 name = name,
Philipp Schrader175a93c2023-02-19 13:13:40 -0800273 srcs = srcs + [":_index"],
274 deps = deps + PACKAGE_DEPS,
275 #visibility = ["//visibility:private"],
276 visibility = ["//visibility:public"],
277 **kwargs
278 )
279
Philipp Schrader175a93c2023-02-19 13:13:40 -0800280def rollup_bundle(name, entry_point, deps = [], visibility = None, **kwargs):
Philipp Schrader87277f42022-01-01 07:45:12 -0800281 """Calls the upstream rollup_bundle() and exposes a .min.js file.
282
283 Legacy version of rollup_bundle() used to provide the .min.js file. This
284 wrapper provides the same interface by explicitly exposing a .min.js file.
285 """
Philipp Schrader175a93c2023-02-19 13:13:40 -0800286 copy_file(
287 name = name + "__rollup_config",
288 src = "//:rollup.config.js",
289 out = name + "__rollup_config.js",
290 )
291
Philipp Schrader87277f42022-01-01 07:45:12 -0800292 upstream_rollup_bundle(
293 name = name,
294 visibility = visibility,
295 deps = deps + [
Philipp Schrader175a93c2023-02-19 13:13:40 -0800296 "//:node_modules/@rollup/plugin-node-resolve",
Philipp Schrader87277f42022-01-01 07:45:12 -0800297 ],
Philipp Schrader175a93c2023-02-19 13:13:40 -0800298 sourcemap = "false",
299 config_file = ":%s__rollup_config.js" % name,
300 entry_point = entry_point,
Philipp Schrader87277f42022-01-01 07:45:12 -0800301 **kwargs
302 )
303
304 terser_minified(
305 name = name + "__min",
Philipp Schrader175a93c2023-02-19 13:13:40 -0800306 srcs = [name + ".js"],
307 sourcemap = False,
Philipp Schrader87277f42022-01-01 07:45:12 -0800308 )
309
310 # Copy the __min.js file (a declared output inside the rule) so that it's a
311 # pre-declared output and publicly visible. I.e. via attr.output() below.
Philipp Schrader175a93c2023-02-19 13:13:40 -0800312 _expose_file_with_suffix(
Philipp Schrader87277f42022-01-01 07:45:12 -0800313 name = name + "__min_exposed",
314 src = ":%s__min" % name,
315 out = name + ".min.js",
Philipp Schrader175a93c2023-02-19 13:13:40 -0800316 suffix = "__min.js",
Philipp Schrader87277f42022-01-01 07:45:12 -0800317 visibility = visibility,
318 )
319
Philipp Schrader175a93c2023-02-19 13:13:40 -0800320def _expose_file_with_suffix_impl(ctx):
Philipp Schrader87277f42022-01-01 07:45:12 -0800321 """Copies the .min.js file in order to make it publicly accessible."""
Philipp Schrader175a93c2023-02-19 13:13:40 -0800322 sources = ctx.attr.src[JsInfo].sources.to_list()
Philipp Schrader87277f42022-01-01 07:45:12 -0800323 min_js = None
324 for src in sources:
Philipp Schrader175a93c2023-02-19 13:13:40 -0800325 if src.basename.endswith(ctx.attr.suffix):
Philipp Schrader87277f42022-01-01 07:45:12 -0800326 min_js = src
327 break
328
329 if min_js == None:
330 fail("Couldn't find .min.js in " + str(ctx.attr.src))
331
332 ctx.actions.run(
333 inputs = [min_js],
334 outputs = [ctx.outputs.out],
335 executable = "cp",
336 arguments = [min_js.path, ctx.outputs.out.path],
337 )
338
Philipp Schrader175a93c2023-02-19 13:13:40 -0800339_expose_file_with_suffix = rule(
340 implementation = _expose_file_with_suffix_impl,
Philipp Schrader87277f42022-01-01 07:45:12 -0800341 attrs = {
Philipp Schrader175a93c2023-02-19 13:13:40 -0800342 "src": attr.label(providers = [JsInfo]),
Philipp Schrader87277f42022-01-01 07:45:12 -0800343 "out": attr.output(mandatory = True),
Philipp Schrader175a93c2023-02-19 13:13:40 -0800344 "suffix": attr.string(mandatory = True),
Philipp Schrader87277f42022-01-01 07:45:12 -0800345 },
346)
Philipp Schrader155e76c2023-02-25 18:42:31 -0800347
348def cypress_test(runner, data = None, **kwargs):
349 """Runs a cypress test with the specified runner.
350
351 Args:
352 runner: The runner that starts up any necessary servers and then
353 invokes Cypress itself. See the Module API documentation for more
354 information: https://docs.cypress.io/guides/guides/module-api
355 data: The spec files (*.cy.js) and the servers under test. Also any
356 other files needed at runtime.
357 kwargs: Arguments forwarded to the upstream cypress_module_test().
358 """
359
360 # Figure out how many directories deep this package is relative to the
361 # workspace root.
362 package_depth = len(native.package_name().split("/"))
363
364 # Chrome is located at the runfiles root. So we need to go up one more
365 # directory than the workspace root.
366 chrome_location = "../" * (package_depth + 1) + "chrome_linux/chrome"
367 config_location = "../" * package_depth + "tools/build_rules/js/cypress.config.js"
368
369 data = data or []
370 data.append("//tools/build_rules/js:cypress.config.js")
371 data.append("@xvfb_amd64//:wrapped_bin/Xvfb")
372
373 cypress_module_test(
374 args = [
375 "run",
376 "--config-file=" + config_location,
377 "--browser=" + chrome_location,
378 ],
379 browsers = ["@chrome_linux//:all"],
380 copy_data_to_bin = False,
381 cypress = "//:node_modules/cypress",
382 data = data,
383 runner = runner,
384 **kwargs
385 )