From 9a876041851c7241ac96f58c7a670f9129efadcd Mon Sep 17 00:00:00 2001 From: Lukas Spirig Date: Sun, 12 Nov 2023 21:28:51 +0100 Subject: [PATCH] fix: adapt to `@angular-devkit/build-angular:application` builder --- package.json | 2 + .../builders/ngsscbuild/index.spec.ts | 117 ++++-- .../builders/ngsscbuild/index.ts | 78 ++-- scripts/build-lib.mts | 4 +- test/hello-world-app/protractor.conf.js | 45 --- test/ng-application-app/angular.json | 118 ++++++ test/ng-application-app/server.ts | 56 +++ .../src/app/app.component.html | 336 ++++++++++++++++++ .../src/app/app.component.scss | 0 .../src/app/app.component.spec.ts | 29 ++ .../src/app/app.component.ts | 14 + .../src/app/app.config.server.ts | 11 + test/ng-application-app/src/app/app.config.ts | 27 ++ test/ng-application-app/src/app/app.routes.ts | 3 + test/ng-application-app/src/assets/.gitkeep | 0 test/ng-application-app/src/favicon.ico | Bin 0 -> 15086 bytes test/ng-application-app/src/index.html | 14 + test/ng-application-app/src/main.server.ts | 7 + test/ng-application-app/src/main.ts | 6 + test/ng-application-app/src/styles.scss | 1 + test/ng-application-app/tsconfig.app.json | 18 + test/ng-application-app/tsconfig.json | 34 ++ test/ng-application-app/tsconfig.spec.json | 14 + test/test-utils.ts | 8 +- yarn.lock | 21 ++ 25 files changed, 857 insertions(+), 106 deletions(-) delete mode 100644 test/hello-world-app/protractor.conf.js create mode 100644 test/ng-application-app/angular.json create mode 100644 test/ng-application-app/server.ts create mode 100644 test/ng-application-app/src/app/app.component.html create mode 100644 test/ng-application-app/src/app/app.component.scss create mode 100644 test/ng-application-app/src/app/app.component.spec.ts create mode 100644 test/ng-application-app/src/app/app.component.ts create mode 100644 test/ng-application-app/src/app/app.config.server.ts create mode 100644 test/ng-application-app/src/app/app.config.ts create mode 100644 test/ng-application-app/src/app/app.routes.ts create mode 100644 test/ng-application-app/src/assets/.gitkeep create mode 100644 test/ng-application-app/src/favicon.ico create mode 100644 test/ng-application-app/src/index.html create mode 100644 test/ng-application-app/src/main.server.ts create mode 100644 test/ng-application-app/src/main.ts create mode 100644 test/ng-application-app/src/styles.scss create mode 100644 test/ng-application-app/tsconfig.app.json create mode 100644 test/ng-application-app/tsconfig.json create mode 100644 test/ng-application-app/tsconfig.spec.json diff --git a/package.json b/package.json index 72b9ff4..fcd5b6d 100644 --- a/package.json +++ b/package.json @@ -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" diff --git a/projects/angular-server-side-configuration/builders/ngsscbuild/index.spec.ts b/projects/angular-server-side-configuration/builders/ngsscbuild/index.spec.ts index bb3406c..4b6d1f8 100644 --- a/projects/angular-server-side-configuration/builders/ngsscbuild/index.spec.ts +++ b/projects/angular-server-side-configuration/builders/ngsscbuild/index.spec.ts @@ -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); @@ -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'); + }); }); }); diff --git a/projects/angular-server-side-configuration/builders/ngsscbuild/index.ts b/projects/angular-server-side-configuration/builders/ngsscbuild/index.ts index 09bc397..c0c8e0b 100644 --- a/projects/angular-server-side-configuration/builders/ngsscbuild/index.ts +++ b/projects/angular-server-side-configuration/builders/ngsscbuild/index.ts @@ -1,26 +1,25 @@ -import { promises } from 'fs'; +import { 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( - rawBrowserOptions, - browserName, + const rawBuilderOptions = await context.getTargetOptions(buildTarget); + const builderName = await context.getBuilderNameForTarget(buildTarget); + const builderOptions = await context.validateOptions( + rawBuilderOptions, + builderName, ); const scheduledTarget = await context.scheduleTarget(buildTarget); const result = await scheduledTarget.result; @@ -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') { + outputPath = browserOutputPath; + } + writeFileSync(join(outputPath, 'ngssc.json'), JSON.stringify(ngssc, null, 2), 'utf8'); } export async function detectVariables( @@ -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; @@ -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); diff --git a/scripts/build-lib.mts b/scripts/build-lib.mts index 911354e..3ed4b7d 100644 --- a/scripts/build-lib.mts +++ b/scripts/build-lib.mts @@ -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[] { diff --git a/test/hello-world-app/protractor.conf.js b/test/hello-world-app/protractor.conf.js deleted file mode 100644 index 772600f..0000000 --- a/test/hello-world-app/protractor.conf.js +++ /dev/null @@ -1,45 +0,0 @@ -/** - * @license - * Copyright Google LLC All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.io/license - */ - -// Protractor configuration file, see link for more information -// https://github.com/angular/protractor/blob/master/lib/config.ts - -const { SpecReporter, StacktraceOption } = require('jasmine-spec-reporter'); -const { resolve } = require('path'); - -exports.config = { - allScriptsTimeout: 11000, - specs: ['./e2e/**/*.e2e-spec.ts'], - capabilities: { - browserName: 'chrome', - chromeOptions: { - args: ['--headless', '--disable-gpu', '--window-size=800,600'], - binary: require('puppeteer').executablePath(), - }, - }, - directConnect: true, - baseUrl: 'http://localhost:4200/', - framework: 'jasmine', - jasmineNodeOpts: { - showColors: true, - defaultTimeoutInterval: 30000, - print: function () {}, - }, - onPrepare() { - require('ts-node').register({ - project: resolve(__dirname, './e2e/tsconfig.e2e.json'), - }); - jasmine.getEnv().addReporter( - new SpecReporter({ - spec: { - displayStacktrace: StacktraceOption.PRETTY, - }, - }), - ); - }, -}; diff --git a/test/ng-application-app/angular.json b/test/ng-application-app/angular.json new file mode 100644 index 0000000..6b8867c --- /dev/null +++ b/test/ng-application-app/angular.json @@ -0,0 +1,118 @@ +{ + "$schema": "./node_modules/@angular/cli/lib/config/schema.json", + "version": 1, + "newProjectRoot": "projects", + "projects": { + "app": { + "projectType": "application", + "schematics": { + "@schematics/angular:component": { + "style": "scss" + } + }, + "root": "", + "sourceRoot": "src", + "prefix": "app", + "architect": { + "build": { + "builder": "@angular-devkit/build-angular:application", + "options": { + "outputPath": "dist", + "index": "src/index.html", + "browser": "src/main.ts", + "polyfills": [ + "zone.js" + ], + "tsConfig": "tsconfig.app.json", + "inlineStyleLanguage": "scss", + "assets": [ + "src/favicon.ico", + "src/assets" + ], + "styles": [ + "src/styles.scss" + ], + "scripts": [], + "server": "src/main.server.ts", + "prerender": true, + "ssr": { + "entry": "server.ts" + } + }, + "configurations": { + "production": { + "budgets": [ + { + "type": "initial", + "maximumWarning": "500kb", + "maximumError": "1mb" + }, + { + "type": "anyComponentStyle", + "maximumWarning": "2kb", + "maximumError": "4kb" + } + ], + "outputHashing": "all" + }, + "development": { + "optimization": false, + "extractLicenses": false, + "sourceMap": true + } + }, + "defaultConfiguration": "production" + }, + "serve": { + "builder": "@angular-devkit/build-angular:dev-server", + "configurations": { + "production": { + "buildTarget": "app:build:production" + }, + "development": { + "buildTarget": "app:build:development" + } + }, + "defaultConfiguration": "development" + }, + "extract-i18n": { + "builder": "@angular-devkit/build-angular:extract-i18n", + "options": { + "buildTarget": "app:build" + } + }, + "test": { + "builder": "@angular-devkit/build-angular:karma", + "options": { + "polyfills": [ + "zone.js", + "zone.js/testing" + ], + "tsConfig": "tsconfig.spec.json", + "inlineStyleLanguage": "scss", + "assets": [ + "src/favicon.ico", + "src/assets" + ], + "styles": [ + "src/styles.scss" + ], + "scripts": [] + } + }, + "ngsscbuild": { + "builder": "angular-server-side-configuration:ngsscbuild", + "options": { + "additionalEnvironmentVariables": [], + "buildTarget": "app:build" + }, + "configurations": { + "production": { + "buildTarget": "app:build:production" + } + } + } + } + } + } +} diff --git a/test/ng-application-app/server.ts b/test/ng-application-app/server.ts new file mode 100644 index 0000000..7083b14 --- /dev/null +++ b/test/ng-application-app/server.ts @@ -0,0 +1,56 @@ +import { APP_BASE_HREF } from '@angular/common'; +import { CommonEngine } from '@angular/ssr'; +import express from 'express'; +import { fileURLToPath } from 'node:url'; +import { dirname, join, resolve } from 'node:path'; +import bootstrap from './src/main.server'; + +// The Express app is exported so that it can be used by serverless Functions. +export function app(): express.Express { + const server = express(); + const serverDistFolder = dirname(fileURLToPath(import.meta.url)); + const browserDistFolder = resolve(serverDistFolder, '../browser'); + const indexHtml = join(serverDistFolder, 'index.server.html'); + + const commonEngine = new CommonEngine(); + + server.set('view engine', 'html'); + server.set('views', browserDistFolder); + + // Example Express Rest API endpoints + // server.get('/api/**', (req, res) => { }); + // Serve static files from /browser + server.get('*.*', express.static(browserDistFolder, { + maxAge: '1y' + })); + + // All regular routes use the Angular engine + server.get('*', (req, res, next) => { + const { protocol, originalUrl, baseUrl, headers } = req; + + commonEngine + .render({ + bootstrap, + documentFilePath: indexHtml, + url: `${protocol}://${headers.host}${originalUrl}`, + publicPath: browserDistFolder, + providers: [{ provide: APP_BASE_HREF, useValue: baseUrl }], + }) + .then((html) => res.send(html)) + .catch((err) => next(err)); + }); + + return server; +} + +function run(): void { + const port = process.env['PORT'] || 4000; + + // Start up the Node server + const server = app(); + server.listen(port, () => { + console.log(`Node Express server listening on http://localhost:${port}`); + }); +} + +run(); diff --git a/test/ng-application-app/src/app/app.component.html b/test/ng-application-app/src/app/app.component.html new file mode 100644 index 0000000..390d3bf --- /dev/null +++ b/test/ng-application-app/src/app/app.component.html @@ -0,0 +1,336 @@ + + + + + + + + + + + +
+
+
+ +

