@sylphx/silk-vite-plugin 2.1.0 → 2.2.1

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/dist/index.d.ts CHANGED
@@ -1,109 +1,50 @@
1
1
  /**
2
2
  * @sylphx/silk-vite-plugin
3
- * Universal plugin for zero-runtime Silk CSS-in-TypeScript
3
+ * Zero-codegen Vite plugin using virtual CSS modules
4
4
  *
5
- * Uses unplugin for cross-bundler compatibility
5
+ * Architecture:
6
+ * 1. Scan source files for css() calls
7
+ * 2. Generate CSS via scanAndGenerate()
8
+ * 3. Create virtual 'silk.css' module
9
+ * 4. CSS flows through Vite's CSS pipeline (PostCSS, optimization, bundling)
6
10
  */
7
- export interface CompressionOptions {
11
+ import type { Plugin } from 'vite';
12
+ import { type GenerateOptions } from '@sylphx/silk/codegen';
13
+ export interface SilkVitePluginOptions extends GenerateOptions {
8
14
  /**
9
- * Enable Brotli compression (.css.br)
10
- * @default true
15
+ * Source directory to scan for css() calls
16
+ * @default './src'
11
17
  */
12
- brotli?: boolean;
18
+ srcDir?: string;
13
19
  /**
14
- * Brotli quality (0-11)
15
- * @default 11
16
- */
17
- brotliQuality?: number;
18
- /**
19
- * Enable gzip compression (.css.gz)
20
- * @default true
21
- */
22
- gzip?: boolean;
23
- /**
24
- * Gzip level (0-9)
25
- * @default 9
26
- */
27
- gzipLevel?: number;
28
- }
29
- export interface PostCssOptions {
30
- /**
31
- * Enable PostCSS processing
32
- * @default false
33
- */
34
- enable?: boolean;
35
- /**
36
- * PostCSS plugins (e.g., autoprefixer, cssnano)
37
- * @example
38
- * ```typescript
39
- * import autoprefixer from 'autoprefixer'
40
- *
41
- * postCss: {
42
- * enable: true,
43
- * plugins: [
44
- * autoprefixer({ overrideBrowserslist: ['> 1%', 'last 2 versions'] })
45
- * ]
46
- * }
47
- * ```
48
- */
49
- plugins?: any[];
50
- /**
51
- * Generate source maps
52
- * @default false
53
- */
54
- sourceMaps?: boolean;
55
- }
56
- export interface SilkPluginOptions {
57
- /**
58
- * Output CSS file path
20
+ * Virtual module ID (what users import)
59
21
  * @default 'silk.css'
60
22
  */
61
- outputFile?: string;
62
- /**
63
- * Minify CSS output
64
- * @default true in production
65
- */
66
- minify?: boolean;
67
- /**
68
- * Pre-compression options
69
- */
70
- compression?: CompressionOptions;
23
+ virtualModuleId?: string;
71
24
  /**
72
- * PostCSS processing (optional)
73
- * Add autoprefixer for legacy browser support or custom PostCSS plugins
74
- *
75
- * @example
76
- * ```typescript
77
- * import autoprefixer from 'autoprefixer'
78
- *
79
- * {
80
- * postCss: {
81
- * enable: true,
82
- * plugins: [autoprefixer()],
83
- * sourceMaps: true
84
- * }
85
- * }
86
- * ```
87
- */
88
- postCss?: PostCssOptions;
89
- /**
90
- * Babel plugin options
25
+ * Enable debug logging
26
+ * @default false
91
27
  */
92
- babelOptions?: {
93
- production?: boolean;
94
- classPrefix?: string;
95
- importSources?: string[];
96
- tokens?: Record<string, any>;
97
- breakpoints?: Record<string, string>;
98
- };
28
+ debug?: boolean;
99
29
  }
100
30
  /**
101
- * Silk unplugin instance
31
+ * Silk Vite plugin - Zero-codegen with virtual CSS module
32
+ *
33
+ * @example
34
+ * ```typescript
35
+ * // vite.config.ts
36
+ * import silk from '@sylphx/silk-vite-plugin'
37
+ *
38
+ * export default {
39
+ * plugins: [silk()]
40
+ * }
41
+ * ```
42
+ *
43
+ * ```typescript
44
+ * // app.tsx
45
+ * import 'silk.css' // Virtual module → Vite CSS pipeline
46
+ * ```
102
47
  */
103
- export declare const unpluginSilk: import("unplugin").UnpluginInstance<SilkPluginOptions, boolean>;
104
- export declare const vite: (options: SilkPluginOptions) => import("vite").Plugin<any> | import("vite").Plugin<any>[];
105
- export declare const webpack: (options: SilkPluginOptions) => import("unplugin").WebpackPluginInstance;
106
- export declare const rollup: (options: SilkPluginOptions) => import("rollup").Plugin<any> | import("rollup").Plugin<any>[];
107
- export declare const esbuild: (options: SilkPluginOptions) => import("unplugin").EsbuildPlugin;
108
- export default vite;
48
+ export default function silkPlugin(options?: SilkVitePluginOptions): Plugin;
49
+ export { silkPlugin };
109
50
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAYH,MAAM,WAAW,kBAAkB;IACjC;;;OAGG;IACH,MAAM,CAAC,EAAE,OAAO,CAAA;IAEhB;;;OAGG;IACH,aAAa,CAAC,EAAE,MAAM,CAAA;IAEtB;;;OAGG;IACH,IAAI,CAAC,EAAE,OAAO,CAAA;IAEd;;;OAGG;IACH,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB;AAED,MAAM,WAAW,cAAc;IAC7B;;;OAGG;IACH,MAAM,CAAC,EAAE,OAAO,CAAA;IAEhB;;;;;;;;;;;;;OAaG;IACH,OAAO,CAAC,EAAE,GAAG,EAAE,CAAA;IAEf;;;OAGG;IACH,UAAU,CAAC,EAAE,OAAO,CAAA;CACrB;AAED,MAAM,WAAW,iBAAiB;IAChC;;;OAGG;IACH,UAAU,CAAC,EAAE,MAAM,CAAA;IAEnB;;;OAGG;IACH,MAAM,CAAC,EAAE,OAAO,CAAA;IAEhB;;OAEG;IACH,WAAW,CAAC,EAAE,kBAAkB,CAAA;IAEhC;;;;;;;;;;;;;;;;OAgBG;IACH,OAAO,CAAC,EAAE,cAAc,CAAA;IAExB;;OAEG;IACH,YAAY,CAAC,EAAE;QACb,UAAU,CAAC,EAAE,OAAO,CAAA;QACpB,WAAW,CAAC,EAAE,MAAM,CAAA;QACpB,aAAa,CAAC,EAAE,MAAM,EAAE,CAAA;QACxB,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAA;QAC5B,WAAW,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;KACrC,CAAA;CACF;AA0DD;;GAEG;AACH,eAAO,MAAM,YAAY,iEAkPvB,CAAA;AAGF,eAAO,MAAM,IAAI,2FAAoB,CAAA;AACrC,eAAO,MAAM,OAAO,0EAAuB,CAAA;AAC3C,eAAO,MAAM,MAAM,+FAAsB,CAAA;AACzC,eAAO,MAAM,OAAO,kEAAuB,CAAA;AAG3C,eAAe,IAAI,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,MAAM,CAAC;AACnC,OAAO,EAAmB,KAAK,eAAe,EAAE,MAAM,sBAAsB,CAAC;AAE7E,MAAM,WAAW,qBAAsB,SAAQ,eAAe;IAC5D;;;OAGG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;IAEhB;;;OAGG;IACH,eAAe,CAAC,EAAE,MAAM,CAAC;IAEzB;;;OAGG;IACH,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;AAID;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,CAAC,OAAO,UAAU,UAAU,CAAC,OAAO,GAAE,qBAA0B,GAAG,MAAM,CAiG9E;AAGD,OAAO,EAAE,UAAU,EAAE,CAAC"}
package/dist/index.js CHANGED
@@ -1,279 +1,113 @@
1
1
  /**
2
2
  * @sylphx/silk-vite-plugin
3
- * Universal plugin for zero-runtime Silk CSS-in-TypeScript
3
+ * Zero-codegen Vite plugin using virtual CSS modules
4
4
  *
5
- * Uses unplugin for cross-bundler compatibility
5
+ * Architecture:
6
+ * 1. Scan source files for css() calls
7
+ * 2. Generate CSS via scanAndGenerate()
8
+ * 3. Create virtual 'silk.css' module
9
+ * 4. CSS flows through Vite's CSS pipeline (PostCSS, optimization, bundling)
6
10
  */
7
- import { createUnplugin } from 'unplugin';
8
- import * as path from 'node:path';
9
- import { gzipSync, brotliCompressSync, constants } from 'node:zlib';
10
- import { createHash } from 'node:crypto';
11
- // @ts-ignore - Babel preset types not needed
12
- import presetReact from '@babel/preset-react';
13
- // @ts-ignore - Babel preset types not needed
14
- import presetTypeScript from '@babel/preset-typescript';
15
- // Global CSS registry
16
- const cssRules = new Map();
11
+ import { scanAndGenerate } from '@sylphx/silk/codegen';
12
+ const VIRTUAL_MODULE_PREFIX = '\0virtual:';
17
13
  /**
18
- * Minify CSS
19
- */
20
- function minifyCSS(css) {
21
- return css
22
- .replace(/\s+/g, ' ')
23
- .replace(/\s*([{}:;,])\s*/g, '$1')
24
- .replace(/;}/g, '}')
25
- .trim();
26
- }
27
- /**
28
- * Format bytes
29
- */
30
- function formatBytes(bytes) {
31
- if (bytes < 1024)
32
- return `${bytes}B`;
33
- if (bytes < 1024 * 1024)
34
- return `${(bytes / 1024).toFixed(1)}KB`;
35
- return `${(bytes / 1024 / 1024).toFixed(1)}MB`;
36
- }
37
- /**
38
- * Process CSS with PostCSS (optional)
39
- */
40
- async function processWithPostCss(css, postCssOptions) {
41
- if (!postCssOptions?.enable || !postCssOptions?.plugins?.length) {
42
- return { css };
43
- }
44
- try {
45
- // Dynamic import to avoid forcing PostCSS as a dependency
46
- const postcss = await import('postcss').then(m => m.default || m);
47
- const result = await postcss(postCssOptions.plugins).process(css, {
48
- from: undefined,
49
- map: postCssOptions.sourceMaps ? { inline: false } : false
50
- });
51
- return {
52
- css: result.css,
53
- map: result.map?.toString()
54
- };
55
- }
56
- catch (error) {
57
- if (error?.code === 'ERR_MODULE_NOT_FOUND') {
58
- console.warn('[Silk] PostCSS enabled but not installed. Run: npm install postcss');
59
- return { css };
60
- }
61
- throw error;
62
- }
63
- }
64
- /**
65
- * Silk unplugin instance
14
+ * Silk Vite plugin - Zero-codegen with virtual CSS module
15
+ *
16
+ * @example
17
+ * ```typescript
18
+ * // vite.config.ts
19
+ * import silk from '@sylphx/silk-vite-plugin'
20
+ *
21
+ * export default {
22
+ * plugins: [silk()]
23
+ * }
24
+ * ```
25
+ *
26
+ * ```typescript
27
+ * // app.tsx
28
+ * import 'silk.css' // Virtual module → Vite CSS pipeline
29
+ * ```
66
30
  */
