|
| 1 | +/** |
| 2 | + * @license |
| 3 | + * Copyright Google LLC All Rights Reserved. |
| 4 | + * |
| 5 | + * Use of this source code is governed by an MIT-style license that can be |
| 6 | + * found in the LICENSE file at https://angular.io/license |
| 7 | + */ |
| 8 | +import { |
| 9 | + basename, |
| 10 | + experimental, |
| 11 | + join, |
| 12 | + normalize, |
| 13 | + parseJson, |
| 14 | + strings, |
| 15 | +} from '@angular-devkit/core'; |
| 16 | +import { |
| 17 | + Rule, |
| 18 | + SchematicContext, |
| 19 | + SchematicsException, |
| 20 | + Tree, |
| 21 | + apply, |
| 22 | + chain, |
| 23 | + mergeWith, |
| 24 | + move, |
| 25 | + template, |
| 26 | + url, |
| 27 | + noop, |
| 28 | + filter, |
| 29 | + externalSchematic, |
| 30 | +} from '@angular-devkit/schematics'; |
| 31 | +import {NodePackageInstallTask} from '@angular-devkit/schematics/tasks'; |
| 32 | +import {getWorkspace} from '@schematics/angular/utility/config'; |
| 33 | +import {Schema as UniversalOptions} from './schema'; |
| 34 | + |
| 35 | + |
| 36 | +function getClientProject( |
| 37 | + host: Tree, options: UniversalOptions, |
| 38 | +): experimental.workspace.WorkspaceProject { |
| 39 | + const workspace = getWorkspace(host); |
| 40 | + const clientProject = workspace.projects[options.clientProject]; |
| 41 | + if (!clientProject) { |
| 42 | + throw new SchematicsException(`Client app ${options.clientProject} not found.`); |
| 43 | + } |
| 44 | + |
| 45 | + return clientProject; |
| 46 | +} |
| 47 | + |
| 48 | +function getClientArchitect( |
| 49 | + host: Tree, |
| 50 | + options: UniversalOptions, |
| 51 | +): experimental.workspace.WorkspaceTool { |
| 52 | + const clientArchitect = getClientProject(host, options).architect; |
| 53 | + |
| 54 | + if (!clientArchitect) { |
| 55 | + throw new Error('Client project architect not found.'); |
| 56 | + } |
| 57 | + |
| 58 | + return clientArchitect; |
| 59 | +} |
| 60 | + |
| 61 | +function addDependenciesAndScripts(options: UniversalOptions): Rule { |
| 62 | + return (host: Tree) => { |
| 63 | + |
| 64 | + const pkgPath = '/package.json'; |
| 65 | + const buffer = host.read(pkgPath); |
| 66 | + if (buffer === null) { |
| 67 | + throw new SchematicsException('Could not find package.json'); |
| 68 | + } |
| 69 | + |
| 70 | + const pkg = JSON.parse(buffer.toString()); |
| 71 | + |
| 72 | + pkg.dependencies['@nguniversal/express-engine'] = '0.0.0-PLACEHOLDER'; |
| 73 | + pkg.dependencies['@nguniversal/module-map-ngfactory-loader'] = '0.0.0-PLACEHOLDER'; |
| 74 | + pkg.dependencies['express'] = 'EXPRESS_VERSION'; |
| 75 | + |
| 76 | + pkg.scripts['serve:ssr'] = 'node dist/server'; |
| 77 | + pkg.scripts['build:ssr'] = 'npm run build:client-and-server-bundles && npm run compile:server'; |
| 78 | + pkg.scripts['build:client-and-server-bundles'] = |
| 79 | + `ng build --prod && ng run ${options.clientProject}:server:production`; |
| 80 | + pkg.scripts['compile:server'] = |
| 81 | + `tsc -p ${options.serverFileName.replace(/\.ts$/, '')}.tsconfig.json`; |
| 82 | + |
| 83 | + host.overwrite(pkgPath, JSON.stringify(pkg, null, 2)); |
| 84 | + |
| 85 | + return host; |
| 86 | + }; |
| 87 | +} |
| 88 | + |
| 89 | +export default function (options: UniversalOptions): Rule { |
| 90 | + return (host: Tree, context: SchematicContext) => { |
| 91 | + const clientProject = getClientProject(host, options); |
| 92 | + if (clientProject.projectType !== 'application') { |
| 93 | + throw new SchematicsException(`Universal requires a project type of "application".`); |
| 94 | + } |
| 95 | + const clientArchitect = getClientArchitect(host, options); |
| 96 | + const tsConfigExtends = basename(clientArchitect.build.options.tsConfig); |
| 97 | + const rootInSrc = clientProject.root === ''; |
| 98 | + const tsConfigDirectory = join(normalize(clientProject.root), rootInSrc ? 'src' : ''); |
| 99 | + |
| 100 | + if (!options.skipInstall) { |
| 101 | + context.addTask(new NodePackageInstallTask()); |
| 102 | + } |
| 103 | + |
| 104 | + const rootSource = apply(url('./files/root'), [ |
| 105 | + options.skipServer ? filter(path => !path.startsWith('__serverFileName')) : noop(), |
| 106 | + template({ |
| 107 | + ...strings, |
| 108 | + ...options as object, |
| 109 | + stripTsExtension: (s: string) => { return s.replace(/\.ts$/, ''); }, |
| 110 | + tsConfigExtends, |
| 111 | + rootInSrc, |
| 112 | + }), |
| 113 | + move(tsConfigDirectory), |
| 114 | + ]); |
| 115 | + |
| 116 | + return chain([ |
| 117 | + externalSchematic('@schematics/angular', 'universal', options), |
| 118 | + mergeWith(rootSource), |
| 119 | + addDependenciesAndScripts(options), |
| 120 | + ]); |
| 121 | + }; |
| 122 | +} |
0 commit comments