@storybook/vue3-vite
Version:
Storybook for Vue3 and Vite: Develop, document, and test UI components in isolation
180 lines (174 loc) • 8.24 kB
JavaScript
import CJS_COMPAT_NODE_URL_hatgpp8tjl from 'node:url';
import CJS_COMPAT_NODE_PATH_hatgpp8tjl from 'node:path';
import CJS_COMPAT_NODE_MODULE_hatgpp8tjl from "node:module";
var __filename = CJS_COMPAT_NODE_URL_hatgpp8tjl.fileURLToPath(import.meta.url);
var __dirname = CJS_COMPAT_NODE_PATH_hatgpp8tjl.dirname(__filename);
var require = CJS_COMPAT_NODE_MODULE_hatgpp8tjl.createRequire(import.meta.url);
// ------------------------------------------------------------
// end of CJS compatibility banner, injected by Storybook's esbuild configuration
// ------------------------------------------------------------
import {
templateCompilation
} from "./_node-chunks/chunk-HR5NLOWU.js";
// src/plugins/vue-component-meta.ts
import { readFile, stat } from "node:fs/promises";
import { join, parse } from "node:path";
import { getProjectRoot } from "storybook/internal/common";
import MagicString from "magic-string";
import {
TypeMeta,
createChecker,
createCheckerByJson
} from "vue-component-meta";
import { parseMulti } from "vue-docgen-api";
async function vueComponentMeta(tsconfigPath = "tsconfig.json") {
let { createFilter } = await import("vite"), filter = createFilter(/\.(vue|ts|js|tsx|jsx)$/, /\.stories\.(ts|tsx|js|jsx)$|^\0\/virtual:|^\/virtual:|\.storybook\/.*\.(ts|js)$/), checker = await createVueComponentMetaChecker(tsconfigPath);
return {
name: "storybook:vue-component-meta-plugin",
async transform(src, id) {
if (filter(id))
try {
let exportNames = checker.getExportNames(id), componentsMeta = exportNames.map((name) => checker.getComponentMeta(id, name));
componentsMeta = await applyTempFixForEventDescriptions(id, componentsMeta);
let metaSources = [];
if (componentsMeta.forEach((meta, index) => {
if (!meta.props.length && !meta.events.length && !meta.slots.length && !meta.exposed.length || meta.type === TypeMeta.Unknown)
return;
let exportName = exportNames[index];
["props", "events", "slots", "exposed"].forEach((key) => {
meta[key].forEach((value) => {
Array.isArray(value.schema) ? value.schema.forEach((eventSchema) => removeNestedSchemas(eventSchema)) : removeNestedSchemas(value.schema);
});
});
let exposed = (
// the meta also includes duplicated entries in the "exposed" array with "on"
// prefix (e.g. onClick instead of click), so we need to filter them out here
meta.exposed.filter((expose) => {
let nameWithoutOnPrefix = expose.name;
return nameWithoutOnPrefix.startsWith("on") && (nameWithoutOnPrefix = lowercaseFirstLetter(expose.name.replace("on", ""))), !meta.events.find((event) => event.name === nameWithoutOnPrefix);
}).filter((expose) => expose.name === "$slots" ? !meta.slots.map((slot) => slot.name).every((slotName) => expose.type.includes(slotName)) : !0)
);
metaSources.push({
exportName,
displayName: exportName === "default" ? getFilenameWithoutExtension(id) : exportName,
...meta,
exposed,
sourceFiles: id
});
}), metaSources.length === 0)
return;
let s = new MagicString(src);
return metaSources.forEach((meta) => {
let isDefaultExport = meta.exportName === "default", name = isDefaultExport ? "_sfc_main" : meta.exportName;
new RegExp(`export {.*${name}.*}`).test(src) || new RegExp(`export \\* from ['"]\\S*${name}['"]`).test(src) || // when using re-exports, some exports might be resolved via checker.getExportNames
// but are not directly exported inside the current file so we need to ignore them too
!src.includes(name) || (!id.endsWith(".vue") && isDefaultExport && (s.replace("export default ", "const _sfc_main = "), s.append(`
export default _sfc_main;`)), s.append(`
;${name}.__docgenInfo = ${JSON.stringify(meta)}`));
}), {
code: s.toString(),
map: s.generateMap({ hires: !0, source: id })
};
} catch {
return;
}
},
// handle hot updates to update the component meta on file changes
async handleHotUpdate({ file, read, server, modules, timestamp }) {
let content = await read();
checker.updateFile(file, content);
let invalidatedModules = /* @__PURE__ */ new Set();
for (let mod of modules)
server.moduleGraph.invalidateModule(mod, invalidatedModules, timestamp, !0);
return server.ws.send({ type: "full-reload" }), [];
}
};
}
async function createVueComponentMetaChecker(tsconfigPath = "tsconfig.json") {
let checkerOptions = {
forceUseTs: !0,
noDeclarations: !0,
printer: { newLine: 1 }
}, projectRoot = getProjectRoot(), projectTsConfigPath = join(projectRoot, tsconfigPath), defaultChecker = createCheckerByJson(projectRoot, { include: ["**/*"] }, checkerOptions);
return await fileExists(projectTsConfigPath) ? (await getTsConfigReferences(projectTsConfigPath)).length > 0 ? defaultChecker : createChecker(projectTsConfigPath, checkerOptions) : defaultChecker;
}
function getFilenameWithoutExtension(filename) {
return parse(filename).name;
}
function lowercaseFirstLetter(string) {
return string.charAt(0).toLowerCase() + string.slice(1);
}
async function fileExists(fullPath) {
try {
return await stat(fullPath), !0;
} catch {
return !1;
}
}
async function applyTempFixForEventDescriptions(filename, componentMeta) {
if (!componentMeta.some((meta) => meta.events.length))
return componentMeta;
try {
let parsedComponentDocs = await parseMulti(filename);
componentMeta.map((meta, index) => {
let eventsWithDescription = parsedComponentDocs[index].events;
return !meta.events.length || !eventsWithDescription?.length || (meta.events = meta.events.map((event) => {
let description = eventsWithDescription.find((i) => i.name === event.name)?.description;
return description && (event.description = description), event;
})), meta;
});
} catch {
}
return componentMeta;
}
async function getTsConfigReferences(tsConfigPath) {
try {
let content = JSON.parse(await readFile(tsConfigPath, "utf-8"));
return !("references" in content) || !Array.isArray(content.references) ? [] : content.references;
} catch {
return [];
}
}
function removeNestedSchemas(schema) {
if (typeof schema == "object") {
if (schema.kind === "enum") {
schema.schema?.forEach((enumSchema) => removeNestedSchemas(enumSchema));
return;
}
delete schema.schema;
}
}
// src/plugins/vue-docgen.ts
import MagicString2 from "magic-string";
import { parse as parse2 } from "vue-docgen-api";
async function vueDocgen() {
let { createFilter } = await import("vite"), filter = createFilter(/\.(vue)$/);
return {
name: "storybook:vue-docgen-plugin",
async transform(src, id) {
if (!filter(id))
return;
let metaData = await parse2(id), s = new MagicString2(src);
return s.append(`;_sfc_main.__docgenInfo = ${JSON.stringify(metaData)}`), {
code: s.toString(),
map: s.generateMap({ hires: !0, source: id })
};
}
};
}
// src/preset.ts
var core = {
builder: import.meta.resolve("@storybook/builder-vite"),
renderer: import.meta.resolve("@storybook/vue3/preset")
}, viteFinal = async (config, options) => {
let plugins = [await templateCompilation()], framework = await options.presets.apply("framework"), frameworkOptions = typeof framework == "string" ? {} : framework.options ?? {}, docgen = resolveDocgenOptions(frameworkOptions.docgen);
docgen !== !1 && (docgen.plugin === "vue-component-meta" ? plugins.push(await vueComponentMeta(docgen.tsconfig)) : plugins.push(await vueDocgen()));
let { mergeConfig } = await import("vite");
return mergeConfig(config, {
plugins
});
}, resolveDocgenOptions = (docgen) => docgen === !1 ? !1 : docgen === void 0 || docgen === !0 ? { plugin: "vue-docgen-api" } : typeof docgen == "string" ? { plugin: docgen } : docgen;
export {
core,
viteFinal
};