Skip to content
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
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