You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
575 lines
19 KiB
575 lines
19 KiB
'use strict';
|
|
|
|
const path = require('node:path');
|
|
const process = require('node:process');
|
|
const pathe = require('pathe');
|
|
const scule = require('scule');
|
|
const MagicString = require('magic-string');
|
|
const mlly = require('mlly');
|
|
const stripLiteral = require('strip-literal');
|
|
|
|
function _interopDefaultCompat (e) { return e && typeof e === 'object' && 'default' in e ? e.default : e; }
|
|
|
|
const process__default = /*#__PURE__*/_interopDefaultCompat(process);
|
|
const MagicString__default = /*#__PURE__*/_interopDefaultCompat(MagicString);
|
|
|
|
const excludeRE = [
|
|
// imported/exported from other module
|
|
/\b(import|export)\b([\w$*{},\s]+?)\bfrom\s*["']/g,
|
|
// defined as function
|
|
/\bfunction\s*([\w$]+)\s*\(/g,
|
|
// defined as class
|
|
/\bclass\s*([\w$]+)\s*\{/g,
|
|
// defined as local variable
|
|
// eslint-disable-next-line regexp/no-super-linear-backtracking
|
|
/\b(?:const|let|var)\s+?(\[.*?\]|\{.*?\}|.+?)\s*?[=;\n]/gs
|
|
];
|
|
const importAsRE = /^.*\sas\s+/;
|
|
const separatorRE = /[,[\]{}\n]|\b(?:import|export)\b/g;
|
|
const matchRE = /(^|\.\.\.|(?:\bcase|\?)\s+|[^\w$/)]|\bextends\s+)([\w$]+)\s*(?=[.()[\]}:;?+\-*&|`<>,\n]|\b(?:instanceof|in)\b|$|(?<=extends\s+\w+)\s+\{)/g;
|
|
const regexRE = /\/\S*?(?<!\\)(?<!\[[^\]]*)\/[gimsuy]*/g;
|
|
function stripCommentsAndStrings(code, options) {
|
|
return stripLiteral.stripLiteral(code, options).replace(regexRE, 'new RegExp("")');
|
|
}
|
|
|
|
function defineUnimportPreset(preset) {
|
|
return preset;
|
|
}
|
|
const safePropertyName = /^[a-z$_][\w$]*$/i;
|
|
function stringifyWith(withValues) {
|
|
let withDefs = "";
|
|
for (let entries = Object.entries(withValues), l = entries.length, i = 0; i < l; i++) {
|
|
const [prop, value] = entries[i];
|
|
withDefs += safePropertyName.test(prop) ? prop : JSON.stringify(prop);
|
|
withDefs += `: ${JSON.stringify(String(value))}`;
|
|
if (i + 1 !== l)
|
|
withDefs += ", ";
|
|
}
|
|
return `{ ${withDefs} }`;
|
|
}
|
|
function stringifyImports(imports, isCJS = false) {
|
|
const map = toImportModuleMap(imports);
|
|
return Object.entries(map).flatMap(([name, importSet]) => {
|
|
const entries = [];
|
|
const imports2 = Array.from(importSet).filter((i) => {
|
|
if (!i.name || i.as === "") {
|
|
let importStr;
|
|
if (isCJS) {
|
|
importStr = `require('${name}');`;
|
|
} else {
|
|
importStr = `import '${name}'`;
|
|
if (i.with)
|
|
importStr += ` with ${stringifyWith(i.with)}`;
|
|
importStr += ";";
|
|
}
|
|
entries.push(importStr);
|
|
return false;
|
|
} else if (i.name === "default" || i.name === "=") {
|
|
let importStr;
|
|
if (isCJS) {
|
|
importStr = i.name === "=" ? `const ${i.as} = require('${name}');` : `const { default: ${i.as} } = require('${name}');`;
|
|
} else {
|
|
importStr = `import ${i.as} from '${name}'`;
|
|
if (i.with)
|
|
importStr += ` with ${stringifyWith(i.with)}`;
|
|
importStr += ";";
|
|
}
|
|
entries.push(importStr);
|
|
return false;
|
|
} else if (i.name === "*") {
|
|
let importStr;
|
|
if (isCJS) {
|
|
importStr = `const ${i.as} = require('${name}');`;
|
|
} else {
|
|
importStr = `import * as ${i.as} from '${name}'`;
|
|
if (i.with)
|
|
importStr += ` with ${stringifyWith(i.with)}`;
|
|
importStr += ";";
|
|
}
|
|
entries.push(importStr);
|
|
return false;
|
|
} else if (!isCJS && i.with) {
|
|
entries.push(`import { ${stringifyImportAlias(i)} } from '${name}' with ${stringifyWith(i.with)};`);
|
|
return false;
|
|
}
|
|
return true;
|
|
});
|
|
if (imports2.length) {
|
|
const importsAs = imports2.map((i) => stringifyImportAlias(i, isCJS));
|
|
entries.push(
|
|
isCJS ? `const { ${importsAs.join(", ")} } = require('${name}');` : `import { ${importsAs.join(", ")} } from '${name}';`
|
|
);
|
|
}
|
|
return entries;
|
|
}).join("\n");
|
|
}
|
|
function dedupeImports(imports, warn) {
|
|
const map = /* @__PURE__ */ new Map();
|
|
const indexToRemove = /* @__PURE__ */ new Set();
|
|
imports.filter((i) => !i.disabled).forEach((i, idx) => {
|
|
if (i.declarationType === "enum")
|
|
return;
|
|
const name = i.as ?? i.name;
|
|
if (!map.has(name)) {
|
|
map.set(name, idx);
|
|
return;
|
|
}
|
|
const other = imports[map.get(name)];
|
|
if (other.from === i.from) {
|
|
indexToRemove.add(idx);
|
|
return;
|
|
}
|
|
const diff = (other.priority || 1) - (i.priority || 1);
|
|
if (diff === 0)
|
|
warn(`Duplicated imports "${name}", the one from "${other.from}" has been ignored and "${i.from}" is used`);
|
|
if (diff <= 0) {
|
|
indexToRemove.add(map.get(name));
|
|
map.set(name, idx);
|
|
} else {
|
|
indexToRemove.add(idx);
|
|
}
|
|
});
|
|
return imports.filter((_, idx) => !indexToRemove.has(idx));
|
|
}
|
|
function toExports(imports, fileDir, includeType = false) {
|
|
const map = toImportModuleMap(imports, includeType);
|
|
return Object.entries(map).flatMap(([name, imports2]) => {
|
|
if (isFilePath(name))
|
|
name = name.replace(/\.[a-z]+$/i, "");
|
|
if (fileDir && pathe.isAbsolute(name)) {
|
|
name = pathe.relative(fileDir, name);
|
|
if (!name.match(/^[./]/))
|
|
name = `./${name}`;
|
|
}
|
|
const entries = [];
|
|
const filtered = Array.from(imports2).filter((i) => {
|
|
if (i.name === "*") {
|
|
entries.push(`export * as ${i.as} from '${name}';`);
|
|
return false;
|
|
}
|
|
return true;
|
|
});
|
|
if (filtered.length)
|
|
entries.push(`export { ${filtered.map((i) => stringifyImportAlias(i, false)).join(", ")} } from '${name}';`);
|
|
return entries;
|
|
}).join("\n");
|
|
}
|
|
function stripFileExtension(path) {
|
|
return path.replace(/\.[a-z]+$/i, "");
|
|
}
|
|
function toTypeDeclarationItems(imports, options) {
|
|
return imports.map((i) => {
|
|
const from = options?.resolvePath?.(i) || stripFileExtension(i.typeFrom || i.from);
|
|
let typeDef = "";
|
|
if (i.with)
|
|
typeDef += `import('${from}', { with: ${stringifyWith(i.with)} })`;
|
|
else
|
|
typeDef += `import('${from}')`;
|
|
if (i.name !== "*" && i.name !== "=")
|
|
typeDef += `['${i.name}']`;
|
|
return `const ${i.as}: typeof ${typeDef}`;
|
|
}).sort();
|
|
}
|
|
function toTypeDeclarationFile(imports, options) {
|
|
const items = toTypeDeclarationItems(imports, options);
|
|
const {
|
|
exportHelper = true
|
|
} = options || {};
|
|
let declaration = "";
|
|
if (exportHelper)
|
|
declaration += "export {}\n";
|
|
declaration += `declare global {
|
|
${items.map((i) => ` ${i}`).join("\n")}
|
|
}`;
|
|
return declaration;
|
|
}
|
|
function makeTypeModulesMap(imports, resolvePath2) {
|
|
const modulesMap = /* @__PURE__ */ new Map();
|
|
const resolveImportFrom = typeof resolvePath2 === "function" ? (i) => {
|
|
return resolvePath2(i) || stripFileExtension(i.typeFrom || i.from);
|
|
} : (i) => stripFileExtension(i.typeFrom || i.from);
|
|
for (const import_ of imports) {
|
|
const from = resolveImportFrom(import_);
|
|
let module = modulesMap.get(from);
|
|
if (!module) {
|
|
module = { typeImports: /* @__PURE__ */ new Set(), starTypeImport: void 0 };
|
|
modulesMap.set(from, module);
|
|
}
|
|
if (import_.name === "*") {
|
|
if (import_.as)
|
|
module.starTypeImport = import_;
|
|
} else {
|
|
module.typeImports.add(import_);
|
|
}
|
|
}
|
|
return modulesMap;
|
|
}
|
|
function toTypeReExports(imports, options) {
|
|
const importsMap = makeTypeModulesMap(imports, options?.resolvePath);
|
|
const code = Array.from(importsMap).flatMap(([from, module]) => {
|
|
const { starTypeImport, typeImports } = module;
|
|
const strings = [];
|
|
if (typeImports.size) {
|
|
const typeImportNames = Array.from(typeImports).map(({ name, as }) => {
|
|
if (as && as !== name)
|
|
return `${name} as ${as}`;
|
|
return name;
|
|
});
|
|
strings.push(
|
|
"// @ts-ignore",
|
|
`export type { ${typeImportNames.join(", ")} } from '${from}'`
|
|
);
|
|
}
|
|
if (starTypeImport) {
|
|
strings.push(
|
|
"// @ts-ignore",
|
|
`export type * as ${starTypeImport.as} from '${from}'`
|
|
);
|
|
}
|
|
if (strings.length) {
|
|
strings.push(
|
|
// This is a workaround for a TypeScript issue where type-only re-exports are not properly initialized.
|
|
`import('${from}')`
|
|
);
|
|
}
|
|
return strings;
|
|
});
|
|
return `// for type re-export
|
|
declare global {
|
|
${code.map((i) => ` ${i}`).join("\n")}
|
|
}`;
|
|
}
|
|
function stringifyImportAlias(item, isCJS = false) {
|
|
return item.as === void 0 || item.name === item.as ? item.name : isCJS ? `${item.name}: ${item.as}` : `${item.name} as ${item.as}`;
|
|
}
|
|
function toImportModuleMap(imports, includeType = false) {
|
|
const map = {};
|
|
for (const _import of imports) {
|
|
if (_import.type && !includeType)
|
|
continue;
|
|
if (!map[_import.from])
|
|
map[_import.from] = /* @__PURE__ */ new Set();
|
|
map[_import.from].add(_import);
|
|
}
|
|
return map;
|
|
}
|
|
function getString(code) {
|
|
if (typeof code === "string")
|
|
return code;
|
|
return code.toString();
|
|
}
|
|
function getMagicString(code) {
|
|
if (typeof code === "string")
|
|
return new MagicString__default(code);
|
|
return code;
|
|
}
|
|
function addImportToCode(code, imports, isCJS = false, mergeExisting = false, injectAtLast = false, firstOccurrence = Number.POSITIVE_INFINITY, onResolved, onStringified) {
|
|
let newImports = [];
|
|
const s = getMagicString(code);
|
|
let _staticImports;
|
|
const strippedCode = stripCommentsAndStrings(s.original);
|
|
function findStaticImportsLazy() {
|
|
if (!_staticImports) {
|
|
_staticImports = mlly.findStaticImports(s.original).filter((i) => Boolean(strippedCode.slice(i.start, i.end).trim())).map((i) => mlly.parseStaticImport(i));
|
|
}
|
|
return _staticImports;
|
|
}
|
|
function hasShebang() {
|
|
const shebangRegex = /^#!.+/;
|
|
return shebangRegex.test(s.original);
|
|
}
|
|
if (mergeExisting && !isCJS) {
|
|
const existingImports = findStaticImportsLazy();
|
|
const map = /* @__PURE__ */ new Map();
|
|
imports.forEach((i) => {
|
|
const target = existingImports.find((e) => e.specifier === i.from && e.imports.startsWith("{"));
|
|
if (!target)
|
|
return newImports.push(i);
|
|
if (!map.has(target))
|
|
map.set(target, []);
|
|
map.get(target).push(i);
|
|
});
|
|
for (const [target, items] of map.entries()) {
|
|
const strings = items.map((i) => `${stringifyImportAlias(i)}, `);
|
|
const importLength = target.code.match(/^\s*import\s*\{/)?.[0]?.length;
|
|
if (importLength)
|
|
s.appendLeft(target.start + importLength, ` ${strings.join("").trim()}`);
|
|
}
|
|
} else {
|
|
newImports = imports;
|
|
}
|
|
newImports = onResolved?.(newImports) ?? newImports;
|
|
let newEntries = stringifyImports(newImports, isCJS);
|
|
newEntries = onStringified?.(newEntries, newImports) ?? newEntries;
|
|
if (newEntries) {
|
|
const insertionIndex = injectAtLast ? findStaticImportsLazy().reverse().find((i) => i.end <= firstOccurrence)?.end ?? 0 : 0;
|
|
if (insertionIndex > 0)
|
|
s.appendRight(insertionIndex, `
|
|
${newEntries}
|
|
`);
|
|
else if (hasShebang())
|
|
s.appendLeft(s.original.indexOf("\n") + 1, `
|
|
${newEntries}
|
|
`);
|
|
else
|
|
s.prepend(`${newEntries}
|
|
`);
|
|
}
|
|
return {
|
|
s,
|
|
get code() {
|
|
return s.toString();
|
|
}
|
|
};
|
|
}
|
|
function normalizeImports(imports) {
|
|
for (const _import of imports)
|
|
_import.as = _import.as ?? _import.name;
|
|
return imports;
|
|
}
|
|
function resolveIdAbsolute(id, parentId) {
|
|
return mlly.resolvePath(id, {
|
|
url: parentId
|
|
});
|
|
}
|
|
function isFilePath(path) {
|
|
return path.startsWith(".") || pathe.isAbsolute(path) || path.includes("://");
|
|
}
|
|
const toImports = stringifyImports;
|
|
|
|
const contextRE$1 = /\b_ctx\.([$\w]+)\b/g;
|
|
const UNREF_KEY = "__unimport_unref_";
|
|
const VUE_TEMPLATE_NAME = "unimport:vue-template";
|
|
function vueTemplateAddon() {
|
|
const self = {
|
|
name: VUE_TEMPLATE_NAME,
|
|
async transform(s, id) {
|
|
if (!s.original.includes("_ctx.") || s.original.includes(UNREF_KEY))
|
|
return s;
|
|
const matches = Array.from(s.original.matchAll(contextRE$1));
|
|
const imports = await this.getImports();
|
|
let targets = [];
|
|
for (const match of matches) {
|
|
const name = match[1];
|
|
const item = imports.find((i) => i.as === name);
|
|
if (!item)
|
|
continue;
|
|
const start = match.index;
|
|
const end = start + match[0].length;
|
|
const tempName = `__unimport_${name}`;
|
|
s.overwrite(start, end, `(${JSON.stringify(name)} in _ctx ? _ctx.${name} : ${UNREF_KEY}(${tempName}))`);
|
|
if (!targets.find((i) => i.as === tempName)) {
|
|
targets.push({
|
|
...item,
|
|
as: tempName
|
|
});
|
|
}
|
|
}
|
|
if (targets.length) {
|
|
targets.push({
|
|
name: "unref",
|
|
from: "vue",
|
|
as: UNREF_KEY
|
|
});
|
|
for (const addon of this.addons) {
|
|
if (addon === self)
|
|
continue;
|
|
targets = await addon.injectImportsResolved?.call(this, targets, s, id) ?? targets;
|
|
}
|
|
let injection = stringifyImports(targets);
|
|
for (const addon of this.addons) {
|
|
if (addon === self)
|
|
continue;
|
|
injection = await addon.injectImportsStringified?.call(this, injection, targets, s, id) ?? injection;
|
|
}
|
|
s.prepend(injection);
|
|
}
|
|
return s;
|
|
},
|
|
async declaration(dts, options) {
|
|
const imports = await this.getImports();
|
|
const items = imports.map((i) => {
|
|
if (i.type || i.dtsDisabled)
|
|
return "";
|
|
const from = options?.resolvePath?.(i) || i.from;
|
|
return `readonly ${i.as}: UnwrapRef<typeof import('${from}')${i.name !== "*" ? `['${i.name}']` : ""}>`;
|
|
}).filter(Boolean).sort();
|
|
const extendItems = items.map((i) => ` ${i}`).join("\n");
|
|
return `${dts}
|
|
// for vue template auto import
|
|
import { UnwrapRef } from 'vue'
|
|
declare module 'vue' {
|
|
interface ComponentCustomProperties {
|
|
${extendItems}
|
|
}
|
|
}`;
|
|
}
|
|
};
|
|
return self;
|
|
}
|
|
|
|
const contextRE = /resolveDirective as _resolveDirective/;
|
|
const contextText = `${contextRE.source}, `;
|
|
const directiveRE = /(?:var|const) (\w+) = _resolveDirective\("([\w.-]+)"\);?\s*/g;
|
|
const VUE_DIRECTIVES_NAME = "unimport:vue-directives";
|
|
function vueDirectivesAddon(options = {}) {
|
|
function isDirective(importEntry) {
|
|
let isDirective2 = importEntry.meta?.vueDirective === true;
|
|
if (isDirective2) {
|
|
return true;
|
|
}
|
|
isDirective2 = options.isDirective?.(normalizePath(process__default.cwd(), importEntry.from), importEntry) ?? false;
|
|
if (isDirective2) {
|
|
importEntry.meta ?? (importEntry.meta = {});
|
|
importEntry.meta.vueDirective = true;
|
|
}
|
|
return isDirective2;
|
|
}
|
|
const self = {
|
|
name: VUE_DIRECTIVES_NAME,
|
|
async transform(s, id) {
|
|
if (!s.original.match(contextRE))
|
|
return s;
|
|
const matches = Array.from(s.original.matchAll(directiveRE)).sort((a, b) => b.index - a.index);
|
|
if (!matches.length)
|
|
return s;
|
|
let targets = [];
|
|
for await (const [
|
|
begin,
|
|
end,
|
|
importEntry
|
|
] of findDirectives(
|
|
isDirective,
|
|
matches,
|
|
this.getImports()
|
|
)) {
|
|
s.overwrite(begin, end, "");
|
|
targets.push(importEntry);
|
|
}
|
|
if (!targets.length)
|
|
return s;
|
|
s.replace(contextText, "");
|
|
for (const addon of this.addons) {
|
|
if (addon === self)
|
|
continue;
|
|
targets = await addon.injectImportsResolved?.call(this, targets, s, id) ?? targets;
|
|
}
|
|
let injection = stringifyImports(targets);
|
|
for (const addon of this.addons) {
|
|
if (addon === self)
|
|
continue;
|
|
injection = await addon.injectImportsStringified?.call(this, injection, targets, s, id) ?? injection;
|
|
}
|
|
s.prepend(injection);
|
|
return s;
|
|
},
|
|
async declaration(dts, options2) {
|
|
const directivesMap = await this.getImports().then((imports) => {
|
|
return imports.filter(isDirective).reduce((acc, i) => {
|
|
if (i.type || i.dtsDisabled)
|
|
return acc;
|
|
let name;
|
|
if (i.name === "default" && (i.as === "default" || !i.as)) {
|
|
const file = path.basename(i.from);
|
|
const idx = file.indexOf(".");
|
|
name = idx > -1 ? file.slice(0, idx) : file;
|
|
} else {
|
|
name = i.as ?? i.name;
|
|
}
|
|
name = name[0] === "v" ? scule.camelCase(name) : scule.camelCase(`v-${name}`);
|
|
if (!acc.has(name)) {
|
|
acc.set(name, i);
|
|
}
|
|
return acc;
|
|
}, /* @__PURE__ */ new Map());
|
|
});
|
|
if (!directivesMap.size)
|
|
return dts;
|
|
const directives = Array.from(directivesMap.entries()).map(([name, i]) => ` ${name}: typeof import('${options2?.resolvePath?.(i) || i.from}')['${i.name}']`).sort().join("\n");
|
|
return `${dts}
|
|
// for vue directives auto import
|
|
declare module 'vue' {
|
|
interface ComponentCustomProperties {
|
|
${directives}
|
|
}
|
|
interface GlobalDirectives {
|
|
${directives}
|
|
}
|
|
}`;
|
|
}
|
|
};
|
|
return self;
|
|
}
|
|
function resolvePath(cwd, path) {
|
|
return path[0] === "." ? pathe.resolve(cwd, path) : path;
|
|
}
|
|
function normalizePath(cwd, path) {
|
|
return resolvePath(cwd, path).replace(/\\/g, "/");
|
|
}
|
|
async function* findDirectives(isDirective, regexArray, importsPromise) {
|
|
const imports = (await importsPromise).filter(isDirective);
|
|
if (!imports.length)
|
|
return;
|
|
const symbols = regexArray.reduce((acc, regex) => {
|
|
const [all, symbol, resolveDirectiveName] = regex;
|
|
if (acc.has(symbol))
|
|
return acc;
|
|
acc.set(symbol, [
|
|
regex.index,
|
|
regex.index + all.length,
|
|
scule.kebabCase(resolveDirectiveName)
|
|
]);
|
|
return acc;
|
|
}, /* @__PURE__ */ new Map());
|
|
for (const [symbol, data] of symbols.entries()) {
|
|
yield* findDirective(imports, symbol, data);
|
|
}
|
|
}
|
|
function* findDirective(imports, symbol, [begin, end, importName]) {
|
|
let resolvedName;
|
|
for (const i of imports) {
|
|
if (i.name === "default" && (i.as === "default" || !i.as)) {
|
|
const file = path.basename(i.from);
|
|
const idx = file.indexOf(".");
|
|
resolvedName = scule.kebabCase(idx > -1 ? file.slice(0, idx) : file);
|
|
} else {
|
|
resolvedName = scule.kebabCase(i.as ?? i.name);
|
|
}
|
|
if (resolvedName[0] === "v") {
|
|
resolvedName = resolvedName.slice(resolvedName[1] === "-" ? 2 : 1);
|
|
}
|
|
if (resolvedName === importName) {
|
|
yield [
|
|
begin,
|
|
end,
|
|
{ ...i, name: i.name, as: symbol }
|
|
];
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
exports.VUE_DIRECTIVES_NAME = VUE_DIRECTIVES_NAME;
|
|
exports.VUE_TEMPLATE_NAME = VUE_TEMPLATE_NAME;
|
|
exports.addImportToCode = addImportToCode;
|
|
exports.dedupeImports = dedupeImports;
|
|
exports.defineUnimportPreset = defineUnimportPreset;
|
|
exports.excludeRE = excludeRE;
|
|
exports.getMagicString = getMagicString;
|
|
exports.getString = getString;
|
|
exports.importAsRE = importAsRE;
|
|
exports.matchRE = matchRE;
|
|
exports.normalizeImports = normalizeImports;
|
|
exports.resolveIdAbsolute = resolveIdAbsolute;
|
|
exports.separatorRE = separatorRE;
|
|
exports.stringifyImports = stringifyImports;
|
|
exports.stripCommentsAndStrings = stripCommentsAndStrings;
|
|
exports.stripFileExtension = stripFileExtension;
|
|
exports.toExports = toExports;
|
|
exports.toImports = toImports;
|
|
exports.toTypeDeclarationFile = toTypeDeclarationFile;
|
|
exports.toTypeDeclarationItems = toTypeDeclarationItems;
|
|
exports.toTypeReExports = toTypeReExports;
|
|
exports.vueDirectivesAddon = vueDirectivesAddon;
|
|
exports.vueTemplateAddon = vueTemplateAddon;
|