Skip to content

Commit b5080b3

Browse files
committed
feat(custom-esbuild): expose current builder options and target to plugins
1 parent 21e3b6e commit b5080b3

File tree

5 files changed

+55
-12
lines changed

5 files changed

+55
-12
lines changed

packages/custom-esbuild/README.md

+20-1
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,7 @@ Builder options:
118118
119119
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`.
120120
121-
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`:
121+
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`:
122122
123123
```ts
124124
// esbuild/plugins.ts
@@ -180,6 +180,25 @@ const updateExternalPlugin: Plugin = {
180180
export default [defineTextPlugin, updateExternalPlugin];
181181
```
182182
183+
Or:
184+
185+
```ts
186+
// esbuild/plugins.ts
187+
import type { Plugin, PluginBuild } from 'esbuild';
188+
import type { ApplicationBuilderOptions } from '@angular-devkit/build-angular';
189+
import type { Target } from '@angular-devkit/architect';
190+
191+
export default (builderOptions: ApplicationBuilderOptions, target: Target): Plugin => {
192+
return {
193+
name: 'define-text',
194+
setup(build: PluginBuild) {
195+
const options = build.initialOptions;
196+
options.define.currentProject = JSON.stringify(target.project);
197+
},
198+
}
199+
};
200+
```
201+
183202
## Custom ESBuild `dev-server`
184203
185204
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.

packages/custom-esbuild/src/application/index.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ export function buildCustomEsbuildApplication(
1717
const tsConfig = path.join(workspaceRoot, options.tsConfig);
1818

1919
return defer(async () => {
20-
const codePlugins = await loadPlugins(options.plugins, workspaceRoot, tsConfig, context.logger);
20+
const codePlugins = await loadPlugins(options.plugins, workspaceRoot, tsConfig, context.logger, options, context.target);
2121

2222
const indexHtmlTransformer = options.indexHtmlTransformer
2323
? await loadModule(

packages/custom-esbuild/src/dev-server/index.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ export function executeCustomDevServerBuilder(
5454
buildOptions.plugins,
5555
workspaceRoot,
5656
tsConfig,
57-
context.logger
57+
context.logger, options, context.target
5858
);
5959

6060
const indexHtmlTransformer: IndexHtmlTransform = buildOptions.indexHtmlTransformer
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
11
import { loadPlugins } from './load-plugins';
2+
import { Target } from '@angular-devkit/architect';
3+
import {Plugin} from 'esbuild'
4+
import { CustomEsbuildApplicationSchema } from './custom-esbuild-schema';
25

36
describe('loadPlugin', () => {
47
beforeEach(() => {
@@ -7,20 +10,33 @@ describe('loadPlugin', () => {
710
});
811

912
it('should load a plugin without configuration', async () => {
10-
const pluginFactory = jest.fn();
13+
const mockPlugin = {name: 'mock'} as Plugin;
14+
jest.mock('test/test-plugin.js', () => mockPlugin, { virtual: true });
15+
const plugin = await loadPlugins(['test-plugin.js'], './test', './tsconfig.json', null as any, {} as any, {} as any);
16+
17+
expect(plugin).toEqual([mockPlugin]);
18+
});
19+
20+
it('should load a plugin factory without configuration and pass options and target', async () => {
21+
const mockPlugin = {name: 'mock'} as Plugin;
22+
const pluginFactory = jest.fn().mockReturnValue(mockPlugin);
23+
const mockOptions = {tsConfig: './tsconfig.json'} as CustomEsbuildApplicationSchema;
24+
const mockTarget = { target: 'test' } as Target;
1125
jest.mock('test/test-plugin.js', () => pluginFactory, { virtual: true });
12-
const plugin = await loadPlugins(['test-plugin.js'], './test', './tsconfig.json', null as any);
26+
const plugin = await loadPlugins(['test-plugin.js'], './test', './tsconfig.json', null as any, mockOptions, mockTarget);
1327

14-
expect(pluginFactory).not.toHaveBeenCalled();
15-
expect(plugin).toBeDefined();
28+
expect(pluginFactory).toHaveBeenCalledWith(mockOptions, mockTarget);
29+
expect(plugin).toEqual([mockPlugin]);
1630
});
1731

1832
it('should load a plugin with configuration', async () => {
1933
const pluginFactory = jest.fn();
34+
const mockOptions = {tsConfig: './tsconfig.json'} as CustomEsbuildApplicationSchema;
35+
const mockTarget = { target: 'test' } as Target;
2036
jest.mock('test/test-plugin.js', () => pluginFactory, { virtual: true });
21-
const plugin = await loadPlugins([{ path: 'test-plugin.js', options: { test: 'test' } }], './test', './tsconfig.json', null as any);
37+
const plugin = await loadPlugins([{ path: 'test-plugin.js', options: { test: 'test' } }], './test', './tsconfig.json', null as any, mockOptions, mockTarget);
2238

23-
expect(pluginFactory).toHaveBeenCalledWith({ test: 'test' });
39+
expect(pluginFactory).toHaveBeenCalledWith({ test: 'test' }, mockOptions, mockTarget);
2440
expect(plugin).toBeDefined();
2541
});
2642
});

packages/custom-esbuild/src/load-plugins.ts

+11-3
Original file line numberDiff line numberDiff line change
@@ -2,21 +2,29 @@ import * as path from 'node:path';
22
import type { Plugin } from 'esbuild';
33
import type { logging } from '@angular-devkit/core';
44
import { loadModule } from '@angular-builders/common';
5-
import { PluginConfig } from './custom-esbuild-schema';
5+
import { CustomEsbuildApplicationSchema, CustomEsbuildDevServerSchema, PluginConfig } from './custom-esbuild-schema';
6+
import { Target } from '@angular-devkit/architect';
67

78
export async function loadPlugins(
89
pluginConfig: PluginConfig[] | undefined,
910
workspaceRoot: string,
1011
tsConfig: string,
1112
logger: logging.LoggerApi,
13+
builderOptions: CustomEsbuildApplicationSchema | CustomEsbuildDevServerSchema,
14+
target: Target,
1215
): Promise<Plugin[]> {
1316
const plugins = await Promise.all(
1417
(pluginConfig || []).map(async pluginConfig => {
1518
if (typeof pluginConfig === 'string') {
16-
return loadModule<Plugin | Plugin[]>(path.join(workspaceRoot, pluginConfig), tsConfig, logger);
19+
const pluginsOrFactory = await loadModule<Plugin | Plugin[] | ((options: CustomEsbuildApplicationSchema | CustomEsbuildDevServerSchema, target: Target) => Plugin | Plugin[])>(path.join(workspaceRoot, pluginConfig), tsConfig, logger);
20+
if (typeof pluginsOrFactory === 'function') {
21+
return pluginsOrFactory(builderOptions, target);
22+
} else{
23+
return pluginsOrFactory;
24+
}
1725
} else {
1826
const pluginFactory = await loadModule<(...args: any[]) => Plugin>(path.join(workspaceRoot, pluginConfig.path), tsConfig, logger);
19-
return pluginFactory(pluginConfig.options);
27+
return pluginFactory(pluginConfig.options, builderOptions, target);
2028
}
2129

2230
},

0 commit comments

Comments
 (0)