Skip to content

Commit 8d06efe

Browse files
authored
fix: adapt to @angular-devkit/build-angular:application builder (#88)
This PR adapts the `ngsscbuild` builder to the new `@angular-devkit/build-angular:application` builder. When using `@angular-devkit/build-angular:application` with a `server` configuration, the `filePattern` is extended to also include the `index.server.html` file. If using `@angular-devkit/build-angular:application` without a `server` configuration, the `ngssc.json` file is generated inside the `{outputPath}/browser` directory, next to the browser files. Closes #87
1 parent 47015b2 commit 8d06efe

25 files changed

+857
-106
lines changed

package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,9 @@
5252
"@angular/forms": "^17.0.1",
5353
"@angular/platform-browser": "^17.0.1",
5454
"@angular/platform-browser-dynamic": "^17.0.1",
55+
"@angular/platform-server": "^17.0.1",
5556
"@angular/router": "^17.0.1",
57+
"@angular/ssr": "^17.0.0",
5658
"rxjs": "7.8.1",
5759
"tslib": "^2.6.2",
5860
"zone.js": "~0.14.2"
Lines changed: 83 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,17 @@
11
import { Architect } from '@angular-devkit/architect';
2+
import { TestProjectHost } from '@angular-devkit/architect/testing';
23
import { normalize, virtualFs } from '@angular-devkit/core';
34

45
import { Ngssc } from 'angular-server-side-configuration';
56

6-
import { createArchitect, host } from '../../../../test/test-utils';
7+
import { applicationHost, createArchitect, legacyHost } from '../../../../test/test-utils';
78

89
describe('Ngssc Builder', () => {
910
const targetSpec = { project: 'app', target: 'ngsscbuild' };
1011
let architect: Architect | undefined;
1112

12-
beforeEach(async () => {
13-
architect = undefined;
14-
await host.initialize().toPromise();
15-
});
16-
afterEach(async () => host.restore().toPromise());
17-
18-
async function runNgsscbuild() {
19-
architect = (await createArchitect(host.root())).architect;
13+
async function runNgsscbuild(host: TestProjectHost) {
14+
architect = (await createArchitect(host.root(), host)).architect;
2015

2116
// A "run" can have multiple outputs, and contains progress information.
2217
const run = await architect.scheduleTarget(targetSpec);
@@ -31,36 +26,90 @@ describe('Ngssc Builder', () => {
3126
return output;
3227
}
3328

34-
function readNgsscJson(): Ngssc {
35-
const content = virtualFs.fileBufferToString(
36-
host.scopedSync().read(normalize('dist/ngssc.json')),
37-
);
29+
function readNgsscJson(host: TestProjectHost, ngsscPath = 'dist/ngssc.json'): Ngssc {
30+
const content = virtualFs.fileBufferToString(host.scopedSync().read(normalize(ngsscPath)));
3831

3932
return JSON.parse(content);
4033
}
4134

42-
it('should build with process variant', async () => {
43-
const output = await runNgsscbuild();
44-
45-
expect(output.success).toBe(true);
46-
47-
const ngssc = readNgsscJson();
48-
expect(ngssc.variant).toEqual('process');
49-
expect(ngssc.filePattern).toEqual('index.html');
35+
describe('@angular-devkit/build-angular:browser', () => {
36+
beforeEach(async () => {
37+
architect = undefined;
38+
await legacyHost.initialize().toPromise();
39+
});
40+
afterEach(async () => legacyHost.restore().toPromise());
41+
42+
it('should build with process variant', async () => {
43+
const output = await runNgsscbuild(legacyHost);
44+
45+
expect(output.success).toBe(true);
46+
47+
const ngssc = readNgsscJson(legacyHost);
48+
expect(ngssc.variant).toEqual('process');
49+
expect(ngssc.filePattern).toEqual('index.html');
50+
});
51+
52+
it('should aggregate environment variables', async () => {
53+
const expected = 'OTHER_VARIABLE';
54+
legacyHost.replaceInFile(
55+
'angular.json',
56+
'"additionalEnvironmentVariables": [],',
57+
`"additionalEnvironmentVariables": ["${expected}"],`,
58+
);
59+
const output = await runNgsscbuild(legacyHost);
60+
61+
expect(output.success).toBe(true);
62+
63+
const ngssc = readNgsscJson(legacyHost);
64+
expect(ngssc.environmentVariables).toContain(expected);
65+
});
5066
});
5167

52-
it('should aggregate environment variables', async () => {
53-
const expected = 'OTHER_VARIABLE';
54-
host.replaceInFile(
55-
'angular.json',
56-
'"additionalEnvironmentVariables": [],',
57-
`"additionalEnvironmentVariables": ["${expected}"],`,
58-
);
59-
const output = await runNgsscbuild();
60-
61-
expect(output.success).toBe(true);
62-
63-
const ngssc = readNgsscJson();
64-
expect(ngssc.environmentVariables).toContain(expected);
68+
describe('@angular-devkit/build-angular:application', () => {
69+
beforeEach(async () => {
70+
architect = undefined;
71+
await applicationHost.initialize().toPromise();
72+
});
73+
afterEach(async () => applicationHost.restore().toPromise());
74+
75+
it('should build with process variant', async () => {
76+
const output = await runNgsscbuild(applicationHost);
77+
78+
expect(output.success).toBe(true);
79+
80+
const ngssc = readNgsscJson(applicationHost);
81+
expect(ngssc.variant).toEqual('process');
82+
expect(ngssc.filePattern).toEqual('**/index{.,.server.}html');
83+
});
84+
85+
it('should aggregate environment variables', async () => {
86+
const expected = 'OTHER_VARIABLE';
87+
applicationHost.replaceInFile(
88+
'angular.json',
89+
'"additionalEnvironmentVariables": [],',
90+
`"additionalEnvironmentVariables": ["${expected}"],`,
91+
);
92+
const output = await runNgsscbuild(applicationHost);
93+
94+
expect(output.success).toBe(true);
95+
96+
const ngssc = readNgsscJson(applicationHost);
97+
expect(ngssc.environmentVariables).toContain(expected);
98+
});
99+
100+
it('should write ngssc into dist/browser directory without SSR', async () => {
101+
applicationHost.replaceInFile(
102+
'angular.json',
103+
/("server":\s+"src\/main.server.ts",|"prerender":\s+true,|"ssr":\s+\{\s+"entry":\s+"server.ts"\s+\})/g,
104+
'',
105+
);
106+
const output = await runNgsscbuild(applicationHost);
107+
108+
expect(output.success).toBe(true);
109+
110+
const ngssc = readNgsscJson(applicationHost, 'dist/browser/ngssc.json');
111+
expect(ngssc.variant).toEqual('process');
112+
expect(ngssc.filePattern).toEqual('index.html');
113+
});
65114
});
66115
});

projects/angular-server-side-configuration/builders/ngsscbuild/index.ts

Lines changed: 56 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,25 @@
1-
import { promises } from 'fs';
1+
import { existsSync, readFileSync, writeFileSync } from 'fs';
22
import { basename, join } from 'path';
33
import { BuilderContext, createBuilder, targetFromTargetString } from '@angular-devkit/architect';
4-
import { BrowserBuilderOptions } from '@angular-devkit/build-angular';
4+
import { ApplicationBuilderOptions, BrowserBuilderOptions } from '@angular-devkit/build-angular';
55
import { json, JsonObject } from '@angular-devkit/core';
66
import { Ngssc } from 'angular-server-side-configuration';
7-
import { glob } from 'glob';
7+
import * as glob from 'glob';
88
import { Schema } from './schema';
99
import { VariableDetector } from './variable-detector';
1010
import { NgsscContext } from './ngssc-context';
1111

1212
export type NgsscBuildSchema = Schema;
13-
14-
const readFileAsync = promises.readFile;
15-
const writeFileAsync = promises.writeFile;
13+
type BuilderOptions = ApplicationBuilderOptions | BrowserBuilderOptions;
14+
type ApplicationBuilderVariant = undefined | 'browser-only' | 'server';
1615

1716
export async function ngsscBuild(options: NgsscBuildSchema, context: BuilderContext) {
1817
const buildTarget = targetFromTargetString(options.buildTarget || options.browserTarget);
19-
const rawBrowserOptions = await context.getTargetOptions(buildTarget);
20-
const browserName = await context.getBuilderNameForTarget(buildTarget);
21-
const browserOptions = await context.validateOptions<json.JsonObject & BrowserBuilderOptions>(
22-
rawBrowserOptions,
23-
browserName,
18+
const rawBuilderOptions = await context.getTargetOptions(buildTarget);
19+
const builderName = await context.getBuilderNameForTarget(buildTarget);
20+
const builderOptions = await context.validateOptions<json.JsonObject & BuilderOptions>(
21+
rawBuilderOptions,
22+
builderName,
2423
);
2524
const scheduledTarget = await context.scheduleTarget(buildTarget);
2625
const result = await scheduledTarget.result;
@@ -32,20 +31,43 @@ export async function ngsscBuild(options: NgsscBuildSchema, context: BuilderCont
3231
return result;
3332
}
3433

35-
await detectVariablesAndBuildNgsscJson(options, browserOptions, context);
34+
await detectVariablesAndBuildNgsscJson(
35+
options,
36+
builderOptions,
37+
context,
38+
false,
39+
builderName !== '@angular-devkit/build-angular:application'
40+
? undefined
41+
: 'server' in builderOptions && builderOptions.server
42+
? 'server'
43+
: 'browser-only',
44+
);
45+
3646
return result;
3747
}
3848

3949
export async function detectVariablesAndBuildNgsscJson(
4050
options: NgsscBuildSchema,
41-
browserOptions: BrowserBuilderOptions,
51+
builderOptions: BuilderOptions,
4252
context: BuilderContext,
4353
multiple: boolean = false,
54+
applicationBuilderVariant: ApplicationBuilderVariant = undefined,
4455
) {
4556
const ngsscContext = await detectVariables(context, options.searchPattern);
46-
const outputPath = join(context.workspaceRoot, browserOptions.outputPath);
47-
const ngssc = buildNgssc(ngsscContext, options, browserOptions, multiple);
48-
await writeFileAsync(join(outputPath, 'ngssc.json'), JSON.stringify(ngssc, null, 2), 'utf8');
57+
let outputPath = join(context.workspaceRoot, builderOptions.outputPath);
58+
const ngssc = buildNgssc(
59+
ngsscContext,
60+
options,
61+
builderOptions,
62+
multiple,
63+
applicationBuilderVariant,
64+
);
65+
66+
const browserOutputPath = join(outputPath, 'browser');
67+
if (applicationBuilderVariant === 'browser-only' && existsSync(browserOutputPath)) {
68+
outputPath = browserOutputPath;
69+
}
70+
writeFileSync(join(outputPath, 'ngssc.json'), JSON.stringify(ngssc, null, 2), 'utf8');
4971
}
5072

5173
export async function detectVariables(
@@ -64,14 +86,14 @@ export async function detectVariables(
6486
: '**/environments/environment*.ts';
6587

6688
const detector = new VariableDetector(context.logger);
67-
const typeScriptFiles = await glob(searchPattern || defaultSearchPattern, {
89+
const typeScriptFiles = await glob.glob(searchPattern || defaultSearchPattern, {
6890
absolute: true,
6991
cwd: context.workspaceRoot,
7092
ignore: ['**/node_modules/**', '**/*.spec.ts', '**/*.d.ts'],
7193
});
7294
let ngsscContext: NgsscContext | null = null;
7395
for (const file of typeScriptFiles) {
74-
const fileContent = await readFileAsync(file, 'utf8');
96+
const fileContent = readFileSync(file, 'utf8');
7597
const innerNgsscContext = detector.detect(fileContent);
7698
if (!innerNgsscContext.variables.length) {
7799
continue;
@@ -103,22 +125,34 @@ export async function detectVariables(
103125
export function buildNgssc(
104126
ngsscContext: NgsscContext,
105127
options: NgsscBuildSchema,
106-
browserOptions?: BrowserBuilderOptions,
128+
builderOptions?: BuilderOptions,
107129
multiple: boolean = false,
130+
applicationBuilderVariant: ApplicationBuilderVariant = undefined,
108131
): Ngssc {
109132
return {
110133
environmentVariables: [
111134
...ngsscContext.variables,
112135
...(options.additionalEnvironmentVariables || []),
113136
],
114-
filePattern: options.filePattern || extractFilePattern(browserOptions?.index, multiple),
137+
filePattern:
138+
options.filePattern ||
139+
extractFilePattern(builderOptions, multiple, applicationBuilderVariant),
115140
variant: ngsscContext.variant,
116141
};
117142
}
118143

119-
function extractFilePattern(index: BrowserBuilderOptions['index'] | undefined, multiple: boolean) {
144+
function extractFilePattern(
145+
builderOptions: BuilderOptions | undefined,
146+
multiple: boolean,
147+
applicationBuilderVariant: ApplicationBuilderVariant = undefined,
148+
) {
149+
if (builderOptions && applicationBuilderVariant === 'server') {
150+
return '**/index{.,.server.}html';
151+
}
152+
153+
const index = builderOptions?.index;
120154
let result = '**/index.html';
121-
if (!index) {
155+
if (!index || typeof index === 'boolean') {
122156
return result;
123157
} else if (typeof index === 'string') {
124158
result = basename(index);

scripts/build-lib.mts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -67,14 +67,14 @@ async function finalizePackage() {
6767
}
6868

6969
const packageJsonPath = join(targetDir, 'package.json');
70-
const distPackageJson = JSON.parse(readFileSync(join(rootDir, packageJsonPath), 'utf8'));
70+
const distPackageJson = JSON.parse(readFileSync(packageJsonPath, 'utf8'));
7171
distPackageJson.sideEffects = glob
7272
.sync(['esm*/**/public_api.{mjs,js}', 'fesm*/*{ng-env,process}.{mjs,js}'], {
7373
cwd: targetDir,
7474
dotRelative: true,
7575
})
7676
.sort();
77-
writeFileSync(join(rootDir, packageJsonPath), JSON.stringify(distPackageJson, null, 2), 'utf8');
77+
writeFileSync(packageJsonPath, JSON.stringify(distPackageJson, null, 2), 'utf8');
7878
}
7979

8080
function walk(root: string | string[], fileRegex: RegExp): string[] {

test/hello-world-app/protractor.conf.js

Lines changed: 0 additions & 45 deletions
This file was deleted.

0 commit comments

Comments
 (0)