From aa53635a6b3a5b754e194fa695a3d689165a9672 Mon Sep 17 00:00:00 2001 From: Maximilian Koeller Date: Fri, 16 Feb 2024 17:43:13 +0100 Subject: [PATCH] feat(custom-esbuild): add support for plugin configuration --- .../sanity-esbuild-app-esm/angular.json | 2 +- .../esbuild/define-text-by-option-plugin.cjs | 11 +++++++++ .../esbuild/define-text-by-option-plugin.js | 11 +++++++++ .../esbuild/define-text-by-option-plugin.ts | 13 +++++++++++ .../src/app/app.component.html | 2 ++ .../src/app/app.component.ts | 3 +++ .../sanity-esbuild-app/angular.json | 2 +- .../esbuild/define-text-by-option-plugin.js | 11 +++++++++ .../esbuild/define-text-by-option-plugin.mjs | 11 +++++++++ .../src/app/app.component.html | 2 ++ .../src/app/app.component.ts | 3 +++ packages/custom-esbuild/README.md | 23 +++++++++++++++++-- .../src/application/schema.ext.json | 20 +++++++++++++++- .../src/custom-esbuild-schema.ts | 2 ++ packages/custom-esbuild/src/load-plugins.ts | 18 +++++++++++---- 15 files changed, 124 insertions(+), 10 deletions(-) create mode 100644 examples/custom-esbuild/sanity-esbuild-app-esm/esbuild/define-text-by-option-plugin.cjs create mode 100644 examples/custom-esbuild/sanity-esbuild-app-esm/esbuild/define-text-by-option-plugin.js create mode 100644 examples/custom-esbuild/sanity-esbuild-app-esm/esbuild/define-text-by-option-plugin.ts create mode 100644 examples/custom-esbuild/sanity-esbuild-app/esbuild/define-text-by-option-plugin.js create mode 100644 examples/custom-esbuild/sanity-esbuild-app/esbuild/define-text-by-option-plugin.mjs diff --git a/examples/custom-esbuild/sanity-esbuild-app-esm/angular.json b/examples/custom-esbuild/sanity-esbuild-app-esm/angular.json index 7ed10f251..36b479d10 100644 --- a/examples/custom-esbuild/sanity-esbuild-app-esm/angular.json +++ b/examples/custom-esbuild/sanity-esbuild-app-esm/angular.json @@ -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", diff --git a/examples/custom-esbuild/sanity-esbuild-app-esm/esbuild/define-text-by-option-plugin.cjs b/examples/custom-esbuild/sanity-esbuild-app-esm/esbuild/define-text-by-option-plugin.cjs new file mode 100644 index 000000000..c3d072e5f --- /dev/null +++ b/examples/custom-esbuild/sanity-esbuild-app-esm/esbuild/define-text-by-option-plugin.cjs @@ -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; diff --git a/examples/custom-esbuild/sanity-esbuild-app-esm/esbuild/define-text-by-option-plugin.js b/examples/custom-esbuild/sanity-esbuild-app-esm/esbuild/define-text-by-option-plugin.js new file mode 100644 index 000000000..7c6cf02b6 --- /dev/null +++ b/examples/custom-esbuild/sanity-esbuild-app-esm/esbuild/define-text-by-option-plugin.js @@ -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; diff --git a/examples/custom-esbuild/sanity-esbuild-app-esm/esbuild/define-text-by-option-plugin.ts b/examples/custom-esbuild/sanity-esbuild-app-esm/esbuild/define-text-by-option-plugin.ts new file mode 100644 index 000000000..2e32da11d --- /dev/null +++ b/examples/custom-esbuild/sanity-esbuild-app-esm/esbuild/define-text-by-option-plugin.ts @@ -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; diff --git a/examples/custom-esbuild/sanity-esbuild-app-esm/src/app/app.component.html b/examples/custom-esbuild/sanity-esbuild-app-esm/src/app/app.component.html index d7f6fe579..6b3672d03 100644 --- a/examples/custom-esbuild/sanity-esbuild-app-esm/src/app/app.component.html +++ b/examples/custom-esbuild/sanity-esbuild-app-esm/src/app/app.component.html @@ -1,2 +1,4 @@

{{ title }}

{{ subtitle }}

+

{{ optionTitle }}

