diff --git a/integration/bazel/package.json b/integration/bazel/package.json index b0b6bdba5a6f8..61bd473e72fae 100644 --- a/integration/bazel/package.json +++ b/integration/bazel/package.json @@ -30,11 +30,11 @@ "@bazel/typescript": "0.39.1", "@types/jasmine": "2.8.8", "http-server": "^0.11.1", - "rollup": "^1.21.4", + "rollup": "~1.25.0", "rollup-plugin-commonjs": "^10.1.0", "rollup-plugin-node-resolve": "^5.2.0", "rollup-plugin-sourcemaps": "^0.4.2", - "terser": "4.3.1", + "terser": "^4.3.9", "typescript": "3.6.4" }, "scripts": { diff --git a/integration/bazel/yarn.lock b/integration/bazel/yarn.lock index e56a9ac55604e..fa2f87dda6ba5 100644 --- a/integration/bazel/yarn.lock +++ b/integration/bazel/yarn.lock @@ -63,29 +63,29 @@ "@angular/router@file:../../dist/packages-dist/router": version "9.0.0-next.14" -"@bazel/bazel-darwin_x64@0.28.1": - version "0.28.1" - resolved "https://registry.yarnpkg.com/@bazel/bazel-darwin_x64/-/bazel-darwin_x64-0.28.1.tgz#415658785e1dbd6f7ab5c8f2b98c1c99c614e1d5" - integrity sha512-VDKWmplAfa4uCAbkIQ5nRn04MFQqtsPNuc2HkluJ8OIum773yC2dPR+OlLBKxrlBuKJYB27TtbOwOa6w/uK7aw== +"@bazel/bazel-darwin_x64@1.0.0": + version "1.0.0" + resolved "https://registry.yarnpkg.com/@bazel/bazel-darwin_x64/-/bazel-darwin_x64-1.0.0.tgz#8ab7ecba867130d87e3ecd6cfd5757e59ea274ab" + integrity sha512-2J8qPpUAhSsuZ1P0kMFLvAQUz8zB8mkKmGL3/8raXUnw9TblsykwAdeg3QlJwTLORn+ZqdAjOYEQIarnTpS1NA== -"@bazel/bazel-linux_x64@0.28.1": - version "0.28.1" - resolved "https://registry.yarnpkg.com/@bazel/bazel-linux_x64/-/bazel-linux_x64-0.28.1.tgz#f78006089e17660261088272a0e04fc886572e34" - integrity sha512-n4XfNxagYhejQD32V4XSxT/qzuH1l/2jxslbKSak66/uQ+wad8Ew9rjNb4JUin3xtrfFtzmxx2jpQkybZsRVGA== +"@bazel/bazel-linux_x64@1.0.0": + version "1.0.0" + resolved "https://registry.yarnpkg.com/@bazel/bazel-linux_x64/-/bazel-linux_x64-1.0.0.tgz#7043cc41eaf7b1d2618766e0759d513873bb9659" + integrity sha512-/ZpOrYyDNGqUyAGPHFr4Y1kn8xCG1G4Lg2VMZtfCZzDohzoYFYs8iyQGU2/8PwldH8XX+oJT9atWqSt1EyoeAw== -"@bazel/bazel-win32_x64@0.28.1": - version "0.28.1" - resolved "https://registry.yarnpkg.com/@bazel/bazel-win32_x64/-/bazel-win32_x64-0.28.1.tgz#60a2819618cf7582cc35ac16c01763a5e807b414" - integrity sha512-T4xksGfKikaHS4zxnGT6r5R436mz9j2lz//L1vc5sJnaEF/1e2Gv6MLl86vfZW2Xxo6iIEi6ntSzgYxP2Blohw== +"@bazel/bazel-win32_x64@1.0.0": + version "1.0.0" + resolved "https://registry.yarnpkg.com/@bazel/bazel-win32_x64/-/bazel-win32_x64-1.0.0.tgz#1111e4910c155a1917162ae96fd3737d062d20dd" + integrity sha512-p5LpQ/WiijwOS+eBkdD7UewHL8JwK+8gpb4tIKqgh/a2yawgzEQPJDPBUV9ykss5t+s85BL2kEMhduuDewt/MA== "@bazel/bazel@file:../../node_modules/@bazel/bazel": - version "0.28.1" + version "1.0.0" dependencies: "@bazel/hide-bazel-files" latest optionalDependencies: - "@bazel/bazel-darwin_x64" "0.28.1" - "@bazel/bazel-linux_x64" "0.28.1" - "@bazel/bazel-win32_x64" "0.28.1" + "@bazel/bazel-darwin_x64" "1.0.0" + "@bazel/bazel-linux_x64" "1.0.0" + "@bazel/bazel-win32_x64" "1.0.0" "@bazel/hide-bazel-files@latest": version "0.38.3" @@ -2867,10 +2867,10 @@ rollup-pluginutils@^2.0.1, rollup-pluginutils@^2.8.1: dependencies: estree-walker "^0.6.1" -rollup@^1.21.4: - version "1.26.0" - resolved "https://registry.yarnpkg.com/rollup/-/rollup-1.26.0.tgz#cf40fd5e1edc4d7f3d4235a0a43f1c2be1cf294b" - integrity sha512-5HljNYn9icFvXX+Oe97qY5TWvnWhKqgGT0HGeWWqFPx7w7+Anzg7dfHMtUif7YYy6QxAgynDSwK6uxbgcrVUxw== +rollup@~1.25.0: + version "1.25.2" + resolved "https://registry.yarnpkg.com/rollup/-/rollup-1.25.2.tgz#739f508bd8f7ece52bb6c1fcda83466af82b7f6d" + integrity sha512-+7z6Wab/L45QCPcfpuTZKwKiB0tynj05s/+s2U3F2Bi7rOLPr9UcjUwO7/xpjlPNXA/hwnth6jBExFRGyf3tMg== dependencies: "@types/estree" "*" "@types/node" "*" @@ -3296,10 +3296,10 @@ tar@^4: safe-buffer "^5.1.2" yallist "^3.0.3" -terser@4.3.1: - version "4.3.1" - resolved "https://registry.yarnpkg.com/terser/-/terser-4.3.1.tgz#09820bcb3398299c4b48d9a86aefc65127d0ed65" - integrity sha512-pnzH6dnFEsR2aa2SJaKb1uSCl3QmIsJ8dEkj0Fky+2AwMMcC9doMqLOQIH6wVTEKaVfKVvLSk5qxPBEZT9mywg== +terser@^4.3.9: + version "4.3.9" + resolved "https://registry.yarnpkg.com/terser/-/terser-4.3.9.tgz#e4be37f80553d02645668727777687dad26bbca8" + integrity sha512-NFGMpHjlzmyOtPL+fDw3G7+6Ueh/sz4mkaUYa4lJCxOPTNzd0Uj0aZJOmsDYoSQyfuVoWDMSWTPU3huyOm2zdA== dependencies: commander "^2.20.0" source-map "~0.6.1" diff --git a/package.json b/package.json index a288280b46e2c..f17bba4b769dc 100644 --- a/package.json +++ b/package.json @@ -44,6 +44,7 @@ "@bazel/jasmine": "0.39.1", "@bazel/karma": "0.39.1", "@bazel/protractor": "0.39.1", + "@bazel/terser": "0.39.1", "@bazel/typescript": "0.39.1", "@microsoft/api-extractor": "^7.3.9", "@schematics/angular": "^8.0.0-beta.15", @@ -102,11 +103,9 @@ "nodejs-websocket": "^1.7.2", "protractor": "^5.4.2", "reflect-metadata": "^0.1.3", - "rollup": "^1.1.0", - "rollup-plugin-amd": "^3.0.0", - "rollup-plugin-commonjs": "^9.2.1", - "rollup-plugin-json": "^4.0.0", - "rollup-plugin-node-resolve": "^4.0.0", + "rollup": "~1.25.0", + "rollup-plugin-commonjs": "^10.1.0", + "rollup-plugin-node-resolve": "^5.2.0", "rollup-plugin-sourcemaps": "^0.4.2", "rxjs": "^6.5.3", "selenium-webdriver": "3.5.0", @@ -114,6 +113,7 @@ "source-map": "^0.6.1", "source-map-support": "0.5.9", "systemjs": "0.18.10", + "terser": "4.3.3", "tsickle": "0.37.0", "tslib": "^1.10.0", "tslint": "5.7.0", diff --git a/packages/bazel/docs/BUILD.bazel b/packages/bazel/docs/BUILD.bazel index 2e32d61ec17f1..55b84494d89db 100644 --- a/packages/bazel/docs/BUILD.bazel +++ b/packages/bazel/docs/BUILD.bazel @@ -4,7 +4,6 @@ skylark_doc( name = "docs", srcs = [ "//packages/bazel/src:ng_module.bzl", - "//packages/bazel/src:ng_rollup_bundle.bzl", "//packages/bazel/src/ng_package:ng_package.bzl", ], format = "html", diff --git a/packages/bazel/package.json b/packages/bazel/package.json index 6cc2f7ef5572b..1c33305055d7f 100644 --- a/packages/bazel/package.json +++ b/packages/bazel/package.json @@ -33,10 +33,11 @@ "peerDependencies": { "@angular/compiler-cli": "0.0.0-PLACEHOLDER", "@bazel/typescript": "0.*", - "typescript": ">=3.6 <3.7" - }, - "devDependencies": { - + "typescript": ">=3.6 <3.7", + "rollup": ">=1.20.0", + "rollup-plugin-commonjs": ">=9.0.0", + "rollup-plugin-node-resolve": ">=4.2.0", + "rollup-plugin-sourcemaps": ">=0.4.0" }, "repository": { "type": "git", @@ -47,4 +48,4 @@ "ng-update": { "packageGroup": "NG_UPDATE_PACKAGE_GROUP" } -} +} \ No newline at end of file diff --git a/packages/bazel/src/BUILD.bazel b/packages/bazel/src/BUILD.bazel index f661f8a17cd0a..7a7b73b51a5eb 100644 --- a/packages/bazel/src/BUILD.bazel +++ b/packages/bazel/src/BUILD.bazel @@ -10,23 +10,6 @@ exports_files(glob(["*.bzl"])) load("@build_bazel_rules_nodejs//:index.bzl", "nodejs_binary") -nodejs_binary( - name = "rollup_with_build_optimizer", - data = [ - "@npm//@angular-devkit/build-optimizer", - "@npm//is-builtin-module", - "@npm//rollup", - "@npm//rollup-plugin-amd", - "@npm//rollup-plugin-commonjs", - "@npm//rollup-plugin-json", - "@npm//rollup-plugin-node-resolve", - "@npm//rollup-plugin-sourcemaps", - ], - entry_point = "@npm//:node_modules/rollup/bin/rollup", - install_source_map_support = False, - visibility = ["//visibility:public"], -) - filegroup( name = "empty_node_modules", srcs = [], diff --git a/packages/bazel/src/ng_package/BUILD.bazel b/packages/bazel/src/ng_package/BUILD.bazel index c2e931be78af9..6de304b664c06 100644 --- a/packages/bazel/src/ng_package/BUILD.bazel +++ b/packages/bazel/src/ng_package/BUILD.bazel @@ -27,9 +27,29 @@ nodejs_binary( install_source_map_support = False, ) -exports_files(["ng_package.bzl"]) +nodejs_binary( + name = "rollup_for_ng_package", + data = [ + "@npm//rollup", + "@npm//rollup-plugin-commonjs", + "@npm//rollup-plugin-node-resolve", + "@npm//rollup-plugin-sourcemaps", + ], + entry_point = "@npm//:node_modules/rollup/dist/bin/rollup", + install_source_map_support = False, +) + +exports_files([ + "ng_package.bzl", + "rollup.config.js", + "terser_config.default.json", +]) filegroup( name = "package_assets", - srcs = glob(["*.bzl"]) + ["BUILD.bazel"], + srcs = glob(["*.bzl"]) + [ + "BUILD.bazel", + "rollup.config.js", + "terser_config.default.json", + ], ) diff --git a/packages/bazel/src/ng_package/ng_package.bzl b/packages/bazel/src/ng_package/ng_package.bzl index cfd7790970d1c..db0ba87a50b72 100644 --- a/packages/bazel/src/ng_package/ng_package.bzl +++ b/packages/bazel/src/ng_package/ng_package.bzl @@ -13,15 +13,7 @@ It packages your library following the Angular Package Format, see the specification of this format at https://goo.gl/jB3GVv """ -load("@build_bazel_rules_nodejs//internal/common:collect_es6_sources.bzl", "collect_es6_sources") -load("@build_bazel_rules_nodejs//:providers.bzl", "JSNamedModuleInfo", "NpmPackageInfo") -load( - "@build_bazel_rules_nodejs//internal/rollup:rollup_bundle.bzl", - "ROLLUP_ATTRS", - "ROLLUP_DEPS_ASPECTS", - "run_terser", - "write_rollup_config", -) +load("@build_bazel_rules_nodejs//:providers.bzl", "JSEcmaScriptModuleInfo", "JSNamedModuleInfo", "NpmPackageInfo", "node_modules_aspect") load( "@build_bazel_rules_nodejs//internal/npm_package:npm_package.bzl", "NPM_PACKAGE_ATTRS", @@ -38,6 +30,47 @@ def _debug(vars, *args): print("[ng_package.bzl]", args) _DEFAULT_NG_PACKAGER = "@npm//@angular/bazel/bin:packager" +_DEFAULT_ROLLUP_CONFIG_TMPL = "@npm_angular_bazel//src/ng_package:rollup.config.js" +_DEFALUT_TERSER_CONFIG_FILE = "@npm_angular_bazel//src/ng_package:terser_config.default.json" +_DEFAULT_ROLLUP = "@npm_angular_bazel//src/ng_package:rollup_for_ng_package" +_DEFAULT_TERSER = "@npm//terser/bin:terser" + +_NG_PACKAGE_MODULE_MAPPINGS_ATTR = "ng_package_module_mappings" + +def _ng_package_module_mappings_aspect_impl(target, ctx): + mappings = dict() + for dep in ctx.rule.attr.deps: + if hasattr(dep, _NG_PACKAGE_MODULE_MAPPINGS_ATTR): + for k, v in getattr(dep, _NG_PACKAGE_MODULE_MAPPINGS_ATTR).items(): + if k in mappings and mappings[k] != v: + fail(("duplicate module mapping at %s: %s maps to both %s and %s" % + (target.label, k, mappings[k], v)), "deps") + mappings[k] = v + if ((hasattr(ctx.rule.attr, "module_name") and ctx.rule.attr.module_name) or + (hasattr(ctx.rule.attr, "module_root") and ctx.rule.attr.module_root)): + mn = ctx.rule.attr.module_name + if not mn: + mn = target.label.name + mr = target.label.package + if target.label.workspace_root: + mr = "%s/%s" % (target.label.workspace_root, mr) + if ctx.rule.attr.module_root and ctx.rule.attr.module_root != ".": + if ctx.rule.attr.module_root.endswith(".ts"): + # This is the type-checking module mapping. Strip the trailing .d.ts + # as it doesn't belong in TypeScript's path mapping. + mr = "%s/%s" % (mr, ctx.rule.attr.module_root.replace(".d.ts", "")) + else: + mr = "%s/%s" % (mr, ctx.rule.attr.module_root) + if mn in mappings and mappings[mn] != mr: + fail(("duplicate module mapping at %s: %s maps to both %s and %s" % + (target.label, mn, mappings[mn], mr)), "deps") + mappings[mn] = mr + return struct(ng_package_module_mappings = mappings) + +ng_package_module_mappings_aspect = aspect( + _ng_package_module_mappings_aspect_impl, + attr_aspects = ["deps"], +) # Convert from some-dash-case to someCamelCase def _convert_dash_case_to_camel_case(s): @@ -102,18 +135,122 @@ WELL_KNOWN_GLOBALS = {p: _global_name(p) for p in [ # TODO(gregmagolan): clean this up _DEPSET_TYPE = "depset" -def _rollup(ctx, bundle_name, rollup_config, entry_point, inputs, js_output, format = "es", module_name = "", include_tslib = False): +def _terser(ctx, input, output): + """Runs terser on an input file. + + Args: + ctx: Bazel rule execution context + input: input file + output: output file + + Returns: + The sourcemap file + """ + + map_output = ctx.actions.declare_file(output.basename + ".map", sibling = output) + + args = ctx.actions.args() + + args.add(input.path) + args.add_all(["--output", output.path]) + + # Source mapping options are comma-packed into one argv + # see https://github.com/terser-js/terser#command-line-usage + source_map_opts = ["includeSources", "base=" + ctx.bin_dir.path] + + # This option doesn't work in the config file, only on the CLI + args.add_all(["--source-map", ",".join(source_map_opts)]) + + args.add("--comments") + + args.add_all(["--config-file", ctx.file.terser_config_file.path]) + + ctx.actions.run( + progress_message = "Optimizing JavaScript %s [terser]" % output.short_path, + executable = ctx.executable.terser, + inputs = [input, ctx.file.terser_config_file], + outputs = [output, map_output], + arguments = [args], + ) + + return map_output + +def _compute_node_modules_root(ctx): + """Computes the node_modules root from the node_modules and deps attributes. + + Args: + ctx: the skylark execution context + + Returns: + The node_modules root as a string + """ + node_modules_root = None + for d in ctx.attr.deps: + if NpmPackageInfo in d: + possible_root = "/".join(["external", d[NpmPackageInfo].workspace, "node_modules"]) + if not node_modules_root: + node_modules_root = possible_root + elif node_modules_root != possible_root: + fail("All npm dependencies need to come from a single workspace. Found '%s' and '%s'." % (node_modules_root, possible_root)) + if not node_modules_root: + # there are no fine grained deps but we still need a node_modules_root even if its empty + node_modules_root = "external/npm/node_modules" + return node_modules_root + +def _write_rollup_config(ctx, root_dir, filename = "_%s.rollup.conf.js"): + """Generate a rollup config file. + + Args: + ctx: Bazel rule execution context + root_dir: root directory for module resolution (defaults to None) + filename: output filename pattern (defaults to `_%s.rollup.conf.js`) + + Returns: + The rollup config file. See https://rollupjs.org/guide/en#configuration-files + """ + config = ctx.actions.declare_file(filename % ctx.label.name) + + mappings = dict() + all_deps = ctx.attr.deps + ctx.attr.srcs + for dep in all_deps: + if hasattr(dep, _NG_PACKAGE_MODULE_MAPPINGS_ATTR): + for k, v in getattr(dep, _NG_PACKAGE_MODULE_MAPPINGS_ATTR).items(): + if k in mappings and mappings[k] != v: + fail(("duplicate module mapping at %s: %s maps to both %s and %s" % + (dep.label, k, mappings[k], v)), "deps") + mappings[k] = v + + ctx.actions.expand_template( + output = config, + template = ctx.file.rollup_config_tmpl, + substitutions = { + "TMPL_banner_file": "\"%s\"" % ctx.file.license_banner.path if ctx.file.license_banner else "undefined", + "TMPL_module_mappings": str(mappings), + "TMPL_node_modules_root": _compute_node_modules_root(ctx), + "TMPL_root_dir": root_dir, + "TMPL_stamp_data": "\"%s\"" % ctx.version_file.path if ctx.version_file else "undefined", + "TMPL_workspace_name": ctx.workspace_name, + }, + ) + + return config + +def _run_rollup(ctx, bundle_name, rollup_config, entry_point, inputs, js_output, format, module_name = "", include_tslib = False): map_output = ctx.actions.declare_file(js_output.basename + ".map", sibling = js_output) args = ctx.actions.args() args.add("--config", rollup_config) - args.add("--input", entry_point) args.add("--output.file", js_output) args.add("--output.format", format) if module_name: args.add("--output.name", _global_name(module_name)) args.add("--amd.id", module_name) + elif format == "umd" or format == "iife": + # If we're generating UMD or IIFE we need a global name. + # See https://risanb.com/posts/bundling-your-javascript-library-with-rollup/#umd-output and + # https://risanb.com/posts/bundling-your-javascript-library-with-rollup/#umd-output. + args.add("--output.name", ctx.label.name) # After updating to build_bazel_rules_nodejs 0.27.0+, rollup has been updated to v1.3.1 # which tree shakes @__PURE__ annotations and const variables which are later amended by NGCC. @@ -129,6 +266,8 @@ def _rollup(ctx, bundle_name, rollup_config, entry_point, inputs, js_output, for # of command line args args.add("--sourcemap") + args.add("--preserveSymlinks") + globals = dict(WELL_KNOWN_GLOBALS, **ctx.attr.globals) external = globals.keys() if not include_tslib: @@ -141,6 +280,8 @@ def _rollup(ctx, bundle_name, rollup_config, entry_point, inputs, js_output, for join_with = ",", ) + # We will produce errors as needed. Anything else is spammy: a well-behaved + # bazel rule prints nothing on success. args.add("--silent") other_inputs = [rollup_config] @@ -153,8 +294,8 @@ def _rollup(ctx, bundle_name, rollup_config, entry_point, inputs, js_output, for mnemonic = "AngularPackageRollup", inputs = inputs.to_list() + other_inputs, outputs = [js_output, map_output], - executable = ctx.executable._rollup, - tools = [ctx.executable._rollup], + executable = ctx.executable.rollup, + tools = [ctx.executable.rollup], arguments = [args], ) return struct( @@ -191,9 +332,6 @@ def _filter_out_generated_files(files, extension, package_path = None): return depset(result) -def _esm2015_root_dir(ctx): - return ctx.label.name + ".es6" - def _filter_js_inputs(all_inputs): all_inputs_list = all_inputs.to_list() if type(all_inputs) == _DEPSET_TYPE else all_inputs return [ @@ -206,7 +344,12 @@ def _filter_js_inputs(all_inputs): def _ng_package_impl(ctx): npm_package_directory = ctx.actions.declare_directory("%s.ng_pkg" % ctx.label.name) - esm_2015_files = _filter_out_generated_files(collect_es6_sources(ctx), "js") + esm_2015_files_depsets = [] + for dep in ctx.attr.deps: + if JSEcmaScriptModuleInfo in dep: + esm_2015_files_depsets.append(dep[JSEcmaScriptModuleInfo].sources) + + esm_2015_files = _filter_out_generated_files(depset(transitive = esm_2015_files_depsets), "mjs") esm5_sources = _filter_out_generated_files(flatten_esm5(ctx), "js") # These accumulators match the directory names where the files live in the @@ -225,7 +368,9 @@ def _ng_package_impl(ctx): if f.path.endswith(".js"): esm5.append(struct(js = f, map = None)) for f in esm_2015_files.to_list(): - if f.path.endswith(".js"): + # tsickle generated `{module}.externs.js` file will be added to JSEcmaScriptModuleInfo sources + # by ng_module so we include both .js and .mjs sources from the JSEcmaScriptModuleInfo provider + if f.path.endswith(".js") or f.path.endswith(".mjs"): esm2015.append(struct(js = f, map = None)) # We infer the entry points to be: @@ -316,10 +461,8 @@ def _ng_package_impl(ctx): es2015_entry_point = "/".join([p for p in [ ctx.bin_dir.path, ctx.label.package, - _esm2015_root_dir(ctx), - ctx.label.package, entry_point, - index_file, + index_file.replace(".js", ".mjs"), ] if p]) es5_entry_point = "/".join([p for p in [ @@ -343,23 +486,43 @@ def _ng_package_impl(ctx): umd_output = ctx.outputs.umd min_output = ctx.outputs.umd_min - node_modules_files = _filter_js_inputs(ctx.files.node_modules) - # Also include files from npm fine grained deps as inputs. # These deps are identified by the NpmPackageInfo provider. + node_modules_files = [] for d in ctx.attr.deps: if NpmPackageInfo in d: node_modules_files += _filter_js_inputs(d.files) esm5_rollup_inputs = depset(node_modules_files, transitive = [esm5_sources]) - esm2015_config = write_rollup_config(ctx, [], "/".join([ctx.bin_dir.path, ctx.label.package, _esm2015_root_dir(ctx)]), filename = "_%s.rollup_esm2015.conf.js") - esm5_config = write_rollup_config(ctx, [], "/".join([ctx.bin_dir.path, ctx.label.package, esm5_root_dir(ctx)]), filename = "_%s.rollup_esm5.conf.js") + esm2015_config = _write_rollup_config(ctx, ctx.bin_dir.path, filename = "_%s.rollup_esm2015.conf.js") + esm5_config = _write_rollup_config(ctx, "/".join([ctx.bin_dir.path, ctx.label.package, esm5_root_dir(ctx)]), filename = "_%s.rollup_esm5.conf.js") + + fesm2015.append( + _run_rollup( + ctx, + "fesm2015", + esm2015_config, + es2015_entry_point, + depset(node_modules_files, transitive = [esm_2015_files]), + fesm2015_output, + format = "esm", + ), + ) - fesm2015.append(_rollup(ctx, "fesm2015", esm2015_config, es2015_entry_point, depset(node_modules_files, transitive = [esm_2015_files]), fesm2015_output)) - fesm5.append(_rollup(ctx, "fesm5", esm5_config, es5_entry_point, esm5_rollup_inputs, fesm5_output)) + fesm5.append( + _run_rollup( + ctx, + "fesm5", + esm5_config, + es5_entry_point, + esm5_rollup_inputs, + fesm5_output, + format = "esm", + ), + ) bundles.append( - _rollup( + _run_rollup( ctx, "umd", esm5_config, @@ -371,11 +534,10 @@ def _ng_package_impl(ctx): include_tslib = True, ), ) - terser_sourcemap = run_terser( + terser_sourcemap = _terser( ctx, umd_output, min_output, - config_name = entry_point.replace("/", "_"), ) bundles.append(struct(js = min_output, map = terser_sourcemap)) @@ -468,21 +630,89 @@ def _ng_package_impl(ctx): files = depset([package_dir]), )] -DEPS_ASPECTS = [esm5_outputs_aspect] +_NG_PACKAGE_DEPS_ASPECTS = [esm5_outputs_aspect, ng_package_module_mappings_aspect, node_modules_aspect] + +_NG_PACKAGE_ATTRS = dict(NPM_PACKAGE_ATTRS, **{ + "srcs": attr.label_list( + doc = """JavaScript source files from the workspace. + These can use ES2015 syntax and ES Modules (import/export)""", + allow_files = True, + ), + "entry_point": attr.label( + doc = """The starting point of the application, passed as the `--input` flag to rollup. + + If the entry JavaScript file belongs to the same package (as the BUILD file), + you can simply reference it by its relative name to the package directory: + + ``` + ng_package( + name = "bundle", + entry_point = ":main.js", + ) + ``` + + You can specify the entry point as a typescript file so long as you also include + the ts_library target in deps: -# Workaround skydoc bug which assumes ROLLUP_DEPS_ASPECTS is a str type -[DEPS_ASPECTS.append(a) for a in ROLLUP_DEPS_ASPECTS] + ``` + ts_library( + name = "main", + srcs = ["main.ts"], + ) + + ng_package( + name = "bundle", + deps = [":main"] + entry_point = ":main.ts", + ) + ``` + + The rule will use the corresponding `.js` output of the ts_library rule as the entry point. + + If the entry point target is a rule, it should produce a single JavaScript entry file that will be passed to the nodejs_binary rule. + For example: -NG_PACKAGE_ATTRS = dict(NPM_PACKAGE_ATTRS, **dict(ROLLUP_ATTRS, **{ - "srcs": attr.label_list(allow_files = True), - "deps": attr.label_list(aspects = DEPS_ASPECTS), + ``` + filegroup( + name = "entry_file", + srcs = ["main.js"], + ) + + ng_package( + name = "bundle", + entry_point = ":entry_file", + ) + ``` + """, + mandatory = True, + allow_single_file = True, + ), + "globals": attr.string_dict( + doc = """A dict of symbols that reference external scripts. + The keys are variable names that appear in the program, + and the values are the symbol to reference at runtime in a global context (UMD bundles). + For example, a program referencing @angular/core should use ng.core + as the global reference, so Angular users should include the mapping + `"@angular/core":"ng.core"` in the globals.""", + default = {}, + ), + "license_banner": attr.label( + doc = """A .txt file passed to the `banner` config option of rollup. + The contents of the file will be copied to the top of the resulting bundles. + Note that you can replace a version placeholder in the license file, by using + the special version `0.0.0-PLACEHOLDER`. See the section on stamping in the README.""", + allow_single_file = [".txt"], + ), + "deps": attr.label_list( + doc = """Other rules that produce JavaScript outputs, such as `ts_library`.""", + aspects = _NG_PACKAGE_DEPS_ASPECTS, + ), "data": attr.label_list( doc = "Additional, non-Angular files to be added to the package, e.g. global CSS assets.", allow_files = True, ), "include_devmode_srcs": attr.bool(default = False), "readme_md": attr.label(allow_single_file = [".md"]), - "globals": attr.string_dict(default = {}), "entry_point_name": attr.string( doc = "Name to use when generating bundle files for the primary entry-point.", ), @@ -491,16 +721,34 @@ NG_PACKAGE_ATTRS = dict(NPM_PACKAGE_ATTRS, **dict(ROLLUP_ATTRS, **{ executable = True, cfg = "host", ), - "_rollup": attr.label( - default = Label("@build_bazel_rules_nodejs//internal/rollup"), + "rollup": attr.label( + default = Label(_DEFAULT_ROLLUP), + executable = True, + cfg = "host", + ), + "terser": attr.label( executable = True, cfg = "host", + default = Label(_DEFAULT_TERSER), + ), + "terser_config_file": attr.label( + doc = """A JSON file containing Terser minify() options. + +This is the file you would pass to the --config-file argument in terser's CLI. +https://github.com/terser-js/terser#minify-options documents the content of the file. + +If `config_file` isn't supplied, Bazel will use a default config file. +""", + allow_single_file = True, + # These defaults match how terser was run in the legacy built-in rollup_bundle rule. + # We keep them the same so it's easier for users to migrate. + default = Label(_DEFALUT_TERSER_CONFIG_FILE), ), - "_rollup_config_tmpl": attr.label( - default = Label("@build_bazel_rules_nodejs//internal/rollup:rollup.config.js"), + "rollup_config_tmpl": attr.label( + default = Label(_DEFAULT_ROLLUP_CONFIG_TMPL), allow_single_file = True, ), -})) +}) # Angular wants these named after the entry_point, # eg. for //packages/core it looks like "packages/core/index.js", we want @@ -539,7 +787,7 @@ def primary_entry_point_name(name, entry_point, entry_point_name): # Fall back to the name of the ng_package rule. return name -def ng_package_outputs(name, entry_point, entry_point_name): +def _ng_package_outputs(name, entry_point, entry_point_name): """This is not a public API. This function computes the named outputs for an ng_package rule. @@ -570,8 +818,8 @@ def ng_package_outputs(name, entry_point, entry_point_name): ng_package = rule( implementation = _ng_package_impl, - attrs = NG_PACKAGE_ATTRS, - outputs = ng_package_outputs, + attrs = _NG_PACKAGE_ATTRS, + outputs = _ng_package_outputs, ) """ ng_package produces an npm-ready package from an Angular library. diff --git a/packages/bazel/src/ng_package/packager.ts b/packages/bazel/src/ng_package/packager.ts index 97d075b6a98ea..c4f17fb77f22c 100644 --- a/packages/bazel/src/ng_package/packager.ts +++ b/packages/bazel/src/ng_package/packager.ts @@ -140,20 +140,28 @@ function main(args: string[]): number { /** * Relativize the path where a file is written. - * @param file a path containing a re-rooted segment like .esm5 or .es6 + * @param file a path containing a re-rooted segment like .esm5 * @param suffix the re-rooted directory * @param outDir path where we copy the file, relative to the out */ function writeEsmFile(file: string, suffix: string, outDir: string) { - // Note that the specified file path is always using the posix path delimiter. - const root = file.substr(0, file.lastIndexOf(`${suffix}/`) + suffix.length + 1); - const rel = path.dirname(path.relative(path.join(root, srcDir), file)); + function relPath(file: string, suffix: string) { + if (suffix) { + // Note that the specified file path is always using the posix path delimiter. + const root = + suffix ? file.substr(0, file.lastIndexOf(`${suffix}/`) + suffix.length + 1) : binDir; + return path.dirname(path.relative(path.join(root, srcDir), file)); + } else { + return path.dirname(path.relative(binDir, file)); + } + } + const rel = relPath(file, suffix); if (!rel.startsWith('..')) { copyFile(file, path.join(out, outDir), rel); } } - esm2015.forEach(file => writeEsmFile(file, '.es6', 'esm2015')); + esm2015.forEach(file => writeEsmFile(file, '', 'esm2015')); esm5.forEach(file => writeEsmFile(file, '.esm5', 'esm5')); bundles.forEach(bundle => { copyFile(bundle, out, 'bundles'); }); @@ -276,19 +284,22 @@ function main(args: string[]): number { function copyFile(file: string, baseDir: string, relative = '.') { const dir = path.join(baseDir, relative); + // output file is .js if the input file is .mjs + const outFile = path.posix.join( + dir, path.basename(file.endsWith('.mjs') ? file.replace(/\.mjs$/, '.js') : file)); shx.mkdir('-p', dir); - shx.cp(file, dir); + shx.cp(file, outFile); // Double-underscore is used to escape forward slash in FESM filenames. // See ng_package.bzl: // fesm_output_filename = entry_point.replace("/", "__") // We need to unescape these. - if (file.indexOf('__') >= 0) { - const outputPath = path.join(dir, ...path.basename(file).split('__')); + if (outFile.indexOf('__') >= 0) { + const outputPath = path.join(dir, ...path.basename(outFile).split('__')); shx.mkdir('-p', path.dirname(outputPath)); shx.mv(path.join(dir, path.basename(file)), outputPath); // if we are renaming the .js file, we'll also need to update the sourceMappingURL in the file - if (file.endsWith('.js')) { + if (outFile.endsWith('.js')) { shx.chmod('+w', outputPath); shx.sed('-i', `${path.basename(file)}.map`, `${path.basename(outputPath)}.map`, outputPath); } diff --git a/packages/bazel/src/ng_package/rollup.config.js b/packages/bazel/src/ng_package/rollup.config.js new file mode 100644 index 0000000000000..66cb42e8fbaf9 --- /dev/null +++ b/packages/bazel/src/ng_package/rollup.config.js @@ -0,0 +1,160 @@ +/** + * @license + * Copyright Google Inc. All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ +// Rollup configuration +// GENERATED BY Bazel + +const nodeResolve = require('rollup-plugin-node-resolve'); +const sourcemaps = require('rollup-plugin-sourcemaps'); +const commonjs = require('rollup-plugin-commonjs'); +const path = require('path'); +const fs = require('fs'); + +function log_verbose(...m) { + // This is a template file so we use __filename to output the actual filename + if (!!process.env['VERBOSE_LOGS']) console.error(`[${path.basename(__filename)}]`, ...m); +} + +const workspaceName = 'TMPL_workspace_name'; +const rootDir = 'TMPL_root_dir'; +const bannerFile = TMPL_banner_file; +const stampData = TMPL_stamp_data; +const moduleMappings = TMPL_module_mappings; +const nodeModulesRoot = 'TMPL_node_modules_root'; + +log_verbose(`running with + cwd: ${process.cwd()} + workspaceName: ${workspaceName} + rootDir: ${rootDir} + bannerFile: ${bannerFile} + stampData: ${stampData} + moduleMappings: ${JSON.stringify(moduleMappings)} + nodeModulesRoot: ${nodeModulesRoot} +`); + +function fileExists(filePath) { + try { + return fs.statSync(filePath).isFile(); + } catch (e) { + return false; + } +} + +// This resolver mimics the TypeScript Path Mapping feature, which lets us resolve +// modules based on a mapping of short names to paths. +function resolveBazel( + importee, importer, baseDir = process.cwd(), resolve = require.resolve, root = rootDir) { + log_verbose(`resolving '${importee}' from ${importer}`); + + function resolveInRootDir(importee) { + var candidate = path.join(baseDir, root, importee); + log_verbose(`try to resolve '${importee}' at '${candidate}'`); + try { + var result = resolve(candidate); + return result; + } catch (e) { + return undefined; + } + } + + // Since mappings are always in POSIX paths, when comparing the importee to mappings + // we should normalize the importee. + // Having it normalized is also useful to determine relative paths. + const normalizedImportee = importee.replace(/\\/g, '/'); + + // If import is fully qualified then resolve it directly + if (fileExists(importee)) { + log_verbose(`resolved fully qualified '${importee}'`); + return importee; + } + + var resolved; + if (normalizedImportee.startsWith('./') || normalizedImportee.startsWith('../')) { + // relative import + if (importer) { + let importerRootRelative = path.dirname(importer); + const relative = path.relative(path.join(baseDir, root), importerRootRelative); + if (!relative.startsWith('.')) { + importerRootRelative = relative; + } + resolved = path.join(importerRootRelative, importee); + } else { + throw new Error('cannot resolve relative paths without an importer'); + } + if (resolved) resolved = resolveInRootDir(resolved); + } + + if (!resolved) { + // possible workspace import or external import if importee matches a module + // mapping + for (const k in moduleMappings) { + if (normalizedImportee == k || normalizedImportee.startsWith(k + '/')) { + // replace the root module name on a mappings match + // note that the module_root attribute is intended to be used for type-checking + // so it uses eg. "index.d.ts". At runtime, we have only index.js, so we strip the + // .d.ts suffix and let node require.resolve do its thing. + var v = moduleMappings[k].replace(/\.d\.ts$/, ''); + const mappedImportee = path.join(v, normalizedImportee.slice(k.length + 1)); + log_verbose(`module mapped '${importee}' to '${mappedImportee}'`); + resolved = resolveInRootDir(mappedImportee); + if (resolved) break; + } + } + } + + if (!resolved) { + // workspace import + const userWorkspacePath = path.relative(workspaceName, importee); + resolved = resolveInRootDir(userWorkspacePath.startsWith('..') ? importee : userWorkspacePath); + } + + if (resolved) { + log_verbose(`resolved to ${resolved}`); + } else { + log_verbose(`allowing rollup to resolve '${importee}' with node module resolution`); + } + + return resolved; +} + +let banner = ''; +if (bannerFile) { + banner = fs.readFileSync(bannerFile, {encoding: 'utf-8'}); + if (stampData) { + const versionTag = fs.readFileSync(stampData, {encoding: 'utf-8'}) + .split('\n') + .find(s => s.startsWith('BUILD_SCM_VERSION')); + // Don't assume BUILD_SCM_VERSION exists + if (versionTag) { + const version = versionTag.split(' ')[1].trim(); + banner = banner.replace(/0.0.0-PLACEHOLDER/, version); + } + } +} + +const plugins = [ + { + name: 'resolveBazel', + resolveId: resolveBazel, + }, + nodeResolve({ + mainFields: ['browser', 'es2015', 'module', 'jsnext:main', 'main'], + jail: process.cwd(), + customResolveOptions: {moduleDirectory: nodeModulesRoot} + }), + commonjs({ignoreGlobal: true}), + sourcemaps(), +]; + +const config = { + plugins, + output: { + banner, + } +}; + +module.exports = config; diff --git a/packages/bazel/src/ng_package/terser_config.default.json b/packages/bazel/src/ng_package/terser_config.default.json new file mode 100644 index 0000000000000..7fe029be719ef --- /dev/null +++ b/packages/bazel/src/ng_package/terser_config.default.json @@ -0,0 +1,12 @@ +{ + "compress": { + "global_defs": {"ngDevMode": false, "ngI18nClosureMode": false}, + "keep_fnames": true, + "passes": 3, + "pure_getters": true, + "reduce_funcs": true, + "reduce_vars": true, + "sequences": true + }, + "mangle": true +} \ No newline at end of file diff --git a/packages/bazel/src/ng_rollup_bundle.bzl b/packages/bazel/src/ng_rollup_bundle.bzl deleted file mode 100644 index 46f9641eade3a..0000000000000 --- a/packages/bazel/src/ng_rollup_bundle.bzl +++ /dev/null @@ -1,229 +0,0 @@ -# Copyright Google Inc. All Rights Reserved. -# -# Use of this source code is governed by an MIT-style license that can be -# found in the LICENSE file at https://angular.io/license - -"""Rollup with Build Optimizer - - This provides a variant of the [rollup_bundle] rule that works better for Angular apps. - - It registers `@angular-devkit/build-optimizer` as a rollup plugin, to get - better optimization. It also uses ESM5 format inputs, as this is what - build-optimizer is hard-coded to look for and transform. - - [rollup_bundle]: https://bazelbuild.github.io/rules_nodejs/rollup/rollup_bundle.html -""" - -load( - "@build_bazel_rules_nodejs//internal/rollup:rollup_bundle.bzl", - "ROLLUP_ATTRS", - "ROLLUP_DEPS_ASPECTS", - "run_rollup", - "run_sourcemapexplorer", - "run_terser", - "write_rollup_config", -) -load("@build_bazel_rules_nodejs//internal/common:collect_es6_sources.bzl", collect_es2015_sources = "collect_es6_sources") -load(":esm5.bzl", "esm5_outputs_aspect", "esm5_root_dir", "flatten_esm5") - -ROLLUP_OUTPUTS = { - "build_cjs": "%{name}.cjs.js", - "build_es2015": "%{name}.es2015.js", - "build_es2015_min": "%{name}.min.es2015.js", - "build_es2015_min_debug": "%{name}.min_debug.es2015.js", - "build_es5": "%{name}.js", - "build_es5_min": "%{name}.min.js", - "build_es5_min_debug": "%{name}.min_debug.js", - "build_umd": "%{name}.umd.js", - "explore_html": "%{name}.explore.html", -} - -PACKAGES = [ - # Generated paths when using ng_rollup_bundle outside this monorepo. - "external/angular/packages/core/src", - "external/angular/packages/common/src", - "external/angular/packages/compiler/src", - "external/angular/packages/platform-browser/src", - "external/rxjs", - # Generated paths when using ng_rollup_bundle inside this monorepo. - "packages/core/src", - "packages/common/src", - "packages/compiler/src", - "packages/platform-browser/src", -] -PLUGIN_CONFIG = "{sideEffectFreeModules: [\n%s]}" % ",\n".join( - [" '.esm5/{0}'".format(p) for p in PACKAGES], -) -BO_ROLLUP = "npm/node_modules/@angular-devkit/build-optimizer/src/build-optimizer/rollup-plugin.js" -BO_PLUGIN = "require('%s').default(%s)" % (BO_ROLLUP, PLUGIN_CONFIG) - -def _use_plain_rollup(ctx): - """Determine whether to use the Angular or upstream versions of the rollup_bundle rule. - - In most modes, the Angular version of rollup is used. This runs build optimizer as part of its - processing, which affects decorators and annotations. - - In JIT modes, an emulation of the upstream rollup_bundle rule is used. This avoids running - build optimizer on code which isn't designed to be optimized by it. - - Args: - ctx: skylark rule execution context - - Returns: - true iff the Angular version of rollup with build optimizer should be used, false otherwise - """ - - if "compile" not in ctx.var: - return False - - strategy = ctx.var["compile"] - return strategy == "jit" - -def run_brotli(ctx, input, output): - """Execute the Brotli compression utility. - - Args: - ctx: Bazel's rule execution context - input: any file - output: the compressed file - """ - ctx.actions.run( - executable = ctx.executable._brotli, - inputs = [input], - outputs = [output], - arguments = ["--output=%s" % output.path, input.path], - ) - -# Borrowed from bazelbuild/rules_nodejs -def _run_tsc(ctx, input, output): - args = ctx.actions.args() - args.add("--target", "es5") - args.add("--allowJS") - args.add(input) - args.add("--outFile", output) - - ctx.actions.run( - executable = ctx.executable._tsc, - inputs = [input], - outputs = [output], - arguments = [args], - ) - -# Borrowed from bazelbuild/rules_nodejs, with the addition of brotli compression output -def _plain_rollup_bundle(ctx): - rollup_config = write_rollup_config(ctx) - run_rollup(ctx, collect_es2015_sources(ctx), rollup_config, ctx.outputs.build_es2015) - run_terser(ctx, ctx.outputs.build_es2015, ctx.outputs.build_es2015_min, config_name = ctx.label.name + "es2015_min") - run_terser(ctx, ctx.outputs.build_es2015, ctx.outputs.build_es2015_min_debug, debug = True, config_name = ctx.label.name + "es2015_min_debug") - _run_tsc(ctx, ctx.outputs.build_es2015, ctx.outputs.build_es5) - source_map = run_terser(ctx, ctx.outputs.build_es5, ctx.outputs.build_es5_min) - run_terser(ctx, ctx.outputs.build_es5, ctx.outputs.build_es5_min_debug, debug = True) - umd_rollup_config = write_rollup_config(ctx, filename = "_%s_umd.rollup.conf.js", output_format = "umd") - run_rollup(ctx, collect_es2015_sources(ctx), umd_rollup_config, ctx.outputs.build_umd) - cjs_rollup_config = write_rollup_config(ctx, filename = "_%s_cjs.rollup.conf.js", output_format = "cjs") - run_rollup(ctx, collect_es2015_sources(ctx), cjs_rollup_config, ctx.outputs.build_cjs) - run_sourcemapexplorer(ctx, ctx.outputs.build_es5_min, source_map, ctx.outputs.explore_html) - - run_brotli(ctx, ctx.outputs.build_es5_min, ctx.outputs.build_es5_min_compressed) - files = [ctx.outputs.build_es5_min, source_map] - return DefaultInfo(files = depset(files), runfiles = ctx.runfiles(files)) - -def _ng_rollup_bundle(ctx): - # Escape and use the plain rollup rule if the compilation strategy requires it - if _use_plain_rollup(ctx): - return _plain_rollup_bundle(ctx) - - # We don't expect anyone to make use of this bundle yet, but it makes this rule - # compatible with rollup_bundle which allows them to be easily swapped back and - # forth. - esm2015_rollup_config = write_rollup_config(ctx, filename = "_%s.rollup_es2015.conf.js") - esm2015_rollup_sourcemap = run_rollup(ctx, collect_es2015_sources(ctx), esm2015_rollup_config, ctx.outputs.build_es2015) - - run_terser( - ctx, - ctx.outputs.build_es2015, - ctx.outputs.build_es2015_min, - config_name = ctx.label.name + "es2015_min", - comments = False, - in_source_map = esm2015_rollup_sourcemap, - ) - run_terser( - ctx, - ctx.outputs.build_es2015, - ctx.outputs.build_es2015_min_debug, - config_name = ctx.label.name + "es2015_min_debug", - debug = True, - comments = False, - ) - - esm5_sources = flatten_esm5(ctx) - - rollup_config = write_rollup_config(ctx, [BO_PLUGIN], "/".join([ctx.bin_dir.path, ctx.label.package, esm5_root_dir(ctx)])) - rollup_sourcemap = run_rollup(ctx, esm5_sources, rollup_config, ctx.outputs.build_es5) - - sourcemap = run_terser( - ctx, - ctx.outputs.build_es5, - ctx.outputs.build_es5_min, - config_name = ctx.label.name + "es5_min", - comments = False, - in_source_map = rollup_sourcemap, - ) - run_terser( - ctx, - ctx.outputs.build_es5, - ctx.outputs.build_es5_min_debug, - config_name = ctx.label.name + "es5_min_debug", - debug = True, - comments = False, - ) - - umd_rollup_config = write_rollup_config(ctx, filename = "_%s_umd.rollup.conf.js", output_format = "umd") - run_rollup(ctx, collect_es2015_sources(ctx), umd_rollup_config, ctx.outputs.build_umd) - cjs_rollup_config = write_rollup_config(ctx, filename = "_%s_cjs.rollup.conf.js", output_format = "cjs") - run_rollup(ctx, collect_es2015_sources(ctx), cjs_rollup_config, ctx.outputs.build_cjs) - - run_brotli(ctx, ctx.outputs.build_es5_min, ctx.outputs.build_es5_min_compressed) - - run_sourcemapexplorer(ctx, ctx.outputs.build_es5_min, sourcemap, ctx.outputs.explore_html) - - return DefaultInfo(files = depset([ctx.outputs.build_es5_min, sourcemap])) - -DEPS_ASPECTS = [esm5_outputs_aspect] - -# Workaround skydoc bug which assumes ROLLUP_DEPS_ASPECTS is a str type -[DEPS_ASPECTS.append(a) for a in ROLLUP_DEPS_ASPECTS] - -ng_rollup_bundle = rule( - implementation = _ng_rollup_bundle, - attrs = dict(ROLLUP_ATTRS, **{ - "deps": attr.label_list( - doc = """Other targets that provide JavaScript files. - Typically this will be `ts_library` or `ng_module` targets.""", - aspects = DEPS_ASPECTS, - ), - "_brotli": attr.label( - executable = True, - cfg = "host", - default = Label("//tools/brotli-cli"), - ), - "_rollup": attr.label( - executable = True, - cfg = "host", - default = Label("@angular//packages/bazel/src:rollup_with_build_optimizer"), - ), - }), - outputs = dict(ROLLUP_OUTPUTS, **{ - "build_es5_min_compressed": "%{name}.min.js.br", - }), -) -""" -Run [Rollup] with the [Build Optimizer] plugin. - -This rule extends from the [rollup_bundle] rule, so attributes and outputs of -that rule are used here too. - -[Rollup]: https://rollupjs.org/ -[Build Optimizer]: https://www.npmjs.com/package/@angular-devkit/build-optimizer -[rollup_bundle]: https://bazelbuild.github.io/rules_nodejs/rollup/rollup_bundle.html -""" diff --git a/packages/bazel/test/ng_package/example_package.golden b/packages/bazel/test/ng_package/example_package.golden index d19653a457919..d9607949bbc64 100644 --- a/packages/bazel/test/ng_package/example_package.golden +++ b/packages/bazel/test/ng_package/example_package.golden @@ -96,7 +96,7 @@ Hello typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('@angular/core')) : typeof define === 'function' && define.amd ? define('example/secondary', ['exports', '@angular/core'], factory) : (global = global || self, factory((global.example = global.example || {}, global.example.secondary = {}), global.ng.core)); -}(this, function (exports, core) { 'use strict'; +}(this, (function (exports, core) { 'use strict'; /*! ***************************************************************************** Copyright (c) Microsoft Corporation. All rights reserved. @@ -329,7 +329,7 @@ Hello Object.defineProperty(exports, '__esModule', { value: true }); -})); +}))); //# sourceMappingURL=waffels-secondary.umd.js.map @@ -340,7 +340,7 @@ Hello * (c) 2010-2019 Google LLC. https://angular.io/ * License: MIT */ -!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?t(exports,require("@angular/core")):"function"==typeof define&&define.amd?define("example/secondary",["exports","@angular/core"],t):t(((e=e||self).example=e.example||{},e.example.secondary={}),e.ng.core)}(this,function(e,t){"use strict"; +!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?t(exports,require("@angular/core")):"function"==typeof define&&define.amd?define("example/secondary",["exports","@angular/core"],t):t(((e=e||self).example=e.example||{},e.example.secondary={}),e.ng.core)}(this,(function(e,t){"use strict"; /** * @license * Copyright Google Inc. All Rights Reserved. @@ -348,7 +348,7 @@ Hello * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ -var o=function(){return function o(e,t,n,r){var c,f=arguments.length,l=f<3?t:null===r?r=Object.getOwnPropertyDescriptor(t,n):r;if("object"==typeof Reflect&&"function"==typeof Reflect.decorate)l=Reflect.decorate(e,t,n,r);else for(var u=e.length-1;u>=0;u--)(c=e[u])&&(l=(f<3?c(l):f>3?c(t,n,l):c(t,n))||l);return f>3&&l&&Object.defineProperty(t,n,l),l}([t.NgModule({})],function e(){})}(); +var o=function n(e,t,o,r){var c,f=arguments.length,l=f<3?t:null===r?r=Object.getOwnPropertyDescriptor(t,o):r;if("object"==typeof Reflect&&"function"==typeof Reflect.decorate)l=Reflect.decorate(e,t,o,r);else for(var a=e.length-1;a>=0;a--)(c=e[a])&&(l=(f<3?c(l):f>3?c(t,o,l):c(t,o))||l);return f>3&&l&&Object.defineProperty(t,o,l),l}([t.NgModule({})],(function o(){})); /** * @license * Copyright Google Inc. All Rights Reserved. @@ -356,7 +356,7 @@ var o=function(){return function o(e,t,n,r){var c,f=arguments.length,l=f<3?t:nul * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ -e.SecondaryModule=o,e.a=1,Object.defineProperty(e,"__esModule",{value:!0})}); +e.SecondaryModule=o,e.a=1,Object.defineProperty(e,"__esModule",{value:!0})})); --- bundles/waffels.umd.js --- @@ -370,7 +370,7 @@ e.SecondaryModule=o,e.a=1,Object.defineProperty(e,"__esModule",{value:!0})}); typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('@angular/core')) : typeof define === 'function' && define.amd ? define('example', ['exports', '@angular/core'], factory) : (global = global || self, factory(global.example = {}, global.ng.core)); -}(this, function (exports, core) { 'use strict'; +}(this, (function (exports, core) { 'use strict'; /*! ***************************************************************************** Copyright (c) Microsoft Corporation. All rights reserved. @@ -601,7 +601,7 @@ e.SecondaryModule=o,e.a=1,Object.defineProperty(e,"__esModule",{value:!0})}); Object.defineProperty(exports, '__esModule', { value: true }); -})); +}))); //# sourceMappingURL=waffels.umd.js.map @@ -612,7 +612,7 @@ e.SecondaryModule=o,e.a=1,Object.defineProperty(e,"__esModule",{value:!0})}); * (c) 2010-2019 Google LLC. https://angular.io/ * License: MIT */ -!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?t(exports,require("@angular/core")):"function"==typeof define&&define.amd?define("example",["exports","@angular/core"],t):t((e=e||self).example={},e.ng.core)}(this,function(e,t){"use strict"; +!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?t(exports,require("@angular/core")):"function"==typeof define&&define.amd?define("example",["exports","@angular/core"],t):t((e=e||self).example={},e.ng.core)}(this,(function(e,t){"use strict"; /** * @license * Copyright Google Inc. All Rights Reserved. @@ -620,14 +620,14 @@ e.SecondaryModule=o,e.a=1,Object.defineProperty(e,"__esModule",{value:!0})}); * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ -var o=function(){return function o(e,t,n,r){var f,c=arguments.length,u=c<3?t:null===r?r=Object.getOwnPropertyDescriptor(t,n):r;if("object"==typeof Reflect&&"function"==typeof Reflect.decorate)u=Reflect.decorate(e,t,n,r);else for(var i=e.length-1;i>=0;i--)(f=e[i])&&(u=(c<3?f(u):c>3?f(t,n,u):f(t,n))||u);return c>3&&u&&Object.defineProperty(t,n,u),u}([t.NgModule({})],function e(){})}(); +var o=function n(e,t,o,r){var f,c=arguments.length,l=c<3?t:null===r?r=Object.getOwnPropertyDescriptor(t,o):r;if("object"==typeof Reflect&&"function"==typeof Reflect.decorate)l=Reflect.decorate(e,t,o,r);else for(var u=e.length-1;u>=0;u--)(f=e[u])&&(l=(c<3?f(l):c>3?f(t,o,l):f(t,o))||l);return c>3&&l&&Object.defineProperty(t,o,l),l}([t.NgModule({})],(function o(){})); /** * @license * Copyright Google Inc. All Rights Reserved. * * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license - */e.MyModule=o,Object.defineProperty(e,"__esModule",{value:!0})}); + */e.MyModule=o,Object.defineProperty(e,"__esModule",{value:!0})})); --- esm2015/example.externs.js --- diff --git a/packages/bazel/test/ng_package/example_with_ts_library_package.golden b/packages/bazel/test/ng_package/example_with_ts_library_package.golden index 0528d83c5114c..03b0e39477c4e 100644 --- a/packages/bazel/test/ng_package/example_with_ts_library_package.golden +++ b/packages/bazel/test/ng_package/example_with_ts_library_package.golden @@ -84,7 +84,7 @@ License: MIT typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('@angular/core')) : typeof define === 'function' && define.amd ? define('example/portal', ['exports', '@angular/core'], factory) : (global = global || self, factory((global.example = global.example || {}, global.example.portal = {}), global.ng.core)); -}(this, function (exports, core) { 'use strict'; +}(this, (function (exports, core) { 'use strict'; /*! ***************************************************************************** Copyright (c) Microsoft Corporation. All rights reserved. @@ -317,7 +317,7 @@ License: MIT Object.defineProperty(exports, '__esModule', { value: true }); -})); +}))); //# sourceMappingURL=example-with-ts-library-portal.umd.js.map @@ -328,7 +328,7 @@ License: MIT * (c) 2010-2019 Google LLC. https://angular.io/ * License: MIT */ -!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?t(exports,require("@angular/core")):"function"==typeof define&&define.amd?define("example/portal",["exports","@angular/core"],t):t(((e=e||self).example=e.example||{},e.example.portal={}),e.ng.core)}(this,function(e,t){"use strict"; +!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?t(exports,require("@angular/core")):"function"==typeof define&&define.amd?define("example/portal",["exports","@angular/core"],t):t(((e=e||self).example=e.example||{},e.example.portal={}),e.ng.core)}(this,(function(e,t){"use strict"; /** * @license * Copyright Google Inc. All Rights Reserved. @@ -336,7 +336,7 @@ License: MIT * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ -var o=function(){return function o(e,t,r,n){var f,l=arguments.length,c=l<3?t:null===n?n=Object.getOwnPropertyDescriptor(t,r):n;if("object"==typeof Reflect&&"function"==typeof Reflect.decorate)c=Reflect.decorate(e,t,r,n);else for(var u=e.length-1;u>=0;u--)(f=e[u])&&(c=(l<3?f(c):l>3?f(t,r,c):f(t,r))||c);return l>3&&c&&Object.defineProperty(t,r,c),c}([t.NgModule({})],function e(){})}(); +var o=function r(e,t,o,n){var f,l=arguments.length,c=l<3?t:null===n?n=Object.getOwnPropertyDescriptor(t,o):n;if("object"==typeof Reflect&&"function"==typeof Reflect.decorate)c=Reflect.decorate(e,t,o,n);else for(var a=e.length-1;a>=0;a--)(f=e[a])&&(c=(l<3?f(c):l>3?f(t,o,c):f(t,o))||c);return l>3&&c&&Object.defineProperty(t,o,c),c}([t.NgModule({})],(function o(){})); /** * @license * Copyright Google Inc. All Rights Reserved. @@ -344,7 +344,7 @@ var o=function(){return function o(e,t,r,n){var f,l=arguments.length,c=l<3?t:nul * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ -e.PortalModule=o,e.a=1,Object.defineProperty(e,"__esModule",{value:!0})}); +e.PortalModule=o,e.a=1,Object.defineProperty(e,"__esModule",{value:!0})})); --- bundles/example-with-ts-library-utils.umd.js --- @@ -358,7 +358,7 @@ e.PortalModule=o,e.a=1,Object.defineProperty(e,"__esModule",{value:!0})}); typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) : typeof define === 'function' && define.amd ? define('example/utils', ['exports'], factory) : (global = global || self, factory((global.example = global.example || {}, global.example.utils = {}))); -}(this, function (exports) { 'use strict'; +}(this, (function (exports) { 'use strict'; /** * @license @@ -383,7 +383,7 @@ e.PortalModule=o,e.a=1,Object.defineProperty(e,"__esModule",{value:!0})}); Object.defineProperty(exports, '__esModule', { value: true }); -})); +}))); //# sourceMappingURL=example-with-ts-library-utils.umd.js.map @@ -394,7 +394,7 @@ e.PortalModule=o,e.a=1,Object.defineProperty(e,"__esModule",{value:!0})}); * (c) 2010-2019 Google LLC. https://angular.io/ * License: MIT */ -!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?t(exports):"function"==typeof define&&define.amd?define("example/utils",["exports"],t):t(((e=e||self).example=e.example||{},e.example.utils={}))}(this,function(e){"use strict"; +!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?t(exports):"function"==typeof define&&define.amd?define("example/utils",["exports"],t):t(((e=e||self).example=e.example||{},e.example.utils={}))}(this,(function(e){"use strict"; /** * @license * Copyright Google Inc. All Rights Reserved. @@ -409,7 +409,7 @@ e.PortalModule=o,e.a=1,Object.defineProperty(e,"__esModule",{value:!0})}); * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ -e.dispatchFakeEvent=function t(e,n){e.dispatchEvent(n)},Object.defineProperty(e,"__esModule",{value:!0})}); +e.dispatchFakeEvent=function t(e,n){e.dispatchEvent(n)},Object.defineProperty(e,"__esModule",{value:!0})})); --- bundles/example-with-ts-library.umd.js --- @@ -423,7 +423,7 @@ e.dispatchFakeEvent=function t(e,n){e.dispatchEvent(n)},Object.defineProperty(e, typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) : typeof define === 'function' && define.amd ? define('example', ['exports'], factory) : (global = global || self, factory(global.example = {})); -}(this, function (exports) { 'use strict'; +}(this, (function (exports) { 'use strict'; /** * @license @@ -438,7 +438,7 @@ e.dispatchFakeEvent=function t(e,n){e.dispatchEvent(n)},Object.defineProperty(e, Object.defineProperty(exports, '__esModule', { value: true }); -})); +}))); //# sourceMappingURL=example-with-ts-library.umd.js.map @@ -449,14 +449,14 @@ e.dispatchFakeEvent=function t(e,n){e.dispatchEvent(n)},Object.defineProperty(e, * (c) 2010-2019 Google LLC. https://angular.io/ * License: MIT */ -!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?t(exports):"function"==typeof define&&define.amd?define("example",["exports"],t):t((e=e||self).example={})}(this,function(e){"use strict"; +!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?t(exports):"function"==typeof define&&define.amd?define("example",["exports"],t):t((e=e||self).example={})}(this,(function(e){"use strict"; /** * @license * Copyright Google Inc. All Rights Reserved. * * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license - */e.VERSION="0.0.0",Object.defineProperty(e,"__esModule",{value:!0})}); + */e.VERSION="0.0.0",Object.defineProperty(e,"__esModule",{value:!0})})); --- esm2015/example.externs.js --- diff --git a/packages/core/test/bundling/cyclic_import/BUILD.bazel b/packages/core/test/bundling/cyclic_import/BUILD.bazel index d6c5b1d2283ad..3d285fde279e7 100644 --- a/packages/core/test/bundling/cyclic_import/BUILD.bazel +++ b/packages/core/test/bundling/cyclic_import/BUILD.bazel @@ -49,8 +49,8 @@ ts_library( jasmine_node_test( name = "test", data = [ - ":bundle", ":bundle.js", + ":bundle.min.js", ":bundle.min.js.br", ":bundle.min_debug.js", ], diff --git a/packages/core/test/bundling/hello_world/BUILD.bazel b/packages/core/test/bundling/hello_world/BUILD.bazel index c22a6cf43202f..488ad2d796342 100644 --- a/packages/core/test/bundling/hello_world/BUILD.bazel +++ b/packages/core/test/bundling/hello_world/BUILD.bazel @@ -46,8 +46,8 @@ ts_library( jasmine_node_test( name = "test", data = [ - ":bundle", ":bundle.js", + ":bundle.min.js", ":bundle.min.js.br", ":bundle.min_debug.js", ], diff --git a/packages/language-service/BUILD.bazel b/packages/language-service/BUILD.bazel index eba37455dba5c..db9d9e1229d23 100644 --- a/packages/language-service/BUILD.bazel +++ b/packages/language-service/BUILD.bazel @@ -31,6 +31,7 @@ npm_package( visibility = ["//visibility:private"], deps = [ ":language-service", - "//packages/language-service/bundles:language-service", + # min bundle is not used at the moment; omit from package to speed up build + "//packages/language-service/bundles:language-service.umd.js", ], ) diff --git a/packages/language-service/bundles/BUILD.bazel b/packages/language-service/bundles/BUILD.bazel index 7ddb1345de160..cfb20da7b5cd5 100644 --- a/packages/language-service/bundles/BUILD.bazel +++ b/packages/language-service/bundles/BUILD.bazel @@ -1,4 +1,4 @@ -load(":rollup.bzl", "ls_rollup_bundle") +load("//tools/ng_rollup_bundle:ng_rollup_bundle.bzl", "ls_rollup_bundle") ls_rollup_bundle( name = "language-service", diff --git a/packages/language-service/bundles/rollup.bzl b/packages/language-service/bundles/rollup.bzl deleted file mode 100644 index 73f1cc207bfa7..0000000000000 --- a/packages/language-service/bundles/rollup.bzl +++ /dev/null @@ -1,65 +0,0 @@ -"""Custom rollup_bundle for language service. - -Overrides format to AMD and produces only umd and min, no FESM. - -We do this so that we bundle all of the dependencies into the bundle -except for typescript, fs and path. - -This allows editors and other tools to easily use the language service bundle -without having to provide all of the angular specific peer dependencies. -""" - -load( - "@build_bazel_rules_nodejs//internal/rollup:rollup_bundle.bzl", - "ROLLUP_ATTRS", - "ROLLUP_DEPS_ASPECTS", - "run_rollup", - # "run_terser", - "write_rollup_config", -) -load("//packages/bazel/src:esm5.bzl", "esm5_outputs_aspect", "esm5_root_dir", "flatten_esm5") - -# Note: the file is called "umd.js" and "umd.min.js" because of historical -# reasons. The format is actually amd and not umd, but we are afraid to rename -# the file because that would likely break the IDE and other integrations that -# have the path hardcoded in them. -_ROLLUP_OUTPUTS = { - "build_umd": "%{name}.umd.js", - # min bundle is not used at the moment. Disable to speed up build - # "build_umd_min": "%{name}.umd.min.js", -} - -DEPS_ASPECTS = [esm5_outputs_aspect] - -# Workaround skydoc bug which assumes ROLLUP_DEPS_ASPECTS is a str type -[DEPS_ASPECTS.append(a) for a in ROLLUP_DEPS_ASPECTS] - -def _ls_rollup_bundle(ctx): - esm5_sources = flatten_esm5(ctx) - rollup_config = write_rollup_config( - ctx, - root_dir = "/".join([ctx.bin_dir.path, ctx.label.package, esm5_root_dir(ctx)]), - output_format = "amd", - ) - run_rollup(ctx, esm5_sources, rollup_config, ctx.outputs.build_umd) - - # source_map = run_terser(ctx, ctx.outputs.build_umd, ctx.outputs.build_umd_min) - return DefaultInfo( - files = depset([ - ctx.outputs.build_umd, - # ctx.outputs.build_umd_min, - # source_map, - ]), - ) - -ls_rollup_bundle = rule( - implementation = _ls_rollup_bundle, - attrs = dict(ROLLUP_ATTRS, **{ - "deps": attr.label_list( - doc = """Other targets that provide JavaScript files. - Typically this will be `ts_library` or `ng_module` targets.""", - aspects = DEPS_ASPECTS, - ), - }), - outputs = _ROLLUP_OUTPUTS, -) diff --git a/tools/defaults.bzl b/tools/defaults.bzl index 08c078de62825..f7f16fca3b8d1 100644 --- a/tools/defaults.bzl +++ b/tools/defaults.bzl @@ -5,14 +5,17 @@ load("@npm_bazel_jasmine//:index.bzl", _jasmine_node_test = "jasmine_node_test") load("@npm_bazel_karma//:index.bzl", _karma_web_test = "karma_web_test", _karma_web_test_suite = "karma_web_test_suite", _ts_web_test = "ts_web_test", _ts_web_test_suite = "ts_web_test_suite") load("@npm_bazel_typescript//:index.bzl", _ts_library = "ts_library") load("//packages/bazel:index.bzl", _ng_module = "ng_module", _ng_package = "ng_package") -load("//packages/bazel/src:ng_rollup_bundle.bzl", _ng_rollup_bundle = "ng_rollup_bundle") +load("//tools/ng_rollup_bundle:ng_rollup_bundle.bzl", _ng_rollup_bundle = "ng_rollup_bundle") load("//tools:ng_benchmark.bzl", _ng_benchmark = "ng_benchmark") _DEFAULT_TSCONFIG_TEST = "//packages:tsconfig-test" _INTERNAL_NG_MODULE_API_EXTRACTOR = "//packages/bazel/src/api-extractor:api_extractor" _INTERNAL_NG_MODULE_COMPILER = "//packages/bazel/src/ngc-wrapped" _INTERNAL_NG_MODULE_XI18N = "//packages/bazel/src/ngc-wrapped:xi18n" -_INTERNAL_NG_PACKAGER_PACKAGER = "//packages/bazel/src/ng_package:packager" +_INTERNAL_NG_PACKAGE_PACKAGER = "//packages/bazel/src/ng_package:packager" +_INTERNAL_NG_PACKAGE_DEFALUT_TERSER_CONFIG_FILE = "//packages/bazel/src/ng_package:terser_config.default.json" +_INTERNAL_NG_PACKAGE_DEFAULT_ROLLUP_CONFIG_TMPL = "//packages/bazel/src/ng_package:rollup.config.js" +_INTERNAL_NG_PACKAGE_DEFAULT_ROLLUP = "//packages/bazel/src/ng_package:rollup_for_ng_package" # Packages which are versioned together on npm ANGULAR_SCOPED_PACKAGES = ["@angular/%s" % p for p in [ @@ -145,7 +148,10 @@ def ng_package(name, readme_md = None, license_banner = None, deps = [], **kwarg readme_md = readme_md, license_banner = license_banner, replacements = PKG_GROUP_REPLACEMENTS, - ng_packager = _INTERNAL_NG_PACKAGER_PACKAGER, + ng_packager = _INTERNAL_NG_PACKAGE_PACKAGER, + terser_config_file = _INTERNAL_NG_PACKAGE_DEFALUT_TERSER_CONFIG_FILE, + rollup_config_tmpl = _INTERNAL_NG_PACKAGE_DEFAULT_ROLLUP_CONFIG_TMPL, + rollup = _INTERNAL_NG_PACKAGE_DEFAULT_ROLLUP, **kwargs ) diff --git a/tools/ng_rollup_bundle/BUILD.bazel b/tools/ng_rollup_bundle/BUILD.bazel new file mode 100644 index 0000000000000..c615a86bbb9be --- /dev/null +++ b/tools/ng_rollup_bundle/BUILD.bazel @@ -0,0 +1,18 @@ +package(default_visibility = ["//visibility:public"]) + +load("@build_bazel_rules_nodejs//:index.bzl", "nodejs_binary") + +exports_files(["rollup.config.js"]) + +nodejs_binary( + name = "rollup_with_build_optimizer", + data = [ + "@npm//@angular-devkit/build-optimizer", + "@npm//rollup", + "@npm//rollup-plugin-commonjs", + "@npm//rollup-plugin-node-resolve", + "@npm//rollup-plugin-sourcemaps", + ], + entry_point = "@npm//:node_modules/rollup/dist/bin/rollup", + install_source_map_support = False, +) diff --git a/tools/ng_rollup_bundle/ng_rollup_bundle.bzl b/tools/ng_rollup_bundle/ng_rollup_bundle.bzl new file mode 100644 index 0000000000000..2780d5eb65b72 --- /dev/null +++ b/tools/ng_rollup_bundle/ng_rollup_bundle.bzl @@ -0,0 +1,457 @@ +# Copyright Google Inc. All Rights Reserved. +# +# Use of this source code is governed by an MIT-style license that can be +# found in the LICENSE file at https://angular.io/license + +"""Rollup with Build Optimizer + + This provides a variant of the [rollup_bundle] rule that works better for Angular apps. + + It registers `@angular-devkit/build-optimizer` as a rollup plugin, to get + better optimization. It also uses ESM5 format inputs, as this is what + build-optimizer is hard-coded to look for and transform. + + [rollup_bundle]: https://bazelbuild.github.io/rules_nodejs/rollup/rollup_bundle.html +""" + +load("@build_bazel_rules_nodejs//:index.bzl", "npm_package_bin") +load("@build_bazel_rules_nodejs//:providers.bzl", "JSEcmaScriptModuleInfo", "NpmPackageInfo", "node_modules_aspect") +load("//packages/bazel/src:esm5.bzl", "esm5_outputs_aspect", "esm5_root_dir", "flatten_esm5") +load("@npm_bazel_terser//:index.bzl", "terser_minified") + +_NG_ROLLUP_BUNDLE_OUTPUTS = { + "bundle": "%{name}.js", + "sourcemap": "%{name}.js.map", +} + +_NG_ROLLUP_MODULE_MAPPINGS_ATTR = "ng_rollup_module_mappings" + +def _ng_rollup_module_mappings_aspect_impl(target, ctx): + mappings = dict() + for dep in ctx.rule.attr.deps: + if hasattr(dep, _NG_ROLLUP_MODULE_MAPPINGS_ATTR): + for k, v in getattr(dep, _NG_ROLLUP_MODULE_MAPPINGS_ATTR).items(): + if k in mappings and mappings[k] != v: + fail(("duplicate module mapping at %s: %s maps to both %s and %s" % + (target.label, k, mappings[k], v)), "deps") + mappings[k] = v + if ((hasattr(ctx.rule.attr, "module_name") and ctx.rule.attr.module_name) or + (hasattr(ctx.rule.attr, "module_root") and ctx.rule.attr.module_root)): + mn = ctx.rule.attr.module_name + if not mn: + mn = target.label.name + mr = target.label.package + if target.label.workspace_root: + mr = "%s/%s" % (target.label.workspace_root, mr) + if ctx.rule.attr.module_root and ctx.rule.attr.module_root != ".": + if ctx.rule.attr.module_root.endswith(".ts"): + # This is the type-checking module mapping. Strip the trailing .d.ts + # as it doesn't belong in TypeScript's path mapping. + mr = "%s/%s" % (mr, ctx.rule.attr.module_root.replace(".d.ts", "")) + else: + mr = "%s/%s" % (mr, ctx.rule.attr.module_root) + if mn in mappings and mappings[mn] != mr: + fail(("duplicate module mapping at %s: %s maps to both %s and %s" % + (target.label, mn, mappings[mn], mr)), "deps") + mappings[mn] = mr + return struct(ng_rollup_module_mappings = mappings) + +ng_rollup_module_mappings_aspect = aspect( + _ng_rollup_module_mappings_aspect_impl, + attr_aspects = ["deps"], +) + +_NG_ROLLUP_BUNDLE_DEPS_ASPECTS = [esm5_outputs_aspect, ng_rollup_module_mappings_aspect, node_modules_aspect] + +_NG_ROLLUP_BUNDLE_ATTRS = { + "build_optimizer": attr.bool( + doc = """Use build optimizer plugin + + Only used if sources are esm5 which depends on value of esm5_sources.""", + default = True, + ), + "esm5_sources": attr.bool( + doc = """Use esm5 input sources""", + default = True, + ), + "srcs": attr.label_list( + doc = """JavaScript source files from the workspace. + These can use ES2015 syntax and ES Modules (import/export)""", + allow_files = True, + ), + "entry_point": attr.label( + doc = """The starting point of the application, passed as the `--input` flag to rollup. + + If the entry JavaScript file belongs to the same package (as the BUILD file), + you can simply reference it by its relative name to the package directory: + + ``` + ng_rollup_bundle( + name = "bundle", + entry_point = ":main.js", + ) + ``` + + You can specify the entry point as a typescript file so long as you also include + the ts_library target in deps: + + ``` + ts_library( + name = "main", + srcs = ["main.ts"], + ) + + ng_rollup_bundle( + name = "bundle", + deps = [":main"] + entry_point = ":main.ts", + ) + ``` + + The rule will use the corresponding `.js` output of the ts_library rule as the entry point. + + If the entry point target is a rule, it should produce a single JavaScript entry file that will be passed to the nodejs_binary rule. + For example: + + ``` + filegroup( + name = "entry_file", + srcs = ["main.js"], + ) + + ng_rollup_bundle( + name = "bundle", + entry_point = ":entry_file", + ) + ``` + """, + mandatory = True, + allow_single_file = True, + ), + "deps": attr.label_list( + doc = """Other targets that provide JavaScript files. + Typically this will be `ts_library` or `ng_module` targets.""", + aspects = _NG_ROLLUP_BUNDLE_DEPS_ASPECTS, + ), + "format": attr.string( + doc = """"Specifies the format of the generated bundle. One of the following: + +- `amd`: Asynchronous Module Definition, used with module loaders like RequireJS +- `cjs`: CommonJS, suitable for Node and other bundlers +- `esm`: Keep the bundle as an ES module file, suitable for other bundlers and inclusion as a `