diff --git a/package.json b/package.json index 9ef29121758c..62a336e44e3e 100644 --- a/package.json +++ b/package.json @@ -207,7 +207,7 @@ "unenv": "^1.10.0", "verdaccio": "6.0.2", "verdaccio-auth-memory": "^10.0.0", - "vite": "5.4.11", + "vite": "6.0.0", "watchpack": "2.4.2", "webpack": "5.96.1", "webpack-dev-middleware": "7.4.2", diff --git a/packages/angular/build/package.json b/packages/angular/build/package.json index f406aa8d7699..45be5d3b49d5 100644 --- a/packages/angular/build/package.json +++ b/packages/angular/build/package.json @@ -41,7 +41,7 @@ "rollup": "4.27.4", "sass": "1.81.0", "semver": "7.6.3", - "vite": "5.4.11", + "vite": "6.0.0", "watchpack": "2.4.2" }, "optionalDependencies": { 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..39b27b2bd6c7 100644 --- a/packages/angular/build/src/builders/dev-server/vite-server.ts +++ b/packages/angular/build/src/builders/dev-server/vite-server.ts @@ -273,7 +273,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 +282,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 +307,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 +459,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; @@ -705,11 +694,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. 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/tests/legacy-cli/e2e/tests/vite/reuse-dep-optimization-cache.ts b/tests/legacy-cli/e2e/tests/vite/reuse-dep-optimization-cache.ts index f4b93ccfcd2f..9ffc9aa67c6a 100644 --- a/tests/legacy-cli/e2e/tests/vite/reuse-dep-optimization-cache.ts +++ b/tests/legacy-cli/e2e/tests/vite/reuse-dep-optimization-cache.ts @@ -17,17 +17,18 @@ export default async function () { await execAndWaitForOutputToMatch( 'ng', ['serve', '--port', `${port}`], - /Dependencies bundled/, + /bundle generation complete/, // Use CI:0 to force caching { DEBUG: 'vite:deps', CI: '0' }, ); - // Make request so that vite writes the cache. - const response = await fetch(`http://localhost:${port}/main.js`); - assert(response.ok, `Expected 'response.ok' to be 'true'.`); - // Wait for vite to write to FS and stablize. - await waitForAnyProcessOutputToMatch(/dependencies optimized/, 5000); + await Promise.all([ + waitForAnyProcessOutputToMatch(/dependencies optimized/, 5000), + fetch(`http://localhost:${port}/main.js`).then((r) => + assert(r.ok, `Expected 'response.ok' to be 'true'.`), + ), + ]); // Terminate the dev-server await killAllProcesses(); diff --git a/tests/legacy-cli/e2e/tests/vite/ssr-new-dep-optimization.ts b/tests/legacy-cli/e2e/tests/vite/ssr-new-dep-optimization.ts new file mode 100644 index 000000000000..e237ad41de80 --- /dev/null +++ b/tests/legacy-cli/e2e/tests/vite/ssr-new-dep-optimization.ts @@ -0,0 +1,49 @@ +import assert from 'node:assert'; +import { ng, waitForAnyProcessOutputToMatch } from '../../utils/process'; +import { installWorkspacePackages, uninstallPackage } from '../../utils/packages'; +import { ngServe, useSha } from '../../utils/project'; +import { getGlobalVariable } from '../../utils/env'; +import { readFile, writeFile } from '../../utils/fs'; + +export default async function () { + assert( + getGlobalVariable('argv')['esbuild'], + 'This test should not be called in the Webpack suite.', + ); + + // Enable caching to test real development workflow. + await ng('cache', 'clean'); + await ng('cache', 'on'); + + // Forcibly remove in case another test doesn't clean itself up. + await uninstallPackage('@angular/ssr'); + await ng('add', '@angular/ssr', '--server-routing', '--skip-confirmation', '--skip-install'); + await useSha(); + await installWorkspacePackages(); + + const port = await ngServe(); + await validateResponse('/', /Hello,/); + + await Promise.all([ + waitForAnyProcessOutputToMatch( + /new dependencies optimized: @angular\/platform-browser\/animations\/async/, + 6000, + ), + writeFile( + 'src/app/app.config.ts', + ` + import { provideAnimationsAsync } from '@angular/platform-browser/animations/async'; + ${(await readFile('src/app/app.config.ts')).replace('provideRouter(routes),', 'provideAnimationsAsync(), provideRouter(routes),')} + `, + ), + ]); + + // Verify the app still works. + await validateResponse('/', /Hello,/); + + async function validateResponse(pathname: string, match: RegExp): Promise { + const response = await fetch(new URL(pathname, `http://localhost:${port}`)); + const text = await response.text(); + assert.match(text, match); + } +} diff --git a/yarn.lock b/yarn.lock index b20c012d077d..3bfb644450b5 100644 --- a/yarn.lock +++ b/yarn.lock @@ -395,7 +395,7 @@ __metadata: rollup: "npm:4.27.4" sass: "npm:1.81.0" semver: "npm:7.6.3" - vite: "npm:5.4.11" + vite: "npm:6.0.0" watchpack: "npm:2.4.2" peerDependencies: "@angular/compiler": ^19.0.0 @@ -782,7 +782,7 @@ __metadata: unenv: "npm:^1.10.0" verdaccio: "npm:6.0.2" verdaccio-auth-memory: "npm:^10.0.0" - vite: "npm:5.4.11" + vite: "npm:6.0.0" watchpack: "npm:2.4.2" webpack: "npm:5.96.1" webpack-dev-middleware: "npm:7.4.2" @@ -15366,7 +15366,7 @@ __metadata: languageName: node linkType: hard -"postcss@npm:8.4.49, postcss@npm:^8.2.14, postcss@npm:^8.4.33, postcss@npm:^8.4.43, postcss@npm:^8.4.47": +"postcss@npm:8.4.49, postcss@npm:^8.2.14, postcss@npm:^8.4.33, postcss@npm:^8.4.43, postcss@npm:^8.4.47, postcss@npm:^8.4.49": version: 8.4.49 resolution: "postcss@npm:8.4.49" dependencies: @@ -16358,7 +16358,7 @@ __metadata: languageName: node linkType: hard -"rollup@npm:4.27.4, rollup@npm:^4.20.0, rollup@npm:^4.24.0, rollup@npm:^4.4.0": +"rollup@npm:4.27.4, rollup@npm:^4.20.0, rollup@npm:^4.23.0, rollup@npm:^4.24.0, rollup@npm:^4.4.0": version: 4.27.4 resolution: "rollup@npm:4.27.4" dependencies: @@ -18761,6 +18761,58 @@ __metadata: languageName: node linkType: hard +"vite@npm:6.0.0": + version: 6.0.0 + resolution: "vite@npm:6.0.0" + dependencies: + esbuild: "npm:^0.24.0" + fsevents: "npm:~2.3.3" + postcss: "npm:^8.4.49" + rollup: "npm:^4.23.0" + peerDependencies: + "@types/node": ^18.0.0 || ^20.0.0 || >=22.0.0 + jiti: ">=1.21.0" + less: "*" + lightningcss: ^1.21.0 + sass: "*" + sass-embedded: "*" + stylus: "*" + sugarss: "*" + terser: ^5.16.0 + tsx: ^4.8.1 + yaml: ^2.4.2 + dependenciesMeta: + fsevents: + optional: true + peerDependenciesMeta: + "@types/node": + optional: true + jiti: + optional: true + less: + optional: true + lightningcss: + optional: true + sass: + optional: true + sass-embedded: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + tsx: + optional: true + yaml: + optional: true + bin: + vite: bin/vite.js + checksum: 10c0/2516144161c108452691ec1379aefd8f3201da8f225e628cb1cdb7b35b92d856d990cd31f1c36c0f36517346efe181ea3c096a04bc846c5b0d1416b7b7111af8 + languageName: node + linkType: hard + "void-elements@npm:^2.0.0": version: 2.0.1 resolution: "void-elements@npm:2.0.1"