Skip to content

Commit

Permalink
feat(custom-esbuild): add support for plugin configuration
Browse files Browse the repository at this point in the history
  • Loading branch information
spike-rabbit committed Feb 16, 2024
1 parent a64a028 commit 81890a5
Show file tree
Hide file tree
Showing 15 changed files with 124 additions and 10 deletions.
Expand Up @@ -17,7 +17,7 @@
"build": {
"builder": "@angular-builders/custom-esbuild:application",
"options": {
"plugins": ["esbuild/define-text-plugin.js"],
"plugins": ["esbuild/define-text-plugin.js", {"path": "esbuild/define-text-by-option-plugin.js", "options": {"title": "sanity-esbuild-app-esm optionTitle (compilation provided)"} }],
"outputPath": "dist/sanity-esbuild-app-esm",
"index": "src/index.html",
"browser": "src/main.ts",
Expand Down
@@ -0,0 +1,11 @@
function defineTitleByOptionPlugin(pluginOptions) {
return {
name: 'define-title',
setup(build) {
const options = build.initialOptions;
options.define.titleByOption = JSON.stringify(pluginOptions.title);
},
};
};

module.exports = defineTitleByOptionPlugin;
@@ -0,0 +1,11 @@
function defineTitleByOptionPlugin(pluginOptions) {
return {
name: 'define-title',
setup(build) {
const options = build.initialOptions;
options.define.titleByOption = JSON.stringify(pluginOptions.title);
},
};
};

export default defineTitleByOptionPlugin;
@@ -0,0 +1,13 @@
import type { Plugin, PluginBuild } from 'esbuild';

function defineTitleByOptionPlugin(pluginOptions: {title: string}): Plugin {
return {
name: 'define-title',
setup(build: PluginBuild) {
const options = build.initialOptions;
options.define!['titleByOption'] = JSON.stringify(pluginOptions.title);
},
};
};

export default defineTitleByOptionPlugin;
@@ -1,2 +1,4 @@
<h1>{{ title }}</h1>
<h2>{{ subtitle }}</h2>
<h3>{{ optionTitle }}</h3>
```
Expand Up @@ -2,6 +2,7 @@ import { Component } from '@angular/core';

declare const title: string;
declare const subtitle: string;
declare const optionTitle: string;

@Component({
selector: 'app-root',
Expand All @@ -12,9 +13,11 @@ declare const subtitle: string;
export class AppComponent {
title: string;
subtitle: string;
optionTitle: string;

constructor() {
this.title = typeof title !== 'undefined' ? title : 'sanity-esbuild-app-esm';
this.subtitle = typeof subtitle !== 'undefined' ? subtitle : 'sanity-esbuild-app-esm subtitle';
this.optionTitle = typeof optionTitle !== 'undefined' ? optionTitle : 'sanity-esbuild-app-esm optionTitle';
}
}
2 changes: 1 addition & 1 deletion examples/custom-esbuild/sanity-esbuild-app/angular.json
Expand Up @@ -17,7 +17,7 @@
"build": {
"builder": "@angular-builders/custom-esbuild:application",
"options": {
"plugins": ["esbuild/define-text-plugin.js"],
"plugins": ["esbuild/define-text-plugin.js", {"path": "esbuild/define-text-by-option-plugin.js", "options": {"title": "sanity-esbuild-app optionTitle (compilation provided)"} }],
"outputPath": "dist/sanity-esbuild-app",
"index": "src/index.html",
"browser": "src/main.ts",
Expand Down
@@ -0,0 +1,11 @@
function defineTitleByOptionPlugin(pluginOptions) {
return {
name: 'define-title',
setup(build) {
const options = build.initialOptions;
options.define.titleByOption = JSON.stringify(pluginOptions.title);
},
};
};

module.exports = defineTitleByOptionPlugin;
@@ -0,0 +1,11 @@
function defineTitleByOptionPlugin(pluginOptions) {
return {
name: 'define-title',
setup(build) {
const options = build.initialOptions;
options.define.titleByOption = JSON.stringify(pluginOptions.title);
},
};
};

export default defineTitleByOptionPlugin;
@@ -1,2 +1,4 @@
<h1>{{ title }}</h1>
<h2>{{ subtitle }}</h2>
<h3>{{ optionTitle }}</h3>
```
Expand Up @@ -2,6 +2,7 @@ import { Component } from '@angular/core';

declare const title: string;
declare const subtitle: string;
declare const optionTitle: string;

@Component({
selector: 'app-root',
Expand All @@ -12,9 +13,11 @@ declare const subtitle: string;
export class AppComponent {
title: string;
subtitle: string;
optionTitle: string;

constructor() {
this.title = typeof title !== 'undefined' ? title : 'sanity-esbuild-app';
this.subtitle = typeof subtitle !== 'undefined' ? subtitle : 'sanity-esbuild-app subtitle';
this.optionTitle = typeof optionTitle !== 'undefined' ? optionTitle : 'sanity-esbuild-app optionTitle';
}
}
23 changes: 21 additions & 2 deletions packages/custom-esbuild/README.md
Expand Up @@ -100,7 +100,7 @@ Builder options:
"build": {
"builder": "@angular-builders/custom-esbuild:application",
"options": {
"plugins": ["./esbuild/plugins.ts", "./esbuild/plugin-2.js"],
"plugins": ["./esbuild/plugins.ts", { "path": "./esbuild/plugin-2.js", "options": { "key": "value" } }],
"indexHtmlTransformer": "./esbuild/index-html-transformer.js",
"outputPath": "dist/my-cool-client",
"index": "src/index.html",
Expand All @@ -112,7 +112,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:
The plugin file can export either a single plugin, a single plugin with configuration or a list of plugins:
```ts
// esbuild/plugins.ts
Expand All @@ -129,6 +129,25 @@ const defineTextPlugin: Plugin = {
export default defineTextPlugin;
```
OR:
```ts
// esbuild/plugins.ts
import type { Plugin, PluginBuild } from 'esbuild';

function defineRewritePathPlugin(options: { text: string }): Plugin {
return {
name: 'define-text',
setup(build: PluginBuild) {
const options = build.initialOptions;
options.define.buildText = JSON.stringify(options.text);
},
};
};

export default defineTextPlugin;
```
Or:
```ts
Expand Down
20 changes: 19 additions & 1 deletion packages/custom-esbuild/src/application/schema.ext.json
Expand Up @@ -7,7 +7,25 @@
"description": "A list of paths to ESBuild plugins",
"default": [],
"items": {
"type": "string",
"anyOf": [
{
"type": "string"
},
{
"type": "object",
"properties": {
"path": {
"type": "string"
},
"options": {
"type": "object"
}
},
"required": [
"path"
]
}
],
"uniqueItems": true
}
},
Expand Down
2 changes: 2 additions & 0 deletions packages/custom-esbuild/src/custom-esbuild-schema.ts
@@ -1,5 +1,7 @@
import { ApplicationBuilderOptions, DevServerBuilderOptions } from '@angular-devkit/build-angular';

export type PluginConfig = string | { path: string; options?: Record<string, unknown> };

export type CustomEsbuildApplicationSchema = ApplicationBuilderOptions & {
plugins?: string[];
indexHtmlTransformer?: string;
Expand Down
18 changes: 13 additions & 5 deletions packages/custom-esbuild/src/load-plugins.ts
Expand Up @@ -2,17 +2,25 @@ 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';

export async function loadPlugins(
paths: string[] | undefined,
pluginConfig: PluginConfig[] | undefined,
workspaceRoot: string,
tsConfig: string,
logger: logging.LoggerApi
logger: logging.LoggerApi,
): Promise<Plugin[]> {
const plugins = await Promise.all(
(paths || []).map(pluginPath =>
loadModule<Plugin | Plugin[]>(path.join(workspaceRoot, pluginPath), tsConfig, logger)
)
(pluginConfig || []).map(async pluginConfig => {
if (typeof pluginConfig === 'string') {
return loadModule<Plugin | Plugin[]>(path.join(workspaceRoot, pluginConfig), tsConfig, logger);
} else {
const pluginFactory = await loadModule<Plugin>(path.join(workspaceRoot, pluginConfig.path), tsConfig, logger);
return pluginFactory(pluginConfig.options);
}

},
),
);

return plugins.flat();
Expand Down

0 comments on commit 81890a5

Please sign in to comment.