Skip to content

Commit

Permalink
fix(@schematics/angular): enable opt-in for new @angular/ssr feature
Browse files Browse the repository at this point in the history
This commit updates several schematics to make the new `@angular/ssr` feature opt-in. Users can opt in by using the `--server-routing` option or by responding with `yes` to the prompt.
  • Loading branch information
alan-agius4 committed Oct 25, 2024
1 parent 1890fe4 commit cca9540
Show file tree
Hide file tree
Showing 31 changed files with 168 additions and 35 deletions.
4 changes: 4 additions & 0 deletions packages/angular/ssr/schematics/ng-add/schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,10 @@
"description": "Skip installing dependency packages.",
"type": "boolean",
"default": false
},
"serverRouting": {
"description": "Creates a server application using the Server Routing and App Engine APIs (Developer Preview).",
"type": "boolean"
}
},
"required": ["project"],
Expand Down
35 changes: 31 additions & 4 deletions packages/schematics/angular/app-shell/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,8 @@ import {
import { applyToUpdateRecorder } from '../utility/change';
import { getAppModulePath, isStandaloneApp } from '../utility/ng-ast-utils';
import { findBootstrapApplicationCall, getMainFilePath } from '../utility/standalone/util';
import { getWorkspace } from '../utility/workspace';
import { getWorkspace, updateWorkspace } from '../utility/workspace';
import { Builders } from '../utility/workspace-models';
import { Schema as AppShellOptions } from './schema';

const APP_SHELL_ROUTE = 'shell';
Expand Down Expand Up @@ -169,6 +170,29 @@ function getMetadataProperty(metadata: ts.Node, propertyName: string): ts.Proper
return property;
}

function addAppShellConfigToWorkspace(options: AppShellOptions): Rule {
return updateWorkspace((workspace) => {
const project = workspace.projects.get(options.project);
if (!project) {
return;
}
const buildTarget = project.targets.get('build');
if (
buildTarget?.builder === Builders.Application ||
buildTarget?.builder === Builders.BuildApplication
) {
// Application builder configuration.
const prodConfig = buildTarget.configurations?.production;
if (!prodConfig) {
throw new SchematicsException(
`A "production" configuration is not defined for the "build" builder.`,
);
}
prodConfig.appShell = true;
}
});
}