Hello, {{ title }}

+

Congratulations! Your app is running. 🎉

+
+ +
+
+ @for (item of [ + { title: 'Explore the Docs', link: 'https://angular.dev' }, + { title: 'Learn with Tutorials', link: 'https://angular.dev/tutorials' }, + { title: 'CLI Docs', link: 'https://angular.dev/tools/cli' }, + { title: 'Angular Language Service', link: 'https://angular.dev/tools/language-service' }, + { title: 'Angular DevTools', link: 'https://angular.dev/tools/devtools' }, + ]; track item.title) { + + {{ item.title }} + + + + + } +
+ +
+
+
+ + + + + + + + + + + diff --git a/test/ng-application-app/src/app/app.component.scss b/test/ng-application-app/src/app/app.component.scss new file mode 100644 index 0000000..e69de29 diff --git a/test/ng-application-app/src/app/app.component.spec.ts b/test/ng-application-app/src/app/app.component.spec.ts new file mode 100644 index 0000000..3daae1a --- /dev/null +++ b/test/ng-application-app/src/app/app.component.spec.ts @@ -0,0 +1,29 @@ +import { TestBed } from '@angular/core/testing'; +import { AppComponent } from './app.component'; + +describe('AppComponent', () => { + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [AppComponent], + }).compileComponents(); + }); + + it('should create the app', () => { + const fixture = TestBed.createComponent(AppComponent); + const app = fixture.componentInstance; + expect(app).toBeTruthy(); + }); + + it(`should have the 'ng17' title`, () => { + const fixture = TestBed.createComponent(AppComponent); + const app = fixture.componentInstance; + expect(app.title).toEqual('ng17'); + }); + + it('should render title', () => { + const fixture = TestBed.createComponent(AppComponent); + fixture.detectChanges(); + const compiled = fixture.nativeElement as HTMLElement; + expect(compiled.querySelector('h1')?.textContent).toContain('Hello, ng17'); + }); +}); diff --git a/test/ng-application-app/src/app/app.component.ts b/test/ng-application-app/src/app/app.component.ts new file mode 100644 index 0000000..3b8cc02 --- /dev/null +++ b/test/ng-application-app/src/app/app.component.ts @@ -0,0 +1,14 @@ +import { Component } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { RouterOutlet } from '@angular/router'; + +@Component({ + selector: 'app-root', + standalone: true, + imports: [CommonModule, RouterOutlet], + templateUrl: './app.component.html', + styleUrls: ['./app.component.scss'] +}) +export class AppComponent { + title = 'ng17'; +} diff --git a/test/ng-application-app/src/app/app.config.server.ts b/test/ng-application-app/src/app/app.config.server.ts new file mode 100644 index 0000000..b4d57c9 --- /dev/null +++ b/test/ng-application-app/src/app/app.config.server.ts @@ -0,0 +1,11 @@ +import { mergeApplicationConfig, ApplicationConfig } from '@angular/core'; +import { provideServerRendering } from '@angular/platform-server'; +import { appConfig } from './app.config'; + +const serverConfig: ApplicationConfig = { + providers: [ + provideServerRendering() + ] +}; + +export const config = mergeApplicationConfig(appConfig, serverConfig); diff --git a/test/ng-application-app/src/app/app.config.ts b/test/ng-application-app/src/app/app.config.ts new file mode 100644 index 0000000..33c1e28 --- /dev/null +++ b/test/ng-application-app/src/app/app.config.ts @@ -0,0 +1,27 @@ +import 'angular-server-side-configuration/process'; + +/** + * How to use angular-server-side-configuration: + * + * Use process.env['NAME_OF_YOUR_ENVIRONMENT_VARIABLE'] + * + * const stringValue = process.env['STRING_VALUE']; + * const stringValueWithDefault = process.env['STRING_VALUE'] || 'defaultValue'; + * const numberValue = Number(process.env['NUMBER_VALUE']); + * const numberValueWithDefault = Number(process.env['NUMBER_VALUE'] || 10); + * const booleanValue = process.env['BOOLEAN_VALUE'] === 'true'; + * const booleanValueInverted = process.env['BOOLEAN_VALUE_INVERTED'] !== 'false'; + * const complexValue = JSON.parse(process.env['COMPLEX_JSON_VALUE]); + * + * Please note that process.env[variable] cannot be resolved. Please directly use strings. + */ + +import { ApplicationConfig } from '@angular/core'; +import { provideRouter } from '@angular/router'; + +import { routes } from './app.routes'; +import { provideClientHydration } from '@angular/platform-browser'; + +export const appConfig: ApplicationConfig = { + providers: [provideRouter(routes), provideClientHydration()] +}; diff --git a/test/ng-application-app/src/app/app.routes.ts b/test/ng-application-app/src/app/app.routes.ts new file mode 100644 index 0000000..dc39edb --- /dev/null +++ b/test/ng-application-app/src/app/app.routes.ts @@ -0,0 +1,3 @@ +import { Routes } from '@angular/router'; + +export const routes: Routes = []; diff --git a/test/ng-application-app/src/assets/.gitkeep b/test/ng-application-app/src/assets/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/test/ng-application-app/src/favicon.ico b/test/ng-application-app/src/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..57614f9c967596fad0a3989bec2b1deff33034f6 GIT binary patch literal 15086 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 Nr__JTFzvMr^FKp4S3v*( literal 0 HcmV?d00001 diff --git a/test/ng-application-app/src/index.html b/test/ng-application-app/src/index.html new file mode 100644 index 0000000..7f24c33 --- /dev/null +++ b/test/ng-application-app/src/index.html @@ -0,0 +1,14 @@ + + + + + Ng17 + + + + + + + + + diff --git a/test/ng-application-app/src/main.server.ts b/test/ng-application-app/src/main.server.ts new file mode 100644 index 0000000..4b9d4d1 --- /dev/null +++ b/test/ng-application-app/src/main.server.ts @@ -0,0 +1,7 @@ +import { bootstrapApplication } from '@angular/platform-browser'; +import { AppComponent } from './app/app.component'; +import { config } from './app/app.config.server'; + +const bootstrap = () => bootstrapApplication(AppComponent, config); + +export default bootstrap; diff --git a/test/ng-application-app/src/main.ts b/test/ng-application-app/src/main.ts new file mode 100644 index 0000000..35b00f3 --- /dev/null +++ b/test/ng-application-app/src/main.ts @@ -0,0 +1,6 @@ +import { bootstrapApplication } from '@angular/platform-browser'; +import { appConfig } from './app/app.config'; +import { AppComponent } from './app/app.component'; + +bootstrapApplication(AppComponent, appConfig) + .catch((err) => console.error(err)); diff --git a/test/ng-application-app/src/styles.scss b/test/ng-application-app/src/styles.scss new file mode 100644 index 0000000..90d4ee0 --- /dev/null +++ b/test/ng-application-app/src/styles.scss @@ -0,0 +1 @@ +/* You can add global styles to this file, and also import other style files */ diff --git a/test/ng-application-app/tsconfig.app.json b/test/ng-application-app/tsconfig.app.json new file mode 100644 index 0000000..7dc7284 --- /dev/null +++ b/test/ng-application-app/tsconfig.app.json @@ -0,0 +1,18 @@ +/* To learn more about this file see: https://angular.io/config/tsconfig. */ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "outDir": "./out-tsc/app", + "types": [ + "node" + ] + }, + "files": [ + "src/main.ts", + "src/main.server.ts", + "server.ts" + ], + "include": [ + "src/**/*.d.ts" + ] +} diff --git a/test/ng-application-app/tsconfig.json b/test/ng-application-app/tsconfig.json new file mode 100644 index 0000000..ee5a3cc --- /dev/null +++ b/test/ng-application-app/tsconfig.json @@ -0,0 +1,34 @@ +/* To learn more about this file see: https://angular.io/config/tsconfig. */ +{ + "compileOnSave": false, + "compilerOptions": { + "outDir": "./dist/out-tsc", + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitOverride": true, + "noPropertyAccessFromIndexSignature": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true, + "esModuleInterop": true, + "sourceMap": true, + "declaration": false, + "downlevelIteration": true, + "experimentalDecorators": true, + "moduleResolution": "node", + "importHelpers": true, + "target": "ES2022", + "module": "ES2022", + "useDefineForClassFields": false, + "lib": ["ES2022", "dom"], + "paths": { + "angular-server-side-configuration": ["../../dist/angular-server-side-configuration"], + "angular-server-side-configuration/*": ["../../dist/angular-server-side-configuration/*"] + } + }, + "angularCompilerOptions": { + "enableI18nLegacyMessageIdFormat": false, + "strictInjectionParameters": true, + "strictInputAccessModifiers": true, + "strictTemplates": true + } +} diff --git a/test/ng-application-app/tsconfig.spec.json b/test/ng-application-app/tsconfig.spec.json new file mode 100644 index 0000000..be7e9da --- /dev/null +++ b/test/ng-application-app/tsconfig.spec.json @@ -0,0 +1,14 @@ +/* To learn more about this file see: https://angular.io/config/tsconfig. */ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "outDir": "./out-tsc/spec", + "types": [ + "jasmine" + ] + }, + "include": [ + "src/**/*.spec.ts", + "src/**/*.d.ts" + ] +} diff --git a/test/test-utils.ts b/test/test-utils.ts index 7362725..b51d374 100644 --- a/test/test-utils.ts +++ b/test/test-utils.ts @@ -14,8 +14,10 @@ import { Path, getSystemPath, join, normalize, schema, workspaces } from '@angul // Default timeout for large specs is 2.5 minutes. jasmine.DEFAULT_TIMEOUT_INTERVAL = 150000; -export const workspaceRoot = join(normalize(__dirname), `hello-world-app/`); -export const host = new TestProjectHost(workspaceRoot); +export const legacyWorkspaceRoot = join(normalize(__dirname), `hello-world-app/`); +export const legacyHost = new TestProjectHost(legacyWorkspaceRoot); +export const applicationWorkspaceRoot = join(normalize(__dirname), `ng-application-app/`); +export const applicationHost = new TestProjectHost(applicationWorkspaceRoot); export const outputPath: Path = normalize('dist'); export const browserTargetSpec = { project: 'app', target: 'build' }; @@ -25,7 +27,7 @@ export const karmaTargetSpec = { project: 'app', target: 'test' }; export const tslintTargetSpec = { project: 'app', target: 'lint' }; export const protractorTargetSpec = { project: 'app-e2e', target: 'e2e' }; -export async function createArchitect(workspaceRoot: Path) { +export async function createArchitect(workspaceRoot: Path, host: TestProjectHost) { const registry = new schema.CoreSchemaRegistry(); registry.addPostTransform(schema.transforms.addUndefinedDefaults); const workspaceSysPath = getSystemPath(workspaceRoot); diff --git a/yarn.lock b/yarn.lock index cd5e435..afddc2d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -285,6 +285,14 @@ dependencies: tslib "^2.3.0" +"@angular/platform-server@^17.0.1": + version "17.0.2" + resolved "https://registry.yarnpkg.com/@angular/platform-server/-/platform-server-17.0.2.tgz#1055867181b84ab3e6119a3587f90dff4b4dbb92" + integrity sha512-+1uCnAw7Ql2r4BDnfaRvQrXI1H5qUB/1f8CwCjaVoIn7kLJs/ps4I0WbOVtujJ2VPnxIggfVtenXRRMlungZlg== + dependencies: + tslib "^2.3.0" + xhr2 "^0.2.0" + "@angular/router@^17.0.1": version "17.0.1" resolved "https://registry.yarnpkg.com/@angular/router/-/router-17.0.1.tgz#c9807df1f705bc48b7ef4d9bd0801f45f59ed082" @@ -292,6 +300,14 @@ dependencies: tslib "^2.3.0" +"@angular/ssr@^17.0.0": + version "17.0.0" + resolved "https://registry.yarnpkg.com/@angular/ssr/-/ssr-17.0.0.tgz#99694e9a602ab87e64415b8bbf35597551140d31" + integrity sha512-yctZuIR9AA9aaAQe6JzBNbBoRUy37449tYYpwdxmI1Fp5rsrzrlJ1HeFidrmca4k3RWXw3VZNpklPWStlZBm2Q== + dependencies: + critters "0.0.20" + tslib "^2.3.0" + "@assemblyscript/loader@^0.10.1": version "0.10.1" resolved "https://registry.yarnpkg.com/@assemblyscript/loader/-/loader-0.10.1.tgz#70e45678f06c72fa2e350e8553ec4a4d72b92e06" @@ -9088,6 +9104,11 @@ ws@~8.11.0: resolved "https://registry.yarnpkg.com/ws/-/ws-8.11.0.tgz#6a0d36b8edfd9f96d8b25683db2f8d7de6e8e143" integrity sha512-HPG3wQd9sNQoT9xHyNCXoDUa+Xw/VevmY9FoHyQ+g+rrMn4j6FB4np7Z0OhdTgjx6MgQLK7jwSy1YecU1+4Asg== +xhr2@^0.2.0: + version "0.2.1" + resolved "https://registry.yarnpkg.com/xhr2/-/xhr2-0.2.1.tgz#4e73adc4f9cfec9cbd2157f73efdce3a5f108a93" + integrity sha512-sID0rrVCqkVNUn8t6xuv9+6FViXjUVXq8H5rWOH2rz9fDNQEd4g0EA2XlcEdJXRz5BMEn4O1pJFdT+z4YHhoWw== + xmlhttprequest-ssl@~2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/xmlhttprequest-ssl/-/xmlhttprequest-ssl-2.0.0.tgz#91360c86b914e67f44dce769180027c0da618c67"