Skip to content

Commit fc44e3a

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

File tree

5 files changed

+106
-20
lines changed

5 files changed

+106
-20
lines changed

packages/custom-esbuild/README.md

+23-4
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
@@ -143,13 +143,13 @@ import type { Plugin, PluginBuild } from 'esbuild';
143143

144144
function defineEnv(pluginOptions: { stage: string }): Plugin {
145145
return {
146-
name: 'define-env',
146+
name: 'define-env',
147147
setup(build: PluginBuild) {
148148
const buildOptions = build.initialOptions;
149149
buildOptions.define.stage = JSON.stringify(pluginOptions.stage);
150150
},
151151
};
152-
};
152+
}
153153

154154
export default defineEnv;
155155
```
@@ -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.
@@ -239,7 +258,7 @@ It is useful when you want to transform your `index.html` according to the build
239258
`index-html-transformer.js`:
240259
241260
```js
242-
module.exports = (indexHtml) => {
261+
module.exports = indexHtml => {
243262
const i = indexHtml.indexOf('</body>');
244263
const content = `<p>Dynamically inserted content</p>`;
245264
return `${indexHtml.slice(0, i)}

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

+8-1
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,14 @@ 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(
21+
options.plugins,
22+
workspaceRoot,
23+
tsConfig,
24+
context.logger,
25+
options,
26+
context.target
27+
);
2128

2229
const indexHtmlTransformer = options.indexHtmlTransformer
2330
? await loadModule(

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

+3-1
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,9 @@ export function executeCustomDevServerBuilder(
5454
buildOptions.plugins,
5555
workspaceRoot,
5656
tsConfig,
57-
context.logger
57+
context.logger,
58+
options,
59+
context.target
5860
);
5961

6062
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,54 @@ 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(
16+
['test-plugin.js'],
17+
'./test',
18+
'./tsconfig.json',
19+
null as any,
20+
{} as any,
21+
{} as any
22+
);
23+
24+
expect(plugin).toEqual([mockPlugin]);
25+
});
26+
27+
it('should load a plugin factory without configuration and pass options and target', async () => {
28+
const mockPlugin = { name: 'mock' } as Plugin;
29+
const pluginFactory = jest.fn().mockReturnValue(mockPlugin);
30+
const mockOptions = { tsConfig: './tsconfig.json' } as CustomEsbuildApplicationSchema;
31+
const mockTarget = { target: 'test' } as Target;
1132
jest.mock('test/test-plugin.js', () => pluginFactory, { virtual: true });
12-
const plugin = await loadPlugins(['test-plugin.js'], './test', './tsconfig.json', null as any);
33+
const plugin = await loadPlugins(
34+
['test-plugin.js'],
35+
'./test',
36+
'./tsconfig.json',
37+
null as any,
38+
mockOptions,
39+
mockTarget
40+
);
1341

14-
expect(pluginFactory).not.toHaveBeenCalled();
15-
expect(plugin).toBeDefined();
42+
expect(pluginFactory).toHaveBeenCalledWith(mockOptions, mockTarget);
43+
expect(plugin).toEqual([mockPlugin]);
1644
});
1745

1846
it('should load a plugin with configuration', async () => {
1947
const pluginFactory = jest.fn();
48+
const mockOptions = { tsConfig: './tsconfig.json' } as CustomEsbuildApplicationSchema;
49+
const mockTarget = { target: 'test' } as Target;
2050
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);
51+
const plugin = await loadPlugins(
52+
[{ path: 'test-plugin.js', options: { test: 'test' } }],
53+
'./test',
54+
'./tsconfig.json',
55+
null as any,
56+
mockOptions,
57+
mockTarget
58+
);
2259

23-
expect(pluginFactory).toHaveBeenCalledWith({ test: 'test' });
60+
expect(pluginFactory).toHaveBeenCalledWith({ test: 'test' }, mockOptions, mockTarget);
2461
expect(plugin).toBeDefined();
2562
});
2663
});

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

+29-8
Original file line numberDiff line numberDiff line change
@@ -2,25 +2,46 @@ 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 {
6+
CustomEsbuildApplicationSchema,
7+
CustomEsbuildDevServerSchema,
8+
PluginConfig,
9+
} from './custom-esbuild-schema';
10+
import { Target } from '@angular-devkit/architect';
611

712
export async function loadPlugins(
813
pluginConfig: PluginConfig[] | undefined,
914
workspaceRoot: string,
1015
tsConfig: string,
1116
logger: logging.LoggerApi,
17+
builderOptions: CustomEsbuildApplicationSchema | CustomEsbuildDevServerSchema,
18+
target: Target
1219
): Promise<Plugin[]> {
1320
const plugins = await Promise.all(
1421
(pluginConfig || []).map(async pluginConfig => {
15-
if (typeof pluginConfig === 'string') {
16-
return loadModule<Plugin | Plugin[]>(path.join(workspaceRoot, pluginConfig), tsConfig, logger);
22+
if (typeof pluginConfig === 'string') {
23+
const pluginsOrFactory = await loadModule<
24+
| Plugin
25+
| Plugin[]
26+
| ((
27+
options: CustomEsbuildApplicationSchema | CustomEsbuildDevServerSchema,
28+
target: Target
29+
) => Plugin | Plugin[])
30+
>(path.join(workspaceRoot, pluginConfig), tsConfig, logger);
31+
if (typeof pluginsOrFactory === 'function') {
32+
return pluginsOrFactory(builderOptions, target);
1733
} else {
18-
const pluginFactory = await loadModule<(...args: any[]) => Plugin>(path.join(workspaceRoot, pluginConfig.path), tsConfig, logger);
19-
return pluginFactory(pluginConfig.options);
34+
return pluginsOrFactory;
2035
}
21-
22-
},
23-
),
36+
} else {
37+
const pluginFactory = await loadModule<(...args: any[]) => Plugin>(
38+
path.join(workspaceRoot, pluginConfig.path),
39+
tsConfig,
40+
logger
41+
);
42+
return pluginFactory(pluginConfig.options, builderOptions, target);
43+
}
44+
})
2445
);
2546

2647
return plugins.flat();

0 commit comments

Comments
 (0)