function addServerRoutes(options: AppShellOptions): Rule {
return async (host: Tree) => {
// The workspace gets updated so this needs to be reloaded
Expand Down Expand Up @@ -349,9 +373,12 @@ export default function (options: AppShellOptions): Rule {
return chain([
validateProject(browserEntryPoint),
schematic('server', options),
isStandalone ? noop() : addRouterModule(browserEntryPoint),
isStandalone ? addStandaloneServerRoute(options) : addServerRoutes(options),
addServerRoutingConfig(options),
...(isStandalone
? [addStandaloneServerRoute(options)]
: [addRouterModule(browserEntryPoint), addServerRoutes(options)]),
options.serverRouting
? addServerRoutingConfig(options)
: addAppShellConfigToWorkspace(options),
schematic('component', {
name: 'app-shell',
module: 'app.module.server.ts',
Expand Down
1 change: 1 addition & 0 deletions packages/schematics/angular/app-shell/index_spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ describe('App Shell Schematic', () => {
);
const defaultOptions: AppShellOptions = {
project: 'bar',
serverRouting: true,
};

const workspaceOptions: WorkspaceOptions = {
Expand Down
5 changes: 5 additions & 0 deletions packages/schematics/angular/app-shell/schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,11 @@
"$default": {
"$source": "projectName"
}
},
"serverRouting": {
"description": "Creates a server application using the Server Routing API (Developer Preview).",
"type": "boolean",
"default": false
}
},
"required": ["project"]
Expand Down
1 change: 1 addition & 0 deletions packages/schematics/angular/application/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,7 @@ export default function (options: ApplicationOptions): Rule {
options.ssr
? schematic('ssr', {
project: options.name,
serverRouting: options.serverRouting,
skipInstall: true,
})
: noop(),
Expand Down
4 changes: 4 additions & 0 deletions packages/schematics/angular/application/schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,10 @@
"default": false,
"x-user-analytics": "ep.ng_ssr"
},
"serverRouting": {
"description": "Creates a server application using the Server Routing and App Engine APIs (Developer Preview).",
"type": "boolean"
},
"experimentalZoneless": {
"description": "Create an application that does not utilize zone.js.",
"type": "boolean",
Expand Down
1 change: 1 addition & 0 deletions packages/schematics/angular/ng-new/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ export default function (options: NgNewOptions): Rule {
minimal: options.minimal,
standalone: options.standalone,
ssr: options.ssr,
serverRouting: options.serverRouting,
experimentalZoneless: options.experimentalZoneless,
};

Expand Down
4 changes: 4 additions & 0 deletions packages/schematics/angular/ng-new/schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,10 @@
"type": "boolean",
"x-user-analytics": "ep.ng_ssr"
},
"serverRouting": {
"description": "Creates a server application using the Server Routing and App Engine APIs (Developer Preview).",
"type": "boolean"
},
"experimentalZoneless": {
"description": "Create an application that does not utilize zone.js.",
"type": "boolean",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import { NgModule } from '@angular/core';
import { ServerModule } from '@angular/platform-server';
import { provideServerRoutesConfig } from '@angular/ssr';
import { ServerModule } from '@angular/platform-server';<% if(serverRouting) { %>
import { provideServerRoutesConfig } from '@angular/ssr';<% } %>
import { AppComponent } from './app.component';
import { AppModule } from './app.module';
import { serverRoutes } from './app.routes.server';
import { AppModule } from './app.module';<% if(serverRouting) { %>
import { serverRoutes } from './app.routes.server';<% } %>

@NgModule({
imports: [AppModule, ServerModule],
providers: [provideServerRoutesConfig(serverRoutes)],
imports: [AppModule, ServerModule],<% if(serverRouting) { %>
providers: [provideServerRoutesConfig(serverRoutes)],<% } %>
bootstrap: [AppComponent],
})
export class AppServerModule {}
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import { mergeApplicationConfig, ApplicationConfig } from '@angular/core';
import { provideServerRendering } from '@angular/platform-server';
import { provideServerRoutesConfig } from '@angular/ssr';
import { appConfig } from './app.config';
import { serverRoutes } from './app.routes.server';
import { provideServerRendering } from '@angular/platform-server';<% if(serverRouting) { %>
import { provideServerRoutesConfig } from '@angular/ssr';<% } %>
import { appConfig } from './app.config';<% if(serverRouting) { %>
import { serverRoutes } from './app.routes.server';<% } %>

const serverConfig: ApplicationConfig = {
providers: [
provideServerRendering(),
provideServerRoutesConfig(serverRoutes)
provideServerRendering(),<% if(serverRouting) { %>
provideServerRoutesConfig(serverRoutes)<% } %>
]
};

Expand Down
10 changes: 8 additions & 2 deletions packages/schematics/angular/server/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,10 @@ import {
apply,
applyTemplates,
chain,
filter,
mergeWith,
move,
renameTemplateFiles,
noop,
strings,
url,
} from '@angular-devkit/schematics';
Expand Down Expand Up @@ -112,7 +113,9 @@ function updateConfigFileApplicationBuilder(options: ServerOptions): Rule {
serverMainEntryName,
);

buildTarget.options['outputMode'] = 'static';
if (options.serverRouting) {
buildTarget.options['outputMode'] = 'static';
}
});
}

Expand Down Expand Up @@ -191,6 +194,9 @@ export default function (options: ServerOptions): Rule {
filesUrl += isStandalone ? 'standalone-src' : 'ngmodule-src';

const templateSource = apply(url(filesUrl), [
options.serverRouting
? noop()
: filter((path) => !path.endsWith('app.routes.server.ts.template')),
applyTemplates({
...strings,
...options,
Expand Down
4 changes: 4 additions & 0 deletions packages/schematics/angular/server/schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,10 @@
"description": "Do not install packages for dependencies.",
"type": "boolean",
"default": false
},
"serverRouting": {
"description": "Creates a server application using the Server Routing and App Engine APIs (Developer Preview).",
"type": "boolean"
}
},
"required": ["project"]
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import { APP_BASE_HREF } from '@angular/common';
import { CommonEngine, isMainModule } from '@angular/ssr/node';
import express from 'express';
import { dirname, join, resolve } from 'node:path';
import { fileURLToPath } from 'node:url';
import <% if (isStandalone) { %>bootstrap<% } else { %>AppServerModule<% } %> from './main.server';

const serverDistFolder = dirname(fileURLToPath(import.meta.url));
const browserDistFolder = resolve(serverDistFolder, '../<%= browserDistDirectory %>');
const indexHtml = join(serverDistFolder, 'index.server.html');

const app = express();
const commonEngine = new CommonEngine();

/**
* Example Express Rest API endpoints can be defined here.
* Uncomment and define endpoints as necessary.
*
* Example:
* ```ts
* app.get('/api/**', (req, res) => {
* // Handle API request
* });
* ```
*/

/**
* Serve static files from /<%= browserDistDirectory %>
*/
app.get(
'**',
express.static(browserDistFolder, {
maxAge: '1y',
index: 'index.html'
}),
);

/**
* Handle all other requests by rendering the Angular application.
*/
app.get('**', (req, res, next) => {
const { protocol, originalUrl, baseUrl, headers } = req;

commonEngine
.render({
<% if (isStandalone) { %>bootstrap<% } else { %>bootstrap: AppServerModule<% } %>,
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));
});

/**
* Start the server if this module is the main entry point.
* The server listens on the port defined by the `PORT` environment variable, or defaults to 4000.
*/
if (isMainModule(import.meta.url)) {
const port = process.env['PORT'] || 4000;
app.listen(port, () => {
console.log(`Node Express server listening on http://localhost:${port}`);
});
}
8 changes: 6 additions & 2 deletions packages/schematics/angular/ssr/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -201,7 +201,8 @@ function updateApplicationBuilderWorkspaceConfigRule(
buildTarget.options = {
...buildTarget.options,
outputPath,
outputMode: 'server',
outputMode: options.serverRouting ? 'server' : undefined,
prerender: options.serverRouting ? undefined : true,
ssr: {
entry: join(normalize(projectSourceRoot), 'server.ts'),
},
Expand Down Expand Up @@ -340,9 +341,12 @@ function addServerFile(
? (await getApplicationBuilderOutputPaths(host, projectName)).browser
: await getLegacyOutputPaths(host, projectName, 'build');

const applicationBuilderFiles =
'application-builder' + (options.serverRouting ? '' : '-common-engine');

return mergeWith(
apply(
url(`./files/${isUsingApplicationBuilder ? 'application-builder' : 'server-builder'}`),
url(`./files/${isUsingApplicationBuilder ? applicationBuilderFiles : 'server-builder'}`),
[
applyTemplates({
...strings,
Expand Down
6 changes: 6 additions & 0 deletions packages/schematics/angular/ssr/schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,12 @@
"description": "Skip installing dependency packages.",
"type": "boolean",
"default": false
},
"serverRouting": {
"description": "Creates a server application using the Server Routing and App Engine APIs (Developer Preview).",
"x-prompt": "Would you like to use the Server Routing and App Engine APIs (Developer Preview) for this server application?",
"type": "boolean",
"default": false
}
},
"required": ["project"],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ export default async function () {
projectName,
'--skip-confirmation',
'--skip-install',
'--server-routing',
);

await useSha();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ export default async function () {

// Forcibly remove in case another test doesn't clean itself up.
await rimraf('node_modules/@angular/ssr');
await ng('add', '@angular/ssr', '--skip-confirmation');
await ng('add', '@angular/ssr', '--server-routing', '--skip-confirmation');
await useSha();
await installWorkspacePackages();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ export default async function () {
const useWebpackBuilder = !getGlobalVariable('argv')['esbuild'];
// forcibly remove in case another test doesn't clean itself up
await rimraf('node_modules/@angular/ssr');
await ng('add', '@angular/ssr', '--skip-confirmation', '--skip-install');
await ng('add', '@angular/ssr', '--server-routing', '--skip-confirmation', '--skip-install');

await useSha();
await installWorkspacePackages();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { updateJsonFile, updateServerFileForWebpack, useSha } from '../../../uti
export default async function () {
// forcibly remove in case another test doesn't clean itself up
await rimraf('node_modules/@angular/ssr');
await ng('add', '@angular/ssr', '--skip-confirmation', '--skip-install');
await ng('add', '@angular/ssr', '--server-routing', '--skip-confirmation', '--skip-install');

const useWebpackBuilder = !getGlobalVariable('argv')['esbuild'];
if (!useWebpackBuilder) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ export default async function () {

// Forcibly remove in case another test doesn't clean itself up.
await uninstallPackage('@angular/ssr');
await ng('add', '@angular/ssr', '--skip-confirmation', '--skip-install');
await ng('add', '@angular/ssr', '--server-routing', '--skip-confirmation', '--skip-install');
await useSha();
await installWorkspacePackages();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ export default async function () {

// Forcibly remove in case another test doesn't clean itself up.
await uninstallPackage('@angular/ssr');
await ng('add', '@angular/ssr', '--skip-confirmation', '--skip-install');
await ng('add', '@angular/ssr', '--server-routing', '--skip-confirmation', '--skip-install');
await useSha();
await installWorkspacePackages();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ export default async function () {

// Forcibly remove in case another test doesn't clean itself up.
await uninstallPackage('@angular/ssr');
await ng('add', '@angular/ssr', '--skip-confirmation', '--skip-install');
await ng('add', '@angular/ssr', '--server-routing', '--skip-confirmation', '--skip-install');
await useSha();
await installWorkspacePackages();
await installPackage('h3@1');
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ export default async function () {

// Forcibly remove in case another test doesn't clean itself up.
await uninstallPackage('@angular/ssr');
await ng('add', '@angular/ssr', '--skip-confirmation', '--skip-install');
await ng('add', '@angular/ssr', '--server-routing', '--skip-confirmation', '--skip-install');
await useSha();
await installWorkspacePackages();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ export default async function () {

// Forcibly remove in case another test doesn't clean itself up.
await uninstallPackage('@angular/ssr');
await ng('add', '@angular/ssr', '--skip-confirmation', '--skip-install');
await ng('add', '@angular/ssr', '--server-routing', '--skip-confirmation', '--skip-install');
await useSha();
await installWorkspacePackages();

Expand Down
Loading

0 comments on commit cca9540

Please sign in to comment.