diff --git a/.aspect/rules/external_repository_action_cache/npm_translate_lock_MzA5NzUwNzMx b/.aspect/rules/external_repository_action_cache/npm_translate_lock_MzA5NzUwNzMx
new file mode 100755
index 000000000000..1652b6e5be5c
--- /dev/null
+++ b/.aspect/rules/external_repository_action_cache/npm_translate_lock_MzA5NzUwNzMx
@@ -0,0 +1,8 @@
+# @generated
+# Input hashes for repository rule npm_translate_lock(name = "npm2", pnpm_lock = "@//:pnpm-lock.yaml").
+# This file should be checked into version control along with the pnpm-lock.yaml file.
+.npmrc=-2023857461
+package.json=1733430966
+pnpm-lock.yaml=-703346517
+pnpm-workspace.yaml=1711114604
+yarn.lock=-931254131
diff --git a/.bazelrc b/.bazelrc
index 614512ec5faa..caa0491048d0 100644
--- a/.bazelrc
+++ b/.bazelrc
@@ -1,6 +1,10 @@
# Disable NG CLI TTY mode
build --action_env=NG_FORCE_TTY=false
+# Required by `rules_ts`.
+common --@aspect_rules_ts//ts:skipLibCheck=always
+common --@aspect_rules_ts//ts:default_to_tsc_transpiler
+
# Make TypeScript compilation fast, by keeping a few copies of the compiler
# running as daemons, and cache SourceFile AST's to reduce parse time.
build --strategy=TypeScriptCompile=worker
@@ -129,8 +133,6 @@ build:remote --jobs=150
# Setup the toolchain and platform for the remote build execution. The platform
# is provided by the shared dev-infra package and targets k8 remote containers.
-build:remote --crosstool_top=@npm//@angular/build-tooling/bazel/remote-execution/cpp:cc_toolchain_suite
-build:remote --extra_toolchains=@npm//@angular/build-tooling/bazel/remote-execution/cpp:cc_toolchain
build:remote --extra_execution_platforms=@npm//@angular/build-tooling/bazel/remote-execution:platform_with_network
build:remote --host_platform=@npm//@angular/build-tooling/bazel/remote-execution:platform_with_network
build:remote --platforms=@npm//@angular/build-tooling/bazel/remote-execution:platform_with_network
diff --git a/.bazelversion b/.bazelversion
index ade65226e0aa..f22d756da39d 100644
--- a/.bazelversion
+++ b/.bazelversion
@@ -1 +1 @@
-5.4.1
+6.5.0
diff --git a/.eslintrc.json b/.eslintrc.json
index f9727d18dd44..62a36e1d072d 100644
--- a/.eslintrc.json
+++ b/.eslintrc.json
@@ -75,7 +75,6 @@
"import/newline-after-import": "error",
"import/no-absolute-path": "error",
"import/no-duplicates": "error",
- "import/no-extraneous-dependencies": ["off", { "devDependencies": false }],
"import/no-unassigned-import": ["error", { "allow": ["symbol-observable"] }],
"import/order": [
"error",
@@ -142,7 +141,6 @@
{
"files": ["!packages/**", "**/*_spec.ts"],
"rules": {
- "import/no-extraneous-dependencies": ["error", { "devDependencies": true }],
"max-lines-per-function": "off",
"no-case-declarations": "off",
"no-console": "off"
diff --git a/.github/workflows/assistant-to-the-branch-manager.yml b/.github/workflows/assistant-to-the-branch-manager.yml
index 29e6de006939..9d9daed036ff 100644
--- a/.github/workflows/assistant-to-the-branch-manager.yml
+++ b/.github/workflows/assistant-to-the-branch-manager.yml
@@ -16,6 +16,6 @@ jobs:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
persist-credentials: false
- - uses: angular/dev-infra/github-actions/branch-manager@910c72bbcc1bf1ae2b22c48d41b2f0e8eeda520d
+ - uses: angular/dev-infra/github-actions/branch-manager@e8ee8c5c247dc83e161ab8cf2281d0f6569ee626
with:
angular-robot-key: ${{ secrets.ANGULAR_ROBOT_PRIVATE_KEY }}
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 246717c86bea..a647a6b8f755 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -21,7 +21,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Initialize environment
- uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@910c72bbcc1bf1ae2b22c48d41b2f0e8eeda520d
+ uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@e8ee8c5c247dc83e161ab8cf2281d0f6569ee626
- name: Install node modules
run: yarn install --immutable
- name: Generate JSON schema types
@@ -42,31 +42,35 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Initialize environment
- uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@910c72bbcc1bf1ae2b22c48d41b2f0e8eeda520d
+ uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@e8ee8c5c247dc83e161ab8cf2281d0f6569ee626
- name: Setup Bazel
- uses: angular/dev-infra/github-actions/bazel/setup@910c72bbcc1bf1ae2b22c48d41b2f0e8eeda520d
+ uses: angular/dev-infra/github-actions/bazel/setup@e8ee8c5c247dc83e161ab8cf2281d0f6569ee626
- name: Setup Bazel RBE
- uses: angular/dev-infra/github-actions/bazel/configure-remote@910c72bbcc1bf1ae2b22c48d41b2f0e8eeda520d
+ uses: angular/dev-infra/github-actions/bazel/configure-remote@e8ee8c5c247dc83e161ab8cf2281d0f6569ee626
- name: Install node modules
run: yarn install --immutable
- name: Build release targets
run: yarn ng-dev release build
test:
+ needs: build
runs-on: ubuntu-latest
steps:
- name: Initialize environment
- uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@910c72bbcc1bf1ae2b22c48d41b2f0e8eeda520d
+ uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@e8ee8c5c247dc83e161ab8cf2281d0f6569ee626
- name: Setup Bazel
- uses: angular/dev-infra/github-actions/bazel/setup@910c72bbcc1bf1ae2b22c48d41b2f0e8eeda520d
+ uses: angular/dev-infra/github-actions/bazel/setup@e8ee8c5c247dc83e161ab8cf2281d0f6569ee626
- name: Setup Bazel RBE
- uses: angular/dev-infra/github-actions/bazel/configure-remote@910c72bbcc1bf1ae2b22c48d41b2f0e8eeda520d
+ uses: angular/dev-infra/github-actions/bazel/configure-remote@e8ee8c5c247dc83e161ab8cf2281d0f6569ee626
- name: Install node modules
run: yarn install --immutable
- name: Run module and package tests
run: yarn bazel test //modules/... //packages/...
+ env:
+ ASPECT_RULES_JS_FROZEN_PNPM_LOCK: '1'
e2e:
+ needs: test
strategy:
fail-fast: false
matrix:
@@ -75,12 +79,6 @@ jobs:
subset: [npm, esbuild]
shard: [0, 1, 2, 3, 4, 5]
exclude:
- # Skip yarn subset on Windows
- - os: windows-latest
- subset: yarn
- # Skip pnpm subset on Windows
- - os: windows-latest
- subset: pnpm
# Skip Node.js v18 tests on Windows
- os: windows-latest
node: 18
@@ -89,18 +87,23 @@ jobs:
node: 20
runs-on: ${{ matrix.os }}
steps:
+ # Workaround for: https://github.com/bazel-contrib/bazel-lib/issues/968.
+ # TODO(devversion): Remove when Aspect lib issue is fixed.
+ - run: choco install gzip
+ if: ${{matrix.os == 'windows-latest'}}
- name: Initialize environment
- uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@910c72bbcc1bf1ae2b22c48d41b2f0e8eeda520d
+ uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@e8ee8c5c247dc83e161ab8cf2281d0f6569ee626
- name: Install node modules
run: yarn install --immutable
- name: Setup Bazel
- uses: angular/dev-infra/github-actions/bazel/setup@910c72bbcc1bf1ae2b22c48d41b2f0e8eeda520d
+ uses: angular/dev-infra/github-actions/bazel/setup@e8ee8c5c247dc83e161ab8cf2281d0f6569ee626
- name: Setup Bazel RBE
- uses: angular/dev-infra/github-actions/bazel/configure-remote@910c72bbcc1bf1ae2b22c48d41b2f0e8eeda520d
+ uses: angular/dev-infra/github-actions/bazel/configure-remote@e8ee8c5c247dc83e161ab8cf2281d0f6569ee626
- name: Run CLI E2E tests
run: yarn bazel test --define=E2E_SHARD_TOTAL=6 --define=E2E_SHARD_INDEX=${{ matrix.shard }} --config=e2e //tests/legacy-cli:e2e.${{ matrix.subset }}_node${{ matrix.node }}
e2e-package-managers:
+ needs: test
strategy:
fail-fast: false
matrix:
@@ -111,17 +114,18 @@ jobs:
runs-on: ${{ matrix.os }}
steps:
- name: Initialize environment
- uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@910c72bbcc1bf1ae2b22c48d41b2f0e8eeda520d
+ uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@e8ee8c5c247dc83e161ab8cf2281d0f6569ee626
- name: Install node modules
run: yarn install --immutable
- name: Setup Bazel
- uses: angular/dev-infra/github-actions/bazel/setup@910c72bbcc1bf1ae2b22c48d41b2f0e8eeda520d
+ uses: angular/dev-infra/github-actions/bazel/setup@e8ee8c5c247dc83e161ab8cf2281d0f6569ee626
- name: Setup Bazel RBE
- uses: angular/dev-infra/github-actions/bazel/configure-remote@910c72bbcc1bf1ae2b22c48d41b2f0e8eeda520d
+ uses: angular/dev-infra/github-actions/bazel/configure-remote@e8ee8c5c247dc83e161ab8cf2281d0f6569ee626
- name: Run CLI E2E tests
run: yarn bazel test --define=E2E_SHARD_TOTAL=3 --define=E2E_SHARD_INDEX=${{ matrix.shard }} --config=e2e //tests/legacy-cli:e2e.${{ matrix.subset }}_node${{ matrix.node }}
e2e-snapshots:
+ needs: test
strategy:
fail-fast: false
matrix:
@@ -132,30 +136,31 @@ jobs:
runs-on: ${{ matrix.os }}
steps:
- name: Initialize environment
- uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@910c72bbcc1bf1ae2b22c48d41b2f0e8eeda520d
+ uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@e8ee8c5c247dc83e161ab8cf2281d0f6569ee626
- name: Install node modules
run: yarn install --immutable
- name: Setup Bazel
- uses: angular/dev-infra/github-actions/bazel/setup@910c72bbcc1bf1ae2b22c48d41b2f0e8eeda520d
+ uses: angular/dev-infra/github-actions/bazel/setup@e8ee8c5c247dc83e161ab8cf2281d0f6569ee626
- name: Setup Bazel RBE
- uses: angular/dev-infra/github-actions/bazel/configure-remote@910c72bbcc1bf1ae2b22c48d41b2f0e8eeda520d
+ uses: angular/dev-infra/github-actions/bazel/configure-remote@e8ee8c5c247dc83e161ab8cf2281d0f6569ee626
- name: Run CLI E2E tests
run: yarn bazel test --define=E2E_SHARD_TOTAL=6 --define=E2E_SHARD_INDEX=${{ matrix.shard }} --config=e2e //tests/legacy-cli:e2e.snapshots.${{ matrix.subset }}_node${{ matrix.node }}
browsers:
+ needs: build
runs-on: ubuntu-latest
name: Browser Compatibility Tests
env:
SAUCE_TUNNEL_IDENTIFIER: angular-cli-${{ github.workflow }}-${{ github.run_number }}
steps:
- name: Initialize environment
- uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@910c72bbcc1bf1ae2b22c48d41b2f0e8eeda520d
+ uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@e8ee8c5c247dc83e161ab8cf2281d0f6569ee626
- name: Install node modules
run: yarn install --immutable
- name: Setup Bazel
- uses: angular/dev-infra/github-actions/bazel/setup@910c72bbcc1bf1ae2b22c48d41b2f0e8eeda520d
+ uses: angular/dev-infra/github-actions/bazel/setup@e8ee8c5c247dc83e161ab8cf2281d0f6569ee626
- name: Setup Bazel RBE
- uses: angular/dev-infra/github-actions/bazel/configure-remote@910c72bbcc1bf1ae2b22c48d41b2f0e8eeda520d
+ uses: angular/dev-infra/github-actions/bazel/configure-remote@e8ee8c5c247dc83e161ab8cf2281d0f6569ee626
- name: Run E2E Browser tests
env:
SAUCE_USERNAME: ${{ vars.SAUCE_USERNAME }}
@@ -170,23 +175,24 @@ jobs:
./scripts/saucelabs/wait-for-tunnel.sh
yarn bazel test --config=saucelabs //tests/legacy-cli:e2e.saucelabs
./scripts/saucelabs/stop-tunnel.sh
- - uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3
+ - uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0
if: ${{ failure() }}
with:
name: sauce-connect-log
path: ${{ env.SAUCE_CONNECT_DIR_IN_HOST }}/sauce-connect.log
publish-snapshots:
+ needs: build
runs-on: ubuntu-latest
env:
CIRCLE_BRANCH: ${{ github.ref_name }}
steps:
- name: Initialize environment
- uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@910c72bbcc1bf1ae2b22c48d41b2f0e8eeda520d
+ uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@e8ee8c5c247dc83e161ab8cf2281d0f6569ee626
- name: Install node modules
run: yarn install --immutable
- name: Setup Bazel
- uses: angular/dev-infra/github-actions/bazel/setup@910c72bbcc1bf1ae2b22c48d41b2f0e8eeda520d
+ uses: angular/dev-infra/github-actions/bazel/setup@e8ee8c5c247dc83e161ab8cf2281d0f6569ee626
- run: yarn admin snapshots --verbose
env:
SNAPSHOT_BUILDS_GITHUB_TOKEN: ${{ secrets.SNAPSHOT_BUILDS_GITHUB_TOKEN }}
diff --git a/.github/workflows/dev-infra.yml b/.github/workflows/dev-infra.yml
index 453ce0f4e046..9f0dd8bf52b5 100644
--- a/.github/workflows/dev-infra.yml
+++ b/.github/workflows/dev-infra.yml
@@ -13,13 +13,13 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- - uses: angular/dev-infra/github-actions/commit-message-based-labels@910c72bbcc1bf1ae2b22c48d41b2f0e8eeda520d
+ - uses: angular/dev-infra/github-actions/commit-message-based-labels@e8ee8c5c247dc83e161ab8cf2281d0f6569ee626
with:
angular-robot-key: ${{ secrets.ANGULAR_ROBOT_PRIVATE_KEY }}
post_approval_changes:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- - uses: angular/dev-infra/github-actions/post-approval-changes@910c72bbcc1bf1ae2b22c48d41b2f0e8eeda520d
+ - uses: angular/dev-infra/github-actions/post-approval-changes@e8ee8c5c247dc83e161ab8cf2281d0f6569ee626
with:
angular-robot-key: ${{ secrets.ANGULAR_ROBOT_PRIVATE_KEY }}
diff --git a/.github/workflows/feature-requests.yml b/.github/workflows/feature-requests.yml
index abc0ac2342c1..12ce31100ea6 100644
--- a/.github/workflows/feature-requests.yml
+++ b/.github/workflows/feature-requests.yml
@@ -16,6 +16,6 @@ jobs:
if: github.repository == 'angular/angular-cli'
runs-on: ubuntu-latest
steps:
- - uses: angular/dev-infra/github-actions/feature-request@910c72bbcc1bf1ae2b22c48d41b2f0e8eeda520d
+ - uses: angular/dev-infra/github-actions/feature-request@e8ee8c5c247dc83e161ab8cf2281d0f6569ee626
with:
angular-robot-key: ${{ secrets.ANGULAR_ROBOT_PRIVATE_KEY }}
diff --git a/.github/workflows/perf.yml b/.github/workflows/perf.yml
new file mode 100644
index 000000000000..0a2d2148ace7
--- /dev/null
+++ b/.github/workflows/perf.yml
@@ -0,0 +1,55 @@
+name: Performance Tracking
+
+on:
+ push:
+ branches:
+ - main
+ # Run workflows for all releasable branches
+ - '[0-9]+.[0-9]+.x'
+
+permissions:
+ contents: 'read'
+ id-token: 'write'
+
+defaults:
+ run:
+ shell: bash
+
+jobs:
+ list:
+ timeout-minutes: 3
+ runs-on: ubuntu-latest
+ outputs:
+ workflows: ${{ steps.workflows.outputs.workflows }}
+ steps:
+ - name: Initialize environment
+ uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@e8ee8c5c247dc83e161ab8cf2281d0f6569ee626
+ - name: Install node modules
+ run: yarn install --immutable
+ - id: workflows
+ run: echo "workflows=$(yarn ng-dev perf workflows --list)" >> "$GITHUB_OUTPUT"
+
+ workflow:
+ timeout-minutes: 30
+ runs-on: ubuntu-latest
+ needs: list
+ strategy:
+ matrix:
+ workflow: ${{ fromJSON(needs.list.outputs.workflows) }}
+ steps:
+ - name: Initialize environment
+ uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@e8ee8c5c247dc83e161ab8cf2281d0f6569ee626
+ - name: Setup Bazel
+ uses: angular/dev-infra/github-actions/bazel/setup@e8ee8c5c247dc83e161ab8cf2281d0f6569ee626
+ - name: Install node modules
+ run: yarn install --immutable
+ # We utilize the google-github-actions/auth action to allow us to get an active credential using workflow
+ # identity federation. This allows us to request short lived credentials on demand, rather than storing
+ # credentials in secrets long term. More information can be found at:
+ # https://docs.github.com/en/actions/security-for-github-actions/security-hardening-your-deployments/configuring-openid-connect-in-google-cloud-platform
+ - uses: google-github-actions/auth@6fc4af4b145ae7821d527454aa9bd537d1f2dc5f # v2.1.7
+ with:
+ project_id: 'internal-200822'
+ workload_identity_provider: 'projects/823469418460/locations/global/workloadIdentityPools/measurables-tracking/providers/angular'
+ service_account: 'measures-uploader@internal-200822.iam.gserviceaccount.com'
+ - run: yarn ng-dev perf workflows --name ${{ matrix.workflow }} --commit-sha ${{github.sha}}
diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml
index 507ad62ac402..2d048b3fe523 100644
--- a/.github/workflows/pr.yml
+++ b/.github/workflows/pr.yml
@@ -34,9 +34,9 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Initialize environment
- uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@910c72bbcc1bf1ae2b22c48d41b2f0e8eeda520d
+ uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@e8ee8c5c247dc83e161ab8cf2281d0f6569ee626
- name: Setup ESLint Caching
- uses: actions/cache@6849a6489940f00c2f30c0fb92c6274307ccb58a # v4.1.2
+ uses: actions/cache@1bd1e32a3bdc45362d1e726936510720a7c30a57 # v4.2.0
with:
path: .eslintcache
key: ${{ runner.os }}-${{ hashFiles('.eslintrc.json') }}
@@ -54,7 +54,7 @@ jobs:
- name: Run Validation
run: yarn admin validate
- name: Check Package Licenses
- uses: angular/dev-infra/github-actions/linting/licenses@910c72bbcc1bf1ae2b22c48d41b2f0e8eeda520d
+ uses: angular/dev-infra/github-actions/linting/licenses@e8ee8c5c247dc83e161ab8cf2281d0f6569ee626
- name: Check tooling setup
run: yarn check-tooling-setup
- name: Check commit message
@@ -70,37 +70,41 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Initialize environment
- uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@910c72bbcc1bf1ae2b22c48d41b2f0e8eeda520d
+ uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@e8ee8c5c247dc83e161ab8cf2281d0f6569ee626
- name: Setup Bazel
- uses: angular/dev-infra/github-actions/bazel/setup@910c72bbcc1bf1ae2b22c48d41b2f0e8eeda520d
+ uses: angular/dev-infra/github-actions/bazel/setup@e8ee8c5c247dc83e161ab8cf2281d0f6569ee626
- name: Setup Bazel RBE
- uses: angular/dev-infra/github-actions/bazel/configure-remote@910c72bbcc1bf1ae2b22c48d41b2f0e8eeda520d
+ uses: angular/dev-infra/github-actions/bazel/configure-remote@e8ee8c5c247dc83e161ab8cf2281d0f6569ee626
- name: Install node modules
run: yarn install --immutable
- name: Build release targets
run: yarn ng-dev release build
- name: Store PR release packages
- uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3
+ uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0
with:
name: packages
path: dist/releases/*.tgz
retention-days: 14
test:
+ needs: build
runs-on: ubuntu-latest
steps:
- name: Initialize environment
- uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@910c72bbcc1bf1ae2b22c48d41b2f0e8eeda520d
+ uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@e8ee8c5c247dc83e161ab8cf2281d0f6569ee626
- name: Setup Bazel
- uses: angular/dev-infra/github-actions/bazel/setup@910c72bbcc1bf1ae2b22c48d41b2f0e8eeda520d
+ uses: angular/dev-infra/github-actions/bazel/setup@e8ee8c5c247dc83e161ab8cf2281d0f6569ee626
- name: Setup Bazel RBE
- uses: angular/dev-infra/github-actions/bazel/configure-remote@910c72bbcc1bf1ae2b22c48d41b2f0e8eeda520d
+ uses: angular/dev-infra/github-actions/bazel/configure-remote@e8ee8c5c247dc83e161ab8cf2281d0f6569ee626
- name: Install node modules
run: yarn install --immutable
- name: Run module and package tests
run: yarn bazel test //modules/... //packages/...
+ env:
+ ASPECT_RULES_JS_FROZEN_PNPM_LOCK: '1'
e2e:
+ needs: build
strategy:
fail-fast: false
matrix:
@@ -111,31 +115,36 @@ jobs:
runs-on: ${{ matrix.os }}
steps:
- name: Initialize environment
- uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@910c72bbcc1bf1ae2b22c48d41b2f0e8eeda520d
+ uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@e8ee8c5c247dc83e161ab8cf2281d0f6569ee626
- name: Install node modules
run: yarn install --immutable
- name: Setup Bazel
- uses: angular/dev-infra/github-actions/bazel/setup@910c72bbcc1bf1ae2b22c48d41b2f0e8eeda520d
+ uses: angular/dev-infra/github-actions/bazel/setup@e8ee8c5c247dc83e161ab8cf2281d0f6569ee626
- name: Setup Bazel RBE
- uses: angular/dev-infra/github-actions/bazel/configure-remote@910c72bbcc1bf1ae2b22c48d41b2f0e8eeda520d
+ uses: angular/dev-infra/github-actions/bazel/configure-remote@e8ee8c5c247dc83e161ab8cf2281d0f6569ee626
- name: Run CLI E2E tests
run: yarn bazel test --define=E2E_SHARD_TOTAL=6 --define=E2E_SHARD_INDEX=${{ matrix.shard }} --config=e2e //tests/legacy-cli:e2e.${{ matrix.subset }}_node${{ matrix.node }}
e2e-windows-subset:
+ needs: build
runs-on: windows-latest
steps:
+ # Workaround for: https://github.com/bazel-contrib/bazel-lib/issues/968.
+ # TODO(devversion): Remove when Aspect lib issue is fixed.
+ - run: choco install gzip
- name: Initialize environment
- uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@910c72bbcc1bf1ae2b22c48d41b2f0e8eeda520d
+ uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@e8ee8c5c247dc83e161ab8cf2281d0f6569ee626
- name: Install node modules
run: yarn install --immutable
- name: Setup Bazel
- uses: angular/dev-infra/github-actions/bazel/setup@910c72bbcc1bf1ae2b22c48d41b2f0e8eeda520d
+ uses: angular/dev-infra/github-actions/bazel/setup@e8ee8c5c247dc83e161ab8cf2281d0f6569ee626
- name: Setup Bazel RBE
- uses: angular/dev-infra/github-actions/bazel/configure-remote@910c72bbcc1bf1ae2b22c48d41b2f0e8eeda520d
+ uses: angular/dev-infra/github-actions/bazel/configure-remote@e8ee8c5c247dc83e161ab8cf2281d0f6569ee626
- name: Run CLI E2E tests
run: yarn bazel test --config=e2e //tests/legacy-cli:e2e_node22 --test_filter="tests/basic/{build,rebuild}.ts" --test_arg="--esbuild"
e2e-package-managers:
+ needs: build
strategy:
fail-fast: false
matrix:
@@ -146,18 +155,18 @@ jobs:
runs-on: ${{ matrix.os }}
steps:
- name: Initialize environment
- uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@910c72bbcc1bf1ae2b22c48d41b2f0e8eeda520d
+ uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@e8ee8c5c247dc83e161ab8cf2281d0f6569ee626
- name: Install node modules
run: yarn install --immutable
- name: Setup Bazel
- uses: angular/dev-infra/github-actions/bazel/setup@910c72bbcc1bf1ae2b22c48d41b2f0e8eeda520d
+ uses: angular/dev-infra/github-actions/bazel/setup@e8ee8c5c247dc83e161ab8cf2281d0f6569ee626
- name: Setup Bazel RBE
- uses: angular/dev-infra/github-actions/bazel/configure-remote@910c72bbcc1bf1ae2b22c48d41b2f0e8eeda520d
+ uses: angular/dev-infra/github-actions/bazel/configure-remote@e8ee8c5c247dc83e161ab8cf2281d0f6569ee626
- name: Run CLI E2E tests
run: yarn bazel test --define=E2E_SHARD_TOTAL=3 --define=E2E_SHARD_INDEX=${{ matrix.shard }} --config=e2e //tests/legacy-cli:e2e.${{ matrix.subset }}_node${{ matrix.node }}
e2e-snapshots:
- needs: analyze
+ needs: [analyze, build]
if: needs.analyze.outputs.snapshots == 'true'
strategy:
fail-fast: false
@@ -169,12 +178,12 @@ jobs:
runs-on: ${{ matrix.os }}
steps:
- name: Initialize environment
- uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@910c72bbcc1bf1ae2b22c48d41b2f0e8eeda520d
+ uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@e8ee8c5c247dc83e161ab8cf2281d0f6569ee626
- name: Install node modules
run: yarn install --immutable
- name: Setup Bazel
- uses: angular/dev-infra/github-actions/bazel/setup@910c72bbcc1bf1ae2b22c48d41b2f0e8eeda520d
+ uses: angular/dev-infra/github-actions/bazel/setup@e8ee8c5c247dc83e161ab8cf2281d0f6569ee626
- name: Setup Bazel RBE
- uses: angular/dev-infra/github-actions/bazel/configure-remote@910c72bbcc1bf1ae2b22c48d41b2f0e8eeda520d
+ uses: angular/dev-infra/github-actions/bazel/configure-remote@e8ee8c5c247dc83e161ab8cf2281d0f6569ee626
- name: Run CLI E2E tests
run: yarn bazel test --define=E2E_SHARD_TOTAL=6 --define=E2E_SHARD_INDEX=${{ matrix.shard }} --config=e2e //tests/legacy-cli:e2e.snapshots.${{ matrix.subset }}_node${{ matrix.node }}
diff --git a/.github/workflows/scorecard.yml b/.github/workflows/scorecard.yml
index a2258b8949a7..4f7c0d713afc 100644
--- a/.github/workflows/scorecard.yml
+++ b/.github/workflows/scorecard.yml
@@ -38,7 +38,7 @@ jobs:
# Upload the results as artifacts.
- name: 'Upload artifact'
- uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3
+ uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0
with:
name: SARIF file
path: results.sarif
@@ -46,6 +46,6 @@ jobs:
# Upload the results to GitHub's code scanning dashboard.
- name: 'Upload to code-scanning'
- uses: github/codeql-action/upload-sarif@ea9e4e37992a54ee68a9622e985e60c8e8f12d9f # v3.27.4
+ uses: github/codeql-action/upload-sarif@48ab28a6f5dbc2a99bf1e0131198dd8f1df78169 # v3.28.0
with:
sarif_file: results.sarif
diff --git a/.ng-dev/dx-perf-workflows.yml b/.ng-dev/dx-perf-workflows.yml
new file mode 100644
index 000000000000..21c8b95acff3
--- /dev/null
+++ b/.ng-dev/dx-perf-workflows.yml
@@ -0,0 +1,49 @@
+workflows:
+ build-cli:
+ name: '@angular/cli build'
+ prepare:
+ - bazel clean
+ workflow:
+ - bazel build //packages/angular/cli:npm_package
+
+ angular-build-integration:
+ name: '@angular/build integration'
+ disabled: true
+ prepare:
+ - bazel clean
+ workflow:
+ - bazel test //packages/angular/build:integration_tests
+
+ modules-builder-tests:
+ name: '@ngtools/webpack test'
+ prepare:
+ - bazel clean
+ workflow:
+ - bazel test //packages/ngtools/webpack:webpack_test
+
+ devkit-core-tests:
+ name: '@angular/devkit/core tests'
+ prepare:
+ - bazel clean
+ workflow:
+ - bazel test //packages/angular_devkit/core:core_test
+
+ devkit-core-tests-rerun:
+ name: '@angular/devkit/core return test'
+ prepare:
+ - bazel clean
+ workflow:
+ - bazel test //packages/angular_devkit/core:core_test
+ # Add a single line to the beginning of a file to trigger a rebuild/retest
+ - sed -i '1i // comment' packages/angular_devkit/core/src/workspace/core_spec.ts
+ - bazel test //packages/angular_devkit/core:core_test
+ cleanup:
+ # Remove the single line added
+ - sed -i '1d' packages/angular_devkit/core/src/workspace/core_spec.ts
+
+ build-unit-tests:
+ name: '@angular/build tests'
+ prepare:
+ - bazel clean
+ workflow:
+ - bazel test //packages/angular/build:unit_tests
diff --git a/.npmrc b/.npmrc
index c42da845b449..b8d41f41d029 100644
--- a/.npmrc
+++ b/.npmrc
@@ -1 +1,7 @@
-engine-strict = true
+# Yarn Berry doesn't check engines at all, so pnpm shouldn't either.
+engine-strict = false
+
+# Disabling pnpm [hoisting](https://pnpm.io/npmrc#hoist) by setting `hoist=false` is recommended on
+# projects using rules_js so that pnpm outside of Bazel lays out a node_modules tree similar to what
+# rules_js lays out under Bazel (without a hidden node_modules/.pnpm/node_modules)
+hoist=false
diff --git a/.prettierignore b/.prettierignore
index b469932f0838..b0b71acaf241 100644
--- a/.prettierignore
+++ b/.prettierignore
@@ -15,3 +15,4 @@
dist/
/tests/legacy-cli/e2e/assets/
/tools/test/*.json
+pnpm-lock.yaml
diff --git a/BUILD.bazel b/BUILD.bazel
index 3b7064d60060..b22de2e3b10b 100644
--- a/BUILD.bazel
+++ b/BUILD.bazel
@@ -1,3 +1,5 @@
+load("@aspect_rules_ts//ts:defs.bzl", rules_js_tsconfig = "ts_config")
+
# Copyright Google Inc. All Rights Reserved.
#
# Use of this source code is governed by an MIT-style license that can be
@@ -5,6 +7,7 @@
load("@bazel_skylib//rules:common_settings.bzl", "bool_flag")
load("@build_bazel_rules_nodejs//:index.bzl", "copy_to_bin")
load("@npm//@bazel/concatjs:index.bzl", "ts_config")
+load("@npm2//:defs.bzl", "npm_link_all_packages")
package(default_visibility = ["//visibility:public"])
@@ -19,6 +22,28 @@ exports_files([
"package.json",
])
+npm_link_all_packages(
+ name = "root_modules",
+)
+
+rules_js_tsconfig(
+ name = "build-tsconfig",
+ src = "tsconfig-build.json",
+ deps = [
+ "tsconfig.json",
+ "//:root_modules/@types/node",
+ ],
+)
+
+rules_js_tsconfig(
+ name = "test-tsconfig",
+ src = "tsconfig-test.json",
+ deps = [
+ "tsconfig.json",
+ "//:root_modules/@types/node",
+ ],
+)
+
# Files required by e2e tests
copy_to_bin(
name = "config-files",
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 0c433e36edcc..438c291a44cd 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,196 +1,263 @@
-
+
-# 19.0.0-rc.2 (2024-11-14)
+# 19.1.0-next.2 (2024-12-18)
-### @angular/cli
+### @angular-devkit/architect
-| Commit | Type | Description |
-| --------------------------------------------------------------------------------------------------- | ---- | ----------------------------------------------------- |
-| [52dcd551c](https://github.com/angular/angular-cli/commit/52dcd551ca286b09b370b37757da76e524a685d7) | fix | support default options for multiselect list x-prompt |
+| Commit | Type | Description |
+| --------------------------------------------------------------------------------------------------- | ---- | ---------------------------------------------------- |
+| [fe1ae6933](https://github.com/angular/angular-cli/commit/fe1ae6933998104c144b2c8854f362289c8d91c6) | fix | avoid Node.js resolution for relative builder schema |
+
+### @angular-devkit/build-angular
+
+| Commit | Type | Description |
+| --------------------------------------------------------------------------------------------------- | ---- | ------------------------------ |
+| [a9a347014](https://github.com/angular/angular-cli/commit/a9a3470147aaf66ff4784a5b5c26c56d1051a5b3) | fix | jasmine.clock with app builder |
### @angular/build
-| Commit | Type | Description |
-| --------------------------------------------------------------------------------------------------- | ---- | ------------------------------------------------------------- |
-| [5a7a2925b](https://github.com/angular/angular-cli/commit/5a7a2925b1f649eabbeb0a75452978cddb3f243d) | fix | add missing redirect in SSR manifest |
-| [53b6cd33c](https://github.com/angular/angular-cli/commit/53b6cd33cff6c153608c5fab3093ecc9a02a97df) | fix | allow .js file replacements in all configuration cases |
-| [3602bbb77](https://github.com/angular/angular-cli/commit/3602bbb77b8924e89978427d9115f0b1fd7d46b7) | fix | avoid overwriting inline style bundling additional results |
-| [172f3c25a](https://github.com/angular/angular-cli/commit/172f3c25a33d51ba290389b8a4742f13df6d7a50) | fix | improve URL rebasing for hyphenated Sass namespaced variables |
+| Commit | Type | Description |
+| --------------------------------------------------------------------------------------------------- | ---- | --------------------------------------------------------- |
+| [298b554a7](https://github.com/angular/angular-cli/commit/298b554a7a40465444b4c508e2250ecbf459ea47) | feat | enable component template hot replacement by default |
+| [19bb2d480](https://github.com/angular/angular-cli/commit/19bb2d48097eaf8dcdbf584603210146b5f1b81e) | fix | force HTTP/1.1 in dev-server SSR with SSL |
+| [3b7e6a8c6](https://github.com/angular/angular-cli/commit/3b7e6a8c6e2e018a85b437256040fd9c8161d537) | fix | invalidate component template updates with dev-server SSR |
### @angular/ssr
-| Commit | Type | Description |
-| --------------------------------------------------------------------------------------------------- | ---- | -------------------------------------------- |
-| [280ebbda4](https://github.com/angular/angular-cli/commit/280ebbda4c65e19b83448a1bb0de056a2ee5d1c6) | fix | support for HTTP/2 request/response handling |
+| Commit | Type | Description |
+| --------------------------------------------------------------------------------------------------- | ---- | ----------------------------------------------------------------------- |
+| [1bf9381c8](https://github.com/angular/angular-cli/commit/1bf9381c8c580321c8a473da1735839ecaf5ad76) | fix | correctly resolve pre-transform resources in Vite SSR without AppEngine |
+| [ad1d7d76f](https://github.com/angular/angular-cli/commit/ad1d7d76fc6b94b8f12841fcfb331e5fb098403e) | fix | ensure correct `Location` header for redirects behind a proxy |
-
+
-# 18.2.12 (2024-11-14)
+# 19.0.6 (2024-12-18)
-### @angular/cli
+### @angular-devkit/build-angular
-| Commit | Type | Description |
-| --------------------------------------------------------------------------------------------------- | ---- | ----------------------------------------------------- |
-| [c3925ed7f](https://github.com/angular/angular-cli/commit/c3925ed7f8e34fd9816cf5a4e8d63c2c45d31d53) | fix | support default options for multiselect list x-prompt |
+| Commit | Type | Description |
+| --------------------------------------------------------------------------------------------------- | ---- | ------------------------------ |
+| [db7421231](https://github.com/angular/angular-cli/commit/db7421231c3da7bbbfde72dc35642aaf005fbeca) | fix | jasmine.clock with app builder |
### @angular/build
-| Commit | Type | Description |
-| --------------------------------------------------------------------------------------------------- | ---- | ------------------------------------------------------------- |
-| [c8bee8415](https://github.com/angular/angular-cli/commit/c8bee8415099dfa03d5309183ebbbaab73b2a0eb) | fix | allow .js file replacements in all configuration cases |
-| [93f552112](https://github.com/angular/angular-cli/commit/93f552112c2bbd10bc0cee4afcae5b012242636c) | fix | improve URL rebasing for hyphenated Sass namespaced variables |
+| Commit | Type | Description |
+| --------------------------------------------------------------------------------------------------- | ---- | ----------------------------------------- |
+| [5fbc105ed](https://github.com/angular/angular-cli/commit/5fbc105ed0cb76106916660d99fc53d7480dcbc8) | fix | force HTTP/1.1 in dev-server SSR with SSL |
+
+### @angular/ssr
+
+| Commit | Type | Description |
+| --------------------------------------------------------------------------------------------------- | ---- | ----------------------------------------------------------------------- |
+| [2f4df6b2b](https://github.com/angular/angular-cli/commit/2f4df6b2be458b3651df49f3bced923e8df4d547) | fix | correctly resolve pre-transform resources in Vite SSR without AppEngine |
+| [0789a9e13](https://github.com/angular/angular-cli/commit/0789a9e133fed4edc29b108630b2cf91e157127e) | fix | ensure correct `Location` header for redirects behind a proxy |
-
+
-# 19.0.0-rc.1 (2024-11-06)
+# 19.1.0-next.1 (2024-12-12)
-### @angular/cli
+## Deprecations
-| Commit | Type | Description |
-| --------------------------------------------------------------------------------------------------- | ---- | ----------------------------------------------------------------- |
-| [b847d4460](https://github.com/angular/angular-cli/commit/b847d4460c352604e1935d494efd761915caaa3f) | fix | recommend optional application update migration during v19 update |
+### @angular/build
-### @schematics/angular
+- The `baseHref` option under `i18n.locales` and `i18n.sourceLocale` in `angular.json` is deprecated in favor of `subPath`.
+
+ The `subPath` defines the URL segment for the locale, serving as both the HTML base HREF and the directory name for output. By default, if not specified, `subPath` will use the locale code.
+
+### @angular-devkit/architect
| Commit | Type | Description |
| --------------------------------------------------------------------------------------------------- | ---- | ---------------------------------- |
-| [b1504c3bc](https://github.com/angular/angular-cli/commit/b1504c3bcca4d4c313e5d795ace8b074fb1f8890) | fix | component spec with export default |
+| [2b8a02bac](https://github.com/angular/angular-cli/commit/2b8a02bac098d4ac4f31b0e74bedfc739171e30b) | feat | require build schemas from modules |
### @angular-devkit/build-angular
-| Commit | Type | Description |
-| --------------------------------------------------------------------------------------------------- | ---- | ---------------------------- |
-| [2ec877dd0](https://github.com/angular/angular-cli/commit/2ec877dd0dede8f3ee849fe83b4a4427bab96447) | fix | handle basename collisions |
-| [43e7aae22](https://github.com/angular/angular-cli/commit/43e7aae2284ff15e0282c9d9597c4f31cf1f60a4) | fix | remove double-watch in karma |
-| [1e37b5939](https://github.com/angular/angular-cli/commit/1e37b59396a2f815d1671ccecc03ff8441730391) | fix | serve assets |
+| Commit | Type | Description |
+| --------------------------------------------------------------------------------------------------- | ---- | -------------------------------------- |
+| [fb41d182e](https://github.com/angular/angular-cli/commit/fb41d182ee9e50386b0412e07a8bacaaa8c7ce13) | fix | fix webpack config transform for karma |
+| [9e2d3fbd1](https://github.com/angular/angular-cli/commit/9e2d3fbd1f5ce1eeca8a46d854aa45598e516d90) | fix | handle windows spec collisions |
### @angular/build
-| Commit | Type | Description |
-| --------------------------------------------------------------------------------------------------- | ---- | --------------------------------------------------------------------------- |
-| [ecaf870b5](https://github.com/angular/angular-cli/commit/ecaf870b5cddd5d43d297f1193eb11b8f73757c0) | fix | always clear dev-server error overlay on non-error result |
-| [71534aadc](https://github.com/angular/angular-cli/commit/71534aadc403404e2dc9bc12054f32c3ed157db9) | fix | check referenced files against native file paths |
-| [fed31e064](https://github.com/angular/angular-cli/commit/fed31e064611894934c86ed36e8b0808029d4143) | fix | correctly use dev-server hmr option to control stylesheet hot replacement |
-| [b86bb080e](https://github.com/angular/angular-cli/commit/b86bb080e3a58a3320b2f68fb79edcdc98bfa7e9) | fix | disable dev-server websocket when live reload is disabled |
-| [efb2232df](https://github.com/angular/angular-cli/commit/efb2232df5475699a44d0f76a70e2d7de4a71f5a) | fix | ensure accurate content size in server asset metadata |
-| [18a8584ea](https://github.com/angular/angular-cli/commit/18a8584ead439d044760fe2abb4a7f657a0b10e3) | fix | ensure SVG template URLs are considered templates with external stylesheets |
-| [7502fee28](https://github.com/angular/angular-cli/commit/7502fee28a057b53e60b97f55b5aff5281019f1b) | fix | Exclude known `--import` from execArgv when spawning workers |
-| [c41529cc1](https://github.com/angular/angular-cli/commit/c41529cc1d762cf508eccf46c44256df21afe24f) | fix | handle `APP_BASE_HREF` correctly in prerendered routes |
-| [cf0228b82](https://github.com/angular/angular-cli/commit/cf0228b828fc43b1b33d48dc0977ff59abb597c2) | fix | skip wildcard routes from being listed as prerendered routes |
-| [c8e1521a2](https://github.com/angular/angular-cli/commit/c8e1521a2bd5b47c811e5d7f9aea7f57e92a4552) | fix | workaround Vite CSS ShadowDOM hot replacement |
+| Commit | Type | Description |
+| --------------------------------------------------------------------------------------------------- | ---- | ------------------------------------------------------------------------------------------------------------- |
+| [0a570c0c2](https://github.com/angular/angular-cli/commit/0a570c0c2e64c61ce9969975a21c0d9aac8d9f3b) | feat | add support for customizing URL segments with i18n |
+| [210bf4e2b](https://github.com/angular/angular-cli/commit/210bf4e2b4eeac6cdda7e810752486b74c6c0876) | fix | Fixing auto-csp edge cases where |
+| [d811a7ffb](https://github.com/angular/angular-cli/commit/d811a7ffb0b6aad741e35025e218ea558590b0bb) | fix | handle external `@angular/` packages during SSR ([#29094](https://github.com/angular/angular-cli/pull/29094)) |
+| [64f32c769](https://github.com/angular/angular-cli/commit/64f32c769f58b4e7306faf73fdae386a30f48a41) | fix | provide component HMR update modules to dev-server SSR |
+| [c832bac9b](https://github.com/angular/angular-cli/commit/c832bac9b23cd7e8c354f4e2428c158b9bb45e47) | fix | show error when Node.js built-ins are used during `ng serve` |
+| [887599822](https://github.com/angular/angular-cli/commit/88759982241ec2c30e00f62691347893d36bbfac) | fix | use consistent path separators for template HMR identifiers |
+| [75998ebab](https://github.com/angular/angular-cli/commit/75998ebabb041f60aab40bf5a11979e8f3615537) | perf | reuse TS package.json cache when rebuilding |
### @angular/ssr
-| Commit | Type | Description |
-| --------------------------------------------------------------------------------------------------- | ---- | ---------------------------------------------------------------------------------- |
-| [74b3e2d51](https://github.com/angular/angular-cli/commit/74b3e2d51c6cf605abd05da81dc7b4c3ccd9b3ea) | fix | add validation to prevent use of `provideServerRoutesConfig` in browser context |
-| [df4e1d360](https://github.com/angular/angular-cli/commit/df4e1d3607c2d5bf71d1234fa730e63cd6ab594b) | fix | enable serving of prerendered pages in the App Engine |
-| [3cf7a5223](https://github.com/angular/angular-cli/commit/3cf7a522318e34daa09f29133e8c3444f154ca0b) | fix | initialize the DI tokens with `null` to avoid requiring them to be set to optional |
-| [f460b91d4](https://github.com/angular/angular-cli/commit/f460b91d46ea5b0413596c4852c80d71d5308910) | perf | integrate ETags for prerendered pages |
+| Commit | Type | Description |
+| --------------------------------------------------------------------------------------------------- | ---- | --------------------------------------------------------------------------------- |
+| [8d7a51dfc](https://github.com/angular/angular-cli/commit/8d7a51dfc9658aa2f0f0c527435c05c2b10f34e5) | feat | add `modulepreload` for lazy-loaded routes |
+| [41ece633b](https://github.com/angular/angular-cli/commit/41ece633b3d42ef110bf6085fe0783ab2a56efcd) | feat | redirect to preferred locale when accessing root route without a specified locale |
+| [d7214e961](https://github.com/angular/angular-cli/commit/d7214e9610588fe1dbd8ce30e51eaac14a038c56) | fix | include `Content-Language` header when locale is set |
+| [10a5b8b6b](https://github.com/angular/angular-cli/commit/10a5b8b6b8ab1f1422adcb66e685af16b2c96549) | fix | disable component bootstrapping during route extraction |
-
-
-# 19.0.0-rc.0 (2024-10-30)
+
-### @schematics/angular
-
-| Commit | Type | Description |
-| --------------------------------------------------------------------------------------------------- | ---- | -------------------------------------------- |
-| [4e2a5fe15](https://github.com/angular/angular-cli/commit/4e2a5fe155006e7154326319ed39e77e5693d9b3) | fix | enable opt-in for new `@angular/ssr` feature |
+# 19.0.5 (2024-12-12)
### @angular-devkit/build-angular
-| Commit | Type | Description |
-| --------------------------------------------------------------------------------------------------- | ---- | ------------------------------- |
-| [476f94f51](https://github.com/angular/angular-cli/commit/476f94f51a3403d03ceb9f58ffb4a3564cc52e5a) | fix | fix --watch regression in karma |
+| Commit | Type | Description |
+| --------------------------------------------------------------------------------------------------- | ---- | -------------------------------------- |
+| [6c319e44c](https://github.com/angular/angular-cli/commit/6c319e44c707b93e430da93fe815ba839ab18ea1) | fix | fix webpack config transform for karma |
### @angular/build
-| Commit | Type | Description |
-| --------------------------------------------------------------------------------------------------- | ---- | ---------------------------------------------------------------------- |
-| [06e5176c2](https://github.com/angular/angular-cli/commit/06e5176c2d3b27aaeb117374a8ae402c6a4c6319) | fix | add warning when `--prerendering` or `--app-shell` are no-ops |
-| [f8677f6a9](https://github.com/angular/angular-cli/commit/f8677f6a9ba155b04c692814a1bc13f5cc47d94d) | fix | always record component style usage for HMR updates |
-| [099e477a8](https://github.com/angular/angular-cli/commit/099e477a8f1bbcf9d0f415dc6fd4743107c967f7) | fix | avoid hashing development external component stylesheets |
-| [0d4558ea5](https://github.com/angular/angular-cli/commit/0d4558ea516a4b8716f2442290e05354c502a49e) | fix | set `ngServerMode` during vite prebundling |
-| [55d7f01b6](https://github.com/angular/angular-cli/commit/55d7f01b66f4867aad4598574582e8505f201c82) | fix | simplify disabling server features with `--no-server` via command line |
+| Commit | Type | Description |
+| --------------------------------------------------------------------------------------------------- | ---- | ------------------------------------------------------------------------------------------------------------- |
+| [251bd9f22](https://github.com/angular/angular-cli/commit/251bd9f226f73529e824b131fa8d08b77aa00d09) | fix | Fixing auto-csp edge cases where |
+| [1047b8635](https://github.com/angular/angular-cli/commit/1047b8635699d55886fff28cbf02d36df224958d) | fix | handle external `@angular/` packages during SSR ([#29094](https://github.com/angular/angular-cli/pull/29094)) |
+| [376ee9966](https://github.com/angular/angular-cli/commit/376ee996699a9610984f3d3e36b3331557dbeaca) | fix | provide component HMR update modules to dev-server SSR |
+| [5ea9ce376](https://github.com/angular/angular-cli/commit/5ea9ce3760a191d13db08f5ae7448ce089e8eacd) | fix | use consistent path separators for template HMR identifiers |
### @angular/ssr
-| Commit | Type | Description |
-| --------------------------------------------------------------------------------------------------- | ---- | ------------------------------------------------------------------------ |
-| [0793c78cf](https://github.com/angular/angular-cli/commit/0793c78cfcbfc5d55fe6ce2cb53cada684bcb8dc) | fix | ensure wildcard RenderMode is applied when no Angular routes are defined |
+| Commit | Type | Description |
+| --------------------------------------------------------------------------------------------------- | ---- | ------------------------------------------------------- |
+| [b3c6c7eb2](https://github.com/angular/angular-cli/commit/b3c6c7eb2cc796796d99758368706b0b8682ae69) | fix | include `Content-Language` header when locale is set |
+| [4203efb90](https://github.com/angular/angular-cli/commit/4203efb90a38fe2f0d45fabab80dc736e8ca2b7b) | fix | disable component bootstrapping during route extraction |
-
+
-# 18.2.11 (2024-10-30)
+# 19.0.4 (2024-12-05)
+
+### @angular-devkit/build-angular
+
+| Commit | Type | Description |
+| --------------------------------------------------------------------------------------------------- | ---- | ------------------------------ |
+| [23667ed4a](https://github.com/angular/angular-cli/commit/23667ed4aa0bedbb591dc0284116402dc42ed95c) | fix | handle windows spec collisions |
### @angular/build
-| Commit | Type | Description |
-| --------------------------------------------------------------------------------------------------- | ---- | ------------------------------------------------ |
-| [87ec15ba2](https://github.com/angular/angular-cli/commit/87ec15ba266436b7b99b0629beaea3e487434115) | fix | show error message when error stack is undefined |
+| Commit | Type | Description |
+| --------------------------------------------------------------------------------------------------- | ---- | ------------------------------------------------------------ |
+| [fc41f50b5](https://github.com/angular/angular-cli/commit/fc41f50b53bbffead017b420105eed5bd8573ac1) | fix | show error when Node.js built-ins are used during `ng serve` |
+| [14451e275](https://github.com/angular/angular-cli/commit/14451e2754caff2c9800cca17e11ffa452575f09) | perf | reuse TS package.json cache when rebuilding |
+
+
+
+
+
+# 19.1.0-next.0 (2024-12-04)
+
+Added support for TypeScript 5.7
-
+
+
+# 19.0.3 (2024-12-04)
-# 19.0.0-next.13 (2024-10-23)
+### @angular/cli
+
+| Commit | Type | Description |
+| --------------------------------------------------------------------------------------------------- | ---- | --------------------------------------------------------------------- |
+| [4e82ca180](https://github.com/angular/angular-cli/commit/4e82ca180b330199b3dffadd9d590c8245dc7785) | fix | correctly select package versions in descending order during `ng add` |
+
+### @schematics/angular
+
+| Commit | Type | Description |
+| --------------------------------------------------------------------------------------------------- | ---- | -------------------------------------------------------------------------------------------------------- |
+| [28a51cc5e](https://github.com/angular/angular-cli/commit/28a51cc5e4a08f9e9627a1ec160ce462d18b88d2) | fix | add required type to `CanDeactivate` guard ([#29004](https://github.com/angular/angular-cli/pull/29004)) |
### @angular/build
-| Commit | Type | Description |
-| --------------------------------------------------------------------------------------------------- | ---- | -------------------------------------------------------------------- |
-| [efb434136](https://github.com/angular/angular-cli/commit/efb434136d8c8df207747ab8fd87b7e2116b7106) | feat | Auto-CSP support as a part of angular.json schema |
-| [cc345b02d](https://github.com/angular/angular-cli/commit/cc345b02d814a37bb23d6c3f1baca9595130d010) | fix | Address build issue in Node.js LTS versions with prerendering or SSR |
+| Commit | Type | Description |
+| --------------------------------------------------------------------------------------------------- | ---- | ----------------------------------------------------- |
+| [f26e1b462](https://github.com/angular/angular-cli/commit/f26e1b462ab012b0863f0889bcd60f5e07ca6fd2) | fix | add timeout to route extraction |
+| [ab4e77c75](https://github.com/angular/angular-cli/commit/ab4e77c75524d42485ac124f4786ab54bc6c404a) | fix | allow .json file replacements with application builds |
+| [06690d87e](https://github.com/angular/angular-cli/commit/06690d87eb590853eed6166857c9c1559d38d260) | fix | apply define option to JavaScript from scripts option |
+| [775e6f780](https://github.com/angular/angular-cli/commit/775e6f7808e6edb89d29b72ee5bdc6d2b26cb30e) | fix | avoid deploy URL usage on absolute preload links |
+| [21f21eda3](https://github.com/angular/angular-cli/commit/21f21eda39c62e284c6cbee0d0ebfe271f605239) | fix | ensure correct handling of `index.output` for SSR |
+
+### @angular/ssr
+
+| Commit | Type | Description |
+| --------------------------------------------------------------------------------------------------- | ---- | --------------------------------------------------- |
+| [75cf47e71](https://github.com/angular/angular-cli/commit/75cf47e71b0584e55750d5350932494f689a7e96) | fix | apply HTML transformation to CSR responses |
+| [5880a0230](https://github.com/angular/angular-cli/commit/5880a02306d9f81f030fcdc91fc6aaeb1986e652) | fix | correctly handle serving of prerendered i18n pages |
+| [277b8a378](https://github.com/angular/angular-cli/commit/277b8a3786d40cb8477287dcb3ef191ec8939447) | fix | ensure compatibility for `Http2ServerResponse` type |
-
+
-# 18.2.10 (2024-10-23)
+# 19.0.2 (2024-11-25)
-### @angular-devkit/build-angular
+### @schematics/angular
-| Commit | Type | Description |
-| --------------------------------------------------------------------------------------------------- | ---- | ----------------------------------------- |
-| [7b775f4e0](https://github.com/angular/angular-cli/commit/7b775f4e008652777bbe7b788dabed02bcc70cc7) | fix | update `http-proxy-middleware` to `3.0.3` |
+| Commit | Type | Description |
+| --------------------------------------------------------------------------------------------------- | ---- | --------------------------------------- |
+| [2f53e2af5](https://github.com/angular/angular-cli/commit/2f53e2af55794795979232b0f3e95359299e1aee) | fix | skip SSR routing prompt in webcontainer |
### @angular/build
-| Commit | Type | Description |
-| --------------------------------------------------------------------------------------------------- | ---- | -------------------------------------------------------------------- |
-| [b1e5f51f9](https://github.com/angular/angular-cli/commit/b1e5f51f9111d7da56ebe64cad51936ad659782d) | fix | Address build issue in Node.js LTS versions with prerendering or SSR |
+| Commit | Type | Description |
+| --------------------------------------------------------------------------------------------------- | ---- | ------------------------------------------------------------------------------- |
+| [f9da163f8](https://github.com/angular/angular-cli/commit/f9da163f8852800763844ae89e85eaafe0c37f2b) | fix | minimize reliance on esbuild `inject` to prevent code reordering |
+| [c497749e6](https://github.com/angular/angular-cli/commit/c497749e670e916e17a4e7fb0acb1abe26d9bd9a) | fix | prevent errors with parameterized routes when `getPrerenderParams` is undefined |
+
+### @angular/ssr
+
+| Commit | Type | Description |
+| --------------------------------------------------------------------------------------------------- | ---- | --------------------------------------------------------------- |
+| [c8cd90e0f](https://github.com/angular/angular-cli/commit/c8cd90e0f601a6baa05b84e45bbd37b4bf6049f5) | fix | handle nested redirects not explicitly defined in router config |
-
+
-# 17.3.11 (2024-10-23)
+# 19.0.1 (2024-11-21)
### @angular-devkit/build-angular
-| Commit | Type | Description |
-| --------------------------------------------------------------------------------------------------- | ---- | ----------------------------------------- |
-| [8bad9cee0](https://github.com/angular/angular-cli/commit/8bad9cee08982fffa5ce8244148b491e66191ed8) | fix | update `http-proxy-middleware` to `2.0.7` |
+| Commit | Type | Description |
+| --------------------------------------------------------------------------------------------------- | ---- | ---------------------------- |
+| [b63123f20](https://github.com/angular/angular-cli/commit/b63123f20702bd53ea99888b83b4253810ae0a09) | fix | use stylePreprocessorOptions |
+
+### @angular/build
+
+| Commit | Type | Description |
+| --------------------------------------------------------------------------------------------------- | ---- | -------------------------------------------------------- |
+| [74461da64](https://github.com/angular/angular-cli/commit/74461da6439b70b5348c99682842ae20043d9b61) | fix | ensure accurate content length for server assets |
+| [1b4dcedd5](https://github.com/angular/angular-cli/commit/1b4dcedd594b5d9a1701cd8d1e9874742c05e47f) | fix | use `sha256` instead of `sha-256` as hash algorithm name |
+
+### @angular/ssr
+
+| Commit | Type | Description |
+| --------------------------------------------------------------------------------------------------- | ---- | ------------------------------------ |
+| [8bd2b260e](https://github.com/angular/angular-cli/commit/8bd2b260e2008f1ffc71af0e55b27884c3660c54) | fix | handle baseHref that start with `./` |
-
+
-# 19.0.0-next.12 (2024-10-21)
+# 19.0.0 (2024-11-19)
## Breaking Changes
+### @schematics/angular
+
+- The app-shell schematic is no longer compatible with Webpack-based builders.
+
### @angular-devkit/build-angular
+- The `browserTarget` option has been removed from the DevServer and ExtractI18n builders. `buildTarget` is to be used instead.
- Protractor is no longer supported.
Protractor was marked end-of-life in August 2023 (see https://protractortest.org/). Projects still relying on Protractor should consider migrating to another E2E testing framework, several support solid migration paths from Protractor.
@@ -198,106 +265,246 @@
- https://angular.dev/tools/cli/end-to-end
- https://blog.angular.dev/the-state-of-end-to-end-testing-with-angular-d175f751cb9c
-### @angular-devkit/build-angular
+### @angular-devkit/core
+
+- The deprecated `fileBuffer` function is no longer available. Update your code to use `stringToFileBuffer` instead to maintain compatibility.
-| Commit | Type | Description |
-| --------------------------------------------------------------------------------------------------- | -------- | ---------------------------------------- |
-| [62877bdf2](https://github.com/angular/angular-cli/commit/62877bdf2b0449d8c12a167c59d0c24c77467f37) | refactor | remove Protractor builder and schematics |
+ **Note:** that this change does not affect application developers.
### @angular/build
-| Commit | Type | Description |
-| --------------------------------------------------------------------------------------------------- | ---- | ----------------------------------------------------- |
-| [1654acf0f](https://github.com/angular/angular-cli/commit/1654acf0ff3010b619a22d11f17eec9975d8e2a2) | fix | relax constraints on external stylesheet component id |
+- The `@angular/localize/init` polyfill will no longer be added automatically to projects. To prevent runtime issues, ensure that this polyfill is manually included in the "polyfills" section of your "angular.json" file if your application relies on Angular localization features.
### @angular/ssr
-| Commit | Type | Description |
-| --------------------------------------------------------------------------------------------------- | ---- | ------------------------------------- |
-| [44077f54e](https://github.com/angular/angular-cli/commit/44077f54e9a95afa5c1f85cf198aaa3412ee08d8) | fix | designate package as side-effect free |
+- The `CommonEngine` API now needs to be imported from `@angular/ssr/node`.
-
+ **Before**
+
+ ```ts
+ import { CommonEngine } from '@angular/ssr';
+ ```
+
+ **After**
+
+ ```ts
+ import { CommonEngine } from '@angular/ssr/node';
+ ```
-
+### @angular-devkit/schematics-cli
-# 19.0.0-next.11 (2024-10-16)
+| Commit | Type | Description |
+| --------------------------------------------------------------------------------------------------- | ---- | --------------------------------------------- |
+| [37693c40e](https://github.com/angular/angular-cli/commit/37693c40e3afc4c6dd7c949ea658bdf94146c9d8) | feat | add package manager option to blank schematic |
### @schematics/angular
-| Commit | Type | Description |
-| --------------------------------------------------------------------------------------------------- | ---- | ------------------------------------------------------------------- |
-| [755f3a07f](https://github.com/angular/angular-cli/commit/755f3a07f5fe485c1ed8c0c6060d6d5c799c085c) | feat | add option to setup new workspace or application as zoneless mode |
-| [fcf7443d6](https://github.com/angular/angular-cli/commit/fcf7443d626d1f3e828ebfad464598f7b9ddef12) | fix | explicitly set standalone:false |
-| [a68e832ae](https://github.com/angular/angular-cli/commit/a68e832aefa0767461e43e3b824f3ef773b02038) | fix | update browserslist config to include last 2 Android major versions |
+| Commit | Type | Description |
+| --------------------------------------------------------------------------------------------------- | ---- | -------------------------------------------------------------------------- |
+| [a381a3db1](https://github.com/angular/angular-cli/commit/a381a3db187f7b20e5ec8d1e1a1f1bd860426fcd) | feat | add option to export component as default |
+| [755f3a07f](https://github.com/angular/angular-cli/commit/755f3a07f5fe485c1ed8c0c6060d6d5c799c085c) | feat | add option to setup new workspace or application as zoneless mode |
+| [cfca5442e](https://github.com/angular/angular-cli/commit/cfca5442ec01cc4eff4fe75822eb7ef780ccfef1) | feat | integrate `withEventReplay()` in `provideClientHydration` for new SSR apps |
+| [292a4b7c2](https://github.com/angular/angular-cli/commit/292a4b7c2f62828606c42258db524341f4a6391e) | feat | update app-shell and ssr schematics to adopt new Server Rendering API |
+| [b1504c3bc](https://github.com/angular/angular-cli/commit/b1504c3bcca4d4c313e5d795ace8b074fb1f8890) | fix | component spec with export default |
+| [4b4e000dd](https://github.com/angular/angular-cli/commit/4b4e000dd60bb43df5c8694eb8a0bc0b45d1cf8d) | fix | don't show server routing prompt when using `browser` builder |
+| [4e2a5fe15](https://github.com/angular/angular-cli/commit/4e2a5fe155006e7154326319ed39e77e5693d9b3) | fix | enable opt-in for new `@angular/ssr` feature |
+| [fcf7443d6](https://github.com/angular/angular-cli/commit/fcf7443d626d1f3e828ebfad464598f7b9ddef12) | fix | explicitly set standalone:false |
+| [7992218a9](https://github.com/angular/angular-cli/commit/7992218a9c22ea9469bd3386c7dc1d5efc6e51f9) | fix | remove `declaration` and `sourceMap` from default tsconfig |
+| [9e6ab1bf2](https://github.com/angular/angular-cli/commit/9e6ab1bf231b35aceb989337fac55a6136594c5d) | fix | use default import for `express` |
+
+### @angular/cli
+
+| Commit | Type | Description |
+| --------------------------------------------------------------------------------------------------- | ---- | ----------------------------------------------------------------- |
+| [201b60e1d](https://github.com/angular/angular-cli/commit/201b60e1dd25b4abb7670e21d103b67d4eda0e14) | feat | handle string key/value pairs, e.g. --define |
+| [b847d4460](https://github.com/angular/angular-cli/commit/b847d4460c352604e1935d494efd761915caaa3f) | fix | recommend optional application update migration during v19 update |
+| [f249e7e85](https://github.com/angular/angular-cli/commit/f249e7e856bf16e8c5f154fdb8aff36421649a1b) | perf | enable Node.js compile code cache when available |
+| [ecc107d83](https://github.com/angular/angular-cli/commit/ecc107d83bfdfd9d5dd1087e264892d60361625c) | perf | enable Node.js compile code cache when available |
+
+### @angular-devkit/architect
+
+| Commit | Type | Description |
+| --------------------------------------------------------------------------------------------------- | ---- | ----------------------------- |
+| [78f76485f](https://github.com/angular/angular-cli/commit/78f76485fe315ffd0262c1a3732092731235828b) | feat | merge object options from CLI |
+
+### @angular-devkit/build-angular
+
+| Commit | Type | Description |
+| --------------------------------------------------------------------------------------------------- | -------- | --------------------------------------------------------- |
+| [0a4ef3026](https://github.com/angular/angular-cli/commit/0a4ef302635e4665ae9881746867dd80ca0d2dc7) | feat | karma-coverage w/ app builder |
+| [dcbdca85c](https://github.com/angular/angular-cli/commit/dcbdca85c7fe1a7371b8f6662e0f68e24d56102e) | feat | karma+esbuild+watch |
+| [54594b5ab](https://github.com/angular/angular-cli/commit/54594b5abfa4c9301cc369e5dea5f76b71e51ab0) | feat | support karma with esbuild |
+| [ea5ae68da](https://github.com/angular/angular-cli/commit/ea5ae68da9e7f2b598bae2ca9ac8be9c20ce7888) | fix | bring back style tags in browser builder |
+| [476f94f51](https://github.com/angular/angular-cli/commit/476f94f51a3403d03ceb9f58ffb4a3564cc52e5a) | fix | fix --watch regression in karma |
+| [25d928b4f](https://github.com/angular/angular-cli/commit/25d928b4fde00ae8396f6b9cfcd92b5254fc49aa) | fix | fix hanging terminal when `browser-sync` is not installed |
+| [2ec877dd0](https://github.com/angular/angular-cli/commit/2ec877dd0dede8f3ee849fe83b4a4427bab96447) | fix | handle basename collisions |
+| [ab6e19e1f](https://github.com/angular/angular-cli/commit/ab6e19e1f9a8781334821048522abe86b149c9c3) | fix | handle main field |
+| [43e7aae22](https://github.com/angular/angular-cli/commit/43e7aae2284ff15e0282c9d9597c4f31cf1f60a4) | fix | remove double-watch in karma |
+| [1e37b5939](https://github.com/angular/angular-cli/commit/1e37b59396a2f815d1671ccecc03ff8441730391) | fix | serve assets |
+| [9d7613db9](https://github.com/angular/angular-cli/commit/9d7613db9bf8b397d5896fcdf6c7b0efeaffa5d5) | fix | zone.js/testing + karma + esbuild |
+| [e40384e63](https://github.com/angular/angular-cli/commit/e40384e637bc6f92c28f4e572f986ca902938ba2) | refactor | remove deprecated `browserTarget` |
+| [62877bdf2](https://github.com/angular/angular-cli/commit/62877bdf2b0449d8c12a167c59d0c24c77467f37) | refactor | remove Protractor builder and schematics |
+
+### @angular-devkit/core
+
+| Commit | Type | Description |
+| --------------------------------------------------------------------------------------------------- | -------- | ------------------------------------------------------------------------ |
+| [0d8a1006d](https://github.com/angular/angular-cli/commit/0d8a1006d4629d8af1144065ea237ab30916e66e) | refactor | remove deprecated `fileBuffer` function in favor of `stringToFileBuffer` |
### @angular/build
-| Commit | Type | Description |
-| --------------------------------------------------------------------------------------------------- | ---- | ------------------------------------------------------------------- |
-| [b6951f448](https://github.com/angular/angular-cli/commit/b6951f4482418f65e4bd1c15d5f7f051c91d59db) | feat | add `sass` to `stylePreprocessorOptions` in application builder |
-| [816e3cb86](https://github.com/angular/angular-cli/commit/816e3cb868961c57a68783601b919370076c41dc) | feat | enable component stylesheet hot replacement by default |
-| [7d883a152](https://github.com/angular/angular-cli/commit/7d883a152e978112245a98f2f737764caa76ec0f) | feat | introduce `ssr.experimentalPlatform` option |
-| [c48d6947e](https://github.com/angular/angular-cli/commit/c48d6947ed17eab19822a97492e3686bcf059494) | feat | set development/production condition |
-| [13b65dfe1](https://github.com/angular/angular-cli/commit/13b65dfe191ca18a577421019c9a9e285d5c95a3) | fix | allow direct bundling of TSX files with application builder |
-| [5f473affc](https://github.com/angular/angular-cli/commit/5f473affcf001888082bf4adc51481c5afca81e0) | fix | avoid race condition in sass importer |
-| [af52fb49b](https://github.com/angular/angular-cli/commit/af52fb49bdd913af8af9bfbe36be279fce70de39) | fix | synchronize import/export conditions between bundler and TypeScript |
-| [bfa8fec9b](https://github.com/angular/angular-cli/commit/bfa8fec9b17d421925a684e2b642dee70165a879) | fix | use named export `reqHandler` for server.ts request handling |
+| Commit | Type | Description |
+| --------------------------------------------------------------------------------------------------- | -------- | ----------------------------------------------------------------------------------- |
+| [b6951f448](https://github.com/angular/angular-cli/commit/b6951f4482418f65e4bd1c15d5f7f051c91d59db) | feat | add `sass` to `stylePreprocessorOptions` in application builder |
+| [efb434136](https://github.com/angular/angular-cli/commit/efb434136d8c8df207747ab8fd87b7e2116b7106) | feat | Auto-CSP support as a part of angular.json schema |
+| [816e3cb86](https://github.com/angular/angular-cli/commit/816e3cb868961c57a68783601b919370076c41dc) | feat | enable component stylesheet hot replacement by default |
+| [3b00fc908](https://github.com/angular/angular-cli/commit/3b00fc908d4f07282e89677928e00665c8578ab5) | feat | introduce `outputMode` option to the application builder |
+| [7d883a152](https://github.com/angular/angular-cli/commit/7d883a152e978112245a98f2f737764caa76ec0f) | feat | introduce `ssr.experimentalPlatform` option |
+| [c48d6947e](https://github.com/angular/angular-cli/commit/c48d6947ed17eab19822a97492e3686bcf059494) | feat | set development/production condition |
+| [f63072668](https://github.com/angular/angular-cli/commit/f63072668e44254da78170445ac2417c7bc1aa18) | feat | utilize `ssr.entry` during prerendering to enable access to local API routes |
+| [bbc290133](https://github.com/angular/angular-cli/commit/bbc290133fc93186980ca3c43f221847ba8e858a) | feat | utilize `ssr.entry` in Vite dev-server when available |
+| [5a7a2925b](https://github.com/angular/angular-cli/commit/5a7a2925b1f649eabbeb0a75452978cddb3f243d) | fix | add missing redirect in SSR manifest |
+| [06e5176c2](https://github.com/angular/angular-cli/commit/06e5176c2d3b27aaeb117374a8ae402c6a4c6319) | fix | add warning when `--prerendering` or `--app-shell` are no-ops |
+| [ecaf870b5](https://github.com/angular/angular-cli/commit/ecaf870b5cddd5d43d297f1193eb11b8f73757c0) | fix | always clear dev-server error overlay on non-error result |
+| [f8677f6a9](https://github.com/angular/angular-cli/commit/f8677f6a9ba155b04c692814a1bc13f5cc47d94d) | fix | always record component style usage for HMR updates |
+| [099e477a8](https://github.com/angular/angular-cli/commit/099e477a8f1bbcf9d0f415dc6fd4743107c967f7) | fix | avoid hashing development external component stylesheets |
+| [3602bbb77](https://github.com/angular/angular-cli/commit/3602bbb77b8924e89978427d9115f0b1fd7d46b7) | fix | avoid overwriting inline style bundling additional results |
+| [71534aadc](https://github.com/angular/angular-cli/commit/71534aadc403404e2dc9bc12054f32c3ed157db9) | fix | check referenced files against native file paths |
+| [fed31e064](https://github.com/angular/angular-cli/commit/fed31e064611894934c86ed36e8b0808029d4143) | fix | correctly use dev-server hmr option to control stylesheet hot replacement |
+| [b86bb080e](https://github.com/angular/angular-cli/commit/b86bb080e3a58a3320b2f68fb79edcdc98bfa7e9) | fix | disable dev-server websocket when live reload is disabled |
+| [7c50ba9e2](https://github.com/angular/angular-cli/commit/7c50ba9e2faca445c196c69e972ac313547dda54) | fix | ensure `index.csr.html` is always generated when prerendering or SSR are enabled |
+| [efb2232df](https://github.com/angular/angular-cli/commit/efb2232df5475699a44d0f76a70e2d7de4a71f5a) | fix | ensure accurate content size in server asset metadata |
+| [18a8584ea](https://github.com/angular/angular-cli/commit/18a8584ead439d044760fe2abb4a7f657a0b10e3) | fix | ensure SVG template URLs are considered templates with external stylesheets |
+| [7502fee28](https://github.com/angular/angular-cli/commit/7502fee28a057b53e60b97f55b5aff5281019f1b) | fix | Exclude known `--import` from execArgv when spawning workers |
+| [2551df533](https://github.com/angular/angular-cli/commit/2551df533d61400c0fda89db77a93378480f5047) | fix | fully disable component style HMR in JIT mode |
+| [c41529cc1](https://github.com/angular/angular-cli/commit/c41529cc1d762cf508eccf46c44256df21afe24f) | fix | handle `APP_BASE_HREF` correctly in prerendered routes |
+| [87a90afd4](https://github.com/angular/angular-cli/commit/87a90afd4600049b184b32f8f92a0634e25890c0) | fix | incomplete string escaping or encoding |
+| [1bb68ba68](https://github.com/angular/angular-cli/commit/1bb68ba6812236a135c1599031bf7e1b7e0d1d79) | fix | move lmdb to optionalDependencies |
+| [a995c8ea6](https://github.com/angular/angular-cli/commit/a995c8ea6d17778af031c2f9797e52739ea4dc81) | fix | prevent prerendering of catch-all routes |
+| [1654acf0f](https://github.com/angular/angular-cli/commit/1654acf0ff3010b619a22d11f17eec9975d8e2a2) | fix | relax constraints on external stylesheet component id |
+| [0d4558ea5](https://github.com/angular/angular-cli/commit/0d4558ea516a4b8716f2442290e05354c502a49e) | fix | set `ngServerMode` during vite prebundling |
+| [55d7f01b6](https://github.com/angular/angular-cli/commit/55d7f01b66f4867aad4598574582e8505f201c82) | fix | simplify disabling server features with `--no-server` via command line |
+| [cf0228b82](https://github.com/angular/angular-cli/commit/cf0228b828fc43b1b33d48dc0977ff59abb597c2) | fix | skip wildcard routes from being listed as prerendered routes |
+| [af52fb49b](https://github.com/angular/angular-cli/commit/af52fb49bdd913af8af9bfbe36be279fce70de39) | fix | synchronize import/export conditions between bundler and TypeScript |
+| [6c618d495](https://github.com/angular/angular-cli/commit/6c618d495c54394eb2b87aee36ba5436c06557bd) | fix | update logic to support both internal and external SSR middlewares |
+| [bfa8fec9b](https://github.com/angular/angular-cli/commit/bfa8fec9b17d421925a684e2b642dee70165a879) | fix | use named export `reqHandler` for server.ts request handling |
+| [c8e1521a2](https://github.com/angular/angular-cli/commit/c8e1521a2bd5b47c811e5d7f9aea7f57e92a4552) | fix | workaround Vite CSS ShadowDOM hot replacement |
+| [d6a34034d](https://github.com/angular/angular-cli/commit/d6a34034d7489c09753090642ade4c606cd98ece) | refactor | remove automatic addition of `@angular/localize/init` polyfill and related warnings |
+
+### @angular/ssr
+
+| Commit | Type | Description |
+| --------------------------------------------------------------------------------------------------- | ---- | ---------------------------------------------------------------------------------------------------------- |
+| [92209dd2e](https://github.com/angular/angular-cli/commit/92209dd2e93af450e3fc657609efe95c6a6b3963) | feat | add `createRequestHandler` and `createNodeRequestHandler `utilities |
+| [41fb2ed86](https://github.com/angular/angular-cli/commit/41fb2ed86056306406832317178ca5d94aa110e2) | feat | Add `getHeaders` Method to `AngularAppEngine` and `AngularNodeAppEngine` for handling pages static headers |
+| [f346ee8a8](https://github.com/angular/angular-cli/commit/f346ee8a8819bb2eaf0ffb3d5523b00093be09e5) | feat | add `isMainModule` function |
+| [d66aaa3ca](https://github.com/angular/angular-cli/commit/d66aaa3ca458e05b535bec7c1dcb98b0e9c5202e) | feat | add server routing configuration API |
+| [bca568389](https://github.com/angular/angular-cli/commit/bca56838937f942c5ef902f5c98d018582188e84) | feat | dynamic route resolution using Angular router |
+| [30c25bf68](https://github.com/angular/angular-cli/commit/30c25bf6885fefea6094ec1815e066e4c6ada097) | feat | export `AngularAppEngine` as public API |
+| [455b5700c](https://github.com/angular/angular-cli/commit/455b5700c29845829235e17efec320e634553108) | feat | expose `writeResponseToNodeResponse` and `createWebRequestFromNodeRequest` in public API |
+| [9692a9054](https://github.com/angular/angular-cli/commit/9692a9054c3cdbf151df01279c2d268332b1a032) | feat | improve handling of aborted requests in `AngularServerApp` |
+| [576ff604c](https://github.com/angular/angular-cli/commit/576ff604cd739a9f41d588fa093ca2568e46826c) | feat | introduce `AngularNodeAppEngine` API for Node.js integration |
+| [3c9697a8c](https://github.com/angular/angular-cli/commit/3c9697a8c34a5e0f3470bde73f11f9f32107f42e) | feat | introduce new hybrid rendering API |
+| [4b09887a9](https://github.com/angular/angular-cli/commit/4b09887a9c82838ccb7a6c95d66225c7875e562b) | feat | move `CommonEngine` API to `/node` entry-point |
+| [d43180af5](https://github.com/angular/angular-cli/commit/d43180af5f3e7b29387fd06625bd8e37f3ebad95) | fix | add missing peer dependency on `@angular/platform-server` |
+| [74b3e2d51](https://github.com/angular/angular-cli/commit/74b3e2d51c6cf605abd05da81dc7b4c3ccd9b3ea) | fix | add validation to prevent use of `provideServerRoutesConfig` in browser context |
+| [2640bf7a6](https://github.com/angular/angular-cli/commit/2640bf7a680300acf18cf6502c57a00e0a5bfda9) | fix | correct route extraction and error handling |
+| [44077f54e](https://github.com/angular/angular-cli/commit/44077f54e9a95afa5c1f85cf198aaa3412ee08d8) | fix | designate package as side-effect free |
+| [df4e1d360](https://github.com/angular/angular-cli/commit/df4e1d3607c2d5bf71d1234fa730e63cd6ab594b) | fix | enable serving of prerendered pages in the App Engine |
+| [0793c78cf](https://github.com/angular/angular-cli/commit/0793c78cfcbfc5d55fe6ce2cb53cada684bcb8dc) | fix | ensure wildcard RenderMode is applied when no Angular routes are defined |
+| [65b6e75a5](https://github.com/angular/angular-cli/commit/65b6e75a5dca581a57a9ac3d61869fdd20f7dc2e) | fix | export `RESPONSE_INIT`, `REQUEST`, and `REQUEST_CONTEXT` tokens |
+| [4ecf63a77](https://github.com/angular/angular-cli/commit/4ecf63a777871bf214bf42fe1738c206bde3201c) | fix | export PrerenderFallback |
+| [50df63196](https://github.com/angular/angular-cli/commit/50df631960550049e7d1779fd2c8fbbcf549b8ef) | fix | improve handling of route mismatches between Angular server routes and Angular router |
+| [3cf7a5223](https://github.com/angular/angular-cli/commit/3cf7a522318e34daa09f29133e8c3444f154ca0b) | fix | initialize the DI tokens with `null` to avoid requiring them to be set to optional |
+| [85df4011b](https://github.com/angular/angular-cli/commit/85df4011ba27254ddb7f22dae550272c9c4406dd) | fix | resolve `bootstrap is not a function` error |
+| [e9c9e4995](https://github.com/angular/angular-cli/commit/e9c9e4995e39d9d62d10fe0497e0b98127bc8afa) | fix | resolve circular dependency issue from main.server.js reference in manifest |
+| [64c52521d](https://github.com/angular/angular-cli/commit/64c52521d052f850aa7ea1aaadfd8a9fcee9c387) | fix | show error when multiple routes are set with `RenderMode.AppShell` |
+| [280ebbda4](https://github.com/angular/angular-cli/commit/280ebbda4c65e19b83448a1bb0de056a2ee5d1c6) | fix | support for HTTP/2 request/response handling |
+| [fb05e7f0a](https://github.com/angular/angular-cli/commit/fb05e7f0abd9d68ac03f243c7774260619b8a623) | fix | use wildcard server route configuration on the '/' route when the app router is empty |
+| [12ff37adb](https://github.com/angular/angular-cli/commit/12ff37adbed552fc0db97251c30c889ef00e50f3) | perf | cache generated inline CSS for HTML |
+| [1d70e3b46](https://github.com/angular/angular-cli/commit/1d70e3b4682806a55d6f7ddacbafcbf615b2a10c) | perf | cache resolved entry-points |
+| [f460b91d4](https://github.com/angular/angular-cli/commit/f460b91d46ea5b0413596c4852c80d71d5308910) | perf | integrate ETags for prerendered pages |
+| [e52ae7f6f](https://github.com/angular/angular-cli/commit/e52ae7f6f5296a9628cc4a517e82339ac54925bb) | perf | prevent potential stampede in entry-points cache |
-
+
-# 18.2.9 (2024-10-16)
+# 18.2.12 (2024-11-14)
-### @schematics/angular
+### @angular/cli
-| Commit | Type | Description |
-| --------------------------------------------------------------------------------------------------- | ---- | ------------------------------------------------------------------- |
-| [237f7c5d0](https://github.com/angular/angular-cli/commit/237f7c5d0355e0a90b23156d3aa97f4328c869e7) | fix | update browserslist config to include last 2 Android major versions |
+| Commit | Type | Description |
+| --------------------------------------------------------------------------------------------------- | ---- | ----------------------------------------------------- |
+| [c3925ed7f](https://github.com/angular/angular-cli/commit/c3925ed7f8e34fd9816cf5a4e8d63c2c45d31d53) | fix | support default options for multiselect list x-prompt |
### @angular/build
-| Commit | Type | Description |
-| --------------------------------------------------------------------------------------------------- | ---- | ----------------------------------------------------------- |
-| [d749ba6a3](https://github.com/angular/angular-cli/commit/d749ba6a3c3dd7a90317bd9b46e858a842f27696) | fix | allow direct bundling of TSX files with application builder |
-| [b91c82d89](https://github.com/angular/angular-cli/commit/b91c82d8997c0009ed4bbf5e9cd9c82cb1f7f755) | fix | avoid race condition in sass importer |
+| Commit | Type | Description |
+| --------------------------------------------------------------------------------------------------- | ---- | ------------------------------------------------------------- |
+| [c8bee8415](https://github.com/angular/angular-cli/commit/c8bee8415099dfa03d5309183ebbbaab73b2a0eb) | fix | allow .js file replacements in all configuration cases |
+| [93f552112](https://github.com/angular/angular-cli/commit/93f552112c2bbd10bc0cee4afcae5b012242636c) | fix | improve URL rebasing for hyphenated Sass namespaced variables |
-
+
-# 19.0.0-next.10 (2024-10-09)
+# 18.2.11 (2024-10-30)
-## Breaking Changes
+### @angular/build
-### @schematics/angular
+| Commit | Type | Description |
+| --------------------------------------------------------------------------------------------------- | ---- | ------------------------------------------------ |
+| [87ec15ba2](https://github.com/angular/angular-cli/commit/87ec15ba266436b7b99b0629beaea3e487434115) | fix | show error message when error stack is undefined |
-- The app-shell schematic is no longer compatible with Webpack-based builders.
+
-### @schematics/angular
+
-| Commit | Type | Description |
-| --------------------------------------------------------------------------------------------------- | ---- | --------------------------------------------------------------------- |
-| [292a4b7c2](https://github.com/angular/angular-cli/commit/292a4b7c2f62828606c42258db524341f4a6391e) | feat | update app-shell and ssr schematics to adopt new Server Rendering API |
-| [6dbfc770b](https://github.com/angular/angular-cli/commit/6dbfc770b2d2f72dbc73e39e763f0773435825c6) | fix | add validation for component and directive class name |
-| [7de93e593](https://github.com/angular/angular-cli/commit/7de93e593a9b6439b2f33d0c25c371e14a0e9e38) | fix | include `index.csr.html` in resources asset group |
+# 18.2.10 (2024-10-23)
+
+### @angular-devkit/build-angular
+
+| Commit | Type | Description |
+| --------------------------------------------------------------------------------------------------- | ---- | ----------------------------------------- |
+| [7b775f4e0](https://github.com/angular/angular-cli/commit/7b775f4e008652777bbe7b788dabed02bcc70cc7) | fix | update `http-proxy-middleware` to `3.0.3` |
+
+### @angular/build
+
+| Commit | Type | Description |
+| --------------------------------------------------------------------------------------------------- | ---- | -------------------------------------------------------------------- |
+| [b1e5f51f9](https://github.com/angular/angular-cli/commit/b1e5f51f9111d7da56ebe64cad51936ad659782d) | fix | Address build issue in Node.js LTS versions with prerendering or SSR |
+
+
+
+
+
+# 17.3.11 (2024-10-23)
### @angular-devkit/build-angular
-| Commit | Type | Description |
-| --------------------------------------------------------------------------------------------------- | ---- | ----------------- |
-| [ab6e19e1f](https://github.com/angular/angular-cli/commit/ab6e19e1f9a8781334821048522abe86b149c9c3) | fix | handle main field |
+| Commit | Type | Description |
+| --------------------------------------------------------------------------------------------------- | ---- | ----------------------------------------- |
+| [8bad9cee0](https://github.com/angular/angular-cli/commit/8bad9cee08982fffa5ce8244148b491e66191ed8) | fix | update `http-proxy-middleware` to `2.0.7` |
+
+
+
+
+
+# 18.2.9 (2024-10-16)
+
+### @schematics/angular
+
+| Commit | Type | Description |
+| --------------------------------------------------------------------------------------------------- | ---- | ------------------------------------------------------------------- |
+| [237f7c5d0](https://github.com/angular/angular-cli/commit/237f7c5d0355e0a90b23156d3aa97f4328c869e7) | fix | update browserslist config to include last 2 Android major versions |
### @angular/build
-| Commit | Type | Description |
-| --------------------------------------------------------------------------------------------------- | ---- | -------------------------------------------------------------------------------- |
-| [549c20a93](https://github.com/angular/angular-cli/commit/549c20a9396b33ae2e6708a8a9a7c77f0167b276) | fix | `Ctrl + C` not terminating dev-server with SSR |
-| [1d9db138f](https://github.com/angular/angular-cli/commit/1d9db138f34132f62fd008c9b8597489b08018e6) | fix | always generate a new hash for optimized chunk |
-| [7c50ba9e2](https://github.com/angular/angular-cli/commit/7c50ba9e2faca445c196c69e972ac313547dda54) | fix | ensure `index.csr.html` is always generated when prerendering or SSR are enabled |
-| [1bb68ba68](https://github.com/angular/angular-cli/commit/1bb68ba6812236a135c1599031bf7e1b7e0d1d79) | fix | move lmdb to optionalDependencies |
-| [9233e5ef4](https://github.com/angular/angular-cli/commit/9233e5ef471e851a173827df7f74a581381c6373) | fix | show error message when error stack is undefined |
-| [6c618d495](https://github.com/angular/angular-cli/commit/6c618d495c54394eb2b87aee36ba5436c06557bd) | fix | update logic to support both internal and external SSR middlewares |
+| Commit | Type | Description |
+| --------------------------------------------------------------------------------------------------- | ---- | ----------------------------------------------------------- |
+| [d749ba6a3](https://github.com/angular/angular-cli/commit/d749ba6a3c3dd7a90317bd9b46e858a842f27696) | fix | allow direct bundling of TSX files with application builder |
+| [b91c82d89](https://github.com/angular/angular-cli/commit/b91c82d8997c0009ed4bbf5e9cd9c82cb1f7f755) | fix | avoid race condition in sass importer |
@@ -321,37 +528,6 @@
-
-
-# 19.0.0-next.9 (2024-10-02)
-
-### @angular-devkit/build-angular
-
-| Commit | Type | Description |
-| --------------------------------------------------------------------------------------------------- | ---- | --------------------------------- |
-| [0a4ef3026](https://github.com/angular/angular-cli/commit/0a4ef302635e4665ae9881746867dd80ca0d2dc7) | feat | karma-coverage w/ app builder |
-| [dcbdca85c](https://github.com/angular/angular-cli/commit/dcbdca85c7fe1a7371b8f6662e0f68e24d56102e) | feat | karma+esbuild+watch |
-| [9d7613db9](https://github.com/angular/angular-cli/commit/9d7613db9bf8b397d5896fcdf6c7b0efeaffa5d5) | fix | zone.js/testing + karma + esbuild |
-
-### @angular/build
-
-| Commit | Type | Description |
-| --------------------------------------------------------------------------------------------------- | ---- | ---------------------------------------------------------------------------------------------------------- |
-| [f63072668](https://github.com/angular/angular-cli/commit/f63072668e44254da78170445ac2417c7bc1aa18) | feat | utilize `ssr.entry` during prerendering to enable access to local API routes |
-| [ecfb2b261](https://github.com/angular/angular-cli/commit/ecfb2b261356946d5f4a653f90c0b78db4ef519c) | fix | add few more SVG elements animateMotion, animateTransform, and feBlend etc. to valid self-closing elements |
-| [87a90afd4](https://github.com/angular/angular-cli/commit/87a90afd4600049b184b32f8f92a0634e25890c0) | fix | incomplete string escaping or encoding |
-| [c0b76e337](https://github.com/angular/angular-cli/commit/c0b76e3377e7f9ded023e5350b9a9ae90a7d31ee) | fix | separate Vite cache by project |
-
-### @angular/ssr
-
-| Commit | Type | Description |
-| --------------------------------------------------------------------------------------------------- | ---- | ------------------------------------------------------------------------------------- |
-| [50df63196](https://github.com/angular/angular-cli/commit/50df631960550049e7d1779fd2c8fbbcf549b8ef) | fix | improve handling of route mismatches between Angular server routes and Angular router |
-| [64c52521d](https://github.com/angular/angular-cli/commit/64c52521d052f850aa7ea1aaadfd8a9fcee9c387) | fix | show error when multiple routes are set with `RenderMode.AppShell` |
-| [12ff37adb](https://github.com/angular/angular-cli/commit/12ff37adbed552fc0db97251c30c889ef00e50f3) | perf | cache generated inline CSS for HTML |
-
-
-
# 18.2.7 (2024-10-02)
@@ -372,44 +548,6 @@
-
-
-# 19.0.0-next.8 (2024-09-26)
-
-### @schematics/angular
-
-| Commit | Type | Description |
-| --------------------------------------------------------------------------------------------------- | ---- | -------------------------------------------------------------------------- |
-| [cfca5442e](https://github.com/angular/angular-cli/commit/cfca5442ec01cc4eff4fe75822eb7ef780ccfef1) | feat | integrate `withEventReplay()` in `provideClientHydration` for new SSR apps |
-| [4179bf2e6](https://github.com/angular/angular-cli/commit/4179bf2e6b38eeddb53b4e9989a7c64238ab23ad) | fix | support single quote setting in JetBrains IDEs |
-
-### @angular-devkit/build-angular
-
-| Commit | Type | Description |
-| --------------------------------------------------------------------------------------------------- | ---- | -------------------------- |
-| [54594b5ab](https://github.com/angular/angular-cli/commit/54594b5abfa4c9301cc369e5dea5f76b71e51ab0) | feat | support karma with esbuild |
-
-### @angular/build
-
-| Commit | Type | Description |
-| --------------------------------------------------------------------------------------------------- | ---- | -------------------------------------------------------- |
-| [3b00fc908](https://github.com/angular/angular-cli/commit/3b00fc908d4f07282e89677928e00665c8578ab5) | feat | introduce `outputMode` option to the application builder |
-| [bbc290133](https://github.com/angular/angular-cli/commit/bbc290133fc93186980ca3c43f221847ba8e858a) | feat | utilize `ssr.entry` in Vite dev-server when available |
-| [dd499499c](https://github.com/angular/angular-cli/commit/dd499499c7e5aeb959cdb1a4442493091c07d667) | fix | add `animate` to valid self-closing elements |
-| [13a3e430d](https://github.com/angular/angular-cli/commit/13a3e430da894fee87e4279f51b166f657b29b3f) | fix | allow missing HTML file request to fallback to index |
-| [a995c8ea6](https://github.com/angular/angular-cli/commit/a995c8ea6d17778af031c2f9797e52739ea4dc81) | fix | prevent prerendering of catch-all routes |
-
-### @angular/ssr
-
-| Commit | Type | Description |
-| --------------------------------------------------------------------------------------------------- | ---- | ------------------------------------------------------------------- |
-| [92209dd2e](https://github.com/angular/angular-cli/commit/92209dd2e93af450e3fc657609efe95c6a6b3963) | feat | add `createRequestHandler` and `createNodeRequestHandler `utilities |
-| [65b6e75a5](https://github.com/angular/angular-cli/commit/65b6e75a5dca581a57a9ac3d61869fdd20f7dc2e) | fix | export `RESPONSE_INIT`, `REQUEST`, and `REQUEST_CONTEXT` tokens |
-| [1d70e3b46](https://github.com/angular/angular-cli/commit/1d70e3b4682806a55d6f7ddacbafcbf615b2a10c) | perf | cache resolved entry-points |
-| [e52ae7f6f](https://github.com/angular/angular-cli/commit/e52ae7f6f5296a9628cc4a517e82339ac54925bb) | perf | prevent potential stampede in entry-points cache |
-
-
-
# 18.2.6 (2024-09-25)
@@ -435,31 +573,6 @@
-
-
-# 19.0.0-next.7 (2024-09-18)
-
-### @angular/cli
-
-| Commit | Type | Description |
-| --------------------------------------------------------------------------------------------------- | ---- | ------------------------------------------------ |
-| [f249e7e85](https://github.com/angular/angular-cli/commit/f249e7e856bf16e8c5f154fdb8aff36421649a1b) | perf | enable Node.js compile code cache when available |
-
-### @angular/build
-
-| Commit | Type | Description |
-| --------------------------------------------------------------------------------------------------- | ---- | --------------------------------------------------- |
-| [f6b7cd925](https://github.com/angular/angular-cli/commit/f6b7cd925dacf0ae34cb8e49b4deaf2e5c52ccd4) | fix | support HTTP HEAD requests for virtual output files |
-
-### @angular/ssr
-
-| Commit | Type | Description |
-| --------------------------------------------------------------------------------------------------- | ---- | ------------------------------------------- |
-| [f346ee8a8](https://github.com/angular/angular-cli/commit/f346ee8a8819bb2eaf0ffb3d5523b00093be09e5) | feat | add `isMainModule` function |
-| [2640bf7a6](https://github.com/angular/angular-cli/commit/2640bf7a680300acf18cf6502c57a00e0a5bfda9) | fix | correct route extraction and error handling |
-
-
-
# 18.2.5 (2024-09-18)
@@ -485,69 +598,6 @@
-
-
-# 19.0.0-next.6 (2024-09-13)
-
-### @angular/cli
-
-| Commit | Type | Description |
-| --------------------------------------------------------------------------------------------------- | ---- | -------------------------------------------------------------- |
-| [de17cbcb8](https://github.com/angular/angular-cli/commit/de17cbcb88e1f057f93d366c3e2eac4315986e54) | fix | Revert commit enable Node.js compile code cache when available |
-
-### @angular/ssr
-
-| Commit | Type | Description |
-| --------------------------------------------------------------------------------------------------- | ---- | ------------------------------------ |
-| [d66aaa3ca](https://github.com/angular/angular-cli/commit/d66aaa3ca458e05b535bec7c1dcb98b0e9c5202e) | feat | add server routing configuration API |
-
-
-
-
-
-# 19.0.0-next.5 (2024-09-12)
-
-### @angular-devkit/architect
-
-| Commit | Type | Description |
-| --------------------------------------------------------------------------------------------------- | ---- | ----------------------------- |
-| [78f76485f](https://github.com/angular/angular-cli/commit/78f76485fe315ffd0262c1a3732092731235828b) | feat | merge object options from CLI |
-
-### @angular/ssr
-
-| Commit | Type | Description |
-| --------------------------------------------------------------------------------------------------- | ---- | ------------------------------------------- |
-| [85df4011b](https://github.com/angular/angular-cli/commit/85df4011ba27254ddb7f22dae550272c9c4406dd) | fix | resolve `bootstrap is not a function` error |
-
-
-
-
-
-# 19.0.0-next.4 (2024-09-11)
-
-### @angular/cli
-
-| Commit | Type | Description |
-| --------------------------------------------------------------------------------------------------- | ---- | ------------------------------------------------ |
-| [201b60e1d](https://github.com/angular/angular-cli/commit/201b60e1dd25b4abb7670e21d103b67d4eda0e14) | feat | handle string key/value pairs, e.g. --define |
-| [ecc107d83](https://github.com/angular/angular-cli/commit/ecc107d83bfdfd9d5dd1087e264892d60361625c) | perf | enable Node.js compile code cache when available |
-
-### @angular/build
-
-| Commit | Type | Description |
-| --------------------------------------------------------------------------------------------------- | ---- | --------------------------------------------------------------- |
-| [4153a6ecf](https://github.com/angular/angular-cli/commit/4153a6ecf6707729a4f0c6a620eeb8d6916588df) | fix | prevent transformation of Node.js internal dependencies by Vite |
-
-### @angular/ssr
-
-| Commit | Type | Description |
-| --------------------------------------------------------------------------------------------------- | ---- | ---------------------------------------------------------------------------------------------------------- |
-| [41fb2ed86](https://github.com/angular/angular-cli/commit/41fb2ed86056306406832317178ca5d94aa110e2) | feat | Add `getHeaders` Method to `AngularAppEngine` and `AngularNodeAppEngine` for handling pages static headers |
-| [576ff604c](https://github.com/angular/angular-cli/commit/576ff604cd739a9f41d588fa093ca2568e46826c) | feat | introduce `AngularNodeAppEngine` API for Node.js integration |
-| [e9c9e4995](https://github.com/angular/angular-cli/commit/e9c9e4995e39d9d62d10fe0497e0b98127bc8afa) | fix | resolve circular dependency issue from main.server.js reference in manifest |
-
-
-
# 18.2.4 (2024-09-11)
@@ -560,24 +610,6 @@
-
-
-# 19.0.0-next.3 (2024-09-04)
-
-### @angular-devkit/build-angular
-
-| Commit | Type | Description |
-| --------------------------------------------------------------------------------------------------- | ---- | ----------------------------------------------------------- |
-| [3ee21631f](https://github.com/angular/angular-cli/commit/3ee21631f481b2e72be2390b5a2cac74824efbb5) | fix | clear context in Karma by default for single run executions |
-
-### @angular/ssr
-
-| Commit | Type | Description |
-| --------------------------------------------------------------------------------------------------- | ---- | ---------------------------------------------------------------------------------------- |
-| [455b5700c](https://github.com/angular/angular-cli/commit/455b5700c29845829235e17efec320e634553108) | feat | expose `writeResponseToNodeResponse` and `createWebRequestFromNodeRequest` in public API |
-
-
-
# 18.2.3 (2024-09-04)
@@ -603,44 +635,6 @@
-
-
-# 19.0.0-next.2 (2024-08-28)
-
-## Breaking Changes
-
-### @angular/ssr
-
-- The `CommonEngine` API now needs to be imported from `@angular/ssr/node`.
-
- **Before**
-
- ```ts
- import { CommonEngine } from '@angular/ssr';
- ```
-
- **After**
-
- ```ts
- import { CommonEngine } from '@angular/ssr/node';
- ```
-
-### @schematics/angular
-
-| Commit | Type | Description |
-| --------------------------------------------------------------------------------------------------- | ---- | ----------------------------------------- |
-| [a381a3db1](https://github.com/angular/angular-cli/commit/a381a3db187f7b20e5ec8d1e1a1f1bd860426fcd) | feat | add option to export component as default |
-
-### @angular/ssr
-
-| Commit | Type | Description |
-| --------------------------------------------------------------------------------------------------- | ---- | --------------------------------------------------------- |
-| [30c25bf68](https://github.com/angular/angular-cli/commit/30c25bf6885fefea6094ec1815e066e4c6ada097) | feat | export `AngularAppEngine` as public API |
-| [4b09887a9](https://github.com/angular/angular-cli/commit/4b09887a9c82838ccb7a6c95d66225c7875e562b) | feat | move `CommonEngine` API to `/node` entry-point |
-| [d43180af5](https://github.com/angular/angular-cli/commit/d43180af5f3e7b29387fd06625bd8e37f3ebad95) | fix | add missing peer dependency on `@angular/platform-server` |
-
-
-
# 17.3.9 (2024-08-29)
@@ -667,61 +661,6 @@
-
-
-# 19.0.0-next.1 (2024-08-22)
-
-## Breaking Changes
-
-### @angular-devkit/build-angular
-
-- The `browserTarget` option has been removed from the DevServer and ExtractI18n builders. `buildTarget` is to be used instead.
-
-### @angular-devkit/core
-
-- The deprecated `fileBuffer` function is no longer available. Update your code to use `stringToFileBuffer` instead to maintain compatibility.
-
- **Note:** that this change does not affect application developers.
-
-### @angular/build
-
-- The `@angular/localize/init` polyfill will no longer be added automatically to projects. To prevent runtime issues, ensure that this polyfill is manually included in the "polyfills" section of your "angular.json" file if your application relies on Angular localization features.
-
-### @schematics/angular
-
-| Commit | Type | Description |
-| --------------------------------------------------------------------------------------------------- | ---- | ---------------------------------------------------------- |
-| [7992218a9](https://github.com/angular/angular-cli/commit/7992218a9c22ea9469bd3386c7dc1d5efc6e51f9) | fix | remove `declaration` and `sourceMap` from default tsconfig |
-
-### @angular-devkit/build-angular
-
-| Commit | Type | Description |
-| --------------------------------------------------------------------------------------------------- | -------- | ---------------------------------------------- |
-| [0b161bc76](https://github.com/angular/angular-cli/commit/0b161bc7616bef9a8f1f9113a50b07291635159d) | fix | remove outdated browser-esbuild option warning |
-| [e40384e63](https://github.com/angular/angular-cli/commit/e40384e637bc6f92c28f4e572f986ca902938ba2) | refactor | remove deprecated `browserTarget` |
-
-### @angular-devkit/core
-
-| Commit | Type | Description |
-| --------------------------------------------------------------------------------------------------- | -------- | ------------------------------------------------------------------------ |
-| [0d8a1006d](https://github.com/angular/angular-cli/commit/0d8a1006d4629d8af1144065ea237ab30916e66e) | refactor | remove deprecated `fileBuffer` function in favor of `stringToFileBuffer` |
-
-### @angular/build
-
-| Commit | Type | Description |
-| --------------------------------------------------------------------------------------------------- | -------- | ----------------------------------------------------------------------------------- |
-| [71c06c69f](https://github.com/angular/angular-cli/commit/71c06c69f6f472e5ea86f2e5adbd5062a8cc5f2a) | fix | improve error message when an unhandled exception occurs during prerendering |
-| [6b544f70e](https://github.com/angular/angular-cli/commit/6b544f70e706b9e13564d4ddbb0f0cb352942b2c) | fix | support reading on-disk files during i18n extraction |
-| [d6a34034d](https://github.com/angular/angular-cli/commit/d6a34034d7489c09753090642ade4c606cd98ece) | refactor | remove automatic addition of `@angular/localize/init` polyfill and related warnings |
-
-### @angular/ssr
-
-| Commit | Type | Description |
-| --------------------------------------------------------------------------------------------------- | ---- | ---------------------------------------------------------- |
-| [9692a9054](https://github.com/angular/angular-cli/commit/9692a9054c3cdbf151df01279c2d268332b1a032) | feat | improve handling of aborted requests in `AngularServerApp` |
-
-
-
# 18.2.1 (2024-08-21)
@@ -753,32 +692,6 @@
-
-
-# 19.0.0-next.0 (2024-08-14)
-
-### @angular/cli
-
-| Commit | Type | Description |
-| --------------------------------------------------------------------------------------------------- | ---- | --------------------------------------------------------------- |
-| [c5ed0b124](https://github.com/angular/angular-cli/commit/c5ed0b1248dc2d5f895f4c4dc6737269a4854a1e) | fix | prevent bypassing select/checkbox prompts on validation failure |
-
-### @angular-devkit/schematics-cli
-
-| Commit | Type | Description |
-| --------------------------------------------------------------------------------------------------- | ---- | --------------------------------------------------------------- |
-| [37693c40e](https://github.com/angular/angular-cli/commit/37693c40e3afc4c6dd7c949ea658bdf94146c9d8) | feat | add package manager option to blank schematic |
-| [73c243796](https://github.com/angular/angular-cli/commit/73c24379651695d8bb82602ab613e568f1233c2c) | fix | prevent bypassing select/checkbox prompts on validation failure |
-
-### @angular/ssr
-
-| Commit | Type | Description |
-| --------------------------------------------------------------------------------------------------- | ---- | --------------------------------------------- |
-| [bca568389](https://github.com/angular/angular-cli/commit/bca56838937f942c5ef902f5c98d018582188e84) | feat | dynamic route resolution using Angular router |
-| [3c9697a8c](https://github.com/angular/angular-cli/commit/3c9697a8c34a5e0f3470bde73f11f9f32107f42e) | feat | introduce new hybrid rendering API |
-
-
-
# 18.2.0 (2024-08-14)
diff --git a/README.md b/README.md
index d32f1fe07916..21a7d3f13398 100644
--- a/README.md
+++ b/README.md
@@ -122,7 +122,7 @@ Read through our [developer guide][developer] to learn about how to build and te
Join the conversation and help the community.
-- [Twitter][twitter]
+- [X (formerly Twitter)][twitter]
- [Discord][discord]
- [Gitter][gitter]
- [YouTube][youtube]
@@ -189,7 +189,7 @@ This is a monorepo which contains many tools and packages:
[node.js]: https://nodejs.org/
[npm]: https://www.npmjs.com/get-npm
[codeofconduct]: https://github.com/angular/angular/blob/main/CODE_OF_CONDUCT.md
-[twitter]: https://www.twitter.com/angular
+[twitter]: https://www.x.com/angular
[discord]: https://discord.gg/angular
[gitter]: https://gitter.im/angular/angular-cli
[stackoverflow]: https://stackoverflow.com/questions/tagged/angular-cli
diff --git a/WORKSPACE b/WORKSPACE
index 0433441fa3e3..5b44f8a27c6c 100644
--- a/WORKSPACE
+++ b/WORKSPACE
@@ -1,9 +1,14 @@
-workspace(
- name = "angular_cli",
- managed_directories = {"@npm": ["node_modules"]},
+workspace(name = "angular_cli")
+
+DEFAULT_NODE_VERSION = "18.19.1"
+
+# Workaround for: https://github.com/bazel-contrib/bazel-lib/issues/968.
+# Override toolchain for tar on windows.
+register_toolchains(
+ "//tools:windows_tar_system_toolchain",
)
-load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")
+load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive", "http_file")
http_archive(
name = "bazel_skylib",
@@ -30,6 +35,17 @@ load("@build_bazel_rules_nodejs//:repositories.bzl", "build_bazel_rules_nodejs_d
build_bazel_rules_nodejs_dependencies()
+http_archive(
+ name = "aspect_rules_js",
+ sha256 = "fbc34d815a0cc52183a1a26732fc0329e26774a51abbe0f26fc9fd2dab6133b4",
+ strip_prefix = "rules_js-2.1.2",
+ url = "https://github.com/aspect-build/rules_js/releases/download/v2.1.2/rules_js-v2.1.2.tar.gz",
+)
+
+load("@aspect_rules_js//js:repositories.bzl", "rules_js_dependencies")
+
+rules_js_dependencies()
+
http_archive(
name = "rules_pkg",
sha256 = "8c20f74bca25d2d442b327ae26768c02cf3c99e93fad0381f32be9aab1967675",
@@ -73,7 +89,7 @@ nodejs_register_toolchains(
name = "nodejs",
# The below can be removed once @rules_nodejs/nodejs is updated to latest which contains https://github.com/bazelbuild/rules_nodejs/pull/3701
node_repositories = NODE_18_REPO,
- node_version = "18.19.1",
+ node_version = DEFAULT_NODE_VERSION,
)
nodejs_register_toolchains(
@@ -106,34 +122,38 @@ nodejs_register_toolchains(
node_version = "22.0.0",
)
+load("@aspect_rules_js//js:toolchains.bzl", "rules_js_register_toolchains")
+
+rules_js_register_toolchains(
+ node_repositories = NODE_18_REPO,
+ node_version = DEFAULT_NODE_VERSION,
+)
+
load("@build_bazel_rules_nodejs//:index.bzl", "yarn_install")
yarn_install(
name = "npm",
data = [
- "//:.yarn/patches/@angular-bazel-https-9848736cf4.patch",
- "//:.yarn/patches/@bazel-concatjs-npm-5.8.1-1bf81df846.patch",
- "//:.yarn/patches/@bazel-jasmine-npm-5.8.1-3370fee155.patch",
"//:.yarn/releases/yarn-4.5.0.cjs",
"//:.yarnrc.yml",
+ "//:patches/@angular+bazel+19.0.0-next.7.patch",
+ "//:patches/@bazel+concatjs+5.8.1.patch",
+ "//:patches/@bazel+jasmine+5.8.1.patch",
],
# Currently disabled due to:
# 1. Missing Windows support currently.
# 2. Incompatibilites with the `ts_library` rule.
exports_directories_only = False,
package_json = "//:package.json",
- # We prefer to symlink the `node_modules` to only maintain a single install.
- # See https://github.com/angular/dev-infra/pull/446#issuecomment-1059820287 for details.
- symlink_node_modules = True,
yarn = "//:.yarn/releases/yarn-4.5.0.cjs",
yarn_lock = "//:yarn.lock",
)
http_archive(
name = "aspect_bazel_lib",
- sha256 = "349aabd3c2b96caeda6181eb0ae1f14f2a1d9f3cd3c8b05d57f709ceb12e9fb3",
- strip_prefix = "bazel-lib-2.9.4",
- url = "https://github.com/aspect-build/bazel-lib/releases/download/v2.9.4/bazel-lib-v2.9.4.tar.gz",
+ sha256 = "7b39d9f38b82260a8151b18dd4a6219d2d7fc4a0ac313d4f5a630ae6907d205d",
+ strip_prefix = "bazel-lib-2.10.0",
+ url = "https://github.com/aspect-build/bazel-lib/releases/download/v2.10.0/bazel-lib-v2.10.0.tar.gz",
)
load("@aspect_bazel_lib//lib:repositories.bzl", "aspect_bazel_lib_dependencies", "aspect_bazel_lib_register_toolchains")
@@ -158,3 +178,50 @@ load("@build_bazel_rules_nodejs//toolchains/esbuild:esbuild_repositories.bzl", "
esbuild_repositories(
npm_repository = "npm",
)
+
+load("@aspect_rules_js//npm:repositories.bzl", "npm_translate_lock")
+
+npm_translate_lock(
+ name = "npm2",
+ data = [
+ "//:package.json",
+ "//:pnpm-workspace.yaml",
+ ],
+ npmrc = "//:.npmrc",
+ patches = {
+ # Note: Patches not needed as the existing patches are only
+ # for `rules_nodejs` dependencies :)
+ },
+ pnpm_lock = "//:pnpm-lock.yaml",
+ update_pnpm_lock = True,
+ verify_node_modules_ignored = "//:.bazelignore",
+ yarn_lock = "//:yarn.lock",
+)
+
+load("@npm2//:repositories.bzl", "npm_repositories")
+
+npm_repositories()
+
+http_archive(
+ name = "aspect_rules_ts",
+ patch_args = ["-p1"],
+ patches = ["//tools:rules_ts_windows.patch"],
+ sha256 = "cff3137b043ff6bf1a2542fd9691dc762432370cd39eb4bb0756d288de52067d",
+ strip_prefix = "rules_ts-3.3.2",
+ url = "https://github.com/aspect-build/rules_ts/releases/download/v3.3.2/rules_ts-v3.3.2.tar.gz",
+)
+
+load("@aspect_rules_ts//ts:repositories.bzl", "rules_ts_dependencies")
+
+rules_ts_dependencies(
+ # ts_version_from = "//:package.json",
+ # Obtained by: curl --silent https://registry.npmjs.org/typescript/5.7.2 | jq -r '.dist.integrity'
+ ts_integrity = "sha512-i5t66RHxDvVN40HfDd1PsEThGNnlMCMT3jMUuoh9/0TaqWevNontacunWyN02LA9/fIbEWlcHZcgTKb9QoaLfg==",
+ ts_version = "5.7.2",
+)
+
+http_file(
+ name = "tsc_worker",
+ sha256 = "",
+ urls = ["https://raw.githubusercontent.com/devversion/rules_angular/a270a74d1e64577bddba96a5484c7c5d2c5d2770/dist/worker.mjs"],
+)
diff --git a/goldens/public-api/angular/ssr/node/index.api.md b/goldens/public-api/angular/ssr/node/index.api.md
index 6488d0f363dd..0bbeb8ae145a 100644
--- a/goldens/public-api/angular/ssr/node/index.api.md
+++ b/goldens/public-api/angular/ssr/node/index.api.md
@@ -57,7 +57,7 @@ export function isMainModule(url: string): boolean;
export type NodeRequestHandlerFunction = (req: IncomingMessage, res: ServerResponse, next: (err?: unknown) => void) => Promise | void;
// @public
-export function writeResponseToNodeResponse(source: Response, destination: ServerResponse | Http2ServerResponse): Promise;
+export function writeResponseToNodeResponse(source: Response, destination: ServerResponse | Http2ServerResponse): Promise;
// (No @packageDocumentation comment for this package)
diff --git a/goldens/public-api/angular_devkit/schematics/index.api.md b/goldens/public-api/angular_devkit/schematics/index.api.md
index e72c46745599..de17a0462162 100644
--- a/goldens/public-api/angular_devkit/schematics/index.api.md
+++ b/goldens/public-api/angular_devkit/schematics/index.api.md
@@ -206,6 +206,8 @@ export interface CreateFileAction extends ActionBase {
export class DelegateTree implements Tree_2 {
constructor(_other: Tree_2);
// (undocumented)
+ [x: symbol]: () => this;
+ // (undocumented)
get actions(): Action[];
// (undocumented)
apply(action: Action, strategy?: MergeStrategy): void;
@@ -518,6 +520,8 @@ export class HostSink extends SimpleSinkBase {
export class HostTree implements Tree_2 {
constructor(_backend?: virtualFs.ReadonlyHost<{}>);
// (undocumented)
+ [x: symbol]: () => this;
+ // (undocumented)
get actions(): Action[];
// (undocumented)
apply(action: Action, strategy?: MergeStrategy): void;
diff --git a/modules/testing/builder/BUILD.bazel b/modules/testing/builder/BUILD.bazel
index bcc6d347cbec..1e0cbcbf33cb 100644
--- a/modules/testing/builder/BUILD.bazel
+++ b/modules/testing/builder/BUILD.bazel
@@ -1,9 +1,9 @@
load("@npm//@bazel/jasmine:index.bzl", "jasmine_node_test")
-load("//tools:defaults.bzl", "ts_library")
+load("//tools:interop.bzl", "ts_project")
package(default_visibility = ["//visibility:public"])
-ts_library(
+ts_project(
name = "builder",
testonly = True,
srcs = glob(
@@ -16,16 +16,16 @@ ts_library(
),
data = glob(["projects/**/*"]),
deps = [
- "//packages/angular_devkit/architect",
- "//packages/angular_devkit/architect/node",
- "//packages/angular_devkit/architect/testing",
- "//packages/angular_devkit/core",
- "//packages/angular_devkit/core/node",
- "@npm//rxjs",
+ "//:root_modules/rxjs",
+ "//packages/angular_devkit/architect:architect_rjs",
+ "//packages/angular_devkit/architect/node:node_rjs",
+ "//packages/angular_devkit/architect/testing:testing_rjs",
+ "//packages/angular_devkit/core:core_rjs",
+ "//packages/angular_devkit/core/node:node_rjs",
],
)
-ts_library(
+ts_project(
name = "unit_test_lib",
testonly = True,
srcs = glob(
@@ -34,8 +34,8 @@ ts_library(
],
),
deps = [
- ":builder",
- "//packages/angular_devkit/architect/testing",
+ ":builder_rjs",
+ "//packages/angular_devkit/architect/testing:testing_rjs",
],
)
diff --git a/modules/testing/builder/projects/hello-world-app/protractor.conf.js b/modules/testing/builder/projects/hello-world-app/protractor.conf.js
index 89b7edda6324..313f7ac7c53b 100644
--- a/modules/testing/builder/projects/hello-world-app/protractor.conf.js
+++ b/modules/testing/builder/projects/hello-world-app/protractor.conf.js
@@ -18,7 +18,7 @@ exports.config = {
capabilities: {
browserName: 'chrome',
chromeOptions: {
- args: ['--headless', '--disable-gpu', '--window-size=800,600'],
+ args: ['--headless', '--no-sandbox', '--disable-gpu', '--disable-dev-shm-usage'],
binary: require('puppeteer').executablePath(),
},
},
diff --git a/modules/testing/builder/src/builder-harness.ts b/modules/testing/builder/src/builder-harness.ts
index d38858565389..9fdca970a277 100644
--- a/modules/testing/builder/src/builder-harness.ts
+++ b/modules/testing/builder/src/builder-harness.ts
@@ -54,6 +54,11 @@ export interface BuilderHarnessExecutionOptions {
outputLogsOnException: boolean;
useNativeFileWatching: boolean;
signal: AbortSignal;
+ additionalExecuteArguments: unknown[];
+}
+
+interface BuilderHandlerFnWithVarArgs extends BuilderHandlerFn {
+ (input: T, context: BuilderContext, ...args: unknown[]): BuilderOutputLike;
}
/**
@@ -256,7 +261,13 @@ export class BuilderHarness {
mergeMap((validator) => validator(targetOptions)),
map((validationResult) => validationResult.data),
mergeMap((data) =>
- convertBuilderOutputToObservable(this.builderHandler(data as T & json.JsonObject, context)),
+ convertBuilderOutputToObservable(
+ (this.builderHandler as BuilderHandlerFnWithVarArgs)(
+ data as T & json.JsonObject,
+ context,
+ ...(options.additionalExecuteArguments ?? []),
+ ),
+ ),
),
map((buildResult) => ({ result: buildResult, error: undefined })),
catchError((error) => {
diff --git a/package.json b/package.json
index a080762f64af..af7effcfcf16 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "@angular/devkit-repo",
- "version": "19.1.0-next.0",
+ "version": "19.1.0-next.2",
"private": true,
"description": "Software Development Kit for Angular",
"keywords": [
@@ -10,7 +10,6 @@
"sdk",
"Angular DevKit"
],
- "packageManager": "yarn@4.5.0",
"scripts": {
"admin": "node --no-warnings=ExperimentalWarning --loader ts-node/esm/transpile-only ./scripts/devkit-admin.mjs",
"test": "bazel test //packages/...",
@@ -18,7 +17,7 @@
"lint": "eslint --cache --max-warnings=0 \"**/*.@(ts|mts|cts)\"",
"templates": "yarn admin templates",
"validate": "yarn admin validate",
- "postinstall": "yarn webdriver-update && yarn husky",
+ "postinstall": "patch-package && yarn webdriver-update && yarn husky",
"//webdriver-update-README": "ChromeDriver version must match Puppeteer Chromium version, see https://github.com/GoogleChrome/puppeteer/releases http://chromedriver.chromium.org/downloads",
"webdriver-update": "webdriver-manager update --standalone false --gecko false --versions.chrome 106.0.5249.21",
"public-api:check": "node goldens/public-api/manage.js test",
@@ -33,7 +32,6 @@
},
"engines": {
"node": "^18.19.1 || ^20.11.1 || >=22.0.0",
- "yarn": ">=1.21.1 <2",
"npm": "Please use yarn instead of NPM to install dependencies"
},
"author": "Angular Authors",
@@ -42,35 +40,27 @@
"url": "https://github.com/angular/angular-cli/issues"
},
"homepage": "https://github.com/angular/angular-cli",
- "workspaces": {
- "packages": [
- "packages/angular/*",
- "packages/angular_devkit/*",
- "packages/ngtools/*",
- "packages/schematics/*"
- ]
- },
"devDependencies": {
"@ampproject/remapping": "2.3.0",
- "@angular/animations": "19.0.0-rc.3",
- "@angular/bazel": "patch:@angular/bazel@https%3A//github.com/angular/bazel-builds.git%23commit=07617f0f8540d27f8895b1820a6f994e1e5b7277#~/.yarn/patches/@angular-bazel-https-9848736cf4.patch",
- "@angular/build-tooling": "https://github.com/angular/dev-infra-private-build-tooling-builds.git#3ba5a1f997a072caffcf19f9c767e7e570043898",
- "@angular/cdk": "19.0.0-rc.3",
- "@angular/common": "19.0.0-rc.3",
- "@angular/compiler": "19.0.0-rc.3",
- "@angular/compiler-cli": "19.0.0-rc.3",
- "@angular/core": "19.0.0-rc.3",
- "@angular/forms": "19.0.0-rc.3",
- "@angular/localize": "19.0.0-rc.3",
- "@angular/material": "19.0.0-rc.3",
- "@angular/ng-dev": "https://github.com/angular/dev-infra-private-ng-dev-builds.git#5f5021669687fdd811f916dc9699eca753ab4a13",
- "@angular/platform-browser": "19.0.0-rc.3",
- "@angular/platform-browser-dynamic": "19.0.0-rc.3",
- "@angular/platform-server": "19.0.0-rc.3",
- "@angular/router": "19.0.0-rc.3",
- "@angular/service-worker": "19.0.0-rc.3",
+ "@angular/animations": "19.1.0-next.4",
+ "@angular/bazel": "https://github.com/angular/bazel-builds.git#722eb514ccf42aaca1642c42ec2d1b70a632142e",
+ "@angular/build-tooling": "https://github.com/angular/dev-infra-private-build-tooling-builds.git#2dba82de08e41f38a47b088384f591f30fd9de19",
+ "@angular/cdk": "19.1.0-next.3",
+ "@angular/common": "19.1.0-next.4",
+ "@angular/compiler": "19.1.0-next.4",
+ "@angular/compiler-cli": "19.1.0-next.4",
+ "@angular/core": "19.1.0-next.4",
+ "@angular/forms": "19.1.0-next.4",
+ "@angular/localize": "19.1.0-next.4",
+ "@angular/material": "19.1.0-next.3",
+ "@angular/ng-dev": "https://github.com/angular/dev-infra-private-ng-dev-builds.git#e4435ccb1b275541908b57b02958dc453c2fed43",
+ "@angular/platform-browser": "19.1.0-next.4",
+ "@angular/platform-browser-dynamic": "19.1.0-next.4",
+ "@angular/platform-server": "19.1.0-next.4",
+ "@angular/router": "19.1.0-next.4",
+ "@angular/service-worker": "19.1.0-next.4",
"@babel/core": "7.26.0",
- "@babel/generator": "7.26.2",
+ "@babel/generator": "7.26.3",
"@babel/helper-annotate-as-pure": "7.25.9",
"@babel/helper-split-export-declaration": "7.24.7",
"@babel/plugin-syntax-import-attributes": "7.26.0",
@@ -79,21 +69,22 @@
"@babel/plugin-transform-runtime": "7.25.9",
"@babel/preset-env": "7.26.0",
"@babel/runtime": "7.26.0",
- "@bazel/bazelisk": "1.23.0",
+ "@bazel/bazelisk": "1.25.0",
"@bazel/buildifier": "7.3.1",
- "@bazel/concatjs": "patch:@bazel/concatjs@npm%3A5.8.1#~/.yarn/patches/@bazel-concatjs-npm-5.8.1-1bf81df846.patch",
- "@bazel/jasmine": "patch:@bazel/jasmine@npm%3A5.8.1#~/.yarn/patches/@bazel-jasmine-npm-5.8.1-3370fee155.patch",
+ "@bazel/concatjs": "5.8.1",
+ "@bazel/jasmine": "5.8.1",
"@bazel/rollup": "^5.8.1",
"@bazel/runfiles": "^5.8.1",
"@discoveryjs/json-ext": "0.6.3",
- "@inquirer/confirm": "5.0.2",
- "@inquirer/prompts": "7.1.0",
+ "@inquirer/confirm": "5.1.1",
+ "@inquirer/prompts": "7.2.1",
"@listr2/prompt-adapter-inquirer": "2.0.18",
"@rollup/plugin-alias": "^5.1.1",
"@rollup/plugin-commonjs": "^28.0.0",
"@rollup/plugin-node-resolve": "^13.0.5",
"@stylistic/eslint-plugin": "^2.8.0",
"@types/babel__core": "7.20.5",
+ "@types/babel__generator": "^7.6.8",
"@types/browser-sync": "^2.27.0",
"@types/express": "^4.16.0",
"@types/http-proxy": "^1.17.4",
@@ -111,14 +102,13 @@
"@types/resolve": "^1.17.1",
"@types/semver": "^7.3.12",
"@types/shelljs": "^0.8.11",
- "@types/tar": "^6.1.2",
"@types/watchpack": "^2.4.4",
"@types/yargs": "^17.0.20",
"@types/yargs-parser": "^21.0.0",
"@types/yarnpkg__lockfile": "^1.1.5",
- "@typescript-eslint/eslint-plugin": "8.14.0",
- "@typescript-eslint/parser": "8.14.0",
- "@vitejs/plugin-basic-ssl": "1.1.0",
+ "@typescript-eslint/eslint-plugin": "8.18.1",
+ "@typescript-eslint/parser": "8.18.1",
+ "@vitejs/plugin-basic-ssl": "1.2.0",
"@web/test-runner": "^0.19.0",
"@yarnpkg/lockfile": "1.1.0",
"ajv": "8.17.1",
@@ -126,30 +116,30 @@
"ansi-colors": "4.1.3",
"autoprefixer": "10.4.20",
"babel-loader": "9.2.1",
- "beasties": "0.1.0",
+ "beasties": "0.2.0",
"browser-sync": "3.0.3",
"browserslist": "^4.21.5",
"buffer": "6.0.3",
- "chokidar": "4.0.1",
+ "chokidar": "4.0.3",
"copy-webpack-plugin": "12.0.2",
"css-loader": "7.1.2",
"debug": "^4.1.1",
- "esbuild": "0.24.0",
- "esbuild-wasm": "0.24.0",
+ "esbuild": "0.24.2",
+ "esbuild-wasm": "0.24.2",
"eslint": "8.57.0",
"eslint-config-prettier": "9.1.0",
"eslint-plugin-header": "3.1.1",
"eslint-plugin-import": "2.31.0",
- "express": "4.21.1",
+ "express": "4.21.2",
"fast-glob": "3.3.2",
"http-proxy": "^1.18.1",
"http-proxy-middleware": "3.0.3",
- "https-proxy-agent": "7.0.5",
- "husky": "9.1.6",
+ "https-proxy-agent": "7.0.6",
+ "husky": "9.1.7",
"ini": "5.0.0",
"istanbul-lib-instrument": "6.0.3",
"jasmine": "^5.0.0",
- "jasmine-core": "~5.4.0",
+ "jasmine-core": "~5.5.0",
"jasmine-spec-reporter": "~7.0.0",
"jsonc-parser": "3.3.1",
"karma": "~6.4.0",
@@ -158,26 +148,27 @@
"karma-jasmine": "~5.1.0",
"karma-jasmine-html-reporter": "~2.1.0",
"karma-source-map-support": "1.4.0",
- "less": "4.2.0",
+ "less": "4.2.1",
"less-loader": "12.2.0",
"license-webpack-plugin": "4.0.2",
"listr2": "8.2.5",
- "lmdb": "3.1.5",
+ "lmdb": "3.2.0",
"loader-utils": "3.3.1",
"lodash": "^4.17.21",
- "magic-string": "0.30.12",
+ "magic-string": "0.30.17",
"mini-css-extract-plugin": "2.9.2",
"mrmime": "2.0.0",
- "ng-packagr": "19.0.0-rc.0",
- "npm": "^10.8.1",
- "npm-package-arg": "12.0.0",
+ "ng-packagr": "19.1.0-next.2",
+ "npm": "^11.0.0",
+ "npm-package-arg": "12.0.1",
"npm-pick-manifest": "10.0.0",
"open": "10.1.0",
"ora": "5.4.1",
"pacote": "20.0.0",
"parse5-html-rewriting-stream": "7.0.0",
+ "patch-package": "^8.0.0",
"picomatch": "4.0.2",
- "piscina": "4.7.0",
+ "piscina": "4.8.0",
"postcss": "8.4.49",
"postcss-loader": "8.1.1",
"prettier": "^3.0.0",
@@ -185,33 +176,33 @@
"puppeteer": "18.2.1",
"quicktype-core": "23.0.170",
"resolve-url-loader": "5.0.0",
- "rollup": "4.26.0",
+ "rollup": "4.29.1",
"rollup-license-plugin": "~3.0.1",
"rollup-plugin-sourcemaps": "^0.6.0",
"rxjs": "7.8.1",
- "sass": "1.80.7",
- "sass-loader": "16.0.3",
+ "sass": "1.83.0",
+ "sass-loader": "16.0.4",
"semver": "7.6.3",
"shelljs": "^0.8.5",
"source-map": "0.7.4",
"source-map-loader": "5.0.0",
"source-map-support": "0.5.21",
"symbol-observable": "4.0.0",
- "tar": "^6.1.6",
- "terser": "5.36.0",
+ "tar": "^7.0.0",
+ "terser": "5.37.0",
"tree-kill": "1.2.2",
"ts-node": "^10.9.1",
"tslib": "2.8.1",
- "typescript": "5.6.3",
- "undici": "6.21.0",
+ "typescript": "5.7.2",
+ "undici": "7.2.0",
"unenv": "^1.10.0",
- "verdaccio": "6.0.1",
+ "verdaccio": "6.0.5",
"verdaccio-auth-memory": "^10.0.0",
- "vite": "5.4.11",
+ "vite": "6.0.5",
"watchpack": "2.4.2",
- "webpack": "5.96.1",
+ "webpack": "5.97.1",
"webpack-dev-middleware": "7.4.2",
- "webpack-dev-server": "5.1.0",
+ "webpack-dev-server": "5.2.0",
"webpack-merge": "6.0.1",
"webpack-subresource-integrity": "5.1.0",
"yargs": "17.7.2",
@@ -226,8 +217,10 @@
"built": true
}
},
+ "pnpm": {
+ "onlyBuiltDependencies": []
+ },
"resolutions": {
- "@bazel/concatjs@npm:5.8.1": "patch:@bazel/concatjs@npm%3A5.8.1#~/.yarn/patches/@bazel-concatjs-npm-5.8.1-1bf81df846.patch",
- "@microsoft/api-extractor/typescript": "5.6.3"
+ "typescript": "5.7.2"
}
}
diff --git a/packages/angular/build/BUILD.bazel b/packages/angular/build/BUILD.bazel
index 5f8cb394d6f6..b6b0c4e26077 100644
--- a/packages/angular/build/BUILD.bazel
+++ b/packages/angular/build/BUILD.bazel
@@ -1,6 +1,7 @@
load("@npm//@angular/build-tooling/bazel/api-golden:index.bzl", "api_golden_test_npm_package")
load("@npm//@bazel/jasmine:index.bzl", "jasmine_node_test")
-load("//tools:defaults.bzl", "pkg_npm", "ts_library")
+load("//tools:defaults.bzl", "pkg_npm")
+load("//tools:interop.bzl", "ts_project")
load("//tools:ts_json_schema.bzl", "ts_json_schema")
licenses(["notice"])
@@ -22,9 +23,8 @@ ts_json_schema(
src = "src/builders/extract-i18n/schema.json",
)
-ts_library(
+ts_project(
name = "build",
- package_name = "@angular/build",
srcs = glob(
include = [
"src/**/*.ts",
@@ -34,9 +34,9 @@ ts_library(
"src/**/*_spec.ts",
"src/**/tests/**/*.ts",
"src/testing/**/*.ts",
- "src/private.ts",
],
) + [
+ "index.ts",
"//packages/angular/build:src/builders/application/schema.ts",
"//packages/angular/build:src/builders/dev-server/schema.ts",
"//packages/angular/build:src/builders/extract-i18n/schema.ts",
@@ -52,67 +52,59 @@ ts_library(
"builders.json",
"package.json",
],
- module_name = "@angular/build",
- module_root = "src/index.d.ts",
- deps = [
+ interop_deps = [
"//packages/angular/ssr",
"//packages/angular/ssr/node",
"//packages/angular_devkit/architect",
- "@npm//@ampproject/remapping",
- "@npm//@angular/common",
- "@npm//@angular/compiler",
- "@npm//@angular/compiler-cli",
- "@npm//@angular/core",
- "@npm//@angular/localize",
- "@npm//@angular/platform-server",
- "@npm//@angular/service-worker",
- "@npm//@babel/core",
- "@npm//@babel/helper-annotate-as-pure",
- "@npm//@babel/helper-split-export-declaration",
- "@npm//@babel/plugin-syntax-import-attributes",
- "@npm//@inquirer/confirm",
- "@npm//@types/babel__core",
- "@npm//@types/less",
- "@npm//@types/node",
- "@npm//@types/picomatch",
- "@npm//@types/semver",
- "@npm//@types/watchpack",
- "@npm//@vitejs/plugin-basic-ssl",
- "@npm//beasties",
- "@npm//browserslist",
- "@npm//esbuild",
- "@npm//esbuild-wasm",
- "@npm//fast-glob",
- "@npm//https-proxy-agent",
- "@npm//listr2",
- "@npm//lmdb",
- "@npm//magic-string",
- "@npm//mrmime",
- "@npm//parse5-html-rewriting-stream",
- "@npm//picomatch",
- "@npm//piscina",
- "@npm//postcss",
- "@npm//rollup",
- "@npm//sass",
- "@npm//semver",
- "@npm//tslib",
- "@npm//typescript",
- "@npm//vite",
- "@npm//watchpack",
],
-)
-
-ts_library(
- name = "private",
- srcs = ["src/private.ts"],
- module_name = "@angular/build/private",
- module_root = "src/private.d.ts",
+ module_name = "@angular/build",
deps = [
- "//packages/angular/build",
+ "//:root_modules/@ampproject/remapping",
+ "//:root_modules/@angular/common",
+ "//:root_modules/@angular/compiler",
+ "//:root_modules/@angular/compiler-cli",
+ "//:root_modules/@angular/core",
+ "//:root_modules/@angular/localize",
+ "//:root_modules/@angular/platform-server",
+ "//:root_modules/@angular/service-worker",
+ "//:root_modules/@babel/core",
+ "//:root_modules/@babel/helper-annotate-as-pure",
+ "//:root_modules/@babel/helper-split-export-declaration",
+ "//:root_modules/@babel/plugin-syntax-import-attributes",
+ "//:root_modules/@inquirer/confirm",
+ "//:root_modules/@types/babel__core",
+ "//:root_modules/@types/less",
+ "//:root_modules/@types/node",
+ "//:root_modules/@types/picomatch",
+ "//:root_modules/@types/semver",
+ "//:root_modules/@types/watchpack",
+ "//:root_modules/@vitejs/plugin-basic-ssl",
+ "//:root_modules/beasties",
+ "//:root_modules/browserslist",
+ "//:root_modules/esbuild",
+ "//:root_modules/esbuild-wasm",
+ "//:root_modules/fast-glob",
+ "//:root_modules/https-proxy-agent",
+ "//:root_modules/jsonc-parser",
+ "//:root_modules/listr2",
+ "//:root_modules/lmdb",
+ "//:root_modules/magic-string",
+ "//:root_modules/mrmime",
+ "//:root_modules/parse5-html-rewriting-stream",
+ "//:root_modules/picomatch",
+ "//:root_modules/piscina",
+ "//:root_modules/postcss",
+ "//:root_modules/rollup",
+ "//:root_modules/sass",
+ "//:root_modules/semver",
+ "//:root_modules/tslib",
+ "//:root_modules/typescript",
+ "//:root_modules/vite",
+ "//:root_modules/watchpack",
],
)
-ts_library(
+ts_project(
name = "unit_test_lib",
testonly = True,
srcs = glob(
@@ -120,13 +112,15 @@ ts_library(
exclude = ["src/builders/**/tests/**"],
),
deps = [
- ":build",
- ":private",
- "//packages/angular_devkit/core",
- "//packages/angular_devkit/core/node",
- "@npm//@angular/compiler-cli",
- "@npm//@babel/core",
- "@npm//prettier",
+ ":build_rjs",
+ "//:root_modules/@angular/compiler-cli",
+ "//:root_modules/@babel/core",
+ "//:root_modules/@types/jasmine",
+ "//:root_modules/prettier",
+ "//:root_modules/typescript",
+ "//packages/angular/build/private:private_rjs",
+ "//packages/angular_devkit/core:core_rjs",
+ "//packages/angular_devkit/core/node:node_rjs",
],
)
@@ -135,38 +129,38 @@ jasmine_node_test(
deps = [":unit_test_lib"],
)
-ts_library(
+ts_project(
name = "integration_test_lib",
testonly = True,
srcs = glob(include = ["src/builders/**/tests/**/*.ts"]),
deps = [
- ":build",
- ":private",
- "//modules/testing/builder",
- "//packages/angular_devkit/architect",
- "//packages/angular_devkit/architect/node",
- "//packages/angular_devkit/architect/testing",
- "//packages/angular_devkit/core",
- "//packages/angular_devkit/core/node",
+ ":build_rjs",
+ "//packages/angular/build/private:private_rjs",
+ "//modules/testing/builder:builder_rjs",
+ "//packages/angular_devkit/architect:architect_rjs",
+ "//packages/angular_devkit/architect/node:node_rjs",
+ "//packages/angular_devkit/architect/testing:testing_rjs",
+ "//packages/angular_devkit/core:core_rjs",
+ "//packages/angular_devkit/core/node:node_rjs",
# dev server only test deps
- "@npm//@types/http-proxy",
- "@npm//http-proxy",
- "@npm//puppeteer",
+ "//:root_modules/@types/http-proxy",
+ "//:root_modules/http-proxy",
+ "//:root_modules/puppeteer",
# Base dependencies for the application in hello-world-app.
- "@npm//@angular/common",
- "@npm//@angular/compiler",
- "@npm//@angular/compiler-cli",
- "@npm//@angular/core",
- "@npm//@angular/platform-browser",
- "@npm//@angular/platform-browser-dynamic",
- "@npm//@angular/router",
- "@npm//rxjs",
- "@npm//tslib",
- "@npm//typescript",
- "@npm//zone.js",
- "@npm//buffer",
+ "//:root_modules/@angular/common",
+ "//:root_modules/@angular/compiler",
+ "//:root_modules/@angular/compiler-cli",
+ "//:root_modules/@angular/core",
+ "//:root_modules/@angular/platform-browser",
+ "//:root_modules/@angular/platform-browser-dynamic",
+ "//:root_modules/@angular/router",
+ "//:root_modules/rxjs",
+ "//:root_modules/tslib",
+ "//:root_modules/typescript",
+ "//:root_modules/zone.js",
+ "//:root_modules/buffer",
],
)
diff --git a/packages/angular/build/index.ts b/packages/angular/build/index.ts
new file mode 100644
index 000000000000..e6da94cc7ded
--- /dev/null
+++ b/packages/angular/build/index.ts
@@ -0,0 +1,9 @@
+/**
+ * @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
+ */
+
+export * from './src/index';
diff --git a/packages/angular/build/package.json b/packages/angular/build/package.json
index 0159c13b3ad0..b296df93b4f6 100644
--- a/packages/angular/build/package.json
+++ b/packages/angular/build/package.json
@@ -24,40 +24,40 @@
"@babel/helper-annotate-as-pure": "7.25.9",
"@babel/helper-split-export-declaration": "7.24.7",
"@babel/plugin-syntax-import-attributes": "7.26.0",
- "@inquirer/confirm": "5.0.2",
- "@vitejs/plugin-basic-ssl": "1.1.0",
- "beasties": "0.1.0",
+ "@inquirer/confirm": "5.1.1",
+ "@vitejs/plugin-basic-ssl": "1.2.0",
+ "beasties": "0.2.0",
"browserslist": "^4.23.0",
- "esbuild": "0.24.0",
+ "esbuild": "0.24.2",
"fast-glob": "3.3.2",
- "https-proxy-agent": "7.0.5",
+ "https-proxy-agent": "7.0.6",
"istanbul-lib-instrument": "6.0.3",
"listr2": "8.2.5",
- "magic-string": "0.30.12",
+ "magic-string": "0.30.17",
"mrmime": "2.0.0",
"parse5-html-rewriting-stream": "7.0.0",
"picomatch": "4.0.2",
- "piscina": "4.7.0",
- "rollup": "4.26.0",
- "sass": "1.80.7",
+ "piscina": "4.8.0",
+ "rollup": "4.29.1",
+ "sass": "1.83.0",
"semver": "7.6.3",
- "vite": "5.4.11",
+ "vite": "6.0.5",
"watchpack": "2.4.2"
},
"optionalDependencies": {
- "lmdb": "3.1.5"
+ "lmdb": "3.2.0"
},
"peerDependencies": {
- "@angular/compiler": "^19.0.0-next.9",
- "@angular/compiler-cli": "^19.0.0-next.9",
- "@angular/localize": "^19.0.0-next.9",
- "@angular/platform-server": "^19.0.0-next.9",
- "@angular/service-worker": "^19.0.0-next.9",
+ "@angular/compiler": "^19.0.0 || ^19.1.0-next.0",
+ "@angular/compiler-cli": "^19.0.0 || ^19.1.0-next.0",
+ "@angular/localize": "^19.0.0 || ^19.1.0-next.0",
+ "@angular/platform-server": "^19.0.0 || ^19.1.0-next.0",
+ "@angular/service-worker": "^19.0.0 || ^19.1.0-next.0",
"@angular/ssr": "^0.0.0-PLACEHOLDER",
"less": "^4.2.0",
"postcss": "^8.4.0",
"tailwindcss": "^2.0.0 || ^3.0.0",
- "typescript": ">=5.5 <5.7"
+ "typescript": ">=5.5 <5.8"
},
"peerDependenciesMeta": {
"@angular/localize": {
diff --git a/packages/angular/build/private/BUILD.bazel b/packages/angular/build/private/BUILD.bazel
new file mode 100644
index 000000000000..4d3cf2ab9fda
--- /dev/null
+++ b/packages/angular/build/private/BUILD.bazel
@@ -0,0 +1,12 @@
+load("//tools:interop.bzl", "ts_project")
+
+package(default_visibility = ["//visibility:public"])
+
+ts_project(
+ name = "private",
+ srcs = ["index.ts"],
+ module_name = "@angular/build/private",
+ deps = [
+ "//packages/angular/build:build_rjs",
+ ],
+)
diff --git a/packages/angular/build/private/index.ts b/packages/angular/build/private/index.ts
new file mode 100644
index 000000000000..1c2b76656baf
--- /dev/null
+++ b/packages/angular/build/private/index.ts
@@ -0,0 +1,9 @@
+/**
+ * @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
+ */
+
+export * from '../src/private';
diff --git a/packages/angular/build/src/builders/application/execute-build.ts b/packages/angular/build/src/builders/application/execute-build.ts
index b5ca83a76405..10d0e297522f 100644
--- a/packages/angular/build/src/builders/application/execute-build.ts
+++ b/packages/angular/build/src/builders/application/execute-build.ts
@@ -31,7 +31,6 @@ import {
generateAngularServerAppEngineManifest,
} from '../../utils/server-rendering/manifest';
import { getSupportedBrowsers } from '../../utils/supported-browsers';
-import { optimizeChunks } from './chunk-optimizer';
import { executePostBundleSteps } from './execute-post-bundle';
import { inlineI18n, loadActiveTranslations } from './i18n';
import { NormalizedApplicationBuildOptions } from './options';
@@ -129,6 +128,7 @@ export async function executeBuild(
}
if (options.optimizationOptions.scripts && shouldOptimizeChunks) {
+ const { optimizeChunks } = await import('./chunk-optimizer');
bundlingResult = await profileAsync('OPTIMIZE_CHUNKS', () =>
optimizeChunks(
bundlingResult,
@@ -247,12 +247,13 @@ export async function executeBuild(
// Perform i18n translation inlining if enabled
if (i18nOptions.shouldInline) {
- const result = await inlineI18n(options, executionResult, initialFiles);
+ const result = await inlineI18n(metafile, options, executionResult, initialFiles);
executionResult.addErrors(result.errors);
executionResult.addWarnings(result.warnings);
executionResult.addPrerenderedRoutes(result.prerenderedRoutes);
} else {
const result = await executePostBundleSteps(
+ metafile,
options,
executionResult.outputFiles,
executionResult.assetFiles,
diff --git a/packages/angular/build/src/builders/application/execute-post-bundle.ts b/packages/angular/build/src/builders/application/execute-post-bundle.ts
index 2f4f73c69b08..5066d5aaca01 100644
--- a/packages/angular/build/src/builders/application/execute-post-bundle.ts
+++ b/packages/angular/build/src/builders/application/execute-post-bundle.ts
@@ -6,6 +6,7 @@
* found in the LICENSE file at https://angular.dev/license
*/
+import type { Metafile } from 'esbuild';
import assert from 'node:assert';
import {
BuildOutputFile,
@@ -34,6 +35,7 @@ import { OutputMode } from './schema';
/**
* Run additional builds steps including SSG, AppShell, Index HTML file and Service worker generation.
+ * @param metafile An esbuild metafile object.
* @param options The normalized application builder options used to create the build.
* @param outputFiles The output files of an executed build.
* @param assetFiles The assets of an executed build.
@@ -42,6 +44,7 @@ import { OutputMode } from './schema';
*/
// eslint-disable-next-line max-lines-per-function
export async function executePostBundleSteps(
+ metafile: Metafile,
options: NormalizedApplicationBuildOptions,
outputFiles: BuildOutputFile[],
assetFiles: BuildOutputAsset[],
@@ -63,6 +66,7 @@ export async function executePostBundleSteps(
const {
baseHref = '/',
serviceWorker,
+ i18nOptions,
indexHtmlOptions,
optimizationOptions,
sourcemapOptions,
@@ -70,6 +74,7 @@ export async function executePostBundleSteps(
serverEntryPoint,
prerenderOptions,
appShellOptions,
+ publicPath,
workspaceRoot,
partialSSRBuild,
} = options;
@@ -107,6 +112,7 @@ export async function executePostBundleSteps(
}
// Create server manifest
+ const initialFilesPaths = new Set(initialFiles.keys());
if (serverEntryPoint) {
const { manifestContent, serverAssetsChunks } = generateAngularServerAppManifest(
additionalHtmlOutputFiles,
@@ -114,6 +120,10 @@ export async function executePostBundleSteps(
optimizationOptions.styles.inlineCritical ?? false,
undefined,
locale,
+ baseHref,
+ initialFilesPaths,
+ metafile,
+ publicPath,
);
additionalOutputFiles.push(
@@ -194,6 +204,10 @@ export async function executePostBundleSteps(
optimizationOptions.styles.inlineCritical ?? false,
serializableRouteTreeNodeForManifest,
locale,
+ baseHref,
+ initialFilesPaths,
+ metafile,
+ publicPath,
);
for (const chunk of serverAssetsChunks) {
diff --git a/packages/angular/build/src/builders/application/i18n.ts b/packages/angular/build/src/builders/application/i18n.ts
index cfb044f0e34f..f526286e35a7 100644
--- a/packages/angular/build/src/builders/application/i18n.ts
+++ b/packages/angular/build/src/builders/application/i18n.ts
@@ -7,6 +7,7 @@
*/
import { BuilderContext } from '@angular-devkit/architect';
+import type { Metafile } from 'esbuild';
import { join } from 'node:path';
import { BuildOutputFileType, InitialFileRecord } from '../../tools/esbuild/bundler-context';
import {
@@ -23,11 +24,13 @@ import { NormalizedApplicationBuildOptions, getLocaleBaseHref } from './options'
/**
* Inlines all active locales as specified by the application build options into all
* application JavaScript files created during the build.
+ * @param metafile An esbuild metafile object.
* @param options The normalized application builder options used to create the build.
* @param executionResult The result of an executed build.
* @param initialFiles A map containing initial file information for the executed build.
*/
export async function inlineI18n(
+ metafile: Metafile,
options: NormalizedApplicationBuildOptions,
executionResult: ExecutionResult,
initialFiles: Map,
@@ -36,12 +39,14 @@ export async function inlineI18n(
warnings: string[];
prerenderedRoutes: PrerenderedRoutesRecord;
}> {
+ const { i18nOptions, optimizationOptions, baseHref } = options;
+
// Create the multi-threaded inliner with common options and the files generated from the build.
const inliner = new I18nInliner(
{
- missingTranslation: options.i18nOptions.missingTranslationBehavior ?? 'warning',
+ missingTranslation: i18nOptions.missingTranslationBehavior ?? 'warning',
outputFiles: executionResult.outputFiles,
- shouldOptimize: options.optimizationOptions.scripts,
+ shouldOptimize: optimizationOptions.scripts,
},
maxWorkers,
);
@@ -60,19 +65,16 @@ export async function inlineI18n(
const updatedOutputFiles = [];
const updatedAssetFiles = [];
try {
- for (const locale of options.i18nOptions.inlineLocales) {
+ for (const locale of i18nOptions.inlineLocales) {
// A locale specific set of files is returned from the inliner.
const localeInlineResult = await inliner.inlineForLocale(
locale,
- options.i18nOptions.locales[locale].translation,
+ i18nOptions.locales[locale].translation,
);
const localeOutputFiles = localeInlineResult.outputFiles;
inlineResult.errors.push(...localeInlineResult.errors);
inlineResult.warnings.push(...localeInlineResult.warnings);
- const baseHref =
- getLocaleBaseHref(options.baseHref, options.i18nOptions, locale) ?? options.baseHref;
-
const {
errors,
warnings,
@@ -80,9 +82,10 @@ export async function inlineI18n(
additionalOutputFiles,
prerenderedRoutes: generatedRoutes,
} = await executePostBundleSteps(
+ metafile,
{
...options,
- baseHref,
+ baseHref: getLocaleBaseHref(baseHref, i18nOptions, locale) ?? baseHref,
},
localeOutputFiles,
executionResult.assetFiles,
@@ -94,16 +97,17 @@ export async function inlineI18n(
inlineResult.errors.push(...errors);
inlineResult.warnings.push(...warnings);
- // Update directory with locale base
- if (options.i18nOptions.flatOutput !== true) {
+ // Update directory with locale base or subPath
+ const subPath = i18nOptions.locales[locale].subPath;
+ if (i18nOptions.flatOutput !== true) {
localeOutputFiles.forEach((file) => {
- file.path = join(locale, file.path);
+ file.path = join(subPath, file.path);
});
for (const assetFile of [...executionResult.assetFiles, ...additionalAssets]) {
updatedAssetFiles.push({
source: assetFile.source,
- destination: join(locale, assetFile.destination),
+ destination: join(subPath, assetFile.destination),
});
}
} else {
@@ -128,7 +132,7 @@ export async function inlineI18n(
];
// Assets are only changed if not using the flat output option
- if (options.i18nOptions.flatOutput !== true) {
+ if (!i18nOptions.flatOutput) {
executionResult.assetFiles = updatedAssetFiles;
}
diff --git a/packages/angular/build/src/builders/application/options.ts b/packages/angular/build/src/builders/application/options.ts
index 39e75ef7ca4d..13adfa354d40 100644
--- a/packages/angular/build/src/builders/application/options.ts
+++ b/packages/angular/build/src/builders/application/options.ts
@@ -168,7 +168,7 @@ export async function normalizeOptions(
const i18nOptions: I18nOptions & {
duplicateTranslationBehavior?: I18NTranslation;
missingTranslationBehavior?: I18NTranslation;
- } = createI18nOptions(projectMetadata, options.localize);
+ } = createI18nOptions(projectMetadata, options.localize, context.logger);
i18nOptions.duplicateTranslationBehavior = options.i18nDuplicateTranslation;
i18nOptions.missingTranslationBehavior = options.i18nMissingTranslation;
if (options.forceI18nFlatOutput) {
@@ -327,24 +327,26 @@ export async function normalizeOptions(
let indexOutput: string;
// The output file will be created within the configured output path
if (typeof options.index === 'string') {
- /**
- * If SSR is activated, create a distinct entry file for the `index.html`.
- * This is necessary because numerous server/cloud providers automatically serve the `index.html` as a static file
- * if it exists (handling SSG).
- *
- * For instance, accessing `foo.com/` would lead to `foo.com/index.html` being served instead of hitting the server.
- *
- * This approach can also be applied to service workers, where the `index.csr.html` is served instead of the prerendered `index.html`.
- */
- const indexBaseName = path.basename(options.index);
- indexOutput =
- (ssrOptions || prerenderOptions) && indexBaseName === 'index.html'
- ? INDEX_HTML_CSR
- : indexBaseName;
+ indexOutput = options.index;
} else {
indexOutput = options.index.output || 'index.html';
}
+ /**
+ * If SSR is activated, create a distinct entry file for the `index.html`.
+ * This is necessary because numerous server/cloud providers automatically serve the `index.html` as a static file
+ * if it exists (handling SSG).
+ *
+ * For instance, accessing `foo.com/` would lead to `foo.com/index.html` being served instead of hitting the server.
+ *
+ * This approach can also be applied to service workers, where the `index.csr.html` is served instead of the prerendered `index.html`.
+ */
+ const indexBaseName = path.basename(indexOutput);
+ indexOutput =
+ (ssrOptions || prerenderOptions) && indexBaseName === 'index.html'
+ ? INDEX_HTML_CSR
+ : indexBaseName;
+
indexHtmlOptions = {
input: path.join(
workspaceRoot,
@@ -381,7 +383,7 @@ export async function normalizeOptions(
// Initial options to keep
const {
allowedCommonJsDependencies,
- aot,
+ aot = true,
baseHref,
crossOrigin,
externalDependencies,
@@ -469,7 +471,7 @@ export async function normalizeOptions(
clearScreen,
define,
partialSSRBuild: usePartialSsrBuild || partialSSRBuild,
- externalRuntimeStyles,
+ externalRuntimeStyles: aot && externalRuntimeStyles,
instrumentForCoverage,
security,
templateUpdates: !!options.templateUpdates,
@@ -643,7 +645,7 @@ function normalizeGlobalEntries(
}
export function getLocaleBaseHref(
- baseHref: string | undefined,
+ baseHref: string | undefined = '',
i18n: NormalizedApplicationBuildOptions['i18nOptions'],
locale: string,
): string | undefined {
@@ -651,9 +653,12 @@ export function getLocaleBaseHref(
return undefined;
}
- if (i18n.locales[locale] && i18n.locales[locale].baseHref !== '') {
- return urlJoin(baseHref || '', i18n.locales[locale].baseHref ?? `/${locale}/`);
+ const localeData = i18n.locales[locale];
+ if (!localeData) {
+ return undefined;
}
- return undefined;
+ const baseHrefSuffix = localeData.baseHref ?? localeData.subPath + '/';
+
+ return baseHrefSuffix !== '' ? urlJoin(baseHref, baseHrefSuffix) : undefined;
}
diff --git a/packages/angular/build/src/builders/application/tests/behavior/index-csr_spec.ts b/packages/angular/build/src/builders/application/tests/behavior/index-csr_spec.ts
new file mode 100644
index 000000000000..0f87dd78a289
--- /dev/null
+++ b/packages/angular/build/src/builders/application/tests/behavior/index-csr_spec.ts
@@ -0,0 +1,57 @@
+/**
+ * @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
+ */
+
+import { buildApplication } from '../../index';
+import { APPLICATION_BUILDER_INFO, BASE_OPTIONS, describeBuilder } from '../setup';
+
+describeBuilder(buildApplication, APPLICATION_BUILDER_INFO, (harness) => {
+ describe('Behavior: "index.csr.html"', () => {
+ beforeEach(async () => {
+ await harness.modifyFile('src/tsconfig.app.json', (content) => {
+ const tsConfig = JSON.parse(content);
+ tsConfig.files ??= [];
+ tsConfig.files.push('main.server.ts');
+
+ return JSON.stringify(tsConfig);
+ });
+ });
+
+ it(`should generate 'index.csr.html' instead of 'index.html' when ssr is enabled.`, async () => {
+ harness.useTarget('build', {
+ ...BASE_OPTIONS,
+ server: 'src/main.server.ts',
+ ssr: true,
+ });
+
+ const { result } = await harness.executeOnce();
+ expect(result?.success).toBeTrue();
+ harness.expectDirectory('dist/server').toExist();
+ harness.expectFile('dist/browser/index.csr.html').toExist();
+ harness.expectFile('dist/browser/index.html').toNotExist();
+ });
+
+ it(`should generate 'index.csr.html' instead of 'index.html' when 'output' is 'index.html' and ssr is enabled.`, async () => {
+ harness.useTarget('build', {
+ ...BASE_OPTIONS,
+ index: {
+ input: 'src/index.html',
+ output: 'index.html',
+ },
+ server: 'src/main.server.ts',
+ ssr: true,
+ });
+
+ const { result } = await harness.executeOnce();
+ expect(result?.success).toBeTrue();
+
+ harness.expectDirectory('dist/server').toExist();
+ harness.expectFile('dist/browser/index.csr.html').toExist();
+ harness.expectFile('dist/browser/index.html').toNotExist();
+ });
+ });
+});
diff --git a/packages/angular/build/src/builders/application/tests/behavior/stylesheet_autoprefixer_spec.ts b/packages/angular/build/src/builders/application/tests/behavior/stylesheet_autoprefixer_spec.ts
index 16e5f8b88e60..41ae225e2d3d 100644
--- a/packages/angular/build/src/builders/application/tests/behavior/stylesheet_autoprefixer_spec.ts
+++ b/packages/angular/build/src/builders/application/tests/behavior/stylesheet_autoprefixer_spec.ts
@@ -200,5 +200,60 @@ describeBuilder(buildApplication, APPLICATION_BUILDER_INFO, (harness) => {
harness.expectFile('dist/browser/main.js').content.toMatch(/{\\n\s*hyphens:\s*none;\\n\s*}/);
});
+
+ it('should add prefixes for listed browsers in inline template styles', async () => {
+ await harness.writeFile(
+ '.browserslistrc',
+ `
+ Safari 15.4
+ Edge 104
+ Firefox 91
+ `,
+ );
+
+ await harness.modifyFile('src/app/app.component.ts', (content) => {
+ return content.replace('styleUrls', 'styles').replace('./app.component.css', '');
+ });
+ await harness.modifyFile('src/app/app.component.html', (content) => {
+ return `\n${content}`;
+ });
+
+ harness.useTarget('build', {
+ ...BASE_OPTIONS,
+ });
+
+ const { result } = await harness.executeOnce();
+ expect(result?.success).toBeTrue();
+
+ harness
+ .expectFile('dist/browser/main.js')
+ // div[_ngcontent-%COMP%] {\n -webkit-hyphens: none;\n hyphens: none;\n}\n
+ .content.toMatch(/{\\n\s*-webkit-hyphens:\s*none;\\n\s*hyphens:\s*none;\\n\s*}/);
+ });
+
+ it('should not add prefixes if not required by browsers in inline template styles', async () => {
+ await harness.writeFile(
+ '.browserslistrc',
+ `
+ Edge 110
+ `,
+ );
+
+ await harness.modifyFile('src/app/app.component.ts', (content) => {
+ return content.replace('styleUrls', 'styles').replace('./app.component.css', '');
+ });
+ await harness.modifyFile('src/app/app.component.html', (content) => {
+ return `\n${content}`;
+ });
+
+ harness.useTarget('build', {
+ ...BASE_OPTIONS,
+ });
+
+ const { result } = await harness.executeOnce();
+ expect(result?.success).toBeTrue();
+
+ harness.expectFile('dist/browser/main.js').content.toMatch(/{\\n\s*hyphens:\s*none;\\n\s*}/);
+ });
});
});
diff --git a/packages/angular/build/src/builders/application/tests/options/define_spec.ts b/packages/angular/build/src/builders/application/tests/options/define_spec.ts
index d4e3319553f2..ec8ccf2e6f24 100644
--- a/packages/angular/build/src/builders/application/tests/options/define_spec.ts
+++ b/packages/angular/build/src/builders/application/tests/options/define_spec.ts
@@ -61,5 +61,22 @@ describeBuilder(buildApplication, APPLICATION_BUILDER_INFO, (harness) => {
harness.expectFile('dist/browser/main.js').content.not.toContain('A_BOOLEAN');
harness.expectFile('dist/browser/main.js').content.toContain('(true)');
});
+
+ it('should replace a value in script code', async () => {
+ harness.useTarget('build', {
+ ...BASE_OPTIONS,
+ define: {
+ 'A_BOOLEAN': 'true',
+ },
+ scripts: ['./src/script.js'],
+ });
+
+ await harness.writeFile('src/script.js', 'console.log(A_BOOLEAN);');
+
+ const { result } = await harness.executeOnce();
+ expect(result?.success).toBe(true);
+ harness.expectFile('dist/browser/scripts.js').content.not.toContain('A_BOOLEAN');
+ harness.expectFile('dist/browser/scripts.js').content.toContain('(true)');
+ });
});
});
diff --git a/packages/angular/build/src/builders/application/tests/options/file-replacements_spec.ts b/packages/angular/build/src/builders/application/tests/options/file-replacements_spec.ts
new file mode 100644
index 000000000000..a937f0bc430a
--- /dev/null
+++ b/packages/angular/build/src/builders/application/tests/options/file-replacements_spec.ts
@@ -0,0 +1,37 @@
+/**
+ * @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
+ */
+
+import { buildApplication } from '../../index';
+import { APPLICATION_BUILDER_INFO, BASE_OPTIONS, describeBuilder } from '../setup';
+
+describeBuilder(buildApplication, APPLICATION_BUILDER_INFO, (harness) => {
+ describe('Option: "fileReplacements"', () => {
+ it('should replace JSON files', async () => {
+ harness.useTarget('build', {
+ ...BASE_OPTIONS,
+ fileReplacements: [{ replace: './src/one.json', with: './src/two.json' }],
+ });
+
+ await harness.modifyFile('tsconfig.json', (content) => {
+ const tsconfig = JSON.parse(content);
+ tsconfig.compilerOptions.resolveJsonModule = true;
+
+ return JSON.stringify(tsconfig);
+ });
+
+ await harness.writeFile('./src/one.json', '{ "x": 12345 }');
+ await harness.writeFile('./src/two.json', '{ "x": 67890 }');
+ await harness.writeFile('src/main.ts', 'import { x } from "./one.json";\n console.log(x);');
+
+ const { result } = await harness.executeOnce();
+ expect(result?.success).toBe(true);
+ harness.expectFile('dist/browser/main.js').content.not.toContain('12345');
+ harness.expectFile('dist/browser/main.js').content.toContain('67890');
+ });
+ });
+});
diff --git a/packages/angular/build/src/builders/application/tests/options/index_spec.ts b/packages/angular/build/src/builders/application/tests/options/index_spec.ts
index 83e3cc132fe5..d3a5fe9e57d3 100644
--- a/packages/angular/build/src/builders/application/tests/options/index_spec.ts
+++ b/packages/angular/build/src/builders/application/tests/options/index_spec.ts
@@ -12,7 +12,6 @@ import { APPLICATION_BUILDER_INFO, BASE_OPTIONS, describeBuilder } from '../setu
describeBuilder(buildApplication, APPLICATION_BUILDER_INFO, (harness) => {
describe('Option: "index"', () => {
beforeEach(async () => {
- // Application code is not needed for index tests
await harness.writeFile('src/main.ts', 'console.log("TEST");');
});
@@ -140,92 +139,72 @@ describeBuilder(buildApplication, APPLICATION_BUILDER_INFO, (harness) => {
});
});
- it('should generate initial preload link elements when preloadInitial is true', async () => {
- harness.useTarget('build', {
- ...BASE_OPTIONS,
- index: {
- input: 'src/index.html',
- preloadInitial: true,
- },
- });
-
- // Setup an initial chunk usage for JS
- await harness.writeFile('src/a.ts', 'console.log("TEST");');
- await harness.writeFile('src/b.ts', 'import "./a";');
- await harness.writeFile('src/main.ts', 'import "./a";\n(() => import("./b"))();');
+ describe('preload', () => {
+ it('should generate initial preload link elements when preloadInitial is true', async () => {
+ harness.useTarget('build', {
+ ...BASE_OPTIONS,
+ index: {
+ input: 'src/index.html',
+ preloadInitial: true,
+ },
+ });
- const { result } = await harness.executeOnce();
+ // Setup an initial chunk usage for JS
+ await harness.writeFile('src/a.ts', 'console.log("TEST");');
+ await harness.writeFile('src/b.ts', 'import "./a";');
+ await harness.writeFile('src/main.ts', 'import "./a";\n(() => import("./b"))();');
- expect(result?.success).toBe(true);
- harness.expectFile('dist/browser/main.js').content.toContain('chunk-');
- harness.expectFile('dist/browser/index.html').content.toContain('modulepreload');
- harness.expectFile('dist/browser/index.html').content.toContain('chunk-');
- });
+ const { result } = await harness.executeOnce();
- it('should generate initial preload link elements when preloadInitial is undefined', async () => {
- harness.useTarget('build', {
- ...BASE_OPTIONS,
- index: {
- input: 'src/index.html',
- preloadInitial: undefined,
- },
+ expect(result?.success).toBe(true);
+ harness.expectFile('dist/browser/main.js').content.toContain('chunk-');
+ harness.expectFile('dist/browser/index.html').content.toContain('modulepreload');
+ harness.expectFile('dist/browser/index.html').content.toContain('chunk-');
});
- // Setup an initial chunk usage for JS
- await harness.writeFile('src/a.ts', 'console.log("TEST");');
- await harness.writeFile('src/b.ts', 'import "./a";');
- await harness.writeFile('src/main.ts', 'import "./a";\n(() => import("./b"))();');
+ it('should generate initial preload link elements when preloadInitial is undefined', async () => {
+ harness.useTarget('build', {
+ ...BASE_OPTIONS,
+ index: {
+ input: 'src/index.html',
+ preloadInitial: undefined,
+ },
+ });
- const { result } = await harness.executeOnce();
+ // Setup an initial chunk usage for JS
+ await harness.writeFile('src/a.ts', 'console.log("TEST");');
+ await harness.writeFile('src/b.ts', 'import "./a";');
+ await harness.writeFile('src/main.ts', 'import "./a";\n(() => import("./b"))();');
- expect(result?.success).toBe(true);
- harness.expectFile('dist/browser/main.js').content.toContain('chunk-');
- harness.expectFile('dist/browser/index.html').content.toContain('modulepreload');
- harness.expectFile('dist/browser/index.html').content.toContain('chunk-');
- });
+ const { result } = await harness.executeOnce();
- it('should not generate initial preload link elements when preloadInitial is false', async () => {
- harness.useTarget('build', {
- ...BASE_OPTIONS,
- index: {
- input: 'src/index.html',
- preloadInitial: false,
- },
+ expect(result?.success).toBe(true);
+ harness.expectFile('dist/browser/main.js').content.toContain('chunk-');
+ harness.expectFile('dist/browser/index.html').content.toContain('modulepreload');
+ harness.expectFile('dist/browser/index.html').content.toContain('chunk-');
});
- // Setup an initial chunk usage for JS
- await harness.writeFile('src/a.ts', 'console.log("TEST");');
- await harness.writeFile('src/b.ts', 'import "./a";');
- await harness.writeFile('src/main.ts', 'import "./a";\n(() => import("./b"))();');
-
- const { result } = await harness.executeOnce();
-
- expect(result?.success).toBe(true);
- harness.expectFile('dist/browser/main.js').content.toContain('chunk-');
- harness.expectFile('dist/browser/index.html').content.not.toContain('modulepreload');
- harness.expectFile('dist/browser/index.html').content.not.toContain('chunk-');
- });
+ it('should not generate initial preload link elements when preloadInitial is false', async () => {
+ harness.useTarget('build', {
+ ...BASE_OPTIONS,
+ index: {
+ input: 'src/index.html',
+ preloadInitial: false,
+ },
+ });
- it(`should generate 'index.csr.html' instead of 'index.html' by default when ssr is enabled.`, async () => {
- await harness.modifyFile('src/tsconfig.app.json', (content) => {
- const tsConfig = JSON.parse(content);
- tsConfig.files ??= [];
- tsConfig.files.push('main.server.ts');
+ // Setup an initial chunk usage for JS
+ await harness.writeFile('src/a.ts', 'console.log("TEST");');
+ await harness.writeFile('src/b.ts', 'import "./a";');
+ await harness.writeFile('src/main.ts', 'import "./a";\n(() => import("./b"))();');
- return JSON.stringify(tsConfig);
- });
+ const { result } = await harness.executeOnce();
- harness.useTarget('build', {
- ...BASE_OPTIONS,
- server: 'src/main.server.ts',
- ssr: true,
+ expect(result?.success).toBe(true);
+ harness.expectFile('dist/browser/main.js').content.toContain('chunk-');
+ harness.expectFile('dist/browser/index.html').content.not.toContain('modulepreload');
+ harness.expectFile('dist/browser/index.html').content.not.toContain('chunk-');
});
-
- const { result } = await harness.executeOnce();
- expect(result?.success).toBeTrue();
- harness.expectDirectory('dist/server').toExist();
- harness.expectFile('dist/browser/index.csr.html').toExist();
- harness.expectFile('dist/browser/index.html').toNotExist();
});
});
});
diff --git a/packages/angular/build/src/builders/dev-server/vite-server.ts b/packages/angular/build/src/builders/dev-server/vite-server.ts
index dfbbc8454b53..fb70e6f964d5 100644
--- a/packages/angular/build/src/builders/dev-server/vite-server.ts
+++ b/packages/angular/build/src/builders/dev-server/vite-server.ts
@@ -13,7 +13,7 @@ import assert from 'node:assert';
import { readFile } from 'node:fs/promises';
import { builtinModules, isBuiltin } from 'node:module';
import { join } from 'node:path';
-import type { Connect, DepOptimizationConfig, InlineConfig, ViteDevServer } from 'vite';
+import type { Connect, InlineConfig, ViteDevServer } from 'vite';
import type { ComponentStyleRecord } from '../../tools/vite/middlewares';
import {
ServerSsrMode,
@@ -23,6 +23,7 @@ import {
createAngularSsrTransformPlugin,
createRemoveIdPrefixPlugin,
} from '../../tools/vite/plugins';
+import { EsbuildLoaderOption, getDepOptimizationConfig } from '../../tools/vite/utils';
import { loadProxyConfiguration, normalizeSourceMaps } from '../../utils';
import { useComponentStyleHmr, useComponentTemplateHmr } from '../../utils/environment-options';
import { loadEsmModule } from '../../utils/load-esm';
@@ -32,7 +33,6 @@ import {
BuildOutputFileType,
type ExternalResultMetadata,
JavaScriptTransformer,
- getFeatureSupport,
getSupportedBrowsers,
isZonelessApp,
transformSupportedBrowsersToTargets,
@@ -138,17 +138,14 @@ export async function* serveWithVite(
process.setSourceMapsEnabled(true);
}
- // Enable to support component style hot reloading (`NG_HMR_CSTYLES=0` can be used to disable selectively)
+ // Enable to support link-based component style hot reloading (`NG_HMR_CSTYLES=0` can be used to disable selectively)
browserOptions.externalRuntimeStyles =
serverOptions.liveReload && serverOptions.hmr && useComponentStyleHmr;
- // Enable to support component template hot replacement (`NG_HMR_TEMPLATE=1` can be used to enable)
- browserOptions.templateUpdates = !!serverOptions.liveReload && useComponentTemplateHmr;
- if (browserOptions.templateUpdates) {
- context.logger.warn(
- 'Experimental support for component template hot replacement has been enabled via the "NG_HMR_TEMPLATE" environment variable.',
- );
- }
+ // Enable to support component template hot replacement (`NG_HMR_TEMPLATE=0` can be used to disable selectively)
+ // This will also replace file-based/inline styles as code if external runtime styles are not enabled.
+ browserOptions.templateUpdates =
+ serverOptions.liveReload && serverOptions.hmr && useComponentTemplateHmr;
// Setup the prebundling transformer that will be shared across Vite prebundling requests
const prebundleTransformer = new JavaScriptTransformer(
@@ -233,6 +230,12 @@ export async function* serveWithVite(
assetFiles.set('/' + normalizePath(outputPath), normalizePath(file.inputPath));
}
}
+
+ // Invalidate SSR module graph to ensure that only new rebuild is used and not stale component updates
+ if (server && browserOptions.ssr && templateUpdates.size > 0) {
+ server.moduleGraph.invalidateAll();
+ }
+
// Clear stale template updates on code rebuilds
templateUpdates.clear();
@@ -256,6 +259,16 @@ export async function* serveWithVite(
'Builder must provide an initial full build before component update results.',
);
+ // Invalidate SSR module graph to ensure that new component updates are used
+ // TODO: Use fine-grained invalidation of only the component update modules
+ if (browserOptions.ssr) {
+ server.moduleGraph.invalidateAll();
+ const { ɵresetCompiledComponents } = (await server.ssrLoadModule('/main.server.mjs')) as {
+ ɵresetCompiledComponents: () => void;
+ };
+ ɵresetCompiledComponents();
+ }
+
for (const componentUpdate of result.updates) {
if (componentUpdate.type === 'template') {
templateUpdates.set(componentUpdate.id, componentUpdate.content);
@@ -273,7 +286,6 @@ export async function* serveWithVite(
}
// To avoid disconnecting the array objects from the option, these arrays need to be mutated instead of replaced.
- let requiresServerRestart = false;
if (result.detail?.['externalMetadata']) {
const { implicitBrowser, implicitServer, explicit } = result.detail[
'externalMetadata'
@@ -283,15 +295,6 @@ export async function* serveWithVite(
);
const implicitBrowserFiltered = implicitBrowser.filter((m) => !isAbsoluteUrl(m));
- if (browserOptions.ssr && serverOptions.prebundle !== false) {
- const previousImplicitServer = new Set(externalMetadata.implicitServer);
- // Restart the server to force SSR dep re-optimization when a dependency has been added.
- // This is a workaround for: https://github.com/vitejs/vite/issues/14896
- requiresServerRestart = implicitServerFiltered.some(
- (dep) => !previousImplicitServer.has(dep),
- );
- }
-
// Empty Arrays to avoid growing unlimited with every re-build.
externalMetadata.explicitBrowser.length = 0;
externalMetadata.explicitServer.length = 0;
@@ -317,20 +320,14 @@ export async function* serveWithVite(
...new Set([...server.config.server.fs.allow, ...assetFiles.values()]),
];
- if (requiresServerRestart) {
- // Restart the server to force SSR dep re-optimization when a dependency has been added.
- // This is a workaround for: https://github.com/vitejs/vite/issues/14896
- await server.restart();
- } else {
- await handleUpdate(
- normalizePath,
- generatedFiles,
- server,
- serverOptions,
- context.logger,
- componentStyles,
- );
- }
+ await handleUpdate(
+ normalizePath,
+ generatedFiles,
+ server,
+ serverOptions,
+ context.logger,
+ componentStyles,
+ );
} else {
const projectName = context.target?.project;
if (!projectName) {
@@ -475,6 +472,11 @@ async function handleUpdate(
return;
}
+ if (destroyAngularServerAppCalled) {
+ // Trigger module evaluation before reload to initiate dependency optimization.
+ await server.ssrLoadModule('/main.server.mjs');
+ }
+
if (serverOptions.hmr) {
if (updatedFiles.every((f) => f.endsWith('.css'))) {
let requiresReload = false;
@@ -692,7 +694,15 @@ export async function setupServer(
headers: serverOptions.headers,
// Disable the websocket if live reload is disabled (false/undefined are the only valid values)
ws: serverOptions.liveReload === false && serverOptions.hmr === false ? false : undefined,
- proxy,
+ // When server-side rendering (SSR) is enabled togather with SSL and Express is being used,
+ // we must configure Vite to use HTTP/1.1.
+ // This is necessary because Express does not support HTTP/2.
+ // We achieve this by defining an empty proxy.
+ // See: https://github.com/vitejs/vite/blob/c4b532cc900bf988073583511f57bd581755d5e3/packages/vite/src/node/http.ts#L106
+ proxy:
+ serverOptions.ssl && ssrMode === ServerSsrMode.ExternalSsrMiddleware
+ ? (proxy ?? {})
+ : proxy,
cors: {
// Allow preflight requests to be proxied.
preflightContinue: true,
@@ -705,11 +715,6 @@ export async function setupServer(
// the Vite client-side code for browser reloading. These would be available by default but when
// the `allow` option is explicitly configured, they must be included manually.
allow: [cacheDir, join(serverOptions.workspaceRoot, 'node_modules'), ...assets.values()],
-
- // Temporary disable cached FS checks.
- // This is because we configure `config.base` to a virtual directory which causes `getRealPath` to fail.
- // See: https://github.com/vitejs/vite/blob/b2873ac3936de25ca8784327cb9ef16bd4881805/packages/vite/src/node/fsUtils.ts#L45-L67
- cachedChecks: false,
},
// This is needed when `externalDependencies` is used to prevent Vite load errors.
// NOTE: If Vite adds direct support for externals, this can be removed.
@@ -751,6 +756,7 @@ export async function setupServer(
await createAngularMemoryPlugin({
virtualProjectRoot,
outputFiles,
+ templateUpdates,
external: externalMetadata.explicitBrowser,
skipViteClient: serverOptions.liveReload === false && serverOptions.hmr === false,
}),
@@ -791,71 +797,6 @@ export async function setupServer(
return configuration;
}
-type ViteEsBuildPlugin = NonNullable<
- NonNullable['plugins']
->[0];
-
-type EsbuildLoaderOption = Exclude['loader'];
-
-function getDepOptimizationConfig({
- disabled,
- exclude,
- include,
- target,
- zoneless,
- prebundleTransformer,
- ssr,
- loader,
- thirdPartySourcemaps,
-}: {
- disabled: boolean;
- exclude: string[];
- include: string[];
- target: string[];
- prebundleTransformer: JavaScriptTransformer;
- ssr: boolean;
- zoneless: boolean;
- loader?: EsbuildLoaderOption;
- thirdPartySourcemaps: boolean;
-}): DepOptimizationConfig {
- const plugins: ViteEsBuildPlugin[] = [
- {
- name: `angular-vite-optimize-deps${ssr ? '-ssr' : ''}${
- thirdPartySourcemaps ? '-vendor-sourcemap' : ''
- }`,
- setup(build) {
- build.onLoad({ filter: /\.[cm]?js$/ }, async (args) => {
- return {
- contents: await prebundleTransformer.transformFile(args.path),
- loader: 'js',
- };
- });
- },
- },
- ];
-
- return {
- // Exclude any explicitly defined dependencies (currently build defined externals)
- exclude,
- // NB: to disable the deps optimizer, set optimizeDeps.noDiscovery to true and optimizeDeps.include as undefined.
- // Include all implict dependencies from the external packages internal option
- include: disabled ? undefined : include,
- noDiscovery: disabled,
- // Add an esbuild plugin to run the Angular linker on dependencies
- esbuildOptions: {
- // Set esbuild supported targets.
- target,
- supported: getFeatureSupport(target, zoneless),
- plugins,
- loader,
- define: {
- 'ngServerMode': `${ssr}`,
- },
- resolveExtensions: ['.mjs', '.js', '.cjs'],
- },
- };
-}
-
/**
* Checks if the given value is an absolute URL.
*
diff --git a/packages/angular/build/src/builders/extract-i18n/options.ts b/packages/angular/build/src/builders/extract-i18n/options.ts
index 8e36f3db28f1..24be86ee7c8f 100644
--- a/packages/angular/build/src/builders/extract-i18n/options.ts
+++ b/packages/angular/build/src/builders/extract-i18n/options.ts
@@ -36,8 +36,7 @@ export async function normalizeOptions(
// Target specifier defaults to the current project's build target with no specified configuration
const buildTargetSpecifier = options.buildTarget ?? ':';
const buildTarget = targetFromTargetString(buildTargetSpecifier, projectName, 'build');
-
- const i18nOptions = createI18nOptions(projectMetadata);
+ const i18nOptions = createI18nOptions(projectMetadata, /** inline */ false, context.logger);
// Normalize xliff format extensions
let format = options.format;
diff --git a/packages/angular/build/src/private.ts b/packages/angular/build/src/private.ts
index 2f2fea08b385..069c7f25c868 100644
--- a/packages/angular/build/src/private.ts
+++ b/packages/angular/build/src/private.ts
@@ -13,6 +13,13 @@
* their existence may change in any future version.
*/
+import {
+ CompilerPluginOptions,
+ createCompilerPlugin as internalCreateCompilerPlugin,
+} from './tools/esbuild/angular/compiler-plugin';
+import { ComponentStylesheetBundler } from './tools/esbuild/angular/component-stylesheets';
+import { BundleStylesheetOptions } from './tools/esbuild/stylesheets/bundle-options';
+
// Builders
export { buildApplicationInternal } from './builders/application';
export type { ApplicationBuilderInternalOptions } from './builders/application/options';
@@ -29,7 +36,20 @@ export { SassWorkerImplementation } from './tools/sass/sass-service';
export { SourceFileCache } from './tools/esbuild/angular/source-file-cache';
export { createJitResourceTransformer } from './tools/angular/transformers/jit-resource-transformer';
export { JavaScriptTransformer } from './tools/esbuild/javascript-transformer';
-export { createCompilerPlugin } from './tools/esbuild/angular/compiler-plugin';
+
+export function createCompilerPlugin(
+ pluginOptions: CompilerPluginOptions,
+ styleOptions: BundleStylesheetOptions & { inlineStyleLanguage: string },
+): import('esbuild').Plugin {
+ return internalCreateCompilerPlugin(
+ pluginOptions,
+ new ComponentStylesheetBundler(
+ styleOptions,
+ styleOptions.inlineStyleLanguage,
+ pluginOptions.incremental,
+ ),
+ );
+}
// Utilities
export * from './utils/bundle-calculator';
diff --git a/packages/angular/build/src/tools/angular/angular-host.ts b/packages/angular/build/src/tools/angular/angular-host.ts
index 1750002f1cdd..103b3e37ac68 100644
--- a/packages/angular/build/src/tools/angular/angular-host.ts
+++ b/packages/angular/build/src/tools/angular/angular-host.ts
@@ -164,6 +164,7 @@ export function createAngularCompilerHost(
typescript: typeof ts,
compilerOptions: AngularCompilerOptions,
hostOptions: AngularHostOptions,
+ packageJsonCache: ts.PackageJsonInfoCache | undefined,
): AngularCompilerHost {
// Create TypeScript compiler host
const host: AngularCompilerHost = typescript.createIncrementalCompilerHost(compilerOptions);
@@ -229,16 +230,17 @@ export function createAngularCompilerHost(
return hostOptions.modifiedFiles;
};
+ // Provide a resolution cache to ensure package.json lookups are cached
+ const resolutionCache = typescript.createModuleResolutionCache(
+ host.getCurrentDirectory(),
+ host.getCanonicalFileName.bind(host),
+ compilerOptions,
+ packageJsonCache,
+ );
+ host.getModuleResolutionCache = () => resolutionCache;
+
// Augment TypeScript Host for file replacements option
if (hostOptions.fileReplacements) {
- // Provide a resolution cache since overriding resolution prevents automatic creation
- const resolutionCache = typescript.createModuleResolutionCache(
- host.getCurrentDirectory(),
- host.getCanonicalFileName.bind(host),
- compilerOptions,
- );
- host.getModuleResolutionCache = () => resolutionCache;
-
augmentHostWithReplacements(typescript, host, hostOptions.fileReplacements, resolutionCache);
}
diff --git a/packages/angular/build/src/tools/angular/compilation/aot-compilation.ts b/packages/angular/build/src/tools/angular/compilation/aot-compilation.ts
index cbfe70a3e5e5..6618f00d25f4 100644
--- a/packages/angular/build/src/tools/angular/compilation/aot-compilation.ts
+++ b/packages/angular/build/src/tools/angular/compilation/aot-compilation.ts
@@ -17,8 +17,17 @@ import {
ensureSourceFileVersions,
} from '../angular-host';
import { replaceBootstrap } from '../transformers/jit-bootstrap-transformer';
+import { lazyRoutesTransformer } from '../transformers/lazy-routes-transformer';
import { createWorkerTransformer } from '../transformers/web-worker-transformer';
import { AngularCompilation, DiagnosticModes, EmitFileResult } from './angular-compilation';
+import { collectHmrCandidates } from './hmr-candidates';
+
+/**
+ * The modified files count limit for performing component HMR analysis.
+ * Performing content analysis for a large amount of files can result in longer rebuild times
+ * than a full rebuild would entail.
+ */
+const HMR_MODIFIED_FILE_LIMIT = 32;
class AngularCompilationState {
constructor(
@@ -39,6 +48,10 @@ class AngularCompilationState {
export class AotCompilation extends AngularCompilation {
#state?: AngularCompilationState;
+ constructor(private readonly browserOnlyBuild: boolean) {
+ super();
+ }
+
async initialize(
tsconfig: string,
hostOptions: AngularHostOptions,
@@ -66,8 +79,39 @@ export class AotCompilation extends AngularCompilation {
hostOptions.externalStylesheets ??= new Map();
}
+ // Reuse the package.json cache from the previous compilation
+ const packageJsonCache = this.#state?.compilerHost
+ .getModuleResolutionCache?.()
+ ?.getPackageJsonInfoCache();
+
+ const useHmr =
+ compilerOptions['_enableHmr'] &&
+ hostOptions.modifiedFiles &&
+ hostOptions.modifiedFiles.size <= HMR_MODIFIED_FILE_LIMIT;
+
+ let staleSourceFiles;
+ let clearPackageJsonCache = false;
+ if (hostOptions.modifiedFiles && this.#state) {
+ for (const modifiedFile of hostOptions.modifiedFiles) {
+ // Clear package.json cache if a node modules file was modified
+ if (!clearPackageJsonCache && modifiedFile.includes('node_modules')) {
+ clearPackageJsonCache = true;
+ packageJsonCache?.clear();
+ }
+
+ // Collect stale source files for HMR analysis of inline component resources
+ if (useHmr) {
+ const sourceFile = this.#state.typeScriptProgram.getSourceFile(modifiedFile);
+ if (sourceFile) {
+ staleSourceFiles ??= new Map();
+ staleSourceFiles.set(modifiedFile, sourceFile);
+ }
+ }
+ }
+ }
+
// Create Angular compiler host
- const host = createAngularCompilerHost(ts, compilerOptions, hostOptions);
+ const host = createAngularCompilerHost(ts, compilerOptions, hostOptions, packageJsonCache);
// Create the Angular specific program that contains the Angular compiler
const angularProgram = profileSync(
@@ -95,14 +139,12 @@ export class AotCompilation extends AngularCompilation {
await profileAsync('NG_ANALYZE_PROGRAM', () => angularCompiler.analyzeAsync());
let templateUpdates;
- if (
- compilerOptions['_enableHmr'] &&
- hostOptions.modifiedFiles &&
- hasOnlyTemplates(hostOptions.modifiedFiles)
- ) {
- const componentNodes = [...hostOptions.modifiedFiles].flatMap((file) => [
- ...angularCompiler.getComponentsWithTemplateFile(file),
- ]);
+ if (useHmr && hostOptions.modifiedFiles && this.#state) {
+ const componentNodes = collectHmrCandidates(
+ hostOptions.modifiedFiles,
+ angularProgram,
+ staleSourceFiles,
+ );
for (const node of componentNodes) {
if (!ts.isClassDeclaration(node)) {
@@ -113,6 +155,7 @@ export class AotCompilation extends AngularCompilation {
if (relativePath.startsWith('..')) {
relativePath = componentFilename;
}
+ relativePath = relativePath.replaceAll('\\', '/');
const updateId = encodeURIComponent(
`${host.getCanonicalFileName(relativePath)}@${node.name?.text}`,
);
@@ -277,8 +320,12 @@ export class AotCompilation extends AngularCompilation {
transformers.before ??= [];
transformers.before.push(
replaceBootstrap(() => typeScriptProgram.getProgram().getTypeChecker()),
+ webWorkerTransform,
);
- transformers.before.push(webWorkerTransform);
+
+ if (!this.browserOnlyBuild) {
+ transformers.before.push(lazyRoutesTransformer(compilerOptions, compilerHost));
+ }
// Emit is handled in write file callback when using TypeScript
if (useTypeScriptTranspilation) {
@@ -422,16 +469,3 @@ function findAffectedFiles(
return affectedFiles;
}
-
-function hasOnlyTemplates(modifiedFiles: Set): boolean {
- for (const file of modifiedFiles) {
- const lowerFile = file.toLowerCase();
- if (lowerFile.endsWith('.html') || lowerFile.endsWith('.svg')) {
- continue;
- }
-
- return false;
- }
-
- return true;
-}
diff --git a/packages/angular/build/src/tools/angular/compilation/factory.ts b/packages/angular/build/src/tools/angular/compilation/factory.ts
index 5984b4815f6a..91447dea24cf 100644
--- a/packages/angular/build/src/tools/angular/compilation/factory.ts
+++ b/packages/angular/build/src/tools/angular/compilation/factory.ts
@@ -14,22 +14,26 @@ import type { AngularCompilation } from './angular-compilation';
* compilation either for AOT or JIT mode. By default a parallel compilation is created
* that uses a Node.js worker thread.
* @param jit True, for Angular JIT compilation; False, for Angular AOT compilation.
+ * @param browserOnlyBuild True, for browser only builds; False, for browser and server builds.
* @returns An instance of an Angular compilation object.
*/
-export async function createAngularCompilation(jit: boolean): Promise {
+export async function createAngularCompilation(
+ jit: boolean,
+ browserOnlyBuild: boolean,
+): Promise {
if (useParallelTs) {
const { ParallelCompilation } = await import('./parallel-compilation');
- return new ParallelCompilation(jit);
+ return new ParallelCompilation(jit, browserOnlyBuild);
}
if (jit) {
const { JitCompilation } = await import('./jit-compilation');
- return new JitCompilation();
+ return new JitCompilation(browserOnlyBuild);
} else {
const { AotCompilation } = await import('./aot-compilation');
- return new AotCompilation();
+ return new AotCompilation(browserOnlyBuild);
}
}
diff --git a/packages/angular/build/src/tools/angular/compilation/hmr-candidates.ts b/packages/angular/build/src/tools/angular/compilation/hmr-candidates.ts
new file mode 100644
index 000000000000..bc271d160cad
--- /dev/null
+++ b/packages/angular/build/src/tools/angular/compilation/hmr-candidates.ts
@@ -0,0 +1,314 @@
+/**
+ * @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
+ */
+
+import type ng from '@angular/compiler-cli';
+import assert from 'node:assert';
+import ts from 'typescript';
+
+/**
+ * Analyzes one or more modified files for changes to determine if any
+ * class declarations for Angular components are candidates for hot
+ * module replacement (HMR). If any source files are also modified but
+ * are not candidates then all candidates become invalid. This invalidation
+ * ensures that a full rebuild occurs and the running application stays
+ * synchronized with the code.
+ * @param modifiedFiles A set of modified files to analyze.
+ * @param param1 An Angular compiler instance
+ * @param staleSourceFiles A map of paths to previous source file instances.
+ * @returns A set of HMR candidate component class declarations.
+ */
+export function collectHmrCandidates(
+ modifiedFiles: Set,
+ { compiler }: ng.NgtscProgram,
+ staleSourceFiles: Map | undefined,
+): Set {
+ const candidates = new Set();
+
+ for (const file of modifiedFiles) {
+ // If the file is a template for component(s), add component classes as candidates
+ const templateFileNodes = compiler.getComponentsWithTemplateFile(file);
+ if (templateFileNodes.size) {
+ templateFileNodes.forEach((node) => candidates.add(node as ts.ClassDeclaration));
+ continue;
+ }
+
+ // If the file is a style for component(s), add component classes as candidates
+ const styleFileNodes = compiler.getComponentsWithStyleFile(file);
+ if (styleFileNodes.size) {
+ styleFileNodes.forEach((node) => candidates.add(node as ts.ClassDeclaration));
+ continue;
+ }
+
+ const staleSource = staleSourceFiles?.get(file);
+ if (staleSource === undefined) {
+ // Unknown file requires a rebuild so clear out the candidates and stop collecting
+ candidates.clear();
+ break;
+ }
+
+ const updatedSource = compiler.getCurrentProgram().getSourceFile(file);
+ if (updatedSource === undefined) {
+ // No longer existing program file requires a rebuild so clear out the candidates and stop collecting
+ candidates.clear();
+ break;
+ }
+
+ // Analyze the stale and updated file for changes
+ const fileCandidates = analyzeFileUpdates(staleSource, updatedSource, compiler);
+ if (fileCandidates) {
+ fileCandidates.forEach((node) => candidates.add(node));
+ } else {
+ // Unsupported HMR changes present
+ // Only template and style literal changes are allowed.
+ candidates.clear();
+ break;
+ }
+ }
+
+ return candidates;
+}
+
+/**
+ * Analyzes the updates of a source file for potential HMR component class candidates.
+ * A source file can contain candidates if only the Angular component metadata of a class
+ * has been changed and the metadata changes are only of supported fields.
+ * @param stale The stale (previous) source file instance.
+ * @param updated The updated source file instance.
+ * @param compiler An Angular compiler instance.
+ * @returns An array of candidate class declarations; or `null` if unsupported changes are present.
+ */
+function analyzeFileUpdates(
+ stale: ts.SourceFile,
+ updated: ts.SourceFile,
+ compiler: ng.NgtscProgram['compiler'],
+): ts.ClassDeclaration[] | null {
+ if (stale.statements.length !== updated.statements.length) {
+ return null;
+ }
+
+ const candidates: ts.ClassDeclaration[] = [];
+
+ for (let i = 0; i < updated.statements.length; ++i) {
+ const updatedNode = updated.statements[i];
+ const staleNode = stale.statements[i];
+
+ if (ts.isClassDeclaration(updatedNode)) {
+ if (!ts.isClassDeclaration(staleNode)) {
+ return null;
+ }
+
+ // Check class declaration differences (name/heritage/modifiers)
+ if (updatedNode.name?.text !== staleNode.name?.text) {
+ return null;
+ }
+ if (!equalRangeText(updatedNode.heritageClauses, updated, staleNode.heritageClauses, stale)) {
+ return null;
+ }
+ const updatedModifiers = ts.getModifiers(updatedNode);
+ const staleModifiers = ts.getModifiers(staleNode);
+ if (
+ updatedModifiers?.length !== staleModifiers?.length ||
+ !updatedModifiers?.every((updatedModifier) =>
+ staleModifiers?.some((staleModifier) => updatedModifier.kind === staleModifier.kind),
+ )
+ ) {
+ return null;
+ }
+
+ // Check for component class nodes
+ const meta = compiler.getMeta(updatedNode);
+ if (meta?.decorator && (meta as { isComponent?: boolean }).isComponent === true) {
+ const updatedDecorators = ts.getDecorators(updatedNode);
+ const staleDecorators = ts.getDecorators(staleNode);
+ if (!staleDecorators || staleDecorators.length !== updatedDecorators?.length) {
+ return null;
+ }
+
+ // TODO: Check other decorators instead of assuming all multi-decorator components are unsupported
+ if (staleDecorators.length > 1) {
+ return null;
+ }
+
+ // Find index of component metadata decorator
+ const metaDecoratorIndex = updatedDecorators?.indexOf(meta.decorator);
+ assert(
+ metaDecoratorIndex !== undefined,
+ 'Component metadata decorator should always be present on component class.',
+ );
+ const updatedDecoratorExpression = meta.decorator.expression;
+ assert(
+ ts.isCallExpression(updatedDecoratorExpression) &&
+ updatedDecoratorExpression.arguments.length === 1,
+ 'Component metadata decorator should contain a call expression with a single argument.',
+ );
+
+ // Check the matching stale index for the component decorator
+ const staleDecoratorExpression = staleDecorators[metaDecoratorIndex]?.expression;
+ if (
+ !staleDecoratorExpression ||
+ !ts.isCallExpression(staleDecoratorExpression) ||
+ staleDecoratorExpression.arguments.length !== 1
+ ) {
+ return null;
+ }
+
+ // Check decorator name/expression
+ // NOTE: This would typically be `Component` but can also be a property expression or some other alias.
+ // To avoid complex checks, this ensures the textual representation does not change. This has a low chance
+ // of a false positive if the expression is changed to still reference the `Component` type but has different
+ // text. However, it is rare for `Component` to not be used directly and additionally unlikely that it would
+ // be changed between edits. A false positive would also only lead to a difference of a full page reload versus
+ // an HMR update.
+ if (
+ !equalRangeText(
+ updatedDecoratorExpression.expression,
+ updated,
+ staleDecoratorExpression.expression,
+ stale,
+ )
+ ) {
+ return null;
+ }
+
+ // Compare component meta decorator object literals
+ if (
+ hasUnsupportedMetaUpdates(
+ staleDecoratorExpression,
+ stale,
+ updatedDecoratorExpression,
+ updated,
+ )
+ ) {
+ return null;
+ }
+
+ // Compare text of the member nodes to determine if any changes have occurred
+ if (!equalRangeText(updatedNode.members, updated, staleNode.members, stale)) {
+ // A change to a member outside a component's metadata is unsupported
+ return null;
+ }
+
+ // If all previous class checks passed, this class is supported for HMR updates
+ candidates.push(updatedNode);
+ continue;
+ }
+ }
+
+ // Compare text of the statement nodes to determine if any changes have occurred
+ // TODO: Consider expanding this to check semantic updates for each node kind
+ if (!equalRangeText(updatedNode, updated, staleNode, stale)) {
+ // A change to a statement outside a component's metadata is unsupported
+ return null;
+ }
+ }
+
+ return candidates;
+}
+
+/**
+ * The set of Angular component metadata fields that are supported by HMR updates.
+ */
+const SUPPORTED_FIELDS = new Set(['template', 'templateUrl', 'styles', 'styleUrl', 'stylesUrl']);
+
+/**
+ * Analyzes the metadata fields of a decorator call expression for unsupported HMR updates.
+ * Only updates to supported fields can be present for HMR to be viable.
+ * @param staleCall A call expression instance.
+ * @param staleSource The source file instance containing the stale call instance.
+ * @param updatedCall A call expression instance.
+ * @param updatedSource The source file instance containing the updated call instance.
+ * @returns true, if unsupported metadata updates are present; false, otherwise.
+ */
+function hasUnsupportedMetaUpdates(
+ staleCall: ts.CallExpression,
+ staleSource: ts.SourceFile,
+ updatedCall: ts.CallExpression,
+ updatedSource: ts.SourceFile,
+): boolean {
+ const staleObject = staleCall.arguments[0];
+ const updatedObject = updatedCall.arguments[0];
+
+ if (!ts.isObjectLiteralExpression(staleObject) || !ts.isObjectLiteralExpression(updatedObject)) {
+ return true;
+ }
+
+ const unsupportedFields: ts.Node[] = [];
+
+ for (const property of staleObject.properties) {
+ if (!ts.isPropertyAssignment(property) || ts.isComputedPropertyName(property.name)) {
+ // Unsupported object literal property
+ return true;
+ }
+
+ const name = property.name.text;
+ if (SUPPORTED_FIELDS.has(name)) {
+ continue;
+ }
+
+ unsupportedFields.push(property.initializer);
+ }
+
+ let i = 0;
+ for (const property of updatedObject.properties) {
+ if (!ts.isPropertyAssignment(property) || ts.isComputedPropertyName(property.name)) {
+ // Unsupported object literal property
+ return true;
+ }
+
+ const name = property.name.text;
+ if (SUPPORTED_FIELDS.has(name)) {
+ continue;
+ }
+
+ // Compare in order
+ if (!equalRangeText(property.initializer, updatedSource, unsupportedFields[i++], staleSource)) {
+ return true;
+ }
+ }
+
+ return i !== unsupportedFields.length;
+}
+
+/**
+ * Compares the text from a provided range in a source file to the text of a range in a second source file.
+ * The comparison avoids making any intermediate string copies.
+ * @param firstRange A text range within the first source file.
+ * @param firstSource A source file instance.
+ * @param secondRange A text range within the second source file.
+ * @param secondSource A source file instance.
+ * @returns true, if the text from both ranges is equal; false, otherwise.
+ */
+function equalRangeText(
+ firstRange: ts.ReadonlyTextRange | undefined,
+ firstSource: ts.SourceFile,
+ secondRange: ts.ReadonlyTextRange | undefined,
+ secondSource: ts.SourceFile,
+): boolean {
+ // Check matching undefined values
+ if (!firstRange || !secondRange) {
+ return firstRange === secondRange;
+ }
+
+ // Ensure lengths are equal
+ const firstLength = firstRange.end - firstRange.pos;
+ const secondLength = secondRange.end - secondRange.pos;
+ if (firstLength !== secondLength) {
+ return false;
+ }
+
+ // Check each character
+ for (let i = 0; i < firstLength; ++i) {
+ const firstChar = firstSource.text.charCodeAt(i + firstRange.pos);
+ const secondChar = secondSource.text.charCodeAt(i + secondRange.pos);
+ if (firstChar !== secondChar) {
+ return false;
+ }
+ }
+
+ return true;
+}
diff --git a/packages/angular/build/src/tools/angular/compilation/jit-compilation.ts b/packages/angular/build/src/tools/angular/compilation/jit-compilation.ts
index eab21a8608c5..a811cb50ec0a 100644
--- a/packages/angular/build/src/tools/angular/compilation/jit-compilation.ts
+++ b/packages/angular/build/src/tools/angular/compilation/jit-compilation.ts
@@ -13,6 +13,7 @@ import { loadEsmModule } from '../../../utils/load-esm';
import { profileSync } from '../../esbuild/profiling';
import { AngularHostOptions, createAngularCompilerHost } from '../angular-host';
import { createJitResourceTransformer } from '../transformers/jit-resource-transformer';
+import { lazyRoutesTransformer } from '../transformers/lazy-routes-transformer';
import { createWorkerTransformer } from '../transformers/web-worker-transformer';
import { AngularCompilation, DiagnosticModes, EmitFileResult } from './angular-compilation';
@@ -29,6 +30,10 @@ class JitCompilationState {
export class JitCompilation extends AngularCompilation {
#state?: JitCompilationState;
+ constructor(private readonly browserOnlyBuild: boolean) {
+ super();
+ }
+
async initialize(
tsconfig: string,
hostOptions: AngularHostOptions,
@@ -53,7 +58,7 @@ export class JitCompilation extends AngularCompilation {
compilerOptionsTransformer?.(originalCompilerOptions) ?? originalCompilerOptions;
// Create Angular compiler host
- const host = createAngularCompilerHost(ts, compilerOptions, hostOptions);
+ const host = createAngularCompilerHost(ts, compilerOptions, hostOptions, undefined);
// Create the TypeScript Program
const typeScriptProgram = profileSync('TS_CREATE_PROGRAM', () =>
@@ -116,8 +121,8 @@ export class JitCompilation extends AngularCompilation {
replaceResourcesTransform,
webWorkerTransform,
} = this.#state;
- const buildInfoFilename =
- typeScriptProgram.getCompilerOptions().tsBuildInfoFile ?? '.tsbuildinfo';
+ const compilerOptions = typeScriptProgram.getCompilerOptions();
+ const buildInfoFilename = compilerOptions.tsBuildInfoFile ?? '.tsbuildinfo';
const emittedFiles: EmitFileResult[] = [];
const writeFileCallback: ts.WriteFileCallback = (filename, contents, _a, _b, sourceFiles) => {
@@ -140,6 +145,10 @@ export class JitCompilation extends AngularCompilation {
],
};
+ if (!this.browserOnlyBuild) {
+ transformers.before.push(lazyRoutesTransformer(compilerOptions, compilerHost));
+ }
+
// TypeScript will loop until there are no more affected files in the program
while (
typeScriptProgram.emitNextAffectedFile(writeFileCallback, undefined, undefined, transformers)
diff --git a/packages/angular/build/src/tools/angular/compilation/parallel-compilation.ts b/packages/angular/build/src/tools/angular/compilation/parallel-compilation.ts
index f3b3503f6988..be612cbfcad4 100644
--- a/packages/angular/build/src/tools/angular/compilation/parallel-compilation.ts
+++ b/packages/angular/build/src/tools/angular/compilation/parallel-compilation.ts
@@ -26,7 +26,10 @@ import { AngularCompilation, DiagnosticModes, EmitFileResult } from './angular-c
export class ParallelCompilation extends AngularCompilation {
readonly #worker: WorkerPool;
- constructor(readonly jit: boolean) {
+ constructor(
+ private readonly jit: boolean,
+ private readonly browserOnlyBuild: boolean,
+ ) {
super();
// TODO: Convert to import.meta usage during ESM transition
@@ -99,6 +102,7 @@ export class ParallelCompilation extends AngularCompilation {
fileReplacements: hostOptions.fileReplacements,
tsconfig,
jit: this.jit,
+ browserOnlyBuild: this.browserOnlyBuild,
stylesheetPort: stylesheetChannel.port2,
optionsPort: optionsChannel.port2,
optionsSignal,
diff --git a/packages/angular/build/src/tools/angular/compilation/parallel-worker.ts b/packages/angular/build/src/tools/angular/compilation/parallel-worker.ts
index 2669951c12e4..d67fbb9bd06c 100644
--- a/packages/angular/build/src/tools/angular/compilation/parallel-worker.ts
+++ b/packages/angular/build/src/tools/angular/compilation/parallel-worker.ts
@@ -17,6 +17,7 @@ import { JitCompilation } from './jit-compilation';
export interface InitRequest {
jit: boolean;
+ browserOnlyBuild: boolean;
tsconfig: string;
fileReplacements?: Record;
stylesheetPort: MessagePort;
@@ -31,7 +32,9 @@ let compilation: AngularCompilation | undefined;
const sourceFileCache = new SourceFileCache();
export async function initialize(request: InitRequest) {
- compilation ??= request.jit ? new JitCompilation() : new AotCompilation();
+ compilation ??= request.jit
+ ? new JitCompilation(request.browserOnlyBuild)
+ : new AotCompilation(request.browserOnlyBuild);
const stylesheetRequests = new Map void, (reason: Error) => void]>();
request.stylesheetPort.on('message', ({ requestId, value, error }) => {
diff --git a/packages/angular/build/src/tools/angular/transformers/lazy-routes-transformer.ts b/packages/angular/build/src/tools/angular/transformers/lazy-routes-transformer.ts
new file mode 100644
index 000000000000..10d45d00d714
--- /dev/null
+++ b/packages/angular/build/src/tools/angular/transformers/lazy-routes-transformer.ts
@@ -0,0 +1,225 @@
+/**
+ * @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
+ */
+
+import assert from 'node:assert';
+import { relative } from 'node:path/posix';
+import ts from 'typescript';
+
+/**
+ * A transformer factory that adds a property to the lazy-loaded route object.
+ * This property is used to allow for the retrieval of the module path during SSR.
+ *
+ * @param compilerOptions The compiler options.
+ * @param compilerHost The compiler host.
+ * @returns A transformer factory.
+ *
+ * @example
+ * **Before:**
+ * ```ts
+ * const routes: Routes = [
+ * {
+ * path: 'lazy',
+ * loadChildren: () => import('./lazy/lazy.module').then(m => m.LazyModule)
+ * }
+ * ];
+ * ```
+ *
+ * **After:**
+ * ```ts
+ * const routes: Routes = [
+ * {
+ * path: 'lazy',
+ * loadChildren: () => import('./lazy/lazy.module').then(m => m.LazyModule),
+ * ...(typeof ngServerMode !== "undefined" && ngServerMode ? { ɵentryName: "./lazy/lazy.module.ts" }: {})
+ * }
+ * ];
+ * ```
+ */
+export function lazyRoutesTransformer(
+ compilerOptions: ts.CompilerOptions,
+ compilerHost: ts.CompilerHost,
+): ts.TransformerFactory {
+ const moduleResolutionCache = compilerHost.getModuleResolutionCache?.();
+ assert(
+ typeof compilerOptions.basePath === 'string',
+ 'compilerOptions.basePath should be a string.',
+ );
+ const basePath = compilerOptions.basePath;
+
+ return (context: ts.TransformationContext) => {
+ const factory = context.factory;
+
+ const visitor = (node: ts.Node): ts.Node => {
+ if (!ts.isObjectLiteralExpression(node)) {
+ // Not an object literal, so skip it.
+ return ts.visitEachChild(node, visitor, context);
+ }
+
+ const loadFunction = getLoadComponentOrChildrenProperty(node)?.initializer;
+ // Check if the initializer is an arrow function or a function expression
+ if (
+ !loadFunction ||
+ (!ts.isArrowFunction(loadFunction) && !ts.isFunctionExpression(loadFunction))
+ ) {
+ return ts.visitEachChild(node, visitor, context);
+ }
+
+ let callExpression: ts.CallExpression | undefined;
+
+ if (ts.isArrowFunction(loadFunction)) {
+ // Handle arrow functions: body can either be a block or a direct call expression
+ const body = loadFunction.body;
+
+ if (ts.isBlock(body)) {
+ // Arrow function with a block: check the first statement for a return call expression
+ const firstStatement = body.statements[0];
+
+ if (
+ firstStatement &&
+ ts.isReturnStatement(firstStatement) &&
+ firstStatement.expression &&
+ ts.isCallExpression(firstStatement.expression)
+ ) {
+ callExpression = firstStatement.expression;
+ }
+ } else if (ts.isCallExpression(body)) {
+ // Arrow function with a direct call expression as its body
+ callExpression = body;
+ }
+ } else if (ts.isFunctionExpression(loadFunction)) {
+ // Handle function expressions: check for a return statement with a call expression
+ const returnExpression = loadFunction.body.statements.find(
+ ts.isReturnStatement,
+ )?.expression;
+
+ if (returnExpression && ts.isCallExpression(returnExpression)) {
+ callExpression = returnExpression;
+ }
+ }
+
+ if (!callExpression) {
+ return ts.visitEachChild(node, visitor, context);
+ }
+
+ // Optionally check for the 'then' property access expression
+ const expression = callExpression.expression;
+ if (
+ !ts.isCallExpression(expression) &&
+ ts.isPropertyAccessExpression(expression) &&
+ expression.name.text !== 'then'
+ ) {
+ return ts.visitEachChild(node, visitor, context);
+ }
+
+ const importExpression = ts.isPropertyAccessExpression(expression)
+ ? expression.expression // Navigate to the underlying expression for 'then'
+ : callExpression;
+
+ // Ensure the underlying expression is an import call
+ if (
+ !ts.isCallExpression(importExpression) ||
+ importExpression.expression.kind !== ts.SyntaxKind.ImportKeyword
+ ) {
+ return ts.visitEachChild(node, visitor, context);
+ }
+
+ // Check if the argument to the import call is a string literal
+ const callExpressionArgument = importExpression.arguments[0];
+ if (!ts.isStringLiteralLike(callExpressionArgument)) {
+ // Not a string literal, so skip it.
+ return ts.visitEachChild(node, visitor, context);
+ }
+
+ const resolvedPath = ts.resolveModuleName(
+ callExpressionArgument.text,
+ node.getSourceFile().fileName,
+ compilerOptions,
+ compilerHost,
+ moduleResolutionCache,
+ )?.resolvedModule?.resolvedFileName;
+
+ if (!resolvedPath) {
+ // Could not resolve the module, so skip it.
+ return ts.visitEachChild(node, visitor, context);
+ }
+
+ const resolvedRelativePath = relative(basePath, resolvedPath);
+
+ // Create the new property
+ // Example: `...(typeof ngServerMode !== "undefined" && ngServerMode ? { ɵentryName: "src/home.ts" }: {})`
+ const newProperty = factory.createSpreadAssignment(
+ factory.createParenthesizedExpression(
+ factory.createConditionalExpression(
+ factory.createBinaryExpression(
+ factory.createBinaryExpression(
+ factory.createTypeOfExpression(factory.createIdentifier('ngServerMode')),
+ factory.createToken(ts.SyntaxKind.ExclamationEqualsEqualsToken),
+ factory.createStringLiteral('undefined'),
+ ),
+ factory.createToken(ts.SyntaxKind.AmpersandAmpersandToken),
+ factory.createIdentifier('ngServerMode'),
+ ),
+ factory.createToken(ts.SyntaxKind.QuestionToken),
+ factory.createObjectLiteralExpression([
+ factory.createPropertyAssignment(
+ factory.createIdentifier('ɵentryName'),
+ factory.createStringLiteral(resolvedRelativePath),
+ ),
+ ]),
+ factory.createToken(ts.SyntaxKind.ColonToken),
+ factory.createObjectLiteralExpression([]),
+ ),
+ ),
+ );
+
+ // Add the new property to the object literal.
+ return factory.updateObjectLiteralExpression(node, [...node.properties, newProperty]);
+ };
+
+ return (sourceFile) => {
+ const text = sourceFile.text;
+ if (!text.includes('loadC')) {
+ // Fast check for 'loadComponent' and 'loadChildren'.
+ return sourceFile;
+ }
+
+ return ts.visitEachChild(sourceFile, visitor, context);
+ };
+ };
+}
+
+/**
+ * Retrieves the property assignment for the `loadComponent` or `loadChildren` property of a route object.
+ *
+ * @param node The object literal expression to search.
+ * @returns The property assignment if found, otherwise `undefined`.
+ */
+function getLoadComponentOrChildrenProperty(
+ node: ts.ObjectLiteralExpression,
+): ts.PropertyAssignment | undefined {
+ let hasPathProperty = false;
+ let loadComponentOrChildrenProperty: ts.PropertyAssignment | undefined;
+ for (const prop of node.properties) {
+ if (!ts.isPropertyAssignment(prop) || !ts.isIdentifier(prop.name)) {
+ continue;
+ }
+
+ const propertyNameText = prop.name.text;
+ if (propertyNameText === 'path') {
+ hasPathProperty = true;
+ } else if (propertyNameText === 'loadComponent' || propertyNameText === 'loadChildren') {
+ loadComponentOrChildrenProperty = prop;
+ }
+
+ if (hasPathProperty && loadComponentOrChildrenProperty) {
+ break;
+ }
+ }
+
+ return loadComponentOrChildrenProperty;
+}
diff --git a/packages/angular/build/src/tools/angular/transformers/lazy-routes-transformer_spec.ts b/packages/angular/build/src/tools/angular/transformers/lazy-routes-transformer_spec.ts
new file mode 100644
index 000000000000..4dd388f28eb1
--- /dev/null
+++ b/packages/angular/build/src/tools/angular/transformers/lazy-routes-transformer_spec.ts
@@ -0,0 +1,208 @@
+/**
+ * @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
+ */
+
+import ts from 'typescript';
+import { lazyRoutesTransformer } from './lazy-routes-transformer';
+
+describe('lazyRoutesTransformer', () => {
+ let program: ts.Program;
+ let compilerHost: ts.CompilerHost;
+
+ beforeEach(() => {
+ // Mock a basic TypeScript program and compilerHost
+ program = ts.createProgram(['/project/src/dummy.ts'], { basePath: '/project/' });
+ compilerHost = {
+ getNewLine: () => '\n',
+ fileExists: () => true,
+ readFile: () => '',
+ writeFile: () => undefined,
+ getCanonicalFileName: (fileName: string) => fileName,
+ getCurrentDirectory: () => '/project',
+ getDefaultLibFileName: () => 'lib.d.ts',
+ getSourceFile: () => undefined,
+ useCaseSensitiveFileNames: () => true,
+ resolveModuleNames: (moduleNames, containingFile) =>
+ moduleNames.map(
+ (name) =>
+ ({
+ resolvedFileName: `/project/src/${name}.ts`,
+ }) as ts.ResolvedModule,
+ ),
+ };
+ });
+
+ const transformSourceFile = (sourceCode: string): ts.SourceFile => {
+ const sourceFile = ts.createSourceFile(
+ '/project/src/dummy.ts',
+ sourceCode,
+ ts.ScriptTarget.ESNext,
+ true,
+ ts.ScriptKind.TS,
+ );
+
+ const transformer = lazyRoutesTransformer(program.getCompilerOptions(), compilerHost);
+ const result = ts.transform(sourceFile, [transformer]);
+
+ return result.transformed[0];
+ };
+
+ it('should return the same object when the routes array contains an empty object', () => {
+ const source = `
+ const routes = [{}];
+ `;
+
+ const transformedSourceFile = transformSourceFile(source);
+ const transformedCode = ts.createPrinter().printFile(transformedSourceFile);
+
+ expect(transformedCode).toContain(`const routes = [{}]`);
+ });
+
+ it('should add ɵentryName property to object with loadComponent and path (Arrow function)', () => {
+ const source = `
+ const routes = [
+ {
+ path: 'home',
+ loadComponent: () => import('./home').then(m => m.HomeComponent)
+ }
+ ];
+ `;
+
+ const transformedSourceFile = transformSourceFile(source);
+ const transformedCode = ts.createPrinter().printFile(transformedSourceFile);
+
+ expect(transformedCode).toContain(
+ `...(typeof ngServerMode !== "undefined" && ngServerMode ? { ɵentryName: "src/home.ts" } : {})`,
+ );
+ });
+
+ it('should add ɵentryName property to object with loadComponent and path (Arrow function with return)', () => {
+ const source = `
+ const routes = [
+ {
+ path: 'home',
+ loadComponent: () => {
+ return import('./home').then(m => m.HomeComponent);
+ }
+ }
+ ];
+ `;
+
+ const transformedSourceFile = transformSourceFile(source);
+ const transformedCode = ts.createPrinter().printFile(transformedSourceFile);
+
+ expect(transformedCode).toContain(
+ `...(typeof ngServerMode !== "undefined" && ngServerMode ? { ɵentryName: "src/home.ts" } : {})`,
+ );
+ });
+
+ it('should add ɵentryName property to object with loadComponent and path (Arrow function without .then)', () => {
+ const source = `
+ const routes = [
+ {
+ path: 'about',
+ loadComponent: () => import('./about')
+ }
+ ];
+ `;
+
+ const transformedSourceFile = transformSourceFile(source);
+ const transformedCode = ts.createPrinter().printFile(transformedSourceFile);
+
+ expect(transformedCode).toContain(
+ `...(typeof ngServerMode !== "undefined" && ngServerMode ? { ɵentryName: "src/about.ts" } : {})`,
+ );
+ });
+
+ it('should add ɵentryName property to object with loadComponent using return and .then', () => {
+ const source = `
+ const routes = [
+ {
+ path: '',
+ loadComponent: () => {
+ return import('./home').then((m) => m.HomeComponent);
+ }
+ }
+ ];
+ `;
+
+ const transformedSourceFile = transformSourceFile(source);
+ const transformedCode = ts.createPrinter().printFile(transformedSourceFile);
+
+ expect(transformedCode).toContain(
+ `...(typeof ngServerMode !== "undefined" && ngServerMode ? { ɵentryName: "src/home.ts" } : {})`,
+ );
+ });
+
+ it('should add ɵentryName property to object with loadComponent and path (Function expression)', () => {
+ const source = `
+ const routes = [
+ {
+ path: 'home',
+ loadComponent: function () { return import('./home').then(m => m.HomeComponent) }
+ }
+ ];
+ `;
+
+ const transformedSourceFile = transformSourceFile(source);
+ const transformedCode = ts.createPrinter().printFile(transformedSourceFile);
+
+ expect(transformedCode).toContain(
+ `...(typeof ngServerMode !== "undefined" && ngServerMode ? { ɵentryName: "src/home.ts" } : {})`,
+ );
+ });
+
+ it('should not modify unrelated object literals', () => {
+ const source = `
+ const routes = [
+ {
+ path: 'home',
+ component: HomeComponent
+ }
+ ];
+ `;
+
+ const transformedSourceFile = transformSourceFile(source);
+ const transformedCode = ts.createPrinter().printFile(transformedSourceFile);
+
+ expect(transformedCode).not.toContain(`ɵentryName`);
+ });
+
+ it('should ignore loadComponent without a valid import call', () => {
+ const source = `
+ const routes = [
+ {
+ path: 'home',
+ loadComponent: () => someFunction()
+ }
+ ];
+ `;
+
+ const transformedSourceFile = transformSourceFile(source);
+ const transformedCode = ts.createPrinter().printFile(transformedSourceFile);
+
+ expect(transformedCode).not.toContain(`ɵentryName`);
+ });
+
+ it('should resolve paths relative to basePath', () => {
+ const source = `
+ const routes = [
+ {
+ path: 'about',
+ loadChildren: () => import('./features/about').then(m => m.AboutModule)
+ }
+ ];
+ `;
+
+ const transformedSourceFile = transformSourceFile(source);
+ const transformedCode = ts.createPrinter().printFile(transformedSourceFile);
+
+ expect(transformedCode).toContain(
+ `...(typeof ngServerMode !== "undefined" && ngServerMode ? { ɵentryName: "src/features/about.ts" } : {})`,
+ );
+ });
+});
diff --git a/packages/angular/build/src/tools/esbuild/angular/compiler-plugin.ts b/packages/angular/build/src/tools/esbuild/angular/compiler-plugin.ts
index 6d23af4399be..740c2d119b5a 100644
--- a/packages/angular/build/src/tools/esbuild/angular/compiler-plugin.ts
+++ b/packages/angular/build/src/tools/esbuild/angular/compiler-plugin.ts
@@ -40,6 +40,7 @@ export interface CompilerPluginOptions {
sourcemap: boolean | 'external';
tsconfig: string;
jit?: boolean;
+ browserOnlyBuild?: boolean;
/** Skip TypeScript compilation setup. This is useful to re-use the TypeScript compilation from another plugin. */
noopTypeScriptCompilation?: boolean;
@@ -119,7 +120,7 @@ export function createCompilerPlugin(
// Create new reusable compilation for the appropriate mode based on the `jit` plugin option
const compilation: AngularCompilation = pluginOptions.noopTypeScriptCompilation
? new NoopCompilation()
- : await createAngularCompilation(!!pluginOptions.jit);
+ : await createAngularCompilation(!!pluginOptions.jit, !!pluginOptions.browserOnlyBuild);
// Compilation is initially assumed to have errors until emitted
let hasCompilationErrors = true;
@@ -198,7 +199,7 @@ export function createCompilerPlugin(
// invalid the output and force a full page reload for HMR cases. The containing file and order
// of the style within the containing file is used.
pluginOptions.externalRuntimeStyles
- ? createHash('sha-256')
+ ? createHash('sha256')
.update(containingFile)
.update((order ?? 0).toString())
.update(className ?? '')
@@ -512,6 +513,31 @@ export function createCompilerPlugin(
}),
);
+ // Add a load handler if there are file replacement option entries for JSON files
+ if (
+ pluginOptions.fileReplacements &&
+ Object.keys(pluginOptions.fileReplacements).some((value) => value.endsWith('.json'))
+ ) {
+ build.onLoad(
+ { filter: /\.json$/ },
+ createCachedLoad(pluginOptions.loadResultCache, async (args) => {
+ const replacement = pluginOptions.fileReplacements?.[path.normalize(args.path)];
+ if (replacement) {
+ return {
+ contents: await import('fs/promises').then(({ readFile }) =>
+ readFile(path.normalize(replacement)),
+ ),
+ loader: 'json' as const,
+ watchFiles: [replacement],
+ };
+ }
+
+ // If no replacement defined, let esbuild handle it directly
+ return null;
+ }),
+ );
+ }
+
// Setup bundling of component templates and stylesheets when in JIT mode
if (pluginOptions.jit) {
setupJitPluginCallbacks(
diff --git a/packages/angular/build/src/tools/esbuild/application-code-bundle.ts b/packages/angular/build/src/tools/esbuild/application-code-bundle.ts
index ceb60c644949..30f6b750a1a0 100644
--- a/packages/angular/build/src/tools/esbuild/application-code-bundle.ts
+++ b/packages/angular/build/src/tools/esbuild/application-code-bundle.ts
@@ -200,6 +200,20 @@ export function createServerPolyfillBundleOptions(
return;
}
+ const jsBanner: string[] = [];
+ if (polyfillBundleOptions.external?.length) {
+ jsBanner.push(`globalThis['ngServerMode'] = true;`);
+ }
+
+ if (isNodePlatform) {
+ // Note: Needed as esbuild does not provide require shims / proxy from ESModules.
+ // See: https://github.com/evanw/esbuild/issues/1921.
+ jsBanner.push(
+ `import { createRequire } from 'node:module';`,
+ `globalThis['require'] ??= createRequire(import.meta.url);`,
+ );
+ }
+
const buildOptions: BuildOptions = {
...polyfillBundleOptions,
platform: isNodePlatform ? 'node' : 'neutral',
@@ -210,16 +224,9 @@ export function createServerPolyfillBundleOptions(
// More details: https://github.com/angular/angular-cli/issues/25405.
mainFields: ['es2020', 'es2015', 'module', 'main'],
entryNames: '[name]',
- banner: isNodePlatform
- ? {
- js: [
- // Note: Needed as esbuild does not provide require shims / proxy from ESModules.
- // See: https://github.com/evanw/esbuild/issues/1921.
- `import { createRequire } from 'node:module';`,
- `globalThis['require'] ??= createRequire(import.meta.url);`,
- ].join('\n'),
- }
- : undefined,
+ banner: {
+ js: jsBanner.join('\n'),
+ },
target,
entryPoints: {
'polyfills.server': namespace,
@@ -254,9 +261,7 @@ export function createServerMainCodeBundleOptions(
return (loadResultCache) => {
const pluginOptions = createCompilerPluginOptions(options, sourceFileCache, loadResultCache);
-
const mainServerNamespace = 'angular:main-server';
- const mainServerInjectPolyfillsNamespace = 'angular:main-server-inject-polyfills';
const mainServerInjectManifestNamespace = 'angular:main-server-inject-manifest';
const zoneless = isZonelessApp(polyfills);
const entryPoints: Record = {
@@ -275,7 +280,9 @@ export function createServerMainCodeBundleOptions(
const buildOptions: BuildOptions = {
...getEsBuildServerCommonOptions(options),
target,
- inject: [mainServerInjectPolyfillsNamespace, mainServerInjectManifestNamespace],
+ banner: {
+ js: `import './polyfills.server.mjs';`,
+ },
entryPoints,
supported: getFeatureSupport(target, zoneless),
plugins: [
@@ -311,18 +318,10 @@ export function createServerMainCodeBundleOptions(
buildOptions.plugins.push(
createServerBundleMetadata(),
- createVirtualModulePlugin({
- namespace: mainServerInjectPolyfillsNamespace,
- cache: loadResultCache,
- loadContent: () => ({
- contents: `import './polyfills.server.mjs';`,
- loader: 'js',
- resolveDir: workspaceRoot,
- }),
- }),
createVirtualModulePlugin({
namespace: mainServerInjectManifestNamespace,
cache: loadResultCache,
+ entryPointOnly: false,
loadContent: async () => {
const contents: string[] = [
// Configure `@angular/ssr` manifest.
@@ -348,16 +347,22 @@ export function createServerMainCodeBundleOptions(
);
const contents: string[] = [
- // Re-export all symbols including default export from 'main.server.ts'
- `export { default } from '${mainServerEntryPointJsImport}';`,
- `export * from '${mainServerEntryPointJsImport}';`,
+ // Inject manifest
+ `import '${mainServerInjectManifestNamespace}';`,
// Add @angular/ssr exports
`export {
- ɵdestroyAngularServerApp,
- ɵextractRoutesAndCreateRouteTree,
- ɵgetOrCreateAngularServerApp,
- } from '@angular/ssr';`,
+ ɵdestroyAngularServerApp,
+ ɵextractRoutesAndCreateRouteTree,
+ ɵgetOrCreateAngularServerApp,
+ } from '@angular/ssr';`,
+
+ // Need for HMR
+ `export { ɵresetCompiledComponents } from '@angular/core';`,
+
+ // Re-export all symbols including default export from 'main.server.ts'
+ `export { default } from '${mainServerEntryPointJsImport}';`,
+ `export * from '${mainServerEntryPointJsImport}';`,
];
return {
@@ -392,22 +397,31 @@ export function createSsrEntryCodeBundleOptions(
return (loadResultCache) => {
const pluginOptions = createCompilerPluginOptions(options, sourceFileCache, loadResultCache);
-
const ssrEntryNamespace = 'angular:ssr-entry';
const ssrInjectManifestNamespace = 'angular:ssr-entry-inject-manifest';
- const ssrInjectRequireNamespace = 'angular:ssr-entry-inject-require';
const isNodePlatform = options.ssrOptions?.platform !== ExperimentalPlatform.Neutral;
- const inject: string[] = [ssrInjectManifestNamespace];
+ const jsBanner: string[] = [];
+ if (options.externalDependencies?.length) {
+ jsBanner.push(`globalThis['ngServerMode'] = true;`);
+ }
+
if (isNodePlatform) {
- inject.unshift(ssrInjectRequireNamespace);
+ // Note: Needed as esbuild does not provide require shims / proxy from ESModules.
+ // See: https://github.com/evanw/esbuild/issues/1921.
+ jsBanner.push(
+ `import { createRequire } from 'node:module';`,
+ `globalThis['require'] ??= createRequire(import.meta.url);`,
+ );
}
const buildOptions: BuildOptions = {
...getEsBuildServerCommonOptions(options),
target,
+ banner: {
+ js: jsBanner.join('\n'),
+ },
entryPoints: {
- // TODO: consider renaming to index
'server': ssrEntryNamespace,
},
supported: getFeatureSupport(target, true),
@@ -420,7 +434,6 @@ export function createSsrEntryCodeBundleOptions(
stylesheetBundler,
),
],
- inject,
};
buildOptions.plugins ??= [];
@@ -443,27 +456,10 @@ export function createSsrEntryCodeBundleOptions(
buildOptions.plugins.push(
createServerBundleMetadata({ ssrEntryBundle: true }),
- createVirtualModulePlugin({
- namespace: ssrInjectRequireNamespace,
- cache: loadResultCache,
- loadContent: () => {
- const contents: string[] = [
- // Note: Needed as esbuild does not provide require shims / proxy from ESModules.
- // See: https://github.com/evanw/esbuild/issues/1921.
- `import { createRequire } from 'node:module';`,
- `globalThis['require'] ??= createRequire(import.meta.url);`,
- ];
-
- return {
- contents: contents.join('\n'),
- loader: 'js',
- resolveDir: workspaceRoot,
- };
- },
- }),
createVirtualModulePlugin({
namespace: ssrInjectManifestNamespace,
cache: loadResultCache,
+ entryPointOnly: false,
loadContent: () => {
const contents: string[] = [
// Configure `@angular/ssr` app engine manifest.
@@ -488,6 +484,9 @@ export function createSsrEntryCodeBundleOptions(
serverEntryPoint,
);
const contents: string[] = [
+ // Configure `@angular/ssr` app engine manifest.
+ `import '${ssrInjectManifestNamespace}';`,
+
// Re-export all symbols including default export
`import * as server from '${serverEntryPointJsImport}';`,
`export * from '${serverEntryPointJsImport}';`,
diff --git a/packages/angular/build/src/tools/esbuild/compiler-plugin-options.ts b/packages/angular/build/src/tools/esbuild/compiler-plugin-options.ts
index 355bbad228ff..93a92fa7e9a1 100644
--- a/packages/angular/build/src/tools/esbuild/compiler-plugin-options.ts
+++ b/packages/angular/build/src/tools/esbuild/compiler-plugin-options.ts
@@ -31,6 +31,7 @@ export function createCompilerPluginOptions(
const incremental = !!options.watch;
return {
+ browserOnlyBuild: !options.serverEntryPoint,
sourcemap: !!sourcemapOptions.scripts && (sourcemapOptions.hidden ? 'external' : true),
thirdPartySourcemaps: sourcemapOptions.vendor,
tsconfig,
diff --git a/packages/angular/build/src/tools/esbuild/global-scripts.ts b/packages/angular/build/src/tools/esbuild/global-scripts.ts
index e69812d2fd30..1fb11a98a8eb 100644
--- a/packages/angular/build/src/tools/esbuild/global-scripts.ts
+++ b/packages/angular/build/src/tools/esbuild/global-scripts.ts
@@ -36,6 +36,7 @@ export function createGlobalScriptsBundleOptions(
sourcemapOptions,
jsonLogs,
workspaceRoot,
+ define,
} = options;
const namespace = 'angular:script/global';
@@ -73,6 +74,7 @@ export function createGlobalScriptsBundleOptions(
platform: 'neutral',
target,
preserveSymlinks,
+ define,
plugins: [
createSourcemapIgnorelistPlugin(),
createVirtualModulePlugin({
diff --git a/packages/angular/build/src/tools/esbuild/javascript-transformer.ts b/packages/angular/build/src/tools/esbuild/javascript-transformer.ts
index 202d922f40ea..8e2d8e31ab8f 100644
--- a/packages/angular/build/src/tools/esbuild/javascript-transformer.ts
+++ b/packages/angular/build/src/tools/esbuild/javascript-transformer.ts
@@ -9,7 +9,7 @@
import { createHash } from 'node:crypto';
import { readFile } from 'node:fs/promises';
import { IMPORT_EXEC_ARGV } from '../../utils/server-rendering/esm-in-memory-loader/utils';
-import { WorkerPool } from '../../utils/worker-pool';
+import { WorkerPool, WorkerPoolOptions } from '../../utils/worker-pool';
import { Cache } from './cache';
/**
@@ -56,12 +56,18 @@ export class JavaScriptTransformer {
}
#ensureWorkerPool(): WorkerPool {
- this.#workerPool ??= new WorkerPool({
+ const workerPoolOptions: WorkerPoolOptions = {
filename: require.resolve('./javascript-transformer-worker'),
maxThreads: this.maxThreads,
- // Prevent passing `--import` (loader-hooks) from parent to child worker.
- execArgv: process.execArgv.filter((v) => v !== IMPORT_EXEC_ARGV),
- });
+ };
+
+ // Prevent passing SSR `--import` (loader-hooks) from parent to child worker.
+ const filteredExecArgv = process.execArgv.filter((v) => v !== IMPORT_EXEC_ARGV);
+ if (process.execArgv.length !== filteredExecArgv.length) {
+ workerPoolOptions.execArgv = filteredExecArgv;
+ }
+
+ this.#workerPool ??= new WorkerPool(workerPoolOptions);
return this.#workerPool;
}
diff --git a/packages/angular/build/src/tools/esbuild/wasm-plugin.ts b/packages/angular/build/src/tools/esbuild/wasm-plugin.ts
index 3e8ad8d1063a..1bc18dd747b2 100644
--- a/packages/angular/build/src/tools/esbuild/wasm-plugin.ts
+++ b/packages/angular/build/src/tools/esbuild/wasm-plugin.ts
@@ -231,7 +231,7 @@ function generateInitHelper(streaming: boolean, wasmContents: Uint8Array) {
let resultContents;
if (streaming) {
const fetchOptions = {
- integrity: 'sha256-' + createHash('sha-256').update(wasmContents).digest('base64'),
+ integrity: 'sha256-' + createHash('sha256').update(wasmContents).digest('base64'),
};
const fetchContents = `fetch(new URL(wasmPath, import.meta.url), ${JSON.stringify(fetchOptions)})`;
resultContents = `await WebAssembly.instantiateStreaming(${fetchContents}, imports)`;
diff --git a/packages/angular/build/src/tools/vite/plugins/angular-memory-plugin.ts b/packages/angular/build/src/tools/vite/plugins/angular-memory-plugin.ts
index 0af30710f2a9..201ce171b3ea 100644
--- a/packages/angular/build/src/tools/vite/plugins/angular-memory-plugin.ts
+++ b/packages/angular/build/src/tools/vite/plugins/angular-memory-plugin.ts
@@ -8,7 +8,7 @@
import assert from 'node:assert';
import { readFile } from 'node:fs/promises';
-import { dirname, join, relative } from 'node:path';
+import { basename, dirname, join, relative } from 'node:path';
import type { Plugin } from 'vite';
import { loadEsmModule } from '../../../utils/load-esm';
import { AngularMemoryOutputFiles } from '../utils';
@@ -16,10 +16,13 @@ import { AngularMemoryOutputFiles } from '../utils';
interface AngularMemoryPluginOptions {
virtualProjectRoot: string;
outputFiles: AngularMemoryOutputFiles;
+ templateUpdates?: ReadonlyMap;
external?: string[];
skipViteClient?: boolean;
}
+const ANGULAR_PREFIX = '/@ng/';
+
export async function createAngularMemoryPlugin(
options: AngularMemoryPluginOptions,
): Promise {
@@ -30,7 +33,12 @@ export async function createAngularMemoryPlugin(
name: 'vite:angular-memory',
// Ensures plugin hooks run before built-in Vite hooks
enforce: 'pre',
- async resolveId(source, importer) {
+ async resolveId(source, importer, { ssr }) {
+ // For SSR with component HMR, pass through as a virtual module
+ if (ssr && source.startsWith(ANGULAR_PREFIX)) {
+ return '\0' + source;
+ }
+
// Prevent vite from resolving an explicit external dependency (`externalDependencies` option)
if (external?.includes(source)) {
// This is still not ideal since Vite will still transform the import specifier to
@@ -43,6 +51,18 @@ export async function createAngularMemoryPlugin(
// Remove query if present
const [importerFile] = importer.split('?', 1);
source = '/' + join(dirname(relative(virtualProjectRoot, importerFile)), source);
+ } else if (
+ !ssr &&
+ source[0] === '/' &&
+ importer.endsWith('index.html') &&
+ normalizePath(importer).startsWith(virtualProjectRoot)
+ ) {
+ // This is only needed when using SSR and `angularSsrMiddleware` (old style) to correctly resolve
+ // .js files when using lazy-loading.
+ // Remove query if present
+ const [importerFile] = importer.split('?', 1);
+ source =
+ '/' + join(dirname(relative(virtualProjectRoot, importerFile)), basename(source));
}
}
@@ -51,7 +71,16 @@ export async function createAngularMemoryPlugin(
return join(virtualProjectRoot, source);
}
},
- load(id) {
+ load(id, loadOptions) {
+ // For SSR component updates, return the component update module or empty if none
+ if (loadOptions?.ssr && id.startsWith(`\0${ANGULAR_PREFIX}`)) {
+ // Extract component identifier (first character is rollup virtual module null)
+ const requestUrl = new URL(id.slice(1), 'http://localhost');
+ const componentId = requestUrl.searchParams.get('c');
+
+ return (componentId && options.templateUpdates?.get(encodeURIComponent(componentId))) ?? '';
+ }
+
const [file] = id.split('?', 1);
const relativeFile = '/' + normalizePath(relative(virtualProjectRoot, file));
const codeContents = outputFiles.get(relativeFile)?.contents;
diff --git a/packages/angular/build/src/tools/vite/plugins/ssr-transform-plugin.ts b/packages/angular/build/src/tools/vite/plugins/ssr-transform-plugin.ts
index 0b164dee5b46..b3af4824eb54 100644
--- a/packages/angular/build/src/tools/vite/plugins/ssr-transform-plugin.ts
+++ b/packages/angular/build/src/tools/vite/plugins/ssr-transform-plugin.ts
@@ -7,6 +7,7 @@
*/
import remapping, { SourceMapInput } from '@ampproject/remapping';
+import type { SourceDescription } from 'rollup';
import type { Plugin } from 'vite';
import { loadEsmModule } from '../../../utils/load-esm';
@@ -15,28 +16,19 @@ export async function createAngularSsrTransformPlugin(workspaceRoot: string): Pr
return {
name: 'vite:angular-ssr-transform',
- enforce: 'pre',
- async configureServer(server) {
- const originalssrTransform = server.ssrTransform;
+ enforce: 'post',
+ transform(code, _id, { ssr, inMap }: { ssr?: boolean; inMap?: SourceMapInput } = {}) {
+ if (!ssr || !inMap) {
+ return null;
+ }
- server.ssrTransform = async (code, map, url, originalCode) => {
- const result = await originalssrTransform(code, null, url, originalCode);
- if (!result || !result.map || !map) {
- return result;
- }
+ const remappedMap = remapping([inMap], () => null);
+ // Set the sourcemap root to the workspace root. This is needed since we set a virtual path as root.
+ remappedMap.sourceRoot = normalizePath(workspaceRoot) + '/';
- const remappedMap = remapping(
- [result.map as SourceMapInput, map as SourceMapInput],
- () => null,
- );
-
- // Set the sourcemap root to the workspace root. This is needed since we set a virtual path as root.
- remappedMap.sourceRoot = normalizePath(workspaceRoot) + '/';
-
- return {
- ...result,
- map: remappedMap as (typeof result)['map'],
- };
+ return {
+ code,
+ map: remappedMap as SourceDescription['map'],
};
},
};
diff --git a/packages/angular/build/src/tools/vite/utils.ts b/packages/angular/build/src/tools/vite/utils.ts
index ae2cd59ec693..83085d910f60 100644
--- a/packages/angular/build/src/tools/vite/utils.ts
+++ b/packages/angular/build/src/tools/vite/utils.ts
@@ -7,7 +7,11 @@
*/
import { lookup as lookupMimeType } from 'mrmime';
+import { isBuiltin } from 'node:module';
import { extname } from 'node:path';
+import type { DepOptimizationConfig } from 'vite';
+import { JavaScriptTransformer } from '../esbuild/javascript-transformer';
+import { getFeatureSupport } from '../esbuild/utils';
export type AngularMemoryOutputFiles = Map<
string,
@@ -33,3 +37,71 @@ export function lookupMimeTypeFromRequest(url: string): string | undefined {
return extension && lookupMimeType(extension);
}
+
+type ViteEsBuildPlugin = NonNullable<
+ NonNullable['plugins']
+>[0];
+
+export type EsbuildLoaderOption = Exclude<
+ DepOptimizationConfig['esbuildOptions'],
+ undefined
+>['loader'];
+
+export function getDepOptimizationConfig({
+ disabled,
+ exclude,
+ include,
+ target,
+ zoneless,
+ prebundleTransformer,
+ ssr,
+ loader,
+ thirdPartySourcemaps,
+}: {
+ disabled: boolean;
+ exclude: string[];
+ include: string[];
+ target: string[];
+ prebundleTransformer: JavaScriptTransformer;
+ ssr: boolean;
+ zoneless: boolean;
+ loader?: EsbuildLoaderOption;
+ thirdPartySourcemaps: boolean;
+}): DepOptimizationConfig {
+ const plugins: ViteEsBuildPlugin[] = [
+ {
+ name: `angular-vite-optimize-deps${ssr ? '-ssr' : ''}${
+ thirdPartySourcemaps ? '-vendor-sourcemap' : ''
+ }`,
+ setup(build) {
+ build.onLoad({ filter: /\.[cm]?js$/ }, async (args) => {
+ return {
+ contents: await prebundleTransformer.transformFile(args.path),
+ loader: 'js',
+ };
+ });
+ },
+ },
+ ];
+
+ return {
+ // Exclude any explicitly defined dependencies (currently build defined externals)
+ exclude,
+ // NB: to disable the deps optimizer, set optimizeDeps.noDiscovery to true and optimizeDeps.include as undefined.
+ // Include all implict dependencies from the external packages internal option
+ include: disabled ? undefined : include,
+ noDiscovery: disabled,
+ // Add an esbuild plugin to run the Angular linker on dependencies
+ esbuildOptions: {
+ // Set esbuild supported targets.
+ target,
+ supported: getFeatureSupport(target, zoneless),
+ plugins,
+ loader,
+ define: {
+ 'ngServerMode': `${ssr}`,
+ },
+ resolveExtensions: ['.mjs', '.js', '.cjs'],
+ },
+ };
+}
diff --git a/packages/angular/build/src/utils/environment-options.ts b/packages/angular/build/src/utils/environment-options.ts
index 40de16a535e3..63abd82af46e 100644
--- a/packages/angular/build/src/utils/environment-options.ts
+++ b/packages/angular/build/src/utils/environment-options.ts
@@ -107,7 +107,7 @@ export const useComponentStyleHmr =
const hmrComponentTemplateVariable = process.env['NG_HMR_TEMPLATES'];
export const useComponentTemplateHmr =
- isPresent(hmrComponentTemplateVariable) && isEnabled(hmrComponentTemplateVariable);
+ !isPresent(hmrComponentTemplateVariable) || !isDisabled(hmrComponentTemplateVariable);
const partialSsrBuildVariable = process.env['NG_BUILD_PARTIAL_SSR'];
export const usePartialSsrBuild =
diff --git a/packages/angular/build/src/utils/i18n-options.ts b/packages/angular/build/src/utils/i18n-options.ts
index 3f63e9a68099..2482729e7813 100644
--- a/packages/angular/build/src/utils/i18n-options.ts
+++ b/packages/angular/build/src/utils/i18n-options.ts
@@ -18,6 +18,7 @@ export interface LocaleDescription {
translation?: Record;
dataPath?: string;
baseHref?: string;
+ subPath: string;
}
export interface I18nOptions {
@@ -54,19 +55,31 @@ function normalizeTranslationFileOption(
function ensureObject(value: unknown, name: string): asserts value is Record {
if (!value || typeof value !== 'object' || Array.isArray(value)) {
- throw new Error(`Project ${name} field is malformed. Expected an object.`);
+ throw new Error(`Project field '${name}' is malformed. Expected an object.`);
}
}
function ensureString(value: unknown, name: string): asserts value is string {
if (typeof value !== 'string') {
- throw new Error(`Project ${name} field is malformed. Expected a string.`);
+ throw new Error(`Project field '${name}' is malformed. Expected a string.`);
}
}
+function ensureValidsubPath(value: unknown, name: string): asserts value is string {
+ ensureString(value, name);
+
+ if (!/^[\w-]*$/.test(value)) {
+ throw new Error(
+ `Project field '${name}' is invalid. It can only contain letters, numbers, hyphens, and underscores.`,
+ );
+ }
+}
export function createI18nOptions(
projectMetadata: { i18n?: unknown },
inline?: boolean | string[],
+ logger?: {
+ warn(message: string): void;
+ },
): I18nOptions {
const { i18n: metadata = {} } = projectMetadata;
@@ -82,22 +95,41 @@ export function createI18nOptions(
},
};
- let rawSourceLocale;
- let rawSourceLocaleBaseHref;
+ let rawSourceLocale: string | undefined;
+ let rawSourceLocaleBaseHref: string | undefined;
+ let rawsubPath: string | undefined;
if (typeof metadata.sourceLocale === 'string') {
rawSourceLocale = metadata.sourceLocale;
} else if (metadata.sourceLocale !== undefined) {
- ensureObject(metadata.sourceLocale, 'i18n sourceLocale');
+ ensureObject(metadata.sourceLocale, 'i18n.sourceLocale');
if (metadata.sourceLocale.code !== undefined) {
- ensureString(metadata.sourceLocale.code, 'i18n sourceLocale code');
+ ensureString(metadata.sourceLocale.code, 'i18n.sourceLocale.code');
rawSourceLocale = metadata.sourceLocale.code;
}
if (metadata.sourceLocale.baseHref !== undefined) {
- ensureString(metadata.sourceLocale.baseHref, 'i18n sourceLocale baseHref');
+ ensureString(metadata.sourceLocale.baseHref, 'i18n.sourceLocale.baseHref');
+ logger?.warn(
+ `The 'baseHref' field under 'i18n.sourceLocale' is deprecated and will be removed in future versions. ` +
+ `Please use 'subPath' instead.\nNote: 'subPath' defines the URL segment for the locale, acting ` +
+ `as both the HTML base HREF and the directory name for output.\nBy default, ` +
+ `if not specified, 'subPath' uses the locale code.`,
+ );
+
rawSourceLocaleBaseHref = metadata.sourceLocale.baseHref;
}
+
+ if (metadata.sourceLocale.subPath !== undefined) {
+ ensureValidsubPath(metadata.sourceLocale.subPath, 'i18n.sourceLocale.subPath');
+ rawsubPath = metadata.sourceLocale.subPath;
+ }
+
+ if (rawsubPath !== undefined && rawSourceLocaleBaseHref !== undefined) {
+ throw new Error(
+ `'i18n.sourceLocale.subPath' and 'i18n.sourceLocale.baseHref' cannot be used together.`,
+ );
+ }
}
if (rawSourceLocale !== undefined) {
@@ -108,21 +140,41 @@ export function createI18nOptions(
i18n.locales[i18n.sourceLocale] = {
files: [],
baseHref: rawSourceLocaleBaseHref,
+ subPath: rawsubPath ?? i18n.sourceLocale,
};
if (metadata.locales !== undefined) {
ensureObject(metadata.locales, 'i18n locales');
for (const [locale, options] of Object.entries(metadata.locales)) {
- let translationFiles;
- let baseHref;
+ let translationFiles: string[] | undefined;
+ let baseHref: string | undefined;
+ let subPath: string | undefined;
+
if (options && typeof options === 'object' && 'translation' in options) {
translationFiles = normalizeTranslationFileOption(options.translation, locale, false);
if ('baseHref' in options) {
- ensureString(options.baseHref, `i18n locales ${locale} baseHref`);
+ ensureString(options.baseHref, `i18n.locales.${locale}.baseHref`);
+ logger?.warn(
+ `The 'baseHref' field under 'i18n.locales.${locale}' is deprecated and will be removed in future versions. ` +
+ `Please use 'subPath' instead.\nNote: 'subPath' defines the URL segment for the locale, acting ` +
+ `as both the HTML base HREF and the directory name for output.\nBy default, ` +
+ `if not specified, 'subPath' uses the locale code.`,
+ );
baseHref = options.baseHref;
}
+
+ if ('subPath' in options) {
+ ensureString(options.subPath, `i18n.locales.${locale}.subPath`);
+ subPath = options.subPath;
+ }
+
+ if (subPath !== undefined && baseHref !== undefined) {
+ throw new Error(
+ `'i18n.locales.${locale}.subPath' and 'i18n.locales.${locale}.baseHref' cannot be used together.`,
+ );
+ }
} else {
translationFiles = normalizeTranslationFileOption(options, locale, true);
}
@@ -136,10 +188,27 @@ export function createI18nOptions(
i18n.locales[locale] = {
files: translationFiles.map((file) => ({ path: file })),
baseHref,
+ subPath: subPath ?? locale,
};
}
}
+ // Check that subPaths are unique.
+ const localesData = Object.entries(i18n.locales);
+ for (let i = 0; i < localesData.length; i++) {
+ const [localeA, { subPath: subPathA }] = localesData[i];
+
+ for (let j = i + 1; j < localesData.length; j++) {
+ const [localeB, { subPath: subPathB }] = localesData[j];
+
+ if (subPathA === subPathB) {
+ throw new Error(
+ `Invalid i18n configuration: Locales '${localeA}' and '${localeB}' cannot have the same subPath: '${subPathB}'.`,
+ );
+ }
+ }
+ }
+
if (inline === true) {
i18n.inlineLocales.add(i18n.sourceLocale);
Object.keys(i18n.locales).forEach((locale) => i18n.inlineLocales.add(locale));
diff --git a/packages/angular/build/src/utils/index-file/augment-index-html.ts b/packages/angular/build/src/utils/index-file/augment-index-html.ts
index 30d5f30d2b6e..7a30770fa450 100644
--- a/packages/angular/build/src/utils/index-file/augment-index-html.ts
+++ b/packages/angular/build/src/utils/index-file/augment-index-html.ts
@@ -65,17 +65,8 @@ export interface FileInfo {
export async function augmentIndexHtml(
params: AugmentIndexHtmlOptions,
): Promise<{ content: string; warnings: string[]; errors: string[] }> {
- const {
- loadOutputFile,
- files,
- entrypoints,
- sri,
- deployUrl = '',
- lang,
- baseHref,
- html,
- imageDomains,
- } = params;
+ const { loadOutputFile, files, entrypoints, sri, deployUrl, lang, baseHref, html, imageDomains } =
+ params;
const warnings: string[] = [];
const errors: string[] = [];
@@ -117,7 +108,7 @@ export async function augmentIndexHtml(
let scriptTags: string[] = [];
for (const [src, isModule] of scripts) {
- const attrs = [`src="${deployUrl}${src}"`];
+ const attrs = [`src="${generateUrl(src, deployUrl)}"`];
// This is also need for non entry-points as they may contain problematic code.
if (isModule) {
@@ -141,7 +132,7 @@ export async function augmentIndexHtml(
let headerLinkTags: string[] = [];
let bodyLinkTags: string[] = [];
for (const src of stylesheets) {
- const attrs = [`rel="stylesheet"`, `href="${deployUrl}${src}"`];
+ const attrs = [`rel="stylesheet"`, `href="${generateUrl(src, deployUrl)}"`];
if (crossOrigin !== 'none') {
attrs.push(`crossorigin="${crossOrigin}"`);
@@ -157,7 +148,7 @@ export async function augmentIndexHtml(
if (params.hints?.length) {
for (const hint of params.hints) {
- const attrs = [`rel="${hint.mode}"`, `href="${deployUrl}${hint.url}"`];
+ const attrs = [`rel="${hint.mode}"`, `href="${generateUrl(hint.url, deployUrl)}"`];
if (hint.mode !== 'modulepreload' && crossOrigin !== 'none') {
// Value is considered anonymous by the browser when not present or empty
@@ -303,6 +294,19 @@ function generateSriAttributes(content: string): string {
return `integrity="${algo}-${hash}"`;
}
+function generateUrl(value: string, deployUrl: string | undefined): string {
+ if (!deployUrl) {
+ return value;
+ }
+
+ // Skip if root-relative, absolute or protocol relative url
+ if (/^((?:\w+:)?\/\/|data:|chrome:|\/)/.test(value)) {
+ return value;
+ }
+
+ return `${deployUrl}${value}`;
+}
+
function updateAttribute(
tag: { attrs: { name: string; value: string }[] },
name: string,
diff --git a/packages/angular/build/src/utils/index-file/augment-index-html_spec.ts b/packages/angular/build/src/utils/index-file/augment-index-html_spec.ts
index 61aaa0674ed8..7ea16ab6121b 100644
--- a/packages/angular/build/src/utils/index-file/augment-index-html_spec.ts
+++ b/packages/angular/build/src/utils/index-file/augment-index-html_spec.ts
@@ -398,6 +398,46 @@ describe('augment-index-html', () => {
`);
});
+ it(`should not add deploy URL to hints with an absolute URL`, async () => {
+ const { content, warnings } = await augmentIndexHtml({
+ ...indexGeneratorOptions,
+ deployUrl: 'https://localhost/',
+ hints: [{ mode: 'preload', url: 'http://example.com/y?b=2' }],
+ });
+
+ expect(warnings).toHaveSize(0);
+ expect(content).toEqual(oneLineHtml`
+
+
+
+
+
+
+
+
+ `);
+ });
+
+ it(`should not add deploy URL to hints with a root-relative URL`, async () => {
+ const { content, warnings } = await augmentIndexHtml({
+ ...indexGeneratorOptions,
+ deployUrl: 'https://example.com/',
+ hints: [{ mode: 'preload', url: '/y?b=2' }],
+ });
+
+ expect(warnings).toHaveSize(0);
+ expect(content).toEqual(oneLineHtml`
+
+
+
+
+
+
+
+
+ `);
+ });
+
it('should add `.mjs` script tags', async () => {
const { content } = await augmentIndexHtml({
...indexGeneratorOptions,
diff --git a/packages/angular/build/src/utils/index-file/auto-csp.ts b/packages/angular/build/src/utils/index-file/auto-csp.ts
index 07e183aaba36..8a5e339a9ac4 100644
--- a/packages/angular/build/src/utils/index-file/auto-csp.ts
+++ b/packages/angular/build/src/utils/index-file/auto-csp.ts
@@ -92,7 +92,7 @@ export async function autoCsp(html: string, unsafeEval = false): Promise
* loader script to the collection of hashes to add to the tag CSP.
*/
function emitLoaderScript() {
- const loaderScript = createLoaderScript(scriptContent);
+ const loaderScript = createLoaderScript(scriptContent, /* enableTrustedTypes = */ false);
hashes.push(hashTextContent(loaderScript));
rewriter.emitRaw(``);
scriptContent = [];
@@ -152,7 +152,7 @@ export async function autoCsp(html: string, unsafeEval = false): Promise
}
}
- if (tag.tagName === 'body' || tag.tagName === 'html') {
+ if (tag.tagName === 'head' || tag.tagName === 'body' || tag.tagName === 'html') {
// Write the loader script if a string of
@@ -96,13 +100,15 @@ describe('auto-csp', () => {
const csps = getCsps(result);
expect(csps.length).toBe(1);
- expect(csps[0]).toMatch(ONE_HASH_CSP);
+ expect(csps[0]).toMatch(TWO_HASH_CSP);
expect(result).toContain(
// eslint-disable-next-line max-len
- `var scripts = [['./main1.js', undefined, false, false],['./main2.js', undefined, true, false],['./main3.js', 'module', true, true]];`,
+ `var scripts = [['./main1.js', '', false, false],['./main2.js', '', true, false],['./main3.js', 'module', true, true]];`,
);
- // Only one loader script is created.
- expect(Array.from(result.matchAll(/`);
+ // Only two loader scripts are created.
+ expect(Array.from(result.matchAll(/
+
+
+ Some text
+
+