Angular v17: the Application Builder

Robert Isaac
3 min readSep 23, 2023

--

Angular v17: the Application Builder

Angular team is working on a new builder called application (the current builder is called browser), it’s available now as a developer preview in Angular 16.2 and will be the default for new applications generated using Angular 17

so first what’s a builder in Angular?

Angular builder (called executer in nx repos) is basically the compiler that convert your Angular application TS, Angular HTML, and SCSS files into plain HTML, JS and css files that can be understood by the browser

right now we have multiple builder

  1. @angular-devkit/build-angular:browser for production build
  2. @angular-devkit/build-angular:dev-server for serving e.g. ng serve that still uses @angular-devkit/build-angular:browser under the hood but without a lot of optimization and exposing some parts of Angular compiler in the runtime
  3. @angular-devkit/build-angular:server for ssr production build
  4. @nguniversal/builders:ssr-dev-server for ssr serve
  5. @nguniversal/builders:prerender for prerender

now all of these are based on webpack, there is a new one@angular-devkit/build-angular:browser-esbuild and as the name hint uses esbuild, it’s currently available as developer preview (actually I’m using in production for https://interview.community without any problems)

so where does the new builder fit?

It will be using @angular-devkit/build-angular:browser-esbuild under the hood but will extend it to replace also @angular-devkit/build-angular:server and @nguniversal/builders:prerender

and since now it can do it both the browser and ssr build, it will make @angular-devkit/build-angular:dev-server able to replace @nguniversal/builders:ssr-dev-server

so basically in the future we will have 2 instead of the current 5 (well there are some other builder for unit testing and i18n, but that’s different story)

so what is the benefits of doing all of this?

  1. Simpler configuration in the angular.json (or project.json in case of nx), since you don’t need to repeat your configuration in a lot of places)
  2. It’s faster, since there are some common steps between building the browser, prerender, and ssr having one builder will mean these steps will be executed once instead of 3 now
  3. it will enable using es modules (aka esm) in the SSR, it’s now working for projects that don’t have SSR

so how to test this new builder?

  1. upgrade to the latest version of Angular (@angular/cli is 16.2.3 by the time I published the article)
  2. update your angular.json file
    – change your builder in build from @angular-devkit/build-angular:browser to @angular-devkit/build-angular:application
    – (optional) update outputPath to be without browser since it’s now one folder for server and browser
    – replace main key to be browser which points still to your main.ts file
    – add "server": "src/main.server.ts" or whatever is the path for your main.server.ts
    – add "ssr: { "entry": "server.ts" } in your production configurations or if you prefer to have ssr in your development then add it as option
    – delete server , serve-ssr , and prerender from your file
  3. update your server.ts file to be
import { APP_BASE_HREF } from '@angular/common';
import { ngExpressEngine } from '@nguniversal/express-engine';
import express from 'express';
import { fileURLToPath } from 'node:url';
import { dirname, join } 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 distFolder = dirname(fileURLToPath(import.meta.url));
const indexHtml = join(distFolder, 'index.server.html');

// Our Universal express-engine (found @ https://github.com/angular/universal/tree/main/modules/express-engine)
server.engine('html', ngExpressEngine({
bootstrap
}));

server.set('view engine', 'html');
server.set('views', distFolder);

// Example Express Rest API endpoints
// server.get('/api/**', (req, res) => { });
// Serve static files from /browser
server.get('*.*', express.static(distFolder, {
maxAge: '1y'
}));

// All regular routes use the Universal engine
server.get('*', (req, res) => {
res.render(indexHtml, { req, providers: [{ provide: APP_BASE_HREF, useValue: req.baseUrl }] });
});

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();

4. update your tsconfig.app.json to be

/* To learn more about this file see: https://angular.io/config/tsconfig. */
{
"extends": "./tsconfig.json",
"compilerOptions": {
"outDir": "./out-tsc/app",
"types": [],
"allowSyntheticDefaultImports": true
},
"files": [
"src/main.ts",
"src/main.server.ts",
"server.ts"
],
"include": [
"src/**/*.d.ts"
]
}

5. your serve:ssr script will be node dist/project_name/server.mjs

and that’s it, you are good to go now

you can see changes example and working application here

https://github.com/robertIsaac/ng_ssr_esm/compare/browser-builder...master

--

--

Robert Isaac

I am a Senior Front-End Developer who fall in love with Angular and TypeScript.