chaincss 2.0.7 → 2.1.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/CHANGELOG.md +30 -0
- package/CODE_OF_CONDUCT.md +21 -0
- package/CONTRIBUTING.md +28 -0
- package/README.md +455 -226
- package/demo/demo/node_modules/caniuse-db/fulldata-json/data-2.0.json +1 -0
- package/demo/index.html +16 -0
- package/demo/package.json +20 -0
- package/demo/src/App.tsx +117 -0
- package/demo/src/chaincss-barrel.ts +9 -0
- package/demo/src/main.tsx +8 -0
- package/demo/src/styles.chain.ts +300 -0
- package/demo/vite.config.ts +46 -0
- package/dist/cli/commands/build.d.ts +0 -1
- package/dist/cli/commands/cache.d.ts +1 -0
- package/dist/cli/commands/init.d.ts +6 -3
- package/dist/cli/commands/timeline.d.ts +0 -1
- package/dist/cli/commands/watch.d.ts +0 -1
- package/dist/cli/index.d.ts +0 -1
- package/dist/cli/index.js +3213 -5296
- package/dist/cli/types.d.ts +51 -20
- package/dist/cli/utils/config-loader.d.ts +0 -1
- package/dist/cli/utils/file-utils.d.ts +27 -3
- package/dist/cli/utils/logger.d.ts +0 -1
- package/dist/compiler/Chain.d.ts +215 -0
- package/dist/compiler/animations.d.ts +76 -0
- package/dist/compiler/atomic-optimizer.d.ts +47 -12
- package/dist/compiler/breakpoints.d.ts +46 -0
- package/dist/compiler/btt.d.ts +36 -60
- package/dist/compiler/cache-manager.d.ts +58 -4
- package/dist/compiler/commonProps.d.ts +0 -1
- package/dist/compiler/content-addressable-cache.d.ts +78 -0
- package/dist/compiler/helpers.d.ts +54 -0
- package/dist/compiler/index.d.ts +16 -9
- package/dist/compiler/index.js +4450 -4316
- package/dist/compiler/prefixer.d.ts +17 -1
- package/dist/compiler/shorthands.d.ts +28 -0
- package/dist/compiler/suggestions.d.ts +43 -0
- package/dist/compiler/theme-contract.d.ts +16 -27
- package/dist/compiler/token-resolver.d.ts +69 -0
- package/dist/compiler/tokens.d.ts +33 -8
- package/dist/core/auto-detector.d.ts +34 -0
- package/dist/core/common-utils.d.ts +97 -0
- package/dist/core/compiler.d.ts +63 -23
- package/dist/core/constants.d.ts +137 -36
- package/dist/core/smart-chain.d.ts +3 -0
- package/dist/core/types.d.ts +122 -15
- package/dist/core/utils.d.ts +134 -17
- package/dist/index.d.ts +52 -8
- package/dist/index.js +7090 -5578
- package/dist/plugins/vite.d.ts +7 -5
- package/dist/plugins/vite.js +2964 -25641
- package/dist/plugins/webpack.d.ts +24 -1
- package/dist/plugins/webpack.js +209 -72
- package/dist/runtime/Chain.d.ts +32 -0
- package/dist/runtime/auto-hooks.d.ts +11 -0
- package/dist/runtime/hmr.d.ts +22 -2
- package/dist/runtime/index.d.ts +3 -2
- package/dist/runtime/index.js +3648 -301
- package/dist/runtime/injector.d.ts +39 -72
- package/dist/runtime/react.d.ts +17 -12
- package/dist/runtime/svelte.d.ts +15 -0
- package/dist/runtime/types.d.ts +126 -4
- package/dist/runtime/utils.d.ts +0 -1
- package/dist/runtime/vue.d.ts +34 -14
- package/package.json +59 -66
- package/src/cli/commands/build.ts +133 -0
- package/src/cli/commands/cache.ts +371 -0
- package/src/cli/commands/init.ts +230 -0
- package/src/cli/commands/timeline.ts +435 -0
- package/src/cli/commands/watch.ts +211 -0
- package/src/cli/index.ts +226 -0
- package/src/cli/types.ts +100 -0
- package/src/cli/utils/config-loader.ts +174 -0
- package/src/cli/utils/file-utils.ts +139 -0
- package/src/cli/utils/logger.ts +74 -0
- package/src/compiler/Chain.ts +831 -0
- package/src/compiler/animations.ts +517 -0
- package/src/compiler/atomic-optimizer.ts +786 -0
- package/src/compiler/breakpoints.ts +347 -0
- package/src/compiler/btt.ts +1147 -0
- package/src/compiler/cache-manager.ts +446 -0
- package/src/compiler/commonProps.ts +18 -0
- package/src/compiler/content-addressable-cache.ts +478 -0
- package/src/compiler/helpers.ts +407 -0
- package/src/compiler/index.ts +72 -0
- package/src/compiler/prefixer.ts +720 -0
- package/src/compiler/shorthands.ts +558 -0
- package/src/compiler/suggestions.ts +436 -0
- package/src/compiler/theme-contract.ts +197 -0
- package/src/compiler/token-resolver.ts +241 -0
- package/src/compiler/tokens.ts +612 -0
- package/src/core/auto-detector.ts +187 -0
- package/src/core/common-utils.ts +423 -0
- package/src/core/compiler.ts +835 -0
- package/src/core/constants.ts +424 -0
- package/src/core/index.ts +107 -0
- package/src/core/smart-chain.ts +163 -0
- package/src/core/types.ts +257 -0
- package/src/core/utils.ts +598 -0
- package/src/index.ts +208 -0
- package/src/plugins/vite.d.ts +316 -0
- package/src/plugins/vite.ts +424 -0
- package/src/plugins/webpack.d.ts +289 -0
- package/src/plugins/webpack.ts +416 -0
- package/src/runtime/Chain.ts +242 -0
- package/src/runtime/auto-hooks.tsx +127 -0
- package/src/runtime/auto-vue.ts +72 -0
- package/src/runtime/hmr.ts +212 -0
- package/src/runtime/index.ts +82 -0
- package/src/runtime/injector.ts +273 -0
- package/src/runtime/react.tsx +269 -0
- package/src/runtime/svelte.ts +15 -0
- package/src/runtime/types.ts +256 -0
- package/src/runtime/utils.ts +128 -0
- package/src/runtime/vite-env.d.ts +120 -0
- package/src/runtime/vue.ts +231 -0
- package/tsconfig.build.json +41 -0
- package/tsconfig.json +25 -0
- package/tsconfig.runtimes.json +18 -0
- package/dist/cli/cli.cjs +0 -7
- package/dist/cli/commands/build.d.ts.map +0 -1
- package/dist/cli/commands/compile.d.ts +0 -3
- package/dist/cli/commands/compile.d.ts.map +0 -1
- package/dist/cli/commands/init.d.ts.map +0 -1
- package/dist/cli/commands/timeline.d.ts.map +0 -1
- package/dist/cli/commands/watch.d.ts.map +0 -1
- package/dist/cli/index.d.ts.map +0 -1
- package/dist/cli/types.d.ts.map +0 -1
- package/dist/cli/utils/config-loader.d.ts.map +0 -1
- package/dist/cli/utils/file-utils.d.ts.map +0 -1
- package/dist/cli/utils/logger.d.ts.map +0 -1
- package/dist/compiler/atomic-optimizer.d.ts.map +0 -1
- package/dist/compiler/btt.d.ts.map +0 -1
- package/dist/compiler/cache-manager.d.ts.map +0 -1
- package/dist/compiler/commonProps.d.ts.map +0 -1
- package/dist/compiler/index.d.ts.map +0 -1
- package/dist/compiler/prefixer.d.ts.map +0 -1
- package/dist/compiler/theme-contract.d.ts.map +0 -1
- package/dist/compiler/tokens.d.ts.map +0 -1
- package/dist/compiler/types.d.ts +0 -57
- package/dist/compiler/types.d.ts.map +0 -1
- package/dist/core/compiler.d.ts.map +0 -1
- package/dist/core/constants.d.ts.map +0 -1
- package/dist/core/index.d.ts +0 -4
- package/dist/core/index.d.ts.map +0 -1
- package/dist/core/types.d.ts.map +0 -1
- package/dist/core/utils.d.ts.map +0 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/plugins/vite.d.ts.map +0 -1
- package/dist/plugins/webpack.d.ts.map +0 -1
- package/dist/runtime/hmr.d.ts.map +0 -1
- package/dist/runtime/index.d.ts.map +0 -1
- package/dist/runtime/injector.d.ts.map +0 -1
- package/dist/runtime/react.d.ts.map +0 -1
- package/dist/runtime/react.js +0 -324
- package/dist/runtime/types.d.ts.map +0 -1
- package/dist/runtime/utils.d.ts.map +0 -1
- package/dist/runtime/vue.d.ts.map +0 -1
- package/dist/runtime/vue.js +0 -286
|
@@ -0,0 +1,416 @@
|
|
|
1
|
+
// chaincss/src/plugins/webpack.ts
|
|
2
|
+
|
|
3
|
+
import path from 'path';
|
|
4
|
+
import { execSync, spawn } from 'child_process';
|
|
5
|
+
import fs from 'fs';
|
|
6
|
+
import { fileURLToPath } from 'url';
|
|
7
|
+
import crypto from 'crypto';
|
|
8
|
+
|
|
9
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
10
|
+
const __dirname = path.dirname(__filename);
|
|
11
|
+
|
|
12
|
+
export interface ChainCSSLoaderOptions {
|
|
13
|
+
/**
|
|
14
|
+
* Mode: 'build' for production (zero-runtime), 'runtime' for development
|
|
15
|
+
* @default process.env.NODE_ENV === 'production' ? 'build' : 'runtime'
|
|
16
|
+
*/
|
|
17
|
+
mode?: 'build' | 'runtime';
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Enable atomic CSS optimization
|
|
21
|
+
* @default false
|
|
22
|
+
*/
|
|
23
|
+
atomic?: boolean;
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Enable CSS minification
|
|
27
|
+
* @default process.env.NODE_ENV === 'production'
|
|
28
|
+
*/
|
|
29
|
+
minify?: boolean;
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Enable source maps
|
|
33
|
+
* @default false
|
|
34
|
+
*/
|
|
35
|
+
sourceMap?: boolean;
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Output directory for compiled CSS
|
|
39
|
+
* @default '.chaincss-cache'
|
|
40
|
+
*/
|
|
41
|
+
outputDir?: string;
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Verbose logging
|
|
45
|
+
* @default false
|
|
46
|
+
*/
|
|
47
|
+
verbose?: boolean;
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Enable CSS extraction to separate file
|
|
51
|
+
* @default false
|
|
52
|
+
*/
|
|
53
|
+
extractCSS?: boolean;
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Enable Hot Module Replacement
|
|
57
|
+
* @default true in development
|
|
58
|
+
*/
|
|
59
|
+
hmr?: boolean;
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Custom cache key for compilation
|
|
63
|
+
*/
|
|
64
|
+
cacheKey?: string;
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Framework to generate components for
|
|
68
|
+
* @default 'auto'
|
|
69
|
+
*/
|
|
70
|
+
framework?: 'react' | 'vue' | 'svelte' | 'solid' | 'auto';
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
interface LoaderContext {
|
|
74
|
+
async: () => (err: Error | null, code?: string) => void;
|
|
75
|
+
getOptions: () => ChainCSSLoaderOptions;
|
|
76
|
+
resourcePath: string;
|
|
77
|
+
context: string;
|
|
78
|
+
cacheable: (flag: boolean) => void;
|
|
79
|
+
addDependency: (file: string) => void;
|
|
80
|
+
emitFile: (name: string, content: string, sourceMap?: any) => void;
|
|
81
|
+
emitWarning: (warning: Error) => void;
|
|
82
|
+
emitError: (error: Error) => void;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
interface StyleExport {
|
|
86
|
+
name: string;
|
|
87
|
+
className: string;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Generate a unique hash for cache busting
|
|
92
|
+
*/
|
|
93
|
+
function generateHash(content: string): string {
|
|
94
|
+
return crypto.createHash('md5').update(content).digest('hex').slice(0, 8);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Extract style variable names and their values from source code
|
|
99
|
+
*/
|
|
100
|
+
function extractStyleExports(source: string): StyleExport[] {
|
|
101
|
+
const exports: StyleExport[] = [];
|
|
102
|
+
|
|
103
|
+
// Match const declarations that use chain() or $()
|
|
104
|
+
const constRegex = /const\s+(\w+)\s*=\s*(?:chain|\$)\(\s*\)\s*(?:\.\w+\([^)]*\)\s*)*\.\$el\(['"`]([^'"`]+)['"`]\)/g;
|
|
105
|
+
let match;
|
|
106
|
+
|
|
107
|
+
while ((match = constRegex.exec(source)) !== null) {
|
|
108
|
+
exports.push({
|
|
109
|
+
name: match[1],
|
|
110
|
+
className: match[2]
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// Also match export declarations
|
|
115
|
+
const exportRegex = /export\s+const\s+(\w+)\s*=\s*(?:chain|\$)\(\s*\)\s*(?:\.\w+\([^)]*\)\s*)*\.\$el\(['"`]([^'"`]+)['"`]\)/g;
|
|
116
|
+
while ((match = exportRegex.exec(source)) !== null) {
|
|
117
|
+
exports.push({
|
|
118
|
+
name: match[1],
|
|
119
|
+
className: match[2]
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
return exports;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Generate runtime code for development mode
|
|
128
|
+
*/
|
|
129
|
+
function generateRuntimeCode(source: string, styleExports: StyleExport[], resourcePath: string, options: ChainCSSLoaderOptions): string {
|
|
130
|
+
const styleNames = styleExports.map(e => e.name).join(', ');
|
|
131
|
+
const hmrEnabled = options.hmr !== false && process.env.NODE_ENV !== 'production';
|
|
132
|
+
|
|
133
|
+
// Generate class mapping
|
|
134
|
+
const classMapping = styleExports.map(exp => ` ${exp.name}: '${exp.className}'`).join(',\n');
|
|
135
|
+
|
|
136
|
+
return `
|
|
137
|
+
// Generated by ChainCSS Webpack Loader (Runtime Mode)
|
|
138
|
+
// Source: ${path.basename(resourcePath)}
|
|
139
|
+
|
|
140
|
+
import { chain, enableDebug } from 'chaincss/runtime';
|
|
141
|
+
|
|
142
|
+
${hmrEnabled ? `// Enable debug in development
|
|
143
|
+
if (process.env.NODE_ENV !== 'production') {
|
|
144
|
+
enableDebug(${options.verbose || false});
|
|
145
|
+
}` : ''}
|
|
146
|
+
|
|
147
|
+
// Original source for reference
|
|
148
|
+
${source}
|
|
149
|
+
|
|
150
|
+
// Compiled style exports
|
|
151
|
+
export const styles = {
|
|
152
|
+
${classMapping}
|
|
153
|
+
};
|
|
154
|
+
|
|
155
|
+
export default styles;
|
|
156
|
+
|
|
157
|
+
${hmrEnabled ? `
|
|
158
|
+
// HMR Support
|
|
159
|
+
if (module.hot) {
|
|
160
|
+
module.hot.accept();
|
|
161
|
+
module.hot.dispose(() => {
|
|
162
|
+
// Cleanup on reload
|
|
163
|
+
const styleId = 'chaincss-${generateHash(resourcePath)}';
|
|
164
|
+
const style = document.getElementById(styleId);
|
|
165
|
+
if (style) style.remove();
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
` : ''}
|
|
169
|
+
`;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Generate production code with CSS extraction
|
|
174
|
+
*/
|
|
175
|
+
function generateProductionCode(
|
|
176
|
+
css: string,
|
|
177
|
+
styleExports: StyleExport[],
|
|
178
|
+
resourcePath: string,
|
|
179
|
+
options: ChainCSSLoaderOptions
|
|
180
|
+
): string {
|
|
181
|
+
const styleNames = styleExports.map(e => e.name).join(', ');
|
|
182
|
+
const classMapping = styleExports.map(exp => ` ${exp.name}: '${exp.className}'`).join(',\n');
|
|
183
|
+
|
|
184
|
+
// Escape CSS for injection
|
|
185
|
+
const escapedCSS = css
|
|
186
|
+
.replace(/\\/g, '\\\\')
|
|
187
|
+
.replace(/`/g, '\\`')
|
|
188
|
+
.replace(/\$/g, '\\$');
|
|
189
|
+
|
|
190
|
+
if (options.extractCSS) {
|
|
191
|
+
// CSS will be extracted to separate file
|
|
192
|
+
return `
|
|
193
|
+
// Generated by ChainCSS Webpack Loader (Build Mode - Extracted)
|
|
194
|
+
// Source: ${path.basename(resourcePath)}
|
|
195
|
+
|
|
196
|
+
export const styles = {
|
|
197
|
+
${classMapping}
|
|
198
|
+
};
|
|
199
|
+
|
|
200
|
+
export default styles;
|
|
201
|
+
`;
|
|
202
|
+
} else {
|
|
203
|
+
// CSS will be injected via JavaScript
|
|
204
|
+
return `
|
|
205
|
+
// Generated by ChainCSS Webpack Loader (Build Mode - Injected)
|
|
206
|
+
// Source: ${path.basename(resourcePath)}
|
|
207
|
+
|
|
208
|
+
export const styles = {
|
|
209
|
+
${classMapping}
|
|
210
|
+
};
|
|
211
|
+
|
|
212
|
+
// Inject CSS
|
|
213
|
+
const css = \`${escapedCSS}\`;
|
|
214
|
+
if (typeof document !== 'undefined' && css && css.trim()) {
|
|
215
|
+
const styleId = 'chaincss-${generateHash(resourcePath)}';
|
|
216
|
+
let style = document.getElementById(styleId);
|
|
217
|
+
if (!style) {
|
|
218
|
+
style = document.createElement('style');
|
|
219
|
+
style.id = styleId;
|
|
220
|
+
style.setAttribute('data-chaincss', 'true');
|
|
221
|
+
style.setAttribute('data-source', ${JSON.stringify(path.basename(resourcePath))});
|
|
222
|
+
document.head.appendChild(style);
|
|
223
|
+
}
|
|
224
|
+
style.textContent = css;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
export default styles;
|
|
228
|
+
`;
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
/**
|
|
233
|
+
* Compile ChainCSS file using the compiler
|
|
234
|
+
*/
|
|
235
|
+
async function compileChainCSS(
|
|
236
|
+
source: string,
|
|
237
|
+
filePath: string,
|
|
238
|
+
options: ChainCSSLoaderOptions,
|
|
239
|
+
context: LoaderContext
|
|
240
|
+
): Promise<{ css: string; classMap: Record<string, string> }> {
|
|
241
|
+
const tempFile = path.join(context.context, `.temp.${generateHash(source)}.chain.js`);
|
|
242
|
+
const outputDir = options.outputDir || path.join(process.cwd(), '.chaincss-cache');
|
|
243
|
+
const baseName = path.basename(filePath, path.extname(filePath));
|
|
244
|
+
|
|
245
|
+
try {
|
|
246
|
+
// Ensure temp directory exists
|
|
247
|
+
const tempDir = path.dirname(tempFile);
|
|
248
|
+
if (!fs.existsSync(tempDir)) {
|
|
249
|
+
fs.mkdirSync(tempDir, { recursive: true });
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
// Write temp file
|
|
253
|
+
fs.writeFileSync(tempFile, source, 'utf8');
|
|
254
|
+
context.addDependency(tempFile);
|
|
255
|
+
|
|
256
|
+
// Ensure output directory exists
|
|
257
|
+
if (!fs.existsSync(outputDir)) {
|
|
258
|
+
fs.mkdirSync(outputDir, { recursive: true });
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
// Build CLI command using the compiler
|
|
262
|
+
const chaincssCli = path.join(__dirname, '../cli/index.js');
|
|
263
|
+
|
|
264
|
+
// Check if CLI exists
|
|
265
|
+
if (!fs.existsSync(chaincssCli)) {
|
|
266
|
+
throw new Error(`ChainCSS CLI not found at ${chaincssCli}. Please ensure chaincss is built.`);
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
const args = [
|
|
270
|
+
chaincssCli,
|
|
271
|
+
'compile',
|
|
272
|
+
tempFile,
|
|
273
|
+
outputDir,
|
|
274
|
+
'--framework', options.framework || 'auto'
|
|
275
|
+
];
|
|
276
|
+
|
|
277
|
+
if (options.atomic) args.push('--atomic');
|
|
278
|
+
if (options.minify) args.push('--minify');
|
|
279
|
+
if (options.sourceMap) args.push('--source-map');
|
|
280
|
+
if (options.verbose) args.push('--verbose');
|
|
281
|
+
|
|
282
|
+
if (options.verbose) {
|
|
283
|
+
console.log(`[chaincss-loader] Executing: node ${args.join(' ')}`);
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
// Execute compilation with timeout
|
|
287
|
+
const result = await new Promise<{ stdout: string; stderr: string }>((resolve, reject) => {
|
|
288
|
+
const proc = spawn('node', args, {
|
|
289
|
+
cwd: process.cwd(),
|
|
290
|
+
timeout: 30000
|
|
291
|
+
});
|
|
292
|
+
|
|
293
|
+
let stdout = '';
|
|
294
|
+
let stderr = '';
|
|
295
|
+
|
|
296
|
+
proc.stdout.on('data', (data) => { stdout += data.toString(); });
|
|
297
|
+
proc.stderr.on('data', (data) => { stderr += data.toString(); });
|
|
298
|
+
|
|
299
|
+
proc.on('close', (code) => {
|
|
300
|
+
if (code === 0) {
|
|
301
|
+
resolve({ stdout, stderr });
|
|
302
|
+
} else {
|
|
303
|
+
reject(new Error(`Compilation failed with code ${code}: ${stderr}`));
|
|
304
|
+
}
|
|
305
|
+
});
|
|
306
|
+
|
|
307
|
+
proc.on('error', reject);
|
|
308
|
+
});
|
|
309
|
+
|
|
310
|
+
if (options.verbose && result.stdout) {
|
|
311
|
+
console.log(result.stdout);
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
if (result.stderr) {
|
|
315
|
+
context.emitWarning(new Error(result.stderr));
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
// Read generated CSS
|
|
319
|
+
const cssPath = path.join(outputDir, `${baseName}.css`);
|
|
320
|
+
let css = '';
|
|
321
|
+
if (fs.existsSync(cssPath)) {
|
|
322
|
+
css = fs.readFileSync(cssPath, 'utf8');
|
|
323
|
+
if (options.verbose) {
|
|
324
|
+
console.log(`[chaincss-loader] Read CSS: ${css.length} bytes from ${cssPath}`);
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
// Read generated class map
|
|
329
|
+
const classMapPath = path.join(outputDir, `${baseName}.class.js`);
|
|
330
|
+
let classMap: Record<string, string> = {};
|
|
331
|
+
if (fs.existsSync(classMapPath)) {
|
|
332
|
+
const classMapContent = fs.readFileSync(classMapPath, 'utf8');
|
|
333
|
+
// Parse the exported class map
|
|
334
|
+
const exportMatch = classMapContent.match(/export const (\w+) = '([^']+)'/g);
|
|
335
|
+
if (exportMatch) {
|
|
336
|
+
exportMatch.forEach(line => {
|
|
337
|
+
const match = line.match(/export const (\w+) = '([^']+)'/);
|
|
338
|
+
if (match) {
|
|
339
|
+
classMap[match[1]] = match[2];
|
|
340
|
+
}
|
|
341
|
+
});
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
return { css, classMap };
|
|
346
|
+
|
|
347
|
+
} finally {
|
|
348
|
+
// Clean up temp file
|
|
349
|
+
try {
|
|
350
|
+
if (fs.existsSync(tempFile)) {
|
|
351
|
+
fs.unlinkSync(tempFile);
|
|
352
|
+
}
|
|
353
|
+
} catch (e) {
|
|
354
|
+
// Ignore cleanup errors
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
/**
|
|
360
|
+
* Webpack loader for ChainCSS
|
|
361
|
+
* Converts .chain.js / .chain.ts files to static CSS at build time
|
|
362
|
+
*/
|
|
363
|
+
export default function chaincssLoader(this: LoaderContext, source: string): void {
|
|
364
|
+
const callback = this.async();
|
|
365
|
+
const options = this.getOptions() || {};
|
|
366
|
+
|
|
367
|
+
// Make loader cacheable
|
|
368
|
+
this.cacheable(true);
|
|
369
|
+
|
|
370
|
+
// Determine mode: default to 'build' in production, 'runtime' in development
|
|
371
|
+
const mode = options.mode || (process.env.NODE_ENV === 'production' ? 'build' : 'runtime');
|
|
372
|
+
|
|
373
|
+
// Extract style exports from source
|
|
374
|
+
const styleExports = extractStyleExports(source);
|
|
375
|
+
|
|
376
|
+
if (options.verbose) {
|
|
377
|
+
console.log(`[chaincss-loader] Processing ${path.basename(this.resourcePath)} in ${mode} mode`);
|
|
378
|
+
console.log(`[chaincss-loader] Found exports: ${styleExports.map(e => e.name).join(', ')}`);
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
// DEVELOPMENT MODE: Use runtime (fast iteration, but has runtime cost)
|
|
382
|
+
if (mode === 'runtime') {
|
|
383
|
+
const code = generateRuntimeCode(source, styleExports, this.resourcePath, options);
|
|
384
|
+
callback(null, code);
|
|
385
|
+
return;
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
// PRODUCTION MODE: Build-time compilation (zero-runtime)
|
|
389
|
+
compileChainCSS(source, this.resourcePath, options, this)
|
|
390
|
+
.then(({ css, classMap }) => {
|
|
391
|
+
// Emit CSS as separate asset if extractCSS is enabled
|
|
392
|
+
if (options.extractCSS && css) {
|
|
393
|
+
const cssFileName = `${path.basename(this.resourcePath, path.extname(this.resourcePath))}.css`;
|
|
394
|
+
this.emitFile(cssFileName, css);
|
|
395
|
+
if (options.verbose) {
|
|
396
|
+
console.log(`[chaincss-loader] Emitted CSS: ${cssFileName} (${css.length} bytes)`);
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
// Generate final code
|
|
401
|
+
const code = generateProductionCode(css, styleExports, this.resourcePath, options);
|
|
402
|
+
|
|
403
|
+
if (options.verbose) {
|
|
404
|
+
console.log(`[chaincss-loader] ✓ Compiled ${path.basename(this.resourcePath)}`);
|
|
405
|
+
console.log(`[chaincss-loader] CSS size: ${css.length} bytes`);
|
|
406
|
+
console.log(`[chaincss-loader] Exports: ${styleExports.length}`);
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
callback(null, code);
|
|
410
|
+
})
|
|
411
|
+
.catch((err: Error) => {
|
|
412
|
+
console.error(`[chaincss-loader] Error compiling ${this.resourcePath}:`, err.message);
|
|
413
|
+
this.emitError(err);
|
|
414
|
+
callback(err);
|
|
415
|
+
});
|
|
416
|
+
}
|
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
// chaincss/src/runtime/Chain.ts
|
|
2
|
+
|
|
3
|
+
import { macros as coreMacros, shorthandMap } from '../compiler/shorthands.js';
|
|
4
|
+
|
|
5
|
+
let debugMode = false;
|
|
6
|
+
|
|
7
|
+
export function enableDebug(enabled: boolean = true): void {
|
|
8
|
+
debugMode = enabled;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* The mutable registry for runtime plugins.
|
|
14
|
+
*/
|
|
15
|
+
const runtimeMacros: Record<string, any> = { ...coreMacros };
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* HYDRATION BRIDGE
|
|
19
|
+
* This stores the mapping between "prop:value" and "atomic-class-name".
|
|
20
|
+
* It is populated at app-start by the user.
|
|
21
|
+
*/
|
|
22
|
+
let globalManifest: Record<string, string> = {};
|
|
23
|
+
|
|
24
|
+
// Update setManifest to use debug flag
|
|
25
|
+
export const setManifest = (manifest: any) => {
|
|
26
|
+
if (manifest.atomicMap) {
|
|
27
|
+
globalManifest = manifest.atomicMap;
|
|
28
|
+
} else if (manifest.atomicClasses) {
|
|
29
|
+
globalManifest = manifest.atomicClasses;
|
|
30
|
+
} else {
|
|
31
|
+
globalManifest = manifest || {};
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
if (debugMode) {
|
|
35
|
+
console.log('[ChainCSS] Manifest loaded with', Object.keys(globalManifest).length, 'entries');
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* THE TOKEN STORE
|
|
41
|
+
* Stores user-defined design tokens (colors, spacing, etc.)
|
|
42
|
+
*/
|
|
43
|
+
let globalTokens: Record<string, any> = {};
|
|
44
|
+
|
|
45
|
+
// Update setTokens to use debug flag
|
|
46
|
+
export const setTokens = (tokens: any) => {
|
|
47
|
+
globalTokens = tokens;
|
|
48
|
+
if (debugMode) {
|
|
49
|
+
console.log('[ChainCSS] Tokens updated:', Object.keys(globalTokens));
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
export class RuntimeChain {
|
|
54
|
+
// catcher now tracks both raw styles and pre-baked class names
|
|
55
|
+
private catcher: Record<string, any> = { _classes: [] };
|
|
56
|
+
private componentName: string = '';
|
|
57
|
+
public proxy: any;
|
|
58
|
+
|
|
59
|
+
constructor(private useTokens: boolean = false) {
|
|
60
|
+
// Only these methods are public API
|
|
61
|
+
const PUBLIC_METHODS = new Set([
|
|
62
|
+
'use', 'hover', '$el', '$name', 'end', 'getCatcher'
|
|
63
|
+
]);
|
|
64
|
+
|
|
65
|
+
this.proxy = new Proxy(this, {
|
|
66
|
+
/**
|
|
67
|
+
* 1. TRAPS FOR EXTERNAL TOOLS (React, DevTools, JSON.stringify)
|
|
68
|
+
* This prevents the "cyclic object value" error.
|
|
69
|
+
*/
|
|
70
|
+
get: (target, prop: string | symbol) => {
|
|
71
|
+
// Handle standard JS/React internal checks
|
|
72
|
+
if (prop === 'toJSON') return () => target.catcher;
|
|
73
|
+
if (prop === 'constructor') return RuntimeChain;
|
|
74
|
+
if (prop === Symbol.toStringTag) return 'RuntimeChain';
|
|
75
|
+
if (prop === '_isChain') return true; // Secret handshake for the hook
|
|
76
|
+
|
|
77
|
+
// If it's a symbol we don't handle, return it from the target
|
|
78
|
+
if (typeof prop !== 'string') return (target as any)[prop];
|
|
79
|
+
|
|
80
|
+
// 2. PUBLIC METHODS only ($el, use, hover, $name, end, getCatcher)
|
|
81
|
+
if (prop in target && PUBLIC_METHODS.has(prop)) {
|
|
82
|
+
const val = (target as any)[prop];
|
|
83
|
+
return typeof val === 'function' ? val.bind(target) : val;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// Block internal methods from being called directly
|
|
87
|
+
if (prop in target && typeof (target as any)[prop] === 'function') {
|
|
88
|
+
if (debugMode) {
|
|
89
|
+
console.warn(`[ChainCSS] '${prop}' is an internal method, not part of the public API`);
|
|
90
|
+
}
|
|
91
|
+
return undefined;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// 3. SHORTHAND RESOLUTION (e.g., 'bg' -> 'backgroundColor')
|
|
95
|
+
const realProp = (shorthandMap as any)[prop] || prop;
|
|
96
|
+
|
|
97
|
+
// 4. MACRO CHECK (.center, .glass, etc.)
|
|
98
|
+
if (runtimeMacros[prop]) {
|
|
99
|
+
return (val: any) => {
|
|
100
|
+
runtimeMacros[prop](val, target.catcher, target.useTokens);
|
|
101
|
+
return target.proxy;
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// 5. THE "BREAD AND BUTTER" CSS HANDLER
|
|
106
|
+
return (val: any) => {
|
|
107
|
+
// --- TOKEN RESOLUTION ---
|
|
108
|
+
let finalVal = val;
|
|
109
|
+
let valueWithUnit = val;
|
|
110
|
+
const unitless = ['opacity', 'zIndex', 'flex', 'fontWeight', 'flexGrow', 'flexShrink', 'flexBasis', 'order', 'lineHeight', 'animationIterationCount', 'orphans', 'widows', 'columnCount'];
|
|
111
|
+
if (typeof finalVal === 'number' && !unitless.includes(realProp)) {
|
|
112
|
+
valueWithUnit = `${val}px`;
|
|
113
|
+
finalVal = valueWithUnit; // Ensure the dynamic style also has px
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// --- HYBRID LOOKUP (Static vs Dynamic) ---
|
|
117
|
+
// Match the compiler's format "property:value"
|
|
118
|
+
const lookupKey = `${realProp}:${valueWithUnit}`;
|
|
119
|
+
const staticClass = globalManifest[lookupKey];
|
|
120
|
+
|
|
121
|
+
// In the RuntimeChain class, when adding to _classes, add debug
|
|
122
|
+
if (staticClass) {
|
|
123
|
+
if (!target.catcher._classes.includes(staticClass)) {
|
|
124
|
+
target.catcher._classes.push(staticClass);
|
|
125
|
+
if (debugMode) {
|
|
126
|
+
console.log(`[ChainCSS] Using atomic class: ${staticClass} for ${lookupKey}`);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
} else {
|
|
130
|
+
if (debugMode) {
|
|
131
|
+
console.log(`[ChainCSS] No atomic class for ${lookupKey}, will inject at runtime`);
|
|
132
|
+
}
|
|
133
|
+
target.catcher[realProp] = finalVal;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// Return the proxy so we can keep chaining: .bg().color().br()
|
|
137
|
+
return target.proxy;
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
use(plugin: any): any {
|
|
146
|
+
const { selectors, atRules, ...styles } = plugin;
|
|
147
|
+
|
|
148
|
+
// Resolve shorthands for incoming plugin styles
|
|
149
|
+
Object.entries(styles).forEach(([key, val]) => {
|
|
150
|
+
const realProp = (shorthandMap as any)[key] || key;
|
|
151
|
+
this.catcher[realProp] = val;
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
return this.proxy;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
hover(): any {
|
|
158
|
+
const hoverCatcher: Record<string, any> = { _classes: [] };
|
|
159
|
+
const hoverHandler: ProxyHandler<object> = {
|
|
160
|
+
get: (_, prop: string) => {
|
|
161
|
+
if (prop === 'end') {
|
|
162
|
+
return () => {
|
|
163
|
+
this.catcher.hover = { ...this.catcher.hover, ...hoverCatcher };
|
|
164
|
+
return this.proxy;
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
const realProp = (shorthandMap as any)[prop] || prop;
|
|
169
|
+
|
|
170
|
+
return (val: any) => {
|
|
171
|
+
// ✅ FIXED: Use hover: prefix for hover state keys
|
|
172
|
+
const lookupKey = `hover:${realProp}:${val}`;
|
|
173
|
+
const staticClass = globalManifest[lookupKey];
|
|
174
|
+
|
|
175
|
+
if (staticClass) {
|
|
176
|
+
if (!hoverCatcher._classes.includes(staticClass)) {
|
|
177
|
+
hoverCatcher._classes.push(staticClass);
|
|
178
|
+
}
|
|
179
|
+
} else if (runtimeMacros[prop]) {
|
|
180
|
+
runtimeMacros[prop](val, hoverCatcher, this.useTokens);
|
|
181
|
+
} else {
|
|
182
|
+
hoverCatcher[realProp] = val;
|
|
183
|
+
}
|
|
184
|
+
return hoverProxy;
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
};
|
|
188
|
+
const hoverProxy = new Proxy({}, hoverHandler);
|
|
189
|
+
return hoverProxy;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* Set the component name for class generation
|
|
194
|
+
*/
|
|
195
|
+
$name(name: string): this {
|
|
196
|
+
this.componentName = name;
|
|
197
|
+
return this;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* Finalizes the chain. Returns the style object and resets the catcher.
|
|
202
|
+
*/
|
|
203
|
+
$el(name?: string): Record<string, any> {
|
|
204
|
+
// Deep clone to prevent reference sharing
|
|
205
|
+
const result = structuredClone(this.catcher);
|
|
206
|
+
|
|
207
|
+
// Set component name
|
|
208
|
+
result._name = name || this.componentName || 'element';
|
|
209
|
+
|
|
210
|
+
// Strip internal metadata
|
|
211
|
+
delete result._componentName;
|
|
212
|
+
delete result._generateComponent;
|
|
213
|
+
delete result._framework;
|
|
214
|
+
delete result._propsDefinition;
|
|
215
|
+
|
|
216
|
+
// Full reset
|
|
217
|
+
this.catcher = { _classes: [] };
|
|
218
|
+
this.componentName = '';
|
|
219
|
+
|
|
220
|
+
return result;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
end(name?: string) {
|
|
224
|
+
return this.$el(name);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
/**
|
|
228
|
+
* Get the current catcher (for debugging)
|
|
229
|
+
*/
|
|
230
|
+
getCatcher(): Record<string, any> {
|
|
231
|
+
return { ...this.catcher };
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
/**
|
|
236
|
+
* --- EXPORTS ---
|
|
237
|
+
*/
|
|
238
|
+
|
|
239
|
+
export const $ = () => new RuntimeChain(false).proxy;
|
|
240
|
+
export const $t = () => new RuntimeChain(true).proxy;
|
|
241
|
+
export const chain = (useTokens: boolean = false) => new RuntimeChain(useTokens).proxy;
|
|
242
|
+
export default chain;
|