Skip to content
Closed
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
88 changes: 23 additions & 65 deletions packages/schematics/angular/app-shell/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -141,74 +141,32 @@ function validateProject(mainPath: string): Rule {
}

function addAppShellConfigToWorkspace(options: AppShellOptions): Rule {
return (host, context) => {
return updateWorkspace((workspace) => {
const project = workspace.projects.get(options.project);
if (!project) {
return;
}

const buildTarget = project.targets.get('build');
if (buildTarget?.builder === Builders.Application) {
// 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;
return updateWorkspace((workspace) => {
const project = workspace.projects.get(options.project);
if (!project) {
return;
}

return;
}
const buildTarget = project.targets.get('build');
if (
buildTarget?.builder !== Builders.Application &&
buildTarget?.builder !== Builders.BuildApplication
) {
throw new SchematicsException(
`App-shell schematic requires the project to use "${Builders.Application}" or "${Builders.BuildApplication}" as the build builder.`,
);
}

// Webpack based builders configuration.
// Validation of targets is handled already in the main function.
// Duplicate keys means that we have configurations in both server and build builders.
const serverConfigKeys = project.targets.get('server')?.configurations ?? {};
const buildConfigKeys = project.targets.get('build')?.configurations ?? {};

const configurationNames = Object.keys({
...serverConfigKeys,
...buildConfigKeys,
});

const configurations: Record<string, {}> = {};
for (const key of configurationNames) {
if (!serverConfigKeys[key]) {
context.logger.warn(
`Skipped adding "${key}" configuration to "app-shell" target as it's missing from "server" target.`,
);

continue;
}

if (!buildConfigKeys[key]) {
context.logger.warn(
`Skipped adding "${key}" configuration to "app-shell" target as it's missing from "build" target.`,
);

continue;
}

configurations[key] = {
browserTarget: `${options.project}:build:${key}`,
serverTarget: `${options.project}:server:${key}`,
};
}
// Application builder configuration.
const prodConfig = buildTarget.configurations?.production;
if (!prodConfig) {
throw new SchematicsException(
`A "production" configuration is not defined for the "build" builder.`,
);
}

project.targets.add({
name: 'app-shell',
builder: Builders.AppShell,
defaultConfiguration: configurations['production'] ? 'production' : undefined,
options: {
route: APP_SHELL_ROUTE,
},
configurations,
});
});
};
prodConfig.appShell = true;
});
}

function addRouterModule(mainPath: string): Rule {
Expand Down
40 changes: 0 additions & 40 deletions packages/schematics/angular/app-shell/index_spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -240,44 +240,4 @@ describe('App Shell Schematic', () => {
);
});
});

describe('Legacy browser builder', () => {
function convertBuilderToLegacyBrowser(): void {
const config = JSON.parse(appTree.readContent('/angular.json'));
const build = config.projects.bar.architect.build;

build.builder = Builders.Browser;
build.options = {
...build.options,
main: build.options.browser,
browser: undefined,
};

build.configurations.development = {
...build.configurations.development,
vendorChunk: true,
namedChunks: true,
buildOptimizer: false,
};

appTree.overwrite('/angular.json', JSON.stringify(config, undefined, 2));
}

beforeEach(async () => {
appTree = await schematicRunner.runSchematic('application', appOptions, appTree);
convertBuilderToLegacyBrowser();
});

it('should add app shell configuration', async () => {
const tree = await schematicRunner.runSchematic('app-shell', defaultOptions, appTree);
const filePath = '/angular.json';
const content = tree.readContent(filePath);
const workspace = JSON.parse(content);
const target = workspace.projects.bar.architect['app-shell'];
expect(target.configurations.development.browserTarget).toEqual('bar:build:development');
expect(target.configurations.development.serverTarget).toEqual('bar:server:development');
expect(target.configurations.production.browserTarget).toEqual('bar:build:production');
expect(target.configurations.production.serverTarget).toEqual('bar:server:production');
});
});
});
39 changes: 0 additions & 39 deletions packages/schematics/angular/environments/index_spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -153,43 +153,4 @@ describe('Environments Schematic', () => {
}),
);
});

it('should update the angular.json file replacements option for server configurations', async () => {
convertBuilderToLegacyBrowser();

await schematicRunner.runSchematic(
'server',
{ project: 'foo', skipInstall: true },
applicationTree,
);

const tree = await runEnvironmentsSchematic();
const workspace = JSON.parse(tree.readContent('/angular.json'));

const developmentConfiguration =
workspace.projects.foo.architect.build.configurations.development;
expect(developmentConfiguration).toEqual(
jasmine.objectContaining({
fileReplacements: [
{
replace: 'projects/foo/src/environments/environment.ts',
with: 'projects/foo/src/environments/environment.development.ts',
},
],
}),
);

const serverDevelopmentConfiguration =
workspace.projects.foo.architect.server.configurations.development;
expect(serverDevelopmentConfiguration).toEqual(
jasmine.objectContaining({
fileReplacements: [
{
replace: 'projects/foo/src/environments/environment.ts',
with: 'projects/foo/src/environments/environment.development.ts',
},
],
}),
);
});
});

This file was deleted.

100 changes: 11 additions & 89 deletions packages/schematics/angular/server/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
* found in the LICENSE file at https://angular.dev/license
*/

