diff --git a/packages/custom-esbuild/README.md b/packages/custom-esbuild/README.md index 4055b7833..f6497554c 100644 --- a/packages/custom-esbuild/README.md +++ b/packages/custom-esbuild/README.md @@ -118,7 +118,7 @@ Builder options: In the above example, we specify the list of `plugins` that should implement the ESBuild plugin schema. These plugins are custom user plugins and are added to the original ESBuild Angular configuration. Additionally, the `indexHtmlTransformer` property is used to specify the path to the file that exports the function used to modify the `index.html`. -The plugin file can export either a single plugin or a list of plugins. If a plugin accepts configuration then the config should be provided in `angular.json`: +The plugin file can export either a single plugin, a list of plugins or a factory function that returns a plugin or list of plugins. If a plugin accepts configuration then the config should be provided in `angular.json`: ```ts // esbuild/plugins.ts @@ -143,13 +143,13 @@ import type { Plugin, PluginBuild } from 'esbuild'; function defineEnv(pluginOptions: { stage: string }): Plugin { return { - name: 'define-env', + name: 'define-env', setup(build: PluginBuild) { const buildOptions = build.initialOptions; buildOptions.define.stage = JSON.stringify(pluginOptions.stage); }, }; -}; +} export default defineEnv; ``` @@ -180,6 +180,25 @@ const updateExternalPlugin: Plugin = { export default [defineTextPlugin, updateExternalPlugin]; ``` +Or: + +```ts +// esbuild/plugins.ts +import type { Plugin, PluginBuild } from 'esbuild'; +import type { ApplicationBuilderOptions } from '@angular-devkit/build-angular'; +import type { Target } from '@angular-devkit/architect'; + +export default (builderOptions: ApplicationBuilderOptions, target: Target): Plugin => { + return { + name: 'define-text', + setup(build: PluginBuild) { + const options = build.initialOptions; + options.define.currentProject = JSON.stringify(target.project); + }, + }; +}; +``` + ## Custom ESBuild `dev-server` The `@angular-builders/custom-esbuild:dev-server` is an enhanced version of the `@angular-devkit/build-angular:dev-server` builder that allows the specification of `middlewares` (Vite's `Connect` functions). It also obtains `plugins` and `indexHtmlTransformer` from the `:application` configuration to run the Vite server with all the necessary configuration applied. @@ -239,7 +258,7 @@ It is useful when you want to transform your `index.html` according to the build `index-html-transformer.js`: ```js -module.exports = (indexHtml) => { +module.exports = indexHtml => { const i = indexHtml.indexOf(''); const content = `

Dynamically inserted content

`; return `${indexHtml.slice(0, i)} diff --git a/packages/custom-esbuild/src/application/index.ts b/packages/custom-esbuild/src/application/index.ts index faf9f1513..76722f1a8 100644 --- a/packages/custom-esbuild/src/application/index.ts +++ b/packages/custom-esbuild/src/application/index.ts @@ -17,7 +17,14 @@ export function buildCustomEsbuildApplication( const tsConfig = path.join(workspaceRoot, options.tsConfig); return defer(async () => { - const codePlugins = await loadPlugins(options.plugins, workspaceRoot, tsConfig, context.logger); + const codePlugins = await loadPlugins( + options.plugins, + workspaceRoot, + tsConfig, + context.logger, + options, + context.target + ); const indexHtmlTransformer = options.indexHtmlTransformer ? await loadModule( diff --git a/packages/custom-esbuild/src/dev-server/index.ts b/packages/custom-esbuild/src/dev-server/index.ts index 3ab9eab88..f15f01a53 100644 --- a/packages/custom-esbuild/src/dev-server/index.ts +++ b/packages/custom-esbuild/src/dev-server/index.ts @@ -54,7 +54,9 @@ export function executeCustomDevServerBuilder( buildOptions.plugins, workspaceRoot, tsConfig, - context.logger + context.logger, + options, + context.target ); const indexHtmlTransformer: IndexHtmlTransform = buildOptions.indexHtmlTransformer diff --git a/packages/custom-esbuild/src/load-plugin.spec.ts b/packages/custom-esbuild/src/load-plugin.spec.ts index 2a6d08cb2..ecb367b42 100644 --- a/packages/custom-esbuild/src/load-plugin.spec.ts +++ b/packages/custom-esbuild/src/load-plugin.spec.ts @@ -1,4 +1,7 @@ import { loadPlugins } from './load-plugins'; +import { Target } from '@angular-devkit/architect'; +import { Plugin } from 'esbuild'; +import { CustomEsbuildApplicationSchema } from './custom-esbuild-schema'; describe('loadPlugin', () => { beforeEach(() => { @@ -7,20 +10,54 @@ describe('loadPlugin', () => { }); it('should load a plugin without configuration', async () => { - const pluginFactory = jest.fn(); + const mockPlugin = { name: 'mock' } as Plugin; + jest.mock('test/test-plugin.js', () => mockPlugin, { virtual: true }); + const plugin = await loadPlugins( + ['test-plugin.js'], + './test', + './tsconfig.json', + null as any, + {} as any, + {} as any + ); + + expect(plugin).toEqual([mockPlugin]); + }); + + it('should load a plugin factory without configuration and pass options and target', async () => { + const mockPlugin = { name: 'mock' } as Plugin; + const pluginFactory = jest.fn().mockReturnValue(mockPlugin); + const mockOptions = { tsConfig: './tsconfig.json' } as CustomEsbuildApplicationSchema; + const mockTarget = { target: 'test' } as Target; jest.mock('test/test-plugin.js', () => pluginFactory, { virtual: true }); - const plugin = await loadPlugins(['test-plugin.js'], './test', './tsconfig.json', null as any); + const plugin = await loadPlugins( + ['test-plugin.js'], + './test', + './tsconfig.json', + null as any, + mockOptions, + mockTarget + ); - expect(pluginFactory).not.toHaveBeenCalled(); - expect(plugin).toBeDefined(); + expect(pluginFactory).toHaveBeenCalledWith(mockOptions, mockTarget); + expect(plugin).toEqual([mockPlugin]); }); it('should load a plugin with configuration', async () => { const pluginFactory = jest.fn(); + const mockOptions = { tsConfig: './tsconfig.json' } as CustomEsbuildApplicationSchema; + const mockTarget = { target: 'test' } as Target; jest.mock('test/test-plugin.js', () => pluginFactory, { virtual: true }); - const plugin = await loadPlugins([{ path: 'test-plugin.js', options: { test: 'test' } }], './test', './tsconfig.json', null as any); + const plugin = await loadPlugins( + [{ path: 'test-plugin.js', options: { test: 'test' } }], + './test', + './tsconfig.json', + null as any, + mockOptions, + mockTarget + ); - expect(pluginFactory).toHaveBeenCalledWith({ test: 'test' }); + expect(pluginFactory).toHaveBeenCalledWith({ test: 'test' }, mockOptions, mockTarget); expect(plugin).toBeDefined(); }); }); diff --git a/packages/custom-esbuild/src/load-plugins.ts b/packages/custom-esbuild/src/load-plugins.ts index 8f4f51aa9..ed73fbee9 100644 --- a/packages/custom-esbuild/src/load-plugins.ts +++ b/packages/custom-esbuild/src/load-plugins.ts @@ -2,25 +2,46 @@ import * as path from 'node:path'; import type { Plugin } from 'esbuild'; import type { logging } from '@angular-devkit/core'; import { loadModule } from '@angular-builders/common'; -import { PluginConfig } from './custom-esbuild-schema'; +import { + CustomEsbuildApplicationSchema, + CustomEsbuildDevServerSchema, + PluginConfig, +} from './custom-esbuild-schema'; +import { Target } from '@angular-devkit/architect'; export async function loadPlugins( pluginConfig: PluginConfig[] | undefined, workspaceRoot: string, tsConfig: string, logger: logging.LoggerApi, + builderOptions: CustomEsbuildApplicationSchema | CustomEsbuildDevServerSchema, + target: Target ): Promise { const plugins = await Promise.all( (pluginConfig || []).map(async pluginConfig => { - if (typeof pluginConfig === 'string') { - return loadModule(path.join(workspaceRoot, pluginConfig), tsConfig, logger); + if (typeof pluginConfig === 'string') { + const pluginsOrFactory = await loadModule< + | Plugin + | Plugin[] + | (( + options: CustomEsbuildApplicationSchema | CustomEsbuildDevServerSchema, + target: Target + ) => Plugin | Plugin[]) + >(path.join(workspaceRoot, pluginConfig), tsConfig, logger); + if (typeof pluginsOrFactory === 'function') { + return pluginsOrFactory(builderOptions, target); } else { - const pluginFactory = await loadModule<(...args: any[]) => Plugin>(path.join(workspaceRoot, pluginConfig.path), tsConfig, logger); - return pluginFactory(pluginConfig.options); + return pluginsOrFactory; } - - }, - ), + } else { + const pluginFactory = await loadModule<(...args: any[]) => Plugin>( + path.join(workspaceRoot, pluginConfig.path), + tsConfig, + logger + ); + return pluginFactory(pluginConfig.options, builderOptions, target); + } + }) ); return plugins.flat();