Skip to content

fix: adapt to @angular-devkit/build-angular:application builder #88

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Nov 12, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,9 @@
"@angular/forms": "^17.0.1",
"@angular/platform-browser": "^17.0.1",
"@angular/platform-browser-dynamic": "^17.0.1",
"@angular/platform-server": "^17.0.1",
"@angular/router": "^17.0.1",
"@angular/ssr": "^17.0.0",
"rxjs": "7.8.1",
"tslib": "^2.6.2",
"zone.js": "~0.14.2"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,22 +1,17 @@
import { Architect } from '@angular-devkit/architect';
import { TestProjectHost } from '@angular-devkit/architect/testing';
import { normalize, virtualFs } from '@angular-devkit/core';

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

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

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

beforeEach(async () => {
architect = undefined;
await host.initialize().toPromise();
});
afterEach(async () => host.restore().toPromise());

async function runNgsscbuild() {
architect = (await createArchitect(host.root())).architect;
async function runNgsscbuild(host: TestProjectHost) {
architect = (await createArchitect(host.root(), host)).architect;

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

function readNgsscJson(): Ngssc {
const content = virtualFs.fileBufferToString(
host.scopedSync().read(normalize('dist/ngssc.json')),
);
function readNgsscJson(host: TestProjectHost, ngsscPath = 'dist/ngssc.json'): Ngssc {
const content = virtualFs.fileBufferToString(host.scopedSync().read(normalize(ngsscPath)));

return JSON.parse(content);
}

it('should build with process variant', async () => {
const output = await runNgsscbuild();

expect(output.success).toBe(true);

const ngssc = readNgsscJson();
expect(ngssc.variant).toEqual('process');
expect(ngssc.filePattern).toEqual('index.html');
describe('@angular-devkit/build-angular:browser', () => {
beforeEach(async () => {
architect = undefined;
await legacyHost.initialize().toPromise();
});
afterEach(async () => legacyHost.restore().toPromise());

it('should build with process variant', async () => {
const output = await runNgsscbuild(legacyHost);

expect(output.success).toBe(true);

const ngssc = readNgsscJson(legacyHost);
expect(ngssc.variant).toEqual('process');
expect(ngssc.filePattern).toEqual('index.html');
});

it('should aggregate environment variables', async () => {
const expected = 'OTHER_VARIABLE';
legacyHost.replaceInFile(
'angular.json',
'"additionalEnvironmentVariables": [],',
`"additionalEnvironmentVariables": ["${expected}"],`,
);
const output = await runNgsscbuild(legacyHost);

expect(output.success).toBe(true);

const ngssc = readNgsscJson(legacyHost);
expect(ngssc.environmentVariables).toContain(expected);
});
});

it('should aggregate environment variables', async () => {
const expected = 'OTHER_VARIABLE';
host.replaceInFile(
'angular.json',
'"additionalEnvironmentVariables": [],',
`"additionalEnvironmentVariables": ["${expected}"],`,
);
const output = await runNgsscbuild();

expect(output.success).toBe(true);

const ngssc = readNgsscJson();
expect(ngssc.environmentVariables).toContain(expected);
describe('@angular-devkit/build-angular:application', () => {
beforeEach(async () => {
architect = undefined;
await applicationHost.initialize().toPromise();
});
afterEach(async () => applicationHost.restore().toPromise());

it('should build with process variant', async () => {
const output = await runNgsscbuild(applicationHost);

expect(output.success).toBe(true);

const ngssc = readNgsscJson(applicationHost);
expect(ngssc.variant).toEqual('process');
expect(ngssc.filePattern).toEqual('**/index{.,.server.}html');
});

it('should aggregate environment variables', async () => {
const expected = 'OTHER_VARIABLE';
applicationHost.replaceInFile(
'angular.json',
'"additionalEnvironmentVariables": [],',
`"additionalEnvironmentVariables": ["${expected}"],`,
);
const output = await runNgsscbuild(applicationHost);

expect(output.success).toBe(true);

const ngssc = readNgsscJson(applicationHost);
expect(ngssc.environmentVariables).toContain(expected);
});

it('should write ngssc into dist/browser directory without SSR', async () => {
applicationHost.replaceInFile(
'angular.json',
/("server":\s+"src\/main.server.ts",|"prerender":\s+true,|"ssr":\s+\{\s+"entry":\s+"server.ts"\s+\})/g,
'',
);
const output = await runNgsscbuild(applicationHost);

expect(output.success).toBe(true);

const ngssc = readNgsscJson(applicationHost, 'dist/browser/ngssc.json');
expect(ngssc.variant).toEqual('process');
expect(ngssc.filePattern).toEqual('index.html');
});
});
});
Original file line number Diff line number Diff line change
@@ -1,26 +1,25 @@
import { promises } from 'fs';
import { existsSync, readFileSync, writeFileSync } from 'fs';
import { basename, join } from 'path';
import { BuilderContext, createBuilder, targetFromTargetString } from '@angular-devkit/architect';
import { BrowserBuilderOptions } from '@angular-devkit/build-angular';
import { ApplicationBuilderOptions, BrowserBuilderOptions } from '@angular-devkit/build-angular';
import { json, JsonObject } from '@angular-devkit/core';
import { Ngssc } from 'angular-server-side-configuration';
import { glob } from 'glob';
import * as glob from 'glob';
import { Schema } from './schema';
import { VariableDetector } from './variable-detector';
import { NgsscContext } from './ngssc-context';

export type NgsscBuildSchema = Schema;

const readFileAsync = promises.readFile;
const writeFileAsync = promises.writeFile;
type BuilderOptions = ApplicationBuilderOptions | BrowserBuilderOptions;
type ApplicationBuilderVariant = undefined | 'browser-only' | 'server';

export async function ngsscBuild(options: NgsscBuildSchema, context: BuilderContext) {
const buildTarget = targetFromTargetString(options.buildTarget || options.browserTarget);
const rawBrowserOptions = await context.getTargetOptions(buildTarget);
const browserName = await context.getBuilderNameForTarget(buildTarget);
const browserOptions = await context.validateOptions<json.JsonObject & BrowserBuilderOptions>(
rawBrowserOptions,
browserName,
const rawBuilderOptions = await context.getTargetOptions(buildTarget);
const builderName = await context.getBuilderNameForTarget(buildTarget);
const builderOptions = await context.validateOptions<json.JsonObject & BuilderOptions>(
rawBuilderOptions,
builderName,
);
const scheduledTarget = await context.scheduleTarget(buildTarget);
const result = await scheduledTarget.result;
Expand All @@ -32,20 +31,43 @@ export async function ngsscBuild(options: NgsscBuildSchema, context: BuilderCont
return result;
}

await detectVariablesAndBuildNgsscJson(options, browserOptions, context);
await detectVariablesAndBuildNgsscJson(
options,
builderOptions,
context,
false,
builderName !== '@angular-devkit/build-angular:application'
? undefined
: 'server' in builderOptions && builderOptions.server
? 'server'
: 'browser-only',
);

return result;
}

export async function detectVariablesAndBuildNgsscJson(
options: NgsscBuildSchema,
browserOptions: BrowserBuilderOptions,
builderOptions: BuilderOptions,
context: BuilderContext,
multiple: boolean = false,
applicationBuilderVariant: ApplicationBuilderVariant = undefined,
) {
const ngsscContext = await detectVariables(context, options.searchPattern);
const outputPath = join(context.workspaceRoot, browserOptions.outputPath);
const ngssc = buildNgssc(ngsscContext, options, browserOptions, multiple);
await writeFileAsync(join(outputPath, 'ngssc.json'), JSON.stringify(ngssc, null, 2), 'utf8');
let outputPath = join(context.workspaceRoot, builderOptions.outputPath);
const ngssc = buildNgssc(
ngsscContext,
options,
builderOptions,
multiple,
applicationBuilderVariant,
);

const browserOutputPath = join(outputPath, 'browser');
if (applicationBuilderVariant === 'browser-only' && existsSync(browserOutputPath)) {
outputPath = browserOutputPath;
}
writeFileSync(join(outputPath, 'ngssc.json'), JSON.stringify(ngssc, null, 2), 'utf8');
}

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

const detector = new VariableDetector(context.logger);
const typeScriptFiles = await glob(searchPattern || defaultSearchPattern, {
const typeScriptFiles = await glob.glob(searchPattern || defaultSearchPattern, {
absolute: true,
cwd: context.workspaceRoot,
ignore: ['**/node_modules/**', '**/*.spec.ts', '**/*.d.ts'],
});
let ngsscContext: NgsscContext | null = null;
for (const file of typeScriptFiles) {
const fileContent = await readFileAsync(file, 'utf8');
const fileContent = readFileSync(file, 'utf8');
const innerNgsscContext = detector.detect(fileContent);
if (!innerNgsscContext.variables.length) {
continue;
Expand Down Expand Up @@ -103,22 +125,34 @@ export async function detectVariables(
export function buildNgssc(
ngsscContext: NgsscContext,
options: NgsscBuildSchema,
browserOptions?: BrowserBuilderOptions,
builderOptions?: BuilderOptions,
multiple: boolean = false,
applicationBuilderVariant: ApplicationBuilderVariant = undefined,
): Ngssc {
return {
environmentVariables: [
...ngsscContext.variables,
...(options.additionalEnvironmentVariables || []),
],
filePattern: options.filePattern || extractFilePattern(browserOptions?.index, multiple),
filePattern:
options.filePattern ||
extractFilePattern(builderOptions, multiple, applicationBuilderVariant),
variant: ngsscContext.variant,
};
}

function extractFilePattern(index: BrowserBuilderOptions['index'] | undefined, multiple: boolean) {
function extractFilePattern(
builderOptions: BuilderOptions | undefined,
multiple: boolean,
applicationBuilderVariant: ApplicationBuilderVariant = undefined,
) {
if (builderOptions && applicationBuilderVariant === 'server') {
return '**/index{.,.server.}html';
}

const index = builderOptions?.index;
let result = '**/index.html';
if (!index) {
if (!index || typeof index === 'boolean') {
return result;
} else if (typeof index === 'string') {
result = basename(index);
Expand Down
4 changes: 2 additions & 2 deletions scripts/build-lib.mts
Original file line number Diff line number Diff line change
Expand Up @@ -67,14 +67,14 @@ async function finalizePackage() {
}

const packageJsonPath = join(targetDir, 'package.json');
const distPackageJson = JSON.parse(readFileSync(join(rootDir, packageJsonPath), 'utf8'));
const distPackageJson = JSON.parse(readFileSync(packageJsonPath, 'utf8'));
distPackageJson.sideEffects = glob
.sync(['esm*/**/public_api.{mjs,js}', 'fesm*/*{ng-env,process}.{mjs,js}'], {
cwd: targetDir,
dotRelative: true,
})
.sort();
writeFileSync(join(rootDir, packageJsonPath), JSON.stringify(distPackageJson, null, 2), 'utf8');
writeFileSync(packageJsonPath, JSON.stringify(distPackageJson, null, 2), 'utf8');
}

function walk(root: string | string[], fileRegex: RegExp): string[] {
Expand Down
45 changes: 0 additions & 45 deletions test/hello-world-app/protractor.conf.js

This file was deleted.

Loading