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 tasks_1 = require("@angular-devkit/schematics/tasks");
13const ts = require("../third_party/github.com/Microsoft/TypeScript/lib/typescript");
14const ast_utils_1 = require("../utility/ast-utils");
15const change_1 = require("../utility/change");
16const dependencies_1 = require("../utility/dependencies");
17const ng_ast_utils_1 = require("../utility/ng-ast-utils");
18const paths_1 = require("../utility/paths");
19const project_targets_1 = require("../utility/project-targets");
20const tsconfig_1 = require("../utility/tsconfig");
21const workspace_1 = require("../utility/workspace");
22const workspace_models_1 = require("../utility/workspace-models");
23function updateConfigFile(options, tsConfigDirectory) {
24 return workspace_1.updateWorkspace(workspace => {
25 const clientProject = workspace.projects.get(options.clientProject);
26 if (clientProject) {
27 const buildTarget = clientProject.targets.get('build');
28 let fileReplacements;
29 if (buildTarget && buildTarget.configurations && buildTarget.configurations.production) {
30 fileReplacements = buildTarget.configurations.production.fileReplacements;
31 }
32 if (buildTarget && buildTarget.options) {
33 buildTarget.options.outputPath = `dist/${options.clientProject}/browser`;
34 }
35 // In case the browser builder hashes the assets
36 // we need to add this setting to the server builder
37 // as otherwise when assets it will be requested twice.
38 // One for the server which will be unhashed, and other on the client which will be hashed.
39 let outputHashing;
40 if (buildTarget && buildTarget.configurations && buildTarget.configurations.production) {
41 switch (buildTarget.configurations.production.outputHashing) {
42 case 'all':
43 case 'media':
44 outputHashing = 'media';
45 break;
46 }
47 }
48 const mainPath = options.main;
49 const serverTsConfig = core_1.join(tsConfigDirectory, 'tsconfig.server.json');
50 clientProject.targets.add({
51 name: 'server',
52 builder: workspace_models_1.Builders.Server,
53 options: {
54 outputPath: `dist/${options.clientProject}/server`,
55 main: core_1.join(core_1.normalize(clientProject.root), 'src', mainPath.endsWith('.ts') ? mainPath : mainPath + '.ts'),
56 tsConfig: serverTsConfig,
57 },
58 configurations: {
59 production: {
60 outputHashing,
61 fileReplacements,
62 sourceMap: false,
63 optimization: true,
64 },
65 },
66 });
67 const lintTarget = clientProject.targets.get('lint');
68 if (lintTarget && lintTarget.options && Array.isArray(lintTarget.options.tsConfig)) {
69 lintTarget.options.tsConfig =
70 lintTarget.options.tsConfig.concat(serverTsConfig);
71 }
72 }
73 });
74}
75function findBrowserModuleImport(host, modulePath) {
76 const moduleBuffer = host.read(modulePath);
77 if (!moduleBuffer) {
78 throw new schematics_1.SchematicsException(`Module file (${modulePath}) not found`);
79 }
80 const moduleFileText = moduleBuffer.toString('utf-8');
81 const source = ts.createSourceFile(modulePath, moduleFileText, ts.ScriptTarget.Latest, true);
82 const decoratorMetadata = ast_utils_1.getDecoratorMetadata(source, 'NgModule', '@angular/core')[0];
83 const browserModuleNode = ast_utils_1.findNode(decoratorMetadata, ts.SyntaxKind.Identifier, 'BrowserModule');
84 if (browserModuleNode === null) {
85 throw new schematics_1.SchematicsException(`Cannot find BrowserModule import in ${modulePath}`);
86 }
87 return browserModuleNode;
88}
89function wrapBootstrapCall(mainFile) {
90 return (host) => {
91 const mainPath = core_1.normalize('/' + mainFile);
92 let bootstrapCall = ng_ast_utils_1.findBootstrapModuleCall(host, mainPath);
93 if (bootstrapCall === null) {
94 throw new schematics_1.SchematicsException('Bootstrap module not found.');
95 }
96 let bootstrapCallExpression = null;
97 let currentCall = bootstrapCall;
98 while (bootstrapCallExpression === null && currentCall.parent) {
99 currentCall = currentCall.parent;
100 if (ts.isExpressionStatement(currentCall) || ts.isVariableStatement(currentCall)) {
101 bootstrapCallExpression = currentCall;
102 }
103 }
104 bootstrapCall = currentCall;
105 // In case the bootstrap code is a variable statement
106 // we need to determine it's usage
107 if (bootstrapCallExpression && ts.isVariableStatement(bootstrapCallExpression)) {
108 const declaration = bootstrapCallExpression.declarationList.declarations[0];
109 const bootstrapVar = declaration.name.text;
110 const sf = bootstrapCallExpression.getSourceFile();
111 bootstrapCall = findCallExpressionNode(sf, bootstrapVar) || currentCall;
112 }
113 // indent contents
114 const triviaWidth = bootstrapCall.getLeadingTriviaWidth();
115 const beforeText = `document.addEventListener('DOMContentLoaded', () => {\n`
116 + ' '.repeat(triviaWidth > 2 ? triviaWidth + 1 : triviaWidth);
117 const afterText = `\n${triviaWidth > 2 ? ' '.repeat(triviaWidth - 1) : ''}});`;
118 // in some cases we need to cater for a trailing semicolon such as;
119 // bootstrap().catch(err => console.log(err));
120 const lastToken = bootstrapCall.parent.getLastToken();
121 let endPos = bootstrapCall.getEnd();
122 if (lastToken && lastToken.kind === ts.SyntaxKind.SemicolonToken) {
123 endPos = lastToken.getEnd();
124 }
125 const recorder = host.beginUpdate(mainPath);
126 recorder.insertLeft(bootstrapCall.getStart(), beforeText);
127 recorder.insertRight(endPos, afterText);
128 host.commitUpdate(recorder);
129 };
130}
131function findCallExpressionNode(node, text) {
132 if (ts.isCallExpression(node)
133 && ts.isIdentifier(node.expression)
134 && node.expression.text === text) {
135 return node;
136 }
137 let foundNode = null;
138 ts.forEachChild(node, childNode => {
139 foundNode = findCallExpressionNode(childNode, text);
140 if (foundNode) {
141 return true;
142 }
143 });
144 return foundNode;
145}
146function addServerTransition(options, mainFile, clientProjectRoot) {
147 return (host) => {
148 const mainPath = core_1.normalize('/' + mainFile);
149 const bootstrapModuleRelativePath = ng_ast_utils_1.findBootstrapModulePath(host, mainPath);
150 const bootstrapModulePath = core_1.normalize(`/${clientProjectRoot}/src/${bootstrapModuleRelativePath}.ts`);
151 const browserModuleImport = findBrowserModuleImport(host, bootstrapModulePath);
152 const appId = options.appId;
153 const transitionCall = `.withServerTransition({ appId: '${appId}' })`;
154 const position = browserModuleImport.pos + browserModuleImport.getFullText().length;
155 const transitionCallChange = new change_1.InsertChange(bootstrapModulePath, position, transitionCall);
156 const transitionCallRecorder = host.beginUpdate(bootstrapModulePath);
157 transitionCallRecorder.insertLeft(transitionCallChange.pos, transitionCallChange.toAdd);
158 host.commitUpdate(transitionCallRecorder);
159 };
160}
161function addDependencies() {
162 return (host) => {
163 const coreDep = dependencies_1.getPackageJsonDependency(host, '@angular/core');
164 if (coreDep === null) {
165 throw new schematics_1.SchematicsException('Could not find version.');
166 }
167 const platformServerDep = {
168 ...coreDep,
169 name: '@angular/platform-server',
170 };
171 dependencies_1.addPackageJsonDependency(host, platformServerDep);
172 return host;
173 };
174}
175function default_1(options) {
176 return async (host, context) => {
177 const workspace = await workspace_1.getWorkspace(host);
178 const clientProject = workspace.projects.get(options.clientProject);
179 if (!clientProject || clientProject.extensions.projectType !== 'application') {
180 throw new schematics_1.SchematicsException(`Universal requires a project type of "application".`);
181 }
182 const clientBuildTarget = clientProject.targets.get('build');
183 if (!clientBuildTarget) {
184 throw project_targets_1.targetBuildNotFoundError();
185 }
186 tsconfig_1.verifyBaseTsConfigExists(host);
187 const clientBuildOptions = (clientBuildTarget.options || {});
188 const clientTsConfig = core_1.normalize(clientBuildOptions.tsConfig);
189 const tsConfigExtends = core_1.basename(clientTsConfig);
190 // this is needed because prior to version 8, tsconfig might have been in 'src'
191 // and we don't want to break the 'ng add @nguniversal/express-engine schematics'
192 const rootInSrc = clientProject.root === '' && clientTsConfig.includes('src/');
193 const tsConfigDirectory = core_1.join(core_1.normalize(clientProject.root), rootInSrc ? 'src' : '');
194 if (!options.skipInstall) {
195 context.addTask(new tasks_1.NodePackageInstallTask());
196 }
197 const templateSource = schematics_1.apply(schematics_1.url('./files/src'), [
198 schematics_1.applyTemplates({
199 ...core_1.strings,
200 ...options,
201 stripTsExtension: (s) => s.replace(/\.ts$/, ''),
202 hasLocalizePackage: !!dependencies_1.getPackageJsonDependency(host, '@angular/localize'),
203 }),
204 schematics_1.move(core_1.join(core_1.normalize(clientProject.root), 'src')),
205 ]);
206 const rootSource = schematics_1.apply(schematics_1.url('./files/root'), [
207 schematics_1.applyTemplates({
208 ...core_1.strings,
209 ...options,
210 stripTsExtension: (s) => s.replace(/\.ts$/, ''),
211 tsConfigExtends,
212 relativePathToWorkspaceRoot: paths_1.relativePathToWorkspaceRoot(tsConfigDirectory),
213 rootInSrc,
214 }),
215 schematics_1.move(tsConfigDirectory),
216 ]);
217 return schematics_1.chain([
218 schematics_1.mergeWith(templateSource),
219 schematics_1.mergeWith(rootSource),
220 addDependencies(),
221 updateConfigFile(options, tsConfigDirectory),
222 wrapBootstrapCall(clientBuildOptions.main),
223 addServerTransition(options, clientBuildOptions.main, clientProject.root),
224 tsconfig_1.addTsConfigProjectReferences([
225 core_1.join(tsConfigDirectory, 'tsconfig.server.json'),
226 ]),
227 ]);
228 };
229}
230exports.default = default_1;