feat(bazel): support ts_library targets as entry-points for ng_package (#32610)
Within an Angular package, it can happen that there are entry-points which do not contain features that belong into an `@NgModule` or need metadata files to be generated. For example: the `cdk`, `cdk/testing` and `cdk/coercion` entry-points. Besides other entry-points in the `cdk` package, those entry-points do not need metadata to be generated and no not use the `ng_module` rule. Currently the "ng_package" rule properly picks up such entry-points and builds bundles, does downleveling etc. The only thing it misses is that no `package.json` files are generated for the entry-point. This means that consumers will not be able to use these entry-points built with "ts_library" (except accessing the individual bundlings explicitly). The "ng_package" rule should follow the full APF specification for such entry-points. Partially building bundles and doing the downleveling is confusing and a breaking issue. The motifivation of supporting this (besides making the rule behavior consistent; the incomplete output is not acceptable), is that using the "ng_module" rule does not make sense to be used for non-Angular entry-points. Especially since it depends on Angular packages to be specified as Bazel action inputs just to compile vanilla TypeScript with `@angular/compiler-cli`. PR Close #32610
This commit is contained in:

committed by
Kara Erickson

parent
be13e8b1e4
commit
217db9b216
@ -33,6 +33,11 @@ load("//packages/bazel/src:external.bzl", "FLAT_DTS_FILE_SUFFIX")
|
||||
load("//packages/bazel/src:esm5.bzl", "esm5_outputs_aspect", "esm5_root_dir", "flatten_esm5")
|
||||
load("//packages/bazel/src/ng_package:collect-type-definitions.bzl", "collect_type_definitions")
|
||||
|
||||
# Prints a debug message if "--define=VERBOSE_LOGS=true" is specified.
|
||||
def _debug(vars, *args):
|
||||
if "VERBOSE_LOGS" in vars.keys():
|
||||
print("[ng_package.bzl]", *args)
|
||||
|
||||
_DEFAULT_NG_PACKAGER = "@npm//@angular/bazel/bin:packager"
|
||||
|
||||
# Convert from some-dash-case to someCamelCase
|
||||
@ -98,7 +103,7 @@ 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", package_name = "", include_tslib = False):
|
||||
def _rollup(ctx, bundle_name, rollup_config, entry_point, inputs, js_output, format = "es", module_name = "", include_tslib = False):
|
||||
map_output = ctx.actions.declare_file(js_output.basename + ".map", sibling = js_output)
|
||||
|
||||
args = ctx.actions.args()
|
||||
@ -107,9 +112,9 @@ def _rollup(ctx, bundle_name, rollup_config, entry_point, inputs, js_output, for
|
||||
args.add("--input", entry_point)
|
||||
args.add("--output.file", js_output)
|
||||
args.add("--output.format", format)
|
||||
if package_name:
|
||||
args.add("--output.name", _global_name(package_name))
|
||||
args.add("--amd.id", package_name)
|
||||
if module_name:
|
||||
args.add("--output.name", _global_name(module_name))
|
||||
args.add("--amd.id", module_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.
|
||||
@ -228,22 +233,70 @@ def _ng_package_impl(ctx):
|
||||
# - ng_module rules in the deps (they have an "angular" provider)
|
||||
# - in this package or a subpackage
|
||||
# - those that have a module_name attribute (they produce flat module metadata)
|
||||
flat_module_metadata = []
|
||||
collected_entry_points = []
|
||||
|
||||
# Name given in the package.json name field, eg. @angular/core/testing
|
||||
package_name = ""
|
||||
deps_in_package = [d for d in ctx.attr.deps if d.label.package.startswith(ctx.label.package)]
|
||||
for dep in deps_in_package:
|
||||
# Module name of the current entry-point. eg. @angular/core/testing
|
||||
module_name = ""
|
||||
|
||||
# Intentionally evaluates to empty string for the main entry point
|
||||
entry_point = dep.label.package[len(ctx.label.package) + 1:]
|
||||
|
||||
# Extract the "module_name" from either "ts_library" or "ng_module". Both
|
||||
# set the "module_name" in the provider struct.
|
||||
if hasattr(dep, "module_name"):
|
||||
package_name = dep.module_name
|
||||
module_name = dep.module_name
|
||||
|
||||
if hasattr(dep, "angular") and hasattr(dep.angular, "flat_module_metadata"):
|
||||
flat_module_metadata.append(dep.angular.flat_module_metadata)
|
||||
flat_module_out_file = dep.angular.flat_module_metadata.flat_module_out_file + ".js"
|
||||
# For dependencies which are built using the "ng_module" with flat module bundles
|
||||
# enabled, we determine the module name, the flat module index file, the metadata
|
||||
# file and the typings entry point from the flat module metadata which is set by
|
||||
# the "ng_module" rule.
|
||||
ng_module_metadata = dep.angular.flat_module_metadata
|
||||
module_name = ng_module_metadata.module_name
|
||||
index_file = ng_module_metadata.flat_module_out_file + ".js"
|
||||
typings_path = ng_module_metadata.typings_file.path
|
||||
metadata_file = ng_module_metadata.metadata_file
|
||||
guessed_paths = False
|
||||
_debug(
|
||||
ctx.var,
|
||||
"entry-point %s is built using a flat module bundle." % dep,
|
||||
"using %s as main file of the entry-point" % index_file,
|
||||
)
|
||||
else:
|
||||
# In case the dependency is built through the "ts_library" rule, or the "ng_module"
|
||||
# rule does not generate a flat module bundle, we determine the index file and
|
||||
# typings entry-point through the most reasonable defaults (i.e. "package/index").
|
||||
output_dir = "/".join([
|
||||
p
|
||||
for p in [
|
||||
ctx.bin_dir.path,
|
||||
ctx.label.package,
|
||||
entry_point,
|
||||
]
|
||||
if p
|
||||
])
|
||||
|
||||
# fallback to a reasonable default
|
||||
flat_module_out_file = "index.js"
|
||||
index_file = "index.js"
|
||||
typings_path = "%s/index.d.ts" % output_dir
|
||||
metadata_file = None
|
||||
guessed_paths = True
|
||||
_debug(
|
||||
ctx.var,
|
||||
"entry-point %s does not have flat module metadata." % dep,
|
||||
"guessing %s as main file of the entry-point" % index_file,
|
||||
)
|
||||
|
||||
# Store the collected entry point in a list of all entry-points. This
|
||||
# can be later passed to the packager as a manifest.
|
||||
collected_entry_points.append(struct(
|
||||
module_name = module_name,
|
||||
typings_path = typings_path,
|
||||
metadata_file = metadata_file,
|
||||
guessed_paths = guessed_paths,
|
||||
))
|
||||
|
||||
if hasattr(dep, "dts_bundles"):
|
||||
bundled_type_definitions += dep.dts_bundles
|
||||
@ -258,8 +311,8 @@ def _ng_package_impl(ctx):
|
||||
).to_list()
|
||||
|
||||
if len(type_definitions) > 0 and len(bundled_type_definitions) > 0:
|
||||
# bundle_dts needs to be enabled/disabled for all ng module packages.
|
||||
fail("Expected all or none of the 'ng_module' dependencies to have 'bundle_dts' enabled.")
|
||||
# bundle_dts needs to be enabled/disabled for all entry points.
|
||||
fail("Expected all or none of the entry points to have 'bundle_dts' enabled.")
|
||||
|
||||
es2015_entry_point = "/".join([p for p in [
|
||||
ctx.bin_dir.path,
|
||||
@ -267,13 +320,13 @@ def _ng_package_impl(ctx):
|
||||
_esm2015_root_dir(ctx),
|
||||
ctx.label.package,
|
||||
entry_point,
|
||||
flat_module_out_file,
|
||||
index_file,
|
||||
] if p])
|
||||
|
||||
es5_entry_point = "/".join([p for p in [
|
||||
ctx.label.package,
|
||||
entry_point,
|
||||
flat_module_out_file,
|
||||
index_file,
|
||||
] if p])
|
||||
|
||||
if entry_point:
|
||||
@ -314,8 +367,8 @@ def _ng_package_impl(ctx):
|
||||
es5_entry_point,
|
||||
esm5_rollup_inputs,
|
||||
umd_output,
|
||||
module_name = module_name,
|
||||
format = "umd",
|
||||
package_name = package_name,
|
||||
include_tslib = True,
|
||||
),
|
||||
)
|
||||
@ -349,12 +402,17 @@ def _ng_package_impl(ctx):
|
||||
# Marshal the metadata into a JSON string so we can parse the data structure
|
||||
# in the TypeScript program easily.
|
||||
metadata_arg = {}
|
||||
for m in flat_module_metadata:
|
||||
packager_inputs.extend([m.metadata_file])
|
||||
for m in collected_entry_points:
|
||||
if m.metadata_file:
|
||||
packager_inputs.extend([m.metadata_file])
|
||||
metadata_arg[m.module_name] = {
|
||||
"index": m.typings_file.path.replace(".d.ts", ".js"),
|
||||
"typings": m.typings_file.path,
|
||||
"metadata": m.metadata_file.path,
|
||||
"index": m.typings_path.replace(".d.ts", ".js"),
|
||||
"typings": m.typings_path,
|
||||
# Metadata can be undefined if entry point is built with "ts_library".
|
||||
"metadata": m.metadata_file.path if m.metadata_file else "",
|
||||
# If the paths for that entry-point were guessed (e.g. "ts_library" rule or
|
||||
# "ng_module" without flat module bundle), we pass this information to the packager.
|
||||
"guessedPaths": "true" if m.guessed_paths else "",
|
||||
}
|
||||
packager_args.add(str(metadata_arg))
|
||||
|
||||
|
@ -178,9 +178,14 @@ function main(args: string[]): number {
|
||||
moduleFiles['esm5_index'] = path.join(binDir, 'esm5', relative);
|
||||
moduleFiles['esm2015_index'] = path.join(binDir, 'esm2015', relative);
|
||||
|
||||
// Metadata file is optional as entry-points can be also built
|
||||
// with the "ts_library" rule.
|
||||
const metadataFile = moduleFiles['metadata'];
|
||||
const typingsOutFile = moduleFiles['typings'];
|
||||
if (!metadataFile) {
|
||||
return;
|
||||
}
|
||||
|
||||
const typingsOutFile = moduleFiles['typings'];
|
||||
// We only support all modules within a package to be dts bundled
|
||||
// ie: if @angular/common/http has flat dts, so should @angular/common
|
||||
if (dtsBundles.length) {
|
||||
@ -220,7 +225,7 @@ function main(args: string[]): number {
|
||||
// Modify package.json files as necessary for publishing
|
||||
if (path.basename(src) === 'package.json') {
|
||||
const packageJson = JSON.parse(content);
|
||||
content = amendPackageJson(src, packageJson);
|
||||
content = amendPackageJson(src, packageJson, false);
|
||||
|
||||
const packageName = packageJson['name'];
|
||||
packagesWithExistingPackageJson.add(packageName);
|
||||
@ -240,8 +245,13 @@ function main(args: string[]): number {
|
||||
const entryPointName = entryPointPackageName.substr(rootPackageName.length + 1);
|
||||
if (!entryPointName) return;
|
||||
|
||||
createMetadataReexportFile(
|
||||
entryPointName, modulesManifest[entryPointPackageName]['metadata'], entryPointPackageName);
|
||||
const metadataFilePath = modulesManifest[entryPointPackageName]['metadata'];
|
||||
if (metadataFilePath) {
|
||||
createMetadataReexportFile(
|
||||
entryPointName, modulesManifest[entryPointPackageName]['metadata'],
|
||||
entryPointPackageName);
|
||||
}
|
||||
|
||||
createTypingsReexportFile(
|
||||
entryPointName, licenseBanner, modulesManifest[entryPointPackageName]['typings']);
|
||||
|
||||
@ -291,11 +301,19 @@ function main(args: string[]): number {
|
||||
*
|
||||
* @param packageJson The path to the package.json file.
|
||||
* @param parsedPackage Parsed package.json content
|
||||
* @param isGeneratedPackageJson Whether the passed package.json has been generated.
|
||||
*/
|
||||
function amendPackageJson(packageJson: string, parsedPackage: {[key: string]: string}) {
|
||||
function amendPackageJson(
|
||||
packageJson: string, parsedPackage: {[key: string]: string},
|
||||
isGeneratedPackageJson: boolean) {
|
||||
const packageName = parsedPackage['name'];
|
||||
const moduleFiles = modulesManifest[packageName];
|
||||
if (!moduleFiles) {
|
||||
const moduleData = modulesManifest[packageName];
|
||||
|
||||
// We don't want to modify the "package.json" if we guessed the entry-point
|
||||
// paths and there is a custom "package.json" for that package already. Module
|
||||
// data will be only undefined if the package name comes from a non-generated
|
||||
// "package.json". In that case we want to leave the file untouched as well.
|
||||
if (!moduleData || moduleData.guessedPaths && !isGeneratedPackageJson) {
|
||||
// Ideally we should throw here, as we got an entry point that doesn't
|
||||
// have flat module metadata / bundle index, so it may have been an
|
||||
// ng_module that's missing a module_name attribute.
|
||||
@ -316,9 +334,9 @@ function main(args: string[]): number {
|
||||
parsedPackage['fesm5'] = getBundleName(packageName, 'fesm5');
|
||||
parsedPackage['fesm2015'] = getBundleName(packageName, 'fesm2015');
|
||||
|
||||
parsedPackage['esm5'] = srcDirRelative(packageJson, moduleFiles['esm5_index']);
|
||||
parsedPackage['esm2015'] = srcDirRelative(packageJson, moduleFiles['esm2015_index']);
|
||||
parsedPackage['typings'] = srcDirRelative(packageJson, moduleFiles['typings']);
|
||||
parsedPackage['esm5'] = srcDirRelative(packageJson, moduleData['esm5_index']);
|
||||
parsedPackage['esm2015'] = srcDirRelative(packageJson, moduleData['esm2015_index']);
|
||||
parsedPackage['typings'] = srcDirRelative(packageJson, moduleData['typings']);
|
||||
|
||||
// For now, we point the primary entry points at the fesm files, because of Webpack
|
||||
// performance issues with a large number of individual files.
|
||||
@ -382,7 +400,7 @@ export * from '${srcDirRelative(inputPath, typingsFile.replace(/\.d\.tsx?$/, '')
|
||||
*/
|
||||
function createEntryPointPackageJson(dir: string, entryPointPackageName: string) {
|
||||
const pkgJson = path.join(srcDir, dir, 'package.json');
|
||||
const content = amendPackageJson(pkgJson, {name: entryPointPackageName});
|
||||
const content = amendPackageJson(pkgJson, {name: entryPointPackageName}, true);
|
||||
writeFileFromInputPath(pkgJson, content);
|
||||
}
|
||||
|
||||
@ -444,4 +462,4 @@ export function newArray<T>(size: number, value?: T): T[] {
|
||||
list.push(value !);
|
||||
}
|
||||
return list;
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user