UNPKG

unplugin-purge-polyfills

Version:

A tiny plugin to replace package imports with better native code.

265 lines (261 loc) 7.36 kB
import { createFilter } from '@rollup/pluginutils'; import { defu } from 'defu'; import MagicString from 'magic-string'; import { findStaticImports, parseStaticImport } from 'mlly'; import { createUnplugin } from 'unplugin'; const defaultPolyfills = { // Micro-utilities "is-number": { default: `v => typeof v === 'number'` }, "is-plain-object": { default: 'v => typeof v === "object" && v !== null && v.constructor === Object' }, "is-primitve": { default: 'v => v === null || (typeof v !== "function" && typeof v !== "object")' }, "is-regexp": { default: 'v => Object.prototype.toString.call(v) === "[object RegExp]"' }, "is-travis": { default: '() => "TRAVIS" in process.env' }, "is-npm": { default: '() => process.env.npm_config_user_agent?.startsWith("npm")' }, "clone-regexp": { default: "v => new RegExp(v)" }, "split-lines": { default: "str => str.split(/\\r?\\n/)" }, "is-windows": { default: '() => process.platform === "win32"' }, "is-whitespace": { default: 'str => str.trim() === ""' }, "is-string": { default: `v => typeof v === 'string'` }, "is-odd": { default: "n => (n % 2) === 1" }, "is-even": { default: "n => (n % 2) === 0" }, "call-bind": { default: "v => Function.call.bind(v)" }, "es-get-iterator": { default: "v => v[Symbol.iterator]?.()" }, "es-set-tostringtag": { default: "(target, value) => Object.defineProperty(target, Symbol.toStringTag, { value, configurable: true })" }, "is-array-buffer": { default: 'v => Object.prototype.toString.call(v) === "[object ArrayBuffer]"' }, "is-boolean-object": { default: 'v => Object.prototype.toString.call(v) === "[object Boolean]"' }, "is-date-object": { default: 'v => Object.prototype.toString.call(v) === "[object Date]"' }, "is-negative-zero": { default: "v => Object.is(v, -0)" }, "is-number-object": { default: 'v => Object.prototype.toString.call(v) === "[object Number]"' }, "is-primitive": { default: 'v => v === null || (typeof v !== "function" && typeof v !== "object")' }, // native replacements "object.entries": { default: "Object.entries" }, "date": { default: "Date" }, "array.of": { default: "Array.of" }, "number.isnan": { default: "Number.isNaN" }, "array.prototype.findindex": { default: "Array.prototype.findIndex" }, "array.from": { default: "Array.from" }, "object-is": { default: "Object.is" }, "hasown": { default: "(obj, prop) => obj.hasOwnProperty(prop)" }, "has-own-prop": { default: "Object.hasOwn ?? ((o, p) => Object.prototype.hasOwnProperty.call(o, p))" }, "array-map": { default: "Array.prototype.map" }, "is-nan": { default: "Number.isNaN" }, "function-bind": { default: "Function.prototype.bind" }, "regexp.prototype.flags": { default: "RegExp.prototype.flags" }, "array.prototype.find": { default: "Array.prototype.find" }, "object-keys": { default: "Object.keys" }, "define-properties": { default: "Object.defineProperties" }, "left-pad": { default: "String.prototype.padStart" }, "pad-left": { default: "String.prototype.padStart" }, "filter-array": { default: "Array.prototype.filter" }, "array-every": { default: "Array.prototype.every" }, "index-of": { default: "Array.prototype.indexOf" }, "last-index-of": { default: "Array.prototype.lastIndexOf" }, // https://github.com/esm-dev/esm.sh/blob/main/server/embed/polyfills "abort-controller": { AbortSignal: "AbortSignal", AbortController: "AbortController", default: "AbortController" }, "array-flatten": { default: '(a, d) => a.flat(typeof d < "u" ? d : Infinity)' }, "array-includes": { default: "(a, p, i) => a.includes(p, i)" }, "has-own": { default: "Object.hasOwn" }, "has-proto": { default: "() => { const foo = { bar: {} }; return ({ __proto__: foo }).bar === foo.bar && !({ __proto__: null } instanceof Object) }" }, "has-symbols": { default: "() => true" }, "object-assign": { default: "Object.assign" } }; const CJS_STATIC_IMPORT_RE = /(?<=\s|^|[;}])(const|var|let)((?<imports>[\p{L}\p{M}\w\t\n\r $*,/{}@.]+))=\s*require\(["']\s*(?<specifier>(?<=")[^"]*[^\s"](?=\s*")|(?<=')[^']*[^\s'](?=\s*'))\s*["']\)[\s;]*/gmu; const VIRTUAL_POLYFILL_PREFIX = "virtual:purge-polyfills:"; const purgePolyfills = createUnplugin((opts = {}) => { const _knownMods = defu(opts.replacements, defaultPolyfills); for (const mod in _knownMods) { if (!_knownMods[mod]) { delete _knownMods[mod]; } } const knownMods = _knownMods; const specifiers = new Set(Object.keys(knownMods)); const logs = /* @__PURE__ */ new Set(); const filter = createFilter(opts.include || [/\.[cm][tj]sx?$/], opts.exclude); function load(id) { if (id.startsWith(VIRTUAL_POLYFILL_PREFIX)) { const polyfillId = id.slice(VIRTUAL_POLYFILL_PREFIX.length); let code = ""; for (const exportName in knownMods[polyfillId]) { if (exportName === "default") { code += `export default ${knownMods[polyfillId].default} `; continue; } code += `export const ${exportName} = ${knownMods[polyfillId][exportName]} `; } logs.add(`Replaced import from ${polyfillId}.`); return code; } } function resolveId(id) { if (specifiers.has(id)) { return VIRTUAL_POLYFILL_PREFIX + id; } } function transform(code, id) { if (!filter(id)) { return; } const staticImports = findStaticImports(code); for (const match of code.matchAll(CJS_STATIC_IMPORT_RE)) { staticImports.push({ type: "static", ...match.groups, code: match[0], start: match.index, end: (match.index || 0) + match[0].length }); } if (!staticImports.length) return; const polyfillImports = staticImports.filter((i) => specifiers.has(i.specifier)); if (!polyfillImports.length) return; const s = new MagicString(code); for (const polyfillImport of polyfillImports) { const parsed = parseStaticImport(polyfillImport); let code2 = ""; const names = parsed.namedImports || {}; if (parsed.defaultImport) { names.default = parsed.defaultImport; } for (const p in names) { const replacement = knownMods[polyfillImport.specifier]?.[p]; if (replacement) { code2 += `const ${names[p]} = ${replacement}; `; } logs.add(`Inlined replacement from ${polyfillImport.specifier}.`); } s.overwrite(polyfillImport.start, polyfillImport.end, code2); } if (s.hasChanged()) { return { code: s.toString(), map: opts.sourcemap ? s.generateMap({ hires: true }) : null }; } } return { name: "unplugin-purge-polyfills", ...opts.mode === "transform" ? { transform } : { resolveId, load }, buildEnd() { if (opts.logLevel === "quiet") { return; } if (opts.logLevel === "verbose") { for (const log of logs) { console.log(log); } console.log(`Purged ${logs.size} polyfills.`); } } }; }); export { defaultPolyfills, purgePolyfills };