Skip to content

Commit 65e5331

Browse files
committed
fix(@schematics/angular): add missing express REQUEST and RESPONSE tokens
This commit updates the nguniversal migration to add `REQUEST` and `RESPONSE` tokens. Closes #26110
1 parent d641c94 commit 65e5331

File tree

2 files changed

+90
-17
lines changed

2 files changed

+90
-17
lines changed

packages/schematics/angular/migrations/update-17/replace-nguniversal-engines.ts

Lines changed: 61 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
* found in the LICENSE file at https://angular.io/license
77
*/
88

9+
import { dirname, normalize, relative } from '@angular-devkit/core';
910
import { DirEntry, Rule, chain } from '@angular-devkit/schematics';
1011
import { addDependency } from '../../utility';
1112
import { getPackageJsonDependency, removePackageJsonDependency } from '../../utility/dependencies';
@@ -81,17 +82,47 @@ export default function (): Rule {
8182
}
8283
}
8384

84-
// Replace server file
85+
// Replace all import specifiers in all files.
86+
let hasExpressTokens = false;
87+
const root = project.sourceRoot ?? `${project.root}/src`;
88+
const tokensFilePath = `/${root}/express.tokens.ts`;
89+
90+
for (const file of visit(tree.getDir(root))) {
91+
const [path, content] = file;
92+
let updatedContent = content;
93+
94+
// Check if file is importing tokens
95+
if (content.includes('@nguniversal/express-engine/tokens')) {
96+
hasExpressTokens ||= true;
97+
98+
let tokensFileRelativePath: string = relative(
99+
dirname(normalize(path)),
100+
normalize(tokensFilePath),
101+
);
102+
103+
if (tokensFileRelativePath.charAt(0) !== '.') {
104+
tokensFileRelativePath = './' + tokensFileRelativePath;
105+
}
106+
107+
updatedContent = updatedContent.replaceAll(
108+
'@nguniversal/express-engine/tokens',
109+
tokensFileRelativePath.slice(0, -3),
110+
);
111+
}
112+
113+
updatedContent = updatedContent.replaceAll(NGUNIVERSAL_PACKAGE_REGEXP, '@angular/ssr');
114+
tree.overwrite(path, updatedContent);
115+
}
116+
117+
// Replace server file and add tokens file if needed
85118
for (const [path, outputPath] of serverMainFiles.entries()) {
86119
tree.rename(path, path + '.bak');
87-
tree.create(path, getServerFileContents(outputPath));
88-
}
89-
}
120+
tree.create(path, getServerFileContents(outputPath, hasExpressTokens));
90121

91-
// Replace all import specifiers in all files.
92-
for (const file of visit(tree.root)) {
93-
const [path, content] = file;
94-
tree.overwrite(path, content.replaceAll(NGUNIVERSAL_PACKAGE_REGEXP, '@angular/ssr'));
122+
if (hasExpressTokens) {
123+
tree.create(tokensFilePath, TOKENS_FILE_CONTENT);
124+
}
125+
}
95126
}
96127

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

