When using the esbuild-based browser application builder with the Vite-based development
server on Windows, source asset paths were previously not normalized prior to being included
in request URLs. This could result in invalid asset request URLs due to invalid path
segment separators.
When using the esbuild-based build system with the service worker enabled, binary assets were
unintentionally being hashed with the assumption of UTF-8 encoding. The assets are now hashed
directly to ensure correct output hashes.
When using the esbuild-based browser application builder with CLI caching enabled, TypeScript's `incremental`
option will also be enabled by default. A TypeScript build information file will be written after each build and
an attempt to load and use the file will be made during compilation setup. Caching is enabled by default within
the CLI and can be controlled via the `ng cache` command. This is the first use of persistent caching for the
esbuild-based builder. If the TypeScript `incremental` option is manually set to `false`, the build system will
not alter the value. This can be used to disable the behavior, if preferred, by setting the option to `false` in
the application's configured `tsconfig` file.
NOTE: The build information only contains information regarding the TypeScript compilation itself and does not
contain information about the Angular AOT compilation. TypeScript does not have knowledge of the AOT compiler
and it therefore cannot include that information in its build information file. Angular AOT analysis is still
performed for each build.
To allow lower overhead trial of the developer preview of the esbuild-based builder system,
the development server now has an option to force the usage of the esbuild-based
build system while still retaining the default Webpack-based build system for the
`build` command. The `forceEsbuild`/`--force-esbuild` option can be added to the
`angular.json` options for the `serve` target or used on the command line, respectively.
The `browser-esbuild` builder will be used to build the application using the options
specified by the server configuration's `browserTarget` option. Unsupported build options
will be ignored. If using a third-party builder, a warning will be issued but the build
will still be attempted. Third-party builder usage in this context is considered
unsupported and may result in unexpected behavior or build failures.
The internal emit strategy for the TypeScript/Angular compiler has been adjusted to prefill
the memory cache during the initial phase of the build. Previously each file was emitted
during the bundling process as requested by the bundler. This change has no immediate effect
on the build process but enables future build performance improvements.
When using the esbuild-based browser application builder, CommonJS file warnings were incorrectly being
issued for relative file imports. The CommonJS warnings are only intended to be generated for node module
imports.
When using the esbuild-based browser application builder with the development server, the incoming
URL for the index HTML may contain search parameters or other URL elements that can cause the index
HTML content to not be found or processed incorrected by the development server. These elements are
cleaned prior to comparison and the original URL is not longer passed to Vite to avoid unneeded
Vite specific processing of the content.
When using the esbuild-based browser application builder with a `url()` in a stylesheet that uses
that Webpack-specific tilde prefix, a note will be added to the resolution error providing additional
information regarding the removal of the tilde.
When using the esbuild-based browser application builder with the development server, an
underlying Vite server is used. The Vite server currently does not support glob-based entries
for the proxy configuration. They must either be prefix strings or regular expressions. The
Webpack-based development server, however, does support globs. To remove the need to have
different proxy configuration files for the two servers, the entries will now be normalized
to regular expressions when using the Vite server. This allows existing proxy configurations
to work without modification.
When using the esbuild-based browser application builder with the development server,
the `baseHref` build option will now be properly propagated to the underlying Vite
server.
esbuild currently has a defect involving self-referencing a class within a static code block or
static field initializer. This is not an issue for projects that use the default browserslist as these
elements are an ES2022 feature which is not support by all browsers in the default list. However, if a
custom browserslist is used that only has newer browsers than the static code elements may be present.
This issue is compounded by the default usage of the tsconfig `"useDefineForClassFields": false` option
present in generated CLI projects which causes static code blocks to be used instead of static fields.
esbuild currently unconditionally downlevels all static fields in top-level classes so to workaround the
Angular issue only static code blocks are disabled here.
Fixes#25127
When using the esbuild-based browser application builder, non-injected global styles and scripts
were unintentionally being output with filenames that contain a hash. This can prevent the filenames
from being discoverable and therefore usable at runtime. The output filenames will now no longer
contain a hash component which matches the behavior of the Webpack-based builder.
This commit fixes and issue were standalone applications would fail during runtime because the `@angular/compiler` is not available.
We now add the `@angular/compiler` as part of the bundle when JIT mode is enabled.
The status of the esbuild-based browser application builder has been changed to developer
preview. Warning messages for unsupported options are now updated to reflect this change.
While currently esbuild is not use to bundle server bundles, it will in the future. This commit adds a check for the `@angular/platform-server/init` entry-point to be excluded from advanced optimizations.
Vite inlines the source map as part of content (ba62be40b4/packages/vite/src/node/server/send.ts (L59-L63)) with no option to disable this behaviour. While this is improves performances when Vite is used without prebundling. When using prebundling this is causes an overhead as the response can grow drastically, in some cases over 50mb.
This configures polyfills to set up the environment before executing Jest tests. We need to do three things:
1. Set the global `jest` symbol. Jest executing in ESM does not provide the `jest` global and users are expected to import from `@jest/globals` or `import.meta.jest`. Zone.js is not compatible with this yet, so we need to manually define the `jest` global for Zone to read it.
2. Run user polyfills, (typically including `zone.js` and `zone.js/testing`). Zone reads the `jest` global to recognize the environment it is in and patch the relevant functions to load fake async properly. Users can override this part if they are building a Zoneless application or have custom polyfills for other browser functionality.
3. Initalize `TestBed`. This configures the `TestBed` environment so users don't have to manually configure it for each test file.
Ordering is very important for these operations, which complicates the implementation somewhat. `zone.js/testing` does not include an import on `zone.js`, meaning there was no guarnatee the bundler would sort their executions in the correct order. Similarly, `zone.js` does not import anything from Jest, so it is not trivial to inject the `globalThis.jest = import.meta.jest;` line before Zone loads. Even setting polyfills to `[jestGlobal, 'zone.js, 'zone.js/testing', initTestBed]` doesn't work because code splitting rearranges the order of operations in an incompatible way. Instead, these are implemented as distinct entry points in `browser-esbuild` with Jest's `--setupFilesAfterEnv` option executing them in the correct order.
Ideally, we could drop the global initialization altogether once Zone.js knows to look for `import.meta.jest` in an ESM context. Also we might be able to reduce down to a single polyfills entry point if `zone.js/testing` had an import on `zone.js` to apply correct ordering.
This runs Jest on the outputs of the built test files and reports the results of the test execution. It depends on `jest` and `jest-environment-jsdom` as optional peer deps validated in the builder, so there isn't a strict dependency on Jest for applications which don't use it.
Jest exports a `runJest()` function, however it can't be used directly because we need to opt-in to `--experimental-vm-modules` in ordre to run Jest on native ESM JavaScript as produced by `browser-esbuild`. This means we need a Node subprocess in order to add this flag, because the `ng` command cannot add a Node flag to its own current execution. This unfortunately means we can't just `import * as jest from 'jest';` or even `require.resolve('jest')` because that returns the library entry point exporting `runJest()`, rather than a script which actually runs Jest on load. Fortunately, Jest exports it's `node_modules/.bin/jest` entry point from its `package.json` under `jest/bin/jest`, so we `require.resolve()` _that_ to get the path to the correct file.
Executing Jest is fairly straightforward, running on the output of the `browser-esbuild` execution with outputs streamed to the console. We opted to use JSDom over Domino in order to align with the existing Jest ecosystem.
For now this just runs ESBuild-er to build test code, Jest is not actually invoked yet.
This uses `glob` to find test files matching the given pattern. I went out of my way to limit `glob` functionality as much as possible in case we change the implementation later.
The change detection logic for the output files is now moved to a separate function
to reduce the code line count for the main dev-server function.
Also changed the setup function for the vite server to return the actual configuration
to allow for better reuse and testability.
The stylesheet related files for the esbuild-based browser application builder
have now been moved into a `stylesheets` directory to better organize the code.
The TypeScript diagnostic to esbuild diagnostic conversion utilities are now contained in a
separate file. This lowers the line count of the main compiler plugin file and also removes
an additional direct usage of the `typescript` dependency from the compiler plugin file.
This commit adds the option to run build-optimizer on the server bundles.
This is needed as in some cases, there can be restrictions on the size of
server bundles that can be executed. One of these cases is CloudFlare workers
which by default does not work with an `ng-new` application due to the size of the
bundle mainly due to the retention of the `@angular/compiler`.
When using the esbuild-based browser application builder with the dev-server builder, sourcemap
URL comments were unintentionally doubled due to vite adding an additional inline sourcemap comment
when processing the JavaScript files for the application. To avoid this, JavaScript files now have
any sourcemap URL comments removed prior to being processed by vite. The sourcemap content is already
passed directly to vite and allows the sourcemaps to be processed and provided to the browser as needed
without the previously existing sourcemap URL comment. The removal is done without modifying the esbuild-
based builder's options to avoid assuming the builder used with the dev-server has sourcemap options that
allow hidden sourcemap generation.
When using the esbuild-based browser application builder with source maps enabled, the Chrome
DevTools `x_google_ignoreList` extension will be added to JavaScript source maps. This extension
supports an improved developer experience with Chrome DevTools. For more information, please see
https://developer.chrome.com/articles/x-google-ignore-list/
When using the esbuild-based browser application builder, the tsconfig path will now be properly
converted to the realpath when the `preserveSymlinks` option is disabled (the default). This ensures
that TypeScript source files will be properly matched with the bundler's resolved paths when symlinks
are used and the `preserveSymlinks` option is not enabled. This is needed to properly support the use
of bazel with rules_js.
The `outExtension` option allows users to generate `*.mjs` files, which is useful for forcing ESM execution in Node under certain use cases. The option is limited to `*.js` and `*.mjs` files to constrain it to expected values. `*.cjs` could theoretically be useful in some specific situations, but `browser-esbuild` does not support that output format anyways, so it is not included in the type.
I also updated `index.html` generation, which will correctly insert a `<script />` tag with the `*.mjs` extension. I opted to explicitly ban a "non-module" `*.mjs` file, since that would be very counterintuitive and I can't think of a valid use case for it.
For testing use cases, we don't need an `index.html` file in the same capacity as a typical application. The builder already omits an `index.html` page when not set, this just updates the schema to reflect that.
The parameter could be left optional, rather than required but allowing `false`. However doing it this way prevents users from accidentally forgetting to provide an index while still allowing users to explicitly disable index generation. We use `false` instead of `null` so users can write `--no-index` on the command line and get the same behavior, which would not be possible with `null`.
This allows `browser-esbuild` to consume absolute file paths and `entryPoints`. Absolute paths will always output in the root of the output directory with the same basename, since they are not within the workspace root and cannot exist at any guaranteed unique relative path. No attempt is made to check if the absolute path is actually within the workspace root, since this would require a call to `fs.realpath()` and make this logic dependent on the actual file system structure which introduces a lot of complexity we'd rather avoid.
Longer term, the ideal approach is probably to leverage virtual files in some capacity, but this should be sufficient for now.
`main` functionality is left alone, and absolute paths like `/main.ts` are treated as relative to the workspace root. This is to preserve existing functionality and discourage public API usage of file paths outside the workspace.
This makes the `main` parameter optional and allows multiple entry points instead. `main` is still technically required by the schema, since it should almost always be set when invoked by a user. However, it now supports `null` as a value so it can be explicitly omitted.
Longer term, we may choose to remove `main` and fold it into `entryPoints`, but for now we want to keep compatibility with the existing `browser` builder.
Since `entryPoints` is an internal-only options (cannot be set in `angular.json` and isn't exposed in the schema), I made a new `buildEsbuildBrowserInternal()` function which adds the extra private option. This way direct invocations of the builder can provide this extra information without compromising the public API surface defined in the schema.