Skip to content

Commit

Permalink
release v3
Browse files Browse the repository at this point in the history
  • Loading branch information
Inqnuam committed Jul 20, 2024
1 parent 68b80a6 commit 5bf3668
Show file tree
Hide file tree
Showing 30 changed files with 1,415 additions and 130 deletions.
4 changes: 2 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
node_modules
/dist
tests
dist
.DS_Store
3 changes: 2 additions & 1 deletion .npmignore
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,5 @@ yarn.lock
src
build.mjs
TODO.md
tsconfig.json
tsconfig.json
test
78 changes: 58 additions & 20 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,10 @@ const { umdWrapper } = require("esbuild-plugin-umd-wrapper");

esbuild
.build({
entryPoints: ["input.js"],
outdir: "public",
format: "umd", // or "cjs"
entryPoints: ["src/input.js"],
outdir: "dist",
bundle: true,
format: "umd", // or "cjs"
plugins: [umdWrapper()],
})
.then((result) => console.log(result))
Expand All @@ -30,32 +30,70 @@ esbuild

### Customize the wrapper.

```js
const umdWrapperOptions = {
libraryName: "myLib", // default is unset
external: "inherit", // <= default
amdLoaderName: "define" // <= default
}

// usual esbuild config
{
...
plugins: [umdWrapper(umdWrapperOptions)],
...
}
See [all options](https://github.com/Inqnuam/esbuild-plugin-umd-wrapper/blob/main/src/declaration.ts).

```js
esbuild.build({
entryPoints: ["src/input.js"],
outdir: "dist",
bundle: true,
format: "umd", // or "cjs"
plugins: [umdWrapper({ libraryName: "myLibrary" })],
});
```

Wrapper options will be applied for all `entryPoints`.

---

## Notes

The plugin will be triggered only if esbuild `format` is set to "cjs" or "umd".
The plugin will be triggered only if esbuild `format` is set to "cjs" or "umd".
Before esbuild execution the plugin will set that option to "cjs".
By default `external` is inherited from your esbuild options. If you know what are you doing you can change that value to an array of strings. Ex:

## Known Issues

Internally the wrapper plugin uses esbuild's `banner` and `footer` options to create UMD.
In consequence running multiple esbuild builds concurrently reusing the same Build option object references _MAY_ produce unexpected build output
Ex:

```js
const umdWrapperOptions = {
external: ["react", "react-dom", "classnames"],
const options = {
entryPoints: ["src/input.js"],
outdir: "dist",
bundle: true,
format: "umd",
plugins: [umdWrapper({ libraryName: "myLibrary" })],
};

// ❌ avoid this
await Promise.all([esbuild.build(options), esbuild.build({ ...options, minify: true, outdir: "dist/min" })]);
```

```js
// ❌ avoid this
esbuild.build(options);
esbuild.build({ ...options, minify: true, outdir: "dist/min" });
```

```js
// ✅ Its better
await esbuild.build(options);
await esbuild.build({ ...options, minify: true, outdir: "dist/min" });
```

---

> When I use `export default myFunc`, resulting output is not directly callable!
> Instead it's an object `{__esModule: true, default: myFunc}`
This is not a bug, and it's not related to umd-wrapper-plugin.
This is how esbuild transpiles `export default` to CJS.

As a workaround use `exports = myFunc`.

---

### Examples

If you are not familiar with UMD, see usage examples in [test](test) directory.
11 changes: 9 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "esbuild-plugin-umd-wrapper",
"version": "2.0.3",
"version": "3.0.0",
"description": "UMD wrapper for esbuild",
"main": "dist/index.js",
"typings": "dist/index.d.ts",
Expand All @@ -15,17 +15,24 @@
"esbuild",
"plugin",
"umd",
"amd",
"wrapper",
"requirejs",
"define"
],
"devDependencies": {
"@types/node": "^18.14.0",
"@types/react": "^18.3.3",
"@types/react-dom": "^18.3.0",
"esbuild": "^0.23.0",
"react": "18.3.1",
"react-dom": "18.3.1",
"serve": "^14.2.3",
"typescript": "^5.5.3"
},
"scripts": {
"build": "node ./build.mjs",
"dev": "node ./build.mjs watch"
"dev": "node ./build.mjs watch",
"serve": "serve test/dist"
}
}
74 changes: 74 additions & 0 deletions src/declaration.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,80 @@
export interface UmdOptions {
/** Module identifier used for both AMD loader and Global scope.
*
* This option attachs your module to global (ex: `window.myAwsomeLibrary`) and provides an id to AMD loader.
*
* If you need to define different identifier for AMD loader, use `amdId` option.
*/
libraryName?: string;
/**
* External dependencies
*
* By default this value is inherited from your esbuild `external` option if provided.
*
* If you are using esbuild's `{packages: "external"}`, use this option to indicate external dependencies for your module.
*
* If you are using wildcard in your esbuild `external` option (ex: `{external: ["@aws-sdk/*"]}`), default `inherit` value may not produce expected output.
* Use this option to provide external dependency full path/name, example:
* ```js
esbuild.build({
entryPoints: ["./src/app.ts"],
outdir: "dist",
format: "umd",
external: ["@aws-sdk/*"],
plugins: [umdWrapper({external: ["@aws-sdk/client-dynamodb", "@aws-sdk/client-sns"]})]
})
* ```
* Another case when this option is useful is when you define an external dependecy in esbuild `external` but its never used in the code, example:
* ```js
esbuild.build({
entryPoints: ["./src/app.ts", "./src/utils.ts"],
outdir: "dist",
format: "umd",
external: ["react", "react-dom/client", "express", "ts-node"], // where "express" and "ts-node" are never used for your UMD output
plugins: [umdWrapper({external: ["react", "react-dom/client"]})]
})
* ```
* @see https://github.com/amdjs/amdjs-api/blob/master/AMD.md#dependencies-
* @default "inherit"
* */
external?: "inherit" | string[];
/** @default "define" */
amdLoaderName?: string;
/**
* If `amdId` is not provided, `libraryName` will be used when provided.
*
* If `libraryName` is provided AND `amdId` is `false`, your module will not have AMD id
* but will still be accessible from global scope using `libraryName`.
*
* @see https://github.com/amdjs/amdjs-api/blob/master/AMD.md#id-
* @default undefined
*/
amdId?: string | false | undefined;
/**
* `this` value in Global Context.
* Default value should work in all JS runtimes
* @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/this#global_context
* @example "window"
*
*/
globalIdentifier?: string;
/**
* Help UMD wrapper to map external dependency paths to global scope variables
* @default
* ```json
* {
* "react": "React",
* "react-dom": "ReactDOM",
* "react-dom/client": "ReactDOM",
* "jquery": "jQuery",
* "lodash": "_",
* "underscore": "_",
* "cropperjs": "Cropper",
* "@popperjs/core": "Popper",
* "backbone": "Backbone",
* }
* ``` */
globals?: {
[packagePath: string]: string;
};
}
58 changes: 40 additions & 18 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,24 @@
import { umdFooter, defaultOptions } from "./lib/constants";
import { umdFooter, defaultOptions, knownDepNames, supportedFormats } from "./lib/constants";
import { getUmdBanner } from "./lib/getUmdBanner";
import type { Plugin } from "esbuild";
import type { UmdOptions } from "./declaration";

const invalidAmdId = new Error(`Boolean true is not a valid value for "amdId" option.\nOnly a string or boolean false is accepted.`);

const umdWrapper = (customOptions: UmdOptions = {}) => {
let options: UmdOptions = { ...defaultOptions, ...customOptions };

const plugin: Plugin = {
name: "umd-wrapper",
setup(build) {
const { initialOptions } = build;
// @ts-ignore
if (!["umd", "cjs"].includes(initialOptions.format)) {

// @ts-expect-error
if (options.amdId === true) {
throw invalidAmdId;
}

if (!supportedFormats.includes(initialOptions.format)) {
return;
}
const external = options.external == "inherit" ? initialOptions.external ?? [] : Array.isArray(options.external) ? options.external : [];
Expand All @@ -20,28 +27,19 @@ const umdWrapper = (customOptions: UmdOptions = {}) => {
amdLoader: options.amdLoaderName,
lib: options.libraryName,
globalIdentifier: options.globalIdentifier,
dependencyNames: { ...knownDepNames, ...options.globals },
amdId: options.amdId,
};
initialOptions.format = "cjs";
initialOptions.metafile = true;

if (initialOptions.footer) {
if (initialOptions.footer.js) {
if (!initialOptions.footer.js.includes(umdFooter)) {
initialOptions.footer.js += umdFooter;
}
} else {
initialOptions.footer.js = umdFooter;
}
} else {
initialOptions.footer = {
js: umdFooter,
};
}
let needsFooter = true;

const umdBanner = getUmdBanner(umdBannerOptions);
if (initialOptions.banner) {
if (initialOptions.banner.js) {
if (!initialOptions.banner.js.includes(umdBanner)) {
if (initialOptions.banner.js.startsWith(umdBanner) || initialOptions.banner.js.endsWith(umdBanner)) {
needsFooter = false;
} else {
initialOptions.banner.js += umdBanner;
}
} else {
Expand All @@ -52,6 +50,30 @@ const umdWrapper = (customOptions: UmdOptions = {}) => {
js: umdBanner,
};
}

if (needsFooter) {
if (initialOptions.footer) {
if (initialOptions.footer.js) {
initialOptions.footer.js += umdFooter;
} else {
initialOptions.footer.js = umdFooter;
}
} else {
initialOptions.footer = {
js: umdFooter,
};
}
}

build.onEnd(() => {
if (initialOptions.banner?.js?.includes(umdBanner)) {
initialOptions.banner.js = initialOptions.banner.js.replace(umdBanner, "");
}

if (initialOptions.footer?.js?.includes(umdFooter)) {
initialOptions.footer.js = initialOptions.footer.js.replace(umdFooter, "");
}
});
},
};

Expand Down
Loading

0 comments on commit 5bf3668

Please sign in to comment.