107-
function getServerFileContents(outputPath: string): string {
108-
return `
138+
const TOKENS_FILE_CONTENT = `
139+
import { InjectionToken } from '@angular/core';
140+
import { Request, Response } from 'express';
141+
142+
export const REQUEST = new InjectionToken<Request>('REQUEST');
143+
export const RESPONSE = new InjectionToken<Response>('RESPONSE');
144+
`;
145+
146+
function getServerFileContents(outputPath: string, hasExpressTokens: boolean): string {
147+
return (
148+
`
109149
import 'zone.js/node';
110150
111151
import { APP_BASE_HREF } from '@angular/common';
112152
import { CommonEngine } from '@angular/ssr';
113153
import * as express from 'express';
114154
import { existsSync } from 'node:fs';
115155
import { join } from 'node:path';
116-
import bootstrap from './src/main.server';
156+
import bootstrap from './src/main.server';` +
157+
(hasExpressTokens ? `\nimport { REQUEST, RESPONSE } from './src/express.tokens';` : '') +
158+
`
117159
118160
// The Express app is exported so that it can be used by serverless Functions.
119161
export function app(): express.Express {
@@ -145,7 +187,12 @@ export function app(): express.Express {
145187
documentFilePath: indexHtml,
146188
url: \`\${protocol}://\${headers.host}\${originalUrl}\`,
147189
publicPath: distFolder,
148-
providers: [{ provide: APP_BASE_HREF, useValue: baseUrl }],
190+
providers: [
191+
{ provide: APP_BASE_HREF, useValue: baseUrl },` +
192+
(hasExpressTokens
193+
? '\n { provide: RESPONSE, useValue: res },\n { provide: REQUEST, useValue: req }\n'
194+
: '') +
195+
`],
149196
})
150197
.then((html) => res.send(html))
151198
.catch((err) => next(err));
@@ -175,5 +222,6 @@ if (moduleFilename === __filename || moduleFilename.includes('iisnode')) {
175222
}
176223
177224
export default bootstrap;
178-
`;
225+
`
226+
);
179227
}

packages/schematics/angular/migrations/update-17/replace-nguniversal-engines_spec.ts

Lines changed: 29 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ function createWorkSpaceConfig(tree: UnitTestTree) {
1616
projects: {
1717
app: {
1818
root: '',
19-
sourceRoot: '/src',
19+
sourceRoot: 'src',
2020
projectType: ProjectType.Application,
2121
prefix: 'app',
2222
architect: {
@@ -181,20 +181,20 @@ if (moduleFilename === __filename || moduleFilename.includes('iisnode')) {
181181

182182
it(`should replace imports from '@nguniversal/common' to '@angular/ssr'`, async () => {
183183
tree.create(
184-
'file.ts',
184+
'src/file.ts',
185185
`
186186
import { CommonEngine } from '@nguniversal/common';
187187
import { Component } from '@angular/core';
188188
`,
189189
);
190190

191191
const newTree = await schematicRunner.runSchematic(schematicName, {}, tree);
192-
expect(newTree.readContent('/file.ts')).toContain(
192+
expect(newTree.readContent('src//file.ts')).toContain(
193193
`import { CommonEngine } from '@angular/ssr';`,
194194
);
195195
});
196196

197-
it(`should replace anf backup 'server.ts' file`, async () => {
197+
it(`should replace and backup 'server.ts' file`, async () => {
198198
const newTree = await schematicRunner.runSchematic(schematicName, {}, tree);
199199
expect(newTree.readContent('server.ts.bak')).toContain(
200200
`import { ngExpressEngine } from '@nguniversal/express-engine';`,
@@ -204,4 +204,29 @@ if (moduleFilename === __filename || moduleFilename.includes('iisnode')) {
204204
expect(newServerFile).toContain(`import { CommonEngine } from '@angular/ssr';`);
205205
expect(newServerFile).toContain(`const distFolder = join(process.cwd(), 'dist/browser');`);
206206
});
207+
208+
it(`should create tokens file and replace usages of '@nguniversal/express-engine/tokens'`, async () => {
209+
const filePath = 'src/tokens-usage.ts';
210+
tree.create(filePath, `import {RESPONSE} from '@nguniversal/express-engine/tokens';`);
211+
212+
const newTree = await schematicRunner.runSchematic(schematicName, {}, tree);
213+
expect(tree.readContent(filePath)).toContain(`import {RESPONSE} from './express.tokens';`);
214+
215+
const newServerFile = newTree.readContent('server.ts');
216+
expect(newServerFile).toContain(`{ provide: RESPONSE, useValue: res }`);
217+
expect(newServerFile).toContain(`import { REQUEST, RESPONSE } from './src/express.tokens';`);
218+
219+
expect(newTree.exists('src/express.tokens.ts')).toBeTrue();
220+
});
221+
222+
it(`should not create tokens file when '@nguniversal/express-engine/tokens' is not used`, async () => {
223+
const newTree = await schematicRunner.runSchematic(schematicName, {}, tree);
224+
const newServerFile = newTree.readContent('server.ts');
225+
expect(newServerFile).not.toContain(`{ provide: RESPONSE, useValue: res }`);
226+
expect(newServerFile).not.toContain(
227+
`import { REQUEST, RESPONSE } from './src/express.tokens';`,
228+
);
229+
230+
expect(newTree.exists('src/express.tokens.ts')).toBeFalse();
231+
});
207232
});

0 commit comments

Comments
 (0)