67
- export const unpluginSilk = createUnplugin((options = {}) => {
68
- const { outputFile = 'silk.css', minify: shouldMinify, compression = {}, babelOptions = {}, postCss, } = options;
69
- const compressionConfig = {
70
- brotli: compression.brotli ?? true,
71
- brotliQuality: compression.brotliQuality ?? 11,
72
- gzip: compression.gzip ?? true,
73
- gzipLevel: compression.gzipLevel ?? 9,
74
- };
75
- let isProduction = false;
31
+ export default function silkPlugin(options = {}) {
32
+ const { srcDir = './src', virtualModuleId = 'silk.css', debug = false, ...generateOptions } = options;
33
+ const resolvedVirtualModuleId = VIRTUAL_MODULE_PREFIX + virtualModuleId;
34
+ let generatedCSS = null;
35
+ let isDevMode = true;
76
36
  return {
77
- name: 'unplugin-silk',
78
- enforce: 'pre', // Run before other plugins
79
- // Only transform TypeScript/JSX files
80
- transformInclude(id) {
81
- // Skip node_modules and virtual modules
82
- if (id.includes('node_modules') || id.includes('\0')) {
83
- return false;
37
+ name: 'vite-plugin-silk',
38
+ enforce: 'pre',
39
+ configResolved(config) {
40
+ isDevMode = config.command === 'serve';
41
+ if (debug) {
42
+ console.log(`[Silk] Mode: ${isDevMode ? 'development' : 'production'}`);
43
+ console.log(`[Silk] Scanning: ${srcDir}`);
84
44
  }
85
- return /\.[jt]sx?$/.test(id);
86
45
  },
87
- // Transform code with Babel
88
- async transform(code, id) {
89
- // Skip if no silk imports
90
- if (!code.includes('@sylphx/silk')) {
91
- return null;
92
- }
46
+ async buildStart() {
47
+ // Generate CSS on build start
93
48
  try {
94
- // Dynamic import to avoid bundling
95
- const { transformSync } = await import('@babel/core');
96
- const babelPluginSilk = (await import('@sylphx/babel-plugin-silk')).default;
97
- // Set production mode if not explicitly set
98
- const babelOpts = {
99
- ...babelOptions,
100
- production: babelOptions.production ?? isProduction,
101
- };
102
- // Transform with Babel plugin
103
- const result = transformSync(code, {
104
- filename: id,
105
- presets: [
106
- [presetReact, { runtime: 'automatic' }],
107
- [presetTypeScript, { isTSX: true, allExtensions: true }],
108
- ],
109
- plugins: [[babelPluginSilk, babelOpts]],
110
- sourceMaps: true,
111
- configFile: false,
112
- babelrc: false,
113
- });
114
- if (!result || !result.code) {
115
- return null;
49
+ if (debug) {
50
+ console.log('[Silk] Generating CSS...');
116
51
  }
117
- // Extract CSS from metadata
118
- const metadata = result.metadata;
119
- if (metadata?.silk?.cssRules) {
120
- for (const [className, rule] of metadata.silk.cssRules) {
121
- cssRules.set(className, rule);
122
- }
123
- // Log in dev mode
124
- if (!isProduction && metadata.silk.cssRules.length > 0) {
125
- console.log(`[Silk] Compiled ${metadata.silk.cssRules.length} CSS rules from ${path.basename(id)}`);
126
- }
52
+ generatedCSS = await scanAndGenerate(srcDir, {
53
+ ...generateOptions,
54
+ minify: generateOptions.minify ?? !isDevMode,
55
+ debug
56
+ });
57
+ if (debug) {
58
+ console.log(`[Silk] Generated ${generatedCSS.length} bytes of CSS`);
127
59
  }
128
- return {
129
- code: result.code,
130
- map: result.map || undefined,
131
- };
132
60
  }
133
61
  catch (error) {
134
- console.error(`[Silk] Transform error in ${id}:`, error);
135
- return null;
62
+ console.error('[Silk] CSS generation failed:', error);
63
+ generatedCSS = '/* Silk CSS generation failed */';
136
64
  }
137
65
  },
138
- // Universal hook - works for all bundlers
139
- async generateBundle() {
140
- if (cssRules.size === 0)
141
- return;
142
- // Generate CSS
143
- let css = Array.from(cssRules.values()).join('\n');
144
- if (shouldMinify ?? isProduction) {
145
- css = minifyCSS(css);
66
+ resolveId(id) {
67
+ // Resolve virtual module
68
+ if (id === virtualModuleId) {
69
+ return resolvedVirtualModuleId;
146
70
  }
147
- // Emit main CSS file
148
- this.emitFile({
149
- type: 'asset',
150
- fileName: outputFile,
151
- source: css,
152
- });
153
- // Generate compressed versions
154
- if (isProduction) {
155
- const cssBuffer = Buffer.from(css, 'utf-8');
156
- const originalSize = cssBuffer.length;
157
- // Brotli compression
158
- if (compressionConfig.brotli) {
159
- try {
160
- const compressed = brotliCompressSync(cssBuffer, {
161
- params: {
162
- [constants.BROTLI_PARAM_QUALITY]: compressionConfig.brotliQuality,
163
- },
164
- });
165
- this.emitFile({
166
- type: 'asset',
167
- fileName: `${outputFile}.br`,
168
- source: compressed,
169
- });
170
- const savings = ((1 - compressed.length / originalSize) * 100).toFixed(1);
171
- console.log(`[Silk] Brotli: ${formatBytes(compressed.length)} (-${savings}%)`);
172
- }
173
- catch (error) {
174
- console.warn('[Silk] Brotli compression failed:', error);
175
- }
176
- }
177
- // Gzip compression
178
- if (compressionConfig.gzip) {
179
- try {
180
- const compressed = gzipSync(cssBuffer, {
181
- level: compressionConfig.gzipLevel,
182
- });
183
- this.emitFile({
184
- type: 'asset',
185
- fileName: `${outputFile}.gz`,
186
- source: compressed,
187
- });
188
- const savings = ((1 - compressed.length / originalSize) * 100).toFixed(1);
189
- console.log(`[Silk] Gzip: ${formatBytes(compressed.length)} (-${savings}%)`);
190
- }
191
- catch (error) {
192
- console.warn('[Silk] Gzip compression failed:', error);
193
- }
71
+ return null;
72
+ },
73
+ load(id) {
74
+ // Load virtual module
75
+ if (id === resolvedVirtualModuleId) {
76
+ if (!generatedCSS) {
77
+ return '/* No CSS generated */';
194
78
  }
195
- // Summary
196
- console.log(`\nšŸ“¦ Silk CSS Bundle:`);
197
- console.log(` Original: ${formatBytes(originalSize)} (${outputFile})`);
198
- console.log(` Rules: ${cssRules.size} atomic classes\n`);
79
+ return generatedCSS;
199
80
  }
81
+ return null;
200
82
  },
201
- // Vite-specific hooks
202
- vite: {
203
- configResolved(config) {
204
- isProduction = config.command === 'build' && config.mode === 'production';
205
- },
206
- },
207
- // Webpack-specific hooks
208
- webpack(compiler) {
209
- compiler.hooks.emit.tapPromise('SilkPlugin', async (compilation) => {
210
- if (cssRules.size === 0)
211
- return;
212
- let css = Array.from(cssRules.values()).join('\n');
213
- let sourceMap;
214
- // Minify CSS
215
- if (shouldMinify ?? true) {
216
- css = minifyCSS(css);
217
- }
218
- // PostCSS processing (optional)
219
- if (postCss?.enable) {
220
- const result = await processWithPostCss(css, postCss);
221
- css = result.css;
222
- sourceMap = result.map;
223
- }
224
- // Generate content hash for cache busting
225
- const hash = createHash('md5').update(css).digest('hex').slice(0, 8);
226
- const baseName = outputFile.replace('.css', '');
227
- const hashedFileName = `${baseName}.${hash}.css`;
228
- // Emit to both locations for Next.js compatibility
229
- compilation.assets[outputFile] = {
230
- source: () => css,
231
- size: () => css.length,
232
- };
233
- // Emit with content hash to static/css directory (production)
234
- const staticCssPath = `static/css/${hashedFileName}`;
235
- compilation.assets[staticCssPath] = {
236
- source: () => css,
237
- size: () => css.length,
238
- };
239
- // Also emit non-hashed version for backwards compatibility
240
- const staticCssPathLegacy = `static/css/${outputFile}`;
241
- compilation.assets[staticCssPathLegacy] = {
242
- source: () => css,
243
- size: () => css.length,
244
- };
245
- // Emit source map if PostCSS generated one
246
- if (sourceMap) {
247
- compilation.assets[`${staticCssPath}.map`] = {
248
- source: () => sourceMap,
249
- size: () => sourceMap.length,
250
- };
251
- compilation.assets[`${outputFile}.map`] = {
252
- source: () => sourceMap,
253
- size: () => sourceMap.length,
254
- };
83
+ async handleHotUpdate({ file, server }) {
84
+ // Watch mode: regenerate CSS on file changes
85
+ if (!isDevMode)
86
+ return;
87
+ // Check if changed file is in srcDir and matches our patterns
88
+ if (file.includes(srcDir) && /\.[jt]sx?$/.test(file)) {
89
+ if (debug) {
90
+ console.log(`[Silk] File changed: ${file}, regenerating CSS...`);
255
91
  }
256
- console.log(`[Silk] Emitted CSS:`);
257
- console.log(` • ${outputFile} (${css.length} bytes)`);
258
- console.log(` • ${staticCssPath} (content-hashed, cacheable)`);
259
- console.log(` • ${staticCssPathLegacy} (legacy, no hash)`);
260
- if (postCss?.enable) {
261
- console.log(` • PostCSS: ${postCss.plugins?.length || 0} plugins applied`);
92
+ try {
93
+ generatedCSS = await scanAndGenerate(srcDir, {
94
+ ...generateOptions,
95
+ minify: false,
96
+ debug
97
+ });
98
+ // Trigger HMR for the virtual module
99
+ const module = server.moduleGraph.getModuleById(resolvedVirtualModuleId);
100
+ if (module) {
101
+ server.moduleGraph.invalidateModule(module);
102
+ return [module];
103
+ }
262
104
  }
263
- if (sourceMap) {
264
- console.log(` • Source map generated`);
105
+ catch (error) {
106
+ console.error('[Silk] CSS regeneration failed:', error);
265
107
  }
266
- // Store the hashed filename for potential use in HTML injection
267
- ;
268
- compilation.__silkCssFileName = hashedFileName;
269
- });
270
- },
108
+ }
109
+ }
271
110
  };
272
- });
273
- // Export for different bundlers
274
- export const vite = unpluginSilk.vite;
275
- export const webpack = unpluginSilk.webpack;
276
- export const rollup = unpluginSilk.rollup;
277
- export const esbuild = unpluginSilk.esbuild;
278
- // Default export for Vite
279
- export default vite;
111
+ }
112
+ // Named exports for clarity
113
+ export { silkPlugin };
@@ -0,0 +1,109 @@
1
+ /**
2
+ * @sylphx/silk-vite-plugin
3
+ * Universal plugin for zero-runtime Silk CSS-in-TypeScript
4
+ *
5
+ * Uses unplugin for cross-bundler compatibility
6
+ */
7
+ export interface CompressionOptions {
8
+ /**
9
+ * Enable Brotli compression (.css.br)
10
+ * @default true
11
+ */
12
+ brotli?: boolean;
13
+ /**
14
+ * Brotli quality (0-11)
15
+ * @default 11
16
+ */
17
+ brotliQuality?: number;
18
+ /**
19
+ * Enable gzip compression (.css.gz)
20
+ * @default true
21
+ */
22
+ gzip?: boolean;
23
+ /**
24
+ * Gzip level (0-9)
25
+ * @default 9
26
+ */
27
+ gzipLevel?: number;
28
+ }
29
+ export interface PostCssOptions {
30
+ /**
31
+ * Enable PostCSS processing
32
+ * @default false
33
+ */
34
+ enable?: boolean;
35
+ /**
36
+ * PostCSS plugins (e.g., autoprefixer, cssnano)
37
+ * @example
38
+ * ```typescript
39
+ * import autoprefixer from 'autoprefixer'
40
+ *
41
+ * postCss: {
42
+ * enable: true,
43
+ * plugins: [
44
+ * autoprefixer({ overrideBrowserslist: ['> 1%', 'last 2 versions'] })
45
+ * ]
46
+ * }
47
+ * ```
48
+ */
49
+ plugins?: any[];
50
+ /**
51
+ * Generate source maps
52
+ * @default false
53
+ */
54
+ sourceMaps?: boolean;
55
+ }
56
+ export interface SilkPluginOptions {
57
+ /**
58
+ * Output CSS file path
59
+ * @default 'silk.css'
60
+ */
61
+ outputFile?: string;
62
+ /**
63
+ * Minify CSS output
64
+ * @default true in production
65
+ */
66
+ minify?: boolean;
67
+ /**
68
+ * Pre-compression options
69
+ */
70
+ compression?: CompressionOptions;
71
+ /**
72
+ * PostCSS processing (optional)
73
+ * Add autoprefixer for legacy browser support or custom PostCSS plugins
74
+ *
75
+ * @example
76
+ * ```typescript
77
+ * import autoprefixer from 'autoprefixer'
78
+ *
79
+ * {
80
+ * postCss: {
81
+ * enable: true,
82
+ * plugins: [autoprefixer()],
83
+ * sourceMaps: true
84
+ * }
85
+ * }
86
+ * ```
87
+ */
88
+ postCss?: PostCssOptions;
89
+ /**
90
+ * Babel plugin options
91
+ */
92
+ babelOptions?: {
93
+ production?: boolean;
94
+ classPrefix?: string;
95
+ importSources?: string[];
96
+ tokens?: Record<string, any>;
97
+ breakpoints?: Record<string, string>;
98
+ };
99
+ }
100
+ /**
101
+ * Silk unplugin instance
102
+ */
103
+ export declare const unpluginSilk: import("unplugin").UnpluginInstance<SilkPluginOptions, boolean>;
104
+ export declare const vite: (options: SilkPluginOptions) => import("vite").Plugin<any> | import("vite").Plugin<any>[];
105
+ export declare const webpack: (options: SilkPluginOptions) => import("unplugin").WebpackPluginInstance;
106
+ export declare const rollup: (options: SilkPluginOptions) => import("rollup").Plugin<any> | import("rollup").Plugin<any>[];
107
+ export declare const esbuild: (options: SilkPluginOptions) => import("unplugin").EsbuildPlugin;
108
+ export default vite;
109
+ //# sourceMappingURL=index.old.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.old.d.ts","sourceRoot":"","sources":["../src/index.old.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAYH,MAAM,WAAW,kBAAkB;IACjC;;;OAGG;IACH,MAAM,CAAC,EAAE,OAAO,CAAA;IAEhB;;;OAGG;IACH,aAAa,CAAC,EAAE,MAAM,CAAA;IAEtB;;;OAGG;IACH,IAAI,CAAC,EAAE,OAAO,CAAA;IAEd;;;OAGG;IACH,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB;AAED,MAAM,WAAW,cAAc;IAC7B;;;OAGG;IACH,MAAM,CAAC,EAAE,OAAO,CAAA;IAEhB;;;;;;;;;;;;;OAaG;IACH,OAAO,CAAC,EAAE,GAAG,EAAE,CAAA;IAEf;;;OAGG;IACH,UAAU,CAAC,EAAE,OAAO,CAAA;CACrB;AAED,MAAM,WAAW,iBAAiB;IAChC;;;OAGG;IACH,UAAU,CAAC,EAAE,MAAM,CAAA;IAEnB;;;OAGG;IACH,MAAM,CAAC,EAAE,OAAO,CAAA;IAEhB;;OAEG;IACH,WAAW,CAAC,EAAE,kBAAkB,CAAA;IAEhC;;;;;;;;;;;;;;;;OAgBG;IACH,OAAO,CAAC,EAAE,cAAc,CAAA;IAExB;;OAEG;IACH,YAAY,CAAC,EAAE;QACb,UAAU,CAAC,EAAE,OAAO,CAAA;QACpB,WAAW,CAAC,EAAE,MAAM,CAAA;QACpB,aAAa,CAAC,EAAE,MAAM,EAAE,CAAA;QACxB,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAA;QAC5B,WAAW,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;KACrC,CAAA;CACF;AA0DD;;GAEG;AACH,eAAO,MAAM,YAAY,iEAkPvB,CAAA;AAGF,eAAO,MAAM,IAAI,2FAAoB,CAAA;AACrC,eAAO,MAAM,OAAO,0EAAuB,CAAA;AAC3C,eAAO,MAAM,MAAM,+FAAsB,CAAA;AACzC,eAAO,MAAM,OAAO,kEAAuB,CAAA;AAG3C,eAAe,IAAI,CAAA"}
@@ -0,0 +1,279 @@
1
+ /**
2
+ * @sylphx/silk-vite-plugin
3
+ * Universal plugin for zero-runtime Silk CSS-in-TypeScript
4
+ *
5
+ * Uses unplugin for cross-bundler compatibility
6
+ */
7
+ import { createUnplugin } from 'unplugin';
8
+ import * as path from 'node:path';
9
+ import { gzipSync, brotliCompressSync, constants } from 'node:zlib';
10
+ import { createHash } from 'node:crypto';
11
+ // @ts-ignore - Babel preset types not needed
12
+ import presetReact from '@babel/preset-react';
13
+ // @ts-ignore - Babel preset types not needed
14
+ import presetTypeScript from '@babel/preset-typescript';
15
+ // Global CSS registry
16
+ const cssRules = new Map();
17
+ /**
18
+ * Minify CSS
19
+ */
20
+ function minifyCSS(css) {
21
+ return css
22
+ .replace(/\s+/g, ' ')
23
+ .replace(/\s*([{}:;,])\s*/g, '$1')
24
+ .replace(/;}/g, '}')
25
+ .trim();
26
+ }
27
+ /**
28
+ * Format bytes
29
+ */
30
+ function formatBytes(bytes) {
31
+ if (bytes < 1024)
32
+ return `${bytes}B`;
33
+ if (bytes < 1024 * 1024)
34
+ return `${(bytes / 1024).toFixed(1)}KB`;
35
+ return `${(bytes / 1024 / 1024).toFixed(1)}MB`;
36
+ }
37
+ /**
38
+ * Process CSS with PostCSS (optional)
39
+ */
40
+ async function processWithPostCss(css, postCssOptions) {
41
+ if (!postCssOptions?.enable || !postCssOptions?.plugins?.length) {
42
+ return { css };
43
+ }
44
+ try {
45
+ // Dynamic import to avoid forcing PostCSS as a dependency
46
+ const postcss = await import('postcss').then(m => m.default || m);
47
+ const result = await postcss(postCssOptions.plugins).process(css, {
48
+ from: undefined,
49
+ map: postCssOptions.sourceMaps ? { inline: false } : false
50
+ });
51
+ return {
52
+ css: result.css,
53
+ map: result.map?.toString()
54
+ };
55
+ }
56
+ catch (error) {
57
+ if (error?.code === 'ERR_MODULE_NOT_FOUND') {
58
+ console.warn('[Silk] PostCSS enabled but not installed. Run: npm install postcss');
59
+ return { css };
60
+ }
61
+ throw error;
62
+ }
63
+ }
64
+ /**
65
+ * Silk unplugin instance
66
+ */
67
+ export const unpluginSilk = createUnplugin((options = {}) => {
68
+ const { outputFile = 'silk.css', minify: shouldMinify, compression = {}, babelOptions = {}, postCss, } = options;
69
+ const compressionConfig = {
70
+ brotli: compression.brotli ?? true,
71
+ brotliQuality: compression.brotliQuality ?? 11,
72
+ gzip: compression.gzip ?? true,
73
+ gzipLevel: compression.gzipLevel ?? 9,
74
+ };
75
+ let isProduction = false;
76
+ return {
77
+ name: 'unplugin-silk',
78
+ enforce: 'pre', // Run before other plugins
79
+ // Only transform TypeScript/JSX files
80
+ transformInclude(id) {
81
+ // Skip node_modules and virtual modules
82
+ if (id.includes('node_modules') || id.includes('\0')) {
83
+ return false;
84
+ }
85
+ return /\.[jt]sx?$/.test(id);
86
+ },
87
+ // Transform code with Babel
88
+ async transform(code, id) {
89
+ // Skip if no silk imports
90
+ if (!code.includes('@sylphx/silk')) {
91
+ return null;
92
+ }
93
+ try {
94
+ // Dynamic import to avoid bundling
95
+ const { transformSync } = await import('@babel/core');
96
+ const babelPluginSilk = (await import('@sylphx/babel-plugin-silk')).default;
97
+ // Set production mode if not explicitly set
98
+ const babelOpts = {
99
+ ...babelOptions,
100
+ production: babelOptions.production ?? isProduction,
101
+ };
102
+ // Transform with Babel plugin
103
+ const result = transformSync(code, {
104
+ filename: id,
105
+ presets: [
106
+ [presetReact, { runtime: 'automatic' }],
107
+ [presetTypeScript, { isTSX: true, allExtensions: true }],
108
+ ],
109
+ plugins: [[babelPluginSilk, babelOpts]],
110
+ sourceMaps: true,
111
+ configFile: false,
112
+ babelrc: false,
113
+ });
114
+ if (!result || !result.code) {
115
+ return null;
116
+ }
117
+ // Extract CSS from metadata
118
+ const metadata = result.metadata;
119
+ if (metadata?.silk?.cssRules) {
120
+ for (const [className, rule] of metadata.silk.cssRules) {
121
+ cssRules.set(className, rule);
122
+ }
123
+ // Log in dev mode
124
+ if (!isProduction && metadata.silk.cssRules.length > 0) {
125
+ console.log(`[Silk] Compiled ${metadata.silk.cssRules.length} CSS rules from ${path.basename(id)}`);
126
+ }
127
+ }
128
+ return {
129
+ code: result.code,
130
+ map: result.map || undefined,
131
+ };
132
+ }
133
+ catch (error) {
134
+ console.error(`[Silk] Transform error in ${id}:`, error);
135
+ return null;
136
+ }
137
+ },
138
+ // Universal hook - works for all bundlers
139
+ async generateBundle() {
140
+ if (cssRules.size === 0)
141
+ return;
142
+ // Generate CSS
143
+ let css = Array.from(cssRules.values()).join('\n');
144
+ if (shouldMinify ?? isProduction) {
145
+ css = minifyCSS(css);
146
+ }
147
+ // Emit main CSS file
148
+ this.emitFile({
149
+ type: 'asset',
150
+ fileName: outputFile,
151
+ source: css,
152
+ });
153
+ // Generate compressed versions
154
+ if (isProduction) {
155
+ const cssBuffer = Buffer.from(css, 'utf-8');
156
+ const originalSize = cssBuffer.length;
157
+ // Brotli compression
158
+ if (compressionConfig.brotli) {
159
+ try {
160
+ const compressed = brotliCompressSync(cssBuffer, {
161
+ params: {
162
+ [constants.BROTLI_PARAM_QUALITY]: compressionConfig.brotliQuality,
163
+ },
164
+ });
165
+ this.emitFile({
166
+ type: 'asset',
167
+ fileName: `${outputFile}.br`,
168
+ source: compressed,
169
+ });
170
+ const savings = ((1 - compressed.length / originalSize) * 100).toFixed(1);
171
+ console.log(`[Silk] Brotli: ${formatBytes(compressed.length)} (-${savings}%)`);
172
+ }
173
+ catch (error) {
174
+ console.warn('[Silk] Brotli compression failed:', error);
175
+ }
176
+ }
177
+ // Gzip compression
178
+ if (compressionConfig.gzip) {
179
+ try {
180
+ const compressed = gzipSync(cssBuffer, {
181
+ level: compressionConfig.gzipLevel,
182
+ });
183
+ this.emitFile({
184
+ type: 'asset',
185
+ fileName: `${outputFile}.gz`,
186
+ source: compressed,
187
+ });
188
+ const savings = ((1 - compressed.length / originalSize) * 100).toFixed(1);
189
+ console.log(`[Silk] Gzip: ${formatBytes(compressed.length)} (-${savings}%)`);
190
+ }
191
+ catch (error) {
192
+ console.warn('[Silk] Gzip compression failed:', error);
193
+ }
194
+ }
195
+ // Summary
196
+ console.log(`\nšŸ“¦ Silk CSS Bundle:`);
197
+ console.log(` Original: ${formatBytes(originalSize)} (${outputFile})`);
198
+ console.log(` Rules: ${cssRules.size} atomic classes\n`);
199
+ }
200
+ },
201
+ // Vite-specific hooks
202
+ vite: {
203
+ configResolved(config) {
204
+ isProduction = config.command === 'build' && config.mode === 'production';
205
+ },
206
+ },
207
+ // Webpack-specific hooks
208
+ webpack(compiler) {
209
+ compiler.hooks.emit.tapPromise('SilkPlugin', async (compilation) => {
210
+ if (cssRules.size === 0)
211
+ return;
212
+ let css = Array.from(cssRules.values()).join('\n');
213
+ let sourceMap;
214
+ // Minify CSS
215
+ if (shouldMinify ?? true) {
216
+ css = minifyCSS(css);
217
+ }
218
+ // PostCSS processing (optional)
219
+ if (postCss?.enable) {
220
+ const result = await processWithPostCss(css, postCss);
221
+ css = result.css;
222
+ sourceMap = result.map;
223
+ }
224
+ // Generate content hash for cache busting
225
+ const hash = createHash('md5').update(css).digest('hex').slice(0, 8);
226
+ const baseName = outputFile.replace('.css', '');
227
+ const hashedFileName = `${baseName}.${hash}.css`;
228
+ // Emit to both locations for Next.js compatibility
229
+ compilation.assets[outputFile] = {
230
+ source: () => css,
231
+ size: () => css.length,
232
+ };
233
+ // Emit with content hash to static/css directory (production)
234
+ const staticCssPath = `static/css/${hashedFileName}`;
235
+ compilation.assets[staticCssPath] = {
236
+ source: () => css,
237
+ size: () => css.length,
238
+ };
239
+ // Also emit non-hashed version for backwards compatibility
240
+ const staticCssPathLegacy = `static/css/${outputFile}`;
241
+ compilation.assets[staticCssPathLegacy] = {
242
+ source: () => css,
243
+ size: () => css.length,
244
+ };
245
+ // Emit source map if PostCSS generated one
246
+ if (sourceMap) {
247
+ compilation.assets[`${staticCssPath}.map`] = {
248
+ source: () => sourceMap,
249
+ size: () => sourceMap.length,
250
+ };
251
+ compilation.assets[`${outputFile}.map`] = {
252
+ source: () => sourceMap,
253
+ size: () => sourceMap.length,
254
+ };
255
+ }
256
+ console.log(`[Silk] Emitted CSS:`);
257
+ console.log(` • ${outputFile} (${css.length} bytes)`);
258
+ console.log(` • ${staticCssPath} (content-hashed, cacheable)`);
259
+ console.log(` • ${staticCssPathLegacy} (legacy, no hash)`);
260
+ if (postCss?.enable) {
261
+ console.log(` • PostCSS: ${postCss.plugins?.length || 0} plugins applied`);
262
+ }
263
+ if (sourceMap) {
264
+ console.log(` • Source map generated`);
265
+ }
266
+ // Store the hashed filename for potential use in HTML injection
267
+ ;
268
+ compilation.__silkCssFileName = hashedFileName;
269
+ });
270
+ },
271
+ };
272
+ });
273
+ // Export for different bundlers
274
+ export const vite = unpluginSilk.vite;
275
+ export const webpack = unpluginSilk.webpack;
276
+ export const rollup = unpluginSilk.rollup;
277
+ export const esbuild = unpluginSilk.esbuild;
278
+ // Default export for Vite
279
+ export default vite;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sylphx/silk-vite-plugin",
3
- "version": "2.1.0",
3
+ "version": "2.2.1",
4
4
  "description": "Vite plugin for Silk - Build-time CSS extraction with production optimizations",
5
5
  "keywords": [
6
6
  "silk",
@@ -40,32 +40,14 @@
40
40
  "prepublishOnly": "bun run build"
41
41
  },
42
42
  "dependencies": {
43
- "@babel/core": "^7.23.0",
44
- "@babel/preset-react": "^7.23.0",
45
- "@babel/preset-typescript": "^7.23.0",
46
- "@sylphx/babel-plugin-silk": "^2.0.1",
47
- "@sylphx/silk": "^2.0.0",
48
- "unplugin": "^2.3.10"
43
+ "@sylphx/silk": "^2.1.0"
49
44
  },
50
45
  "devDependencies": {
51
- "@types/babel__core": "^7.20.5",
52
- "autoprefixer": "^10.4.21",
53
- "postcss": "^8.5.6",
54
46
  "typescript": "^5.3.0",
55
47
  "vite": "^5.0.0"
56
48
  },
57
49
  "peerDependencies": {
58
- "vite": "^4.0.0 || ^5.0.0",
59
- "postcss": "^8.0.0",
60
- "autoprefixer": "^10.0.0"
61
- },
62
- "peerDependenciesMeta": {
63
- "postcss": {
64
- "optional": true
65
- },
66
- "autoprefixer": {
67
- "optional": true
68
- }
50
+ "vite": "^4.0.0 || ^5.0.0 || ^6.0.0"
69
51
  },
70
52
  "publishConfig": {
71
53
  "access": "public"