UNPKG

11.3 kBJavaScriptView Raw
1"use strict";
2Object.defineProperty(exports, "__esModule", { value: true });
3/**
4 * @license
5 * Copyright Google Inc. All Rights Reserved.
6 *
7 * Use of this source code is governed by an MIT-style license that can be
8 * found in the LICENSE file at https://angular.io/license
9 */
10const core_1 = require("@angular-devkit/core");
11const schematics_1 = require("@angular-devkit/schematics");
12const ts = require("../third_party/github.com/Microsoft/TypeScript/lib/typescript");
13const ast_utils_1 = require("../utility/ast-utils");
14const change_1 = require("../utility/change");
15const ng_ast_utils_1 = require("../utility/ng-ast-utils");
16const project_targets_1 = require("../utility/project-targets");
17const workspace_1 = require("../utility/workspace");
18const workspace_models_1 = require("../utility/workspace-models");
19function getSourceFile(host, path) {
20 const buffer = host.read(path);
21 if (!buffer) {
22 throw new schematics_1.SchematicsException(`Could not find ${path}.`);
23 }
24 const content = buffer.toString();
25 const source = ts.createSourceFile(path, content, ts.ScriptTarget.Latest, true);
26 return source;
27}
28function getServerModulePath(host, sourceRoot, mainPath) {
29 const mainSource = getSourceFile(host, core_1.join(core_1.normalize(sourceRoot), mainPath));
30 const allNodes = ast_utils_1.getSourceNodes(mainSource);
31 const expNode = allNodes.find(node => ts.isExportDeclaration(node));
32 if (!expNode) {
33 return null;
34 }
35 const relativePath = expNode.moduleSpecifier;
36 const modulePath = core_1.normalize(`/${sourceRoot}/${relativePath.text}.ts`);
37 return modulePath;
38}
39function getComponentTemplateInfo(host, componentPath) {
40 const compSource = getSourceFile(host, componentPath);
41 const compMetadata = ast_utils_1.getDecoratorMetadata(compSource, 'Component', '@angular/core')[0];
42 return {
43 templateProp: getMetadataProperty(compMetadata, 'template'),
44 templateUrlProp: getMetadataProperty(compMetadata, 'templateUrl'),
45 };
46}
47function getComponentTemplate(host, compPath, tmplInfo) {
48 let template = '';
49 if (tmplInfo.templateProp) {
50 template = tmplInfo.templateProp.getFullText();
51 }
52 else if (tmplInfo.templateUrlProp) {
53 const templateUrl = tmplInfo.templateUrlProp.initializer.text;
54 const dir = core_1.dirname(core_1.normalize(compPath));
55 const templatePath = core_1.join(dir, templateUrl);
56 const buffer = host.read(templatePath);
57 if (buffer) {
58 template = buffer.toString();
59 }
60 }
61 return template;
62}
63function getBootstrapComponentPath(host, mainPath) {
64 const modulePath = ng_ast_utils_1.getAppModulePath(host, mainPath);
65 const moduleSource = getSourceFile(host, modulePath);
66 const metadataNode = ast_utils_1.getDecoratorMetadata(moduleSource, 'NgModule', '@angular/core')[0];
67 const bootstrapProperty = getMetadataProperty(metadataNode, 'bootstrap');
68 const arrLiteral = bootstrapProperty
69 .initializer;
70 const componentSymbol = arrLiteral.elements[0].getText();
71 const relativePath = ast_utils_1.getSourceNodes(moduleSource)
72 .filter(node => node.kind === ts.SyntaxKind.ImportDeclaration)
73 .filter(imp => {
74 return ast_utils_1.findNode(imp, ts.SyntaxKind.Identifier, componentSymbol);
75 })
76 .map((imp) => {
77 const pathStringLiteral = imp.moduleSpecifier;
78 return pathStringLiteral.text;
79 })[0];
80 return core_1.join(core_1.dirname(core_1.normalize(modulePath)), relativePath + '.ts');
81}
82// end helper functions.
83function validateProject(mainPath) {
84 return (host, context) => {
85 const routerOutletCheckRegex = /<router\-outlet.*?>([\s\S]*?)<\/router\-outlet>/;
86 const componentPath = getBootstrapComponentPath(host, mainPath);
87 const tmpl = getComponentTemplateInfo(host, componentPath);
88 const template = getComponentTemplate(host, componentPath, tmpl);
89 if (!routerOutletCheckRegex.test(template)) {
90 const errorMsg = `Prerequisite for app shell is to define a router-outlet in your root component.`;
91 context.logger.error(errorMsg);
92 throw new schematics_1.SchematicsException(errorMsg);
93 }
94 };
95}
96function addUniversalTarget(options) {
97 return () => {
98 // Copy options.
99 const universalOptions = {
100 ...options,
101 };
102 // Delete non-universal options.
103 delete universalOptions.universalProject;
104 delete universalOptions.route;
105 delete universalOptions.name;
106 delete universalOptions.outDir;
107 delete universalOptions.root;
108 delete universalOptions.index;
109 delete universalOptions.sourceDir;
110 return schematics_1.schematic('universal', universalOptions);
111 };
112}
113function addAppShellConfigToWorkspace(options) {
114 return () => {
115 if (!options.route) {
116 throw new schematics_1.SchematicsException(`Route is not defined`);
117 }
118 return workspace_1.updateWorkspace(workspace => {
119 const project = workspace.projects.get(options.clientProject);
120 if (!project) {
121 return;
122 }
123 project.targets.add({
124 name: 'app-shell',
125 builder: workspace_models_1.Builders.AppShell,
126 options: {
127 browserTarget: `${options.clientProject}:build`,
128 serverTarget: `${options.clientProject}:server`,
129 route: options.route,
130 },
131 configurations: {
132 production: {
133 browserTarget: `${options.clientProject}:build:production`,
134 serverTarget: `${options.clientProject}:server:production`,
135 },
136 },
137 });
138 });
139 };
140}
141function addRouterModule(mainPath) {
142 return (host) => {
143 const modulePath = ng_ast_utils_1.getAppModulePath(host, mainPath);
144 const moduleSource = getSourceFile(host, modulePath);
145 const changes = ast_utils_1.addImportToModule(moduleSource, modulePath, 'RouterModule', '@angular/router');
146 const recorder = host.beginUpdate(modulePath);
147 changes.forEach((change) => {
148 if (change instanceof change_1.InsertChange) {
149 recorder.insertLeft(change.pos, change.toAdd);
150 }
151 });
152 host.commitUpdate(recorder);
153 return host;
154 };
155}
156function getMetadataProperty(metadata, propertyName) {
157 const properties = metadata.properties;
158 const property = properties
159 .filter(prop => prop.kind === ts.SyntaxKind.PropertyAssignment)
160 .filter((prop) => {
161 const name = prop.name;
162 switch (name.kind) {
163 case ts.SyntaxKind.Identifier:
164 return name.getText() === propertyName;
165 case ts.SyntaxKind.StringLiteral:
166 return name.text === propertyName;
167 }
168 return false;
169 })[0];
170 return property;
171}
172function addServerRoutes(options) {
173 return async (host) => {
174 // The workspace gets updated so this needs to be reloaded
175 const workspace = await workspace_1.getWorkspace(host);
176 const clientProject = workspace.projects.get(options.clientProject);
177 if (!clientProject) {
178 throw new Error('Universal schematic removed client project.');
179 }
180 const clientServerTarget = clientProject.targets.get('server');
181 if (!clientServerTarget) {
182 throw new Error('Universal schematic did not add server target to client project.');
183 }
184 const clientServerOptions = clientServerTarget.options;
185 if (!clientServerOptions) {
186 throw new schematics_1.SchematicsException('Server target does not contain options.');
187 }
188 const modulePath = getServerModulePath(host, clientProject.sourceRoot || 'src', options.main);
189 if (modulePath === null) {
190 throw new schematics_1.SchematicsException('Universal/server module not found.');
191 }
192 let moduleSource = getSourceFile(host, modulePath);
193 if (!ast_utils_1.isImported(moduleSource, 'Routes', '@angular/router')) {
194 const recorder = host.beginUpdate(modulePath);
195 const routesChange = ast_utils_1.insertImport(moduleSource, modulePath, 'Routes', '@angular/router');
196 if (routesChange.toAdd) {
197 recorder.insertLeft(routesChange.pos, routesChange.toAdd);
198 }
199 const imports = ast_utils_1.getSourceNodes(moduleSource)
200 .filter(node => node.kind === ts.SyntaxKind.ImportDeclaration)
201 .sort((a, b) => a.getStart() - b.getStart());
202 const insertPosition = imports[imports.length - 1].getEnd();
203 const routeText = `\n\nconst routes: Routes = [ { path: '${options.route}', component: AppShellComponent }];`;
204 recorder.insertRight(insertPosition, routeText);
205 host.commitUpdate(recorder);
206 }
207 moduleSource = getSourceFile(host, modulePath);
208 if (!ast_utils_1.isImported(moduleSource, 'RouterModule', '@angular/router')) {
209 const recorder = host.beginUpdate(modulePath);
210 const routerModuleChange = ast_utils_1.insertImport(moduleSource, modulePath, 'RouterModule', '@angular/router');
211 if (routerModuleChange.toAdd) {
212 recorder.insertLeft(routerModuleChange.pos, routerModuleChange.toAdd);
213 }
214 const metadataChange = ast_utils_1.addSymbolToNgModuleMetadata(moduleSource, modulePath, 'imports', 'RouterModule.forRoot(routes)');
215 if (metadataChange) {
216 metadataChange.forEach((change) => {
217 recorder.insertRight(change.pos, change.toAdd);
218 });
219 }
220 host.commitUpdate(recorder);
221 }
222 };
223}
224function addShellComponent(options) {
225 const componentOptions = {
226 name: 'app-shell',
227 module: options.rootModuleFileName,
228 project: options.clientProject,
229 };
230 return schematics_1.schematic('component', componentOptions);
231}
232function default_1(options) {
233 return async (tree) => {
234 const workspace = await workspace_1.getWorkspace(tree);
235 const clientProject = workspace.projects.get(options.clientProject);
236 if (!clientProject || clientProject.extensions.projectType !== 'application') {
237 throw new schematics_1.SchematicsException(`A client project type of "application" is required.`);
238 }
239 const clientBuildTarget = clientProject.targets.get('build');
240 if (!clientBuildTarget) {
241 throw project_targets_1.targetBuildNotFoundError();
242 }
243 const clientBuildOptions = (clientBuildTarget.options || {});
244 return schematics_1.chain([
245 validateProject(clientBuildOptions.main),
246 clientProject.targets.has('server') ? schematics_1.noop() : addUniversalTarget(options),
247 addAppShellConfigToWorkspace(options),
248 addRouterModule(clientBuildOptions.main),
249 addServerRoutes(options),
250 addShellComponent(options),
251 ]);
252 };
253}
254exports.default = default_1;