Skip to content

Commit 6a85b13

Browse files
committed
refactor(@angular/ssr): move ng-add schematic to @schematics/angular
This move is in preparation to enable `ng new --ssr`.
1 parent 732aab5 commit 6a85b13

File tree

16 files changed

+550
-497
lines changed

16 files changed

+550
-497
lines changed

packages/angular/ssr/schematics/BUILD.bazel

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -40,13 +40,8 @@ filegroup(
4040
name = "schematics_assets",
4141
srcs = glob(
4242
[
43-
"**/files/**/*",
4443
"**/*.json",
4544
],
46-
exclude = [
47-
# NB: we need to exclude the nested node_modules that is laid out by yarn workspaces
48-
"node_modules/**",
49-
],
5045
),
5146
)
5247

@@ -66,11 +61,8 @@ ts_library(
6661
],
6762
data = [":schematics_assets"],
6863
deps = [
69-
"//packages/angular_devkit/core",
7064
"//packages/angular_devkit/schematics",
7165
"//packages/schematics/angular",
72-
"@npm//@types/node",
73-
"@npm//typescript",
7466
],
7567
)
7668

@@ -89,8 +81,6 @@ ts_library(
8981
# @external_begin
9082
deps = [
9183
":schematics",
92-
"//packages/angular_devkit/core",
93-
"//packages/angular_devkit/schematics",
9484
"//packages/angular_devkit/schematics/testing",
9585
],
9686
# @external_end

packages/angular/ssr/schematics/collection.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
"schematics": {
33
"ng-add": {
44
"description": "Adds Angular SSR to the application without affecting any templates",
5-
"factory": "./ng-add",
5+
"factory": "./ng-add/index",
66
"schema": "./ng-add/schema.json"
77
}
88
}

packages/angular/ssr/schematics/ng-add/index.ts

Lines changed: 4 additions & 246 deletions
Original file line numberDiff line numberDiff line change
@@ -6,251 +6,9 @@
66
* found in the LICENSE file at https://angular.io/license
77
*/
88

9-
import { join, normalize, strings } from '@angular-devkit/core';
10-
import {
11-
Rule,
12-
SchematicsException,
13-
apply,
14-
applyTemplates,
15-
chain,
16-
externalSchematic,
17-
mergeWith,
18-
move,
19-
noop,
20-
url,
21-
} from '@angular-devkit/schematics';
22-
import { Schema as ServerOptions } from '@schematics/angular/server/schema';
23-
import { DependencyType, addDependency, updateWorkspace } from '@schematics/angular/utility';
24-
import { JSONFile } from '@schematics/angular/utility/json-file';
25-
import { isStandaloneApp } from '@schematics/angular/utility/ng-ast-utils';
26-
import { targetBuildNotFoundError } from '@schematics/angular/utility/project-targets';
27-
import { getMainFilePath } from '@schematics/angular/utility/standalone/util';
28-
import { getWorkspace } from '@schematics/angular/utility/workspace';
29-
import { Builders } from '@schematics/angular/utility/workspace-models';
9+
import { Rule, externalSchematic } from '@angular-devkit/schematics';
10+
import { Schema as SSROptions } from './schema';
3011

31-
import { latestVersions } from '../utility/latest-versions';
32-
import { getOutputPath, getProject } from '../utility/utils';
33-
34-
import { Schema as AddServerOptions } from './schema';
35-
36-
const SERVE_SSR_TARGET_NAME = 'serve-ssr';
37-
const PRERENDER_TARGET_NAME = 'prerender';
38-
39-
function addScriptsRule(options: AddServerOptions): Rule {
40-
return async (host) => {
41-
const pkgPath = '/package.json';
42-
const buffer = host.read(pkgPath);
43-
if (buffer === null) {
44-
throw new SchematicsException('Could not find package.json');
45-
}
46-
47-
const serverDist = await getOutputPath(host, options.project, 'server');
48-
const pkg = JSON.parse(buffer.toString()) as { scripts?: Record<string, string> };
49-
pkg.scripts = {
50-
...pkg.scripts,
51-
'dev:ssr': `ng run ${options.project}:${SERVE_SSR_TARGET_NAME}`,
52-
'serve:ssr': `node ${serverDist}/main.js`,
53-
'build:ssr': `ng build && ng run ${options.project}:server`,
54-
'prerender': `ng run ${options.project}:${PRERENDER_TARGET_NAME}`,
55-
};
56-
57-
host.overwrite(pkgPath, JSON.stringify(pkg, null, 2));
58-
};
59-
}
60-
61-
function updateApplicationBuilderTsConfigRule(options: AddServerOptions): Rule {
62-
return async (host) => {
63-
const project = await getProject(host, options.project);
64-
const buildTarget = project.targets.get('build');
65-
if (!buildTarget || !buildTarget.options) {
66-
return;
67-
}
68-
69-
const tsConfigPath = buildTarget.options.tsConfig;
70-
if (!tsConfigPath || typeof tsConfigPath !== 'string') {
71-
// No tsconfig path
72-
return;
73-
}
74-
75-
const tsConfig = new JSONFile(host, tsConfigPath);
76-
const filesAstNode = tsConfig.get(['files']);
77-
const serverFilePath = 'server.ts';
78-
if (Array.isArray(filesAstNode) && !filesAstNode.some(({ text }) => text === serverFilePath)) {
79-
tsConfig.modify(['files'], [...filesAstNode, serverFilePath]);
80-
}
81-
};
82-
}
83-
84-
function updateApplicationBuilderWorkspaceConfigRule(
85-
projectRoot: string,
86-
options: AddServerOptions,
87-
): Rule {
88-
return updateWorkspace((workspace) => {
89-
const buildTarget = workspace.projects.get(options.project)?.targets.get('build');
90-
if (!buildTarget) {
91-
return;
92-
}
93-
94-
buildTarget.options = {
95-
...buildTarget.options,
96-
prerender: true,
97-
ssr: join(normalize(projectRoot), 'server.ts'),
98-
};
99-
});
100-
}
101-
102-
function updateWebpackBuilderWorkspaceConfigRule(options: AddServerOptions): Rule {
103-
return updateWorkspace((workspace) => {
104-
const projectName = options.project;
105-
const project = workspace.projects.get(projectName);
106-
if (!project) {
107-
return;
108-
}
109-
110-
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
111-
const serverTarget = project.targets.get('server')!;
112-
(serverTarget.options ??= {}).main = join(normalize(project.root), 'server.ts');
113-
114-
const serveSSRTarget = project.targets.get(SERVE_SSR_TARGET_NAME);
115-
if (serveSSRTarget) {
116-
return;
117-
}
118-
119-
project.targets.add({
120-
name: SERVE_SSR_TARGET_NAME,
121-
builder: '@angular-devkit/build-angular:ssr-dev-server',
122-
defaultConfiguration: 'development',
123-
options: {},
124-
configurations: {
125-
development: {
126-
browserTarget: `${projectName}:build:development`,
127-
serverTarget: `${projectName}:server:development`,
128-
},
129-
production: {
130-
browserTarget: `${projectName}:build:production`,
131-
serverTarget: `${projectName}:server:production`,
132-
},
133-
},
134-
});
135-
136-
const prerenderTarget = project.targets.get(PRERENDER_TARGET_NAME);
137-
if (prerenderTarget) {
138-
return;
139-
}
140-
141-
project.targets.add({
142-
name: PRERENDER_TARGET_NAME,
143-
builder: '@angular-devkit/build-angular:prerender',
144-
defaultConfiguration: 'production',
145-
options: {
146-
routes: ['/'],
147-
},
148-
configurations: {
149-
production: {
150-
browserTarget: `${projectName}:build:production`,
151-
serverTarget: `${projectName}:server:production`,
152-
},
153-
development: {
154-
browserTarget: `${projectName}:build:development`,
155-
serverTarget: `${projectName}:server:development`,
156-
},
157-
},
158-
});
159-
});
160-
}
161-
162-
function updateWebpackBuilderServerTsConfigRule(options: AddServerOptions): Rule {
163-
return async (host) => {
164-
const project = await getProject(host, options.project);
165-
const serverTarget = project.targets.get('server');
166-
if (!serverTarget || !serverTarget.options) {
167-
return;
168-
}
169-
170-
const tsConfigPath = serverTarget.options.tsConfig;
171-
if (!tsConfigPath || typeof tsConfigPath !== 'string') {
172-
// No tsconfig path
173-
return;
174-
}
175-
176-
const tsConfig = new JSONFile(host, tsConfigPath);
177-
const filesAstNode = tsConfig.get(['files']);
178-
const serverFilePath = 'server.ts';
179-
if (Array.isArray(filesAstNode) && !filesAstNode.some(({ text }) => text === serverFilePath)) {
180-
tsConfig.modify(['files'], [...filesAstNode, serverFilePath]);
181-
}
182-
};
183-
}
184-
185-
function addDependencies(): Rule {
186-
return chain([
187-
addDependency('express', latestVersions['express'], {
188-
type: DependencyType.Default,
189-
}),
190-
addDependency('@types/express', latestVersions['@types/express'], {
191-
type: DependencyType.Dev,
192-
}),
193-
]);
194-
}
195-
196-
function addServerFile(options: ServerOptions, isStandalone: boolean): Rule {
197-
return async (host) => {
198-
const project = await getProject(host, options.project);
199-
const browserDistDirectory = await getOutputPath(host, options.project, 'build');
200-
201-
return mergeWith(
202-
apply(
203-
url(
204-
`./files/${
205-
project?.targets?.get('build')?.builder === Builders.Application
206-
? 'application-builder'
207-
: 'server-builder'
208-
}`,
209-
),
210-
[
211-
applyTemplates({
212-
...strings,
213-
...options,
214-
browserDistDirectory,
215-
isStandalone,
216-
}),
217-
move(project.root),
218-
],
219-
),
220-
);
221-
};
222-
}
223-
224-
export default function (options: AddServerOptions): Rule {
225-
return async (host) => {
226-
const browserEntryPoint = await getMainFilePath(host, options.project);
227-
const isStandalone = isStandaloneApp(host, browserEntryPoint);
228-
229-
const workspace = await getWorkspace(host);
230-
const clientProject = workspace.projects.get(options.project);
231-
if (!clientProject) {
232-
throw targetBuildNotFoundError();
233-
}
234-
const isUsingApplicationBuilder =
235-
clientProject.targets.get('build')?.builder === Builders.Application;
236-
237-
return chain([
238-
externalSchematic('@schematics/angular', 'server', {
239-
...options,
240-
skipInstall: true,
241-
}),
242-
...(isUsingApplicationBuilder
243-
? [
244-
updateApplicationBuilderWorkspaceConfigRule(clientProject.root, options),
245-
updateApplicationBuilderTsConfigRule(options),
246-
]
247-
: [
248-
addScriptsRule(options),
249-
updateWebpackBuilderServerTsConfigRule(options),
250-
updateWebpackBuilderWorkspaceConfigRule(options),
251-
]),
252-
addServerFile(options, isStandalone),
253-
addDependencies(),
254-
]);
255-
};
12+
export default function (options: SSROptions): Rule {
13+
return externalSchematic('@schematics/angular', 'ssr', options);
25614
}

0 commit comments

Comments
 (0)