esm-styles 0.1.3 → 0.1.6
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/build.d.ts +2 -0
- package/dist/build.js +24 -0
- package/dist/index.d.ts +3 -8
- package/dist/index.js +4 -11
- package/dist/lib/build.d.ts +1 -0
- package/dist/lib/build.js +184 -0
- package/dist/lib/index.d.ts +3 -0
- package/dist/lib/index.js +211 -0
- package/dist/lib/types/index.d.ts +41 -39
- package/dist/lib/types/index.js +1 -0
- package/dist/lib/utils/cartesian.d.ts +1 -0
- package/dist/lib/utils/cartesian.js +7 -0
- package/dist/lib/utils/content.d.ts +2 -9
- package/dist/lib/utils/content.js +17 -31
- package/dist/lib/utils/end-value.d.ts +2 -0
- package/dist/lib/utils/end-value.js +16 -0
- package/dist/lib/utils/endValue.d.ts +2 -0
- package/dist/lib/utils/endValue.js +10 -0
- package/dist/lib/utils/format.d.ts +2 -0
- package/dist/lib/utils/format.js +15 -0
- package/dist/lib/utils/get-css-variables.d.ts +9 -0
- package/dist/lib/utils/get-css-variables.js +24 -0
- package/dist/lib/utils/index.d.ts +8 -0
- package/dist/lib/utils/index.js +8 -0
- package/dist/lib/utils/key.d.ts +2 -0
- package/dist/lib/utils/key.js +7 -0
- package/dist/lib/utils/media-shorthand.d.ts +12 -0
- package/dist/lib/utils/media-shorthand.js +56 -0
- package/dist/lib/utils/selector.d.ts +4 -0
- package/dist/lib/utils/selector.js +186 -0
- package/dist/watch.js +32 -0
- package/package.json +17 -5
package/dist/build.d.ts
ADDED
package/dist/build.js
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { build } from './lib/build.js';
|
|
3
|
+
async function main() {
|
|
4
|
+
const args = process.argv.slice(2);
|
|
5
|
+
const configPath = args.find((arg) => !arg.startsWith('-')) || 'esm-styles.config.js';
|
|
6
|
+
const watch = args.includes('--watch') || args.includes('-w');
|
|
7
|
+
if (watch) {
|
|
8
|
+
console.log('[esm-styles] Watch mode is not supported internally due to ESM module caching.');
|
|
9
|
+
console.log('[esm-styles] Please use nodemon or a similar tool to restart the build process on file changes.');
|
|
10
|
+
console.log('[esm-styles] Example:');
|
|
11
|
+
console.log(" npx nodemon --watch <source-dir> --ext mjs --exec 'node dist/build.js <config-file>'");
|
|
12
|
+
process.exit(0);
|
|
13
|
+
}
|
|
14
|
+
try {
|
|
15
|
+
console.log('Build v0.0.11');
|
|
16
|
+
await build(configPath);
|
|
17
|
+
console.log('Build completed successfully.');
|
|
18
|
+
}
|
|
19
|
+
catch (err) {
|
|
20
|
+
console.error('Build failed:', err);
|
|
21
|
+
process.exit(1);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
main();
|
package/dist/index.d.ts
CHANGED
|
@@ -1,13 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* esm-styles
|
|
3
3
|
*
|
|
4
|
-
* A library for
|
|
4
|
+
* A library for building CSS styles from ES modules
|
|
5
5
|
*/
|
|
6
|
-
export * from
|
|
7
|
-
export
|
|
8
|
-
export { joinSelectors, cartesianSelectors } from './lib/utils/selectors.js';
|
|
9
|
-
export { formatContentValue } from './lib/utils/content.js';
|
|
10
|
-
export { jsKeyToCssKey, isEndValue } from './lib/utils/common.js';
|
|
11
|
-
export { processMediaQueries } from './lib/utils/media.js';
|
|
12
|
-
export { obj2css, prettifyCss } from './lib/utils/obj2css.js';
|
|
6
|
+
export * from "./lib/types/index.js";
|
|
7
|
+
export * from "./lib/index.js";
|
|
13
8
|
export declare function greet(): string;
|
package/dist/index.js
CHANGED
|
@@ -1,18 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* esm-styles
|
|
3
3
|
*
|
|
4
|
-
* A library for
|
|
4
|
+
* A library for building CSS styles from ES modules
|
|
5
5
|
*/
|
|
6
6
|
// Export types
|
|
7
|
-
export * from
|
|
8
|
-
|
|
9
|
-
export { default as getCss } from './lib/getCss.js';
|
|
10
|
-
// Utils exports for advanced usage
|
|
11
|
-
export { joinSelectors, cartesianSelectors } from './lib/utils/selectors.js';
|
|
12
|
-
export { formatContentValue } from './lib/utils/content.js';
|
|
13
|
-
export { jsKeyToCssKey, isEndValue } from './lib/utils/common.js';
|
|
14
|
-
export { processMediaQueries } from './lib/utils/media.js';
|
|
15
|
-
export { obj2css, prettifyCss } from './lib/utils/obj2css.js';
|
|
7
|
+
export * from "./lib/types/index.js";
|
|
8
|
+
export * from "./lib/index.js";
|
|
16
9
|
export function greet() {
|
|
17
|
-
return
|
|
10
|
+
return "esm-styles 0.1.4";
|
|
18
11
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function build(configPath?: string): Promise<void>;
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
import { getCss } from './index.js';
|
|
2
|
+
import { getCssVariables } from './utils/index.js';
|
|
3
|
+
import { getMediaShorthands } from './utils/media-shorthand.js';
|
|
4
|
+
import { isEndValue } from './utils/index.js';
|
|
5
|
+
import path from 'node:path';
|
|
6
|
+
import fs from 'node:fs/promises';
|
|
7
|
+
import { inspect } from 'node:util';
|
|
8
|
+
import _ from 'lodash';
|
|
9
|
+
export async function build(configPath = 'esm-styles.config.js') {
|
|
10
|
+
// --- Supporting module generation ---
|
|
11
|
+
// Debug: log mergedSets and sets
|
|
12
|
+
// console.log('[DEBUG] sets:', sets)
|
|
13
|
+
// console.log('[DEBUG] mergedSets:', inspect(mergedSets, { depth: 10 }))
|
|
14
|
+
// 1. Load config
|
|
15
|
+
const configFile = path.isAbsolute(configPath)
|
|
16
|
+
? configPath
|
|
17
|
+
: path.resolve(process.cwd(), configPath);
|
|
18
|
+
const config = (await import(configFile)).default;
|
|
19
|
+
const basePath = path.resolve(process.cwd(), config.basePath || '.');
|
|
20
|
+
const sourcePath = path.join(basePath, config.sourcePath || '');
|
|
21
|
+
const outputPath = path.join(basePath, config.outputPath || '');
|
|
22
|
+
const suffix = config.sourceFilesSuffix || '.styles.mjs';
|
|
23
|
+
const layers = config.layers || [];
|
|
24
|
+
const mainCssFile = config.mainCssFile || 'styles.css';
|
|
25
|
+
// Merge media shorthands
|
|
26
|
+
const mediaShorthands = getMediaShorthands(config);
|
|
27
|
+
// Ensure output directory exists
|
|
28
|
+
await fs.mkdir(outputPath, { recursive: true });
|
|
29
|
+
const cssFiles = [];
|
|
30
|
+
// 2. Process globalVariables
|
|
31
|
+
if (config.globalVariables) {
|
|
32
|
+
const inputFile = path.join(sourcePath, `${config.globalVariables}${suffix}`);
|
|
33
|
+
const outputFile = path.join(outputPath, `global.css`);
|
|
34
|
+
const fileUrl = pathToFileUrl(inputFile).href + `?update=${Date.now()}`;
|
|
35
|
+
const varsObj = (await import(fileUrl)).default;
|
|
36
|
+
const cssVars = getCssVariables(varsObj);
|
|
37
|
+
const rootSelector = config.globalRootSelector || ':root';
|
|
38
|
+
const wrappedCss = `${rootSelector} {\n${cssVars}\n}`;
|
|
39
|
+
await fs.writeFile(outputFile, wrappedCss, 'utf8');
|
|
40
|
+
cssFiles.push({ type: 'global', file: 'global.css' });
|
|
41
|
+
}
|
|
42
|
+
// 3. Process media variable sets
|
|
43
|
+
if (config.media && config.mediaSelectors) {
|
|
44
|
+
for (const mediaType of Object.keys(config.media)) {
|
|
45
|
+
const sets = config.media[mediaType]; // e.g. ['light', 'twilight', 'dark']
|
|
46
|
+
let prevVarsObj = {};
|
|
47
|
+
// Collect all merged sets for supporting module
|
|
48
|
+
const mergedSets = {};
|
|
49
|
+
for (const setName of sets) {
|
|
50
|
+
// Inheritance: merge with previous
|
|
51
|
+
const inputFile = path.join(sourcePath, `${setName}${suffix}`);
|
|
52
|
+
const fileUrl = pathToFileUrl(inputFile).href + `?update=${Date.now()}`;
|
|
53
|
+
const varsObj = _.merge({}, prevVarsObj, (await import(fileUrl)).default);
|
|
54
|
+
prevVarsObj = varsObj;
|
|
55
|
+
mergedSets[setName] = _.cloneDeep(varsObj);
|
|
56
|
+
// For each selector config for this set
|
|
57
|
+
const selectorConfigs = config.mediaSelectors?.[mediaType]?.[setName] || [];
|
|
58
|
+
for (const selectorConfig of selectorConfigs) {
|
|
59
|
+
const { selector, mediaQuery, prefix } = selectorConfig;
|
|
60
|
+
// File name: {prefix.}{setName}.{mediaType}.css
|
|
61
|
+
const prefixPart = prefix ? `${prefix}.` : '';
|
|
62
|
+
const fileName = `${prefixPart}${setName}.${mediaType}.css`;
|
|
63
|
+
const outputFile = path.join(outputPath, fileName);
|
|
64
|
+
const cssVars = getCssVariables(varsObj);
|
|
65
|
+
const rootSelector = config.globalRootSelector || ':root';
|
|
66
|
+
let fullSelector = rootSelector;
|
|
67
|
+
if (selector) {
|
|
68
|
+
// If selector starts with combinator or pseudo, don't add space
|
|
69
|
+
if (/^[.:#[]./.test(selector)) {
|
|
70
|
+
fullSelector = `${rootSelector}${selector}`;
|
|
71
|
+
}
|
|
72
|
+
else {
|
|
73
|
+
fullSelector = `${rootSelector} ${selector}`;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
const block = `${fullSelector} {\n${cssVars}\n}`;
|
|
77
|
+
await fs.writeFile(outputFile, block, 'utf8');
|
|
78
|
+
cssFiles.push({
|
|
79
|
+
type: 'media',
|
|
80
|
+
file: fileName,
|
|
81
|
+
mediaType,
|
|
82
|
+
setName,
|
|
83
|
+
mediaQuery,
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
// --- Supporting module generation ---
|
|
88
|
+
// Debug: log mergedSets and sets
|
|
89
|
+
console.log('[DEBUG] sets:', sets);
|
|
90
|
+
console.log('[DEBUG] mergedSets:', inspect(mergedSets, { depth: 10 }));
|
|
91
|
+
// Define the recursive function here so it has access to sets and mergedSets
|
|
92
|
+
const buildSupportingModule = (path, isRoot) => {
|
|
93
|
+
const allKeys = new Set();
|
|
94
|
+
for (const set of sets) {
|
|
95
|
+
const v = path.length === 0 ? mergedSets[set] : _.get(mergedSets[set], path);
|
|
96
|
+
if (v && typeof v === 'object' && !Array.isArray(v)) {
|
|
97
|
+
Object.keys(v).forEach((k) => allKeys.add(k));
|
|
98
|
+
}
|
|
99
|
+
else if (v !== undefined && !isRoot) {
|
|
100
|
+
allKeys.add(''); // placeholder for leaf, but only at non-root
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
// Debug: show allKeys and values at this path
|
|
104
|
+
console.log('[DEBUG] path:', path.join('.'), 'allKeys:', Array.from(allKeys));
|
|
105
|
+
for (const set of sets) {
|
|
106
|
+
const v = path.length === 0 ? mergedSets[set] : _.get(mergedSets[set], path);
|
|
107
|
+
console.log('[DEBUG] set:', set, 'path:', path.join('.'), 'value:', JSON.stringify(v));
|
|
108
|
+
}
|
|
109
|
+
const result = {};
|
|
110
|
+
for (const key of allKeys) {
|
|
111
|
+
if (key === '') {
|
|
112
|
+
// Only possible at non-root
|
|
113
|
+
const varName = '--' + path.map((k) => k.replace(/_/g, '-')).join('-');
|
|
114
|
+
const leaf = { var: varName };
|
|
115
|
+
for (let i = 0; i < sets.length; i++) {
|
|
116
|
+
const v = path.length === 0
|
|
117
|
+
? mergedSets[sets[i]]
|
|
118
|
+
: _.get(mergedSets[sets[i]], path);
|
|
119
|
+
if (isEndValue(v)) {
|
|
120
|
+
leaf[sets[i]] = v;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
if (Object.keys(leaf).length > 1) {
|
|
124
|
+
return leaf;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
else {
|
|
128
|
+
const child = buildSupportingModule([...path, key], false);
|
|
129
|
+
if (child && Object.keys(child).length > 0) {
|
|
130
|
+
result[key] = child;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
// Debug log for each recursion
|
|
135
|
+
console.log('[DEBUG] path:', path.join('.'), 'result:', JSON.stringify(result, null, 2));
|
|
136
|
+
return result;
|
|
137
|
+
};
|
|
138
|
+
const supportingModuleObj = buildSupportingModule([], true);
|
|
139
|
+
const supportingModulePath = path.join(sourcePath, `$${mediaType}.mjs`);
|
|
140
|
+
const moduleContent = `export default ${JSON.stringify(supportingModuleObj, null, 2)}\n`;
|
|
141
|
+
await fs.writeFile(supportingModulePath, moduleContent, 'utf8');
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
// 4. Process each layer (legacy, keep for now)
|
|
145
|
+
for (const layer of layers) {
|
|
146
|
+
const inputFile = path.join(sourcePath, `${layer}${suffix}`);
|
|
147
|
+
const outputFile = path.join(outputPath, `${layer}.css`);
|
|
148
|
+
const fileUrl = pathToFileUrl(inputFile).href + `?update=${Date.now()}`;
|
|
149
|
+
const stylesObj = (await import(fileUrl)).default;
|
|
150
|
+
// Always call getCss for the layer object, so media queries are rendered
|
|
151
|
+
const css = getCss(stylesObj, {
|
|
152
|
+
...mediaShorthands,
|
|
153
|
+
globalRootSelector: config.globalRootSelector,
|
|
154
|
+
});
|
|
155
|
+
const wrappedCss = `@layer ${layer} {\n${css}\n}`;
|
|
156
|
+
await fs.writeFile(outputFile, wrappedCss, 'utf8');
|
|
157
|
+
cssFiles.push({ layer, file: `${layer}.css` });
|
|
158
|
+
}
|
|
159
|
+
// 5. Create main CSS file
|
|
160
|
+
const mainCssPath = path.join(outputPath, mainCssFile);
|
|
161
|
+
// Compose imports for variable sets
|
|
162
|
+
const varImports = cssFiles
|
|
163
|
+
.filter((f) => f.type === 'global' || f.type === 'media')
|
|
164
|
+
.map((f) => {
|
|
165
|
+
if (f.mediaQuery) {
|
|
166
|
+
return `@import '${f.file}' ${f.mediaQuery};`;
|
|
167
|
+
}
|
|
168
|
+
return `@import '${f.file}';`;
|
|
169
|
+
})
|
|
170
|
+
.join('\n');
|
|
171
|
+
// Compose imports for layers
|
|
172
|
+
const layerFiles = cssFiles.filter((f) => f.layer).map((f) => f.file);
|
|
173
|
+
const layerNames = cssFiles
|
|
174
|
+
.filter((f) => f.layer)
|
|
175
|
+
.map((f) => f.layer)
|
|
176
|
+
.join(', ');
|
|
177
|
+
const layerImports = layerFiles.map((f) => `@import '${f}';`).join('\n');
|
|
178
|
+
const mainCss = [varImports, layerNames ? `@layer ${layerNames};` : '', layerImports]
|
|
179
|
+
.filter(Boolean)
|
|
180
|
+
.join('\n') + '\n';
|
|
181
|
+
await fs.writeFile(mainCssPath, mainCss, 'utf8');
|
|
182
|
+
}
|
|
183
|
+
// Helper for file URL import
|
|
184
|
+
import { pathToFileURL as pathToFileUrl } from 'node:url';
|
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
import * as utils from './utils/index.js';
|
|
2
|
+
function flatWalk(obj, selectorPath = [], result = { rules: [], media: {}, layers: {} }, options = {}, currentMedia = []) {
|
|
3
|
+
const props = {};
|
|
4
|
+
for (const key in obj) {
|
|
5
|
+
const value = obj[key];
|
|
6
|
+
if (utils.isEndValue(value)) {
|
|
7
|
+
const cssKey = utils.jsKeyToCssKey(key);
|
|
8
|
+
if (typeof value === 'object' &&
|
|
9
|
+
value !== null &&
|
|
10
|
+
'var' in value &&
|
|
11
|
+
typeof value.var === 'string') {
|
|
12
|
+
// Use CSS variable reference
|
|
13
|
+
props[cssKey] = `var(${value.var})`;
|
|
14
|
+
}
|
|
15
|
+
else {
|
|
16
|
+
props[cssKey] = cssKey === 'content' ? utils.contentValue(value) : value;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
else if (typeof value === 'object' && value !== null) {
|
|
20
|
+
if (key.startsWith('@media ')) {
|
|
21
|
+
// Recursively walk value, collecting rules for this media block
|
|
22
|
+
const nested = flatWalk(value, selectorPath, { rules: [], media: {}, layers: {} }, options, [...currentMedia, key]);
|
|
23
|
+
const mediaKey = [...currentMedia, key].join(' && ');
|
|
24
|
+
if (!result.media[mediaKey])
|
|
25
|
+
result.media[mediaKey] = [];
|
|
26
|
+
result.media[mediaKey].push(...nested.rules);
|
|
27
|
+
// Also merge any nested media
|
|
28
|
+
for (const nestedMediaKey in nested.media) {
|
|
29
|
+
if (!result.media[nestedMediaKey])
|
|
30
|
+
result.media[nestedMediaKey] = [];
|
|
31
|
+
result.media[nestedMediaKey].push(...nested.media[nestedMediaKey]);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
else if (key.startsWith('@layer ')) {
|
|
35
|
+
if (!result.layers[key])
|
|
36
|
+
result.layers[key] = { rules: [], media: {}, layers: {} };
|
|
37
|
+
flatWalk(value, selectorPath, result.layers[key], options, currentMedia);
|
|
38
|
+
}
|
|
39
|
+
else if (key.startsWith('@')) {
|
|
40
|
+
// Other special keys (shorthands, etc.)
|
|
41
|
+
const shorthand = key.slice(1);
|
|
42
|
+
let handled = false;
|
|
43
|
+
if (options.mediaQueries && options.mediaQueries[shorthand]) {
|
|
44
|
+
const mediaKey = `@media ${options.mediaQueries[shorthand]}`;
|
|
45
|
+
flatWalk(value, selectorPath, result, options, [
|
|
46
|
+
...currentMedia,
|
|
47
|
+
mediaKey,
|
|
48
|
+
]);
|
|
49
|
+
handled = true;
|
|
50
|
+
}
|
|
51
|
+
if (options.selectorShorthands &&
|
|
52
|
+
options.selectorShorthands[shorthand]) {
|
|
53
|
+
const root = options.globalRootSelector || ':root';
|
|
54
|
+
for (const entry of options.selectorShorthands[shorthand]) {
|
|
55
|
+
if (entry.selector && !entry.mediaQuery) {
|
|
56
|
+
flatWalk(value, [[root + entry.selector], ...selectorPath], result, options, currentMedia);
|
|
57
|
+
handled = true;
|
|
58
|
+
}
|
|
59
|
+
if (entry.selector && entry.mediaQuery) {
|
|
60
|
+
const mediaKey = `@media ${entry.mediaQuery}`;
|
|
61
|
+
flatWalk(value, [[root + entry.selector], ...selectorPath], result, options, [...currentMedia, mediaKey]);
|
|
62
|
+
handled = true;
|
|
63
|
+
}
|
|
64
|
+
if (!entry.selector && entry.mediaQuery) {
|
|
65
|
+
const mediaKey = `@media ${entry.mediaQuery}`;
|
|
66
|
+
flatWalk(value, selectorPath, result, options, [
|
|
67
|
+
...currentMedia,
|
|
68
|
+
mediaKey,
|
|
69
|
+
]);
|
|
70
|
+
handled = true;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
if (!handled) {
|
|
75
|
+
if (key.startsWith('@media') || key.startsWith('@layer')) {
|
|
76
|
+
// skip
|
|
77
|
+
}
|
|
78
|
+
else {
|
|
79
|
+
const parts = key.split(',').map((k) => k.trim());
|
|
80
|
+
flatWalk(value, [...selectorPath, parts], result, options, currentMedia);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
else {
|
|
85
|
+
// Always treat as selector segment if not a special key
|
|
86
|
+
const parts = key.split(',').map((k) => k.trim());
|
|
87
|
+
flatWalk(value, [...selectorPath, parts], result, options, currentMedia);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
if (Object.keys(props).length > 0) {
|
|
92
|
+
const selectors = utils.joinSelectorPath(selectorPath);
|
|
93
|
+
for (const selector of selectors) {
|
|
94
|
+
if (currentMedia.length === 0) {
|
|
95
|
+
result.rules.push({ selector, declarations: { ...props } });
|
|
96
|
+
}
|
|
97
|
+
else {
|
|
98
|
+
const mediaKey = currentMedia.join(' && ');
|
|
99
|
+
if (!result.media[mediaKey])
|
|
100
|
+
result.media[mediaKey] = [];
|
|
101
|
+
result.media[mediaKey].push({ selector, declarations: { ...props } });
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
return result;
|
|
106
|
+
}
|
|
107
|
+
function renderRules(rules) {
|
|
108
|
+
let css = '';
|
|
109
|
+
const groups = {};
|
|
110
|
+
for (const rule of rules) {
|
|
111
|
+
const declKey = JSON.stringify(rule.declarations);
|
|
112
|
+
if (!groups[declKey])
|
|
113
|
+
groups[declKey] = [];
|
|
114
|
+
groups[declKey].push(rule.selector);
|
|
115
|
+
}
|
|
116
|
+
for (const declKey in groups) {
|
|
117
|
+
const selectors = groups[declKey].join(', ');
|
|
118
|
+
const declarations = JSON.parse(declKey);
|
|
119
|
+
css += `${selectors} {\n`;
|
|
120
|
+
for (const key in declarations) {
|
|
121
|
+
css += ` ${key}: ${declarations[key]};\n`;
|
|
122
|
+
}
|
|
123
|
+
css += '}\n';
|
|
124
|
+
}
|
|
125
|
+
return css;
|
|
126
|
+
}
|
|
127
|
+
function renderLayers(layers) {
|
|
128
|
+
let css = '';
|
|
129
|
+
for (const key in layers) {
|
|
130
|
+
css += `${key} {\n`;
|
|
131
|
+
css += renderRules(layers[key].rules);
|
|
132
|
+
css += renderFlatMedia(layers[key].media);
|
|
133
|
+
css += renderLayers(layers[key].layers);
|
|
134
|
+
css += '}\n';
|
|
135
|
+
}
|
|
136
|
+
return css;
|
|
137
|
+
}
|
|
138
|
+
function renderFlatMedia(media) {
|
|
139
|
+
let css = '';
|
|
140
|
+
for (const key in media) {
|
|
141
|
+
// If the key contains '&&', recursively nest the media blocks
|
|
142
|
+
const mediaParts = key.split(' && ');
|
|
143
|
+
if (mediaParts.length > 1) {
|
|
144
|
+
css += mediaParts.reduceRight((inner, part) => `${part} {\n${inner}\n}`, renderFlatMedia({ ['']: media[key] }));
|
|
145
|
+
}
|
|
146
|
+
else if (key === '') {
|
|
147
|
+
// Render rules directly, no block
|
|
148
|
+
// Group rules by their declarations (stringified)
|
|
149
|
+
const groups = {};
|
|
150
|
+
for (const rule of media[key]) {
|
|
151
|
+
const declKey = JSON.stringify(rule.declarations);
|
|
152
|
+
if (!groups[declKey])
|
|
153
|
+
groups[declKey] = [];
|
|
154
|
+
groups[declKey].push(rule.selector);
|
|
155
|
+
}
|
|
156
|
+
for (const declKey in groups) {
|
|
157
|
+
const selectors = groups[declKey].join(', ');
|
|
158
|
+
const declarations = JSON.parse(declKey);
|
|
159
|
+
css += `${selectors} {\n`;
|
|
160
|
+
for (const k in declarations) {
|
|
161
|
+
css += ` ${k}: ${declarations[k]};\n`;
|
|
162
|
+
}
|
|
163
|
+
css += '}\n';
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
else {
|
|
167
|
+
css += `${key} {\n`;
|
|
168
|
+
// Group rules by their declarations (stringified)
|
|
169
|
+
const groups = {};
|
|
170
|
+
for (const rule of media[key]) {
|
|
171
|
+
const declKey = JSON.stringify(rule.declarations);
|
|
172
|
+
if (!groups[declKey])
|
|
173
|
+
groups[declKey] = [];
|
|
174
|
+
groups[declKey].push(rule.selector);
|
|
175
|
+
}
|
|
176
|
+
for (const declKey in groups) {
|
|
177
|
+
const selectors = groups[declKey].join(', ');
|
|
178
|
+
const declarations = JSON.parse(declKey);
|
|
179
|
+
css += `${selectors} {\n`;
|
|
180
|
+
for (const k in declarations) {
|
|
181
|
+
css += ` ${k}: ${declarations[k]};\n`;
|
|
182
|
+
}
|
|
183
|
+
css += '}\n';
|
|
184
|
+
}
|
|
185
|
+
css += '}\n';
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
return css;
|
|
189
|
+
}
|
|
190
|
+
function mergeMedia(target, source) {
|
|
191
|
+
for (const key in source) {
|
|
192
|
+
if (!target[key])
|
|
193
|
+
target[key] = [];
|
|
194
|
+
target[key].push(...source[key]);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
export function getCss(styles, options = {}) {
|
|
198
|
+
const result = flatWalk(styles, [], { rules: [], media: {}, layers: {} }, options);
|
|
199
|
+
if (typeof console !== 'undefined') {
|
|
200
|
+
// console.log(
|
|
201
|
+
// '[esm-styles] flatWalk result:',
|
|
202
|
+
// JSON.stringify(result, null, 2)
|
|
203
|
+
// )
|
|
204
|
+
}
|
|
205
|
+
let css = '';
|
|
206
|
+
css += renderRules(result.rules);
|
|
207
|
+
css += renderFlatMedia(result.media);
|
|
208
|
+
css += renderLayers(result.layers);
|
|
209
|
+
return utils.prettifyCssString(css);
|
|
210
|
+
}
|
|
211
|
+
export * from './types/index.js';
|
|
@@ -1,43 +1,45 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Type definitions for the CSS in JS library
|
|
3
3
|
*/
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
export interface CssStyles {
|
|
16
|
-
[key: CssKey]: CssValue | CssStyles;
|
|
17
|
-
}
|
|
18
|
-
/**
|
|
19
|
-
* Named media queries configuration
|
|
20
|
-
*/
|
|
21
|
-
export interface MediaQueries {
|
|
22
|
-
[name: string]: string;
|
|
23
|
-
}
|
|
24
|
-
/**
|
|
25
|
-
* Media prefixes configuration
|
|
26
|
-
*/
|
|
27
|
-
export interface MediaPrefixes {
|
|
28
|
-
[name: string]: string;
|
|
29
|
-
}
|
|
30
|
-
/**
|
|
31
|
-
* Auto mode configuration for color schemes
|
|
32
|
-
*/
|
|
33
|
-
export interface AutoConfig {
|
|
34
|
-
[mode: string]: [string, string];
|
|
35
|
-
}
|
|
36
|
-
/**
|
|
37
|
-
* Configuration for the CSS generator
|
|
38
|
-
*/
|
|
39
|
-
export interface CssConfig {
|
|
40
|
-
mediaQueries?: MediaQueries;
|
|
41
|
-
mediaPrefixes?: MediaPrefixes;
|
|
42
|
-
auto?: AutoConfig;
|
|
4
|
+
export type CssJsObject = Record<string, any>;
|
|
5
|
+
export interface GetCssOptions {
|
|
6
|
+
mediaQueries?: Record<string, string>;
|
|
7
|
+
mediaPrefixes?: Record<string, string>;
|
|
8
|
+
auto?: Record<string, string[]>;
|
|
9
|
+
globalRootSelector?: string;
|
|
10
|
+
selectorShorthands?: Record<string, {
|
|
11
|
+
selector?: string;
|
|
12
|
+
mediaQuery?: string;
|
|
13
|
+
prefix?: string;
|
|
14
|
+
}[]>;
|
|
43
15
|
}
|
|
16
|
+
export type CssString = string;
|
|
17
|
+
export type CssPropertyValue = string | number;
|
|
18
|
+
export type CssRuleObject = Record<string, CssPropertyValue>;
|
|
19
|
+
export type CssAstNode = {
|
|
20
|
+
type: 'rule';
|
|
21
|
+
selector: string;
|
|
22
|
+
declarations: CssRuleObject;
|
|
23
|
+
} | {
|
|
24
|
+
type: 'group';
|
|
25
|
+
children: CssAstNode[];
|
|
26
|
+
} | {
|
|
27
|
+
type: 'at-rule';
|
|
28
|
+
name: string;
|
|
29
|
+
params: string;
|
|
30
|
+
children: CssAstNode[];
|
|
31
|
+
};
|
|
32
|
+
export type SelectorPath = string[][];
|
|
33
|
+
export type WalkCallback = (node: any, path: SelectorPath) => void;
|
|
34
|
+
export type JsKeyToCssKey = (key: string) => string;
|
|
35
|
+
export type IsEndValue = (value: any) => boolean;
|
|
36
|
+
export type JoinSelectorPath = (path: SelectorPath) => string;
|
|
37
|
+
export type ContentValue = (value: string) => string;
|
|
38
|
+
export type CartesianProduct = <T>(arrays: T[][]) => T[][];
|
|
39
|
+
export type PrettifyCssString = (css: string) => string;
|
|
40
|
+
export type MediaQueryHandler = (name: string, node: any, path: SelectorPath, options: GetCssOptions) => string;
|
|
41
|
+
export type LayerHandler = (name: string, node: any, path: SelectorPath, options: GetCssOptions) => string;
|
|
42
|
+
export type ContainerQueryHandler = (name: string, node: any, path: SelectorPath, options: GetCssOptions) => string;
|
|
43
|
+
export type IsHtmlTag = (key: string) => boolean;
|
|
44
|
+
export type IsSpecialSelector = (key: string) => boolean;
|
|
45
|
+
export type IsClassSelector = (key: string) => boolean;
|
package/dist/lib/types/index.js
CHANGED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function cartesianProduct<T>(arrays: T[][]): T[][];
|
|
@@ -1,9 +1,2 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
*/
|
|
4
|
-
/**
|
|
5
|
-
* Converts a JavaScript string value to a valid CSS content property value
|
|
6
|
-
* @param value - String value to convert for CSS content property
|
|
7
|
-
* @returns CSS-compatible content value
|
|
8
|
-
*/
|
|
9
|
-
export declare function formatContentValue(value: string | number | boolean | null): string;
|
|
1
|
+
import type { ContentValue } from '../types/index.js';
|
|
2
|
+
export declare const contentValue: ContentValue;
|
|
@@ -1,33 +1,19 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
return
|
|
16
|
-
}
|
|
17
|
-
// Handle unicode and emoji characters
|
|
18
|
-
const formattedValue = Array.from(stringValue)
|
|
19
|
-
.map((char) => {
|
|
20
|
-
const codePoint = char.codePointAt(0);
|
|
21
|
-
if (!codePoint)
|
|
22
|
-
return char;
|
|
23
|
-
// Convert emoji and special characters to unicode escape sequences
|
|
24
|
-
// Control characters and non-ASCII characters need escaping
|
|
25
|
-
if (codePoint > 127 || codePoint < 32) {
|
|
26
|
-
return `\\${codePoint.toString(16).padStart(6, '0')}`;
|
|
27
|
-
}
|
|
28
|
-
return char;
|
|
1
|
+
export const contentValue = value => {
|
|
2
|
+
if (typeof value !== 'string')
|
|
3
|
+
return value;
|
|
4
|
+
// If already quoted, return as is
|
|
5
|
+
if (/^'.*'$/.test(value) || /^".*"$/.test(value))
|
|
6
|
+
return value;
|
|
7
|
+
// Convert each character to CSS unicode escape: \00xxxx
|
|
8
|
+
const unicode = value
|
|
9
|
+
.split('')
|
|
10
|
+
.map(ch => {
|
|
11
|
+
const code = ch.codePointAt(0);
|
|
12
|
+
if (!code)
|
|
13
|
+
return '';
|
|
14
|
+
// CSS unicode escape: \00xxxx (4 hex digits, single backslash)
|
|
15
|
+
return '\\00' + code.toString(16).padStart(4, '0');
|
|
29
16
|
})
|
|
30
17
|
.join('');
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
}
|
|
18
|
+
return `'${unicode}'`;
|
|
19
|
+
};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export const isEndValue = (value) => {
|
|
2
|
+
if (value == null)
|
|
3
|
+
return false;
|
|
4
|
+
if (typeof value === 'string' || typeof value === 'number')
|
|
5
|
+
return true;
|
|
6
|
+
if (Array.isArray(value)) {
|
|
7
|
+
return value.every((v) => typeof v === 'string' || typeof v === 'number');
|
|
8
|
+
}
|
|
9
|
+
if (typeof value === 'object' &&
|
|
10
|
+
value !== null &&
|
|
11
|
+
'var' in value &&
|
|
12
|
+
typeof value.var === 'string') {
|
|
13
|
+
return true;
|
|
14
|
+
}
|
|
15
|
+
return false;
|
|
16
|
+
};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export const isEndValue = (value) => {
|
|
2
|
+
if (value == null)
|
|
3
|
+
return false;
|
|
4
|
+
if (typeof value === 'string' || typeof value === 'number')
|
|
5
|
+
return true;
|
|
6
|
+
if (Array.isArray(value)) {
|
|
7
|
+
return value.every((v) => typeof v === 'string' || typeof v === 'number');
|
|
8
|
+
}
|
|
9
|
+
return false;
|
|
10
|
+
};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
// CSS formatting utility for getCss
|
|
2
|
+
import beautify from 'js-beautify';
|
|
3
|
+
export const prettifyCssString = (cssString) => {
|
|
4
|
+
// Simple pretty-print: trim and collapse extra whitespace
|
|
5
|
+
// return css
|
|
6
|
+
// .replace(/\s+/g, ' ')
|
|
7
|
+
// .replace(/\s*{\s*/g, ' {\n ')
|
|
8
|
+
// .replace(/;\s*/g, ';\n ')
|
|
9
|
+
// .replace(/}\s*/g, '\n}\n')
|
|
10
|
+
// .trim()
|
|
11
|
+
return beautify.css(cssString, {
|
|
12
|
+
indent_size: 2,
|
|
13
|
+
end_with_newline: true,
|
|
14
|
+
});
|
|
15
|
+
};
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Flattens a nested JS object into CSS variable declarations.
|
|
3
|
+
* Example:
|
|
4
|
+
* { colors: { paper: { normal: '#212121' } } }
|
|
5
|
+
* => [ '--colors-paper-normal: #212121;' ]
|
|
6
|
+
*/
|
|
7
|
+
export declare function getCssVariables(obj: Record<string, any>, options?: {
|
|
8
|
+
prefix?: string;
|
|
9
|
+
}): string;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import _ from 'lodash';
|
|
2
|
+
/**
|
|
3
|
+
* Flattens a nested JS object into CSS variable declarations.
|
|
4
|
+
* Example:
|
|
5
|
+
* { colors: { paper: { normal: '#212121' } } }
|
|
6
|
+
* => [ '--colors-paper-normal: #212121;' ]
|
|
7
|
+
*/
|
|
8
|
+
export function getCssVariables(obj, options = {}) {
|
|
9
|
+
const result = [];
|
|
10
|
+
function walk(current, path = []) {
|
|
11
|
+
if (_.isPlainObject(current)) {
|
|
12
|
+
for (const key in current) {
|
|
13
|
+
walk(current[key], [...path, key]);
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
else {
|
|
17
|
+
// Leaf value: build variable name
|
|
18
|
+
const varName = '--' + path.map(k => k.replace(/_/g, '-')).join('-');
|
|
19
|
+
result.push(`${varName}: ${current};`);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
walk(obj);
|
|
23
|
+
return result.join('\n');
|
|
24
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export * from './selector.js';
|
|
2
|
+
export * from './key.js';
|
|
3
|
+
export * from './content.js';
|
|
4
|
+
export * from './cartesian.js';
|
|
5
|
+
export * from './format.js';
|
|
6
|
+
export * from './end-value.js';
|
|
7
|
+
export * from './get-css-variables.js';
|
|
8
|
+
export * from './media-shorthand.js';
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export * from './selector.js';
|
|
2
|
+
export * from './key.js';
|
|
3
|
+
export * from './content.js';
|
|
4
|
+
export * from './cartesian.js';
|
|
5
|
+
export * from './format.js';
|
|
6
|
+
export * from './end-value.js';
|
|
7
|
+
export * from './get-css-variables.js';
|
|
8
|
+
export * from './media-shorthand.js';
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
interface MediaShorthand {
|
|
2
|
+
selector?: string;
|
|
3
|
+
mediaQuery?: string;
|
|
4
|
+
prefix?: string;
|
|
5
|
+
}
|
|
6
|
+
export interface MediaShorthandResult {
|
|
7
|
+
mediaQueries: Record<string, string>;
|
|
8
|
+
selectorShorthands: Record<string, MediaShorthand[]>;
|
|
9
|
+
allShorthands: Record<string, MediaShorthand[]>;
|
|
10
|
+
}
|
|
11
|
+
export declare function getMediaShorthands(config: any): MediaShorthandResult;
|
|
12
|
+
export {};
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
export function getMediaShorthands(config) {
|
|
2
|
+
const mediaQueries = { ...config.mediaQueries };
|
|
3
|
+
const selectorShorthands = {};
|
|
4
|
+
const allShorthands = {};
|
|
5
|
+
const usedNames = new Set();
|
|
6
|
+
// 1. From mediaSelectors
|
|
7
|
+
if (config.mediaSelectors) {
|
|
8
|
+
for (const mediaType of Object.keys(config.mediaSelectors)) {
|
|
9
|
+
const variants = config.mediaSelectors[mediaType];
|
|
10
|
+
for (const variantName of Object.keys(variants)) {
|
|
11
|
+
const entries = variants[variantName];
|
|
12
|
+
for (const entry of entries) {
|
|
13
|
+
// If has mediaQuery and no selector: treat as media query
|
|
14
|
+
if (entry.mediaQuery && !entry.selector) {
|
|
15
|
+
if (mediaQueries[variantName]) {
|
|
16
|
+
console.warn(`[esm-styles] Warning: Duplicate media shorthand '${variantName}' in both mediaQueries and mediaSelectors. Using value from mediaQueries.`);
|
|
17
|
+
continue;
|
|
18
|
+
}
|
|
19
|
+
mediaQueries[variantName] = entry.mediaQuery;
|
|
20
|
+
usedNames.add(variantName);
|
|
21
|
+
allShorthands[variantName] = allShorthands[variantName] || [];
|
|
22
|
+
allShorthands[variantName].push({ mediaQuery: entry.mediaQuery });
|
|
23
|
+
}
|
|
24
|
+
// If has selector: treat as selector shorthand
|
|
25
|
+
if (entry.selector) {
|
|
26
|
+
selectorShorthands[variantName] =
|
|
27
|
+
selectorShorthands[variantName] || [];
|
|
28
|
+
selectorShorthands[variantName].push({
|
|
29
|
+
selector: entry.selector,
|
|
30
|
+
mediaQuery: entry.mediaQuery,
|
|
31
|
+
prefix: entry.prefix,
|
|
32
|
+
});
|
|
33
|
+
usedNames.add(variantName);
|
|
34
|
+
allShorthands[variantName] = allShorthands[variantName] || [];
|
|
35
|
+
allShorthands[variantName].push({
|
|
36
|
+
selector: entry.selector,
|
|
37
|
+
mediaQuery: entry.mediaQuery,
|
|
38
|
+
prefix: entry.prefix,
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
// 2. From mediaQueries (overrides)
|
|
46
|
+
if (config.mediaQueries) {
|
|
47
|
+
for (const name of Object.keys(config.mediaQueries)) {
|
|
48
|
+
if (usedNames.has(name)) {
|
|
49
|
+
console.warn(`[esm-styles] Warning: Duplicate media shorthand '${name}' in both mediaQueries and mediaSelectors. Using value from mediaQueries.`);
|
|
50
|
+
}
|
|
51
|
+
mediaQueries[name] = config.mediaQueries[name];
|
|
52
|
+
allShorthands[name] = [{ mediaQuery: config.mediaQueries[name] }];
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
return { mediaQueries, selectorShorthands, allShorthands };
|
|
56
|
+
}
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
import * as utils from './cartesian.js';
|
|
2
|
+
// List of standard HTML tags (not exhaustive, but covers common cases)
|
|
3
|
+
const HTML_TAGS = new Set([
|
|
4
|
+
'html',
|
|
5
|
+
'body',
|
|
6
|
+
'div',
|
|
7
|
+
'span',
|
|
8
|
+
'p',
|
|
9
|
+
'a',
|
|
10
|
+
'ul',
|
|
11
|
+
'ol',
|
|
12
|
+
'li',
|
|
13
|
+
'img',
|
|
14
|
+
'input',
|
|
15
|
+
'textarea',
|
|
16
|
+
'button',
|
|
17
|
+
'form',
|
|
18
|
+
'label',
|
|
19
|
+
'table',
|
|
20
|
+
'thead',
|
|
21
|
+
'tbody',
|
|
22
|
+
'tfoot',
|
|
23
|
+
'tr',
|
|
24
|
+
'th',
|
|
25
|
+
'td',
|
|
26
|
+
'section',
|
|
27
|
+
'article',
|
|
28
|
+
'nav',
|
|
29
|
+
'header',
|
|
30
|
+
'footer',
|
|
31
|
+
'main',
|
|
32
|
+
'aside',
|
|
33
|
+
'h1',
|
|
34
|
+
'h2',
|
|
35
|
+
'h3',
|
|
36
|
+
'h4',
|
|
37
|
+
'h5',
|
|
38
|
+
'h6',
|
|
39
|
+
'video',
|
|
40
|
+
'audio',
|
|
41
|
+
'canvas',
|
|
42
|
+
'svg',
|
|
43
|
+
'select',
|
|
44
|
+
'option',
|
|
45
|
+
'blockquote',
|
|
46
|
+
'pre',
|
|
47
|
+
'code',
|
|
48
|
+
'figure',
|
|
49
|
+
'figcaption',
|
|
50
|
+
'dl',
|
|
51
|
+
'dt',
|
|
52
|
+
'dd',
|
|
53
|
+
'fieldset',
|
|
54
|
+
'legend',
|
|
55
|
+
'details',
|
|
56
|
+
'summary',
|
|
57
|
+
'iframe',
|
|
58
|
+
'picture',
|
|
59
|
+
'source',
|
|
60
|
+
'map',
|
|
61
|
+
'area',
|
|
62
|
+
'meta',
|
|
63
|
+
'link',
|
|
64
|
+
'style',
|
|
65
|
+
'script',
|
|
66
|
+
'noscript',
|
|
67
|
+
'b',
|
|
68
|
+
'i',
|
|
69
|
+
'u',
|
|
70
|
+
's',
|
|
71
|
+
'em',
|
|
72
|
+
'strong',
|
|
73
|
+
'small',
|
|
74
|
+
'cite',
|
|
75
|
+
'q',
|
|
76
|
+
'dfn',
|
|
77
|
+
'abbr',
|
|
78
|
+
'data',
|
|
79
|
+
'time',
|
|
80
|
+
'mark',
|
|
81
|
+
'ruby',
|
|
82
|
+
'rt',
|
|
83
|
+
'rp',
|
|
84
|
+
'bdi',
|
|
85
|
+
'bdo',
|
|
86
|
+
'span',
|
|
87
|
+
'br',
|
|
88
|
+
'wbr',
|
|
89
|
+
'ins',
|
|
90
|
+
'del',
|
|
91
|
+
'sub',
|
|
92
|
+
'sup',
|
|
93
|
+
'hr',
|
|
94
|
+
'progress',
|
|
95
|
+
'meter',
|
|
96
|
+
'output',
|
|
97
|
+
'details',
|
|
98
|
+
'dialog',
|
|
99
|
+
'menu',
|
|
100
|
+
'menuitem',
|
|
101
|
+
'fieldset',
|
|
102
|
+
'legend',
|
|
103
|
+
'datalist',
|
|
104
|
+
'optgroup',
|
|
105
|
+
'keygen',
|
|
106
|
+
'command',
|
|
107
|
+
'track',
|
|
108
|
+
'embed',
|
|
109
|
+
'object',
|
|
110
|
+
'param',
|
|
111
|
+
'col',
|
|
112
|
+
'colgroup',
|
|
113
|
+
'caption',
|
|
114
|
+
'address',
|
|
115
|
+
'applet',
|
|
116
|
+
'base',
|
|
117
|
+
'basefont',
|
|
118
|
+
'big',
|
|
119
|
+
'center',
|
|
120
|
+
'dir',
|
|
121
|
+
'font',
|
|
122
|
+
'frame',
|
|
123
|
+
'frameset',
|
|
124
|
+
'isindex',
|
|
125
|
+
'listing',
|
|
126
|
+
'marquee',
|
|
127
|
+
'multicol',
|
|
128
|
+
'nextid',
|
|
129
|
+
'nobr',
|
|
130
|
+
'noembed',
|
|
131
|
+
'noframes',
|
|
132
|
+
'plaintext',
|
|
133
|
+
'rb',
|
|
134
|
+
'rtc',
|
|
135
|
+
'strike',
|
|
136
|
+
'tt',
|
|
137
|
+
'xmp',
|
|
138
|
+
]);
|
|
139
|
+
const isHtmlTag = (key) => {
|
|
140
|
+
// Only match if the key is a plain tag name (no underscores, no special chars)
|
|
141
|
+
return HTML_TAGS.has(key);
|
|
142
|
+
};
|
|
143
|
+
export const isSpecialSelector = (key) => {
|
|
144
|
+
// Pseudo-classes/elements, id, attribute selectors
|
|
145
|
+
return (key.startsWith(':') ||
|
|
146
|
+
key.startsWith('::') ||
|
|
147
|
+
key.startsWith('#') ||
|
|
148
|
+
key.startsWith('['));
|
|
149
|
+
};
|
|
150
|
+
export const isClassSelector = (key) => {
|
|
151
|
+
// _foo = class, __foo = descendant class
|
|
152
|
+
return key.startsWith('_');
|
|
153
|
+
};
|
|
154
|
+
export const joinSelectorPath = (path) => {
|
|
155
|
+
// Compute cartesian product of all segments
|
|
156
|
+
const combos = utils.cartesianProduct(path);
|
|
157
|
+
// Join each combination into a selector string
|
|
158
|
+
return combos.map((parts) => parts.reduce((acc, part) => {
|
|
159
|
+
if (part.startsWith('__')) {
|
|
160
|
+
return acc + (acc ? ' ' : '') + '.' + part.slice(2);
|
|
161
|
+
}
|
|
162
|
+
else if (part.startsWith('_')) {
|
|
163
|
+
return acc + (acc ? ' ' : '') + '.' + part.slice(1);
|
|
164
|
+
}
|
|
165
|
+
else if (part.startsWith('>') ||
|
|
166
|
+
part.startsWith('+') ||
|
|
167
|
+
part.startsWith('~')) {
|
|
168
|
+
// Combinators: always join with a space
|
|
169
|
+
return acc + ' ' + part;
|
|
170
|
+
}
|
|
171
|
+
else if (part.startsWith(':') ||
|
|
172
|
+
part.startsWith('::') ||
|
|
173
|
+
part.startsWith('#') ||
|
|
174
|
+
part.startsWith('[') ||
|
|
175
|
+
part.startsWith('.')) {
|
|
176
|
+
return acc + part;
|
|
177
|
+
}
|
|
178
|
+
else if (isHtmlTag(part)) {
|
|
179
|
+
return acc + (acc ? ' ' : '') + part;
|
|
180
|
+
}
|
|
181
|
+
else {
|
|
182
|
+
// Not a tag, not a special selector: treat as class
|
|
183
|
+
return acc + (acc ? '' : '') + '.' + part;
|
|
184
|
+
}
|
|
185
|
+
}, ''));
|
|
186
|
+
};
|
package/dist/watch.js
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { spawn } from 'child_process'
|
|
3
|
+
import path from 'path'
|
|
4
|
+
import process from 'process'
|
|
5
|
+
|
|
6
|
+
const configPath = process.argv[2] || 'esm-styles.config.js'
|
|
7
|
+
const configModule = await import(
|
|
8
|
+
path.isAbsolute(configPath)
|
|
9
|
+
? configPath
|
|
10
|
+
: path.resolve(process.cwd(), configPath)
|
|
11
|
+
)
|
|
12
|
+
const config = configModule.default
|
|
13
|
+
|
|
14
|
+
const basePath = path.resolve(process.cwd(), config.basePath || '.')
|
|
15
|
+
const sourcePath = path.join(basePath, config.sourcePath || '')
|
|
16
|
+
|
|
17
|
+
const nodemonArgs = [
|
|
18
|
+
'--watch',
|
|
19
|
+
sourcePath,
|
|
20
|
+
'--ext',
|
|
21
|
+
'mjs',
|
|
22
|
+
'--ignore',
|
|
23
|
+
path.join(sourcePath, '$*.mjs'),
|
|
24
|
+
'--exec',
|
|
25
|
+
`node dist/build.js ${configPath}`,
|
|
26
|
+
]
|
|
27
|
+
|
|
28
|
+
console.log('[esm-styles] Running:', 'nodemon', ...nodemonArgs)
|
|
29
|
+
|
|
30
|
+
const nodemon = spawn('nodemon', nodemonArgs, { stdio: 'inherit' })
|
|
31
|
+
|
|
32
|
+
nodemon.on('exit', (code) => process.exit(code))
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "esm-styles",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.6",
|
|
4
4
|
"description": "A library for working with ESM styles",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -15,7 +15,8 @@
|
|
|
15
15
|
"test": "node --experimental-vm-modules node_modules/jest/bin/jest.js",
|
|
16
16
|
"test:watch": "node --experimental-vm-modules node_modules/jest/bin/jest.js --watch",
|
|
17
17
|
"test:coverage": "node --experimental-vm-modules node_modules/jest/bin/jest.js --coverage",
|
|
18
|
-
"prepublishOnly": "npm run build"
|
|
18
|
+
"prepublishOnly": "npm run build",
|
|
19
|
+
"watch": "node watch.js"
|
|
19
20
|
},
|
|
20
21
|
"keywords": [
|
|
21
22
|
"esm",
|
|
@@ -27,14 +28,25 @@
|
|
|
27
28
|
"engines": {
|
|
28
29
|
"node": ">=14.16"
|
|
29
30
|
},
|
|
31
|
+
"bin": {
|
|
32
|
+
"build": "dist/build.js",
|
|
33
|
+
"watch": "dist/watch.js"
|
|
34
|
+
},
|
|
30
35
|
"devDependencies": {
|
|
31
|
-
"@types/jest": "^29.5.
|
|
36
|
+
"@types/jest": "^29.5.14",
|
|
37
|
+
"@types/js-beautify": "^1.14.3",
|
|
38
|
+
"@types/lodash": "^4.17.16",
|
|
32
39
|
"@types/node": "^20.11.0",
|
|
33
40
|
"@typescript-eslint/eslint-plugin": "^6.18.1",
|
|
34
41
|
"@typescript-eslint/parser": "^6.18.1",
|
|
35
42
|
"eslint": "^8.56.0",
|
|
36
43
|
"jest": "^29.7.0",
|
|
37
|
-
"
|
|
38
|
-
"
|
|
44
|
+
"lodash": "^4.17.21",
|
|
45
|
+
"nodemon": "^3.1.9",
|
|
46
|
+
"ts-jest": "^29.3.2",
|
|
47
|
+
"typescript": "^5.8.3"
|
|
48
|
+
},
|
|
49
|
+
"dependencies": {
|
|
50
|
+
"js-beautify": "^1.15.4"
|
|
39
51
|
}
|
|
40
52
|
}
|