import { JsonValue, Path, basename, dirname, join, normalize } from '@angular-devkit/core';
import { join, normalize } from '@angular-devkit/core';
import {
Rule,
SchematicsException,
Expand All @@ -25,7 +25,6 @@ import { getPackageJsonDependency } from '../utility/dependencies';
import { JSONFile } from '../utility/json-file';
import { latestVersions } from '../utility/latest-versions';
import { isStandaloneApp } from '../utility/ng-ast-utils';
import { relativePathToWorkspaceRoot } from '../utility/paths';
import { targetBuildNotFoundError } from '../utility/project-targets';
import { getMainFilePath } from '../utility/standalone/util';
import { getWorkspace, updateWorkspace } from '../utility/workspace';
Expand All @@ -34,65 +33,6 @@ import { Schema as ServerOptions } from './schema';

const serverMainEntryName = 'main.server.ts';

function updateConfigFileBrowserBuilder(options: ServerOptions, tsConfigDirectory: Path): Rule {
return updateWorkspace((workspace) => {
const clientProject = workspace.projects.get(options.project);

if (clientProject) {
// In case the browser builder hashes the assets
// we need to add this setting to the server builder
// as otherwise when assets it will be requested twice.
// One for the server which will be unhashed, and other on the client which will be hashed.
const getServerOptions = (options: Record<string, JsonValue | undefined> = {}): {} => {
return {
buildOptimizer: options?.buildOptimizer,
outputHashing: options?.outputHashing === 'all' ? 'media' : options?.outputHashing,
fileReplacements: options?.fileReplacements,
optimization: options?.optimization === undefined ? undefined : !!options?.optimization,
sourceMap: options?.sourceMap,
localization: options?.localization,
stylePreprocessorOptions: options?.stylePreprocessorOptions,
resourcesOutputPath: options?.resourcesOutputPath,
deployUrl: options?.deployUrl,
i18nMissingTranslation: options?.i18nMissingTranslation,
preserveSymlinks: options?.preserveSymlinks,
extractLicenses: options?.extractLicenses,
inlineStyleLanguage: options?.inlineStyleLanguage,
vendorChunk: options?.vendorChunk,
};
};

const buildTarget = clientProject.targets.get('build');
if (buildTarget?.options) {
buildTarget.options.outputPath = `dist/${options.project}/browser`;
}

const buildConfigurations = buildTarget?.configurations;
const configurations: Record<string, {}> = {};
if (buildConfigurations) {
for (const [key, options] of Object.entries(buildConfigurations)) {
configurations[key] = getServerOptions(options);
}
}

const sourceRoot = clientProject.sourceRoot ?? join(normalize(clientProject.root), 'src');
const serverTsConfig = join(tsConfigDirectory, 'tsconfig.server.json');
clientProject.targets.add({
name: 'server',
builder: Builders.Server,
defaultConfiguration: 'production',
options: {
outputPath: `dist/${options.project}/server`,
main: join(normalize(sourceRoot), serverMainEntryName),
tsConfig: serverTsConfig,
...(buildTarget?.options ? getServerOptions(buildTarget?.options) : {}),
},
configurations,
});
}
});
}

function updateConfigFileApplicationBuilder(options: ServerOptions): Rule {
return updateWorkspace((workspace) => {
const project = workspace.projects.get(options.project);
Expand Down Expand Up @@ -169,11 +109,16 @@ export default function (options: ServerOptions): Rule {
throw targetBuildNotFoundError();
}

const isUsingApplicationBuilder = clientBuildTarget.builder === Builders.Application;
if (
clientProject.targets.has('server') ||
(isUsingApplicationBuilder && clientBuildTarget.options?.server !== undefined)
clientBuildTarget?.builder !== Builders.Application &&
clientBuildTarget?.builder !== Builders.BuildApplication
) {
throw new SchematicsException(
`Ssr schematic requires the project to use "${Builders.Application}" or "${Builders.BuildApplication}" as the build builder.`,
);
}

if (clientBuildTarget.options?.server) {
// Server has already been added.
return;
}
Expand All @@ -190,33 +135,10 @@ export default function (options: ServerOptions): Rule {
move(join(normalize(clientProject.root), 'src')),
]);

const clientTsConfig = normalize(clientBuildOptions.tsConfig);
const tsConfigExtends = basename(clientTsConfig);
const tsConfigDirectory = dirname(clientTsConfig);

return chain([
mergeWith(templateSource),
...(isUsingApplicationBuilder
? [
updateConfigFileApplicationBuilder(options),
updateTsConfigFile(clientBuildOptions.tsConfig),
]
: [
mergeWith(
apply(url('./files/root'), [
applyTemplates({
...strings,
...options,
stripTsExtension: (s: string) => s.replace(/\.ts$/, ''),
tsConfigExtends,
hasLocalizePackage: !!getPackageJsonDependency(host, '@angular/localize'),
relativePathToWorkspaceRoot: relativePathToWorkspaceRoot(tsConfigDirectory),
}),
move(tsConfigDirectory),
]),
),
updateConfigFileBrowserBuilder(options, tsConfigDirectory),
]),
updateConfigFileApplicationBuilder(options),
updateTsConfigFile(clientBuildOptions.tsConfig),
addDependencies(options.skipInstall),
addRootProvider(
options.project,
Expand Down
Loading