Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 58 additions & 0 deletions packages/dashboard/vite/tests/plugin-hooks.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,64 @@ describe('themeVariablesPlugin', () => {
const result = callTransform(plugin, css, '/app/styles.css');
expect(result).toContain('@theme inline');
});

it('injects additionalStylesheets after @import tailwindcss (single path)', () => {
const plugin = themeVariablesPlugin({
additionalStylesheets: '/abs/project/src/dashboard.css',
});
const css = `@import 'tailwindcss';\n@import 'tw-animate-css';`;
const result = callTransform(plugin, css, '/app/styles.css');
expect(result).toContain(
`@import 'tailwindcss';\n@import '/abs/project/src/dashboard.css';\n@import 'tw-animate-css';`,
);
});

it('injects multiple additionalStylesheets in order', () => {
const plugin = themeVariablesPlugin({
additionalStylesheets: ['/abs/project/a.css', '/abs/project/b.css'],
});
const css = `@import 'tailwindcss';`;
const result = callTransform(plugin, css, '/app/styles.css');
expect(result).toContain(
`@import 'tailwindcss';\n@import '/abs/project/a.css';\n@import '/abs/project/b.css';`,
);
});

it('resolves relative additionalStylesheets paths against cwd', () => {
const plugin = themeVariablesPlugin({
additionalStylesheets: 'src/dashboard.css',
});
const css = `@import 'tailwindcss';`;
const result = callTransform(plugin, css, '/app/styles.css');
const expected = path.resolve('src/dashboard.css').replace(/\\/g, '/');
expect(result).toContain(`@import '${expected}';`);
});

it('does not duplicate an additionalStylesheets import already present', () => {
const plugin = themeVariablesPlugin({
additionalStylesheets: '/abs/project/custom.css',
});
const css = `@import 'tailwindcss';\n@import '/abs/project/custom.css';`;
// Already there → nothing to do → transform should be a no-op.
const result = callTransform(plugin, css, '/app/styles.css');
expect(result).toBeNull();
});

it('does nothing when additionalStylesheets is empty', () => {
const plugin = themeVariablesPlugin({ additionalStylesheets: [] });
const css = `@import 'tailwindcss';\nbody { color: red; }`;
const result = callTransform(plugin, css, '/app/styles.css');
expect(result).toBeNull();
});

it('skips injection when @import tailwindcss is absent', () => {
const plugin = themeVariablesPlugin({
additionalStylesheets: '/abs/project/custom.css',
});
const css = `body { color: red; }`;
const result = callTransform(plugin, css, '/app/styles.css');
expect(result).toBeNull();
});
});

// ─── transformIndexHtmlPlugin ────────────────────────────────────────────────
Expand Down
53 changes: 53 additions & 0 deletions packages/dashboard/vite/vite-plugin-theme.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import path from 'node:path';

import { brand, darkTheme, fontFamily, lightTheme, radii, shadows } from '@vendure-io/design-tokens';
import { Plugin } from 'vite';

Expand Down Expand Up @@ -43,8 +45,38 @@ const defaultVariables: ThemeVariables = {

export type ThemeVariablesPluginOptions = {
theme?: ThemeVariables;
/**
* @description
* One or more paths to additional CSS files that should be imported into
* the dashboard's main stylesheet. Each path is injected as an `@import`
* statement right after `@import 'tailwindcss';`, so the file participates
* in Tailwind's build pipeline — you can use `@source`, `@theme`, `@apply`,
* `@utility`, custom variants, etc. inside it.
*
* Paths may be absolute or relative to the current working directory;
* relative paths are resolved against `process.cwd()`. Backslashes are
* normalized to forward slashes so the resulting `@import` statement is
* valid on Windows.
*
* To override design tokens (e.g. brand colors), prefer the `theme`
* option — `additionalStylesheets` is for layering custom CSS rules.
*
* @example
* ```ts
* vendureDashboardPlugin({
* additionalStylesheets: [path.resolve(__dirname, 'src/dashboard.css')],
* })
* ```
*/
additionalStylesheets?: string | string[];
};

function normalizeStylesheetPaths(input: string | string[] | undefined): string[] {
if (!input) return [];
const list = Array.isArray(input) ? input : [input];
return list.map(p => path.resolve(p).replace(/\\/g, '/'));
}

/**
* Generates the `@theme inline` block from design-token JS exports,
* mirroring the approach used by `@vendure-io/design-tokens/scripts/generate-css.ts`.
Expand Down Expand Up @@ -83,6 +115,7 @@ function generateThemeInlineBlock(): string {
export function themeVariablesPlugin(options: ThemeVariablesPluginOptions): Plugin {
const virtualModuleId = 'virtual:admin-theme';
const resolvedVirtualModuleId = `\0${virtualModuleId}`;
const additionalStylesheets = normalizeStylesheetPaths(options.additionalStylesheets);

return {
name: 'vendure:admin-theme',
Expand All @@ -96,6 +129,26 @@ export function themeVariablesPlugin(options: ThemeVariablesPluginOptions): Plug
let result = code;
let modified = false;

// Inject user-supplied stylesheets as @import statements right after
// `@import 'tailwindcss';`. Placement among the @import statements
// keeps the CSS valid (imports must precede rules) and lets the file
// contribute @source, @theme, @apply, etc. to the dashboard build.
if (additionalStylesheets.length > 0) {
const newImports = additionalStylesheets
.map(p => `@import '${p}';`)
.filter(stmt => !result.includes(stmt));
if (newImports.length > 0) {
const tailwindImportRe = /(@import\s+['"]tailwindcss['"];)/;
if (tailwindImportRe.test(result)) {
result = result.replace(
tailwindImportRe,
`$1\n${newImports.join('\n')}`,
);
modified = true;
}
}
}

// Replace @import 'virtual:admin-theme' with :root / .dark CSS custom properties
if (
result.includes('@import "virtual:admin-theme";') ||
Expand Down
6 changes: 5 additions & 1 deletion packages/dashboard/vite/vite-plugin-vendure-dashboard.ts
Original file line number Diff line number Diff line change
Expand Up @@ -214,7 +214,11 @@ export function vendureDashboardPlugin(options: VitePluginVendureDashboardOption
},
{
key: 'themeVariables',
plugin: () => themeVariablesPlugin({ theme: options.theme }),
plugin: () =>
themeVariablesPlugin({
theme: options.theme,
additionalStylesheets: options.additionalStylesheets,
}),
},
{
key: 'tailwindSource',
Expand Down
Loading