mirror of
https://github.com/angular/angular
synced 2026-05-24 09:28:37 +00:00
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 #65408
This commit is contained in:
parent
42e73ff9ce
commit
c1dfd9cde6
2 changed files with 109 additions and 8 deletions
|
|
@ -749,6 +749,55 @@ describe('bootstrap options migration', () => {
|
|||
.toEqual(expected.replace(/\s+/g, ''));
|
||||
});
|
||||
});
|
||||
|
||||
it('should not migrate a SSR config that has provideZonelessChangeDetection in the base config', async () => {
|
||||
return runTsurgeMigration(new BootstrapOptionsMigration(), [
|
||||
...typeFiles,
|
||||
{
|
||||
name: absoluteFrom('/app/app.config.ts'),
|
||||
contents: `
|
||||
import { provideZonelessChangeDetection } from '@angular/core';
|
||||
export const appConfig = {
|
||||
providers: [provideZonelessChangeDetection()],
|
||||
};
|
||||
`,
|
||||
},
|
||||
{
|
||||
name: absoluteFrom('/app/app.config.server.ts'),
|
||||
contents: `
|
||||
import { mergeApplicationConfig, ApplicationConfig } from '@angular/core';
|
||||
import { appConfig } from './app.config';
|
||||
const serverConfig: ApplicationConfig = {
|
||||
providers: []
|
||||
};
|
||||
export const appServerConfig = mergeApplicationConfig(appConfig, serverConfig);
|
||||
`,
|
||||
},
|
||||
{
|
||||
name: absoluteFrom('/main.server.ts'),
|
||||
isProgramRootFile: true,
|
||||
contents: `
|
||||
import { bootstrapApplication } from '@angular/platform-browser';
|
||||
import { AppComponent } from './app/app.component';
|
||||
import { appServerConfig } from './app/app.config.server';
|
||||
const bootstrap = () => bootstrapApplication(AppComponent, appServerConfig);
|
||||
export default bootstrap;
|
||||
`,
|
||||
},
|
||||
]).then(({fs}) => {
|
||||
const actualMainServer = fs.readFile(absoluteFrom('/main.server.ts'));
|
||||
const expectedMainServer = `
|
||||
import { bootstrapApplication } from '@angular/platform-browser';
|
||||
import { AppComponent } from './app/app.component';
|
||||
import { appServerConfig } from './app/app.config.server';
|
||||
const bootstrap = () => bootstrapApplication(AppComponent, appServerConfig);
|
||||
export default bootstrap;
|
||||
`;
|
||||
expect(actualMainServer.replace(/\s+/g, ''))
|
||||
.withContext(diffText(expectedMainServer, actualMainServer))
|
||||
.toEqual(expectedMainServer.replace(/\s+/g, ''));
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('bootstrapModule', () => {
|
||||
|
|
|
|||
|
|
@ -197,15 +197,10 @@ export class BootstrapOptionsMigration extends TsurgeFunnelMigration<
|
|||
if (ts.isObjectLiteralExpression(optionsNode)) {
|
||||
optionLiteral = optionsNode;
|
||||
addProvidersToBootstrapOption(optionProjectFile, optionLiteral, providerFn, replacements);
|
||||
} else if (this.isServerConfigZoneless(optionsNode, typeChecker)) {
|
||||
// Nothing to migrate for the SSR bootstrap
|
||||
return;
|
||||
} else if (ts.isIdentifier(optionsNode)) {
|
||||
// This case handled both `bootstrapApplication(App, appConfig)` and the server () => bootstrapApplication(App, appConfig)
|
||||
// where appConfig is the result of a `mergeApplicationConfig` call.
|
||||
|
||||
// This is tricky case to handle, in G3 we're might not be able to resolve the identifier's value
|
||||
// Our best alternative is to assume there is not CD providers set and add the ZoneChangeDetection provider
|
||||
// In the cases where it is, we'll just override the zone provider we just set by re-used inthe appConfig providers
|
||||
|
||||
// TODO: Should we insert a TODO to clean this up ?
|
||||
const text = `{...${optionsNode.getText()}, providers: [${providerFn}, ...${optionsNode.getText()}.providers]}`;
|
||||
replacements.push(
|
||||
new Replacement(
|
||||
|
|
@ -239,6 +234,63 @@ export class BootstrapOptionsMigration extends TsurgeFunnelMigration<
|
|||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* The optionsNode can be a appConfig built with mergeApplicationConfig
|
||||
* In this case we need to analyze if the base config uses provideZonelessChangeDetection
|
||||
*/
|
||||
private isServerConfigZoneless(optionsNode: ts.Expression, typeChecker: ts.TypeChecker): boolean {
|
||||
// Check if optionsNode is a result of mergeApplicationConfig
|
||||
let symbol = typeChecker.getSymbolAtLocation(optionsNode);
|
||||
if (symbol && (symbol.flags & ts.SymbolFlags.Alias) !== 0) {
|
||||
symbol = typeChecker.getAliasedSymbol(symbol);
|
||||
}
|
||||
const optionDeclaration = symbol?.getDeclarations()?.[0];
|
||||
if (!optionDeclaration) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (
|
||||
!ts.isVariableDeclaration(optionDeclaration) ||
|
||||
!optionDeclaration.initializer ||
|
||||
!ts.isCallExpression(optionDeclaration.initializer) ||
|
||||
!ts.isIdentifier(optionDeclaration.initializer.expression) ||
|
||||
optionDeclaration.initializer.expression.text !== 'mergeApplicationConfig'
|
||||
) {
|
||||
// We didn't find a mergeApplicationConfig call, this isn't a server config
|
||||
return false;
|
||||
}
|
||||
|
||||
let maybeAppConfig = optionDeclaration.initializer.arguments[0];
|
||||
if (ts.isIdentifier(maybeAppConfig)) {
|
||||
const resolved = getObjectLiteralFromIdentifier(maybeAppConfig, typeChecker);
|
||||
if (resolved) {
|
||||
maybeAppConfig = resolved;
|
||||
}
|
||||
}
|
||||
|
||||
if (maybeAppConfig && ts.isObjectLiteralExpression(maybeAppConfig)) {
|
||||
for (const prop of maybeAppConfig.properties) {
|
||||
if (
|
||||
ts.isPropertyAssignment(prop) &&
|
||||
ts.isIdentifier(prop.name) &&
|
||||
prop.name.text === 'providers' &&
|
||||
ts.isArrayLiteralExpression(prop.initializer)
|
||||
) {
|
||||
for (const el of prop.initializer.elements) {
|
||||
if (
|
||||
ts.isCallExpression(el) &&
|
||||
ts.isIdentifier(el.expression) &&
|
||||
el.expression.text === 'provideZonelessChangeDetection'
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private analyzeCreateApplication(
|
||||
node: ts.CallExpression,
|
||||
sourceFile: ts.SourceFile,
|
||||
|
|
|
|||
Loading…
Reference in a new issue