Skip to content

Commit

Permalink
refactor(@angular/ssr): remove code that sets `initialNavigation: ena…
Browse files Browse the repository at this point in the history
…bledBlocking`

This removes that code that was used to set `initialNavigation: enabledBlocking` as this is no longer needed to reduce flickering. This is because `initialNavigation: enabledBlocking` is not needed when enabling hydration via `provideClientHydration` which is done in the internal server schematic.
  • Loading branch information
alan-agius4 committed Sep 22, 2023
1 parent 6979eba commit fc68dca
Show file tree
Hide file tree
Showing 2 changed files with 2 additions and 222 deletions.
116 changes: 2 additions & 114 deletions packages/angular/ssr/schematics/ng-add/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,10 @@
* found in the LICENSE file at https://angular.io/license
*/

import { dirname, join, normalize, strings } from '@angular-devkit/core';
import { join, normalize, strings } from '@angular-devkit/core';
import {
Rule,
SchematicsException,
Tree,
apply,
applyTemplates,
chain,
Expand All @@ -28,16 +27,9 @@ import { targetBuildNotFoundError } from '@schematics/angular/utility/project-ta
import { getMainFilePath } from '@schematics/angular/utility/standalone/util';
import { getWorkspace } from '@schematics/angular/utility/workspace';
import { Builders } from '@schematics/angular/utility/workspace-models';
import * as ts from 'typescript';

import { latestVersions } from '../utility/latest-versions';
import {
addInitialNavigation,
findImport,
getImportOfIdentifier,
getOutputPath,
getProject,
} from '../utility/utils';
import { getOutputPath, getProject } from '../utility/utils';

import { Schema as AddServerOptions } from './schema';

Expand Down Expand Up @@ -199,109 +191,6 @@ function updateWebpackBuilderServerTsConfigRule(options: AddServerOptions): Rule
};
}

function routingInitialNavigationRule(options: ServerOptions): Rule {
return async (host) => {
const project = await getProject(host, options.project);
const serverTarget = project.targets.get('server');
if (!serverTarget || !serverTarget.options) {
return;
}

const tsConfigPath = serverTarget.options.tsConfig;
if (!tsConfigPath || typeof tsConfigPath !== 'string' || !host.exists(tsConfigPath)) {
// No tsconfig path
return;
}

const parseConfigHost: ts.ParseConfigHost = {
useCaseSensitiveFileNames: ts.sys.useCaseSensitiveFileNames,
readDirectory: ts.sys.readDirectory,
fileExists: function (fileName: string): boolean {
return host.exists(fileName);
},
readFile: function (fileName: string): string {
return host.readText(fileName);
},
};
const { config } = ts.readConfigFile(tsConfigPath, parseConfigHost.readFile);
const parsed = ts.parseJsonConfigFileContent(
config,
parseConfigHost,
dirname(normalize(tsConfigPath)),
);
const tsHost = ts.createCompilerHost(parsed.options, true);
// Strip BOM as otherwise TSC methods (Ex: getWidth) will return an offset,
// which breaks the CLI UpdateRecorder.
// See: https://github.com/angular/angular/pull/30719
tsHost.readFile = function (fileName: string): string {
return host.readText(fileName).replace(/^\uFEFF/, '');
};
tsHost.directoryExists = function (directoryName: string): boolean {
// When the path is file getDir will throw.
try {
const dir = host.getDir(directoryName);

return !!(dir.subdirs.length || dir.subfiles.length);
} catch {
return false;
}
};
tsHost.fileExists = function (fileName: string): boolean {
return host.exists(fileName);
};
tsHost.realpath = function (path: string): string {
return path;
};
tsHost.getCurrentDirectory = function () {
return host.root.path;
};

const program = ts.createProgram(parsed.fileNames, parsed.options, tsHost);
const typeChecker = program.getTypeChecker();
const sourceFiles = program
.getSourceFiles()
.filter((f) => !f.isDeclarationFile && !program.isSourceFileFromExternalLibrary(f));
const printer = ts.createPrinter();
const routerModule = 'RouterModule';
const routerSource = '@angular/router';

sourceFiles.forEach((sourceFile) => {
const routerImport = findImport(sourceFile, routerSource, routerModule);
if (!routerImport) {
return;
}

ts.forEachChild(sourceFile, function visitNode(node: ts.Node) {
if (
ts.isCallExpression(node) &&
ts.isPropertyAccessExpression(node.expression) &&
ts.isIdentifier(node.expression.expression) &&
node.expression.name.text === 'forRoot'
) {
const imp = getImportOfIdentifier(typeChecker, node.expression.expression);

if (imp && imp.name === routerModule && imp.importModule === routerSource) {
const print = printer.printNode(
ts.EmitHint.Unspecified,
addInitialNavigation(node),
sourceFile,
);

const recorder = host.beginUpdate(sourceFile.fileName);
recorder.remove(node.getStart(), node.getWidth());
recorder.insertRight(node.getStart(), print);
host.commitUpdate(recorder);

return;
}
}

ts.forEachChild(node, visitNode);
});
});
};
}

