unplugin-purge-polyfills
Version:
A tiny plugin to replace package imports with better native code.
265 lines (261 loc) • 7.36 kB
JavaScript
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 };