storybook-addon-sdc
Version:
Drupal Single Directory Components as stories
800 lines (780 loc) • 26.1 kB
JavaScript
// src/vite-plugin-storybook-yaml-stories.ts
import { readdirSync as readdirSync2, readFileSync } from "fs";
import { parse as parseYaml } from "yaml";
import { join as join2, basename as basename2, dirname as dirname3, extname } from "path";
// src/logger.ts
import pino from "pino";
var logger = pino({
transport: {
target: "pino-pretty",
options: {
colorize: true
}
}
});
// src/utils.ts
import { dirname, sep } from "path";
var capitalize = (str) => str[0].toUpperCase() + str.slice(1);
var convertToKebabCase = (str) => str.replace(/[-:]/g, "");
var toAttributes = (attrs) => {
if (!attrs) return "";
return " " + Object.entries(attrs).map(([key, value]) => {
if (Array.isArray(value)) {
value = value.join(" ");
}
return `${key}="${value}"`;
}).join(" ");
};
var deriveGroupFromPath = (fileName) => {
const dir = dirname(fileName);
const parts = dir.split(sep).filter(Boolean);
const lower = parts.map((p) => p.toLowerCase());
const index = lower.lastIndexOf("components");
if (index !== -1 && parts[index + 2]) {
return parts[index + 1];
}
return "SDC";
};
var sanitizeStoryKey = (key) => {
if (/^\d/.test(key)) {
return `_${key}`;
}
return key;
};
// src/argsGenerator.ts
import { JSONSchemaFaker } from "json-schema-faker";
var generateArgs = (schema, defs) => {
return Object.entries(schema).reduce((acc, [key, property]) => {
acc[key] = JSONSchemaFaker.generate(property, defs);
if (acc[key] instanceof Object) {
acc[key] = Object.values(acc[key]);
}
return acc;
}, {});
};
var slotsToSchemaProperties = (slots) => {
return Object.fromEntries(
Object.entries(slots).map(([key, value]) => [
key,
{ type: "string", ...value }
])
);
};
function generateStorybookArgs(content, jsonSchemaFakerOptions) {
JSONSchemaFaker.option({
...jsonSchemaFakerOptions
});
const { props, slots, $defs } = content;
const generatedArgs = {
...props?.properties && generateArgs(props.properties, $defs),
...slots && generateArgs(slotsToSchemaProperties(slots), $defs)
};
return generatedArgs;
}
// src/argTypesGenerator.ts
var schemaToArgtypes = (prop) => ({
...prop,
...prop.enum && {
control: "radio",
options: prop.enum
}
});
var argTypesGenerator_default = (content) => {
const generated = content?.props?.properties ? Object.entries(content.props.properties).reduce((acc, [key, value]) => {
acc[key] = schemaToArgtypes(value);
return acc;
}, {}) : {};
return generated;
};
// src/storyNodeRender.ts
var generateArgs2 = (args, isSlot = false) => Object.entries(args).map(([key, value]) => `${key}: ${formatArgValue(value, isSlot)},`).join("\n");
var formatArgValue = (value, isSlot) => {
if (Array.isArray(value)) {
const arrayContent = value.map((item) => storyNodeRenderer.render(item));
return `new TwigSafeArray(${arrayContent.join(", ")})`;
}
return storyNodeRenderer.render(value);
};
var StoryNodeRenderService = class {
renderer = [];
register(renderers) {
renderers.forEach((renderer) => {
this.renderer.push(renderer);
this.renderer.sort((a, b) => (b.priority ?? 0) - (a.priority ?? 0));
});
}
render(item) {
const renderer = this.renderer.find((h) => h.appliesTo(item));
return renderer?.render ? renderer.render(item) : JSON.stringify(item);
}
};
var renderComponent = (item) => {
const kebabCaseName = convertToKebabCase(item.component);
const componentProps = `...{ ${generateArgs2(item.props ?? {}, false)}}, ...{${generateArgs2(item.slots ?? {}, true)}}`;
const storyArgs = item.story ? `...${kebabCaseName}.${capitalize(item.story)}.args` : "...{}";
return `${kebabCaseName}.default.component({...${kebabCaseName}.Basic.baseArgs, ${storyArgs}, ${componentProps}})`;
};
var renderImage = (item) => {
return JSON.stringify(
`<img src="${item.uri}"${toAttributes(item.attributes)}>`
);
};
var renderElement = (item) => {
return JSON.stringify(
`<${item.tag ?? "div"}${toAttributes(item.attributes)}> ${item.value} </${item.tag ?? "div"}>`
);
};
var renderMarkup = (item) => {
return JSON.stringify(`${item.markup}`);
};
var defaultStoryNodes = [
{
appliesTo: (item) => item?.type === "component",
render: (item) => renderComponent(item),
priority: -4
},
{
appliesTo: (item) => item?.theme === "image" || item?.type === "image",
render: (item) => renderImage(item),
priority: -1
},
{
appliesTo: (item) => item?.type === "element" || item?.theme === "element",
render: (item) => renderElement(item),
priority: -2
},
{
appliesTo: (item) => item?.type === "markup" || item?.theme === "markup",
render: (item) => renderMarkup(item),
priority: -2
}
];
var storyNodeRenderer = new StoryNodeRenderService();
storyNodeRenderer.register(defaultStoryNodes);
// src/storiesGenerator.ts
var storiesGenerator_default = (stories, componentGlobals = {}) => Object.entries(stories).map(
([
storyKey,
{
props = {},
slots = {},
variants = {},
description = "",
name = void 0,
library_wrapper = "",
parameters = void 0,
globals = void 0,
thirdPartySettings = void 0
}
]) => {
const storyParameters = thirdPartySettings?.sdcStorybook?.parameters ?? parameters ?? {};
const storyGlobals = thirdPartySettings?.sdcStorybook?.globals ?? globals ?? {};
const mergedGlobals = {
...componentGlobals,
...storyGlobals
};
const globalsBlock = Object.keys(mergedGlobals).length > 0 ? ` globals: ${JSON.stringify(mergedGlobals, null, 2)},` : "";
const capitalizedKey = capitalize(storyKey);
const exportName = capitalizedKey === "Basic" ? `Variant_${capitalizedKey}` : capitalizedKey;
return `
export const ${exportName} = {
parameters: {...${JSON.stringify(storyParameters, null, 2)}, ...{docs: {description: {story: ${JSON.stringify(description, null, 2)}}}}},
${globalsBlock}
name: ${JSON.stringify(name ?? capitalizedKey, null, 2)},
args: {
...Basic.baseArgs,
${processPropsAttributes(props)}
${generateArgs2(slots, true)}
${generateVariants(variants)}
},
${library_wrapper ? `decorators: [
(Story) => {
const wrapper = ${JSON.stringify(library_wrapper)};
if (!wrapper) return Story();
// Replace {{ _story }} with the actual story component
const wrappedHtml = wrapper.replace('{{ _story }}', Story());
return wrappedHtml;
}
],` : ""}
play: async ({ canvasElement }) => {
Drupal.attachBehaviors(canvasElement, window.drupalSettings);
},
};
`;
}
).join("\n");
var generateVariants = (variants) => {
return Object.entries(variants).map(
([variantKey, variantValue]) => `${variantKey}: ${JSON.stringify(variantValue.title)},`
).join("\n");
};
var processPropsAttributes = (props) => {
if (!props || !props.attributes || Object.keys(props.attributes).length === 0) {
return generateArgs2(props, false);
}
const { attributes, ...otherProps } = props;
const propsArgs = generateArgs2(otherProps, false);
const attributeEntries = Object.entries(attributes).map(([key, value]) => `['${key}', ${JSON.stringify(value)}]`).join(", ");
return propsArgs + (propsArgs ? "\n" : "") + `defaultAttributes: [...Basic.baseArgs.defaultAttributes || [], ${attributeEntries}],`;
};
// src/componentMetadata.ts
import { dirname as dirname2, relative } from "path";
import { cwd } from "process";
var componentMetadata_default = (id, content) => {
return {
path: relative(cwd(), dirname2(id)),
machineName: id,
status: content.status || "stable",
name: content.name,
group: content.group
};
};
// src/validateJson.ts
import { Validator } from "jsonschema";
import fetch from "node-fetch";
var validator = new Validator();
var schemaCache = /* @__PURE__ */ new Map();
var fetchSchema = async (url) => {
if (schemaCache.has(url)) {
return schemaCache.get(url);
}
try {
const response = await fetch(url);
if (!response.ok) {
throw new Error(`Failed to fetch schema: ${response.statusText}`);
}
const schema = await response.json();
schemaCache.set(url, schema);
return schema;
} catch (error) {
logger.error("Error fetching schema:", error);
throw new Error(`Could not fetch schema from ${url}`);
}
};
var validateJson = async (data, schemaUrl) => {
const rootSchema = await fetchSchema(schemaUrl);
validator.addSchema(rootSchema, schemaUrl);
const unresolvedRefs = validator.unresolvedRefs;
while (unresolvedRefs.length > 0) {
const refUrl = unresolvedRefs.shift();
const schema = await fetchSchema(refUrl);
validator.addSchema(schema, refUrl);
}
const validationResult = validator.validate(data, rootSchema);
if (validationResult.errors.length > 0) {
logger.warn(`
${data.name}.component.yml has validation errors:
${validationResult.errors.map((error) => error.stack).join("\n")}
`);
}
};
// src/namespaces.ts
import { readdirSync, existsSync } from "fs";
import { basename, join, resolve, sep as sep2, relative as relative2 } from "path";
import { cwd as cwd2 } from "process";
import { normalizePath } from "vite";
var getProjectName = (p) => {
const fullPath = resolve(p);
const parts = fullPath.split(sep2);
const i = parts.lastIndexOf("components");
if (i < 1) {
throw new Error(`Could not find 'components' folder in path: ${fullPath}`);
}
return parts[i - 1];
};
var getAllSubdirectoriesRecursive = (baseDir) => {
const result = [];
const scan = (dir) => {
try {
const entries = readdirSync(dir, { withFileTypes: true });
for (const entry of entries) {
if (entry.isDirectory()) {
const fullPath = join(dir, entry.name);
result.push(fullPath);
scan(fullPath);
}
}
} catch (error) {
}
};
scan(baseDir);
return result;
};
var resolveComponentPath = (namespace, component, namespaces) => {
const baseDir = namespaces.findPath(namespace);
if (!baseDir) return void 0;
const componentFileName = `${component}.component.yml`;
const directPath = join(baseDir, "components", component, componentFileName);
if (existsSync(directPath)) return directPath;
const componentsDir = join(baseDir, "components");
const directories = [
componentsDir,
...getAllSubdirectoriesRecursive(componentsDir)
];
for (const dir of directories) {
const possiblePath = join(dir, component, componentFileName);
if (existsSync(possiblePath)) return possiblePath;
}
return void 0;
};
var Namespaces = class {
namespaces;
stripTrailingSlash = (p) => p.replace(/\/+$/g, "");
constructor(namespaceDefinition) {
this.namespaces = namespaceDefinition.namespaces ?? {};
if (namespaceDefinition.namespace) {
this.namespaces[namespaceDefinition.namespace] = cwd2();
} else {
this.namespaces[basename(cwd2())] = cwd2();
}
logger.info(`REGISTER NAMESPACES: ${JSON.stringify(this.namespaces)}`);
}
toViteAlias() {
const aliases = [];
Object.entries(this.namespaces).forEach(([namespace, path]) => {
const hasComponents = existsSync(join(path, "components"));
aliases.push({
find: "@" + namespace,
replacement: normalizePath(
hasComponents ? join(path, "components") : path
)
});
});
logger.info(`REGISTER VITE ALIASES: ${JSON.stringify(aliases)}`);
return aliases;
}
toTwigJsNamespaces() {
let namespaces = {};
for (const [ns, path] of Object.entries(this.namespaces)) {
const componentsPath = join(path, "components");
if (existsSync(componentsPath)) {
namespaces[ns] = componentsPath;
} else {
namespaces[ns] = path;
}
}
return namespaces;
}
toTwingNamespaces() {
let namespaces = {};
for (const [ns, path] of Object.entries(this.namespaces)) {
const componentsPath = join(path, "components");
if (existsSync(componentsPath)) {
namespaces[ns] = [componentsPath];
} else {
namespaces[ns] = [path];
}
}
return namespaces;
}
findPath = (namespace) => {
return this.namespaces[namespace] || "";
};
find = (fsPath) => {
const fp = this.stripTrailingSlash(fsPath);
let bestPath;
let bestNs;
for (const [ns, path] of Object.entries(this.namespaces)) {
const base = this.stripTrailingSlash(path);
if (fp === base || fp.startsWith(base + "/")) {
if (!bestPath || base.length > bestPath.length) {
bestPath = base;
bestNs = ns;
}
}
}
return bestNs;
};
pathToNamespace(fsPath, makeComponentIdFormat = false) {
const ns = this.find(fsPath);
if (!ns) {
throw new Error(
`Could not find valid 'namespace' for folder: ${fsPath} in namespaces: ${JSON.stringify(this.namespaces)}`
);
}
const rootPath = resolve(this.namespaces[ns]);
const componentsDir = join(rootPath, "components");
if (existsSync(componentsDir)) {
const isExact = fsPath === componentsDir;
const isChild = fsPath.startsWith(componentsDir + sep2);
if (!isExact && !isChild) {
throw new Error(
`Could not find 'components' folder in path: ${fsPath}, namespace: ${ns} (${Object.values(this.namespaces).join(", ")})`
);
}
let rel = relative2(componentsDir, fsPath);
if (sep2 !== "/") {
rel = rel.split(sep2).join("/");
}
if (makeComponentIdFormat) {
return `${ns}:${rel}`;
}
return `@${ns}/${rel}`;
}
return `@${ns}`;
}
};
var toNamespaces = (namespaceDefinition) => {
return new Namespaces(namespaceDefinition ?? {});
};
// src/vite-plugin-storybook-yaml-stories.ts
var readSDC = (filePath, defs, validate) => {
const sdcSchema = {
$defs: defs,
...parseYaml(readFileSync(filePath, "utf8"))
};
if (typeof validate === "string" && validate.length > 0) {
validateJson(sdcSchema, sdcSchema["$schema"] || validate);
}
return sdcSchema;
};
var generateImports = (directory, namespaces) => {
const componentName = basename2(directory);
return readdirSync2(directory).filter(
(file) => [".css", ".js", ".mjs", ".twig", ".yml"].includes(extname(file))
).map((file) => {
const filePath = `./${file}`;
const namespace = namespaces.pathToNamespace(directory);
logger.info(`IMPORT ASSET ${directory}/${file}`);
if (extname(file) === ".twig") {
const fileName = basename2(file, ".twig");
if (fileName === componentName) {
return `import COMPONENT from '${namespace}/${file}';`;
}
logger.info(`Skipping variant template: ${file}`);
return "";
}
return `import '${filePath}';`;
}).filter(Boolean).join("\n");
};
var dynamicImports = (stories, namespaces) => {
const imports = /* @__PURE__ */ new Set();
const importComponent = (item) => {
const [namespace, componentName] = item.component.split(":");
const resolvedPath = resolveComponentPath(
namespace,
componentName,
namespaces
);
const kebabCaseName = convertToKebabCase(item.component);
if (resolvedPath) {
imports.add(`import * as ${kebabCaseName} from '${resolvedPath}';`);
}
};
const extractComponentImports = (args) => {
Object.values(args).forEach((value) => {
if (Array.isArray(value)) {
value.forEach((item) => {
if (item.type === "component") {
importComponent(item);
}
extractComponentImports(value);
});
} else if (value && typeof value === "object") {
if (value.type === "component") {
importComponent(value);
}
extractComponentImports(value);
}
});
};
Object.values(stories).forEach(
({ slots = {}, props = {} }) => extractComponentImports({ ...slots, ...props })
);
return Array.from(imports).join("\n");
};
var createStoryIndex = (fileName, baseTitle, stories, disabledStories, tags) => {
const storiesIndex = [];
const isAllDisabled = disabledStories.includes("all");
if (!isAllDisabled && !disabledStories.includes("basic")) {
storiesIndex.push({
type: "story",
importPath: fileName,
exportName: "Basic",
title: baseTitle,
tags
});
}
if (stories && !isAllDisabled) {
Object.keys(stories).forEach((storyKey) => {
if (!disabledStories.includes(storyKey)) {
const capitalizedKey = capitalize(storyKey);
const exportName = capitalizedKey === "Basic" ? `Variant_${capitalizedKey}` : capitalizedKey;
storiesIndex.push({
type: "story",
importPath: fileName,
exportName,
title: baseTitle,
tags
});
}
});
}
return storiesIndex;
};
var vite_plugin_storybook_yaml_stories_default = ({
jsonSchemaFakerOptions = {},
sdcStorybookOptions = {},
globalDefs = {},
namespaces = {}
}) => ({
name: "vite-plugin-storybook-yaml-stories",
async load(id) {
if (id.endsWith("story.yml")) {
return "";
}
if (!id.endsWith("component.yml")) return;
try {
const content = readSDC(id, globalDefs, sdcStorybookOptions.validate);
const imports = generateImports(dirname3(id), namespaces);
const previewsStories = {
...content.thirdPartySettings?.sdcStorybook?.stories || {},
...loadStoryFilesSync(id)
};
storyNodeRenderer.register(sdcStorybookOptions.storyNodesRenderer ?? []);
const storiesImports = dynamicImports(previewsStories, namespaces);
const metadata = componentMetadata_default(id, content);
const componentGlobals = content?.thirdPartySettings?.sdcStorybook?.globals ?? {};
const argTypes = {
componentMetadata: { table: { disable: true } },
defaultAttributes: { table: { disable: true } },
...content.variants && {
variant: {
control: "select",
options: Object.keys(content.variants)
}
},
...argTypesGenerator_default(content)
};
const baseArgs = {
defaultAttributes: [
["data-component-id", namespaces.pathToNamespace(dirname3(id), true)]
],
componentMetadata: metadata,
...content.variants && {
variant: Object.keys(content.variants)[0]
}
};
const generatedArgs = generateStorybookArgs(content, jsonSchemaFakerOptions);
const args = sdcStorybookOptions.useBasicArgsForStories ? { ...baseArgs, ...generatedArgs } : baseArgs;
const basicArgs = sdcStorybookOptions.useBasicArgsForStories ? baseArgs : { ...baseArgs, ...generatedArgs };
const stories = previewsStories ? storiesGenerator_default(previewsStories, componentGlobals) : "";
return `
${imports}
${storiesImports}
class TwigSafeArray extends Array {
toString() {
return this.join('');
}
}
export default {
component: COMPONENT,
parameters: {...${JSON.stringify(content?.thirdPartySettings?.sdcStorybook?.parameters ?? {}, null, 2)}, ...{docs: {description: {component: ${JSON.stringify(content.description, null, 2)}}}}},
${Object.keys(componentGlobals).length > 0 ? `globals: ${JSON.stringify(componentGlobals, null, 2)},` : ""}
argTypes: ${JSON.stringify(argTypes, null, 2)},
args: ${JSON.stringify(args, null, 2)},
};
export const Basic = {
args: ${JSON.stringify(basicArgs, null, 2)},
baseArgs: ${JSON.stringify(args, null, 2)},
play: async ({ canvasElement }) => {
Drupal.attachBehaviors(canvasElement, window.drupalSettings);
},
};
${stories}
`;
} catch (error) {
logger.error(`Error loading component YAML file: ${id}, ${error}`);
throw error;
}
}
});
var yamlStoriesIndexer = {
test: /component\.yml$/,
createIndex: async (fileName, { makeTitle }) => {
try {
const content = readSDC(fileName);
const group = content.group || deriveGroupFromPath(fileName);
const baseTitle = makeTitle(
`${getProjectName(fileName)}/${capitalize(group)}/${content.name}`
);
const stories = content.thirdPartySettings?.sdcStorybook?.stories;
const storiesContent = loadStoryFilesSync(fileName);
const mergedStories = { ...stories, ...storiesContent };
const tags = content?.thirdPartySettings?.sdcStorybook?.tags ?? [];
const oldDisableBasicStory = content.thirdPartySettings?.sdcStorybook?.disableBasicStory;
const newDisabledStories = content.thirdPartySettings?.sdcStorybook?.disabledStories;
let disabledStories = [];
if (oldDisableBasicStory === true) {
disabledStories = ["basic"];
} else if (newDisabledStories) {
disabledStories = newDisabledStories;
}
return createStoryIndex(
fileName,
baseTitle,
mergedStories,
disabledStories,
tags
);
} catch (error) {
logger.error(`Error creating index for YAML file: ${fileName}, ${error}`);
throw error;
}
}
};
var loadStoryFilesSync = (fileName) => {
const folderPath = dirname3(fileName);
const storyFiles = readdirSync2(folderPath).filter((file) => file.endsWith(".story.yml")).map((file) => join2(folderPath, file));
return storyFiles.reduce(
(acc, file) => {
const content = readFileSync(file, "utf8");
const rawKey = basename2(file).split(".")[1];
const key = sanitizeStoryKey(rawKey);
return {
...acc,
[key]: parseYaml(content)
};
},
{}
);
};
// src/preset.ts
import { mergeConfig } from "vite";
// src/definitions.ts
import { parse } from "yaml";
import { readFileSync as readFileSync2 } from "fs";
import fetch2 from "node-fetch";
async function loadExternalDef(defPath) {
try {
if (defPath.startsWith("http://") || defPath.startsWith("https://")) {
const response = await fetch2(defPath);
if (!response.ok) {
throw new Error(`Failed to fetch ${defPath}: ${response.statusText}`);
}
const content = await response.text();
return parse(content);
} else {
const content = readFileSync2(defPath, "utf8");
return parse(content);
}
} catch (error) {
logger.error(`Error loading external definition from ${defPath}: ${error}`);
throw error;
}
}
async function loadAndMergeDefinitions(externalDefs, customDefs) {
const globalDefs = {};
if (externalDefs) {
await Promise.all(
externalDefs.map(async (defPath) => {
const def = await loadExternalDef(defPath);
Object.entries(def).forEach(([component, schema]) => {
globalDefs[component] = schema;
});
})
);
}
if (customDefs) {
Object.entries(customDefs).forEach(([component, schema]) => {
globalDefs[component] = schema;
});
}
if (Object.keys(globalDefs).length > 0) {
logger.info(
`REGISTER CUSTOM DEFINITIONS ${JSON.stringify(Object.keys(globalDefs))}`
);
}
return globalDefs;
}
// src/constants.ts
var DEFAULT_ADDON_OPTIONS = {
sdcStorybookOptions: {
useBasicArgsForStories: true,
twigLib: "twig",
validate: false
// validate:
// 'https://git.drupalcode.org/project/drupal/-/raw/HEAD/core/assets/schemas/v1/metadata.schema.json',
},
vitePluginTwingDrupalOptions: {
include: /\.twig(\?.*)?$/
},
vitePluginTwigDrupalOptions: {},
jsonSchemaFakerOptions: {
ignoreMissingRefs: true,
failOnInvalidTypes: false,
useExamplesValue: true,
useDefaultValue: true
}
};
// src/preset.ts
import { merge as lodashMerge } from "lodash-es";
async function viteFinal(config, options) {
options = lodashMerge({}, DEFAULT_ADDON_OPTIONS, options);
const {
sdcStorybookOptions,
vitePluginTwigDrupalOptions,
vitePluginTwingDrupalOptions
} = options;
const { customDefs, externalDefs } = sdcStorybookOptions;
const { nodePolyfills } = await import("vite-plugin-node-polyfills");
const globalDefs = await loadAndMergeDefinitions(externalDefs, customDefs);
const namespaces = toNamespaces(sdcStorybookOptions);
let twigPlugin = null;
if (sdcStorybookOptions.twigLib === "twing") {
options.vitePluginTwingDrupalOptions = {
...vitePluginTwingDrupalOptions,
namespaces: { ...namespaces.toTwingNamespaces() }
};
const { default: twing } = await import("vite-plugin-twing-drupal");
twigPlugin = twing(options.vitePluginTwingDrupalOptions);
} else if (sdcStorybookOptions.twigLib === "twig") {
options.vitePluginTwigDrupalOptions = {
...vitePluginTwigDrupalOptions,
namespaces: { ...namespaces.toTwigJsNamespaces() }
};
const { default: twig } = await import("vite-plugin-twig-drupal");
twigPlugin = twig(options.vitePluginTwigDrupalOptions);
}
return mergeConfig(config, {
plugins: [
nodePolyfills({
include: ["buffer", "stream", "path"]
}),
...twigPlugin ? [twigPlugin] : [],
vite_plugin_storybook_yaml_stories_default({ ...options, globalDefs, namespaces })
],
optimizeDeps: {
exclude: ["vite-plugin-twig-drupal", "vite-plugin-twing-drupal"]
},
resolve: {
alias: [...namespaces.toViteAlias()]
}
});
}
var experimental_indexers = async (existingIndexers) => [
...existingIndexers || [],
yamlStoriesIndexer
];
var previewHead = (head) => `
<style>
.visually-hidden {
position: absolute !important;
overflow: hidden;
clip: rect(1px, 1px, 1px, 1px);
width: 1px;
height: 1px;
word-wrap: normal;
}
</style>
<script src="https://cdn.jsdelivr.net/gh/drupal/drupal/core/misc/drupalSettingsLoader.js"></script>
<script src="https://cdn.jsdelivr.net/gh/drupal/drupal/core/misc/drupal.js"></script>
<script src="https://cdn.jsdelivr.net/gh/drupal/drupal/core/misc/drupal.init.js"></script>
<script src="https://cdn.jsdelivr.net/npm/@drupal/once@1.0.1/dist/once.min.js"></script>
${head}
`;
export {
experimental_indexers,
previewHead,
viteFinal
};