function addDependencies(): Rule {
return chain([
addDependency('express', latestVersions['express'], {
Expand Down Expand Up @@ -369,7 +258,6 @@ export default function (options: AddServerOptions): Rule {
updateWebpackBuilderServerTsConfigRule(options),
updateWebpackBuilderWorkspaceConfigRule(options),
]),
isStandalone ? noop() : routingInitialNavigationRule(options),
addServerFile(options, isStandalone),
addDependencies(),
]);
Expand Down
108 changes: 0 additions & 108 deletions packages/angular/ssr/schematics/utility/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@
import { workspaces } from '@angular-devkit/core';
import { SchematicsException, Tree } from '@angular-devkit/schematics';
import { readWorkspace } from '@schematics/angular/utility';
import * as ts from 'typescript';

export async function getProject(
host: Tree,
Expand All @@ -25,10 +24,6 @@ export async function getProject(
return project;
}

export function stripTsExtension(file: string): string {
return file.replace(/\.ts$/, '');
}

export async function getOutputPath(
host: Tree,
projectName: string,
Expand All @@ -50,106 +45,3 @@ export async function getOutputPath(

return outputPath;
}

export function findImport(
sourceFile: ts.SourceFile,
moduleName: string,
symbolName: string,
): ts.NamedImports | null {
// Only look through the top-level imports.
for (const node of sourceFile.statements) {
if (
!ts.isImportDeclaration(node) ||
!ts.isStringLiteral(node.moduleSpecifier) ||
node.moduleSpecifier.text !== moduleName
) {
continue;
}

const namedBindings = node.importClause && node.importClause.namedBindings;

if (!namedBindings || !ts.isNamedImports(namedBindings)) {
continue;
}

if (namedBindings.elements.some((element) => element.name.text === symbolName)) {
return namedBindings;
}
}

return null;
}

export type Import = {
name: string;
importModule: string;
node: ts.ImportDeclaration;
};

/** Gets import information about the specified identifier by using the Type checker. */
export function getImportOfIdentifier(
typeChecker: ts.TypeChecker,
node: ts.Identifier,
): Import | null {
const symbol = typeChecker.getSymbolAtLocation(node);

if (!symbol || !symbol.declarations?.length) {
return null;
}

const decl = symbol.declarations[0];

if (!ts.isImportSpecifier(decl)) {
return null;
}

const importDecl = decl.parent.parent.parent;

if (!ts.isStringLiteral(importDecl.moduleSpecifier)) {
return null;
}

return {
// Handles aliased imports: e.g. "import {Component as myComp} from ...";
name: decl.propertyName ? decl.propertyName.text : decl.name.text,
importModule: importDecl.moduleSpecifier.text,
node: importDecl,
};
}

export function addInitialNavigation(node: ts.CallExpression): ts.CallExpression {
const existingOptions = node.arguments[1] as ts.ObjectLiteralExpression | undefined;

// If the user has explicitly set initialNavigation, we respect that
if (
existingOptions &&
existingOptions.properties.some(
(exp) =>
ts.isPropertyAssignment(exp) &&
ts.isIdentifier(exp.name) &&
exp.name.text === 'initialNavigation',
)
) {
return node;
}

const enabledLiteral = ts.factory.createStringLiteral('enabledBlocking');
// TypeScript will emit the Node with double quotes.
// In schematics we usually write code with a single quotes
// eslint-disable-next-line @typescript-eslint/no-explicit-any
(enabledLiteral as any).singleQuote = true;

const initialNavigationProperty = ts.factory.createPropertyAssignment(
'initialNavigation',
enabledLiteral,
);
const routerOptions = existingOptions
? ts.factory.updateObjectLiteralExpression(
existingOptions,
ts.factory.createNodeArray([...existingOptions.properties, initialNavigationProperty]),
)
: ts.factory.createObjectLiteralExpression([initialNavigationProperty], true);
const args = [node.arguments[0], routerOptions];

return ts.factory.createCallExpression(node.expression, node.typeArguments, args);
}

0 comments on commit fc68dca

Please sign in to comment.