753 lines
27 KiB
JavaScript
753 lines
27 KiB
JavaScript
import { DIRECTIVE_IMPORT_PREFIX, DISABLE_COMMENT, escapeSpecialChars, getNameFromFilePath, getTransformedPath, isExclude, matchGlobs, normalizeComponentInfo, notNullish, parseId, pascalCase, resolveAlias, shouldTransform, slash, stringifyComponentImport, throttle, toArray } from "./utils-BoXu-4gQ.js";
|
|
import { existsSync } from "node:fs";
|
|
import process from "node:process";
|
|
import chokidar from "chokidar";
|
|
import { glob, globSync } from "tinyglobby";
|
|
import { createUnplugin } from "unplugin";
|
|
import { createFilter } from "unplugin-utils";
|
|
import { dirname, isAbsolute, join, relative, resolve } from "node:path";
|
|
import Debug from "debug";
|
|
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
import { getPackageInfoSync, importModule, isPackageExists } from "local-pkg";
|
|
import MagicString from "magic-string";
|
|
|
|
//#region src/core/type-imports/index.ts
|
|
const TypeImportPresets = [{
|
|
from: "vue-router",
|
|
names: ["RouterView", "RouterLink"]
|
|
}, {
|
|
from: "vue-starport",
|
|
names: ["Starport", "StarportCarrier"]
|
|
}];
|
|
|
|
//#endregion
|
|
//#region src/core/type-imports/detect.ts
|
|
function detectTypeImports() {
|
|
return TypeImportPresets.map((i) => isPackageExists(i.from) ? i : void 0).filter(notNullish);
|
|
}
|
|
function resolveTypeImports(imports) {
|
|
return imports.flatMap((i) => i.names.map((n) => ({
|
|
from: i.from,
|
|
name: n,
|
|
as: n
|
|
})));
|
|
}
|
|
|
|
//#endregion
|
|
//#region src/core/declaration.ts
|
|
const multilineCommentsRE = /\/\*.*?\*\//gs;
|
|
const singlelineCommentsRE = /\/\/.*$/gm;
|
|
function extractImports(code) {
|
|
return Object.fromEntries(Array.from(code.matchAll(/['"]?([^\s'"]+)['"]?\s*:\s*(.+?)[,;\r\n]/g)).map((i) => [i[1], i[2]]));
|
|
}
|
|
function parseDeclaration(code) {
|
|
var _exec, _exec2;
|
|
if (!code) return;
|
|
code = code.replace(multilineCommentsRE, "").replace(singlelineCommentsRE, "");
|
|
const imports = {
|
|
component: {},
|
|
directive: {}
|
|
};
|
|
const componentDeclaration = (_exec = /export\s+interface\s+GlobalComponents\s*\{.*?\}/s.exec(code)) === null || _exec === void 0 ? void 0 : _exec[0];
|
|
if (componentDeclaration) imports.component = extractImports(componentDeclaration);
|
|
const directiveDeclaration = (_exec2 = /export\s+interface\s+GlobalDirectives\s*\{.*?\}/s.exec(code)) === null || _exec2 === void 0 ? void 0 : _exec2[0];
|
|
if (directiveDeclaration) imports.directive = extractImports(directiveDeclaration);
|
|
return imports;
|
|
}
|
|
/**
|
|
* Converts `ComponentInfo` to an array
|
|
*
|
|
* `[name, "typeof import(path)[importName]"]`
|
|
*/
|
|
function stringifyComponentInfo(filepath, { from: path, as: name, name: importName }, importPathTransform) {
|
|
if (!name) return void 0;
|
|
path = getTransformedPath(path, importPathTransform);
|
|
return [name, `typeof import('${slash(isAbsolute(path) ? `./${relative(dirname(filepath), path)}` : path)}')['${importName || "default"}']`];
|
|
}
|
|
/**
|
|
* Converts array of `ComponentInfo` to an import map
|
|
*
|
|
* `{ name: "typeof import(path)[importName]", ... }`
|
|
*/
|
|
function stringifyComponentsInfo(filepath, components, importPathTransform) {
|
|
return Object.fromEntries(components.map((info) => stringifyComponentInfo(filepath, info, importPathTransform)).filter(notNullish));
|
|
}
|
|
function getDeclarationImports(ctx, filepath) {
|
|
const component = stringifyComponentsInfo(filepath, [...Object.values({
|
|
...ctx.componentNameMap,
|
|
...ctx.componentCustomMap
|
|
}), ...resolveTypeImports(ctx.options.types)], ctx.options.importPathTransform);
|
|
const directive = stringifyComponentsInfo(filepath, Object.values(ctx.directiveCustomMap), ctx.options.importPathTransform);
|
|
if (Object.keys(component).length + Object.keys(directive).length === 0) return;
|
|
return {
|
|
component,
|
|
directive
|
|
};
|
|
}
|
|
function stringifyDeclarationImports(imports) {
|
|
return Object.entries(imports).sort(([a], [b]) => a.localeCompare(b)).map(([name, v]) => {
|
|
if (!/^\w+$/.test(name)) name = `'${name}'`;
|
|
return `${name}: ${v}`;
|
|
});
|
|
}
|
|
function getDeclaration(ctx, filepath, originalImports) {
|
|
const imports = getDeclarationImports(ctx, filepath);
|
|
if (!imports) return;
|
|
const declarations = {
|
|
component: stringifyDeclarationImports({
|
|
...originalImports === null || originalImports === void 0 ? void 0 : originalImports.component,
|
|
...imports.component
|
|
}),
|
|
directive: stringifyDeclarationImports({
|
|
...originalImports === null || originalImports === void 0 ? void 0 : originalImports.directive,
|
|
...imports.directive
|
|
})
|
|
};
|
|
let code = `/* eslint-disable */
|
|
// @ts-nocheck
|
|
// biome-ignore lint: disable
|
|
// oxlint-disable
|
|
// ------
|
|
// Generated by unplugin-vue-components
|
|
// Read more: https://github.com/vuejs/core/pull/3399
|
|
${ctx.options.dtsTsx ? `import { GlobalComponents } from 'vue'\n` : ""}
|
|
export {}
|
|
|
|
/* prettier-ignore */
|
|
declare module 'vue' {`;
|
|
if (Object.keys(declarations.component).length > 0) code += `
|
|
export interface GlobalComponents {
|
|
${declarations.component.join("\n ")}
|
|
}`;
|
|
if (Object.keys(declarations.directive).length > 0) code += `
|
|
export interface GlobalDirectives {
|
|
${declarations.directive.join("\n ")}
|
|
}`;
|
|
code += "\n}\n";
|
|
if (ctx.options.dtsTsx) {
|
|
if (Object.keys(declarations.component).length > 0) code += `
|
|
// For TSX support
|
|
declare global {
|
|
${declarations.component.map((d) => d.replace(/(.+):/, "const $1:")).join("\n ")}
|
|
}`;
|
|
}
|
|
return code;
|
|
}
|
|
async function writeFile$1(filePath, content) {
|
|
await mkdir(dirname(filePath), { recursive: true });
|
|
return await writeFile(filePath, content, "utf-8");
|
|
}
|
|
async function writeDeclaration(ctx, filepath, removeUnused = false) {
|
|
const originalContent = existsSync(filepath) ? await readFile(filepath, "utf-8") : "";
|
|
const code = getDeclaration(ctx, filepath, removeUnused ? void 0 : parseDeclaration(originalContent));
|
|
if (!code) return;
|
|
if (code !== originalContent) await writeFile$1(filepath, code);
|
|
}
|
|
async function writeComponentsJson(ctx, _removeUnused = false) {
|
|
if (!ctx.dumpComponentsInfoPath) return;
|
|
const components = [...Object.entries({
|
|
...ctx.componentNameMap,
|
|
...ctx.componentCustomMap
|
|
}).map(([_, { name, as, from }]) => ({
|
|
name: name || "default",
|
|
as,
|
|
from
|
|
})), ...resolveTypeImports(ctx.options.types)];
|
|
await writeFile$1(ctx.dumpComponentsInfoPath, JSON.stringify(components, null, 2));
|
|
}
|
|
|
|
//#endregion
|
|
//#region src/core/fs/glob.ts
|
|
const debug$4 = Debug("unplugin-vue-components:glob");
|
|
function searchComponents(ctx) {
|
|
var _ctx$options$resolver;
|
|
debug$4(`started with: [${ctx.options.globs.join(", ")}]`);
|
|
const root = ctx.root;
|
|
const files = globSync(ctx.options.globs, {
|
|
ignore: ctx.options.globsExclude,
|
|
onlyFiles: true,
|
|
cwd: root,
|
|
absolute: true,
|
|
expandDirectories: false
|
|
});
|
|
if (!files.length && !((_ctx$options$resolver = ctx.options.resolvers) === null || _ctx$options$resolver === void 0 ? void 0 : _ctx$options$resolver.length)) console.warn("[unplugin-vue-components] no components found");
|
|
debug$4(`${files.length} components found.`);
|
|
ctx.addComponents(files);
|
|
}
|
|
|
|
//#endregion
|
|
//#region src/core/options.ts
|
|
const defaultOptions = {
|
|
dirs: "src/components",
|
|
extensions: "vue",
|
|
deep: true,
|
|
dts: isPackageExists("typescript"),
|
|
dtsTsx: isPackageExists("@vitejs/plugin-vue-jsx"),
|
|
directoryAsNamespace: false,
|
|
collapseSamePrefixes: false,
|
|
globalNamespaces: [],
|
|
transformerUserResolveFunctions: true,
|
|
resolvers: [],
|
|
importPathTransform: (v) => v,
|
|
allowOverrides: false,
|
|
sourcemap: true,
|
|
dumpComponentsInfo: false,
|
|
syncMode: "default",
|
|
prefix: ""
|
|
};
|
|
function normalizeResolvers(resolvers) {
|
|
return toArray(resolvers).flat().map((r) => typeof r === "function" ? {
|
|
resolve: r,
|
|
type: "component"
|
|
} : r);
|
|
}
|
|
function resolveGlobsExclude(root, glob$1) {
|
|
const excludeReg = /^!/;
|
|
return slash(`${excludeReg.test(glob$1) ? "!" : ""}${resolve(root, glob$1.replace(excludeReg, ""))}`);
|
|
}
|
|
function resolveOptions(options, root) {
|
|
const resolved = Object.assign({}, defaultOptions, options);
|
|
resolved.resolvers = normalizeResolvers(resolved.resolvers);
|
|
resolved.extensions = toArray(resolved.extensions);
|
|
if (resolved.globs) {
|
|
resolved.globs = toArray(resolved.globs).map((glob$1) => resolveGlobsExclude(root, glob$1));
|
|
resolved.resolvedDirs = [];
|
|
} else {
|
|
const extsGlob = resolved.extensions.length === 1 ? resolved.extensions : `{${resolved.extensions.join(",")}}`;
|
|
resolved.dirs = toArray(resolved.dirs);
|
|
const globs = resolved.dirs.map((i) => resolveGlobsExclude(root, i));
|
|
resolved.resolvedDirs = globs.filter((i) => !i.startsWith("!"));
|
|
resolved.globs = globs.map((i) => {
|
|
let prefix = "";
|
|
if (i.startsWith("!")) {
|
|
prefix = "!";
|
|
i = i.slice(1);
|
|
}
|
|
return resolved.deep ? prefix + escapeSpecialChars(slash(join(i, `**/*.${extsGlob}`))) : prefix + escapeSpecialChars(slash(join(i, `*.${extsGlob}`)));
|
|
});
|
|
if (!resolved.extensions.length) throw new Error("[unplugin-vue-components] `extensions` option is required to search for components");
|
|
}
|
|
resolved.globsExclude = toArray(resolved.globsExclude || []).map((i) => resolveGlobsExclude(root, i));
|
|
resolved.globs = resolved.globs.filter((i) => {
|
|
if (!i.startsWith("!")) return true;
|
|
resolved.globsExclude.push(i.slice(1));
|
|
return false;
|
|
});
|
|
resolved.dts = !resolved.dts ? false : resolve(root, typeof resolved.dts === "string" ? resolved.dts : "components.d.ts");
|
|
if (!resolved.types && resolved.dts) resolved.types = detectTypeImports();
|
|
resolved.types = resolved.types || [];
|
|
resolved.root = root;
|
|
resolved.version = resolved.version ?? getVueVersion(root);
|
|
if (resolved.version < 2 || resolved.version >= 4) throw new Error(`[unplugin-vue-components] unsupported version: ${resolved.version}`);
|
|
resolved.transformer = options.transformer || `vue${Math.trunc(resolved.version)}`;
|
|
resolved.directives = typeof options.directives === "boolean" ? options.directives : !resolved.resolvers.some((i) => i.type === "directive") ? false : resolved.version >= 3;
|
|
return resolved;
|
|
}
|
|
function getVueVersion(root) {
|
|
var _getPackageInfoSync;
|
|
const version = +(((_getPackageInfoSync = getPackageInfoSync("vue", { paths: [join(root, "/")] })) === null || _getPackageInfoSync === void 0 ? void 0 : _getPackageInfoSync.version) || "3").split(".").slice(0, 2).join(".");
|
|
if (version === 2.7) return 2.7;
|
|
else if (version < 2.7) return 2;
|
|
return 3;
|
|
}
|
|
|
|
//#endregion
|
|
//#region src/core/transforms/component.ts
|
|
const debug$3 = Debug("unplugin-vue-components:transform:component");
|
|
function resolveVue2$1(code, s) {
|
|
const results = [];
|
|
for (const match of code.matchAll(/\b(_c|h)\(\s*['"](.+?)["']([,)])/g)) {
|
|
const [full, renderFunctionName, matchedName, append] = match;
|
|
if (match.index != null && matchedName && !matchedName.startsWith("_")) {
|
|
const start = match.index;
|
|
const end = start + full.length;
|
|
results.push({
|
|
rawName: matchedName,
|
|
replace: (resolved) => s.overwrite(start, end, `${renderFunctionName}(${resolved}${append}`)
|
|
});
|
|
}
|
|
}
|
|
return results;
|
|
}
|
|
function resolveVue3$1(code, s, transformerUserResolveFunctions) {
|
|
const results = [];
|
|
/**
|
|
* when using some plugin like plugin-vue-jsx, resolveComponent will be imported as resolveComponent1 to avoid duplicate import
|
|
*/
|
|
for (const match of code.matchAll(/_?resolveComponent\d*\("(.+?)"\)/g)) {
|
|
if (!transformerUserResolveFunctions && !match[0].startsWith("_")) continue;
|
|
const matchedName = match[1];
|
|
if (match.index != null && matchedName && !matchedName.startsWith("_")) {
|
|
const start = match.index;
|
|
const end = start + match[0].length;
|
|
results.push({
|
|
rawName: matchedName,
|
|
replace: (resolved) => s.overwrite(start, end, resolved)
|
|
});
|
|
}
|
|
}
|
|
return results;
|
|
}
|
|
async function transformComponent(code, transformer$1, s, ctx, sfcPath) {
|
|
let no = 0;
|
|
const results = transformer$1 === "vue2" ? resolveVue2$1(code, s) : resolveVue3$1(code, s, ctx.options.transformerUserResolveFunctions);
|
|
for (const { rawName, replace } of results) {
|
|
debug$3(`| ${rawName}`);
|
|
const name = pascalCase(rawName);
|
|
ctx.updateUsageMap(sfcPath, [name]);
|
|
const component = await ctx.findComponent(name, "component", [sfcPath]);
|
|
if (component) {
|
|
const varName = `__unplugin_components_${no}`;
|
|
s.prepend(`${stringifyComponentImport({
|
|
...component,
|
|
as: varName
|
|
}, ctx)};\n`);
|
|
no += 1;
|
|
replace(varName);
|
|
}
|
|
}
|
|
debug$3(`^ (${no})`);
|
|
}
|
|
|
|
//#endregion
|
|
//#region src/core/transforms/directive/vue2.ts
|
|
/**
|
|
* Get Vue 2 render function position
|
|
*/
|
|
function getRenderFnStart(program) {
|
|
var _ref;
|
|
const renderFn = program.body.find((node) => node.type === "VariableDeclaration" && node.declarations[0].id.type === "Identifier" && ["render", "_sfc_render"].includes(node.declarations[0].id.name));
|
|
const start = renderFn === null || renderFn === void 0 || (_ref = renderFn.declarations[0].init) === null || _ref === void 0 || (_ref = _ref.body) === null || _ref === void 0 ? void 0 : _ref.start;
|
|
if (start === null || start === void 0) throw new Error("[unplugin-vue-components:directive] Cannot find render function position.");
|
|
return start + 1;
|
|
}
|
|
async function resolveVue2(code, s) {
|
|
if (!isPackageExists("@babel/parser")) throw new Error("[unplugin-vue-components:directive] To use Vue 2 directive you will need to install Babel first: \"npm install -D @babel/parser\"");
|
|
const { parse: parse$1 } = await importModule("@babel/parser");
|
|
const { program } = parse$1(code, { sourceType: "module" });
|
|
const nodes = [];
|
|
const { walk } = await import("./src-D2-JfLYq.js");
|
|
walk(program, { enter(node) {
|
|
if (node.type === "CallExpression") nodes.push(node);
|
|
} });
|
|
if (nodes.length === 0) return [];
|
|
let _renderStart;
|
|
const getRenderStart = () => {
|
|
if (_renderStart !== void 0) return _renderStart;
|
|
return _renderStart = getRenderFnStart(program);
|
|
};
|
|
const results = [];
|
|
for (const node of nodes) {
|
|
var _args$, _args$1$properties$fi;
|
|
const { callee, arguments: args } = node;
|
|
if (callee.type !== "Identifier" || callee.name !== "_c" || ((_args$ = args[1]) === null || _args$ === void 0 ? void 0 : _args$.type) !== "ObjectExpression") continue;
|
|
const directives = (_args$1$properties$fi = args[1].properties.find((property) => property.type === "ObjectProperty" && property.key.type === "Identifier" && property.key.name === "directives")) === null || _args$1$properties$fi === void 0 ? void 0 : _args$1$properties$fi.value;
|
|
if (!directives || directives.type !== "ArrayExpression") continue;
|
|
for (const directive of directives.elements) {
|
|
var _directive$properties;
|
|
if ((directive === null || directive === void 0 ? void 0 : directive.type) !== "ObjectExpression") continue;
|
|
const nameNode = (_directive$properties = directive.properties.find((p) => p.type === "ObjectProperty" && p.key.type === "Identifier" && p.key.name === "name")) === null || _directive$properties === void 0 ? void 0 : _directive$properties.value;
|
|
if ((nameNode === null || nameNode === void 0 ? void 0 : nameNode.type) !== "StringLiteral") continue;
|
|
const name = nameNode.value;
|
|
if (!name || name.startsWith("_")) continue;
|
|
results.push({
|
|
rawName: name,
|
|
replace: (resolved) => {
|
|
s.prependLeft(getRenderStart(), `\nthis.$options.directives["${name}"] = ${resolved};`);
|
|
}
|
|
});
|
|
}
|
|
}
|
|
return results;
|
|
}
|
|
|
|
//#endregion
|
|
//#region src/core/transforms/directive/vue3.ts
|
|
function resolveVue3(code, s, transformerUserResolveFunctions) {
|
|
const results = [];
|
|
for (const match of code.matchAll(/_?resolveDirective\("(.+?)"\)/g)) {
|
|
const matchedName = match[1];
|
|
if (!transformerUserResolveFunctions && !match[0].startsWith("_")) continue;
|
|
if (match.index != null && matchedName && !matchedName.startsWith("_")) {
|
|
const start = match.index;
|
|
const end = start + match[0].length;
|
|
results.push({
|
|
rawName: matchedName,
|
|
replace: (resolved) => s.overwrite(start, end, resolved)
|
|
});
|
|
}
|
|
}
|
|
return results;
|
|
}
|
|
|
|
//#endregion
|
|
//#region src/core/transforms/directive/index.ts
|
|
const debug$2 = Debug("unplugin-vue-components:transform:directive");
|
|
async function transformDirective(code, transformer$1, s, ctx, sfcPath) {
|
|
let no = 0;
|
|
const results = await (transformer$1 === "vue2" ? resolveVue2(code, s) : resolveVue3(code, s));
|
|
for (const { rawName, replace } of results) {
|
|
debug$2(`| ${rawName}`);
|
|
const name = `${DIRECTIVE_IMPORT_PREFIX}${pascalCase(rawName)}`;
|
|
ctx.updateUsageMap(sfcPath, [name]);
|
|
const directive = await ctx.findComponent(name, "directive", [sfcPath]);
|
|
if (!directive) continue;
|
|
const varName = `__unplugin_directives_${no}`;
|
|
s.prepend(`${stringifyComponentImport({
|
|
...directive,
|
|
as: varName
|
|
}, ctx)};\n`);
|
|
no += 1;
|
|
replace(varName);
|
|
}
|
|
debug$2(`^ (${no})`);
|
|
}
|
|
|
|
//#endregion
|
|
//#region src/core/transformer.ts
|
|
const debug$1 = Debug("unplugin-vue-components:transformer");
|
|
function transformer(ctx, transformer$1) {
|
|
return async (code, id, path) => {
|
|
ctx.searchGlob();
|
|
const sfcPath = ctx.normalizePath(path);
|
|
debug$1(sfcPath);
|
|
const s = new MagicString(code);
|
|
await transformComponent(code, transformer$1, s, ctx, sfcPath);
|
|
if (ctx.options.directives) await transformDirective(code, transformer$1, s, ctx, sfcPath);
|
|
s.prepend(DISABLE_COMMENT);
|
|
const result = { code: s.toString() };
|
|
if (ctx.sourcemap) result.map = s.generateMap({
|
|
source: id,
|
|
includeContent: true,
|
|
hires: "boundary"
|
|
});
|
|
return result;
|
|
};
|
|
}
|
|
|
|
//#endregion
|
|
//#region src/core/context.ts
|
|
const debug = {
|
|
components: Debug("unplugin-vue-components:context:components"),
|
|
search: Debug("unplugin-vue-components:context:search"),
|
|
hmr: Debug("unplugin-vue-components:context:hmr"),
|
|
declaration: Debug("unplugin-vue-components:declaration"),
|
|
env: Debug("unplugin-vue-components:env")
|
|
};
|
|
var Context = class {
|
|
options;
|
|
transformer = void 0;
|
|
_componentPaths = /* @__PURE__ */ new Set();
|
|
_componentNameMap = {};
|
|
_componentUsageMap = {};
|
|
_componentCustomMap = {};
|
|
_directiveCustomMap = {};
|
|
_removeUnused = false;
|
|
_server;
|
|
root = process.cwd();
|
|
sourcemap = true;
|
|
alias = {};
|
|
dumpComponentsInfoPath;
|
|
constructor(rawOptions) {
|
|
this.rawOptions = rawOptions;
|
|
this.options = resolveOptions(rawOptions, this.root);
|
|
this.sourcemap = rawOptions.sourcemap ?? true;
|
|
this.generateDeclaration = throttle(500, this._generateDeclaration.bind(this), { noLeading: false });
|
|
this._removeUnused = this.options.syncMode !== "append";
|
|
if (this.options.dumpComponentsInfo) {
|
|
this.dumpComponentsInfoPath = this.options.dumpComponentsInfo === true ? "./.components-info.json" : this.options.dumpComponentsInfo ?? false;
|
|
this.generateComponentsJson = throttle(500, this._generateComponentsJson.bind(this), { noLeading: false });
|
|
}
|
|
this.setTransformer(this.options.transformer);
|
|
}
|
|
setRoot(root) {
|
|
if (this.root === root) return;
|
|
debug.env("root", root);
|
|
this.root = root;
|
|
this.options = resolveOptions(this.rawOptions, this.root);
|
|
}
|
|
setTransformer(name) {
|
|
debug.env("transformer", name);
|
|
this.transformer = transformer(this, name || "vue3");
|
|
}
|
|
transform(code, id) {
|
|
const { path, query } = parseId(id);
|
|
return this.transformer(code, id, path, query);
|
|
}
|
|
setupViteServer(server) {
|
|
if (this._server === server) return;
|
|
this._server = server;
|
|
this._removeUnused = this.options.syncMode === "overwrite";
|
|
this.setupWatcher(server.watcher);
|
|
}
|
|
setupWatcher(watcher) {
|
|
const { globs } = this.options;
|
|
this._removeUnused = this.options.syncMode === "overwrite";
|
|
watcher.on("unlink", (path) => {
|
|
if (!matchGlobs(path, globs)) return;
|
|
path = slash(path);
|
|
this.removeComponents(path);
|
|
this.onUpdate(path);
|
|
});
|
|
watcher.on("add", (path) => {
|
|
if (!matchGlobs(path, globs)) return;
|
|
path = slash(path);
|
|
this.addComponents(path);
|
|
this.onUpdate(path);
|
|
});
|
|
}
|
|
/**
|
|
* start watcher for webpack
|
|
*/
|
|
setupWatcherWebpack(watcher, emitUpdate) {
|
|
const { globs } = this.options;
|
|
this._removeUnused = this.options.syncMode === "overwrite";
|
|
watcher.on("unlink", (path) => {
|
|
if (!matchGlobs(path, globs)) return;
|
|
path = slash(path);
|
|
this.removeComponents(path);
|
|
emitUpdate(path, "unlink");
|
|
});
|
|
watcher.on("add", (path) => {
|
|
if (!matchGlobs(path, globs)) return;
|
|
path = slash(path);
|
|
this.addComponents(path);
|
|
emitUpdate(path, "add");
|
|
});
|
|
}
|
|
/**
|
|
* Record the usage of components
|
|
* @param path
|
|
* @param paths paths of used components
|
|
*/
|
|
updateUsageMap(path, paths) {
|
|
if (!this._componentUsageMap[path]) this._componentUsageMap[path] = /* @__PURE__ */ new Set();
|
|
paths.forEach((p) => {
|
|
this._componentUsageMap[path].add(p);
|
|
});
|
|
}
|
|
addComponents(paths) {
|
|
debug.components("add", paths);
|
|
const size = this._componentPaths.size;
|
|
toArray(paths).forEach((p) => this._componentPaths.add(p));
|
|
if (this._componentPaths.size !== size) {
|
|
this.updateComponentNameMap();
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
addCustomComponents(info) {
|
|
if (info.as) this._componentCustomMap[info.as] = info;
|
|
}
|
|
addCustomDirectives(info) {
|
|
if (info.as) this._directiveCustomMap[info.as] = info;
|
|
}
|
|
removeComponents(paths) {
|
|
debug.components("remove", paths);
|
|
const size = this._componentPaths.size;
|
|
toArray(paths).forEach((p) => this._componentPaths.delete(p));
|
|
if (this._componentPaths.size !== size) {
|
|
this.updateComponentNameMap();
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
onUpdate(path) {
|
|
this.generateDeclaration();
|
|
this.generateComponentsJson();
|
|
if (!this._server) return;
|
|
const payload = {
|
|
type: "update",
|
|
updates: []
|
|
};
|
|
const timestamp = +/* @__PURE__ */ new Date();
|
|
const name = pascalCase(getNameFromFilePath(path, this.options));
|
|
Object.entries(this._componentUsageMap).forEach(([key, values]) => {
|
|
if (values.has(name)) {
|
|
const r = `/${slash(relative(this.root, key))}`;
|
|
payload.updates.push({
|
|
acceptedPath: r,
|
|
path: r,
|
|
timestamp,
|
|
type: "js-update"
|
|
});
|
|
}
|
|
});
|
|
if (payload.updates.length) this._server.ws.send(payload);
|
|
}
|
|
updateComponentNameMap() {
|
|
this._componentNameMap = {};
|
|
Array.from(this._componentPaths).forEach((path) => {
|
|
const fileName = getNameFromFilePath(path, this.options);
|
|
const name = this.options.prefix ? `${pascalCase(this.options.prefix)}${pascalCase(fileName)}` : pascalCase(fileName);
|
|
if (isExclude(name, this.options.excludeNames)) {
|
|
debug.components("exclude", name);
|
|
return;
|
|
}
|
|
if (this._componentNameMap[name] && !this.options.allowOverrides) {
|
|
console.warn(`[unplugin-vue-components] component "${name}"(${path}) has naming conflicts with other components, ignored.`);
|
|
return;
|
|
}
|
|
this._componentNameMap[name] = {
|
|
as: name,
|
|
from: path
|
|
};
|
|
});
|
|
}
|
|
async findComponent(name, type, excludePaths = []) {
|
|
let info = this._componentNameMap[name];
|
|
if (info && !excludePaths.includes(info.from) && !excludePaths.includes(info.from.slice(1))) return info;
|
|
for (const resolver of this.options.resolvers) {
|
|
if (resolver.type !== type) continue;
|
|
const result = await resolver.resolve(type === "directive" ? name.slice(DIRECTIVE_IMPORT_PREFIX.length) : name);
|
|
if (!result) continue;
|
|
if (typeof result === "string") info = {
|
|
as: name,
|
|
from: result
|
|
};
|
|
else info = {
|
|
as: name,
|
|
...normalizeComponentInfo(result)
|
|
};
|
|
if (type === "component") this.addCustomComponents(info);
|
|
else if (type === "directive") this.addCustomDirectives(info);
|
|
return info;
|
|
}
|
|
}
|
|
normalizePath(path) {
|
|
var _this$viteConfig, _this$viteConfig2;
|
|
return resolveAlias(path, ((_this$viteConfig = this.viteConfig) === null || _this$viteConfig === void 0 || (_this$viteConfig = _this$viteConfig.resolve) === null || _this$viteConfig === void 0 ? void 0 : _this$viteConfig.alias) || ((_this$viteConfig2 = this.viteConfig) === null || _this$viteConfig2 === void 0 ? void 0 : _this$viteConfig2.alias) || []);
|
|
}
|
|
relative(path) {
|
|
if (path.startsWith("/") && !path.startsWith(this.root)) return slash(path.slice(1));
|
|
return slash(relative(this.root, path));
|
|
}
|
|
_searched = false;
|
|
/**
|
|
* This search for components in with the given options.
|
|
* Will be called multiple times to ensure file loaded,
|
|
* should normally run only once.
|
|
*/
|
|
searchGlob() {
|
|
if (this._searched) return;
|
|
searchComponents(this);
|
|
debug.search(this._componentNameMap);
|
|
this._searched = true;
|
|
}
|
|
_generateDeclaration(removeUnused = this._removeUnused) {
|
|
if (!this.options.dts) return;
|
|
debug.declaration("generating dts");
|
|
return writeDeclaration(this, this.options.dts, removeUnused);
|
|
}
|
|
generateDeclaration(removeUnused = this._removeUnused) {
|
|
this._generateDeclaration(removeUnused);
|
|
}
|
|
_generateComponentsJson(removeUnused = this._removeUnused) {
|
|
if (!Object.keys(this._componentNameMap).length) return;
|
|
debug.components("generating components-info");
|
|
return writeComponentsJson(this, removeUnused);
|
|
}
|
|
generateComponentsJson(removeUnused = this._removeUnused) {
|
|
this._generateComponentsJson(removeUnused);
|
|
}
|
|
get componentNameMap() {
|
|
return this._componentNameMap;
|
|
}
|
|
get componentCustomMap() {
|
|
return this._componentCustomMap;
|
|
}
|
|
get directiveCustomMap() {
|
|
return this._directiveCustomMap;
|
|
}
|
|
};
|
|
|
|
//#endregion
|
|
//#region src/core/unplugin.ts
|
|
const PLUGIN_NAME = "unplugin:webpack";
|
|
var unplugin_default = createUnplugin((options = {}) => {
|
|
const filter = createFilter(options.include || [
|
|
/\.vue$/,
|
|
/\.vue\?vue/,
|
|
/\.vue\.[tj]sx?\?vue/,
|
|
/\.vue\?v=/
|
|
], options.exclude || [
|
|
/[\\/]node_modules[\\/]/,
|
|
/[\\/]\.git[\\/]/,
|
|
/[\\/]\.nuxt[\\/]/
|
|
]);
|
|
const ctx = new Context(options);
|
|
return {
|
|
name: "unplugin-vue-components",
|
|
enforce: "post",
|
|
api: {
|
|
async findComponent(name, filename) {
|
|
return await ctx.findComponent(name, "component", filename ? [filename] : []);
|
|
},
|
|
stringifyImport(info) {
|
|
return stringifyComponentImport(info, ctx);
|
|
}
|
|
},
|
|
transformInclude(id) {
|
|
return filter(id);
|
|
},
|
|
async transform(code, id) {
|
|
if (!shouldTransform(code)) return null;
|
|
try {
|
|
const result = await ctx.transform(code, id);
|
|
ctx.generateDeclaration();
|
|
ctx.generateComponentsJson();
|
|
return result;
|
|
} catch (e) {
|
|
this.error(e);
|
|
}
|
|
},
|
|
vite: {
|
|
async configResolved(config) {
|
|
ctx.setRoot(config.root);
|
|
ctx.sourcemap = true;
|
|
if (config.plugins.find((i) => i.name === "vite-plugin-vue2")) ctx.setTransformer("vue2");
|
|
if (ctx.options.dts) {
|
|
ctx.searchGlob();
|
|
if (!existsSync(ctx.options.dts)) ctx.generateDeclaration();
|
|
}
|
|
if (ctx.options.dumpComponentsInfo && ctx.dumpComponentsInfoPath) {
|
|
if (!existsSync(ctx.dumpComponentsInfoPath)) ctx.generateComponentsJson();
|
|
}
|
|
if (config.build.watch && config.command === "build") ctx.setupWatcher(chokidar.watch(await glob(ctx.options.globs)));
|
|
},
|
|
configureServer(server) {
|
|
ctx.setupViteServer(server);
|
|
}
|
|
},
|
|
webpack(compiler) {
|
|
let watcher;
|
|
let fileDepQueue = [];
|
|
compiler.hooks.watchRun.tapAsync(PLUGIN_NAME, async () => {
|
|
if (!watcher && compiler.watching) {
|
|
watcher = compiler.watching;
|
|
ctx.setupWatcherWebpack(chokidar.watch(await glob(ctx.options.globs)), (path, type) => {
|
|
fileDepQueue.push({
|
|
path,
|
|
type
|
|
});
|
|
process.nextTick(() => {
|
|
watcher.invalidate();
|
|
});
|
|
});
|
|
}
|
|
});
|
|
compiler.hooks.compilation.tap(PLUGIN_NAME, (compilation) => {
|
|
if (fileDepQueue.length) {
|
|
fileDepQueue.forEach(({ path, type }) => {
|
|
if (type === "unlink") compilation.fileDependencies.delete(path);
|
|
else compilation.fileDependencies.add(path);
|
|
});
|
|
fileDepQueue = [];
|
|
}
|
|
});
|
|
}
|
|
};
|
|
});
|
|
|
|
//#endregion
|
|
export { unplugin_default }; |