From 5f52ea3d06d0d5a4d12e4e0a075d6258c74952e7 Mon Sep 17 00:00:00 2001 From: Alex Eagle Date: Tue, 13 Feb 2018 11:26:06 -0800 Subject: [PATCH] feat(bazel): ng_module produces bundle index (#22176) It creates the bundle index .d.ts and .metadata.json files. The names are based on the ng_module target. PR Close #22176 --- WORKSPACE | 20 +++++--- packages/bazel/src/BUILD.bazel | 32 +++++++++++- packages/bazel/src/ng_module.bzl | 49 +++++++++++++++++++ packages/compiler-cli/BUILD.bazel | 1 - .../bazel/ng_module/BUILD.bazel | 23 +++++++++ .../integrationtest/bazel/ng_module/child.ts | 14 ++++++ .../ng_module/extract_flat_module_index.bzl | 20 ++++++++ .../integrationtest/bazel/ng_module/index.ts | 9 ++++ .../integrationtest/bazel/ng_module/parent.ts | 13 +++++ .../integrationtest/bazel/ng_module/spec.js | 31 ++++++++++++ .../src/metadata/bundle_index_main.ts | 43 ++++++++++++++++ packages/compiler-cli/src/metadata/bundler.ts | 1 + 12 files changed, 247 insertions(+), 9 deletions(-) create mode 100644 packages/compiler-cli/integrationtest/bazel/ng_module/BUILD.bazel create mode 100644 packages/compiler-cli/integrationtest/bazel/ng_module/child.ts create mode 100644 packages/compiler-cli/integrationtest/bazel/ng_module/extract_flat_module_index.bzl create mode 100644 packages/compiler-cli/integrationtest/bazel/ng_module/index.ts create mode 100644 packages/compiler-cli/integrationtest/bazel/ng_module/parent.ts create mode 100644 packages/compiler-cli/integrationtest/bazel/ng_module/spec.js create mode 100644 packages/compiler-cli/src/metadata/bundle_index_main.ts diff --git a/WORKSPACE b/WORKSPACE index 1beda0c030..57ce53f7c9 100644 --- a/WORKSPACE +++ b/WORKSPACE @@ -1,11 +1,14 @@ workspace(name = "angular") -load("@bazel_tools//tools/build_defs/repo:git.bzl", "git_repository") +# Using a pre-release snapshot to pick up a commit that makes all nodejs_binary +# programs produce source-mapped stack traces. +RULES_NODEJS_VERSION = "926349cea4cd360afcd5647ccdd09d2d2fb471aa" -git_repository( +http_archive( name = "build_bazel_rules_nodejs", - remote = "https://github.com/bazelbuild/rules_nodejs.git", - commit = "230d39a391226f51c03448f91eb61370e2e58c42", + url = "https://github.com/bazelbuild/rules_nodejs/archive/%s.zip" % RULES_NODEJS_VERSION, + strip_prefix = "rules_nodejs-%s" % RULES_NODEJS_VERSION, + sha256 = "5ba3c8c209078c2e3f0c6aa4abd01a1a561f92a5bfda04e25604af5f4734d69d", ) load("@build_bazel_rules_nodejs//:defs.bzl", "check_bazel_version", "node_repositories") @@ -13,10 +16,13 @@ load("@build_bazel_rules_nodejs//:defs.bzl", "check_bazel_version", "node_reposi check_bazel_version("0.9.0") node_repositories(package_json = ["//:package.json"]) -git_repository( +RULES_TYPESCRIPT_VERSION = "0.10.1" + +http_archive( name = "build_bazel_rules_typescript", - remote = "https://github.com/bazelbuild/rules_typescript.git", - commit = "d3ad16d1f105e2490859da9ad528ba4c45991d09" + url = "https://github.com/bazelbuild/rules_typescript/archive/%s.zip" % RULES_TYPESCRIPT_VERSION, + strip_prefix = "rules_typescript-%s" % RULES_TYPESCRIPT_VERSION, + sha256 = "a2c81776a4a492ff9f878f9705639f5647bef345f7f3e1da09c9eeb8dec80485", ) load("@build_bazel_rules_typescript//:defs.bzl", "ts_setup_workspace") diff --git a/packages/bazel/src/BUILD.bazel b/packages/bazel/src/BUILD.bazel index 00301d3053..5234f7f060 100644 --- a/packages/bazel/src/BUILD.bazel +++ b/packages/bazel/src/BUILD.bazel @@ -1 +1,31 @@ -# Empty marker file, indicating this directory is a Bazel package. +package(default_visibility = ["//visibility:public"]) + +load("@build_bazel_rules_nodejs//:defs.bzl", "nodejs_binary") + +nodejs_binary( + name = "rollup_with_build_optimizer", + data = ["@angular_devkit//packages/angular_devkit/build_optimizer:lib"], + # Since our rule extends the one in rules_nodejs, we use the same runtime + # dependency @build_bazel_rules_nodejs_rollup_deps. We don't need any + # additional npm dependencies when we run rollup or uglify. + entry_point = "build_bazel_rules_nodejs_rollup_deps/node_modules/rollup/bin/rollup", + node_modules = "@build_bazel_rules_nodejs_rollup_deps//:node_modules", +) + +nodejs_binary( + name = "modify_tsconfig", + data = ["modify_tsconfig.js"], + entry_point = "angular/packages/bazel/src/modify_tsconfig.js", +) + +nodejs_binary( + name = "index_bundler", + data = [ + # BEGIN-INTERNAL + # Only needed when compiling within the Angular repo. + # Users will get this dependency from node_modules. + "//packages/compiler-cli", + # END-INTERNAL + ], + entry_point = "@angular/compiler-cli/src/metadata/bundle_index_main.js", +) diff --git a/packages/bazel/src/ng_module.bzl b/packages/bazel/src/ng_module.bzl index 67745b0953..931624c9d4 100644 --- a/packages/bazel/src/ng_module.bzl +++ b/packages/bazel/src/ng_module.bzl @@ -12,6 +12,7 @@ load(":rules_typescript.bzl", "compile_ts", "DEPS_ASPECTS", "ts_providers_dict_to_struct", + "json_marshal", ) # Calculate the expected output of the template compiler for every source in @@ -220,6 +221,43 @@ def _ts_expected_outs(ctx, label): _ignored = [label] return _expected_outs(ctx) +def _write_bundle_index(ctx): + basename = "_%s.bundle_index" % ctx.label.name + tsconfig_file = ctx.actions.declare_file("%s.tsconfig.json" % basename) + metadata_file = ctx.actions.declare_file("%s.metadata.json" % basename) + tstyping_file = ctx.actions.declare_file("%s.d.ts" % basename) + + tsconfig = dict(tsc_wrapped_tsconfig(ctx, ctx.files.srcs, ctx.files.srcs), **{ + "angularCompilerOptions": { + "flatModuleOutFile": basename, + }, + }) + if ctx.attr.module_name: + tsconfig["angularCompilerOptions"]["flatModuleId"] = ctx.attr.module_name + + # createBundleIndexHost in bundle_index_host.ts will throw if the "files" has more than one entry. + # We don't want to fail() here, however, because not all ng_module's will have the bundle index written. + # So we make the assumption that the index.ts file in the highest parent directory is the entry point. + index_file = None + for f in tsconfig["files"]: + if f.endswith("/index.ts"): + if not index_file or len(f) < len(index_file): + index_file = f + tsconfig["files"] = [index_file] + + ctx.actions.write(tsconfig_file, json_marshal(tsconfig)) + + outputs = [metadata_file, tstyping_file] + + ctx.action( + progress_message = "Producing metadata for bundle %s" % ctx.label.name, + executable = ctx.executable._index_bundler, + inputs = ctx.files.srcs + [tsconfig_file], + outputs = outputs, + arguments = ["-p", tsconfig_file.path], + ) + return outputs + def ng_module_impl(ctx, ts_compile_actions): """Implementation function for the ng_module rule. @@ -247,6 +285,13 @@ def ng_module_impl(ctx, ts_compile_actions): } providers["ngc_messages"] = outs.i18n_messages + # Only produces the flattened "index bundle" metadata when requested by some other rule + # and only under Bazel + if hasattr(ctx.executable, "_index_bundler"): + bundle_index_metadata = _write_bundle_index(ctx) + # note, not recursive + providers["angular"]["flat_module_metadata"] = depset(bundle_index_metadata) + return providers def _ng_module_impl(ctx): @@ -291,6 +336,10 @@ ng_module = rule( "node_modules": attr.label( default = Label("@//:node_modules") ), + "_index_bundler": attr.label( + executable = True, + cfg = "host", + default = Label("//packages/bazel/src:index_bundler")), }), outputs = COMMON_OUTPUTS, ) diff --git a/packages/compiler-cli/BUILD.bazel b/packages/compiler-cli/BUILD.bazel index 43547a0624..1a6d5282d8 100644 --- a/packages/compiler-cli/BUILD.bazel +++ b/packages/compiler-cli/BUILD.bazel @@ -17,7 +17,6 @@ ts_library( ], exclude = [ "src/extract_i18n.ts", - "src/main.ts", "src/integrationtest/**/*.ts", ], ), diff --git a/packages/compiler-cli/integrationtest/bazel/ng_module/BUILD.bazel b/packages/compiler-cli/integrationtest/bazel/ng_module/BUILD.bazel new file mode 100644 index 0000000000..bc070a3baa --- /dev/null +++ b/packages/compiler-cli/integrationtest/bazel/ng_module/BUILD.bazel @@ -0,0 +1,23 @@ +load("//packages/bazel:index.bzl", "ng_module") + +ng_module( + name = "test_module", + srcs = glob(["*.ts"]), + module_name = "some_npm_module", + deps = ["//packages/core"], +) + +load(":extract_flat_module_index.bzl", "extract_flat_module_index") + +extract_flat_module_index( + name = "flat_module_index", + deps = [":test_module"], +) + +load("@build_bazel_rules_nodejs//:defs.bzl", "jasmine_node_test") + +jasmine_node_test( + name = "test", + srcs = ["spec.js"], + data = [":flat_module_index"], +) diff --git a/packages/compiler-cli/integrationtest/bazel/ng_module/child.ts b/packages/compiler-cli/integrationtest/bazel/ng_module/child.ts new file mode 100644 index 0000000000..019518f629 --- /dev/null +++ b/packages/compiler-cli/integrationtest/bazel/ng_module/child.ts @@ -0,0 +1,14 @@ +/** + * @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 + */ + +import {NgModule} from '@angular/core'; +import {Parent} from './parent'; + +@NgModule({imports: [Parent]}) +export class Child { +} diff --git a/packages/compiler-cli/integrationtest/bazel/ng_module/extract_flat_module_index.bzl b/packages/compiler-cli/integrationtest/bazel/ng_module/extract_flat_module_index.bzl new file mode 100644 index 0000000000..e15f4539a9 --- /dev/null +++ b/packages/compiler-cli/integrationtest/bazel/ng_module/extract_flat_module_index.bzl @@ -0,0 +1,20 @@ +# 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 +"""Test utility to extract the "flat_module_metadata" from transitive Angular deps. +""" + +def _extract_flat_module_index(ctx): + return [DefaultInfo(files = depset(transitive = [ + dep.angular.flat_module_metadata + for dep in ctx.attr.deps + if hasattr(dep, "angular") + ]))] + +extract_flat_module_index = rule( + implementation = _extract_flat_module_index, + attrs = { + "deps": attr.label_list(), + }, +) diff --git a/packages/compiler-cli/integrationtest/bazel/ng_module/index.ts b/packages/compiler-cli/integrationtest/bazel/ng_module/index.ts new file mode 100644 index 0000000000..548fbc52f9 --- /dev/null +++ b/packages/compiler-cli/integrationtest/bazel/ng_module/index.ts @@ -0,0 +1,9 @@ +/** + * @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 + */ + +export * from './child'; diff --git a/packages/compiler-cli/integrationtest/bazel/ng_module/parent.ts b/packages/compiler-cli/integrationtest/bazel/ng_module/parent.ts new file mode 100644 index 0000000000..ece48ffa2d --- /dev/null +++ b/packages/compiler-cli/integrationtest/bazel/ng_module/parent.ts @@ -0,0 +1,13 @@ +/** + * @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 + */ + +import {NgModule} from '@angular/core'; + +@NgModule({}) +export class Parent { +} diff --git a/packages/compiler-cli/integrationtest/bazel/ng_module/spec.js b/packages/compiler-cli/integrationtest/bazel/ng_module/spec.js new file mode 100644 index 0000000000..99b887af49 --- /dev/null +++ b/packages/compiler-cli/integrationtest/bazel/ng_module/spec.js @@ -0,0 +1,31 @@ +/** + * @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 + */ + +const fs = require('fs'); +const PKG = 'angular/packages/compiler-cli/integrationtest/bazel/ng_module'; +describe('flat module index', () => { + describe('child metadata', () => { + it('should have contents', () => { + const metadata = fs.readFileSync( + require.resolve(`${PKG}/_test_module.bundle_index.metadata.json`), {encoding: 'utf-8'}); + expect(metadata).toContain('"__symbolic":"module"'); + expect(metadata).toContain('"__symbolic":"reference","module":"@angular/core"'); + expect(metadata).toContain('"origins":{"Child":"./child","ɵa":"./parent"}'); + expect(metadata).toContain('"importAs":"some_npm_module"'); + }); + }); + describe('child typings', () => { + it('should have contents', () => { + const dts = fs.readFileSync( + require.resolve(`${PKG}/_test_module.bundle_index.d.ts`), {encoding: 'utf-8'}); + + expect(dts).toContain('export * from \'./index\';'); + expect(dts).toContain('export { Parent as ɵa } from \'./parent\';'); + }); + }); +}); \ No newline at end of file diff --git a/packages/compiler-cli/src/metadata/bundle_index_main.ts b/packages/compiler-cli/src/metadata/bundle_index_main.ts new file mode 100644 index 0000000000..b2ed39b416 --- /dev/null +++ b/packages/compiler-cli/src/metadata/bundle_index_main.ts @@ -0,0 +1,43 @@ +#!/usr/bin/env node +/** + * @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 + */ + +// Must be imported first, because Angular decorators throw on load. +import 'reflect-metadata'; + +import * as ts from 'typescript'; +import * as path from 'path'; +import {readCommandLineAndConfiguration} from '../main'; +import {createBundleIndexHost} from './bundle_index_host'; +import * as ng from '../transformers/entry_points'; + +export function main(args: string[], consoleError: (s: string) => void = console.error): number { + const {options, rootNames} = readCommandLineAndConfiguration(args); + const host = ng.createCompilerHost({options}); + const {host: bundleHost, indexName, errors} = createBundleIndexHost(options, rootNames, host); + if (!indexName) { + console.error('Did not find an index.ts in the top-level of the package.'); + return 1; + } + // The index file is synthetic, so we have to add it to the program after parsing the tsconfig + rootNames.push(indexName); + const program = ts.createProgram(rootNames, options, bundleHost); + const indexSourceFile = program.getSourceFile(indexName); + if (!indexSourceFile) { + console.error(`${indexSourceFile} is not in the program. Please file a bug.`); + return 1; + } + program.emit(indexSourceFile); + return 0; +} + +// CLI entry point +if (require.main === module) { + const args = process.argv.slice(2); + process.exitCode = main(args); +} diff --git a/packages/compiler-cli/src/metadata/bundler.ts b/packages/compiler-cli/src/metadata/bundler.ts index b0444a96db..c3b60e8182 100644 --- a/packages/compiler-cli/src/metadata/bundler.ts +++ b/packages/compiler-cli/src/metadata/bundler.ts @@ -597,6 +597,7 @@ export class CompilerHostAdapter implements MetadataBundlerHost { constructor(private host: ts.CompilerHost) {} getMetadataFor(fileName: string): ModuleMetadata|undefined { + if (!this.host.fileExists(fileName + '.ts')) return undefined; const sourceFile = this.host.getSourceFile(fileName + '.ts', ts.ScriptTarget.Latest); return sourceFile && this.collector.getMetadata(sourceFile); }