Skip to content

Commit c1dfd9c

Browse files
JeanMechekirjs
authored andcommitted
refactor(migrations): don't migration the server bootstrapApplicaiton on zoneless apps.
With the change we specifically analyse `boostrapApplication` with a config that uses `mergeApplicationConfig`. fixes angular#65408
1 parent 42e73ff commit c1dfd9c

File tree

2 files changed

+109
-8
lines changed

2 files changed

+109
-8
lines changed

packages/core/schematics/migrations/bootstrap-options-migration/migration.spec.ts

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -749,6 +749,55 @@ describe('bootstrap options migration', () => {
749749
.toEqual(expected.replace(/\s+/g, ''));
750750
});
751751
});
752+
753+
it('should not migrate a SSR config that has provideZonelessChangeDetection in the base config', async () => {
754+
return runTsurgeMigration(new BootstrapOptionsMigration(), [
755+
...typeFiles,
756+
{
757+
name: absoluteFrom('/app/app.config.ts'),
758+
contents: `
759+
import { provideZonelessChangeDetection } from '@angular/core';
760+
export const appConfig = {
761+
providers: [provideZonelessChangeDetection()],
762+
};
763+
`,
764+
},
765+
{
766+
name: absoluteFrom('/app/app.config.server.ts'),
767+
contents: `
768+
import { mergeApplicationConfig, ApplicationConfig } from '@angular/core';
769+
import { appConfig } from './app.config';
770+
const serverConfig: ApplicationConfig = {
771+
providers: []
772+
};
773+
export const appServerConfig = mergeApplicationConfig(appConfig, serverConfig);
774+
`,
775+
},
776+
{
777+
name: absoluteFrom('/main.server.ts'),
778+
isProgramRootFile: true,
779+
contents: `
780+
import { bootstrapApplication } from '@angular/platform-browser';
781+
import { AppComponent } from './app/app.component';
782+
import { appServerConfig } from './app/app.config.server';
783+
const bootstrap = () => bootstrapApplication(AppComponent, appServerConfig);
784+
export default bootstrap;
785+
`,
786+
},
787+
]).then(({fs}) => {
788+
const actualMainServer = fs.readFile(absoluteFrom('/main.server.ts'));
789+
const expectedMainServer = `
790+
import { bootstrapApplication } from '@angular/platform-browser';
791+
import { AppComponent } from './app/app.component';
792+
import { appServerConfig } from './app/app.config.server';
793+
const bootstrap = () => bootstrapApplication(AppComponent, appServerConfig);
794+
export default bootstrap;
795+
`;
796+
expect(actualMainServer.replace(/\s+/g, ''))
797+
.withContext(diffText(expectedMainServer, actualMainServer))
798+
.toEqual(expectedMainServer.replace(/\s+/g, ''));
799+
});
800+
});
752801
});
753802

754803
describe('bootstrapModule', () => {

packages/core/schematics/migrations/bootstrap-options-migration/migration.ts

Lines changed: 60 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -197,15 +197,10 @@ export class BootstrapOptionsMigration extends TsurgeFunnelMigration<
197197
if (ts.isObjectLiteralExpression(optionsNode)) {
198198
optionLiteral = optionsNode;
199199
addProvidersToBootstrapOption(optionProjectFile, optionLiteral, providerFn, replacements);
200+
} else if (this.isServerConfigZoneless(optionsNode, typeChecker)) {
201+
// Nothing to migrate for the SSR bootstrap
202+
return;
200203
} else if (ts.isIdentifier(optionsNode)) {
201-
// This case handled both `bootstrapApplication(App, appConfig)` and the server () => bootstrapApplication(App, appConfig)
202-
// where appConfig is the result of a `mergeApplicationConfig` call.
203-
204-
// This is tricky case to handle, in G3 we're might not be able to resolve the identifier's value
205-
// Our best alternative is to assume there is not CD providers set and add the ZoneChangeDetection provider
206-
// In the cases where it is, we'll just override the zone provider we just set by re-used inthe appConfig providers
207-
208-
// TODO: Should we insert a TODO to clean this up ?
209204
const text = `{...${optionsNode.getText()}, providers: [${providerFn}, ...${optionsNode.getText()}.providers]}`;
210205
replacements.push(
211206
new Replacement(
@@ -239,6 +234,63 @@ export class BootstrapOptionsMigration extends TsurgeFunnelMigration<
239234
});
240235
}
241236

237+
/**
238+
* The optionsNode can be a appConfig built with mergeApplicationConfig
239+
* In this case we need to analyze if the base config uses provideZonelessChangeDetection
240+
*/
241+
private isServerConfigZoneless(optionsNode: ts.Expression, typeChecker: ts.TypeChecker): boolean {
242+
// Check if optionsNode is a result of mergeApplicationConfig
243+
let symbol = typeChecker.getSymbolAtLocation(optionsNode);
244+
if (symbol && (symbol.flags & ts.SymbolFlags.Alias) !== 0) {
245+
symbol = typeChecker.getAliasedSymbol(symbol);
246+
}
247+
const optionDeclaration = symbol?.getDeclarations()?.[0];
248+
if (!optionDeclaration) {
249+
return false;
250+
}
251+
252+
if (
253+
!ts.isVariableDeclaration(optionDeclaration) ||
254+
!optionDeclaration.initializer ||
255+
!ts.isCallExpression(optionDeclaration.initializer) ||
256+
!ts.isIdentifier(optionDeclaration.initializer.expression) ||
257+
optionDeclaration.initializer.expression.text !== 'mergeApplicationConfig'
258+
) {
259+
// We didn't find a mergeApplicationConfig call, this isn't a server config
260+
return false;
261+
}
262+
263+
let maybeAppConfig = optionDeclaration.initializer.arguments[0];
264+
if (ts.isIdentifier(maybeAppConfig)) {
265+
const resolved = getObjectLiteralFromIdentifier(maybeAppConfig, typeChecker);
266+
if (resolved) {
267+
maybeAppConfig = resolved;
268+
}
269+
}
270+
271+
if (maybeAppConfig && ts.isObjectLiteralExpression(maybeAppConfig)) {
272+
for (const prop of maybeAppConfig.properties) {
273+
if (
274+
ts.isPropertyAssignment(prop) &&
275+
ts.isIdentifier(prop.name) &&
276+
prop.name.text === 'providers' &&
277+
ts.isArrayLiteralExpression(prop.initializer)
278+
) {
279+
for (const el of prop.initializer.elements) {
280+
if (
281+
ts.isCallExpression(el) &&
282+
ts.isIdentifier(el.expression) &&
283+
el.expression.text === 'provideZonelessChangeDetection'
284+
) {
285+
return true;
286+
}
287+
}
288+
}
289+
}
290+
}
291+
return false;
292+
}
293+
242294
private analyzeCreateApplication(
243295
node: ts.CallExpression,
244296
sourceFile: ts.SourceFile,

0 commit comments

Comments
 (0)