Skip to content

Latest commit

 

History

History
261 lines (191 loc) · 9.24 KB

File metadata and controls

261 lines (191 loc) · 9.24 KB

Extension Points

Emulsify Core provides shared Vite and Storybook conventions. Project-specific framework tooling should live in the consuming project and connect through documented extension points.

Directory Conventions

Project-level extension locations live under config/emulsify-core:

Extension Type Directory Why
Vite plugins/config patches config/emulsify-core/vite/plugins.(mjs|js|cjs) Build-time Vite extensions are loaded only by Node/Vite.
Storybook overrides config/emulsify-core/storybook/... Storybook preview/head overrides are project-facing assets that Storybook imports.
A11y config config/emulsify-core/a11y.config.js Configure Pa11y ignores, manual Storybook IDs, and Storybook discovery behavior.

Vite extensions should use config/emulsify-core/vite/. Storybook overrides should continue using config/emulsify-core/storybook/, and the a11y script continues to read config/emulsify-core/a11y.config.js.

Vite Plugins And Config Patches

Projects can extend the shared Vite config with one of these files:

  • config/emulsify-core/vite/plugins.mjs
  • config/emulsify-core/vite/plugins.js
  • config/emulsify-core/vite/plugins.cjs

Supported plugin shapes:

export default [myVitePlugin()];
export default ({ env }) => [myVitePlugin({ env })];

Projects can also export extendConfig() when they need to patch Vite config beyond adding plugins:

export const extendConfig = (config, { env }) => ({
  define: {
    __PROJECT_NAME__: JSON.stringify(env.machineName),
  },
});

Use plugin arrays for normal framework integration. Use extendConfig() only when a plugin does not expose the needed config directly.

Tailwind CSS

For Tailwind CSS v4, install Tailwind in the project:

npm install tailwindcss @tailwindcss/vite

Add the Tailwind Vite plugin from the project extension file:

// config/emulsify-core/vite/plugins.mjs
import tailwindcss from '@tailwindcss/vite';

export default () => [tailwindcss()];

Create a CSS file that imports Tailwind. This example places it under src/global, but the file can live anywhere that makes sense for the project:

/* src/global/tailwind.css */
@import 'tailwindcss';

/* Choose the source roots your project uses. */
@source "../components";
@source "../../components";
@source "../foundation";
@source "../layout";
@source "../tokens";

The @source lines are optional when Tailwind's automatic detection already sees the right files, but they make multi-root Emulsify projects explicit. Use ../components for src/components, ../../components for root ./components, and add one line for each variant.structureImplementations root that should be scanned. Keep @source paths focused on active component source directories so Tailwind does not scan generated output, archived templates, or dependency folders.

For production builds, import the Tailwind CSS file from a discovered JavaScript entry:

// src/global/tailwind.js
import './tailwind.css';

For Storybook development, import the same CSS file from the project preview override so Twig and React stories see the same utility classes:

// config/emulsify-core/storybook/preview.js
import '../../../src/global/tailwind.css';

export const parameters = {};

Tailwind detects complete class names in Twig, React, and other templates. Avoid constructing utility class fragments dynamically, such as text-${color}-600; map variants to complete class strings instead.

Other Vite Frameworks

Other Vite-based framework integrations follow the same pattern:

  1. Install the framework package in the consuming project.
  2. Return its Vite plugin from config/emulsify-core/vite/plugins.*.
  3. Import any required framework CSS or setup files from a discovered project entry or Storybook preview override.
  4. Use extendConfig() only when the framework needs additional Vite config.

Emulsify Core should not carry optional framework dependencies for every consuming project. Keep those dependencies local to the project that uses them.

Storybook Main Overrides And Addons

Projects can provide config/emulsify-core/storybook/main.js to extend the shared Storybook main configuration. Use this for Storybook features that belong in Node-side config, such as addons, additional static directories, or final config shaping.

