Skip to content

Commit

Permalink
fix(@angular-devkit/build-angular): display warning when `preserveWhi…
Browse files Browse the repository at this point in the history
…tespaces` 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)
  • Loading branch information
alan-agius4 authored and clydin committed Apr 19, 2023
1 parent 6e9586d commit 107851a
Show file tree
Hide file tree
Showing 2 changed files with 77 additions and 1 deletion.
Expand Up @@ -8,7 +8,8 @@

import { BuilderContext, BuilderOutput, createBuilder } from '@angular-devkit/architect';
import { runWebpack } from '@angular-devkit/build-webpack';
import * as path from 'path';
import { readFile } from 'node:fs/promises';
import * as path from 'node:path';
import { Observable, concatMap, from } from 'rxjs';
import webpack, { Configuration } from 'webpack';
import { ExecutionTransformer } from '../../transforms';
Expand Down Expand Up @@ -191,6 +192,8 @@ async function initialize(
// Purge old build disk cache.
await purgeStaleBuildCache(context);

await checkTsConfigForPreserveWhitespacesSetting(context, options.tsConfig);

const browserslist = (await import('browserslist')).default;
const originalOutputPath = options.outputPath;
// Assets are processed directly by the builder except when watching
Expand Down Expand Up @@ -223,6 +226,32 @@ async function initialize(
return { config: transformedConfig, i18n, projectRoot, projectSourceRoot };
}

async function checkTsConfigForPreserveWhitespacesSetting(
context: BuilderContext,
tsConfigPath: string,
): Promise<void> {
// We don't use the `readTsConfig` method on purpose here.
// To only catch cases were `preserveWhitespaces` is set directly in the `tsconfig.server.json`,
// which in the majority of cases will cause a mistmatch between client and server builds.
// Technically we should check if `tsconfig.server.json` and `tsconfig.app.json` values match.

// But:
// 1. It is not guaranteed that `tsconfig.app.json` is used to build the client side of this app.
// 2. There is no easy way to access the build build config from the server builder.
// 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.
const content = await readFile(path.join(context.workspaceRoot, tsConfigPath), 'utf-8');
const { parse } = await import('jsonc-parser');
const tsConfig = parse(content, [], { allowTrailingComma: true });
if (tsConfig.angularCompilerOptions?.preserveWhitespaces !== undefined) {
context.logger.warn(
`"preserveWhitespaces" was set in "${tsConfigPath}". ` +
'Make sure that this setting is set consistently in both "tsconfig.server.json" for your server side ' +
'and "tsconfig.app.json" for your client side. A mismatched value will cause hydration to break.\n' +
'For more information see: https://angular.io/guide/hydration#preserve-whitespaces',
);
}
}

/**
* Add `@angular/platform-server` exports.
* This is needed so that DI tokens can be referenced and set at runtime outside of the bundle.
Expand Down
@@ -0,0 +1,47 @@
/**
* @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.io/license
*/

import { logging } from '@angular-devkit/core';
import { execute } from '../../index';
import { BASE_OPTIONS, SERVER_BUILDER_INFO, describeBuilder } from '../setup';

describeBuilder(execute, SERVER_BUILDER_INFO, (harness) => {
describe('Behavior: "preserveWhitespaces warning"', () => {
it('should not show warning when "preserveWhitespaces" is not set.', async () => {
harness.useTarget('server', {
...BASE_OPTIONS,
});

const { logs } = await harness.executeOnce();
expect(logs).not.toContain(
jasmine.objectContaining<logging.LogEntry>({
message: jasmine.stringMatching('"preserveWhitespaces" was set in'),
}),
);
});
it('should show warning when "preserveWhitespaces" is set.', async () => {
harness.useTarget('server', {
...BASE_OPTIONS,
});

await harness.modifyFile('src/tsconfig.server.json', (content) => {
const tsconfig = JSON.parse(content);
(tsconfig.angularCompilerOptions ??= {}).preserveWhitespaces = false;

return JSON.stringify(tsconfig);
});

const { logs } = await harness.executeOnce();
expect(logs).toContain(
jasmine.objectContaining<logging.LogEntry>({
message: jasmine.stringMatching('"preserveWhitespaces" was set in'),
}),
);
});
});
});

0 comments on commit 107851a

Please sign in to comment.