Skip to content

Commit 3f8aa9d

Browse files
committed
feat(@schematics/angular): update ng new to use the esbuild application builder based builder
This commit updates the `ng generate application` to use the esbuild `application` builder. This also updates the schematics to support both `browser` and `application` builders. BREAKING CHANGE: `rootModuleClassName`, `rootModuleFileName` and `main` options have been removed from the public `pwa` and `app-shell` schematics.
1 parent 3d3afc7 commit 3f8aa9d

37 files changed

+953
-700
lines changed

‎packages/angular/pwa/pwa/index.ts

+4-1
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,10 @@ export default function (options: PwaOptions): Rule {
114114
const buildTargets = [];
115115
const testTargets = [];
116116
for (const target of project.targets.values()) {
117-
if (target.builder === '@angular-devkit/build-angular:browser') {
117+
if (
118+
target.builder === '@angular-devkit/build-angular:browser' ||
119+
target.builder === '@angular-devkit/build-angular:application'
120+
) {
118121
buildTargets.push(target);
119122
} else if (target.builder === '@angular-devkit/build-angular:karma') {
120123
testTargets.push(target);

‎packages/angular/pwa/pwa/index_spec.ts

+92-91
Original file line numberDiff line numberDiff line change
@@ -52,122 +52,123 @@ describe('PWA Schematic', () => {
5252
);
5353
});
5454

55-
it('should run the service worker schematic', (done) => {
56-
schematicRunner
57-
.runSchematic('ng-add', defaultOptions, appTree)
58-
59-
.then((tree) => {
60-
const configText = tree.readContent('/angular.json');
61-
const config = JSON.parse(configText);
62-
const swFlag = config.projects.bar.architect.build.options.serviceWorker;
63-
expect(swFlag).toEqual(true);
64-
done();
65-
}, done.fail);
66-
});
67-
68-
it('should create icon files', (done) => {
55+
it('should create icon files', async () => {
6956
const dimensions = [72, 96, 128, 144, 152, 192, 384, 512];
7057
const iconPath = '/projects/bar/src/assets/icons/icon-';
71-
schematicRunner
72-
.runSchematic('ng-add', defaultOptions, appTree)
73-
74-
.then((tree) => {
75-
dimensions.forEach((d) => {
76-
const path = `${iconPath}${d}x${d}.png`;
77-
expect(tree.exists(path)).toEqual(true);
78-
});
79-
done();
80-
}, done.fail);
58+
const tree = await schematicRunner.runSchematic('ng-add', defaultOptions, appTree);
59+
60+
dimensions.forEach((d) => {
61+
const path = `${iconPath}${d}x${d}.png`;
62+
expect(tree.exists(path)).toBeTrue();
63+
});
8164
});
8265

83-
it('should create a manifest file', (done) => {
84-
schematicRunner
85-
.runSchematic('ng-add', defaultOptions, appTree)
66+
it('should run the service worker schematic', async () => {
67+
const tree = await schematicRunner.runSchematic('ng-add', defaultOptions, appTree);
68+
const configText = tree.readContent('/angular.json');
69+
const config = JSON.parse(configText);
70+
const swFlag = config.projects.bar.architect.build.configurations.production.serviceWorker;
8671

87-
.then((tree) => {
88-
expect(tree.exists('/projects/bar/src/manifest.webmanifest')).toEqual(true);
89-
done();
90-
}, done.fail);
72+
expect(swFlag).toBe('projects/bar/ngsw-config.json');
9173
});
9274

93-
it('should set the name & short_name in the manifest file', (done) => {
94-
schematicRunner
95-
.runSchematic('ng-add', defaultOptions, appTree)
75+
it('should create a manifest file', async () => {
76+
const tree = await schematicRunner.runSchematic('ng-add', defaultOptions, appTree);
77+
expect(tree.exists('/projects/bar/src/manifest.webmanifest')).toBeTrue();
78+
});
79+
80+
it('should set the name & short_name in the manifest file', async () => {
81+
const tree = await schematicRunner.runSchematic('ng-add', defaultOptions, appTree);
9682

97-
.then((tree) => {
98-
const manifestText = tree.readContent('/projects/bar/src/manifest.webmanifest');
99-
const manifest = JSON.parse(manifestText);
83+
const manifestText = tree.readContent('/projects/bar/src/manifest.webmanifest');
84+
const manifest = JSON.parse(manifestText);
10085

101-
expect(manifest.name).toEqual(defaultOptions.title);
102-
expect(manifest.short_name).toEqual(defaultOptions.title);
103-
done();
104-
}, done.fail);
86+
expect(manifest.name).toEqual(defaultOptions.title);
87+
expect(manifest.short_name).toEqual(defaultOptions.title);
10588
});
10689

107-
it('should set the name & short_name in the manifest file when no title provided', (done) => {
90+
it('should set the name & short_name in the manifest file when no title provided', async () => {
10891
const options = { ...defaultOptions, title: undefined };
109-
schematicRunner
110-
.runSchematic('ng-add', options, appTree)
92+
const tree = await schematicRunner.runSchematic('ng-add', options, appTree);
11193

112-
.then((tree) => {
113-
const manifestText = tree.readContent('/projects/bar/src/manifest.webmanifest');
114-
const manifest = JSON.parse(manifestText);
94+
const manifestText = tree.readContent('/projects/bar/src/manifest.webmanifest');
95+
const manifest = JSON.parse(manifestText);
11596

116-
expect(manifest.name).toEqual(defaultOptions.project);
117-
expect(manifest.short_name).toEqual(defaultOptions.project);
118-
done();
119-
}, done.fail);
97+
expect(manifest.name).toEqual(defaultOptions.project);
98+
expect(manifest.short_name).toEqual(defaultOptions.project);
12099
});
121100

122-
it('should update the index file', (done) => {
123-
schematicRunner
124-
.runSchematic('ng-add', defaultOptions, appTree)
125-
126-
.then((tree) => {
127-
const content = tree.readContent('projects/bar/src/index.html');
101+
it('should update the index file', async () => {
102+
const tree = await schematicRunner.runSchematic('ng-add', defaultOptions, appTree);
103+
const content = tree.readContent('projects/bar/src/index.html');
128104

129-
expect(content).toMatch(/<link rel="manifest" href="manifest.webmanifest">/);
130-
expect(content).toMatch(/<meta name="theme-color" content="#1976d2">/);
131-
expect(content).toMatch(
132-
/<noscript>Please enable JavaScript to continue using this application.<\/noscript>/,
133-
);
134-
done();
135-
}, done.fail);
105+
expect(content).toMatch(/<link rel="manifest" href="manifest.webmanifest">/);
106+
expect(content).toMatch(/<meta name="theme-color" content="#1976d2">/);
107+
expect(content).toMatch(
108+
/<noscript>Please enable JavaScript to continue using this application.<\/noscript>/,
109+
);
136110
});
137111

138-
it('should not add noscript element to the index file if already present', (done) => {
112+
it('should not add noscript element to the index file if already present', async () => {
139113
let index = appTree.readContent('projects/bar/src/index.html');
140114
index = index.replace('</body>', '<noscript>NO JAVASCRIPT</noscript></body>');
141115
appTree.overwrite('projects/bar/src/index.html', index);
142-
schematicRunner
143-
.runSchematic('ng-add', defaultOptions, appTree)
144-
145-
.then((tree) => {
146-
const content = tree.readContent('projects/bar/src/index.html');
147-
148-
expect(content).toMatch(/<link rel="manifest" href="manifest.webmanifest">/);
149-
expect(content).toMatch(/<meta name="theme-color" content="#1976d2">/);
150-
expect(content).not.toMatch(
151-
/<noscript>Please enable JavaScript to continue using this application.<\/noscript>/,
152-
);
153-
expect(content).toMatch(/<noscript>NO JAVASCRIPT<\/noscript>/);
154-
done();
155-
}, done.fail);
116+
117+
const tree = await schematicRunner.runSchematic('ng-add', defaultOptions, appTree);
118+
const content = tree.readContent('projects/bar/src/index.html');
119+
120+
expect(content).toMatch(/<link rel="manifest" href="manifest.webmanifest">/);
121+
expect(content).toMatch(/<meta name="theme-color" content="#1976d2">/);
122+
expect(content).not.toMatch(
123+
/<noscript>Please enable JavaScript to continue using this application.<\/noscript>/,
124+
);
125+
expect(content).toMatch(/<noscript>NO JAVASCRIPT<\/noscript>/);
156126
});
157127

158-
it('should update the build and test assets configuration', (done) => {
159-
schematicRunner
160-
.runSchematic('ng-add', defaultOptions, appTree)
128+
it('should update the build and test assets configuration', async () => {
129+
const tree = await schematicRunner.runSchematic('ng-add', defaultOptions, appTree);
130+
const configText = tree.readContent('/angular.json');
131+
const config = JSON.parse(configText);
132+
const targets = config.projects.bar.architect;
161133

162-
.then((tree) => {
163-
const configText = tree.readContent('/angular.json');
164-
const config = JSON.parse(configText);
165-
const targets = config.projects.bar.architect;
134+
['build', 'test'].forEach((target) => {
135+
expect(targets[target].options.assets).toContain('projects/bar/src/manifest.webmanifest');
136+
});
137+
});
166138

167-
['build', 'test'].forEach((target) => {
168-
expect(targets[target].options.assets).toContain('projects/bar/src/manifest.webmanifest');
169-
});
170-
done();
171-
}, done.fail);
139+
describe('Legacy browser builder', () => {
140+
function convertBuilderToLegacyBrowser(): void {
141+
const config = JSON.parse(appTree.readContent('/angular.json'));
142+
const build = config.projects.bar.architect.build;
143+
144+
build.builder = '@angular-devkit/build-angular:browser';
145+
build.options = {
146+
...build.options,
147+
main: build.options.browser,
148+
browser: undefined,
149+
};
150+
151+
build.configurations.development = {
152+
...build.configurations.development,
153+
vendorChunk: true,
154+
namedChunks: true,
155+
buildOptimizer: false,
156+
};
157+
158+
appTree.overwrite('/angular.json', JSON.stringify(config, undefined, 2));
159+
}
160+
161+
beforeEach(() => {
162+
convertBuilderToLegacyBrowser();
163+
});
164+
165+
it('should run the service worker schematic', async () => {
166+
const tree = await schematicRunner.runSchematic('ng-add', defaultOptions, appTree);
167+
const configText = tree.readContent('/angular.json');
168+
const config = JSON.parse(configText);
169+
const swFlag = config.projects.bar.architect.build.options.serviceWorker;
170+
171+
expect(swFlag).toBeTrue();
172+
});
172173
});
173174
});

‎packages/angular/ssr/package.json

+3
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,9 @@
99
"ssr",
1010
"universal"
1111
],
12+
"ng-add": {
13+
"save": "dependencies"
14+
},
1215
"dependencies": {
1316
"critters": "0.0.20",
1417
"tslib": "^2.3.0"
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
import { APP_BASE_HREF } from '@angular/common';
2+
import { CommonEngine } from '@angular/ssr';
3+
import express from 'express';
4+
import { fileURLToPath } from 'node:url';
5+
import { dirname, join } from 'node:path';
6+
import <% if (isStandalone) { %>bootstrap<% } else { %>AppServerModule<% } %> from './src/main.server';
7+
8+
// The Express app is exported so that it can be used by serverless Functions.
9+
export function app(): express.Express {
10+
const server = express();
11+
const browserDistFolder = dirname(fileURLToPath(import.meta.url));
12+
const indexHtml = join(browserDistFolder, 'index.server.html');
13+
14+
const commonEngine = new CommonEngine();
15+
16+
server.set('view engine', 'html');
17+
server.set('views', browserDistFolder);
18+
19+
// Example Express Rest API endpoints
20+
// server.get('/api/**', (req, res) => { });
21+
// Serve static files from /browser
22+
server.get('*.*', express.static(browserDistFolder, {
23+
maxAge: '1y'
24+
}));
25+
26+
// All regular routes use the Angular engine
27+
server.get('*', (req, res, next) => {
28+
commonEngine
29+
.render({
30+
<% if (isStandalone) { %>bootstrap<% } else { %>bootstrap: AppServerModule<% } %>,
31+
documentFilePath: indexHtml,
32+
url: req.originalUrl,
33+
publicPath: browserDistFolder,
34+
providers: [{ provide: APP_BASE_HREF, useValue: req.baseUrl }],
35+
})
36+
.then((html) => res.send(html))
37+
.catch((err) => next(err));
38+
});
39+
40+
return server;
41+
}
42+
43+
function run(): void {
44+
const port = process.env['PORT'] || 4000;
45+
46+
// Start up the Node server
47+
const server = app();
48+
server.listen(port, () => {
49+
console.log(`Node Express server listening on http://localhost:${port}`);
50+
});
51+
}
52+
53+
run();

‎packages/angular/ssr/schematics/ng-add/files/server.ts.template renamed to ‎packages/angular/ssr/schematics/ng-add/files/server-builder/server.ts.template

+2-2
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import { CommonEngine } from '@angular/ssr';
55
import * as express from 'express';
66
import { existsSync } from 'node:fs';
77
import { join } from 'node:path';
8-
import <% if (isStandalone) { %>bootstrap<% } else { %>{ AppServerModule }<% } %> from './src/<%= stripTsExtension(main) %>';
8+
import <% if (isStandalone) { %>bootstrap<% } else { %>AppServerModule<% } %> from './src/main.server';
99

1010
// The Express app is exported so that it can be used by serverless Functions.
1111
export function app(): express.Express {
@@ -64,4 +64,4 @@ if (moduleFilename === __filename || moduleFilename.includes('iisnode')) {
6464
run();
6565
}
6666

67-
<% if (isStandalone) { %>export default bootstrap;<% } else { %>export * from './src/<%= stripTsExtension(main) %>';<% } %>
67+
<% if (isStandalone) { %>export default bootstrap;<% } else { %>export * from './src/main.server';<% } %>

0 commit comments

Comments
 (0)