Skip to content

Commit

Permalink
fix(@schematics/angular): add missing express REQUEST and `RESPONSE…
Browse files Browse the repository at this point in the history
…` tokens

This commit updates the nguniversal migration to add `REQUEST` and `RESPONSE` tokens.

Closes #26110

(cherry picked from commit 65e5331)
  • Loading branch information
alan-agius4 committed Oct 31, 2023
1 parent 13b41ff commit eb0fc74
Show file tree
Hide file tree
Showing 2 changed files with 90 additions and 17 deletions.
Expand Up @@ -6,6 +6,7 @@
* found in the LICENSE file at https://angular.io/license
*/

import { dirname, normalize, relative } from '@angular-devkit/core';
import { DirEntry, Rule, chain } from '@angular-devkit/schematics';
import { addDependency } from '../../utility';
import { getPackageJsonDependency, removePackageJsonDependency } from '../../utility/dependencies';
Expand Down Expand Up @@ -81,17 +82,47 @@ export default function (): Rule {
}
}

// Replace server file
// Replace all import specifiers in all files.
let hasExpressTokens = false;
const root = project.sourceRoot ?? `${project.root}/src`;
const tokensFilePath = `/${root}/express.tokens.ts`;

for (const file of visit(tree.getDir(root))) {
const [path, content] = file;
let updatedContent = content;

// Check if file is importing tokens
if (content.includes('@nguniversal/express-engine/tokens')) {
hasExpressTokens ||= true;

let tokensFileRelativePath: string = relative(
dirname(normalize(path)),
normalize(tokensFilePath),
);

if (tokensFileRelativePath.charAt(0) !== '.') {
tokensFileRelativePath = './' + tokensFileRelativePath;
}

updatedContent = updatedContent.replaceAll(
'@nguniversal/express-engine/tokens',
tokensFileRelativePath.slice(0, -3),
);
}

updatedContent = updatedContent.replaceAll(NGUNIVERSAL_PACKAGE_REGEXP, '@angular/ssr');
tree.overwrite(path, updatedContent);
}

// Replace server file and add tokens file if needed
for (const [path, outputPath] of serverMainFiles.entries()) {
tree.rename(path, path + '.bak');
tree.create(path, getServerFileContents(outputPath));
}
}
tree.create(path, getServerFileContents(outputPath, hasExpressTokens));

// Replace all import specifiers in all files.
for (const file of visit(tree.root)) {
const [path, content] = file;
tree.overwrite(path, content.replaceAll(NGUNIVERSAL_PACKAGE_REGEXP, '@angular/ssr'));
if (hasExpressTokens) {
tree.create(tokensFilePath, TOKENS_FILE_CONTENT);
}
}
}

// Remove universal packages from deps.
Expand All @@ -104,16 +135,27 @@ export default function (): Rule {
};
}

function getServerFileContents(outputPath: string): string {
return `
const TOKENS_FILE_CONTENT = `
import { InjectionToken } from '@angular/core';
import { Request, Response } from 'express';
export const REQUEST = new InjectionToken<Request>('REQUEST');
export const RESPONSE = new InjectionToken<Response>('RESPONSE');
`;

function getServerFileContents(outputPath: string, hasExpressTokens: boolean): string {
return (
`
import 'zone.js/node';
import { APP_BASE_HREF } from '@angular/common';
import { CommonEngine } from '@angular/ssr';
import * as express from 'express';
import { existsSync } from 'node:fs';
import { join } from 'node:path';
import bootstrap from './src/main.server';
import bootstrap from './src/main.server';` +
(hasExpressTokens ? `\nimport { REQUEST, RESPONSE } from './src/express.tokens';` : '') +
`
// The Express app is exported so that it can be used by serverless Functions.
export function app(): express.Express {
Expand Down Expand Up @@ -145,7 +187,12 @@ export function app(): express.Express {
documentFilePath: indexHtml,
url: \`\${protocol}://\${headers.host}\${originalUrl}\`,
publicPath: distFolder,
providers: [{ provide: APP_BASE_HREF, useValue: baseUrl }],
providers: [
{ provide: APP_BASE_HREF, useValue: baseUrl },` +
(hasExpressTokens
? '\n { provide: RESPONSE, useValue: res },\n { provide: REQUEST, useValue: req }\n'
: '') +
`],
})
.then((html) => res.send(html))
.catch((err) => next(err));
Expand Down Expand Up @@ -175,5 +222,6 @@ if (moduleFilename === __filename || moduleFilename.includes('iisnode')) {
}
export default bootstrap;
`;
`
);
}
Expand Up @@ -16,7 +16,7 @@ function createWorkSpaceConfig(tree: UnitTestTree) {
projects: {
app: {
root: '',
sourceRoot: '/src',
sourceRoot: 'src',
projectType: ProjectType.Application,
prefix: 'app',
architect: {
Expand Down Expand Up @@ -181,20 +181,20 @@ if (moduleFilename === __filename || moduleFilename.includes('iisnode')) {

it(`should replace imports from '@nguniversal/common' to '@angular/ssr'`, async () => {
tree.create(
'file.ts',
'src/file.ts',
`
import { CommonEngine } from '@nguniversal/common';
import { Component } from '@angular/core';
`,
);

const newTree = await schematicRunner.runSchematic(schematicName, {}, tree);
expect(newTree.readContent('/file.ts')).toContain(
expect(newTree.readContent('src//file.ts')).toContain(
`import { CommonEngine } from '@angular/ssr';`,
);
});

it(`should replace anf backup 'server.ts' file`, async () => {
it(`should replace and backup 'server.ts' file`, async () => {
const newTree = await schematicRunner.runSchematic(schematicName, {}, tree);
expect(newTree.readContent('server.ts.bak')).toContain(
`import { ngExpressEngine } from '@nguniversal/express-engine';`,
Expand All @@ -204,4 +204,29 @@ if (moduleFilename === __filename || moduleFilename.includes('iisnode')) {
expect(newServerFile).toContain(`import { CommonEngine } from '@angular/ssr';`);
expect(newServerFile).toContain(`const distFolder = join(process.cwd(), 'dist/browser');`);
});

it(`should create tokens file and replace usages of '@nguniversal/express-engine/tokens'`, async () => {
const filePath = 'src/tokens-usage.ts';
tree.create(filePath, `import {RESPONSE} from '@nguniversal/express-engine/tokens';`);

const newTree = await schematicRunner.runSchematic(schematicName, {}, tree);
expect(tree.readContent(filePath)).toContain(`import {RESPONSE} from './express.tokens';`);

const newServerFile = newTree.readContent('server.ts');
expect(newServerFile).toContain(`{ provide: RESPONSE, useValue: res }`);
expect(newServerFile).toContain(`import { REQUEST, RESPONSE } from './src/express.tokens';`);

expect(newTree.exists('src/express.tokens.ts')).toBeTrue();
});

it(`should not create tokens file when '@nguniversal/express-engine/tokens' is not used`, async () => {
const newTree = await schematicRunner.runSchematic(schematicName, {}, tree);
const newServerFile = newTree.readContent('server.ts');
expect(newServerFile).not.toContain(`{ provide: RESPONSE, useValue: res }`);
expect(newServerFile).not.toContain(
`import { REQUEST, RESPONSE } from './src/express.tokens';`,
);

expect(newTree.exists('src/express.tokens.ts')).toBeFalse();
});
});

0 comments on commit eb0fc74

Please sign in to comment.