Skip to content

Commit baf8679

Browse files
mattlewis92just-jeb
authored andcommitted
feat(custom-esbuild): expose current builder options and target to plugins
1 parent 83b5c41 commit baf8679

File tree

9 files changed

+114
-35
lines changed

9 files changed

+114
-35
lines changed

packages/custom-esbuild/README.md

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,7 @@ Builder options:
120120
121121
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`.
122122
123-
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`:
123+
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`:
124124
125125
```ts
126126
// esbuild/plugins.ts
@@ -145,13 +145,13 @@ import type { Plugin, PluginBuild } from 'esbuild';
145145

146146
function defineEnv(pluginOptions: { stage: string }): Plugin {
147147
return {
148-
name: 'define-env',
148+
name: 'define-env',
149149
setup(build: PluginBuild) {
150150
const buildOptions = build.initialOptions;
151151
buildOptions.define.stage = JSON.stringify(pluginOptions.stage);
152152
},
153153
};
154-
};
154+
}
155155

156156
export default defineEnv;
157157
```
@@ -182,6 +182,25 @@ const updateExternalPlugin: Plugin = {
182182
export default [defineTextPlugin, updateExternalPlugin];
183183
```
184184
185+
Or:
186+
187+
```ts
188+
// esbuild/plugins.ts
189+
import type { Plugin, PluginBuild } from 'esbuild';
190+
import type { ApplicationBuilderOptions } from '@angular-devkit/build-angular';
191+
import type { Target } from '@angular-devkit/architect';
192+
193+
export default (builderOptions: ApplicationBuilderOptions, target: Target): Plugin => {
194+
return {
195+
name: 'define-text',
196+
setup(build: PluginBuild) {
197+
const options = build.initialOptions;
198+
options.define.currentProject = JSON.stringify(target.project);
199+
},
200+
};
201+
};
202+
```
203+
185204
## Custom ESBuild `dev-server`
186205
187206
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.
@@ -241,7 +260,7 @@ It is useful when you want to transform your `index.html` according to the build
241260
`index-html-transformer.js`:
242261
243262
```js
244-
module.exports = (indexHtml) => {
263+
module.exports = indexHtml => {
245264
const i = indexHtml.indexOf('</body>');
246265
const content = `<p>Dynamically inserted content</p>`;
247266
return `${indexHtml.slice(0, i)}

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

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,14 @@ export function buildCustomEsbuildApplication(
1616
const tsConfig = path.join(workspaceRoot, options.tsConfig);
1717

1818
return defer(async () => {
19-
const codePlugins = await loadPlugins(options.plugins, workspaceRoot, tsConfig, context.logger);
19+
const codePlugins = await loadPlugins(
20+
options.plugins,
21+
workspaceRoot,
22+
tsConfig,
23+
context.logger,
24+
options,
25+
context.target
26+
);
2027

2128
const indexHtmlTransformer = options.indexHtmlTransformer
2229
? await loadIndexHtmlTransformer(

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

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,9 @@ export function executeCustomDevServerBuilder(
5555
buildOptions.plugins,
5656
workspaceRoot,
5757
tsConfig,
58-
context.logger
58+
context.logger,
59+
options,
60+
context.target
5961
);
6062

6163
const indexHtmlTransformer = buildOptions.indexHtmlTransformer

packages/custom-esbuild/src/load-index-html-transformer.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { loadModule } from '@angular-builders/common';
22
import { logging } from '@angular-devkit/core';
33
import { Target } from '@angular-devkit/architect';
4-
import type { IndexHtmlTransform } from '@angular/build/src/utils/index-file/index-html-generator';
4+
import type { IndexHtmlTransform } from '@angular/build/private';
55

66
export async function loadIndexHtmlTransformer(
77
indexHtmlTransformerPath: string,
Lines changed: 43 additions & 6 deletions
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

Lines changed: 29 additions & 8 deletions
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();

packages/custom-webpack/src/generic-browser-builder.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import {
44
BuilderHandlerFn,
55
} from '@angular-devkit/architect';
66
import { ExecutionTransformer } from '@angular-devkit/build-angular';
7-
import type { IndexHtmlTransform } from '@angular/build/src/utils/index-file/index-html-generator';
7+
import type { IndexHtmlTransform } from '@angular/build/private';
88
import { from } from 'rxjs';
99
import { switchMap } from 'rxjs/operators';
1010
import { Configuration } from 'webpack';

packages/custom-webpack/src/transform-factories.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import * as path from 'node:path';
22
import { BuilderContext, Target } from '@angular-devkit/architect';
33
import { ExecutionTransformer } from '@angular-devkit/build-angular';
4-
import type { IndexHtmlTransform } from '@angular/build/src/utils/index-file/index-html-generator';
4+
import type { IndexHtmlTransform } from '@angular/build/private';
55
import { getSystemPath, normalize } from '@angular-devkit/core';
66
import { Configuration } from 'webpack';
77
import { loadModule } from '@angular-builders/common';

tsconfig.json

Lines changed: 5 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,9 @@
11
{
22
"compilerOptions": {
3-
"moduleResolution": "node",
4-
"module": "commonjs",
5-
"target": "es6",
6-
"lib": [
7-
"dom",
8-
"es2015",
9-
"es2016",
10-
"es2017"
11-
],
3+
"moduleResolution": "Node16",
4+
"module": "Node16",
5+
"target": "ES2022",
6+
"lib": ["DOM", "ES2022"],
127
"strict": true,
138
"allowSyntheticDefaultImports": true,
149
"forceConsistentCasingInFileNames": true,
@@ -20,9 +15,7 @@
2015
"stripInternal": true,
2116
"skipLibCheck": true
2217
},
23-
"exclude": [
24-
"node_modules"
25-
],
18+
"exclude": ["node_modules"],
2619
"compileOnSave": false,
2720
"buildOnSave": false
2821
}

0 commit comments

Comments
 (0)