Philipp Schrader | 175a93c | 2023-02-19 13:13:40 -0800 | [diff] [blame] | 1 | load("@aspect_rules_js//js:providers.bzl", "JsInfo") |
| 2 | load("@bazel_skylib//rules:write_file.bzl", "write_file") |
| 3 | load("@aspect_rules_js//js:defs.bzl", "js_library") |
| 4 | load("@aspect_rules_js//npm:defs.bzl", "npm_package") |
| 5 | load("@aspect_bazel_lib//lib:copy_to_directory.bzl", "copy_to_directory") |
| 6 | load("@aspect_bazel_lib//lib:copy_file.bzl", "copy_file") |
| 7 | load("@aspect_rules_esbuild//esbuild:defs.bzl", "esbuild") |
Philipp Schrader | 3de4dfc | 2023-02-15 20:18:25 -0800 | [diff] [blame] | 8 | |
Philipp Schrader | 175a93c | 2023-02-19 13:13:40 -0800 | [diff] [blame] | 9 | #load("@npm//:history-server/package_json.bzl", history_server_bin = "bin") |
| 10 | load("@npm//:html-insert-assets/package_json.bzl", html_insert_assets_bin = "bin") |
| 11 | load("//tools/build_rules/js:ng.bzl", "ng_esbuild", "ng_project") |
| 12 | load("//tools/build_rules/js:ts.bzl", _ts_project = "ts_project") |
| 13 | load("@aspect_rules_rollup//rollup:defs.bzl", upstream_rollup_bundle = "rollup_bundle") |
| 14 | load("@aspect_rules_terser//terser:defs.bzl", "terser_minified") |
Philipp Schrader | 3de4dfc | 2023-02-15 20:18:25 -0800 | [diff] [blame] | 15 | |
Philipp Schrader | 175a93c | 2023-02-19 13:13:40 -0800 | [diff] [blame] | 16 | ts_project = _ts_project |
| 17 | |
| 18 | # Common dependencies of Angular applications |
| 19 | APPLICATION_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 | |
| 29 | APPLICATION_HTML_ASSETS = ["styles.css", "favicon.ico"] |
| 30 | |
| 31 | # Common dependencies of Angular packages |
| 32 | PACKAGE_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 | |
| 41 | TEST_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 | |
| 48 | NG_DEV_DEFINE = { |
| 49 | "process.env.NODE_ENV": "'development'", |
| 50 | "ngJitMode": "false", |
| 51 | } |
| 52 | NG_PROD_DEFINE = { |
| 53 | "process.env.NODE_ENV": "'production'", |
| 54 | "ngDevMode": "false", |
| 55 | "ngJitMode": "false", |
| 56 | } |
| 57 | |
| 58 | def 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 Schrader | 3de4dfc | 2023-02-15 20:18:25 -0800 | [diff] [blame] | 67 | """ |
Philipp Schrader | 175a93c | 2023-02-19 13:13:40 -0800 | [diff] [blame] | 68 | Bazel macro for compiling an Angular application. Creates {name}, test, serve targets. |
Philipp Schrader | 87277f4 | 2022-01-01 07:45:12 -0800 | [diff] [blame] | 69 | |
Philipp Schrader | 175a93c | 2023-02-19 13:13:40 -0800 | [diff] [blame] | 70 | 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 | |
| 157 | def _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 | |
| 223 | def 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 | |
| 295 | def rollup_bundle(name, entry_point, deps = [], visibility = None, **kwargs): |
Philipp Schrader | 87277f4 | 2022-01-01 07:45:12 -0800 | [diff] [blame] | 296 | """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 Schrader | 175a93c | 2023-02-19 13:13:40 -0800 | [diff] [blame] | 301 | copy_file( |
| 302 | name = name + "__rollup_config", |
| 303 | src = "//:rollup.config.js", |
| 304 | out = name + "__rollup_config.js", |
| 305 | ) |
| 306 | |
Philipp Schrader | 87277f4 | 2022-01-01 07:45:12 -0800 | [diff] [blame] | 307 | upstream_rollup_bundle( |
| 308 | name = name, |
| 309 | visibility = visibility, |
| 310 | deps = deps + [ |
Philipp Schrader | 175a93c | 2023-02-19 13:13:40 -0800 | [diff] [blame] | 311 | "//:node_modules/@rollup/plugin-node-resolve", |
Philipp Schrader | 87277f4 | 2022-01-01 07:45:12 -0800 | [diff] [blame] | 312 | ], |
Philipp Schrader | 175a93c | 2023-02-19 13:13:40 -0800 | [diff] [blame] | 313 | sourcemap = "false", |
| 314 | config_file = ":%s__rollup_config.js" % name, |
| 315 | entry_point = entry_point, |
Philipp Schrader | 87277f4 | 2022-01-01 07:45:12 -0800 | [diff] [blame] | 316 | **kwargs |
| 317 | ) |
| 318 | |
| 319 | terser_minified( |
| 320 | name = name + "__min", |
Philipp Schrader | 175a93c | 2023-02-19 13:13:40 -0800 | [diff] [blame] | 321 | srcs = [name + ".js"], |
| 322 | sourcemap = False, |
Philipp Schrader | 87277f4 | 2022-01-01 07:45:12 -0800 | [diff] [blame] | 323 | ) |
| 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 Schrader | 175a93c | 2023-02-19 13:13:40 -0800 | [diff] [blame] | 327 | _expose_file_with_suffix( |
Philipp Schrader | 87277f4 | 2022-01-01 07:45:12 -0800 | [diff] [blame] | 328 | name = name + "__min_exposed", |
| 329 | src = ":%s__min" % name, |
| 330 | out = name + ".min.js", |
Philipp Schrader | 175a93c | 2023-02-19 13:13:40 -0800 | [diff] [blame] | 331 | suffix = "__min.js", |
Philipp Schrader | 87277f4 | 2022-01-01 07:45:12 -0800 | [diff] [blame] | 332 | visibility = visibility, |
| 333 | ) |
| 334 | |
Philipp Schrader | 175a93c | 2023-02-19 13:13:40 -0800 | [diff] [blame] | 335 | def _expose_file_with_suffix_impl(ctx): |
Philipp Schrader | 87277f4 | 2022-01-01 07:45:12 -0800 | [diff] [blame] | 336 | """Copies the .min.js file in order to make it publicly accessible.""" |
Philipp Schrader | 175a93c | 2023-02-19 13:13:40 -0800 | [diff] [blame] | 337 | sources = ctx.attr.src[JsInfo].sources.to_list() |
Philipp Schrader | 87277f4 | 2022-01-01 07:45:12 -0800 | [diff] [blame] | 338 | min_js = None |
| 339 | for src in sources: |
Philipp Schrader | 175a93c | 2023-02-19 13:13:40 -0800 | [diff] [blame] | 340 | if src.basename.endswith(ctx.attr.suffix): |
Philipp Schrader | 87277f4 | 2022-01-01 07:45:12 -0800 | [diff] [blame] | 341 | 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 Schrader | 175a93c | 2023-02-19 13:13:40 -0800 | [diff] [blame] | 354 | _expose_file_with_suffix = rule( |
| 355 | implementation = _expose_file_with_suffix_impl, |
Philipp Schrader | 87277f4 | 2022-01-01 07:45:12 -0800 | [diff] [blame] | 356 | attrs = { |
Philipp Schrader | 175a93c | 2023-02-19 13:13:40 -0800 | [diff] [blame] | 357 | "src": attr.label(providers = [JsInfo]), |
Philipp Schrader | 87277f4 | 2022-01-01 07:45:12 -0800 | [diff] [blame] | 358 | "out": attr.output(mandatory = True), |
Philipp Schrader | 175a93c | 2023-02-19 13:13:40 -0800 | [diff] [blame] | 359 | "suffix": attr.string(mandatory = True), |
Philipp Schrader | 87277f4 | 2022-01-01 07:45:12 -0800 | [diff] [blame] | 360 | }, |
| 361 | ) |