unimport.b860d1ad.mjs 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374
  1. import { isAbsolute, relative } from 'pathe';
  2. import { resolvePath, findStaticImports, parseStaticImport } from 'mlly';
  3. import MagicString from 'magic-string';
  4. import { stripLiteral } from 'strip-literal';
  5. const excludeRE = [
  6. // imported/exported from other module
  7. /\b(import|export)\b([\w$*{},\s]+?)\bfrom\s*["']/g,
  8. // defined as function
  9. /\bfunction\s*([\w$]+)\s*\(/g,
  10. // defined as class
  11. /\bclass\s*([\w$]+)\s*\{/g,
  12. // defined as local variable
  13. // eslint-disable-next-line regexp/no-super-linear-backtracking
  14. /\b(?:const|let|var)\s+?(\[.*?\]|\{.*?\}|.+?)\s*?[=;\n]/gs
  15. ];
  16. const importAsRE = /^.*\sas\s+/;
  17. const separatorRE = /[,[\]{}\n]|\b(?:import|export)\b/g;
  18. const matchRE = /(^|\.\.\.|(?:\bcase|\?)\s+|[^\w$/)]|\bextends\s+)([\w$]+)\s*(?=[.()[\]}:;?+\-*&|`<>,\n]|\b(?:instanceof|in)\b|$|(?<=extends\s+\w+)\s+\{)/g;
  19. const regexRE = /\/\S*?(?<!\\)(?<!\[[^\]]*)\/[gimsuy]*/g;
  20. function stripCommentsAndStrings(code, options) {
  21. return stripLiteral(code, options).replace(regexRE, 'new RegExp("")');
  22. }
  23. function defineUnimportPreset(preset) {
  24. return preset;
  25. }
  26. const safePropertyName = /^[a-z$_][\w$]*$/i;
  27. function stringifyWith(withValues) {
  28. let withDefs = "";
  29. for (let entries = Object.entries(withValues), l = entries.length, i = 0; i < l; i++) {
  30. const [prop, value] = entries[i];
  31. withDefs += safePropertyName.test(prop) ? prop : JSON.stringify(prop);
  32. withDefs += `: ${JSON.stringify(String(value))}`;
  33. if (i + 1 !== l)
  34. withDefs += ", ";
  35. }
  36. return `{ ${withDefs} }`;
  37. }
  38. function stringifyImports(imports, isCJS = false) {
  39. const map = toImportModuleMap(imports);
  40. return Object.entries(map).flatMap(([name, importSet]) => {
  41. const entries = [];
  42. const imports2 = Array.from(importSet).filter((i) => {
  43. if (!i.name || i.as === "") {
  44. let importStr;
  45. if (isCJS) {
  46. importStr = `require('${name}');`;
  47. } else {
  48. importStr = `import '${name}'`;
  49. if (i.with)
  50. importStr += ` with ${stringifyWith(i.with)}`;
  51. importStr += ";";
  52. }
  53. entries.push(importStr);
  54. return false;
  55. } else if (i.name === "default" || i.name === "=") {
  56. let importStr;
  57. if (isCJS) {
  58. importStr = i.name === "=" ? `const ${i.as} = require('${name}');` : `const { default: ${i.as} } = require('${name}');`;
  59. } else {
  60. importStr = `import ${i.as} from '${name}'`;
  61. if (i.with)
  62. importStr += ` with ${stringifyWith(i.with)}`;
  63. importStr += ";";
  64. }
  65. entries.push(importStr);
  66. return false;
  67. } else if (i.name === "*") {
  68. let importStr;
  69. if (isCJS) {
  70. importStr = `const ${i.as} = require('${name}');`;
  71. } else {
  72. importStr = `import * as ${i.as} from '${name}'`;
  73. if (i.with)
  74. importStr += ` with ${stringifyWith(i.with)}`;
  75. importStr += ";";
  76. }
  77. entries.push(importStr);
  78. return false;
  79. } else if (!isCJS && i.with) {
  80. entries.push(`import { ${stringifyImportAlias(i)} } from '${name}' with ${stringifyWith(i.with)};`);
  81. return false;
  82. }
  83. return true;
  84. });
  85. if (imports2.length) {
  86. const importsAs = imports2.map((i) => stringifyImportAlias(i, isCJS));
  87. entries.push(
  88. isCJS ? `const { ${importsAs.join(", ")} } = require('${name}');` : `import { ${importsAs.join(", ")} } from '${name}';`
  89. );
  90. }
  91. return entries;
  92. }).join("\n");
  93. }
  94. function dedupeImports(imports, warn) {
  95. const map = /* @__PURE__ */ new Map();
  96. const indexToRemove = /* @__PURE__ */ new Set();
  97. imports.filter((i) => !i.disabled).forEach((i, idx) => {
  98. if (i.declarationType === "enum")
  99. return;
  100. const name = i.as ?? i.name;
  101. if (!map.has(name)) {
  102. map.set(name, idx);
  103. return;
  104. }
  105. const other = imports[map.get(name)];
  106. if (other.from === i.from) {
  107. indexToRemove.add(idx);
  108. return;
  109. }
  110. const diff = (other.priority || 1) - (i.priority || 1);
  111. if (diff === 0)
  112. warn(`Duplicated imports "${name}", the one from "${other.from}" has been ignored and "${i.from}" is used`);
  113. if (diff <= 0) {
  114. indexToRemove.add(map.get(name));
  115. map.set(name, idx);
  116. } else {
  117. indexToRemove.add(idx);
  118. }
  119. });
  120. return imports.filter((_, idx) => !indexToRemove.has(idx));
  121. }
  122. function toExports(imports, fileDir, includeType = false) {
  123. const map = toImportModuleMap(imports, includeType);
  124. return Object.entries(map).flatMap(([name, imports2]) => {
  125. if (isFilePath(name))
  126. name = name.replace(/\.[a-z]+$/i, "");
  127. if (fileDir && isAbsolute(name)) {
  128. name = relative(fileDir, name);
  129. if (!name.match(/^[./]/))
  130. name = `./${name}`;
  131. }
  132. const entries = [];
  133. const filtered = Array.from(imports2).filter((i) => {
  134. if (i.name === "*") {
  135. entries.push(`export * as ${i.as} from '${name}';`);
  136. return false;
  137. }
  138. return true;
  139. });
  140. if (filtered.length)
  141. entries.push(`export { ${filtered.map((i) => stringifyImportAlias(i, false)).join(", ")} } from '${name}';`);
  142. return entries;
  143. }).join("\n");
  144. }
  145. function stripFileExtension(path) {
  146. return path.replace(/\.[a-z]+$/i, "");
  147. }
  148. function toTypeDeclarationItems(imports, options) {
  149. return imports.map((i) => {
  150. const from = options?.resolvePath?.(i) || stripFileExtension(i.typeFrom || i.from);
  151. let typeDef = "";
  152. if (i.with)
  153. typeDef += `import('${from}', { with: ${stringifyWith(i.with)} })`;
  154. else
  155. typeDef += `import('${from}')`;
  156. if (i.name !== "*" && i.name !== "=")
  157. typeDef += `['${i.name}']`;
  158. return `const ${i.as}: typeof ${typeDef}`;
  159. }).sort();
  160. }
  161. function toTypeDeclarationFile(imports, options) {
  162. const items = toTypeDeclarationItems(imports, options);
  163. const {
  164. exportHelper = true
  165. } = options || {};
  166. let declaration = "";
  167. if (exportHelper)
  168. declaration += "export {}\n";
  169. declaration += `declare global {
  170. ${items.map((i) => ` ${i}`).join("\n")}
  171. }`;
  172. return declaration;
  173. }
  174. function toTypeReExports(imports, options) {
  175. const importsMap = /* @__PURE__ */ new Map();
  176. imports.forEach((i) => {
  177. const from = options?.resolvePath?.(i) || stripFileExtension(i.typeFrom || i.from);
  178. const list = importsMap.get(from) || [];
  179. list.push(i);
  180. importsMap.set(from, list);
  181. });
  182. const code = Array.from(importsMap.entries()).flatMap(([from, items]) => {
  183. const names = items.map((i) => {
  184. let name = i.name === "*" ? "default" : i.name;
  185. if (i.as && i.as !== name)
  186. name += ` as ${i.as}`;
  187. return name;
  188. });
  189. return [
  190. // Because of TypeScript's limitation, it errors when re-exporting type in declare.
  191. // But it actually works so we use @ts-ignore to dismiss the error.
  192. "// @ts-ignore",
  193. // Re-export type
  194. `export type { ${names.join(", ")} } from '${from}'`,
  195. // If a module is only been re-exported as type, TypeScript will not initialize it for some reason.
  196. // Adding an import statement will fix it.
  197. `import('${from}')`
  198. ];
  199. });
  200. return `// for type re-export
  201. declare global {
  202. ${code.map((i) => ` ${i}`).join("\n")}
  203. }`;
  204. }
  205. function stringifyImportAlias(item, isCJS = false) {
  206. return item.as === void 0 || item.name === item.as ? item.name : isCJS ? `${item.name}: ${item.as}` : `${item.name} as ${item.as}`;
  207. }
  208. function toImportModuleMap(imports, includeType = false) {
  209. const map = {};
  210. for (const _import of imports) {
  211. if (_import.type && !includeType)
  212. continue;
  213. if (!map[_import.from])
  214. map[_import.from] = /* @__PURE__ */ new Set();
  215. map[_import.from].add(_import);
  216. }
  217. return map;
  218. }
  219. function getString(code) {
  220. if (typeof code === "string")
  221. return code;
  222. return code.toString();
  223. }
  224. function getMagicString(code) {
  225. if (typeof code === "string")
  226. return new MagicString(code);
  227. return code;
  228. }
  229. function addImportToCode(code, imports, isCJS = false, mergeExisting = false, injectAtLast = false, firstOccurrence = Number.POSITIVE_INFINITY, onResolved, onStringified) {
  230. let newImports = [];
  231. const s = getMagicString(code);
  232. let _staticImports;
  233. const strippedCode = stripCommentsAndStrings(s.original);
  234. function findStaticImportsLazy() {
  235. if (!_staticImports) {
  236. _staticImports = findStaticImports(s.original).filter((i) => Boolean(strippedCode.slice(i.start, i.end).trim())).map((i) => parseStaticImport(i));
  237. }
  238. return _staticImports;
  239. }
  240. function hasShebang() {
  241. const shebangRegex = /^#!.+/;
  242. return shebangRegex.test(s.original);
  243. }
  244. if (mergeExisting && !isCJS) {
  245. const existingImports = findStaticImportsLazy();
  246. const map = /* @__PURE__ */ new Map();
  247. imports.forEach((i) => {
  248. const target = existingImports.find((e) => e.specifier === i.from && e.imports.startsWith("{"));
  249. if (!target)
  250. return newImports.push(i);
  251. if (!map.has(target))
  252. map.set(target, []);
  253. map.get(target).push(i);
  254. });
  255. for (const [target, items] of map.entries()) {
  256. const strings = items.map((i) => `${stringifyImportAlias(i)}, `);
  257. const importLength = target.code.match(/^\s*import\s*\{/)?.[0]?.length;
  258. if (importLength)
  259. s.appendLeft(target.start + importLength, ` ${strings.join("").trim()}`);
  260. }
  261. } else {
  262. newImports = imports;
  263. }
  264. newImports = onResolved?.(newImports) ?? newImports;
  265. let newEntries = stringifyImports(newImports, isCJS);
  266. newEntries = onStringified?.(newEntries, newImports) ?? newEntries;
  267. if (newEntries) {
  268. const insertionIndex = injectAtLast ? findStaticImportsLazy().reverse().find((i) => i.end <= firstOccurrence)?.end ?? 0 : 0;
  269. if (insertionIndex > 0)
  270. s.appendRight(insertionIndex, `
  271. ${newEntries}
  272. `);
  273. else if (hasShebang())
  274. s.appendLeft(s.original.indexOf("\n") + 1, `
  275. ${newEntries}
  276. `);
  277. else
  278. s.prepend(`${newEntries}
  279. `);
  280. }
  281. return {
  282. s,
  283. get code() {
  284. return s.toString();
  285. }
  286. };
  287. }
  288. function normalizeImports(imports) {
  289. for (const _import of imports)
  290. _import.as = _import.as ?? _import.name;
  291. return imports;
  292. }
  293. function resolveIdAbsolute(id, parentId) {
  294. return resolvePath(id, {
  295. url: parentId
  296. });
  297. }
  298. function isFilePath(path) {
  299. return path.startsWith(".") || isAbsolute(path) || path.includes("://");
  300. }
  301. const toImports = stringifyImports;
  302. const contextRE = /\b_ctx\.([$\w]+)\b/g;
  303. const UNREF_KEY = "__unimport_unref_";
  304. function vueTemplateAddon() {
  305. const self = {
  306. async transform(s, id) {
  307. if (!s.original.includes("_ctx.") || s.original.includes(UNREF_KEY))
  308. return s;
  309. const matches = Array.from(s.original.matchAll(contextRE));
  310. const imports = await this.getImports();
  311. let targets = [];
  312. for (const match of matches) {
  313. const name = match[1];
  314. const item = imports.find((i) => i.as === name);
  315. if (!item)
  316. continue;
  317. const start = match.index;
  318. const end = start + match[0].length;
  319. const tempName = `__unimport_${name}`;
  320. s.overwrite(start, end, `(${JSON.stringify(name)} in _ctx ? _ctx.${name} : ${UNREF_KEY}(${tempName}))`);
  321. if (!targets.find((i) => i.as === tempName)) {
  322. targets.push({
  323. ...item,
  324. as: tempName
  325. });
  326. }
  327. }
  328. if (targets.length) {
  329. targets.push({
  330. name: "unref",
  331. from: "vue",
  332. as: UNREF_KEY
  333. });
  334. for (const addon of this.addons) {
  335. if (addon === self)
  336. continue;
  337. targets = await addon.injectImportsResolved?.call(this, targets, s, id) ?? targets;
  338. }
  339. let injection = stringifyImports(targets);
  340. for (const addon of this.addons) {
  341. if (addon === self)
  342. continue;
  343. injection = await addon.injectImportsStringified?.call(this, injection, targets, s, id) ?? injection;
  344. }
  345. s.prepend(injection);
  346. }
  347. return s;
  348. },
  349. async declaration(dts, options) {
  350. const imports = await this.getImports();
  351. const items = imports.map((i) => {
  352. if (i.type || i.dtsDisabled)
  353. return "";
  354. const from = options?.resolvePath?.(i) || i.from;
  355. return `readonly ${i.as}: UnwrapRef<typeof import('${from}')${i.name !== "*" ? `['${i.name}']` : ""}>`;
  356. }).filter(Boolean).sort();
  357. const extendItems = items.map((i) => ` ${i}`).join("\n");
  358. return `${dts}
  359. // for vue template auto import
  360. import { UnwrapRef } from 'vue'
  361. declare module 'vue' {
  362. interface ComponentCustomProperties {
  363. ${extendItems}
  364. }
  365. }`;
  366. }
  367. };
  368. return self;
  369. }
  370. export { dedupeImports as a, stripFileExtension as b, toTypeDeclarationItems as c, defineUnimportPreset as d, toTypeDeclarationFile as e, toTypeReExports as f, getString as g, getMagicString as h, addImportToCode as i, toImports as j, excludeRE as k, importAsRE as l, separatorRE as m, normalizeImports as n, matchRE as o, stripCommentsAndStrings as p, resolveIdAbsolute as r, stringifyImports as s, toExports as t, vueTemplateAddon as v };