Project addons are appended to the Emulsify Core defaults, so a project can add one addon without repeating @storybook/addon-a11y, @storybook/addon-links, or @storybook/addon-themes.

npm install @storybook/addon-viewport
// config/emulsify-core/storybook/main.js
export default {
  addons: ['@storybook/addon-viewport'],
};

Addon objects are also supported. If a project provides the same addon package name as a default addon, the project entry replaces the default entry so options can be customized without creating duplicates.

// config/emulsify-core/storybook/main.js
export default {
  addons: [
    {
      name: '@storybook/addon-a11y',
      options: {
        manual: true,
      },
    },
  ],
};

When a project intentionally wants to replace the full addon list, export replaceAddons.

// config/emulsify-core/storybook/main.js
export const replaceAddons = true;

export default {
  addons: ['@storybook/addon-viewport'],
};

For advanced cases, export extendConfig(). It receives the already-merged Storybook config and the resolved Emulsify environment.

// config/emulsify-core/storybook/main.js
export function extendConfig(config, { env }) {
  const staticDirs = [...(config.staticDirs || [])];
  if (env.platform === 'none') {
    staticDirs.push('public');
  }

  return {
    ...config,
    staticDirs,
  };
}

Storybook Preview Overrides

Projects can provide config/emulsify-core/storybook/preview.js to override or extend Storybook preview parameters. Missing override files are ignored. Default a11y parameters remain in place unless explicitly overridden.

export const parameters = {
  layout: 'centered',
  a11y: {
    config: {
      detailedReport: false,
    },
  },
};

Preview overrides are loaded in the browser-bundled Storybook preview through Vite-safe imports. They should not rely on CommonJS require().

Drupal projects can use the same preview override to add project-specific browser settings. Emulsify Core's Drupal Storybook shim provides neutral window.drupalSettings defaults, then merges existing project-provided values when the shim loads:

window.drupalSettings = {
  ...(window.drupalSettings || {}),
  exampleModule: {
    apiUrl: '/example-endpoint',
  },
};

Storybook Twig.js Extensions

Emulsify Core registers its native Twig.js helpers automatically. Some projects also need Drupal-compatible Twig.js filters and stubs from twig-drupal-filters, such as clean_class, clean_id, without, render, path, url, link(), or attach_library(), so Drupal-authored templates can compile in Storybook.

Projects using the drupal platform adapter get those filters automatically:

{
  "project": {
    "platform": "drupal"
  }
}

none and wordpress projects can opt into only the Twig.js filters without enabling Drupal behavior attachment, mirrored SDC output, or other Drupal adapter behavior:

{
  "project": {
    "platform": "wordpress"
  },
  "storybook": {
    "registerDrupalTwigFilters": true
  }
}

Restart Storybook after changing project.emulsify.json. The setting applies to Storybook's shared Twig runtime and to imported .twig modules generated by Emulsify Core's Vite plugin.

twig-drupal-filters helps Twig.js compile and render Drupal-style filters in Storybook, but it does not provide Drupal services. Stub functions such as attach_library() keep templates from failing during Storybook rendering; the real Drupal runtime still owns library attachment and server-side behavior.

Preview And Manager Head HTML

Preview head and manager head HTML remain separate extension points through:

  • config/emulsify-core/storybook/preview-head.html
  • config/emulsify-core/storybook/manager-head.html

Use preview head for markup needed inside the story iframe, such as fonts, meta tags, or scripts that rendered components depend on. Use manager head for Storybook chrome only.

Public Imports

Emulsify Core exposes stable public package paths:

defineReactExtension is reserved for future React extension support. It currently returns the input unchanged. Adopting the import path is safe; the runtime is intentionally a no-op until the registry lands.

import { renderTwig } from '@emulsify/core/storybook';
import { registerTwigExtensions } from '@emulsify/core/extensions/twig';
import { defineReactExtension } from '@emulsify/core/extensions/react';

Vite consumers can import the shared config from @emulsify/core/vite and public Vite plugin helpers from @emulsify/core/vite/plugins.