Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
168 changes: 92 additions & 76 deletions packages/angular/build/src/builders/dev-server/vite-server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,8 @@ export async function* serveWithVite(
browserOptions.templateUpdates =
serverOptions.liveReload && serverOptions.hmr && useComponentTemplateHmr;

browserOptions.incrementalResults = true;

// Setup the prebundling transformer that will be shared across Vite prebundling requests
const prebundleTransformer = new JavaScriptTransformer(
// Always enable JIT linking to support applications built with and without AOT.
Expand Down Expand Up @@ -225,10 +227,25 @@ export async function* serveWithVite(
}

assetFiles.clear();
for (const [outputPath, file] of Object.entries(result.files)) {
componentStyles.clear();
generatedFiles.clear();
for (const entry of Object.entries(result.files)) {
const [outputPath, file] = entry;
if (file.origin === 'disk') {
assetFiles.set('/' + normalizePath(outputPath), normalizePath(file.inputPath));
continue;
}

updateResultRecord(
outputPath,
file,
normalizePath,
htmlIndexPath,
generatedFiles,
componentStyles,
// The initial build will not yet have a server setup
!server,
);
}

// Invalidate SSR module graph to ensure that only new rebuild is used and not stale component updates
Expand All @@ -239,18 +256,36 @@ export async function* serveWithVite(
// Clear stale template updates on code rebuilds
templateUpdates.clear();

// Analyze result files for changes
analyzeResultFiles(
normalizePath,
htmlIndexPath,
result.files,
generatedFiles,
componentStyles,
);
break;
case ResultKind.Incremental:
assert(server, 'Builder must provide an initial full build before incremental results.');
// TODO: Implement support -- application builder currently does not use

for (const removed of result.removed) {
const filePath = '/' + normalizePath(removed.path);
generatedFiles.delete(filePath);
assetFiles.delete(filePath);
}
for (const modified of result.modified) {
updateResultRecord(
modified,
result.files[modified],
normalizePath,
htmlIndexPath,
generatedFiles,
componentStyles,
);
}
for (const added of result.added) {
updateResultRecord(
added,
result.files[added],
normalizePath,
htmlIndexPath,
generatedFiles,
componentStyles,
);
}

break;
case ResultKind.ComponentUpdate:
assert(serverOptions.hmr, 'Component updates are only supported with HMR enabled.');
Expand Down Expand Up @@ -444,12 +479,13 @@ async function handleUpdate(
let destroyAngularServerAppCalled = false;

// Invalidate any updated files
for (const [file, { updated, type }] of generatedFiles) {
if (!updated) {
for (const [file, record] of generatedFiles) {
if (!record.updated) {
continue;
}
record.updated = false;

if (type === BuildOutputFileType.ServerApplication && !destroyAngularServerAppCalled) {
if (record.type === BuildOutputFileType.ServerApplication && !destroyAngularServerAppCalled) {
// Clear the server app cache
// This must be done before module invalidation.
const { ɵdestroyAngularServerApp } = (await server.ssrLoadModule('/main.server.mjs')) as {
Expand Down Expand Up @@ -541,85 +577,65 @@ async function handleUpdate(
}
}

function analyzeResultFiles(
function updateResultRecord(
outputPath: string,
file: ResultFile,
normalizePath: (id: string) => string,
htmlIndexPath: string,
resultFiles: Record<string, ResultFile>,
generatedFiles: Map<string, OutputFileRecord>,
componentStyles: Map<string, ComponentStyleRecord>,
) {
const seen = new Set<string>(['/index.html']);
for (const [outputPath, file] of Object.entries(resultFiles)) {
if (file.origin === 'disk') {
continue;
}
let filePath;
if (outputPath === htmlIndexPath) {
// Convert custom index output path to standard index path for dev-server usage.
// This mimics the Webpack dev-server behavior.
filePath = '/index.html';
} else {
filePath = '/' + normalizePath(outputPath);
}

seen.add(filePath);

const servable =
file.type === BuildOutputFileType.Browser || file.type === BuildOutputFileType.Media;

// Skip analysis of sourcemaps
if (filePath.endsWith('.map')) {
generatedFiles.set(filePath, {
contents: file.contents,
servable,
size: file.contents.byteLength,
hash: file.hash,
type: file.type,
updated: false,
});
initial = false,
): void {
if (file.origin === 'disk') {
return;
}

continue;
}
let filePath;
if (outputPath === htmlIndexPath) {
// Convert custom index output path to standard index path for dev-server usage.
// This mimics the Webpack dev-server behavior.
filePath = '/index.html';
} else {
filePath = '/' + normalizePath(outputPath);
}

const existingRecord = generatedFiles.get(filePath);
if (
existingRecord &&
existingRecord.size === file.contents.byteLength &&
existingRecord.hash === file.hash
) {
// Same file
existingRecord.updated = false;
continue;
}
const servable =
file.type === BuildOutputFileType.Browser || file.type === BuildOutputFileType.Media;

// New or updated file
// Skip analysis of sourcemaps
if (filePath.endsWith('.map')) {
generatedFiles.set(filePath, {
contents: file.contents,
servable,
size: file.contents.byteLength,
hash: file.hash,
updated: true,
type: file.type,
servable,
updated: false,
});

// Record any external component styles
if (filePath.endsWith('.css') && /^\/[a-f0-9]{64}\.css$/.test(filePath)) {
const componentStyle = componentStyles.get(filePath);
if (componentStyle) {
componentStyle.rawContent = file.contents;
} else {
componentStyles.set(filePath, {
rawContent: file.contents,
});
}
}
return;
}

// Clear stale output files
for (const file of generatedFiles.keys()) {
if (!seen.has(file)) {
generatedFiles.delete(file);
componentStyles.delete(file);
// New or updated file
generatedFiles.set(filePath, {
contents: file.contents,
size: file.contents.byteLength,
hash: file.hash,
// Consider the files updated except on the initial build result
updated: !initial,
type: file.type,
servable,
});

// Record any external component styles
if (filePath.endsWith('.css') && /^\/[a-f0-9]{64}\.css$/.test(filePath)) {
const componentStyle = componentStyles.get(filePath);
if (componentStyle) {
componentStyle.rawContent = file.contents;
} else {
componentStyles.set(filePath, {
rawContent: file.contents,
});
}
}
}
Expand Down
Loading