The esbuild-based ECMAScript module (ESM) application build system feature is available for developer preview. It's ready for you to try, but it might change before it is stable and is not yet recommended for production builds.
In v16 and higher, the new build system provides a way to build Angular applications. This new build system includes:
You can opt-in to use the new builder on a per application basis with minimal configuration updates required.
A new builder named browser-esbuild
is available within the @angular-devkit/build-angular
package that is present in an Angular CLI generated application. The build is a drop-in replacement for the existing browser
builder that provides the current stable browser application build system. You can try out the new build system for applications that use the browser
builder.
The new build system was implemented to minimize the amount of changes necessary to transition your applications. Currently, the new build system is provided via an alternate builder (browser-esbuild
). You can update the build
target for any application target to try out the new build system.
The following is what you would typically find in angular.json
for an application:
... "architect": { "build": { "builder": "@angular-devkit/build-angular:browser", ...
Changing the builder
field is the only change you will need to make.
... "architect": { "build": { "builder": "@angular-devkit/build-angular:browser-esbuild", ...
Once you have updated the application configuration, builds can be performed using the ng build
as was previously done. For the remaining options that are currently not yet implemented in the developer preview, a warning will be issued for each and the option will be ignored during the build.
ng build
The development server now has the ability to automatically detect the new build system and use it to build the application. To start the development server no changes are necessary to the dev-server
builder configuration or command line.
ng serve
You can continue to use the command line options you have used in the past with the development server.
The developer preview currently does not provide HMR support and the HMR related options will be ignored if used. Angular focused HMR capabilities are currently planned and will be introduced in a future version.
Several build options are not yet implemented but will be added in the future as the build system moves towards a stable status. If your application uses these options, you can still try out the build system without removing them. Warnings will be issued for any unimplemented options but they will otherwise be ignored. However, if your application relies on any of these options to function, you may want to wait to try.
budgets
)localize
/i18nDuplicateTranslation
/i18nMissingTranslation
)webWorkerTsConfig
)Building libraries with the new build system via ng-packagr
is also not yet possible but library build support will be available in a future release.
TypeScript by default allows default exports to be imported as namespace imports and then used in call expressions. This is unfortunately a divergence from the ECMAScript specification. The underlying bundler (esbuild
) within the new build system expects ESM code that conforms to the specification. The build system will now generate a warning if your application uses an incorrect type of import of a package. However, to allow TypeScript to accept the correct usage, a TypeScript option must be enabled within the application's tsconfig
file. When enabled, the esModuleInterop
option provides better alignment with the ECMAScript specification and is also recommended by the TypeScript team. Once enabled, you can update package imports where applicable to an ECMAScript conformant form.
Using the moment
package as an example, the following application code will cause runtime errors:
import * as moment from 'moment'; console.log(moment().format());
The build will generate a warning to notify you that there is a potential problem. The warning will be similar to:
▲ [WARNING] Calling "moment" will crash at run-time because it's an import namespace object, not a function [call-import-namespace] src/main.ts:2:12: 2 │ console.log(moment().format()); ╵ ~~~~~~ Consider changing "moment" to a default import instead: src/main.ts:1:7: 1 │ import * as moment from 'moment'; │ ~~~~~~~~~~~ ╵ moment
However, you can avoid the runtime errors and the warning by enabling the esModuleInterop
TypeScript option for the application and changing the import to the following:
import moment from 'moment'; console.log(moment().format());
The usage of Vite in the Angular CLI is currently only within a development server capacity only. Even without using the underlying Vite build system, Vite provides a full-featured development server with client side support that has been bundled into a low dependency npm package. This makes it an ideal candidate to provide comprehensive development server functionality. The current development server process uses the new build system to generate a development build of the application in memory and passes the results to Vite to serve the application. The usage of Vite, much like the Webpack-based development server, is encapsulated within the Angular CLI dev-server
builder and currently cannot be directly configured.
There are currently several known issues that you may encounter when trying the new build system. This list will be updated to stay current. If any of these issues are currently blocking you from trying out the new build system, please check back in the future as it may have been solved.
Dynamic import expressions that do not contain static values will be kept in their original form and not processed at build time. This is a limitation of the underlying bundler but is planned to be implemented in the future. In many cases, application code can be made to work by changing the import expressions into static strings with some form of conditional statement such as an if
or switch
for the known potential files.
Unsupported:
return await import(`/abc/${name}.json`);
Supported:
switch (name) { case 'x': return await import('/abc/x.json'); case 'y': return await import('/abc/y.json'); case 'z': return await import('/abc/z.json'); }
Import statements that are dependent on a specific ordering and are also used in multiple lazy modules can cause top-level statements to be executed out of order. This is not common as it depends on the usage of side-effectful modules and does not apply to the polyfills
option. This is caused by a defect in the underlying bundler but will be addressed in a future update.
Avoiding the use of modules with non-local side effects (outside of polyfills) is recommended whenever possible regardless of the build system being used and avoids this particular issue. Modules with non-local side effects can have a negative effect on both application size and runtime performance as well.
If your application currently uses the inject
sub-option for any global styles and scripts via the styles
or scripts
build options, the output file names for those styles/scripts will incorrectly contain a hash. Depending on the usage of the output files, this may cause runtime failures for your application. See the related issue for more information.
Report issues and feature requests on GitHub.
Please provide a minimal reproduction where possible to aid the team in addressing issues.
© 2010–2023 Google, Inc.
Licensed under the Creative Commons Attribution License 4.0.
https://angular.io/guide/esbuild