mirror of
https://github.com/angular/angular-cli.git
synced 2025-05-17 11:03:53 +08:00
In angular/angular#40559, a new `@angular/platform-server/init` entry-point was introduced, which can be used to initialize the server environment by providing shims for APIs normally provided by the browser (such as DOM globals). If the project is using a version of Angular (and thus `@angular/platform-server`) that is known to include this new entry-point, the newly generated `main.server.ts` file should import it at the top in order to ensure that the shims will be available as soon as possible (before other direct or transitive imports that may rely on them). See also angular/angular#40559 for more details. NOTE: This `universal` schematic is the base for `app-shell`, `@nguniversal/express-engine` and `@nguniversal/hapi-engine`. Fixes angular/angular#40559
275 lines
11 KiB
TypeScript
275 lines
11 KiB
TypeScript
/**
|
|
* @license
|
|
* Copyright Google Inc. All Rights Reserved.
|
|
*
|
|
* Use of this source code is governed by an MIT-style license that can be
|
|
* found in the LICENSE file at https://angular.io/license
|
|
*/
|
|
import { SchematicTestRunner, UnitTestTree } from '@angular-devkit/schematics/testing';
|
|
import { parse as parseJson } from 'jsonc-parser';
|
|
import { Schema as ApplicationOptions, Style } from '../application/schema';
|
|
import { NodeDependencyType, addPackageJsonDependency } from '../utility/dependencies';
|
|
import { Schema as WorkspaceOptions } from '../workspace/schema';
|
|
import { Schema as UniversalOptions } from './schema';
|
|
|
|
// tslint:disable-next-line:no-big-function
|
|
describe('Universal Schematic', () => {
|
|
const schematicRunner = new SchematicTestRunner(
|
|
'@schematics/angular',
|
|
require.resolve('../collection.json'),
|
|
);
|
|
const defaultOptions: UniversalOptions = {
|
|
clientProject: 'bar',
|
|
};
|
|
const workspaceUniversalOptions: UniversalOptions = {
|
|
clientProject: 'workspace',
|
|
};
|
|
|
|
const workspaceOptions: WorkspaceOptions = {
|
|
name: 'workspace',
|
|
newProjectRoot: 'projects',
|
|
version: '6.0.0',
|
|
};
|
|
|
|
const appOptions: ApplicationOptions = {
|
|
name: 'bar',
|
|
inlineStyle: false,
|
|
inlineTemplate: false,
|
|
routing: false,
|
|
style: Style.Css,
|
|
skipTests: false,
|
|
skipPackageJson: false,
|
|
};
|
|
|
|
const initialWorkspaceAppOptions: ApplicationOptions = {
|
|
name: 'workspace',
|
|
projectRoot: '',
|
|
inlineStyle: false,
|
|
inlineTemplate: false,
|
|
routing: false,
|
|
style: Style.Css,
|
|
skipTests: false,
|
|
skipPackageJson: false,
|
|
};
|
|
|
|
let appTree: UnitTestTree;
|
|
|
|
beforeEach(async () => {
|
|
appTree = await schematicRunner.runSchematicAsync('workspace', workspaceOptions).toPromise();
|
|
appTree = await schematicRunner.runSchematicAsync(
|
|
'application',
|
|
initialWorkspaceAppOptions,
|
|
appTree,
|
|
).toPromise();
|
|
appTree = await schematicRunner.runSchematicAsync('application', appOptions, appTree)
|
|
.toPromise();
|
|
});
|
|
|
|
it('should create a root module file', async () => {
|
|
const tree = await schematicRunner.runSchematicAsync('universal', defaultOptions, appTree)
|
|
.toPromise();
|
|
const filePath = '/projects/bar/src/app/app.server.module.ts';
|
|
expect(tree.exists(filePath)).toEqual(true);
|
|
});
|
|
|
|
it('should create a main file', async () => {
|
|
const tree = await schematicRunner.runSchematicAsync('universal', defaultOptions, appTree)
|
|
.toPromise();
|
|
const filePath = '/projects/bar/src/main.server.ts';
|
|
expect(tree.exists(filePath)).toEqual(true);
|
|
const contents = tree.readContent(filePath);
|
|
expect(contents).toMatch(/export { AppServerModule } from '\.\/app\/app\.server\.module'/);
|
|
});
|
|
|
|
it('should create a tsconfig file for the workspace project', async () => {
|
|
const tree = await schematicRunner
|
|
.runSchematicAsync('universal', workspaceUniversalOptions, appTree)
|
|
.toPromise();
|
|
const filePath = '/tsconfig.server.json';
|
|
expect(tree.exists(filePath)).toEqual(true);
|
|
// tslint:disable-next-line: no-any
|
|
const contents = parseJson(tree.readContent(filePath).toString()) as any;
|
|
expect(contents).toEqual({
|
|
extends: './tsconfig.app.json',
|
|
compilerOptions: {
|
|
outDir: './out-tsc/server',
|
|
target: 'es2016',
|
|
types: ['node'],
|
|
},
|
|
files: [
|
|
'src/main.server.ts',
|
|
],
|
|
angularCompilerOptions: {
|
|
entryModule: './src/app/app.server.module#AppServerModule',
|
|
},
|
|
});
|
|
const angularConfig = JSON.parse(tree.readContent('angular.json'));
|
|
expect(angularConfig.projects.workspace.architect
|
|
.server.options.tsConfig).toEqual('tsconfig.server.json');
|
|
});
|
|
|
|
it('should create a tsconfig file for a generated application', async () => {
|
|
const tree = await schematicRunner.runSchematicAsync('universal', defaultOptions, appTree)
|
|
.toPromise();
|
|
const filePath = '/projects/bar/tsconfig.server.json';
|
|
expect(tree.exists(filePath)).toEqual(true);
|
|
// tslint:disable-next-line: no-any
|
|
const contents = parseJson(tree.readContent(filePath).toString()) as any;
|
|
expect(contents).toEqual({
|
|
extends: './tsconfig.app.json',
|
|
compilerOptions: {
|
|
outDir: '../../out-tsc/server',
|
|
target: 'es2016',
|
|
types: ['node'],
|
|
},
|
|
files: [
|
|
'src/main.server.ts',
|
|
],
|
|
angularCompilerOptions: {
|
|
entryModule: './src/app/app.server.module#AppServerModule',
|
|
},
|
|
});
|
|
const angularConfig = JSON.parse(tree.readContent('angular.json'));
|
|
expect(angularConfig.projects.bar.architect
|
|
.server.options.tsConfig).toEqual('projects/bar/tsconfig.server.json');
|
|
});
|
|
|
|
it('should add dependency: @angular/platform-server', async () => {
|
|
const tree = await schematicRunner.runSchematicAsync('universal', defaultOptions, appTree)
|
|
.toPromise();
|
|
const filePath = '/package.json';
|
|
const contents = tree.readContent(filePath);
|
|
expect(contents).toMatch(/\"@angular\/platform-server\": \"/);
|
|
});
|
|
|
|
it('should update workspace with a server target', async () => {
|
|
const tree = await schematicRunner.runSchematicAsync('universal', defaultOptions, appTree)
|
|
.toPromise();
|
|
const filePath = '/angular.json';
|
|
const contents = tree.readContent(filePath);
|
|
const config = JSON.parse(contents.toString());
|
|
const targets = config.projects.bar.architect;
|
|
expect(targets.server).toBeDefined();
|
|
expect(targets.server.builder).toBeDefined();
|
|
const opts = targets.server.options;
|
|
expect(opts.outputPath).toEqual('dist/bar/server');
|
|
expect(opts.main).toEqual('projects/bar/src/main.server.ts');
|
|
expect(opts.tsConfig).toEqual('projects/bar/tsconfig.server.json');
|
|
const configurations = targets.server.configurations;
|
|
expect(configurations.production).toBeDefined();
|
|
expect(configurations.production.fileReplacements).toBeDefined();
|
|
expect(configurations.production.outputHashing).toBe('media');
|
|
const fileReplacements = targets.server.configurations.production.fileReplacements;
|
|
expect(fileReplacements.length).toEqual(1);
|
|
expect(fileReplacements[0].replace).toEqual('projects/bar/src/environments/environment.ts');
|
|
expect(fileReplacements[0].with).toEqual('projects/bar/src/environments/environment.prod.ts');
|
|
});
|
|
|
|
it('should update workspace with a build target outputPath', async () => {
|
|
const tree = await schematicRunner.runSchematicAsync('universal', defaultOptions, appTree)
|
|
.toPromise();
|
|
const filePath = '/angular.json';
|
|
const contents = tree.readContent(filePath);
|
|
const config = JSON.parse(contents.toString());
|
|
const targets = config.projects.bar.architect;
|
|
expect(targets.build.options.outputPath).toEqual('dist/bar/browser');
|
|
});
|
|
|
|
it('should add a server transition to BrowerModule import', async () => {
|
|
const tree = await schematicRunner.runSchematicAsync('universal', defaultOptions, appTree)
|
|
.toPromise();
|
|
const filePath = '/projects/bar/src/app/app.module.ts';
|
|
const contents = tree.readContent(filePath);
|
|
expect(contents).toMatch(/BrowserModule\.withServerTransition\({ appId: 'serverApp' }\)/);
|
|
});
|
|
|
|
it('should wrap the bootstrap call in a DOMContentLoaded event handler', async () => {
|
|
const tree = await schematicRunner.runSchematicAsync('universal', defaultOptions, appTree)
|
|
.toPromise();
|
|
const filePath = '/projects/bar/src/main.ts';
|
|
const contents = tree.readContent(filePath);
|
|
expect(contents)
|
|
.toMatch(/document.addEventListener\('DOMContentLoaded', \(\) => {/);
|
|
});
|
|
|
|
it('should wrap the bootstrap declaration in a DOMContentLoaded event handler', async () => {
|
|
const filePath = '/projects/bar/src/main.ts';
|
|
appTree.overwrite(
|
|
filePath,
|
|
`
|
|
import { enableProdMode } from '@angular/core';
|
|
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
|
|
import { AppModule } from './app/app.module';
|
|
import { environment } from './environments/environment';
|
|
import { hmrBootstrap } from './hmr';
|
|
|
|
if (environment.production) {
|
|
enableProdMode();
|
|
}
|
|
|
|
const bootstrap = () => platformBrowserDynamic().bootstrapModule(AppModule);
|
|
|
|
if (!hmrBootstrap) {
|
|
bootstrap().catch(err => console.log(err));
|
|
}
|
|
`,
|
|
);
|
|
|
|
const tree = await schematicRunner.runSchematicAsync('universal', defaultOptions, appTree)
|
|
.toPromise();
|
|
const contents = tree.readContent(filePath);
|
|
expect(contents).toMatch(
|
|
/document.addEventListener\('DOMContentLoaded', \(\) => {[\n\r\s]+bootstrap\(\)/,
|
|
);
|
|
});
|
|
|
|
it('should install npm dependencies', async () => {
|
|
await schematicRunner.runSchematicAsync('universal', defaultOptions, appTree).toPromise();
|
|
expect(schematicRunner.tasks.length).toBe(1);
|
|
expect(schematicRunner.tasks[0].name).toBe('node-package');
|
|
expect((schematicRunner.tasks[0].options as {command: string}).command).toBe('install');
|
|
});
|
|
|
|
it(`should work when 'tsconfig.app.json' has comments`, async () => {
|
|
const appTsConfigPath = '/projects/bar/tsconfig.app.json';
|
|
const appTsConfigContent = appTree.readContent(appTsConfigPath);
|
|
appTree.overwrite(appTsConfigPath, '// comment in json file\n' + appTsConfigContent);
|
|
|
|
const tree = await schematicRunner.runSchematicAsync('universal', defaultOptions, appTree)
|
|
.toPromise();
|
|
|
|
const filePath = '/projects/bar/tsconfig.server.json';
|
|
expect(tree.exists(filePath)).toEqual(true);
|
|
});
|
|
|
|
it(`should add import to '@angular/platform-server/init' in main file`, async () => {
|
|
const tree = await schematicRunner.runSchematicAsync('universal', defaultOptions, appTree)
|
|
.toPromise();
|
|
const filePath = '/projects/bar/src/main.server.ts';
|
|
const contents = tree.readContent(filePath);
|
|
expect(contents).toContain('import \'@angular/platform-server/init\'');
|
|
});
|
|
|
|
it(`should not add import to '@angular/localize' in main file when it's not a depedency`, async () => {
|
|
const tree = await schematicRunner.runSchematicAsync('universal', defaultOptions, appTree)
|
|
.toPromise();
|
|
const filePath = '/projects/bar/src/main.server.ts';
|
|
const contents = tree.readContent(filePath);
|
|
expect(contents).not.toContain('@angular/localize');
|
|
});
|
|
|
|
it(`should add import to '@angular/localize' in main file when it's a depedency`, async () => {
|
|
addPackageJsonDependency(appTree, {
|
|
name: '@angular/localize',
|
|
type: NodeDependencyType.Default,
|
|
version: 'latest',
|
|
});
|
|
|
|
const tree = await schematicRunner.runSchematicAsync('universal', defaultOptions, appTree)
|
|
.toPromise();
|
|
const filePath = '/projects/bar/src/main.server.ts';
|
|
const contents = tree.readContent(filePath);
|
|
expect(contents).toContain('@angular/localize/init');
|
|
});
|
|
});
|