1 | "use strict";
|
2 | Object.defineProperty(exports, "__esModule", { value: true });
|
3 |
|
4 |
|
5 |
|
6 |
|
7 |
|
8 |
|
9 |
|
10 | const core_1 = require("@angular-devkit/core");
|
11 | const schematics_1 = require("@angular-devkit/schematics");
|
12 | const tasks_1 = require("@angular-devkit/schematics/tasks");
|
13 | const ts = require("../third_party/github.com/Microsoft/TypeScript/lib/typescript");
|
14 | const ast_utils_1 = require("../utility/ast-utils");
|
15 | const change_1 = require("../utility/change");
|
16 | const dependencies_1 = require("../utility/dependencies");
|
17 | const ng_ast_utils_1 = require("../utility/ng-ast-utils");
|
18 | const paths_1 = require("../utility/paths");
|
19 | const project_targets_1 = require("../utility/project-targets");
|
20 | const tsconfig_1 = require("../utility/tsconfig");
|
21 | const workspace_1 = require("../utility/workspace");
|
22 | const workspace_models_1 = require("../utility/workspace-models");
|
23 | function 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 |
|
36 |
|
37 |
|
38 |
|
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 | }
|
75 | function 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 | }
|
89 | function 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 |
|
106 |
|
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 |
|
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 |
|
119 |
|
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 | }
|
131 | function 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 | }
|
146 | function 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 | }
|
161 | function 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 | }
|
175 | function 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 |
|
191 |
|
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 | }
|
230 | exports.default = default_1;
|