blob: f66d9c4a6d08f2f6059efd9ec90333923beb6ea9 [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 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 = [],
61 test_deps = [],
62 extra_srcs = [],
63 assets = None,
64 html_assets = APPLICATION_HTML_ASSETS,
65 visibility = ["//visibility:public"],
66 **kwargs):
Philipp Schrader3de4dfc2023-02-15 20:18:25 -080067 """
Philipp Schrader175a93c2023-02-19 13:13:40 -080068 Bazel macro for compiling an Angular application. Creates {name}, test, serve targets.
Philipp Schrader87277f42022-01-01 07:45:12 -080069
Philipp Schrader175a93c2023-02-19 13:13:40 -080070 Projects structure:
71 main.ts
72 index.html
73 polyfills.ts
74 styles.css, favicon.ico (defaults, can be overriden)
75 app/
76 **/*.{ts,css,html}
77
78 Tests:
79 app/
80 **/*.spec.ts
81
82 Args:
83 name: the rule name
84 deps: direct dependencies of the application
85 test_deps: additional dependencies for tests
86 html_assets: assets to insert into the index.html, [styles.css, favicon.ico] by default
87 assets: assets to include in the file bundle
88 visibility: visibility of the primary targets ({name}, 'test', 'serve')
89 **kwargs: extra args passed to main Angular CLI rules
90 """
91 assets = assets if assets else native.glob(["assets/**/*"])
92 html_assets = html_assets if html_assets else []
93
94 test_spec_srcs = native.glob(["app/**/*.spec.ts"])
95
96 srcs = native.glob(
97 ["main.ts", "app/**/*", "package.json"],
98 exclude = test_spec_srcs,
99 ) + extra_srcs
100
101 # Primary app source
102 ng_project(
103 name = "_app",
104 srcs = srcs,
105 deps = deps + APPLICATION_DEPS,
106 visibility = ["//visibility:private"],
107 )
108
109 # App polyfills source + bundle.
110 ng_project(
111 name = "_polyfills",
112 srcs = ["polyfills.ts"],
113 deps = ["//:node_modules/zone.js"],
114 visibility = ["//visibility:private"],
115 )
116 esbuild(
117 name = "polyfills-bundle",
118 entry_point = "polyfills.js",
119 srcs = [":_polyfills"],
120 define = {"process.env.NODE_ENV": "'production'"},
121 config = {
122 "resolveExtensions": [".mjs", ".js"],
123 },
124 metafile = False,
125 format = "esm",
126 minify = True,
127 visibility = ["//visibility:private"],
128 )
129
130 _pkg_web(
131 name = "prod",
132 entry_point = "main.js",
133 entry_deps = [":_app"],
134 html_assets = html_assets,
135 assets = assets,
136 production = True,
137 visibility = ["//visibility:private"],
138 )
139
140 _pkg_web(
141 name = "dev",
142 entry_point = "main.js",
143 entry_deps = [":_app"],
144 html_assets = html_assets,
145 assets = assets,
146 production = False,
147 visibility = ["//visibility:private"],
148 )
149
150 # The default target: the prod package
151 native.alias(
152 name = name,
153 actual = "prod",
154 visibility = visibility,
155 )
156
157def _pkg_web(name, entry_point, entry_deps, html_assets, assets, production, visibility):
158 """ Bundle and create runnable web package.
159
160 For a given application entry_point, assets and defined constants... generate
161 a bundle using that entry and constants, an index.html referencing the bundle and
162 providated assets, package all content into a resulting directory of the given name.
163 """
164
165 bundle = "bundle-%s" % name
166
167 ng_esbuild(
168 name = bundle,
169 entry_points = [entry_point],
170 srcs = entry_deps,
171 define = NG_PROD_DEFINE if production else NG_DEV_DEFINE,
172 format = "esm",
173 output_dir = True,
174 splitting = True,
175 metafile = False,
176 minify = production,
177 visibility = ["//visibility:private"],
178 )
179
180 html_out = "_%s_html" % name
181
182 html_insert_assets_bin.html_insert_assets(
183 name = html_out,
184 outs = ["%s/index.html" % html_out],
185 args = [
186 # Template HTML file.
187 "--html",
188 "$(location :index.html)",
189 # Output HTML file.
190 "--out",
191 "%s/%s/index.html" % (native.package_name(), html_out),
192 # Root directory prefixes to strip from asset paths.
193 "--roots",
194 native.package_name(),
195 "%s/%s" % (native.package_name(), html_out),
196 ] +
197 # Generic Assets
198 ["--assets"] + ["$(execpath %s)" % s for s in html_assets] +
199 ["--scripts", "--module", "polyfills-bundle.js"] +
200 # Main bundle to bootstrap the app last
201 ["--scripts", "--module", "%s/main.js" % bundle],
202 # The input HTML template, all assets for potential access for stamping
203 srcs = [":index.html", ":%s" % bundle, ":polyfills-bundle"] + html_assets,
204 visibility = ["//visibility:private"],
205 )
206
207 copy_to_directory(
208 name = name,
209 srcs = [":%s" % bundle, ":polyfills-bundle", ":%s" % html_out] + html_assets + assets,
210 root_paths = [".", "%s/%s" % (native.package_name(), html_out)],
211 visibility = visibility,
212 )
213
214 # http server serving the bundle
215 # TODO(phil): Get this working.
216 #history_server_bin.history_server_binary(
217 # name = "serve" + ("-prod" if production else ""),
218 # args = ["$(location :%s)" % name],
219 # data = [":%s" % name],
220 # visibility = visibility,
221 #)
222
223def ng_pkg(name, generate_public_api = True, extra_srcs = [], deps = [], test_deps = [], visibility = ["//visibility:public"], **kwargs):
224 """
225 Bazel macro for compiling an npm-like Angular package project. Creates '{name}' and 'test' targets.
226
227 Projects structure:
228 src/
229 public-api.ts
230 **/*.{ts,css,html}
231
232 Tests:
233 src/
234 **/*.spec.ts
235
236 Args:
237 name: the rule name
238 deps: package dependencies
239 test_deps: additional dependencies for tests
240 visibility: visibility of the primary targets ('{name}', 'test')
241 """
242
243 test_spec_srcs = native.glob(["**/*.spec.ts"])
244
245 srcs = native.glob(
246 ["**/*.ts", "**/*.css", "**/*.html"],
247 exclude = test_spec_srcs + [
248 "public-api.ts",
249 ],
250 ) + extra_srcs
251
252 # An index file to allow direct imports of the directory similar to a package.json "main"
253 write_file(
254 name = "_index",
255 out = "index.ts",
256 content = ["export * from \"./public-api\";"],
257 visibility = ["//visibility:private"],
258 )
259
260 if generate_public_api:
261 write_file(
262 name = "_public_api",
263 out = "public-api.ts",
264 content = [
265 "export * from './%s.component';" % name,
266 "export * from './%s.module';" % name,
267 ],
268 visibility = ["//visibility:private"],
269 )
270 srcs.append(":_public_api")
271
272 ng_project(
273 name = "_lib",
274 srcs = srcs + [":_index"],
275 deps = deps + PACKAGE_DEPS,
276 #visibility = ["//visibility:private"],
277 visibility = ["//visibility:public"],
278 **kwargs
279 )
280
281 js_library(
282 name = name + "_js",
283 srcs = [":_lib"],
284 visibility = ["//visibility:public"],
285 )
286
287 npm_package(
288 name = name,
289 srcs = ["package.json", ":_lib"],
290 # This is a perf improvement; the default will be flipped to False in rules_js 2.0
291 include_runfiles = False,
292 visibility = ["//visibility:public"],
293 )
294
295def rollup_bundle(name, entry_point, deps = [], visibility = None, **kwargs):
Philipp Schrader87277f42022-01-01 07:45:12 -0800296 """Calls the upstream rollup_bundle() and exposes a .min.js file.
297
298 Legacy version of rollup_bundle() used to provide the .min.js file. This
299 wrapper provides the same interface by explicitly exposing a .min.js file.
300 """
Philipp Schrader175a93c2023-02-19 13:13:40 -0800301 copy_file(
302 name = name + "__rollup_config",
303 src = "//:rollup.config.js",
304 out = name + "__rollup_config.js",
305 )
306
Philipp Schrader87277f42022-01-01 07:45:12 -0800307 upstream_rollup_bundle(
308 name = name,
309 visibility = visibility,
310 deps = deps + [
Philipp Schrader175a93c2023-02-19 13:13:40 -0800311 "//:node_modules/@rollup/plugin-node-resolve",
Philipp Schrader87277f42022-01-01 07:45:12 -0800312 ],
Philipp Schrader175a93c2023-02-19 13:13:40 -0800313 sourcemap = "false",
314 config_file = ":%s__rollup_config.js" % name,
315 entry_point = entry_point,
Philipp Schrader87277f42022-01-01 07:45:12 -0800316 **kwargs
317 )
318
319 terser_minified(
320 name = name + "__min",
Philipp Schrader175a93c2023-02-19 13:13:40 -0800321 srcs = [name + ".js"],
322 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)