+``` diff --git a/examples/custom-esbuild/sanity-esbuild-app-esm/src/app/app.component.ts b/examples/custom-esbuild/sanity-esbuild-app-esm/src/app/app.component.ts index 13530b452..90418811b 100644 --- a/examples/custom-esbuild/sanity-esbuild-app-esm/src/app/app.component.ts +++ b/examples/custom-esbuild/sanity-esbuild-app-esm/src/app/app.component.ts @@ -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', @@ -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'; } } diff --git a/examples/custom-esbuild/sanity-esbuild-app/angular.json b/examples/custom-esbuild/sanity-esbuild-app/angular.json index 9d5f0c0b2..e639d8d2b 100644 --- a/examples/custom-esbuild/sanity-esbuild-app/angular.json +++ b/examples/custom-esbuild/sanity-esbuild-app/angular.json @@ -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", diff --git a/examples/custom-esbuild/sanity-esbuild-app/esbuild/define-text-by-option-plugin.js b/examples/custom-esbuild/sanity-esbuild-app/esbuild/define-text-by-option-plugin.js new file mode 100644 index 000000000..c3d072e5f --- /dev/null +++ b/examples/custom-esbuild/sanity-esbuild-app/esbuild/define-text-by-option-plugin.js @@ -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; diff --git a/examples/custom-esbuild/sanity-esbuild-app/esbuild/define-text-by-option-plugin.mjs b/examples/custom-esbuild/sanity-esbuild-app/esbuild/define-text-by-option-plugin.mjs new file mode 100644 index 000000000..7c6cf02b6 --- /dev/null +++ b/examples/custom-esbuild/sanity-esbuild-app/esbuild/define-text-by-option-plugin.mjs @@ -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; diff --git a/examples/custom-esbuild/sanity-esbuild-app/src/app/app.component.html b/examples/custom-esbuild/sanity-esbuild-app/src/app/app.component.html index d7f6fe579..6b3672d03 100644 --- a/examples/custom-esbuild/sanity-esbuild-app/src/app/app.component.html +++ b/examples/custom-esbuild/sanity-esbuild-app/src/app/app.component.html @@ -1,2 +1,4 @@

{{ title }}

{{ subtitle }}

+

{{ optionTitle }}

+``` diff --git a/examples/custom-esbuild/sanity-esbuild-app/src/app/app.component.ts b/examples/custom-esbuild/sanity-esbuild-app/src/app/app.component.ts index f8ae812e3..4519515d6 100644 --- a/examples/custom-esbuild/sanity-esbuild-app/src/app/app.component.ts +++ b/examples/custom-esbuild/sanity-esbuild-app/src/app/app.component.ts @@ -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', @@ -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'; } } diff --git a/packages/custom-esbuild/README.md b/packages/custom-esbuild/README.md index 878e2570d..9c96140b2 100644 --- a/packages/custom-esbuild/README.md +++ b/packages/custom-esbuild/README.md @@ -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", @@ -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 @@ -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 diff --git a/packages/custom-esbuild/src/application/schema.ext.json b/packages/custom-esbuild/src/application/schema.ext.json index 01e7e7dd8..f3ab84900 100644 --- a/packages/custom-esbuild/src/application/schema.ext.json +++ b/packages/custom-esbuild/src/application/schema.ext.json @@ -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 } }, diff --git a/packages/custom-esbuild/src/custom-esbuild-schema.ts b/packages/custom-esbuild/src/custom-esbuild-schema.ts index eac9dc1e8..59c07d6e3 100644 --- a/packages/custom-esbuild/src/custom-esbuild-schema.ts +++ b/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 }; + export type CustomEsbuildApplicationSchema = ApplicationBuilderOptions & { plugins?: string[]; indexHtmlTransformer?: string; diff --git a/packages/custom-esbuild/src/load-plugins.ts b/packages/custom-esbuild/src/load-plugins.ts index f0e154943..f31a0adb9 100644 --- a/packages/custom-esbuild/src/load-plugins.ts +++ b/packages/custom-esbuild/src/load-plugins.ts @@ -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 { const plugins = await Promise.all( - (paths || []).map(pluginPath => - loadModule(path.join(workspaceRoot, pluginPath), tsConfig, logger) - ) + (pluginConfig || []).map(async pluginConfig => { + if (typeof pluginConfig === 'string') { + return loadModule(path.join(workspaceRoot, pluginConfig), tsConfig, logger); + } else { + const pluginFactory = await loadModule(path.join(workspaceRoot, pluginConfig.path), tsConfig, logger); + return pluginFactory(pluginConfig.options); + } + + }, + ), ); return plugins.flat();