mirror of
https://github.com/angular/angular
synced 2026-05-24 09:28:37 +00:00
build: fix linking of cdk/material in devtools and simplify code (#60516)
This commit fixes the linking of CDK/Material which recently broke because the CDK/Material package now comes with potential shared FESM chunks; that our current hard-coded, manual linking process doesn't know about. This ultimately resulted in duplicate code, breaking Material/CDK. This commit fixes that. In addition to the fix, we simplify our linking significantly and reduce the rather large complexity around linking, or having to specify every entry-point manually, by linking the full package and putting it into a different location. This is also what we conceptually are doing in Angular Material as part of the `rules_js` migration. PR Close #60516
This commit is contained in:
parent
7408a1f58b
commit
115c2f8d38
8 changed files with 159 additions and 136 deletions
|
|
@ -1,51 +0,0 @@
|
|||
_CDK_ENTRY_POINTS = [
|
||||
"scrolling",
|
||||
"tree",
|
||||
"keycodes",
|
||||
"collections",
|
||||
"overlay",
|
||||
"table",
|
||||
"text-field",
|
||||
"accordion",
|
||||
"drag-drop",
|
||||
"a11y",
|
||||
"platform",
|
||||
]
|
||||
|
||||
_MATERIAL_ENTRY_POINTS = [
|
||||
"dialog",
|
||||
"menu",
|
||||
"slide-toggle",
|
||||
"grid-list",
|
||||
"tree",
|
||||
"expansion",
|
||||
"checkbox",
|
||||
"select",
|
||||
"input",
|
||||
"button",
|
||||
"core",
|
||||
"progress-bar",
|
||||
"snack-bar",
|
||||
"icon",
|
||||
"progress-spinner",
|
||||
"tabs",
|
||||
"card",
|
||||
"form-field",
|
||||
"tooltip",
|
||||
"toolbar",
|
||||
]
|
||||
|
||||
ANGULAR_PACKAGES_CONFIG = [
|
||||
("@angular/cdk", struct(entry_points = _CDK_ENTRY_POINTS)),
|
||||
("@angular/material", struct(entry_points = _MATERIAL_ENTRY_POINTS)),
|
||||
]
|
||||
|
||||
ANGULAR_PACKAGES = [
|
||||
struct(
|
||||
name = name[len("@angular/"):],
|
||||
entry_points = config.entry_points,
|
||||
platform = config.platform if hasattr(config, "platform") else "browser",
|
||||
module_name = name,
|
||||
)
|
||||
for name, config in ANGULAR_PACKAGES_CONFIG
|
||||
]
|
||||
|
|
@ -1,3 +1,19 @@
|
|||
load("//devtools/tools/linking:index.bzl", "link_package")
|
||||
|
||||
package(default_visibility = ["//devtools:__subpackages__"])
|
||||
|
||||
exports_files([
|
||||
"bazel-karma-local-config.js",
|
||||
])
|
||||
|
||||
link_package(
|
||||
name = "angular_cdk",
|
||||
package_name = "@angular/cdk",
|
||||
npm_package = "@npm//@angular/cdk",
|
||||
)
|
||||
|
||||
link_package(
|
||||
name = "angular_material",
|
||||
package_name = "@angular/material",
|
||||
npm_package = "@npm//@angular/material",
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
load("@build_bazel_rules_nodejs//:index.bzl", "js_library")
|
||||
load("//tools:defaults.bzl", "esbuild_config")
|
||||
load(":index.bzl", "create_angular_bundle_targets")
|
||||
|
||||
package(default_visibility = ["//visibility:public"])
|
||||
|
||||
|
|
@ -46,5 +45,3 @@ esbuild_config(
|
|||
":esbuild_base",
|
||||
],
|
||||
)
|
||||
|
||||
create_angular_bundle_targets()
|
||||
|
|
|
|||
|
|
@ -1,84 +1,4 @@
|
|||
load("@build_bazel_rules_nodejs//:providers.bzl", "ExternalNpmPackageInfo", "JSModuleInfo")
|
||||
load("@build_bazel_rules_nodejs//internal/linker:link_node_modules.bzl", "LinkerPackageMappingInfo")
|
||||
load("@npm//@angular/build-tooling/bazel/esbuild:index.bzl", "esbuild")
|
||||
load("//devtools:packages.bzl", "ANGULAR_PACKAGES")
|
||||
|
||||
"""
|
||||
Starlark file exposing a definition for generating Angular linker-processed ESM bundles
|
||||
for all entry-points the Angular framework packages expose.
|
||||
|
||||
These linker-processed ESM bundles are useful as they can be integrated into the
|
||||
spec bundling, or dev-app to avoid unnecessary re-linking of framework entry-points
|
||||
every time the bundler executes. This helps with the overall development turnaround and
|
||||
is more idiomatic as it allows caching of the Angular framework packages.
|
||||
"""
|
||||
|
||||
def _linker_mapping_impl(ctx):
|
||||
return [
|
||||
# Pass through the `ExternalNpmPackageInfo` which is needed for the linker
|
||||
# resolving dependencies which might be external. e.g. `rxjs` for `@angular/core`.
|
||||
ctx.attr.package[ExternalNpmPackageInfo],
|
||||
JSModuleInfo(
|
||||
direct_sources = depset(ctx.files.srcs),
|
||||
sources = depset(ctx.files.srcs),
|
||||
),
|
||||
LinkerPackageMappingInfo(
|
||||
mappings = depset([
|
||||
struct(
|
||||
package_name = ctx.attr.module_name,
|
||||
package_path = "",
|
||||
link_path = "%s/%s" % (ctx.label.package, ctx.attr.subpath),
|
||||
),
|
||||
]),
|
||||
node_modules_roots = depset([]),
|
||||
),
|
||||
]
|
||||
|
||||
_linker_mapping = rule(
|
||||
implementation = _linker_mapping_impl,
|
||||
attrs = {
|
||||
"package": attr.label(),
|
||||
"srcs": attr.label_list(allow_files = False),
|
||||
"subpath": attr.string(),
|
||||
"module_name": attr.string(),
|
||||
},
|
||||
)
|
||||
|
||||
def _get_target_name_base(pkg, entry_point):
|
||||
return "%s%s" % (pkg.name, "_%s" % entry_point if entry_point else "")
|
||||
|
||||
def _create_bundle_targets(pkg, entry_point, module_name):
|
||||
target_name_base = _get_target_name_base(pkg, entry_point)
|
||||
fesm_bundle_path = "fesm2022/%s.mjs" % (entry_point if entry_point else pkg.name)
|
||||
|
||||
esbuild(
|
||||
name = "%s_linked_bundle" % target_name_base,
|
||||
output = "%s/index.mjs" % target_name_base,
|
||||
platform = pkg.platform,
|
||||
entry_point = "@npm//:node_modules/@angular/%s/%s" % (pkg.name, fesm_bundle_path),
|
||||
deps = ["@npm//@angular/%s" % pkg.name],
|
||||
config = "//devtools/tools/esbuild:esbuild_config_esm",
|
||||
# List of dependencies which should never be bundled into these linker-processed bundles.
|
||||
external = ["rxjs", "@angular", "domino", "xhr2", "@material"],
|
||||
)
|
||||
|
||||
_linker_mapping(
|
||||
name = "%s_linked" % target_name_base,
|
||||
srcs = [":%s_linked_bundle" % target_name_base],
|
||||
package = "@npm//@angular/%s" % pkg.name,
|
||||
module_name = module_name,
|
||||
subpath = target_name_base,
|
||||
)
|
||||
|
||||
def create_angular_bundle_targets():
|
||||
for pkg in ANGULAR_PACKAGES:
|
||||
_create_bundle_targets(pkg, None, pkg.module_name)
|
||||
|
||||
for entry_point in pkg.entry_points:
|
||||
_create_bundle_targets(pkg, entry_point, "%s/%s" % (pkg.module_name, entry_point))
|
||||
|
||||
LINKER_PROCESSED_FW_PACKAGES = [
|
||||
"//devtools/tools/esbuild:%s_linked" % _get_target_name_base(pkg, entry_point)
|
||||
for pkg in ANGULAR_PACKAGES
|
||||
for entry_point in [None] + pkg.entry_points
|
||||
"//devtools/tools:angular_cdk",
|
||||
"//devtools/tools:angular_material",
|
||||
]
|
||||
|
|
|
|||
19
devtools/tools/linking/BUILD.bazel
Normal file
19
devtools/tools/linking/BUILD.bazel
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
load("@build_bazel_rules_nodejs//:index.bzl", "copy_to_bin")
|
||||
load("//tools:defaults.bzl", "nodejs_binary")
|
||||
|
||||
copy_to_bin(
|
||||
name = "linker_srcs",
|
||||
srcs = ["index.mjs"],
|
||||
)
|
||||
|
||||
nodejs_binary(
|
||||
name = "linker_bin",
|
||||
data = [
|
||||
":linker_srcs",
|
||||
"//packages/compiler-cli/linker/babel",
|
||||
"@npm//@babel/core",
|
||||
"@npm//tinyglobby",
|
||||
],
|
||||
entry_point = ":index.mjs",
|
||||
visibility = ["//devtools/tools:__subpackages__"],
|
||||
)
|
||||
19
devtools/tools/linking/index.bzl
Normal file
19
devtools/tools/linking/index.bzl
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
load("@build_bazel_rules_nodejs//:index.bzl", "npm_package_bin")
|
||||
load("//devtools/tools/linking:linker_mapping.bzl", "linker_mapping")
|
||||
|
||||
def link_package(name, package_name, npm_package):
|
||||
npm_package_bin(
|
||||
name = "%s_package_out" % name,
|
||||
data = [npm_package],
|
||||
args = ["./external/npm/node_modules/%s" % package_name, "$(@D)"],
|
||||
output_dir = True,
|
||||
tool = "//devtools/tools/linking:linker_bin",
|
||||
)
|
||||
|
||||
linker_mapping(
|
||||
name = name,
|
||||
srcs = [":%s_package_out" % name],
|
||||
package = npm_package,
|
||||
module_name = package_name,
|
||||
subpath = "./%s_package_out" % name,
|
||||
)
|
||||
70
devtools/tools/linking/index.mjs
Normal file
70
devtools/tools/linking/index.mjs
Normal file
|
|
@ -0,0 +1,70 @@
|
|||
/**
|
||||
* @license
|
||||
* Copyright Google LLC 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.dev/license
|
||||
*/
|
||||
|
||||
/**
|
||||
* @fileoverview
|
||||
*
|
||||
* Script that copies the npm package contents of e.g. `@angular/cdk` over into
|
||||
* a new output directory while performing Angular linking using the local
|
||||
* Angular compiler-cli version.
|
||||
*
|
||||
* This is necessary for the devtools as we don't want to rely on JIT compilation,
|
||||
* and consumed libraries like Angular CDK, or Angular Material are only shipping
|
||||
* partial compilation output to npm.
|
||||
*/
|
||||
|
||||
import linkerBabelPlugin from '../../../packages/compiler-cli/linker/babel/index.mjs';
|
||||
import {transformAsync} from '@babel/core';
|
||||
import {readFile, writeFile, mkdir} from 'node:fs/promises';
|
||||
import {globSync} from 'tinyglobby';
|
||||
import path from 'path';
|
||||
|
||||
async function main() {
|
||||
const [packageDir, outDir] = process.argv.slice(2);
|
||||
|
||||
// Copy without preserving readonly permissions from Bazel.
|
||||
await Promise.all(
|
||||
globSync('**/*', {cwd: packageDir}).map(async (filePath) => {
|
||||
await mkdir(path.dirname(path.join(outDir, filePath)), {recursive: true});
|
||||
await writeFile(path.join(outDir, filePath), await readFile(path.join(packageDir, filePath)));
|
||||
}),
|
||||
);
|
||||
|
||||
const fesmBundles = globSync('fesm2022/**/*.mjs', {cwd: outDir});
|
||||
const tasks = [];
|
||||
const babelOptions = {
|
||||
plugins: [
|
||||
[
|
||||
linkerBabelPlugin,
|
||||
{
|
||||
// We compile with an unstamped version of the compiler, so ignore.
|
||||
unknownDeclarationVersionHandling: 'ignore',
|
||||
},
|
||||
],
|
||||
],
|
||||
};
|
||||
|
||||
for (const bundleFile of fesmBundles) {
|
||||
tasks.push(
|
||||
(async () => {
|
||||
const filePath = path.join(outDir, bundleFile);
|
||||
const content = await readFile(filePath, 'utf8');
|
||||
const result = await transformAsync(content, {...babelOptions, filename: filePath});
|
||||
|
||||
await writeFile(path.join(outDir, bundleFile), result.code);
|
||||
})(),
|
||||
);
|
||||
}
|
||||
|
||||
await Promise.all(tasks);
|
||||
}
|
||||
|
||||
main().catch((e) => {
|
||||
console.error(e, e.stack);
|
||||
process.exitCode = 1;
|
||||
});
|
||||
33
devtools/tools/linking/linker_mapping.bzl
Normal file
33
devtools/tools/linking/linker_mapping.bzl
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
load("@build_bazel_rules_nodejs//:providers.bzl", "ExternalNpmPackageInfo", "JSModuleInfo")
|
||||
load("@build_bazel_rules_nodejs//internal/linker:link_node_modules.bzl", "LinkerPackageMappingInfo")
|
||||
|
||||
def _linker_mapping_impl(ctx):
|
||||
return [
|
||||
# Pass through the `ExternalNpmPackageInfo` which is needed for the linker
|
||||
# resolving dependencies which might be external. e.g. `rxjs` for `@angular/core`.
|
||||
ctx.attr.package[ExternalNpmPackageInfo],
|
||||
JSModuleInfo(
|
||||
direct_sources = depset(ctx.files.srcs),
|
||||
sources = depset(ctx.files.srcs),
|
||||
),
|
||||
LinkerPackageMappingInfo(
|
||||
mappings = depset([
|
||||
struct(
|
||||
package_name = ctx.attr.module_name,
|
||||
package_path = "",
|
||||
link_path = "%s/%s" % (ctx.label.package, ctx.attr.subpath),
|
||||
),
|
||||
]),
|
||||
node_modules_roots = depset([]),
|
||||
),
|
||||
]
|
||||
|
||||
linker_mapping = rule(
|
||||
implementation = _linker_mapping_impl,
|
||||
attrs = {
|
||||
"package": attr.label(),
|
||||
"srcs": attr.label_list(allow_files = False),
|
||||
"subpath": attr.string(),
|
||||
"module_name": attr.string(),
|
||||
},
|
||||
)
|
||||
Loading…
Reference in a new issue