esbuild-plugins-node-modules-polyfill
Advanced tools
Comparing version 1.4.2-next.8681bbb.0 to 1.5.0-next.12bc0dd.0
@@ -10,3 +10,3 @@ import { Plugin } from 'esbuild'; | ||
interface NodePolyfillsOptions { | ||
fallback?: 'empty' | 'none'; | ||
fallback?: 'empty' | 'error' | 'none'; | ||
globals?: { | ||
@@ -16,3 +16,3 @@ Buffer?: boolean; | ||
}; | ||
modules?: string[] | Record<string, boolean | 'empty'>; | ||
modules?: string[] | Record<string, boolean | 'empty' | 'error'>; | ||
name?: string; | ||
@@ -19,0 +19,0 @@ namespace?: string; |
@@ -5,2 +5,3 @@ 'use strict'; | ||
var path = require('path'); | ||
var process = require('process'); | ||
var localPkg = require('local-pkg'); | ||
@@ -13,2 +14,3 @@ var esbuild = require('esbuild'); | ||
var path__default = /*#__PURE__*/_interopDefault(path); | ||
var process__default = /*#__PURE__*/_interopDefault(process); | ||
@@ -120,3 +122,9 @@ var __defProp = Object.defineProperty; | ||
var nodeModulesPolyfillPlugin = /* @__PURE__ */ __name((options = {}) => { | ||
const { globals = {}, modules: modulesOption = module$1.builtinModules, fallback, namespace = NAME, name = NAME } = options; | ||
const { | ||
globals = {}, | ||
modules: modulesOption = module$1.builtinModules, | ||
fallback = "none", | ||
namespace = NAME, | ||
name = NAME | ||
} = options; | ||
if (namespace.endsWith("commonjs")) { | ||
@@ -128,8 +136,17 @@ throw new Error(`namespace ${namespace} must not end with commonjs`); | ||
} | ||
if (namespace.endsWith("error")) { | ||
throw new Error(`namespace ${namespace} must not end with error`); | ||
} | ||
const modules = Array.isArray(modulesOption) ? Object.fromEntries(modulesOption.map((mod) => [mod, true])) : modulesOption; | ||
const commonjsNamespace = `${namespace}-commonjs`; | ||
const emptyNamespace = `${namespace}-empty`; | ||
const errorNamespace = `${namespace}-error`; | ||
const shouldDetectErrorModules = fallback === "error" || Object.values(modules).includes("error"); | ||
return { | ||
name, | ||
setup: ({ onLoad, onResolve, initialOptions }) => { | ||
setup: ({ onLoad, onResolve, onEnd, initialOptions }) => { | ||
if (shouldDetectErrorModules && initialOptions.write !== false) { | ||
throw new Error(`The "write" build option must be set to false when using the "error" polyfill type`); | ||
} | ||
const root = initialOptions.absWorkingDir ?? process__default.default.cwd(); | ||
if (initialOptions.define && !initialOptions.define.global) { | ||
@@ -156,11 +173,31 @@ initialOptions.define.global = "globalThis"; | ||
}); | ||
onLoad({ filter: /.*/, namespace: errorNamespace }, (args) => { | ||
return { | ||
loader: "js", | ||
contents: `module.exports = ${JSON.stringify( | ||
// This encoded string is detected and parsed at the end of the build to report errors | ||
`__POLYFILL_ERROR_START__::MODULE::${args.path}::IMPORTER::${args.pluginData.importer}::__POLYFILL_ERROR_END__` | ||
)}` | ||
}; | ||
}); | ||
onLoad({ filter: /.*/, namespace }, loader); | ||
onLoad({ filter: /.*/, namespace: commonjsNamespace }, loader); | ||
const bundledModules = fallback === "empty" ? module$1.builtinModules : Object.keys(modules).filter((moduleName) => module$1.builtinModules.includes(moduleName)); | ||
const bundledModules = fallback === "none" ? Object.keys(modules).filter((moduleName) => module$1.builtinModules.includes(moduleName)) : module$1.builtinModules; | ||
const filter = new RegExp(`^(?:node:)?(?:${bundledModules.map(escapeRegex).join("|")})$`); | ||
const resolver = /* @__PURE__ */ __name(async (args) => { | ||
const emptyResult = { | ||
namespace: emptyNamespace, | ||
path: args.path, | ||
sideEffects: false | ||
const result = { | ||
empty: { | ||
namespace: emptyNamespace, | ||
path: args.path, | ||
sideEffects: false | ||
}, | ||
error: { | ||
namespace: errorNamespace, | ||
path: args.path, | ||
sideEffects: false, | ||
pluginData: { | ||
importer: path__default.default.relative(root, args.importer).replace(/\\/g, "/") | ||
} | ||
}, | ||
none: void 0 | ||
}; | ||
@@ -170,16 +207,20 @@ if (initialOptions.platform === "browser") { | ||
const browserFieldValue = packageJson?.browser?.[args.path]; | ||
if (browserFieldValue !== void 0) | ||
if (browserFieldValue === false) { | ||
return result.empty; | ||
} | ||
if (browserFieldValue !== void 0) { | ||
return; | ||
} | ||
} | ||
const moduleName = normalizeNodeBuiltinPath(args.path); | ||
const fallbackResult = fallback === "empty" ? emptyResult : void 0; | ||
if (!modules[moduleName]) { | ||
return fallbackResult; | ||
const polyfillOption = modules[moduleName]; | ||
if (!polyfillOption) { | ||
return result[fallback]; | ||
} | ||
if (modules[moduleName] === "empty") { | ||
return emptyResult; | ||
if (polyfillOption === "error" || polyfillOption === "empty") { | ||
return result[polyfillOption]; | ||
} | ||
const polyfill = await getCachedPolyfillPath(moduleName).catch(() => null); | ||
if (!polyfill) { | ||
return fallbackResult; | ||
const polyfillPath2 = await getCachedPolyfillPath(moduleName).catch(() => null); | ||
if (!polyfillPath2) { | ||
return result[fallback]; | ||
} | ||
@@ -195,2 +236,22 @@ const ignoreRequire = args.namespace === commonjsNamespace; | ||
onResolve({ filter }, resolver); | ||
onEnd(({ outputFiles = [] }) => { | ||
if (!shouldDetectErrorModules) | ||
return; | ||
const errors = []; | ||
const { outfile, outExtension = {} } = initialOptions; | ||
const jsExtension = outfile ? path__default.default.extname(outfile) : outExtension[".js"] || ".js"; | ||
const jsFiles = outputFiles.filter((file) => path__default.default.extname(file.path) === jsExtension); | ||
for (const file of jsFiles) { | ||
const matches = file.text.matchAll( | ||
/__POLYFILL_ERROR_START__::MODULE::(?<module>.+?)::IMPORTER::(?<importer>.+?)::__POLYFILL_ERROR_END__/g | ||
); | ||
for (const { groups } of matches) { | ||
errors.push({ | ||
pluginName: name, | ||
text: `Module "${groups.module}" is not polyfilled, imported by "${groups.importer}"` | ||
}); | ||
} | ||
} | ||
return { errors }; | ||
}); | ||
} | ||
@@ -197,0 +258,0 @@ }; |
{ | ||
"name": "esbuild-plugins-node-modules-polyfill", | ||
"version": "1.4.2-next.8681bbb.0", | ||
"version": "1.5.0-next.12bc0dd.0", | ||
"description": "Polyfills nodejs builtin modules and globals for the browser.", | ||
@@ -5,0 +5,0 @@ "main": "dist/index.js", |
131
README.md
@@ -43,3 +43,3 @@ <div align="center"> | ||
Optionally configure which modules to polyfill: | ||
### Inject globals when detected: | ||
@@ -50,9 +50,14 @@ ```ts | ||
build({ | ||
plugins: [nodeModulesPolyfillPlugin({ | ||
modules: ['crypto'], | ||
})], | ||
plugins: [ | ||
nodeModulesPolyfillPlugin({ | ||
globals: { | ||
process: true, | ||
Buffer: true, | ||
}, | ||
}), | ||
], | ||
}); | ||
``` | ||
Optionally provide empty polyfills: | ||
### Configure which modules to polyfill: | ||
@@ -63,13 +68,29 @@ ```ts | ||
build({ | ||
plugins: [nodeModulesPolyfillPlugin({ | ||
modules: { | ||
fs: 'empty', | ||
crypto: true, | ||
} | ||
})], | ||
plugins: [ | ||
nodeModulesPolyfillPlugin({ | ||
modules: ['crypto'], | ||
}), | ||
], | ||
}); | ||
``` | ||
Optionally provide empty fallbacks for any unpolyfilled or unconfigured modules: | ||
```ts | ||
import { nodeModulesPolyfillPlugin } from 'esbuild-plugins-node-modules-polyfill'; | ||
import { build } from 'esbuild'; | ||
build({ | ||
plugins: [ | ||
nodeModulesPolyfillPlugin({ | ||
modules: { | ||
crypto: true, | ||
fs: false, | ||
}, | ||
}), | ||
], | ||
}); | ||
``` | ||
### Provide empty polyfills: | ||
### Provide empty polyfills for specific modules: | ||
```ts | ||
@@ -79,12 +100,14 @@ import { nodeModulesPolyfillPlugin } from 'esbuild-plugins-node-modules-polyfill'; | ||
build({ | ||
plugins: [nodeModulesPolyfillPlugin({ | ||
fallback: 'empty', | ||
modules: { | ||
crypto: true, | ||
} | ||
})], | ||
plugins: [ | ||
nodeModulesPolyfillPlugin({ | ||
modules: { | ||
fs: 'empty', | ||
crypto: true, | ||
}, | ||
}), | ||
], | ||
}); | ||
``` | ||
Optionally inject globals when detected: | ||
#### Provide empty fallbacks for any unpolyfilled modules: | ||
@@ -95,11 +118,69 @@ ```ts | ||
build({ | ||
plugins: [nodeModulesPolyfillPlugin({ | ||
globals: { | ||
process: true, | ||
Buffer: true, | ||
} | ||
})], | ||
plugins: [ | ||
nodeModulesPolyfillPlugin({ | ||
fallback: 'empty', | ||
}), | ||
], | ||
}); | ||
``` | ||
#### Provide empty fallbacks for any unconfigured modules: | ||
```ts | ||
import { nodeModulesPolyfillPlugin } from 'esbuild-plugins-node-modules-polyfill'; | ||
import { build } from 'esbuild'; | ||
build({ | ||
plugins: [ | ||
nodeModulesPolyfillPlugin({ | ||
fallback: 'empty', | ||
modules: { | ||
crypto: true, | ||
}, | ||
}), | ||
], | ||
}); | ||
``` | ||
### Fail the build when certain modules are used: | ||
> **Note** | ||
> The `write` option in `esbuild` must be `false` to support this. | ||
```ts | ||
import { nodeModulesPolyfillPlugin } from 'esbuild-plugins-node-modules-polyfill'; | ||
import { build } from 'esbuild'; | ||
const buildResult = await build({ | ||
write: false, | ||
plugins: [ | ||
nodeModulesPolyfillPlugin({ | ||
modules: { | ||
crypto: 'error', | ||
path: true, | ||
}, | ||
}), | ||
], | ||
}); | ||
``` | ||
### Fail the build when a module is not polyfilled or configured: | ||
> **Note** | ||
> The `write` option in `esbuild` must be `false` to support this. | ||
```ts | ||
import { nodeModulesPolyfillPlugin } from 'esbuild-plugins-node-modules-polyfill'; | ||
import { build } from 'esbuild'; | ||
const buildResult = await build({ | ||
write: false, | ||
plugins: [ | ||
nodeModulesPolyfillPlugin({ | ||
fallback: 'error', | ||
modules: { | ||
path: true, | ||
}, | ||
}), | ||
], | ||
}); | ||
``` | ||
## Buy me some doughnuts | ||
@@ -106,0 +187,0 @@ |
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
46219
277
195