Skip to content

Commit 107851a

Browse files
alan-agius4clydin
authored andcommitted
fix(@angular-devkit/build-angular): display warning when preserveWhitespaces is set in the tsconfig provided to the server builder
This commits add a check to display a warning when `preserveWhitespaces` is configured in `tsconfig.server.json`, as potentially this could cause issue with hydration. (cherry picked from commit 688bb2d)
1 parent 6e9586d commit 107851a

File tree

2 files changed

+77
-1
lines changed

2 files changed

+77
-1
lines changed

packages/angular_devkit/build_angular/src/builders/server/index.ts

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,8 @@
88

99
import { BuilderContext, BuilderOutput, createBuilder } from '@angular-devkit/architect';
1010
import { runWebpack } from '@angular-devkit/build-webpack';
11-
import * as path from 'path';
11+
import { readFile } from 'node:fs/promises';
12+
import * as path from 'node:path';
1213
import { Observable, concatMap, from } from 'rxjs';
1314
import webpack, { Configuration } from 'webpack';
1415
import { ExecutionTransformer } from '../../transforms';
@@ -191,6 +192,8 @@ async function initialize(
191192
// Purge old build disk cache.
192193
await purgeStaleBuildCache(context);
193194

195+
await checkTsConfigForPreserveWhitespacesSetting(context, options.tsConfig);
196+
194197
const browserslist = (await import('browserslist')).default;
195198
const originalOutputPath = options.outputPath;
196199
// Assets are processed directly by the builder except when watching
@@ -223,6 +226,32 @@ async function initialize(
223226
return { config: transformedConfig, i18n, projectRoot, projectSourceRoot };
224227
}
225228

229+
async function checkTsConfigForPreserveWhitespacesSetting(
230+
context: BuilderContext,
231+
tsConfigPath: string,
232+
): Promise<void> {
233+
// We don't use the `readTsConfig` method on purpose here.
234+
// To only catch cases were `preserveWhitespaces` is set directly in the `tsconfig.server.json`,
235+
// which in the majority of cases will cause a mistmatch between client and server builds.
236+
// Technically we should check if `tsconfig.server.json` and `tsconfig.app.json` values match.
237+
238+
// But:
239+
// 1. It is not guaranteed that `tsconfig.app.json` is used to build the client side of this app.
240+
// 2. There is no easy way to access the build build config from the server builder.
241+
// 4. This will no longer be an issue with a single compilation model were the same tsconfig is used for both browser and server builds.
242+
const content = await readFile(path.join(context.workspaceRoot, tsConfigPath), 'utf-8');
243+
const { parse } = await import('jsonc-parser');
244+
const tsConfig = parse(content, [], { allowTrailingComma: true });
245+
if (tsConfig.angularCompilerOptions?.preserveWhitespaces !== undefined) {
246+
context.logger.warn(
247+
`"preserveWhitespaces" was set in "${tsConfigPath}". ` +
248+
'Make sure that this setting is set consistently in both "tsconfig.server.json" for your server side ' +
249+
'and "tsconfig.app.json" for your client side. A mismatched value will cause hydration to break.\n' +
250+
'For more information see: https://angular.io/guide/hydration#preserve-whitespaces',
251+
);
252+
}
253+
}
254+
226255
/**
227256
* Add `@angular/platform-server` exports.
228257
* This is needed so that DI tokens can be referenced and set at runtime outside of the bundle.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
/**
2+
* @license
3+
* Copyright Google LLC All Rights Reserved.
4+
*
5+
* Use of this source code is governed by an MIT-style license that can be
6+
* found in the LICENSE file at https://angular.io/license
7+
*/
8+
9+
import { logging } from '@angular-devkit/core';
10+
import { execute } from '../../index';
11+
import { BASE_OPTIONS, SERVER_BUILDER_INFO, describeBuilder } from '../setup';
12+
13+
describeBuilder(execute, SERVER_BUILDER_INFO, (harness) => {
14+
describe('Behavior: "preserveWhitespaces warning"', () => {
15+
it('should not show warning when "preserveWhitespaces" is not set.', async () => {
16+
harness.useTarget('server', {
17+
...BASE_OPTIONS,
18+
});
19+
20+
const { logs } = await harness.executeOnce();
21+
expect(logs).not.toContain(
22+
jasmine.objectContaining<logging.LogEntry>({
23+
message: jasmine.stringMatching('"preserveWhitespaces" was set in'),
24+
}),
25+
);
26+
});
27+
it('should show warning when "preserveWhitespaces" is set.', async () => {
28+
harness.useTarget('server', {
29+
...BASE_OPTIONS,
30+
});
31+
32+
await harness.modifyFile('src/tsconfig.server.json', (content) => {
33+
const tsconfig = JSON.parse(content);
34+
(tsconfig.angularCompilerOptions ??= {}).preserveWhitespaces = false;
35+
36+
return JSON.stringify(tsconfig);
37+
});
38+
39+
const { logs } = await harness.executeOnce();
40+
expect(logs).toContain(
41+
jasmine.objectContaining<logging.LogEntry>({
42+
message: jasmine.stringMatching('"preserveWhitespaces" was set in'),
43+
}),
44+
);
45+
});
46+
});
47+
});

0 commit comments

Comments
 (0)