@db-ux/core-vite-plugin 0.0.0 → 4.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +7 -0
- package/README.md +215 -0
- package/build/detector.d.ts +30 -0
- package/build/detector.js +253 -0
- package/build/generator.d.ts +7 -0
- package/build/generator.js +138 -0
- package/build/index.d.ts +11 -0
- package/build/index.js +168 -0
- package/build/optimizer.d.ts +12 -0
- package/build/optimizer.js +35 -0
- package/build/types.d.ts +81 -0
- package/build/types.js +1 -0
- package/index.css +1 -0
- package/package.json +40 -2
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
# @db-ux/core-vite-plugin
|
|
2
|
+
|
|
3
|
+
## 4.6.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- feat: add vite-plugin to resolve CSS automatically based on detected components and styles - [see commit 5b1b2d8](https://github.com/db-ux-design-system/core-web/commit/5b1b2d811245fe132685171cb497bd5b16f5ff56)
|
package/README.md
ADDED
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
# @db-ux/core-vite-plugin
|
|
2
|
+
|
|
3
|
+
Vite plugin for optimized DB UX Design System CSS imports. Automatically includes only the CSS you need based on components and classes used in your application.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```shell
|
|
8
|
+
npm install @db-ux/core-vite-plugin --save-dev
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Usage
|
|
12
|
+
|
|
13
|
+
### Basic Usage
|
|
14
|
+
|
|
15
|
+
Add the plugin to your `vite.config.js` or `vite.config.ts`:
|
|
16
|
+
|
|
17
|
+
```js
|
|
18
|
+
import { defineConfig } from "vite";
|
|
19
|
+
import dbUxPlugin from "@db-ux/core-vite-plugin";
|
|
20
|
+
|
|
21
|
+
export default defineConfig({
|
|
22
|
+
plugins: [dbUxPlugin()]
|
|
23
|
+
});
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
Then import the plugin in your CSS file:
|
|
27
|
+
|
|
28
|
+
```css
|
|
29
|
+
/* styles.css or main.css */
|
|
30
|
+
@import "@db-ux/core-vite-plugin/index.css";
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
The plugin handles theme detection, component CSS, and foundation styles automatically.
|
|
34
|
+
|
|
35
|
+
#### Migration
|
|
36
|
+
|
|
37
|
+
If you're currently using the manual CSS layer imports, you can replace them with the plugin's single import:
|
|
38
|
+
|
|
39
|
+
```css
|
|
40
|
+
/* Before */
|
|
41
|
+
@layer whitelabel-theme, db-ux;
|
|
42
|
+
@import "@db-ux/core-foundations/build/styles/theme/rollup.css"
|
|
43
|
+
layer(whitelabel-theme);
|
|
44
|
+
@import "@db-ux/core-components/build/styles/bundle.css" layer(db-ux);
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
```css
|
|
48
|
+
/* After */
|
|
49
|
+
@import "@db-ux/core-vite-plugin/index.css";
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
### Advanced Usage
|
|
53
|
+
|
|
54
|
+
For more control, you can configure the plugin:
|
|
55
|
+
|
|
56
|
+
```js
|
|
57
|
+
import { defineConfig } from "vite";
|
|
58
|
+
import dbUxPlugin from "@db-ux/core-vite-plugin";
|
|
59
|
+
|
|
60
|
+
export default defineConfig({
|
|
61
|
+
plugins: [
|
|
62
|
+
dbUxPlugin({
|
|
63
|
+
include: {
|
|
64
|
+
components: ["button", "input"], // Force include specific components
|
|
65
|
+
foundations: ["helpers", "animations", "icons"], // Force include foundation features
|
|
66
|
+
colors: ["neutral", "brand"], // Force include color schemes
|
|
67
|
+
densities: ["regular", "functional"], // Force include densities
|
|
68
|
+
fontSizes: ["body-md", "headline-lg"] // Force include font sizes
|
|
69
|
+
},
|
|
70
|
+
exclude: {
|
|
71
|
+
components: ["tooltip"], // Exclude specific components
|
|
72
|
+
foundations: ["elevation"], // Exclude foundation features
|
|
73
|
+
colors: ["critical"], // Exclude color schemes
|
|
74
|
+
densities: ["expressive"], // Exclude densities
|
|
75
|
+
fontSizes: ["body-3xs"] // Exclude font sizes
|
|
76
|
+
},
|
|
77
|
+
optimize: true, // Remove unused CSS variable declarations to reduce bundle size (default: true).
|
|
78
|
+
theme: "db-theme", // Specify preferred theme package name (e.g., "db-theme")
|
|
79
|
+
additionalLayers: { after: ["ri-extension"] }, // Append custom layers to the auto-generated order
|
|
80
|
+
debug: false // Generate detection report for debugging (default: false)
|
|
81
|
+
})
|
|
82
|
+
]
|
|
83
|
+
});
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
## Configuration
|
|
87
|
+
|
|
88
|
+
### `include`
|
|
89
|
+
|
|
90
|
+
- **Type:** `{ components?: Component[], foundations?: FoundationFeature[], colors?: ColorScheme[], densities?: Density[], fontSizes?: FontSize[] }`
|
|
91
|
+
- Force include specific components, foundation features, color schemes, densities, or font sizes
|
|
92
|
+
- **Foundation features:** `icons`, `helpers`, `elevation`, `animations`, `code`
|
|
93
|
+
|
|
94
|
+
### `exclude`
|
|
95
|
+
|
|
96
|
+
- **Type:** `{ components?: Component[], foundations?: FoundationFeature[], colors?: ColorScheme[], densities?: Density[], fontSizes?: FontSize[] }`
|
|
97
|
+
- Exclude specific components, foundation features, color schemes, densities, or font sizes
|
|
98
|
+
|
|
99
|
+
### `optimize`
|
|
100
|
+
|
|
101
|
+
- **Type:** `boolean`
|
|
102
|
+
- **Default:** `true`
|
|
103
|
+
- Remove unused CSS variable declarations to reduce bundle size
|
|
104
|
+
|
|
105
|
+
### `theme`
|
|
106
|
+
|
|
107
|
+
- **Type:** `string`
|
|
108
|
+
- **Default:** `undefined`
|
|
109
|
+
- Specify a preferred theme package name (e.g., `"db-theme"`). The plugin automatically detects installed theme packages from `@db-ux/*-theme` or `@db-ux-inner-source/*-theme`. Use this option to select a specific theme when multiple are available.
|
|
110
|
+
|
|
111
|
+
### `additionalLayers`
|
|
112
|
+
|
|
113
|
+
- **Type:** `{ before?: string[], after?: string[] }`
|
|
114
|
+
- **Default:** `undefined`
|
|
115
|
+
- Append custom CSS cascade layers before or after the auto-generated layer order. Useful when your project defines its own layers on top of DB UX.
|
|
116
|
+
|
|
117
|
+
```js
|
|
118
|
+
// Adds layers around the auto-generated ones
|
|
119
|
+
dbUxPlugin({
|
|
120
|
+
additionalLayers: {
|
|
121
|
+
before: ["reset"],
|
|
122
|
+
after: ["ri-extension"]
|
|
123
|
+
}
|
|
124
|
+
});
|
|
125
|
+
// → @layer reset, db-theme, db-ux, ri-extension;
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
### `overrideLayers`
|
|
129
|
+
|
|
130
|
+
- **Type:** `string[]`
|
|
131
|
+
- **Default:** `undefined`
|
|
132
|
+
- Fully replace the auto-generated `@layer` statement with a custom layer order. When set, `additionalLayers` is ignored.
|
|
133
|
+
|
|
134
|
+
```js
|
|
135
|
+
dbUxPlugin({
|
|
136
|
+
overrideLayers: ["db-theme", "db-ux", "ri-extension"]
|
|
137
|
+
});
|
|
138
|
+
// → @layer db-theme, db-ux, ri-extension;
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
### `debug`
|
|
142
|
+
|
|
143
|
+
- **Type:** `boolean`
|
|
144
|
+
- **Default:** `false`
|
|
145
|
+
- Generate a `db-ux-detection-report.json` file in the project root directory for debugging purposes. This report contains all detected components, colors, densities, and font sizes. Useful for troubleshooting optimization issues.
|
|
146
|
+
|
|
147
|
+
## How it works
|
|
148
|
+
|
|
149
|
+
The plugin analyzes your source files to detect:
|
|
150
|
+
|
|
151
|
+
- DB UX component usage (e.g., `class="db-button"`)
|
|
152
|
+
- Custom classes using design tokens
|
|
153
|
+
- Explicitly configured includes/excludes
|
|
154
|
+
|
|
155
|
+
It then generates an optimized CSS bundle containing only:
|
|
156
|
+
|
|
157
|
+
- Required foundation styles (theme, fonts, icons)
|
|
158
|
+
- Component styles for detected components
|
|
159
|
+
- Optional features based on configuration
|
|
160
|
+
|
|
161
|
+
## Troubleshooting
|
|
162
|
+
|
|
163
|
+
### Plugin Order with Tailwind CSS
|
|
164
|
+
|
|
165
|
+
If you're using Tailwind CSS alongside this plugin, **the order matters**. The `@db-ux/core-vite-plugin` must be placed **before** the Tailwind plugin in your Vite configuration:
|
|
166
|
+
|
|
167
|
+
```js
|
|
168
|
+
import { defineConfig } from "vite";
|
|
169
|
+
import dbUxPlugin from "@db-ux/core-vite-plugin";
|
|
170
|
+
import tailwindcss from "@tailwindcss/vite";
|
|
171
|
+
|
|
172
|
+
export default defineConfig({
|
|
173
|
+
plugins: [
|
|
174
|
+
dbUxPlugin(), // Must come before tailwindcss()
|
|
175
|
+
tailwindcss()
|
|
176
|
+
]
|
|
177
|
+
});
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
And in your CSS file:
|
|
181
|
+
|
|
182
|
+
```css
|
|
183
|
+
@import "tailwindcss";
|
|
184
|
+
@import "@db-ux/core-vite-plugin/index.css";
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
**Why?** The DB UX plugin needs to process and replace the `@import "@db-ux/core-vite-plugin/index.css"` statement with actual CSS before Tailwind processes the file. Tailwind will strip out unrecognized imports, so the DB UX plugin must run first to transform the import into valid CSS that Tailwind can then process.
|
|
188
|
+
|
|
189
|
+
### CSS `@property` Warnings
|
|
190
|
+
|
|
191
|
+
You may see warnings about "Unknown at rule: @property" in your build output or IDE. These are informational warnings from CSS parsers that don't yet fully support the CSS Houdini `@property` at-rule. These warnings don't affect functionality - the `@property` rule is valid CSS and works correctly in modern browsers. You can safely ignore these warnings or configure your CSS linter to suppress them.
|
|
192
|
+
|
|
193
|
+
### Styles Not Loading in Dev Mode with `<link>` Tags
|
|
194
|
+
|
|
195
|
+
If you load your CSS via a `<link>` tag in `index.html`:
|
|
196
|
+
|
|
197
|
+
```html
|
|
198
|
+
<!-- ❌ This won't work with the plugin in dev mode -->
|
|
199
|
+
<link rel="stylesheet" href="/src/styles.css" />
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
The plugin's `transform` hook only runs for CSS that is part of Vite's module graph. Static `<link>` tags are served as-is by Vite's dev server, so the `@import "@db-ux/core-vite-plugin/index.css"` inside the CSS file is never processed.
|
|
203
|
+
|
|
204
|
+
**Solution:** Import the CSS from your JavaScript/TypeScript entry file instead:
|
|
205
|
+
|
|
206
|
+
```ts
|
|
207
|
+
// main.ts or main.tsx
|
|
208
|
+
import "./styles.css";
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
This pulls the CSS into Vite's module graph where the plugin can transform it. The production build is not affected by this issue.
|
|
212
|
+
|
|
213
|
+
## License
|
|
214
|
+
|
|
215
|
+
This project is licensed under [Apache-2.0](LICENSE).
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Walk up the directory tree from `root` to locate a package path inside node_modules.
|
|
3
|
+
* Handles monorepo hoisting where dependencies may live in a parent node_modules.
|
|
4
|
+
* Returns the resolved absolute path or null if not found.
|
|
5
|
+
*/
|
|
6
|
+
export declare function resolvePackagePath(root: string, packagePath: string): string | null;
|
|
7
|
+
/** Public accessor for the discovery cache. */
|
|
8
|
+
export declare function discoverAll(root: string): {
|
|
9
|
+
components: Set<string>;
|
|
10
|
+
colors: string[];
|
|
11
|
+
densities: string[];
|
|
12
|
+
fontSizes: string[];
|
|
13
|
+
};
|
|
14
|
+
/**
|
|
15
|
+
* Scan detected component CSS files for referenced colors, densities, and font sizes
|
|
16
|
+
* so the optimizer doesn't strip variables that components depend on.
|
|
17
|
+
*/
|
|
18
|
+
export declare function scanComponentDependencies(root: string, components: Set<string>, colors: Set<string>, densities: Set<string>, fontSizes: Set<string>): void;
|
|
19
|
+
/**
|
|
20
|
+
* Scan all project source files to detect which DB UX components are used.
|
|
21
|
+
* Supports JSX (<DBButton>), kebab-case (<db-button>), CSS classes (class="db-button"),
|
|
22
|
+
* and named imports (import { DBButton } from '...').
|
|
23
|
+
*/
|
|
24
|
+
export declare function detectComponents(root: string, forceInclude: string[]): Promise<Set<string>>;
|
|
25
|
+
/** Detect which color schemes are used in the project (e.g. "cyan", "brand"). */
|
|
26
|
+
export declare function detectColors(root: string, forceInclude: string[]): Promise<Set<string>>;
|
|
27
|
+
/** Detect which density variants are used in the project (e.g. "functional", "expressive"). */
|
|
28
|
+
export declare function detectDensities(root: string, forceInclude: string[]): Promise<Set<string>>;
|
|
29
|
+
/** Detect which font size combinations are used in the project (e.g. "body-md", "headline-lg"). */
|
|
30
|
+
export declare function detectFontSizes(root: string, forceInclude: string[]): Promise<Set<string>>;
|
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
import fg from 'fast-glob';
|
|
2
|
+
import { readFileSync, readdirSync } from 'fs';
|
|
3
|
+
import { resolve } from 'path';
|
|
4
|
+
/**
|
|
5
|
+
* Walk up the directory tree from `root` to locate a package path inside node_modules.
|
|
6
|
+
* Handles monorepo hoisting where dependencies may live in a parent node_modules.
|
|
7
|
+
* Returns the resolved absolute path or null if not found.
|
|
8
|
+
*/
|
|
9
|
+
export function resolvePackagePath(root, packagePath) {
|
|
10
|
+
let currentDir = root;
|
|
11
|
+
for (let i = 0; i < 10; i++) {
|
|
12
|
+
const resolved = resolve(currentDir, 'node_modules', packagePath);
|
|
13
|
+
try {
|
|
14
|
+
readdirSync(resolved);
|
|
15
|
+
return resolved;
|
|
16
|
+
}
|
|
17
|
+
catch {
|
|
18
|
+
// Try parent directory
|
|
19
|
+
}
|
|
20
|
+
const parentDir = resolve(currentDir, '..');
|
|
21
|
+
if (parentDir === currentDir)
|
|
22
|
+
break;
|
|
23
|
+
currentDir = parentDir;
|
|
24
|
+
}
|
|
25
|
+
return null;
|
|
26
|
+
}
|
|
27
|
+
/** Return subdirectory names (folders only) from the given path. */
|
|
28
|
+
function readDirNames(dirPath) {
|
|
29
|
+
try {
|
|
30
|
+
return readdirSync(dirPath, { withFileTypes: true })
|
|
31
|
+
.filter((e) => e.isDirectory())
|
|
32
|
+
.map((e) => e.name);
|
|
33
|
+
}
|
|
34
|
+
catch {
|
|
35
|
+
return [];
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
/** Return CSS file stems (without .css extension) from the given path, excluding "all.css". */
|
|
39
|
+
function readCssNames(dirPath) {
|
|
40
|
+
try {
|
|
41
|
+
return readdirSync(dirPath)
|
|
42
|
+
.filter((f) => f.endsWith('.css') && f !== 'all.css')
|
|
43
|
+
.map((f) => f.replace('.css', ''));
|
|
44
|
+
}
|
|
45
|
+
catch {
|
|
46
|
+
return [];
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
/** Cache of discovered design system values, keyed by project root. */
|
|
50
|
+
const cache = new Map();
|
|
51
|
+
/**
|
|
52
|
+
* Discover all available components, colors, densities, and font sizes
|
|
53
|
+
* by reading the installed @db-ux packages from the filesystem.
|
|
54
|
+
* Results are cached per project root.
|
|
55
|
+
*/
|
|
56
|
+
function discover(root) {
|
|
57
|
+
if (cache.has(root))
|
|
58
|
+
return cache.get(root);
|
|
59
|
+
// Components
|
|
60
|
+
const compDir = resolvePackagePath(root, '@db-ux/core-components/build/components');
|
|
61
|
+
const components = new Set(compDir ? readDirNames(compDir) : []);
|
|
62
|
+
// Colors
|
|
63
|
+
const colorDir = resolvePackagePath(root, '@db-ux/core-foundations/build/styles/colors/classes');
|
|
64
|
+
const colors = colorDir ? readCssNames(colorDir) : [];
|
|
65
|
+
// Densities
|
|
66
|
+
const densityDir = resolvePackagePath(root, '@db-ux/core-foundations/build/styles/density/classes');
|
|
67
|
+
const densities = densityDir ? readCssNames(densityDir) : [];
|
|
68
|
+
// Font sizes: body/<size>.css + headline/<size>.css → "body-sm", "headline-lg"
|
|
69
|
+
const fontDir = resolvePackagePath(root, '@db-ux/core-foundations/build/styles/fonts/classes');
|
|
70
|
+
const fontSizes = [];
|
|
71
|
+
if (fontDir) {
|
|
72
|
+
for (const category of readDirNames(fontDir)) {
|
|
73
|
+
for (const size of readCssNames(resolve(fontDir, category))) {
|
|
74
|
+
fontSizes.push(`${category}-${size}`);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
const result = { components, colors, densities, fontSizes };
|
|
79
|
+
cache.set(root, result);
|
|
80
|
+
return result;
|
|
81
|
+
}
|
|
82
|
+
/** Public accessor for the discovery cache. */
|
|
83
|
+
export function discoverAll(root) {
|
|
84
|
+
return discover(root);
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Scan detected component CSS files for referenced colors, densities, and font sizes
|
|
88
|
+
* so the optimizer doesn't strip variables that components depend on.
|
|
89
|
+
*/
|
|
90
|
+
export function scanComponentDependencies(root, components, colors, densities, fontSizes) {
|
|
91
|
+
const compDir = resolvePackagePath(root, '@db-ux/core-components/build/components');
|
|
92
|
+
if (!compDir)
|
|
93
|
+
return;
|
|
94
|
+
const { colors: validColors, densities: validDensities, fontSizes: validFontSizes } = discover(root);
|
|
95
|
+
const validFontSizeSet = new Set(validFontSizes);
|
|
96
|
+
for (const component of components) {
|
|
97
|
+
const css = readSource(resolve(compDir, component, `${component}.css`));
|
|
98
|
+
if (!css)
|
|
99
|
+
continue;
|
|
100
|
+
for (const color of validColors) {
|
|
101
|
+
if (css.includes(`--db-${color}-`))
|
|
102
|
+
colors.add(color);
|
|
103
|
+
}
|
|
104
|
+
for (const density of validDensities) {
|
|
105
|
+
if (css.includes(`-${density}-`))
|
|
106
|
+
densities.add(density);
|
|
107
|
+
}
|
|
108
|
+
for (const m of css.matchAll(/--db-type-(body|headline)-(\w+)/g)) {
|
|
109
|
+
const fs = `${m[1]}-${m[2]}`;
|
|
110
|
+
if (validFontSizeSet.has(fs))
|
|
111
|
+
fontSizes.add(fs);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
/** Convert PascalCase to kebab-case: "NavigationItem" → "navigation-item". */
|
|
116
|
+
function toKebabCase(str) {
|
|
117
|
+
return str.replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase();
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Build an array of regex patterns to detect usage of design system values.
|
|
121
|
+
* Covers CSS classes (e.g. db-color-cyan), data attributes (e.g. data-color="cyan"),
|
|
122
|
+
* HTML attributes, and JS object notation.
|
|
123
|
+
*/
|
|
124
|
+
function buildPatterns(classPrefix, dataAttr, values) {
|
|
125
|
+
return [
|
|
126
|
+
new RegExp(`${classPrefix}(${values})`, 'g'),
|
|
127
|
+
new RegExp(`\\[${dataAttr}=["']?(${values})["']?\\]`, 'g'),
|
|
128
|
+
new RegExp(`${dataAttr}=["'](${values})["']`, 'g'),
|
|
129
|
+
new RegExp(`["']${dataAttr}["']:\\s*["'](${values})["']`, 'g')
|
|
130
|
+
];
|
|
131
|
+
}
|
|
132
|
+
/** Matches JSX/TSX component usage: <DBButton>, <DBNavigationItem> */
|
|
133
|
+
const JSX_COMPONENT_PATTERN = /<DB(\w+)[\s>/]/g;
|
|
134
|
+
/** Matches Angular/HTML kebab-case usage: <db-button>, <db-navigation-item> */
|
|
135
|
+
const KEBAB_COMPONENT_PATTERN = /<db-([\w-]+)[\s>/]/g;
|
|
136
|
+
/** Matches CSS class-based usage: class="db-button ...", className="db-card" */
|
|
137
|
+
const CLASS_COMPONENT_PATTERN = /(?:class|className)=(?:"[^"]*|'[^']*|\{[^}]*)db-([\w-]+)/g;
|
|
138
|
+
/** Matches named imports from @db-ux framework packages: import { DBButton, DBCard } from '...' */
|
|
139
|
+
const IMPORT_PATTERN = /import\s+\{([^}]+)}\s+from\s+['"]@db-ux\/(?:react|ngx|v|wc)-core-components['"]/g;
|
|
140
|
+
/** Glob all source files from the project root, excluding node_modules/dist/build. */
|
|
141
|
+
async function scanFiles(root) {
|
|
142
|
+
return fg(['**/*.{vue,jsx,tsx,ts,html}'], {
|
|
143
|
+
cwd: root,
|
|
144
|
+
absolute: true,
|
|
145
|
+
ignore: ['**/node_modules/**', '**/dist/**', '**/build/**']
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
/** Safely read a file's contents, returning null on failure. */
|
|
149
|
+
function readSource(filePath) {
|
|
150
|
+
try {
|
|
151
|
+
return readFileSync(filePath, 'utf-8');
|
|
152
|
+
}
|
|
153
|
+
catch {
|
|
154
|
+
return null;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* Scan all project source files to detect which DB UX components are used.
|
|
159
|
+
* Supports JSX (<DBButton>), kebab-case (<db-button>), CSS classes (class="db-button"),
|
|
160
|
+
* and named imports (import { DBButton } from '...').
|
|
161
|
+
*/
|
|
162
|
+
export async function detectComponents(root, forceInclude) {
|
|
163
|
+
const components = new Set(forceInclude);
|
|
164
|
+
const { components: validComponents } = discover(root);
|
|
165
|
+
const files = await scanFiles(root);
|
|
166
|
+
for (const file of files) {
|
|
167
|
+
const code = readSource(file);
|
|
168
|
+
if (!code)
|
|
169
|
+
continue;
|
|
170
|
+
// Detect JSX usage: <DBButton>, <DBNavigationItem>
|
|
171
|
+
for (const match of code.matchAll(JSX_COMPONENT_PATTERN)) {
|
|
172
|
+
const name = toKebabCase(match[1]);
|
|
173
|
+
if (validComponents.has(name)) {
|
|
174
|
+
components.add(name);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
// Detect kebab-case usage: <db-button>, <db-navigation-item>
|
|
178
|
+
for (const match of code.matchAll(KEBAB_COMPONENT_PATTERN)) {
|
|
179
|
+
if (validComponents.has(match[1])) {
|
|
180
|
+
components.add(match[1]);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
// Detect class-based usage: class="db-button ..."
|
|
184
|
+
for (const match of code.matchAll(CLASS_COMPONENT_PATTERN)) {
|
|
185
|
+
// Extract all db-* names from the matched attribute value
|
|
186
|
+
const fragment = match[0];
|
|
187
|
+
for (const inner of fragment.matchAll(/db-([\w-]+)/g)) {
|
|
188
|
+
if (validComponents.has(inner[1])) {
|
|
189
|
+
components.add(inner[1]);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
// Detect from imports: import { DBButton, DBCard } from '...'
|
|
194
|
+
for (const match of code.matchAll(IMPORT_PATTERN)) {
|
|
195
|
+
const importList = match[1];
|
|
196
|
+
for (const nameMatch of importList.matchAll(/\bDB(\w+)\b/g)) {
|
|
197
|
+
const name = toKebabCase(nameMatch[1]);
|
|
198
|
+
if (validComponents.has(name)) {
|
|
199
|
+
components.add(name);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
return components;
|
|
205
|
+
}
|
|
206
|
+
/**
|
|
207
|
+
* Shared detection logic for colors, densities, and font sizes.
|
|
208
|
+
* Scans all project source files for class names and data attributes
|
|
209
|
+
* matching the given patterns, returning the set of detected values.
|
|
210
|
+
*/
|
|
211
|
+
async function detectByPatterns(root, forceInclude, classPrefix, dataAttr, validValues, mapMatch) {
|
|
212
|
+
const result = new Set(forceInclude);
|
|
213
|
+
if (validValues.length === 0)
|
|
214
|
+
return result;
|
|
215
|
+
const patterns = buildPatterns(classPrefix, dataAttr, validValues.join('|'));
|
|
216
|
+
const files = await scanFiles(root);
|
|
217
|
+
for (const file of files) {
|
|
218
|
+
const code = readSource(file);
|
|
219
|
+
if (!code)
|
|
220
|
+
continue;
|
|
221
|
+
for (const pattern of patterns) {
|
|
222
|
+
for (const match of code.matchAll(pattern)) {
|
|
223
|
+
const value = mapMatch ? mapMatch(match) : match[1];
|
|
224
|
+
if (value)
|
|
225
|
+
result.add(value);
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
return result;
|
|
230
|
+
}
|
|
231
|
+
/** Detect which color schemes are used in the project (e.g. "cyan", "brand"). */
|
|
232
|
+
export async function detectColors(root, forceInclude) {
|
|
233
|
+
const { colors } = discover(root);
|
|
234
|
+
return detectByPatterns(root, forceInclude, 'db-color-', 'data-color', colors);
|
|
235
|
+
}
|
|
236
|
+
/** Detect which density variants are used in the project (e.g. "functional", "expressive"). */
|
|
237
|
+
export async function detectDensities(root, forceInclude) {
|
|
238
|
+
const { densities } = discover(root);
|
|
239
|
+
return detectByPatterns(root, forceInclude, 'db-density-', 'data-density', densities);
|
|
240
|
+
}
|
|
241
|
+
/** Detect which font size combinations are used in the project (e.g. "body-md", "headline-lg"). */
|
|
242
|
+
export async function detectFontSizes(root, forceInclude) {
|
|
243
|
+
const { fontSizes: validFontSizes } = discover(root);
|
|
244
|
+
if (validFontSizes.length === 0)
|
|
245
|
+
return new Set(forceInclude);
|
|
246
|
+
const categories = [...new Set(validFontSizes.map((f) => f.split('-')[0]))];
|
|
247
|
+
const sizes = [...new Set(validFontSizes.map((f) => f.split('-')[1]))];
|
|
248
|
+
const validSet = new Set(validFontSizes);
|
|
249
|
+
return detectByPatterns(root, forceInclude, 'db-font-size-', 'data-font-size', [`(${categories.join('|')})-(${sizes.join('|')})`], (match) => {
|
|
250
|
+
const value = `${match[1]}-${match[2]}`;
|
|
251
|
+
return validSet.has(value) ? value : null;
|
|
252
|
+
});
|
|
253
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { GenerateOptions } from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Generate the CSS import statements based on detected/discovered values.
|
|
4
|
+
* Produces @import rules for theme, foundations, colors, densities,
|
|
5
|
+
* font sizes, and component styles, all wrapped in @layer declarations.
|
|
6
|
+
*/
|
|
7
|
+
export declare function generateCSS(options: GenerateOptions): string;
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import { readdirSync } from 'fs';
|
|
2
|
+
import { resolve } from 'path';
|
|
3
|
+
/** Maps foundation feature names to their CSS file paths relative to build/styles/. */
|
|
4
|
+
const FOUNDATION_IMPORTS = {
|
|
5
|
+
helpers: 'helpers/classes/all.css',
|
|
6
|
+
elevation: 'defaults/default-elevation.css',
|
|
7
|
+
animations: 'component-animations.css',
|
|
8
|
+
icons: 'defaults/default-icons.css',
|
|
9
|
+
code: 'defaults/default-code.css'
|
|
10
|
+
};
|
|
11
|
+
/** Theme package scopes to search for installed themes. */
|
|
12
|
+
const THEME_SCOPES = ['@db-ux', '@db-ux-inner-source'];
|
|
13
|
+
/**
|
|
14
|
+
* Auto-detect the installed DB UX theme package (e.g. @db-ux/db-theme).
|
|
15
|
+
* Walks up from `root` checking each node_modules for *-theme packages
|
|
16
|
+
* in both @db-ux/* and @db-ux-inner-source/* scopes.
|
|
17
|
+
* Returns the package specifier or null if no theme is found.
|
|
18
|
+
*/
|
|
19
|
+
function detectTheme(root, preferredTheme) {
|
|
20
|
+
let currentDir = root;
|
|
21
|
+
for (let i = 0; i < 10; i++) {
|
|
22
|
+
for (const scope of THEME_SCOPES) {
|
|
23
|
+
try {
|
|
24
|
+
const scopeDir = resolve(currentDir, 'node_modules', scope);
|
|
25
|
+
const packages = readdirSync(scopeDir);
|
|
26
|
+
const themePackages = packages.filter((pkg) => pkg.endsWith('-theme'));
|
|
27
|
+
if (themePackages.length === 0)
|
|
28
|
+
continue;
|
|
29
|
+
const match = preferredTheme && themePackages.includes(preferredTheme)
|
|
30
|
+
? preferredTheme
|
|
31
|
+
: themePackages[0];
|
|
32
|
+
return `${scope}/${match}`;
|
|
33
|
+
}
|
|
34
|
+
catch {
|
|
35
|
+
// Scope not found at this level
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
const parentDir = resolve(currentDir, '..');
|
|
39
|
+
if (parentDir === currentDir)
|
|
40
|
+
break;
|
|
41
|
+
currentDir = parentDir;
|
|
42
|
+
}
|
|
43
|
+
return null;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Generate the CSS import statements based on detected/discovered values.
|
|
47
|
+
* Produces @import rules for theme, foundations, colors, densities,
|
|
48
|
+
* font sizes, and component styles, all wrapped in @layer declarations.
|
|
49
|
+
*/
|
|
50
|
+
export function generateCSS(options) {
|
|
51
|
+
const { root, include, exclude, theme: preferredTheme, hasTailwind, overrideLayers, additionalLayers } = options;
|
|
52
|
+
const { components, foundations, colors, densities, fontSizes } = include;
|
|
53
|
+
const imports = [];
|
|
54
|
+
const theme = detectTheme(root, preferredTheme);
|
|
55
|
+
const themeName = theme ? theme.split('/').pop() : null;
|
|
56
|
+
// Layer order declaration
|
|
57
|
+
if (overrideLayers?.length) {
|
|
58
|
+
imports.push(`@layer ${overrideLayers.join(', ')};`);
|
|
59
|
+
}
|
|
60
|
+
else {
|
|
61
|
+
let autoLayers;
|
|
62
|
+
if (hasTailwind) {
|
|
63
|
+
autoLayers = themeName
|
|
64
|
+
? [
|
|
65
|
+
themeName,
|
|
66
|
+
'theme',
|
|
67
|
+
'base',
|
|
68
|
+
'components',
|
|
69
|
+
'db-ux',
|
|
70
|
+
'utilities'
|
|
71
|
+
]
|
|
72
|
+
: ['theme', 'base', 'components', 'db-ux', 'utilities'];
|
|
73
|
+
}
|
|
74
|
+
else {
|
|
75
|
+
autoLayers = themeName ? [themeName, 'db-ux'] : ['db-ux'];
|
|
76
|
+
}
|
|
77
|
+
if (additionalLayers?.before?.length) {
|
|
78
|
+
autoLayers = [...additionalLayers.before, ...autoLayers];
|
|
79
|
+
}
|
|
80
|
+
if (additionalLayers?.after?.length) {
|
|
81
|
+
autoLayers = [...autoLayers, ...additionalLayers.after];
|
|
82
|
+
}
|
|
83
|
+
imports.push(`@layer ${autoLayers.join(', ')};`);
|
|
84
|
+
}
|
|
85
|
+
// Theme or default fallback
|
|
86
|
+
if (theme) {
|
|
87
|
+
imports.push(`@import "${theme}/build/styles/rollup.css" layer(${themeName});`);
|
|
88
|
+
imports.push(`@import "@db-ux/core-foundations/build/styles/defaults/default-container-properties.css" layer(db-ux);`);
|
|
89
|
+
}
|
|
90
|
+
else {
|
|
91
|
+
imports.push(`@import "@db-ux/core-foundations/build/styles/theme/rollup.css" layer(db-ux);`);
|
|
92
|
+
imports.push(`@import "@db-ux/core-foundations/build/styles/fonts/rollup.css" layer(db-ux);`);
|
|
93
|
+
}
|
|
94
|
+
// Tailwind theme
|
|
95
|
+
if (hasTailwind) {
|
|
96
|
+
imports.push(`@import "@db-ux/core-foundations/build/tailwind/theme/index.css";`);
|
|
97
|
+
}
|
|
98
|
+
// Required foundation styles
|
|
99
|
+
imports.push(`@import "@db-ux/core-foundations/build/styles/defaults/default-required.css" layer(db-ux);`);
|
|
100
|
+
imports.push(`@import "@db-ux/core-foundations/build/styles/defaults/default-root.css" layer(db-ux);`);
|
|
101
|
+
// Optional foundation features
|
|
102
|
+
for (const [key, path] of Object.entries(FOUNDATION_IMPORTS)) {
|
|
103
|
+
const feature = key;
|
|
104
|
+
if (foundations?.includes(feature) &&
|
|
105
|
+
!exclude.foundations?.includes(feature)) {
|
|
106
|
+
const basePath = feature === 'animations'
|
|
107
|
+
? '@db-ux/core-components'
|
|
108
|
+
: '@db-ux/core-foundations';
|
|
109
|
+
imports.push(`@import "${basePath}/build/styles/${path}" layer(db-ux);`);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
// Color schemes
|
|
113
|
+
for (const color of colors || []) {
|
|
114
|
+
if (!exclude.colors?.includes(color)) {
|
|
115
|
+
imports.push(`@import "@db-ux/core-foundations/build/styles/colors/classes/${color}.css" layer(db-ux);`);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
// Densities
|
|
119
|
+
for (const density of densities || []) {
|
|
120
|
+
if (!exclude.densities?.includes(density)) {
|
|
121
|
+
imports.push(`@import "@db-ux/core-foundations/build/styles/density/classes/${density}.css" layer(db-ux);`);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
// Font sizes
|
|
125
|
+
for (const fontSize of fontSizes || []) {
|
|
126
|
+
if (!exclude.fontSizes?.includes(fontSize)) {
|
|
127
|
+
const [category, size] = fontSize.split('-');
|
|
128
|
+
imports.push(`@import "@db-ux/core-foundations/build/styles/fonts/classes/${category}/${size}.css" layer(db-ux);`);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
// Component styles
|
|
132
|
+
for (const component of components || []) {
|
|
133
|
+
if (!exclude.components?.includes(component)) {
|
|
134
|
+
imports.push(`@import "@db-ux/core-components/build/components/${component}/${component}.css" layer(db-ux);`);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
return imports.join('\n');
|
|
138
|
+
}
|
package/build/index.d.ts
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { PluginConfig } from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Create the DB UX Vite plugin.
|
|
4
|
+
* Returns two plugins: a pre-transform plugin for CSS generation and
|
|
5
|
+
* a post-build plugin for optimizing the final CSS bundle.
|
|
6
|
+
*
|
|
7
|
+
* During dev, all available styles are included for instant HMR.
|
|
8
|
+
* During build, only detected styles are included and unused ones are stripped.
|
|
9
|
+
*/
|
|
10
|
+
export default function dbUxPlugin(config?: PluginConfig): any[];
|
|
11
|
+
export type { PluginConfig };
|
package/build/index.js
ADDED
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
import { writeFile } from 'fs/promises';
|
|
2
|
+
import { resolve } from 'path';
|
|
3
|
+
import { detectColors, detectComponents, detectDensities, detectFontSizes, discoverAll, scanComponentDependencies } from './detector.js';
|
|
4
|
+
import { generateCSS } from './generator.js';
|
|
5
|
+
import { removeUnusedStyles } from './optimizer.js';
|
|
6
|
+
/** Default foundation features included unless overridden by user config. */
|
|
7
|
+
const DEFAULT_FOUNDATIONS = [
|
|
8
|
+
'helpers',
|
|
9
|
+
'icons',
|
|
10
|
+
'animations',
|
|
11
|
+
'code',
|
|
12
|
+
'elevation'
|
|
13
|
+
];
|
|
14
|
+
/**
|
|
15
|
+
* Create the DB UX Vite plugin.
|
|
16
|
+
* Returns two plugins: a pre-transform plugin for CSS generation and
|
|
17
|
+
* a post-build plugin for optimizing the final CSS bundle.
|
|
18
|
+
*
|
|
19
|
+
* During dev, all available styles are included for instant HMR.
|
|
20
|
+
* During build, only detected styles are included and unused ones are stripped.
|
|
21
|
+
*/
|
|
22
|
+
export default function dbUxPlugin(config = {}) {
|
|
23
|
+
const { optimize = true, debug = false } = config;
|
|
24
|
+
// Deep-merge include/exclude so partial user config doesn't lose defaults
|
|
25
|
+
const include = {
|
|
26
|
+
foundations: config.include?.foundations ?? [...DEFAULT_FOUNDATIONS],
|
|
27
|
+
components: config.include?.components,
|
|
28
|
+
colors: config.include?.colors,
|
|
29
|
+
densities: config.include?.densities,
|
|
30
|
+
fontSizes: config.include?.fontSizes
|
|
31
|
+
};
|
|
32
|
+
const exclude = config.exclude ?? {};
|
|
33
|
+
let detectedComponents = new Set();
|
|
34
|
+
let detectedColors = new Set();
|
|
35
|
+
let detectedDensities = new Set();
|
|
36
|
+
let detectedFontSizes = new Set();
|
|
37
|
+
let hasDetected = false;
|
|
38
|
+
let detectionPromise = null;
|
|
39
|
+
let cssModuleId = null;
|
|
40
|
+
let hasTailwind = false;
|
|
41
|
+
let root = process.cwd();
|
|
42
|
+
let isBuild = false;
|
|
43
|
+
let generatedImports = [];
|
|
44
|
+
const mainPlugin = {
|
|
45
|
+
name: 'db-ux-vite-plugin',
|
|
46
|
+
enforce: 'pre',
|
|
47
|
+
resolveId(id) {
|
|
48
|
+
if (id === '@db-ux/core-vite-plugin/index.css') {
|
|
49
|
+
return id;
|
|
50
|
+
}
|
|
51
|
+
},
|
|
52
|
+
load(id) {
|
|
53
|
+
if (id === '@db-ux/core-vite-plugin/index.css') {
|
|
54
|
+
return '';
|
|
55
|
+
}
|
|
56
|
+
},
|
|
57
|
+
configResolved(resolvedConfig) {
|
|
58
|
+
root = resolvedConfig.root;
|
|
59
|
+
isBuild = resolvedConfig.command === 'build';
|
|
60
|
+
hasTailwind = resolvedConfig.plugins.some((plugin) => plugin.name.startsWith('@tailwindcss/vite'));
|
|
61
|
+
if (debug) {
|
|
62
|
+
console.log(`[db-ux-vite-plugin] Initialized (mode: ${resolvedConfig.command}, tailwind: ${hasTailwind})`);
|
|
63
|
+
}
|
|
64
|
+
},
|
|
65
|
+
handleHotUpdate({ file, server, modules }) {
|
|
66
|
+
if (/\.(vue|jsx|tsx|ts|html)$/.test(file) && cssModuleId) {
|
|
67
|
+
const mod = server.moduleGraph.getModuleById(cssModuleId);
|
|
68
|
+
if (mod) {
|
|
69
|
+
server.moduleGraph.invalidateModule(mod);
|
|
70
|
+
return [...modules, mod];
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
},
|
|
74
|
+
async transform(code, id) {
|
|
75
|
+
if (!id.endsWith('.css'))
|
|
76
|
+
return;
|
|
77
|
+
const hasImport = code.includes('@import "@db-ux/core-vite-plugin/index.css"') ||
|
|
78
|
+
code.includes("@import '@db-ux/core-vite-plugin/index.css'");
|
|
79
|
+
if (hasImport) {
|
|
80
|
+
cssModuleId = id;
|
|
81
|
+
if (isBuild && !hasDetected && !detectionPromise) {
|
|
82
|
+
detectionPromise = (async () => {
|
|
83
|
+
hasDetected = true;
|
|
84
|
+
detectedComponents = await detectComponents(root, include.components || []);
|
|
85
|
+
detectedColors = await detectColors(root, include.colors || []);
|
|
86
|
+
detectedDensities = await detectDensities(root, include.densities || []);
|
|
87
|
+
detectedFontSizes = await detectFontSizes(root, include.fontSizes || []);
|
|
88
|
+
scanComponentDependencies(root, detectedComponents, detectedColors, detectedDensities, detectedFontSizes);
|
|
89
|
+
})();
|
|
90
|
+
}
|
|
91
|
+
if (detectionPromise) {
|
|
92
|
+
await detectionPromise;
|
|
93
|
+
}
|
|
94
|
+
// During dev, include all discovered values for instant HMR.
|
|
95
|
+
// During build, use only detected values for tree-shaking.
|
|
96
|
+
const discovered = discoverAll(root);
|
|
97
|
+
const css = generateCSS({
|
|
98
|
+
root,
|
|
99
|
+
include: {
|
|
100
|
+
components: isBuild
|
|
101
|
+
? Array.from(detectedComponents)
|
|
102
|
+
: Array.from(discovered.components),
|
|
103
|
+
foundations: include.foundations || [],
|
|
104
|
+
colors: isBuild
|
|
105
|
+
? Array.from(detectedColors)
|
|
106
|
+
: discovered.colors,
|
|
107
|
+
densities: isBuild
|
|
108
|
+
? Array.from(detectedDensities)
|
|
109
|
+
: discovered.densities,
|
|
110
|
+
fontSizes: isBuild
|
|
111
|
+
? Array.from(detectedFontSizes)
|
|
112
|
+
: discovered.fontSizes
|
|
113
|
+
},
|
|
114
|
+
exclude,
|
|
115
|
+
theme: config.theme,
|
|
116
|
+
additionalLayers: config.additionalLayers,
|
|
117
|
+
overrideLayers: config.overrideLayers,
|
|
118
|
+
hasTailwind
|
|
119
|
+
});
|
|
120
|
+
generatedImports = css.split('\n');
|
|
121
|
+
if (debug) {
|
|
122
|
+
console.log('\n[db-ux-vite-plugin] Generated imports:\n' +
|
|
123
|
+
generatedImports
|
|
124
|
+
.map((line) => ` ${line}`)
|
|
125
|
+
.join('\n') +
|
|
126
|
+
'\n');
|
|
127
|
+
}
|
|
128
|
+
code = code.replace(/@import ["']@db-ux\/core-vite-plugin\/index\.css["'];?/g, css);
|
|
129
|
+
}
|
|
130
|
+
return code;
|
|
131
|
+
},
|
|
132
|
+
async buildEnd() {
|
|
133
|
+
if (debug && hasDetected) {
|
|
134
|
+
const reportPath = resolve(root, 'db-ux-detection-report.json');
|
|
135
|
+
await writeFile(reportPath, JSON.stringify({
|
|
136
|
+
components: Array.from(detectedComponents).sort(),
|
|
137
|
+
colors: Array.from(detectedColors).sort(),
|
|
138
|
+
densities: Array.from(detectedDensities).sort(),
|
|
139
|
+
fontSizes: Array.from(detectedFontSizes).sort(),
|
|
140
|
+
generatedImports
|
|
141
|
+
}, null, 2), 'utf-8');
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
};
|
|
145
|
+
const optimizerPlugin = {
|
|
146
|
+
name: 'db-ux-vite-plugin:optimizer',
|
|
147
|
+
enforce: 'post',
|
|
148
|
+
generateBundle(_options, bundle) {
|
|
149
|
+
if (!optimize || !hasDetected)
|
|
150
|
+
return;
|
|
151
|
+
const { colors, densities, fontSizes } = discoverAll(root);
|
|
152
|
+
const optimizerContext = {
|
|
153
|
+
allColors: colors,
|
|
154
|
+
allDensities: densities,
|
|
155
|
+
allFontSizes: fontSizes
|
|
156
|
+
};
|
|
157
|
+
for (const [, asset] of Object.entries(bundle)) {
|
|
158
|
+
if (asset.type === 'asset' &&
|
|
159
|
+
typeof asset.fileName === 'string' &&
|
|
160
|
+
asset.fileName.endsWith('.css') &&
|
|
161
|
+
typeof asset.source === 'string') {
|
|
162
|
+
asset.source = removeUnusedStyles(asset.source, detectedColors, detectedDensities, detectedFontSizes, optimizerContext);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
};
|
|
167
|
+
return [mainPlugin, optimizerPlugin];
|
|
168
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/** All available values discovered from the filesystem, used to determine what is unused. */
|
|
2
|
+
export interface OptimizerContext {
|
|
3
|
+
allColors: string[];
|
|
4
|
+
allDensities: string[];
|
|
5
|
+
allFontSizes: string[];
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Remove unused CSS custom properties, @property declarations, and rule blocks
|
|
9
|
+
* for colors, densities, and font sizes that were not detected in the project.
|
|
10
|
+
* Also strips empty @layer declarations and @charset directives.
|
|
11
|
+
*/
|
|
12
|
+
export declare function removeUnusedStyles(css: string, detectedColors: Set<string>, detectedDensities: Set<string>, detectedFontSizes: Set<string>, context: OptimizerContext): string;
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Remove unused CSS custom properties, @property declarations, and rule blocks
|
|
3
|
+
* for colors, densities, and font sizes that were not detected in the project.
|
|
4
|
+
* Also strips empty @layer declarations and @charset directives.
|
|
5
|
+
*/
|
|
6
|
+
export function removeUnusedStyles(css, detectedColors, detectedDensities, detectedFontSizes, context) {
|
|
7
|
+
const unusedColors = context.allColors.filter((c) => !detectedColors.has(c));
|
|
8
|
+
const unusedDensities = context.allDensities.filter((d) => !detectedDensities.has(d));
|
|
9
|
+
const unusedFontSizes = context.allFontSizes.filter((f) => !detectedFontSizes.has(f));
|
|
10
|
+
for (const color of unusedColors) {
|
|
11
|
+
css = css.replace(new RegExp(`@property --db-${color}-[a-z0-9-]+\\{[^}]+\\}`, 'g'), '');
|
|
12
|
+
// Make semicolon optional to handle last property in a block
|
|
13
|
+
css = css.replace(new RegExp(`--db-${color}-[a-z0-9-]+:[^;}]+;?`, 'g'), '');
|
|
14
|
+
// Normalize selectors — handle both quoted and unquoted data-color
|
|
15
|
+
css = css.replace(new RegExp(`\\[data-color=["']?${color}["']?\\],\\.db-color-${color}`, 'g'), `.db-color-${color}`);
|
|
16
|
+
css = css.replace(new RegExp(`\\.db-color-${color},\\[data-color=["']?${color}["']?\\]`, 'g'), `.db-color-${color}`);
|
|
17
|
+
// Remove entire rule blocks including pseudo-classes
|
|
18
|
+
css = css.replace(new RegExp(`\\.db-color-${color}(?:[^{]*?)\\{[^}]+\\}`, 'g'), '');
|
|
19
|
+
}
|
|
20
|
+
for (const density of unusedDensities) {
|
|
21
|
+
css = css.replace(new RegExp(`@property --db-[a-z-]+-${density}-[a-z0-9-]+\\{[^}]+\\}`, 'g'), '');
|
|
22
|
+
css = css.replace(new RegExp(`--db-[a-z-]+-${density}-[a-z0-9-]+:[^;}]+;?`, 'g'), '');
|
|
23
|
+
}
|
|
24
|
+
for (const fontSize of unusedFontSizes) {
|
|
25
|
+
const [type, size] = fontSize.split('-');
|
|
26
|
+
css = css.replace(new RegExp(`--db-type-${fontSize}:[^;}]+;?`, 'g'), '');
|
|
27
|
+
css = css.replace(new RegExp(`--db-base-${type}-icon-weight-${size}:[^;}]+;?`, 'g'), '');
|
|
28
|
+
css = css.replace(new RegExp(`@property --db-base-icon-weight-[a-z]+-[a-z]+-${type}-${size}\\{[^}]+\\}`, 'g'), '');
|
|
29
|
+
css = css.replace(new RegExp(`@property --db-base-icon-font-size-[a-z]+-[a-z]+-${type}-${size}\\{[^}]+\\}`, 'g'), '');
|
|
30
|
+
css = css.replace(new RegExp(`@property --db-typography-[a-z]+-[a-z]+-${type}-${size}\\{[^}]+\\}`, 'g'), '');
|
|
31
|
+
}
|
|
32
|
+
css = css.replace(/@layer variables;/g, '');
|
|
33
|
+
css = css.replace(/@charset "UTF-8";/g, '');
|
|
34
|
+
return css;
|
|
35
|
+
}
|
package/build/types.d.ts
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Foundation features that can be included or excluded.
|
|
3
|
+
* - `icons`: Include icon fonts
|
|
4
|
+
* - `helpers`: Include helper classes
|
|
5
|
+
* - `elevation`: Include elevation styles
|
|
6
|
+
* - `animations`: Include component animations
|
|
7
|
+
* - `code`: Include code styling
|
|
8
|
+
*/
|
|
9
|
+
export type FoundationFeature = 'icons' | 'helpers' | 'elevation' | 'animations' | 'code';
|
|
10
|
+
/**
|
|
11
|
+
* Available color schemes for the design system.
|
|
12
|
+
*/
|
|
13
|
+
export type ColorScheme = 'neutral' | 'brand' | 'blue' | 'burgundy' | 'critical' | 'cyan' | 'green' | 'informational' | 'light-green' | 'orange' | 'pink' | 'red' | 'successful' | 'turquoise' | 'violet' | 'warning' | 'yellow';
|
|
14
|
+
/**
|
|
15
|
+
* Available density options for components.
|
|
16
|
+
*/
|
|
17
|
+
export type Density = 'regular' | 'functional' | 'expressive';
|
|
18
|
+
/**
|
|
19
|
+
* Available font size options.
|
|
20
|
+
*/
|
|
21
|
+
export type FontSize = 'body-3xs' | 'body-2xs' | 'body-xs' | 'body-sm' | 'body-md' | 'body-lg' | 'body-xl' | 'body-2xl' | 'body-3xl' | 'headline-3xs' | 'headline-2xs' | 'headline-xs' | 'headline-sm' | 'headline-md' | 'headline-lg' | 'headline-xl' | 'headline-2xl' | 'headline-3xl';
|
|
22
|
+
/**
|
|
23
|
+
* Configuration options for the Vite plugin.
|
|
24
|
+
*/
|
|
25
|
+
export interface PluginConfig {
|
|
26
|
+
/**
|
|
27
|
+
* Force include specific components, foundation features, color schemes, densities, or font sizes.
|
|
28
|
+
*/
|
|
29
|
+
include?: {
|
|
30
|
+
components?: string[];
|
|
31
|
+
foundations?: FoundationFeature[];
|
|
32
|
+
colors?: ColorScheme[];
|
|
33
|
+
densities?: Density[];
|
|
34
|
+
fontSizes?: FontSize[];
|
|
35
|
+
};
|
|
36
|
+
/**
|
|
37
|
+
* Exclude specific components, foundation features, color schemes, densities, or font sizes.
|
|
38
|
+
*/
|
|
39
|
+
exclude?: {
|
|
40
|
+
components?: string[];
|
|
41
|
+
foundations?: FoundationFeature[];
|
|
42
|
+
colors?: ColorScheme[];
|
|
43
|
+
densities?: Density[];
|
|
44
|
+
fontSizes?: FontSize[];
|
|
45
|
+
};
|
|
46
|
+
/**
|
|
47
|
+
* Remove unused CSS variable declarations to reduce bundle size (default: true).
|
|
48
|
+
*/
|
|
49
|
+
optimize?: boolean;
|
|
50
|
+
/**
|
|
51
|
+
* Specify preferred theme package name (e.g., "db-theme").
|
|
52
|
+
*/
|
|
53
|
+
theme?: string;
|
|
54
|
+
/**
|
|
55
|
+
* Append custom layers to the auto-generated `@layer` order.
|
|
56
|
+
* @example { after: ['ri-extension'] } → `@layer db-theme, db-ux, ri-extension;`
|
|
57
|
+
* @example { before: ['reset'] } → `@layer reset, db-theme, db-ux;`
|
|
58
|
+
*/
|
|
59
|
+
additionalLayers?: {
|
|
60
|
+
before?: string[];
|
|
61
|
+
after?: string[];
|
|
62
|
+
};
|
|
63
|
+
/**
|
|
64
|
+
* Fully replace the auto-generated `@layer` statement.
|
|
65
|
+
* When set, `additionalLayers` is ignored.
|
|
66
|
+
* @example ['db-theme', 'db-ux', 'ri-extension']
|
|
67
|
+
*/
|
|
68
|
+
overrideLayers?: string[];
|
|
69
|
+
/**
|
|
70
|
+
* Generate detection report for debugging (default: false).
|
|
71
|
+
*/
|
|
72
|
+
debug?: boolean;
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Internal options passed to the CSS generator.
|
|
76
|
+
*/
|
|
77
|
+
export interface GenerateOptions extends Pick<Required<PluginConfig>, 'include' | 'exclude'>, Pick<PluginConfig, 'theme' | 'additionalLayers' | 'overrideLayers'> {
|
|
78
|
+
/** Vite project root, used for resolving node_modules. */
|
|
79
|
+
root: string;
|
|
80
|
+
hasTailwind: boolean;
|
|
81
|
+
}
|
package/build/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/index.css
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
/* This file is processed by the @db-ux/core-vite-plugin */
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@db-ux/core-vite-plugin",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "4.6.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Vite plugin for optimized DB UX Design System CSS imports",
|
|
6
6
|
"repository": {
|
|
@@ -8,14 +8,52 @@
|
|
|
8
8
|
"url": "git+https://github.com/db-ux-design-system/core-web.git"
|
|
9
9
|
},
|
|
10
10
|
"license": "Apache-2.0",
|
|
11
|
+
"exports": {
|
|
12
|
+
".": {
|
|
13
|
+
"types": "./build/index.d.ts",
|
|
14
|
+
"default": "./build/index.js"
|
|
15
|
+
},
|
|
16
|
+
"./index.css": "./index.css",
|
|
17
|
+
"./index": "./index.css"
|
|
18
|
+
},
|
|
19
|
+
"files": [
|
|
20
|
+
"CHANGELOG.md",
|
|
21
|
+
"build",
|
|
22
|
+
"index.css",
|
|
23
|
+
"LICENSE"
|
|
24
|
+
],
|
|
11
25
|
"keywords": [
|
|
12
26
|
"vite",
|
|
13
27
|
"vite-plugin",
|
|
14
28
|
"db-ux",
|
|
15
29
|
"design-system"
|
|
16
30
|
],
|
|
31
|
+
"scripts": {
|
|
32
|
+
"build": "tsc",
|
|
33
|
+
"copy-output": "npm-run-all copy:*",
|
|
34
|
+
"copy:changelog": "cpr CHANGELOG.md ../../build-outputs/vite-plugin/CHANGELOG.md --overwrite",
|
|
35
|
+
"copy:index.css": "cpr index.css ../../build-outputs/vite-plugin/index.css -o",
|
|
36
|
+
"copy:outputs": "cpr build ../../build-outputs/vite-plugin/build -o",
|
|
37
|
+
"copy:package.json": "cpr package.json ../../build-outputs/vite-plugin/package.json -o",
|
|
38
|
+
"copy:readme": "cpr README.md ../../build-outputs/vite-plugin/README.md -o",
|
|
39
|
+
"test": "vitest run --config vitest.config.ts",
|
|
40
|
+
"test:update": "npm run test -- --update"
|
|
41
|
+
},
|
|
42
|
+
"peerDependencies": {
|
|
43
|
+
"vite": ">=4.0.0"
|
|
44
|
+
},
|
|
45
|
+
"dependencies": {
|
|
46
|
+
"fast-glob": "^3.3.3"
|
|
47
|
+
},
|
|
48
|
+
"devDependencies": {
|
|
49
|
+
"@types/node": "22.16.0",
|
|
50
|
+
"typescript": "5.9.3",
|
|
51
|
+
"vite": "8.0.6",
|
|
52
|
+
"vitest": "4.1.3"
|
|
53
|
+
},
|
|
17
54
|
"publishConfig": {
|
|
18
55
|
"registry": "https://registry.npmjs.org/",
|
|
19
56
|
"access": "public"
|
|
20
|
-
}
|
|
57
|
+
},
|
|
58
|
+
"style": "index.css"
|
|
21
59
|
}
|