Skip to content

Commit

Permalink
fix(angular): clean up ssr main.server.ts generation
Browse files Browse the repository at this point in the history
  • Loading branch information
leosvelperez committed May 2, 2023
1 parent 32cbab7 commit 44955aa
Show file tree
Hide file tree
Showing 13 changed files with 261 additions and 57 deletions.
9 changes: 9 additions & 0 deletions packages/angular/migrations.json
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,15 @@
"version": "16.0.0-beta.6",
"description": "Remove karma as default unitTestRunner from nxJson and project configurations",
"implementation": "./src/migrations/update-16-0-0/remove-karma-defaults"
},
"remove-render-module-platform-server-exports": {
"cli": "nx",
"version": "16.1.0-beta.0",
"requires": {
"@angular/core": ">=15.0.0"
},
"description": "Remove exported `@angular/platform-server` `renderModule` method. The `renderModule` method is now exported by the Angular CLI.",
"factory": "./src/migrations/update-16-1-0/remove-render-module-platform-server-exports"
}
},
"packageJsonUpdates": {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,17 +41,7 @@ if (document.readyState !== 'loading') {
`;

exports[`Host App Generator --ssr should generate the correct files 3`] = `
"/***************************************************************************************************
* Initialize the server environment - for example, adding DOM built-in types to the global scope.
*
* NOTE:
* This import must come before any imports (direct or transitive) that rely on DOM built-ins being
* available, such as \`@angular/elements\`.
*/
import '@angular/platform-server/init';
export { AppServerModule } from './app/app.server.module';
export { renderModule } from '@angular/platform-server';
"export { AppServerModule } from './app/app.server.module';
"
`;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,17 +41,7 @@ platformBrowserDynamic()
`;

exports[`MF Remote App Generator --ssr should generate the correct files 3`] = `
"/***************************************************************************************************
* Initialize the server environment - for example, adding DOM built-in types to the global scope.
*
* NOTE:
* This import must come before any imports (direct or transitive) that rely on DOM built-ins being
* available, such as \`@angular/elements\`.
*/
import '@angular/platform-server/init';
export { AppServerModule } from './app/app.server.module';
export { renderModule } from '@angular/platform-server';
"export { AppServerModule } from './app/app.server.module';
"
`;

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { <%= rootModuleClassName %> } from './app/<%= rootModuleFileName.slice(0, -3) %>';
18 changes: 16 additions & 2 deletions packages/angular/src/generators/setup-ssr/lib/generate-files.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,26 @@ import {
joinPathFragments,
readProjectConfiguration,
} from '@nx/devkit';
import { getInstalledAngularMajorVersion } from '../../utils/version-utils';
import type { Schema } from '../schema';

export function generateSSRFiles(tree: Tree, schema: Schema) {
const projectRoot = readProjectConfiguration(tree, schema.project).root;

const pathToFiles = joinPathFragments(__dirname, '../', 'files');
generateFiles(
tree,
joinPathFragments(__dirname, '..', 'files', 'base'),
projectRoot,
{ ...schema, tpl: '' }
);

generateFiles(tree, pathToFiles, projectRoot, { ...schema, tpl: '' });
const angularMajorVersion = getInstalledAngularMajorVersion(tree);
if (angularMajorVersion < 15) {
generateFiles(
tree,
joinPathFragments(__dirname, '..', 'files', 'v14'),
projectRoot,
{ ...schema, tpl: '' }
);
}
}
94 changes: 61 additions & 33 deletions packages/angular/src/generators/setup-ssr/setup-ssr.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,17 +30,7 @@ describe('setupSSR', () => {
expect(tree.read('apps/app1/server.ts', 'utf-8')).toMatchSnapshot();
expect(tree.read('apps/app1/src/main.server.ts', 'utf-8'))
.toMatchInlineSnapshot(`
"/***************************************************************************************************
* Initialize the server environment - for example, adding DOM built-in types to the global scope.
*
* NOTE:
* This import must come before any imports (direct or transitive) that rely on DOM built-ins being
* available, such as \`@angular/elements\`.
*/
import '@angular/platform-server/init';
export { AppServerModule } from './app/app.server.module';
export { renderModule } from '@angular/platform-server';
"export { AppServerModule } from './app/app.server.module';
"
`);
expect(tree.read('apps/app1/src/main.ts', 'utf-8')).toMatchInlineSnapshot(`
Expand Down Expand Up @@ -167,30 +157,68 @@ describe('setupSSR', () => {
).toMatchSnapshot();
});

it('should install the correct versions when using older versions of Angular', async () => {
// ARRANGE
const tree = createTreeWithEmptyWorkspace({ layout: 'apps-libs' });
describe('compat', () => {
it('should install the correct versions when using older versions of Angular', async () => {
// ARRANGE
const tree = createTreeWithEmptyWorkspace({ layout: 'apps-libs' });

await generateTestApplication(tree, {
name: 'app1',
});

updateJson(tree, 'package.json', (json) => ({
...json,
dependencies: {
'@angular/core': '14.2.0',
},
}));
await generateTestApplication(tree, {
name: 'app1',
});

// ACT
await setupSsr(tree, { project: 'app1' });
updateJson(tree, 'package.json', (json) => ({
...json,
dependencies: {
'@angular/core': '14.2.0',
},
}));

// ACT
await setupSsr(tree, { project: 'app1' });

// ASSERT
const pkgJson = readJson(tree, 'package.json');
expect(pkgJson.dependencies['@angular/platform-server']).toEqual(
'~14.2.0'
);
expect(pkgJson.dependencies['@nguniversal/express-engine']).toEqual(
'~14.2.0'
);
expect(pkgJson.devDependencies['@nguniversal/builders']).toEqual(
'~14.2.0'
);
});

// ASSERT
const pkgJson = readJson(tree, 'package.json');
expect(pkgJson.dependencies['@angular/platform-server']).toEqual('~14.2.0');
expect(pkgJson.dependencies['@nguniversal/express-engine']).toEqual(
'~14.2.0'
);
expect(pkgJson.devDependencies['@nguniversal/builders']).toEqual('~14.2.0');
it('should create the main.server.ts file correctly for Angular v14', async () => {
// ARRANGE
const tree = createTreeWithEmptyWorkspace({ layout: 'apps-libs' });
await generateTestApplication(tree, {
name: 'app1',
});
updateJson(tree, 'package.json', (json) => ({
...json,
dependencies: { '@angular/core': '14.2.0' },
}));

// ACT
await setupSsr(tree, { project: 'app1' });

// ASSERT
expect(tree.read('apps/app1/src/main.server.ts', 'utf-8'))
.toMatchInlineSnapshot(`
"/***************************************************************************************************
* Initialize the server environment - for example, adding DOM built-in types to the global scope.
*
* NOTE:
* This import must come before any imports (direct or transitive) that rely on DOM built-ins being
* available, such as \`@angular/elements\`.
*/
import '@angular/platform-server/init';
export { AppServerModule } from './app/app.server.module';
export { renderModule } from '@angular/platform-server';
"
`);
});
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
import type { Tree } from '@nx/devkit';
import { createTreeWithEmptyWorkspace } from '@nx/devkit/testing-pre16';
import migration from './remove-render-module-platform-server-exports';

describe('remove-render-module-platform-server-exports migration', () => {
let tree: Tree;
const testTypeScriptFilePath = 'test.ts';

beforeEach(() => {
tree = createTreeWithEmptyWorkspace();
});

describe(`Migration to remove '@angular/platform-server' exports`, () => {
it(`should delete '@angular/platform-server' export when 'renderModule' is the only exported symbol`, async () => {
tree.write(
testTypeScriptFilePath,
`
import { Path, join } from '@angular-devkit/core';
export { renderModule } from '@angular/platform-server';
`
);

await migration(tree);

const content = tree.read(testTypeScriptFilePath, 'utf-8');
expect(content).not.toContain('@angular/platform-server');
expect(content).toContain(
`import { Path, join } from '@angular-devkit/core';`
);
});

it(`should delete only 'renderModule' when there are additional exports`, async () => {
tree.write(
testTypeScriptFilePath,
`
import { Path, join } from '@angular-devkit/core';
export { renderModule, ServerModule } from '@angular/platform-server';
`
);

await migration(tree);

const content = tree.read(testTypeScriptFilePath, 'utf-8');

expect(content).toContain(
`import { Path, join } from '@angular-devkit/core';`
);
expect(content).toContain(
`export { ServerModule } from '@angular/platform-server';`
);
});

it(`should not delete 'renderModule' when it's exported from another module`, async () => {
tree.write(
testTypeScriptFilePath,
`
export { renderModule } from '@angular/core';
`
);

await migration(tree);

const content = tree.read(testTypeScriptFilePath, 'utf-8');
expect(content).toContain(
`export { renderModule } from '@angular/core';`
);
});

it(`should not delete 'renderModule' when it's imported from '@angular/platform-server'`, async () => {
tree.write(
testTypeScriptFilePath,
`
import { renderModule } from '@angular/platform-server';
`
);

await migration(tree);

const content = tree.read(testTypeScriptFilePath, 'utf-8');
expect(content).toContain(
`import { renderModule } from '@angular/platform-server'`
);
});
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
import type { Tree } from '@nx/devkit';
import { formatFiles, visitNotIgnoredFiles } from '@nx/devkit';
import * as ts from 'typescript';
import { FileChangeRecorder } from '../../utils/file-change-recorder';

export default async function (tree: Tree) {
visitNotIgnoredFiles(tree, '/', (path) => {
if (path.endsWith('.ts') && !path.endsWith('.d.ts')) {
const content = tree.read(path, 'utf8');
if (
content.includes('@angular/platform-server') &&
content.includes('renderModule')
) {
const source = ts.createSourceFile(
path,
content.toString().replace(/^\uFEFF/, ''),
ts.ScriptTarget.Latest,
true
);

let recorder: FileChangeRecorder | undefined;
let printer: ts.Printer | undefined;

ts.forEachChild(source, function analyze(node) {
if (
!(
ts.isExportDeclaration(node) &&
node.moduleSpecifier &&
ts.isStringLiteral(node.moduleSpecifier) &&
node.moduleSpecifier.text === '@angular/platform-server' &&
node.exportClause &&
ts.isNamedExports(node.exportClause)
)
) {
// Not a @angular/platform-server named export.
return;
}

const exportClause = node.exportClause;
const newElements: ts.ExportSpecifier[] = [];
for (const element of exportClause.elements) {
if (element.name.text !== 'renderModule') {
newElements.push(element);
}
}

if (newElements.length === exportClause.elements.length) {
// No changes
return;
}

recorder ??= new FileChangeRecorder(tree, path);

if (newElements.length) {
// Update named exports as there are leftovers.
const newExportClause = ts.factory.updateNamedExports(
exportClause,
newElements
);
printer ??= ts.createPrinter();
const fix = printer.printNode(
ts.EmitHint.Unspecified,
newExportClause,
source
);

const index = exportClause.getStart();
const length = exportClause.getWidth();
recorder.remove(index, index + length);
recorder.insertLeft(index, fix);
} else {
// Delete export as no exports remain.
recorder.remove(node.getStart(), node.getStart() + node.getWidth());
}

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

if (recorder) {
recorder.applyChanges();
}
}
}
});

await formatFiles(tree);
}

0 comments on commit 44955aa

Please sign in to comment.