From 48a62c9597a7fd8c9b80d2de91701f8250d2e003 Mon Sep 17 00:00:00 2001 From: Greg Magolan Date: Tue, 22 Oct 2019 16:17:32 -0700 Subject: [PATCH] build: remove deps on legacy nodejs rules rollup_bundle internals The legacy nodejs rules rollup_bundle is now deprecated and will be removed in the nodejs rules 1.0 release due in mid-November. This PR brings in the rules_nodejs internal API deps that ng_rollup_bundle, ng_package and ls_rollup_bundle depend on into this repo to break the dependency. In the future these rules should switch to use the new rollup_bundle via a macro as done in https://github.com/angular/angular/pull/33329 but this is not possible right now due to the complication of having esm5 re-rooted ts_library dependencies. The es6 sources now have .mjs extensions so they no longer need to be re-rooted to `{package}.es6`. This eliminates the need for the collect_es6_sources() function. --- package.json | 1 + packages/bazel/docs/BUILD.bazel | 1 - packages/bazel/src/BUILD.bazel | 17 - packages/bazel/src/ng_package/BUILD.bazel | 12 +- packages/bazel/src/ng_package/ng_package.bzl | 338 +++++++++++-- packages/bazel/src/ng_package/packager.ts | 29 +- .../bazel/src/ng_package/rollup.config.js | 160 ++++++ .../src/ng_package/terser_config.default.json | 12 + packages/bazel/src/ng_rollup_bundle.bzl | 229 --------- .../test/ng_package/example_package.golden | 8 +- .../example_with_ts_library_package.golden | 4 +- .../test/bundling/cyclic_import/BUILD.bazel | 2 +- .../test/bundling/hello_world/BUILD.bazel | 2 +- packages/language-service/BUILD.bazel | 3 +- packages/language-service/bundles/BUILD.bazel | 2 +- packages/language-service/bundles/rollup.bzl | 65 --- tools/defaults.bzl | 6 +- tools/ng_rollup_bundle/BUILD.bazel | 22 + tools/ng_rollup_bundle/ng_rollup_bundle.bzl | 457 ++++++++++++++++++ tools/ng_rollup_bundle/rollup.config.js | 177 +++++++ yarn.lock | 5 + 21 files changed, 1173 insertions(+), 379 deletions(-) create mode 100644 packages/bazel/src/ng_package/rollup.config.js create mode 100644 packages/bazel/src/ng_package/terser_config.default.json delete mode 100644 packages/bazel/src/ng_rollup_bundle.bzl delete mode 100644 packages/language-service/bundles/rollup.bzl create mode 100644 tools/ng_rollup_bundle/BUILD.bazel create mode 100644 tools/ng_rollup_bundle/ng_rollup_bundle.bzl create mode 100644 tools/ng_rollup_bundle/rollup.config.js diff --git a/package.json b/package.json index b09e2ebc3e56dc..7d524be70a7e41 100644 --- a/package.json +++ b/package.json @@ -44,6 +44,7 @@ "@bazel/jasmine": "0.38.3", "@bazel/karma": "0.38.3", "@bazel/protractor": "0.38.3", + "@bazel/terser": "0.38.3", "@bazel/typescript": "0.38.3", "@microsoft/api-extractor": "^7.3.9", "@schematics/angular": "^8.0.0-beta.15", diff --git a/packages/bazel/docs/BUILD.bazel b/packages/bazel/docs/BUILD.bazel index 2e32d61ec17f1c..55b84494d89db8 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/src/BUILD.bazel b/packages/bazel/src/BUILD.bazel index 16121e38c62863..aca43575aed9ee 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//:defs.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 48a1cc03afd126..535ea3a4bcad8d 100644 --- a/packages/bazel/src/ng_package/BUILD.bazel +++ b/packages/bazel/src/ng_package/BUILD.bazel @@ -27,9 +27,17 @@ nodejs_binary( install_source_map_support = False, ) -exports_files(["ng_package.bzl"]) +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 cfd7790970d1cb..b3de955b89a2d8 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//rollup/bin:rollup" +_DEFAULT_TERSER = "@npm//terser/bin:terser" + +def _rollup_module_mappings_aspect_impl(target, ctx): + mappings = dict() + for dep in ctx.rule.attr.deps: + if hasattr(dep, "es6_module_mappings"): + for k, v in getattr(dep, "es6_module_mappings").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(rollup_module_mappings = mappings) + +rollup_module_mappings_aspect = aspect( + _rollup_module_mappings_aspect_impl, + attr_aspects = ["deps"], +) + +_ROLLUP_MODULE_MAPPINGS_ATTR = "rollup_module_mappings" # 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, _ROLLUP_MODULE_MAPPINGS_ATTR): + for k, v in getattr(dep, _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" % + (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, rollup_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 97d075b6a98ea0..c4f17fb77f22c5 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 00000000000000..66cb42e8fbaf98 --- /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 00000000000000..7fe029be719efc --- /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 46f9641eade3a8..00000000000000 --- 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 d19653a4579191..82641371f16ef7 100644 --- a/packages/bazel/test/ng_package/example_package.golden +++ b/packages/bazel/test/ng_package/example_package.golden @@ -246,7 +246,7 @@ Hello for (var a = arguments[i], j = 0, jl = a.length; j < jl; j++, k++) r[k] = a[j]; return r; - }; + } function __await(v) { return this instanceof __await ? (this.v = v, this) : new __await(v); @@ -281,7 +281,7 @@ Hello function __makeTemplateObject(cooked, raw) { if (Object.defineProperty) { Object.defineProperty(cooked, "raw", { value: raw }); } else { cooked.raw = raw; } return cooked; - }; + } function __importStar(mod) { if (mod && mod.__esModule) return mod; @@ -520,7 +520,7 @@ e.SecondaryModule=o,e.a=1,Object.defineProperty(e,"__esModule",{value:!0})}); for (var a = arguments[i], j = 0, jl = a.length; j < jl; j++, k++) r[k] = a[j]; return r; - }; + } function __await(v) { return this instanceof __await ? (this.v = v, this) : new __await(v); @@ -555,7 +555,7 @@ e.SecondaryModule=o,e.a=1,Object.defineProperty(e,"__esModule",{value:!0})}); function __makeTemplateObject(cooked, raw) { if (Object.defineProperty) { Object.defineProperty(cooked, "raw", { value: raw }); } else { cooked.raw = raw; } return cooked; - }; + } function __importStar(mod) { if (mod && mod.__esModule) return mod; 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 0528d83c5114ce..a6f9eb7aa7a6d8 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 @@ -234,7 +234,7 @@ License: MIT for (var a = arguments[i], j = 0, jl = a.length; j < jl; j++, k++) r[k] = a[j]; return r; - }; + } function __await(v) { return this instanceof __await ? (this.v = v, this) : new __await(v); @@ -269,7 +269,7 @@ License: MIT function __makeTemplateObject(cooked, raw) { if (Object.defineProperty) { Object.defineProperty(cooked, "raw", { value: raw }); } else { cooked.raw = raw; } return cooked; - }; + } function __importStar(mod) { if (mod && mod.__esModule) return mod; diff --git a/packages/core/test/bundling/cyclic_import/BUILD.bazel b/packages/core/test/bundling/cyclic_import/BUILD.bazel index d6c5b1d2283ad2..3d285fde279e7f 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 c22a6cf43202ff..488ad2d7963428 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 eba37455dba5c3..db9d9e1229d239 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 7ddb1345de1604..cfb20da7b5cd51 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 73f1cc207bfa7c..00000000000000 --- 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 8012abef686495..f357c44753320e 100644 --- a/tools/defaults.bzl +++ b/tools/defaults.bzl @@ -5,7 +5,7 @@ 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" @@ -13,6 +13,8 @@ _INTERNAL_NG_MODULE_API_EXTRACTOR = "//packages/bazel/src/api-extractor:api_extr _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_PACKAGER_DEFALUT_TERSER_CONFIG_FILE = "//packages/bazel/src/ng_package:terser_config.default.json" +_INTERNAL_NG_PACKAGER_DEFAULT_ROLLUP_CONFIG_TMPL = "//packages/bazel/src/ng_package:rollup.config.js" # Packages which are versioned together on npm ANGULAR_SCOPED_PACKAGES = ["@angular/%s" % p for p in [ @@ -146,6 +148,8 @@ def ng_package(name, readme_md = None, license_banner = None, deps = [], **kwarg license_banner = license_banner, replacements = PKG_GROUP_REPLACEMENTS, ng_packager = _INTERNAL_NG_PACKAGER_PACKAGER, + terser_config_file = _INTERNAL_NG_PACKAGER_DEFALUT_TERSER_CONFIG_FILE, + rollup_config_tmpl = _INTERNAL_NG_PACKAGER_DEFAULT_ROLLUP_CONFIG_TMPL, **kwargs ) diff --git a/tools/ng_rollup_bundle/BUILD.bazel b/tools/ng_rollup_bundle/BUILD.bazel new file mode 100644 index 00000000000000..3b17da0834d809 --- /dev/null +++ b/tools/ng_rollup_bundle/BUILD.bazel @@ -0,0 +1,22 @@ +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//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"], +) 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 00000000000000..5d2062c34dbc11 --- /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", +} + +def _rollup_module_mappings_aspect_impl(target, ctx): + mappings = dict() + for dep in ctx.rule.attr.deps: + if hasattr(dep, "es6_module_mappings"): + for k, v in getattr(dep, "es6_module_mappings").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(rollup_module_mappings = mappings) + +rollup_module_mappings_aspect = aspect( + _rollup_module_mappings_aspect_impl, + attr_aspects = ["deps"], +) + +_ROLLUP_MODULE_MAPPINGS_ATTR = "rollup_module_mappings" + +_NG_ROLLUP_BUNDLE_DEPS_ASPECTS = [esm5_outputs_aspect, 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 `