@plumeria/codemod 18.3.1 → 18.3.3
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/README.md +21 -7
- package/dist/release.js +199 -26
- package/dist/transforms/css-modules.js +163 -14
- package/dist/transforms/from-plumeria.d.ts +2 -0
- package/dist/transforms/from-plumeria.js +17 -5
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -64,6 +64,11 @@ A descendant rule has no combinator to translate into, so it becomes
|
|
|
64
64
|
is read from the stylesheet; no markup is inspected. Class names become
|
|
65
65
|
camel-case keys, and a bare pixel length loses its unit.
|
|
66
66
|
|
|
67
|
+
A `@keyframes` the stylesheet declares becomes a `css.keyframes` binding above
|
|
68
|
+
the `css.create`, and the rules naming it read the binding. An `animation`
|
|
69
|
+
shorthand naming one is split into its longhands, since only `animationName`
|
|
70
|
+
resolves a binding.
|
|
71
|
+
|
|
67
72
|
### What it reports instead of converting
|
|
68
73
|
|
|
69
74
|
```
|
|
@@ -214,13 +219,22 @@ reported as `dynamic-style-access` and its definitions stay in Plumeria.
|
|
|
214
219
|
|
|
215
220
|
### Global styles
|
|
216
221
|
|
|
217
|
-
`css.createTheme
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
222
|
+
`css.createTheme` and `css.viewTransition` produce rules that belong in a global
|
|
223
|
+
stylesheet, not in a CSS Module. The codemod extracts them and appends them to
|
|
224
|
+
the stylesheet the project already loads — `app/global.css` or
|
|
225
|
+
`styles/global.css`, under `src` or not, spelled `global` or `globals` — and
|
|
226
|
+
writes `src/styles/global.css` (or `styles/global.css` when `src` does not
|
|
227
|
+
exist) when it finds none. Theme tokens become CSS custom properties under
|
|
228
|
+
`:where(:root)` and the theme selector; view transitions become
|
|
229
|
+
`::view-transition-*` pseudo-element rules. References in style objects are
|
|
230
|
+
replaced by their generated names. A `keyframes` or `viewTransition` nothing the
|
|
231
|
+
migration exports reaches is left where it is.
|
|
232
|
+
|
|
233
|
+
A CSS Module renames the value of `animation-name` the way it renames a class,
|
|
234
|
+
so a `css.keyframes` is written as `@keyframes kf-<hash>` at the top of each
|
|
235
|
+
module that reads it. In the global stylesheet the renamed reference would match
|
|
236
|
+
nothing. One a view transition names is written globally as well, since the
|
|
237
|
+
`::view-transition-*` rule is not scoped.
|
|
224
238
|
|
|
225
239
|
`css.createStatic` values are statically evaluated and inlined directly into the
|
|
226
240
|
generated CSS rules — no separate output file is needed.
|
package/dist/release.js
CHANGED
|
@@ -117,17 +117,24 @@ const walk = (node, visit) => {
|
|
|
117
117
|
};
|
|
118
118
|
const releasedPath = (source) => source.replace(/(?:\.styles)?\.[^.]+$/, '.module.css');
|
|
119
119
|
exports.releasedPath = releasedPath;
|
|
120
|
+
const GLOBAL_DIRECTORIES = [
|
|
121
|
+
['src', 'app'],
|
|
122
|
+
['app'],
|
|
123
|
+
['src', 'styles'],
|
|
124
|
+
['styles'],
|
|
125
|
+
];
|
|
126
|
+
const GLOBAL_NAMES = ['global.css', 'globals.css'];
|
|
120
127
|
const globalPath = (targets) => {
|
|
121
128
|
const first = path.resolve(targets[0]);
|
|
122
129
|
const root = fs.existsSync(first) && fs.statSync(first).isDirectory()
|
|
123
130
|
? first
|
|
124
131
|
: path.dirname(first);
|
|
125
|
-
const
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
132
|
+
const existing = GLOBAL_DIRECTORIES.flatMap((directory) => GLOBAL_NAMES.map((name) => path.join(root, ...directory, name))).find((candidate) => fs.existsSync(candidate));
|
|
133
|
+
if (existing)
|
|
134
|
+
return existing;
|
|
135
|
+
return fs.existsSync(path.join(root, 'src'))
|
|
136
|
+
? path.join(root, 'src', 'styles', 'global.css')
|
|
137
|
+
: path.join(root, 'styles', 'global.css');
|
|
131
138
|
};
|
|
132
139
|
function planRelease(targets) {
|
|
133
140
|
const stylesheets = [];
|
|
@@ -141,9 +148,10 @@ function planRelease(targets) {
|
|
|
141
148
|
const animationValues = new Map();
|
|
142
149
|
const staticValues = new Map();
|
|
143
150
|
const reportsBySource = new Map();
|
|
144
|
-
const globalBySource = new Map();
|
|
145
151
|
const themeKeys = new Map();
|
|
152
|
+
const themeCss = new Map();
|
|
146
153
|
const animationKeys = new Map();
|
|
154
|
+
const animationCss = new Map();
|
|
147
155
|
const staticKeys = new Map();
|
|
148
156
|
const dependencies = new Map();
|
|
149
157
|
const note = (source, reports) => {
|
|
@@ -154,15 +162,6 @@ function planRelease(targets) {
|
|
|
154
162
|
...reports,
|
|
155
163
|
]);
|
|
156
164
|
};
|
|
157
|
-
const addGlobal = (source, css) => {
|
|
158
|
-
const trimmed = css.trim();
|
|
159
|
-
if (trimmed.length === 0)
|
|
160
|
-
return;
|
|
161
|
-
globalBySource.set(source, [
|
|
162
|
-
...(globalBySource.get(source) ?? []),
|
|
163
|
-
trimmed,
|
|
164
|
-
]);
|
|
165
|
-
};
|
|
166
165
|
for (const source of sources) {
|
|
167
166
|
try {
|
|
168
167
|
const extracted = (0, from_plumeria_1.extractPlumeriaStatics)(sourceText.get(source));
|
|
@@ -248,7 +247,7 @@ function planRelease(targets) {
|
|
|
248
247
|
if (Object.keys(extracted.bindings).length === 0)
|
|
249
248
|
continue;
|
|
250
249
|
themeKeys.set(source, Object.keys(extracted.bindings));
|
|
251
|
-
|
|
250
|
+
themeCss.set(source, extracted.cssByBinding);
|
|
252
251
|
}
|
|
253
252
|
catch {
|
|
254
253
|
}
|
|
@@ -279,7 +278,7 @@ function planRelease(targets) {
|
|
|
279
278
|
...extracted.keyframes,
|
|
280
279
|
...extracted.viewTransitions,
|
|
281
280
|
]);
|
|
282
|
-
|
|
281
|
+
animationCss.set(source, extracted.cssByBinding);
|
|
283
282
|
}
|
|
284
283
|
catch {
|
|
285
284
|
}
|
|
@@ -564,16 +563,174 @@ function planRelease(targets) {
|
|
|
564
563
|
}
|
|
565
564
|
}
|
|
566
565
|
}
|
|
566
|
+
const definedKey = (source, binding) => `${source}\u0000${binding}`;
|
|
567
|
+
const definedIn = (source) => [
|
|
568
|
+
...(themeKeys.get(source) ?? []),
|
|
569
|
+
...(animationKeys.get(source) ?? []),
|
|
570
|
+
];
|
|
571
|
+
const outerReferences = new Map();
|
|
572
|
+
const innerReferences = new Map();
|
|
573
|
+
for (const source of sources) {
|
|
574
|
+
if (blocked.has(source))
|
|
575
|
+
continue;
|
|
576
|
+
const owned = definedIn(source);
|
|
577
|
+
let ast;
|
|
578
|
+
try {
|
|
579
|
+
ast = (0, parser_1.parse)(sourceText.get(source), {
|
|
580
|
+
sourceType: 'module',
|
|
581
|
+
ecmaFeatures: { jsx: true },
|
|
582
|
+
});
|
|
583
|
+
}
|
|
584
|
+
catch {
|
|
585
|
+
continue;
|
|
586
|
+
}
|
|
587
|
+
const local = new Map();
|
|
588
|
+
const namespaces = new Map();
|
|
589
|
+
for (const binding of owned)
|
|
590
|
+
local.set(binding, definedKey(source, binding));
|
|
591
|
+
for (const node of ast.body) {
|
|
592
|
+
if (node.type !== 'ImportDeclaration' ||
|
|
593
|
+
typeof node.source.value !== 'string')
|
|
594
|
+
continue;
|
|
595
|
+
const target = (0, resolve_1.resolveSourcePath)(node.source.value, source);
|
|
596
|
+
const available = target ? definedIn(target) : [];
|
|
597
|
+
if (!target || available.length === 0)
|
|
598
|
+
continue;
|
|
599
|
+
for (const specifier of node.specifiers) {
|
|
600
|
+
if (specifier.type === 'ImportNamespaceSpecifier') {
|
|
601
|
+
namespaces.set(specifier.local.name, target);
|
|
602
|
+
continue;
|
|
603
|
+
}
|
|
604
|
+
if (specifier.type !== 'ImportSpecifier')
|
|
605
|
+
continue;
|
|
606
|
+
const imported = specifier.imported.name ?? specifier.imported.value;
|
|
607
|
+
if (available.includes(imported))
|
|
608
|
+
local.set(specifier.local.name, definedKey(target, imported));
|
|
609
|
+
}
|
|
610
|
+
}
|
|
611
|
+
if (local.size === 0 && namespaces.size === 0)
|
|
612
|
+
continue;
|
|
613
|
+
const named = new Set();
|
|
614
|
+
walk(ast, (node) => {
|
|
615
|
+
if (node.type === 'MemberExpression' && !node.computed)
|
|
616
|
+
named.add(node.property);
|
|
617
|
+
else if (node.type === 'Property' && !node.computed)
|
|
618
|
+
named.add(node.key);
|
|
619
|
+
else if (node.type === 'ImportSpecifier') {
|
|
620
|
+
named.add(node.local);
|
|
621
|
+
named.add(node.imported);
|
|
622
|
+
}
|
|
623
|
+
else if (node.type === 'ImportDefaultSpecifier' ||
|
|
624
|
+
node.type === 'ImportNamespaceSpecifier')
|
|
625
|
+
named.add(node.local);
|
|
626
|
+
});
|
|
627
|
+
const definitions = new Map();
|
|
628
|
+
for (const raw of ast.body) {
|
|
629
|
+
const statement = raw.type === 'ExportNamedDeclaration' ? raw.declaration : raw;
|
|
630
|
+
if (statement?.type !== 'VariableDeclaration')
|
|
631
|
+
continue;
|
|
632
|
+
for (const declaration of statement.declarations) {
|
|
633
|
+
if (declaration.id.type !== 'Identifier' ||
|
|
634
|
+
!owned.includes(declaration.id.name))
|
|
635
|
+
continue;
|
|
636
|
+
named.add(declaration.id);
|
|
637
|
+
definitions.set(declaration.id.name, declaration.init);
|
|
638
|
+
}
|
|
639
|
+
}
|
|
640
|
+
const inside = new Set();
|
|
641
|
+
for (const init of definitions.values())
|
|
642
|
+
walk(init, (node) => inside.add(node));
|
|
643
|
+
const readsOf = (root, skip) => {
|
|
644
|
+
const found = new Set();
|
|
645
|
+
walk(root, (node) => {
|
|
646
|
+
if (skip(node))
|
|
647
|
+
return;
|
|
648
|
+
if (node.type === 'Identifier' && !named.has(node)) {
|
|
649
|
+
const key = local.get(node.name);
|
|
650
|
+
if (key)
|
|
651
|
+
found.add(key);
|
|
652
|
+
return;
|
|
653
|
+
}
|
|
654
|
+
if (node.type !== 'MemberExpression' ||
|
|
655
|
+
node.computed ||
|
|
656
|
+
node.object.type !== 'Identifier' ||
|
|
657
|
+
node.property.type !== 'Identifier')
|
|
658
|
+
return;
|
|
659
|
+
const target = namespaces.get(node.object.name);
|
|
660
|
+
const available = target ? definedIn(target) : [];
|
|
661
|
+
if (target && available.includes(node.property.name))
|
|
662
|
+
found.add(definedKey(target, node.property.name));
|
|
663
|
+
});
|
|
664
|
+
return [...found];
|
|
665
|
+
};
|
|
666
|
+
outerReferences.set(source, readsOf(ast, (node) => inside.has(node)));
|
|
667
|
+
innerReferences.set(source, new Map([...definitions].map(([binding, init]) => [
|
|
668
|
+
binding,
|
|
669
|
+
readsOf(init, () => false),
|
|
670
|
+
])));
|
|
671
|
+
}
|
|
672
|
+
const reachable = new Set();
|
|
673
|
+
const pending = [];
|
|
674
|
+
const reach = (key) => {
|
|
675
|
+
if (reachable.has(key))
|
|
676
|
+
return;
|
|
677
|
+
reachable.add(key);
|
|
678
|
+
pending.push(key);
|
|
679
|
+
};
|
|
680
|
+
for (const keys of outerReferences.values())
|
|
681
|
+
for (const key of keys)
|
|
682
|
+
reach(key);
|
|
683
|
+
while (pending.length > 0) {
|
|
684
|
+
const [target, binding] = pending.pop().split('\u0000');
|
|
685
|
+
for (const key of innerReferences.get(target)?.get(binding) ?? [])
|
|
686
|
+
reach(key);
|
|
687
|
+
}
|
|
688
|
+
const releasedFrom = (source, binding) => reachable.has(definedKey(source, binding));
|
|
689
|
+
const keyframesByName = new Map();
|
|
690
|
+
for (const [source, bindings] of animationValues) {
|
|
691
|
+
for (const [binding, name] of Object.entries(bindings)) {
|
|
692
|
+
if (!name.startsWith('kf-') || !releasedFrom(source, binding))
|
|
693
|
+
continue;
|
|
694
|
+
const rule = animationCss.get(source)?.[binding]?.trim();
|
|
695
|
+
if (rule)
|
|
696
|
+
keyframesByName.set(name, rule);
|
|
697
|
+
}
|
|
698
|
+
}
|
|
699
|
+
const globalReads = new Set();
|
|
700
|
+
for (const [source, bindings] of animationValues) {
|
|
701
|
+
for (const [binding, name] of Object.entries(bindings)) {
|
|
702
|
+
if (!name.startsWith('vt-') || !releasedFrom(source, binding))
|
|
703
|
+
continue;
|
|
704
|
+
for (const read of animationCss
|
|
705
|
+
.get(source)?.[binding]?.match(/\bkf-[a-z0-9]+\b/g) ?? [])
|
|
706
|
+
globalReads.add(read);
|
|
707
|
+
}
|
|
708
|
+
}
|
|
709
|
+
const carried = new Map();
|
|
710
|
+
const placed = new Set();
|
|
711
|
+
for (const [source, module] of converted) {
|
|
712
|
+
if (blocked.has(source) || !module)
|
|
713
|
+
continue;
|
|
714
|
+
const read = [...new Set(module.css.match(/\bkf-[a-z0-9]+\b/g) ?? [])]
|
|
715
|
+
.filter((name) => keyframesByName.has(name))
|
|
716
|
+
.map((name) => {
|
|
717
|
+
placed.add(name);
|
|
718
|
+
return keyframesByName.get(name);
|
|
719
|
+
});
|
|
720
|
+
if (read.length > 0)
|
|
721
|
+
carried.set(source, read);
|
|
722
|
+
}
|
|
567
723
|
const globalRules = [];
|
|
568
724
|
const values = {};
|
|
569
725
|
for (const source of sources) {
|
|
570
726
|
const reports = reportsBySource.get(source) ?? [];
|
|
571
727
|
const module = converted.get(source);
|
|
572
728
|
if (module) {
|
|
729
|
+
const held = carried.get(source);
|
|
573
730
|
stylesheets.push({
|
|
574
731
|
source,
|
|
575
732
|
target: (0, exports.releasedPath)(source),
|
|
576
|
-
css: module.css,
|
|
733
|
+
css: held ? `${held.join('\n\n')}\n\n${module.css}` : module.css,
|
|
577
734
|
reports,
|
|
578
735
|
});
|
|
579
736
|
}
|
|
@@ -584,16 +741,32 @@ function planRelease(targets) {
|
|
|
584
741
|
continue;
|
|
585
742
|
const resolved = {
|
|
586
743
|
...(staticValues.get(source) ?? {}),
|
|
587
|
-
...(themeValues.get(source) ?? {}),
|
|
588
|
-
...(animationValues.get(source) ?? {}),
|
|
744
|
+
...Object.fromEntries(Object.entries(themeValues.get(source) ?? {}).filter(([binding]) => releasedFrom(source, binding))),
|
|
745
|
+
...Object.fromEntries(Object.entries(animationValues.get(source) ?? {}).filter(([binding]) => releasedFrom(source, binding))),
|
|
589
746
|
};
|
|
590
747
|
if (Object.keys(resolved).length > 0)
|
|
591
748
|
values[source] = resolved;
|
|
592
|
-
|
|
593
|
-
if (
|
|
594
|
-
themes[source] =
|
|
595
|
-
|
|
596
|
-
|
|
749
|
+
const releasedThemes = (themeKeys.get(source) ?? []).filter((binding) => releasedFrom(source, binding));
|
|
750
|
+
if (releasedThemes.length > 0) {
|
|
751
|
+
themes[source] = releasedThemes;
|
|
752
|
+
for (const binding of releasedThemes) {
|
|
753
|
+
const rule = themeCss.get(source)?.[binding]?.trim();
|
|
754
|
+
if (rule)
|
|
755
|
+
globalRules.push(rule);
|
|
756
|
+
}
|
|
757
|
+
}
|
|
758
|
+
const releasedAnimations = (animationKeys.get(source) ?? []).filter((binding) => releasedFrom(source, binding));
|
|
759
|
+
if (releasedAnimations.length > 0) {
|
|
760
|
+
animations[source] = releasedAnimations;
|
|
761
|
+
for (const binding of releasedAnimations) {
|
|
762
|
+
const name = animationValues.get(source)?.[binding];
|
|
763
|
+
if (name && placed.has(name) && !globalReads.has(name))
|
|
764
|
+
continue;
|
|
765
|
+
const rule = animationCss.get(source)?.[binding]?.trim();
|
|
766
|
+
if (rule)
|
|
767
|
+
globalRules.push(rule);
|
|
768
|
+
}
|
|
769
|
+
}
|
|
597
770
|
if (staticKeys.has(source))
|
|
598
771
|
statics[source] = staticKeys.get(source);
|
|
599
772
|
if (!module)
|
|
@@ -25,6 +25,43 @@ const toKey = (className) => camel(className);
|
|
|
25
25
|
exports.toKey = toKey;
|
|
26
26
|
const PX = /^-?(\d+\.?\d*|\.\d+)px$/;
|
|
27
27
|
const NUMBER = /^-?(\d+\.?\d*|\.\d+)$/;
|
|
28
|
+
const IDENTIFIER = /^[A-Za-z_$][A-Za-z0-9_$]*$/;
|
|
29
|
+
const RESERVED_BINDINGS = new Set(['css', 'styles']);
|
|
30
|
+
const ANIMATION_PARTS = [
|
|
31
|
+
['animationTimingFunction', /^(cubic-bezier|steps|linear)\(/i],
|
|
32
|
+
[
|
|
33
|
+
'animationTimingFunction',
|
|
34
|
+
/^(linear|ease|ease-in|ease-out|ease-in-out|step-start|step-end)$/i,
|
|
35
|
+
],
|
|
36
|
+
['animationIterationCount', /^(infinite|\d+\.?\d*)$/i],
|
|
37
|
+
['animationDirection', /^(normal|reverse|alternate|alternate-reverse)$/i],
|
|
38
|
+
['animationFillMode', /^(none|forwards|backwards|both)$/i],
|
|
39
|
+
['animationPlayState', /^(running|paused)$/i],
|
|
40
|
+
];
|
|
41
|
+
const TIME = /^-?(\d+\.?\d*|\.\d+)(s|ms)$/i;
|
|
42
|
+
const words = (value) => {
|
|
43
|
+
const found = [];
|
|
44
|
+
let depth = 0;
|
|
45
|
+
let current = '';
|
|
46
|
+
for (const character of value) {
|
|
47
|
+
if (character === '(')
|
|
48
|
+
depth += 1;
|
|
49
|
+
if (character === ')')
|
|
50
|
+
depth -= 1;
|
|
51
|
+
if (character === ',' && depth === 0)
|
|
52
|
+
return null;
|
|
53
|
+
if (/\s/.test(character) && depth === 0) {
|
|
54
|
+
if (current)
|
|
55
|
+
found.push(current);
|
|
56
|
+
current = '';
|
|
57
|
+
continue;
|
|
58
|
+
}
|
|
59
|
+
current += character;
|
|
60
|
+
}
|
|
61
|
+
if (current)
|
|
62
|
+
found.push(current);
|
|
63
|
+
return found;
|
|
64
|
+
};
|
|
28
65
|
const toValue = (raw) => {
|
|
29
66
|
const value = raw.trim();
|
|
30
67
|
if (PX.test(value))
|
|
@@ -193,7 +230,11 @@ const serialise = (node, indent) => {
|
|
|
193
230
|
lines.push(`${indent}${key}: ${written},`);
|
|
194
231
|
}
|
|
195
232
|
for (const [key, child] of node.nested) {
|
|
196
|
-
const written = key.startsWith('css.extended(')
|
|
233
|
+
const written = key.startsWith('css.extended(')
|
|
234
|
+
? `[${key}]`
|
|
235
|
+
: IDENTIFIER.test(key)
|
|
236
|
+
? key
|
|
237
|
+
: `'${key}'`;
|
|
197
238
|
lines.push(`${indent}${written}: {`);
|
|
198
239
|
lines.push(serialise(child, indent + ' '));
|
|
199
240
|
lines.push(`${indent}},`);
|
|
@@ -233,6 +274,94 @@ function convertStylesheet(css) {
|
|
|
233
274
|
hint,
|
|
234
275
|
});
|
|
235
276
|
};
|
|
277
|
+
const animations = new Map();
|
|
278
|
+
const animationNodes = new Map();
|
|
279
|
+
const reachedAnimations = new Set();
|
|
280
|
+
root.walkAtRules(/^(-[a-z]+-)?keyframes$/i, (atRule) => {
|
|
281
|
+
const name = atRule.params.trim();
|
|
282
|
+
const binding = (0, exports.toKey)(name);
|
|
283
|
+
const taken = RESERVED_BINDINGS.has(binding) ||
|
|
284
|
+
[...animations.values()].includes(binding);
|
|
285
|
+
if (!IDENTIFIER.test(binding) || taken) {
|
|
286
|
+
reports.push({
|
|
287
|
+
line: atRule.source?.start?.line ?? 0,
|
|
288
|
+
column: atRule.source?.start?.column ?? 0,
|
|
289
|
+
kind: 'unsupported-keyframes',
|
|
290
|
+
source: `@${atRule.name} ${atRule.params}`,
|
|
291
|
+
hint: `\`${name}\` cannot be written as a \`css.keyframes\` binding. Rename it, then run again.`,
|
|
292
|
+
});
|
|
293
|
+
atRule.remove();
|
|
294
|
+
return;
|
|
295
|
+
}
|
|
296
|
+
const node = emptyNode();
|
|
297
|
+
atRule.each((child) => {
|
|
298
|
+
if (child.type !== 'rule')
|
|
299
|
+
return;
|
|
300
|
+
const step = nest(node, child.selectors.join(', '));
|
|
301
|
+
child.each((declaration) => {
|
|
302
|
+
if (declaration.type !== 'decl')
|
|
303
|
+
return;
|
|
304
|
+
step.decls.push([
|
|
305
|
+
(0, exports.toProperty)(declaration.prop),
|
|
306
|
+
(0, exports.toValue)(declaration.value),
|
|
307
|
+
]);
|
|
308
|
+
});
|
|
309
|
+
});
|
|
310
|
+
animations.set(name, binding);
|
|
311
|
+
animationNodes.set(binding, node);
|
|
312
|
+
atRule.remove();
|
|
313
|
+
});
|
|
314
|
+
const animationDeclarations = (declaration) => {
|
|
315
|
+
if (animations.size === 0)
|
|
316
|
+
return null;
|
|
317
|
+
const value = declaration.value.trim();
|
|
318
|
+
if (declaration.prop === 'animation-name') {
|
|
319
|
+
const binding = animations.get(value);
|
|
320
|
+
if (!binding)
|
|
321
|
+
return null;
|
|
322
|
+
reachedAnimations.add(binding);
|
|
323
|
+
return [['animation-name', { raw: binding }]];
|
|
324
|
+
}
|
|
325
|
+
if (declaration.prop !== 'animation')
|
|
326
|
+
return null;
|
|
327
|
+
const tokens = words(value);
|
|
328
|
+
const named = tokens?.filter((token) => animations.has(token)) ?? [];
|
|
329
|
+
if (!tokens || named.length === 0) {
|
|
330
|
+
const reads = (words(value.replace(/,/g, ' ')) ?? []).some((token) => animations.has(token));
|
|
331
|
+
if (reads)
|
|
332
|
+
report(declaration, 'unsupported-animation', 'Only a single `animation` naming one local `@keyframes` is split into longhands. Write the longhands instead.');
|
|
333
|
+
return null;
|
|
334
|
+
}
|
|
335
|
+
const written = new Map();
|
|
336
|
+
for (const token of tokens) {
|
|
337
|
+
if (animations.has(token)) {
|
|
338
|
+
if (written.has('animation-name'))
|
|
339
|
+
return null;
|
|
340
|
+
written.set('animation-name', { raw: animations.get(token) });
|
|
341
|
+
continue;
|
|
342
|
+
}
|
|
343
|
+
if (TIME.test(token)) {
|
|
344
|
+
const property = written.has('animation-duration')
|
|
345
|
+
? 'animation-delay'
|
|
346
|
+
: 'animation-duration';
|
|
347
|
+
if (written.has(property))
|
|
348
|
+
return null;
|
|
349
|
+
written.set(property, token);
|
|
350
|
+
continue;
|
|
351
|
+
}
|
|
352
|
+
const part = ANIMATION_PARTS.find(([, pattern]) => pattern.test(token));
|
|
353
|
+
const property = part
|
|
354
|
+
? part[0].replace(/([A-Z])/g, '-$1').toLowerCase()
|
|
355
|
+
: undefined;
|
|
356
|
+
if (!property || written.has(property)) {
|
|
357
|
+
report(declaration, 'unsupported-animation', `\`${token}\` was not recognised as an \`animation\` component, so the shorthand was left as it was written.`);
|
|
358
|
+
return null;
|
|
359
|
+
}
|
|
360
|
+
written.set(property, token);
|
|
361
|
+
}
|
|
362
|
+
reachedAnimations.add(written.get('animation-name').raw);
|
|
363
|
+
return [...written];
|
|
364
|
+
};
|
|
236
365
|
root.walkRules((rule) => {
|
|
237
366
|
for (const selector of rule.selectors) {
|
|
238
367
|
const read = readSelector(selector);
|
|
@@ -302,18 +431,23 @@ function convertStylesheet(css) {
|
|
|
302
431
|
composes[target.key] = (composes[target.key] ?? []).concat(name.split(/\s+/).map(exports.toKey));
|
|
303
432
|
return;
|
|
304
433
|
}
|
|
305
|
-
const
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
434
|
+
const expanded = animationDeclarations(child) ?? [
|
|
435
|
+
[child.prop, (0, exports.toValue)(child.value)],
|
|
436
|
+
];
|
|
437
|
+
for (const [name, value] of expanded) {
|
|
438
|
+
const property = (0, exports.toProperty)(name);
|
|
439
|
+
props.add(`${where}::${property}`);
|
|
440
|
+
mine.push({
|
|
441
|
+
property: name,
|
|
442
|
+
suffix: target.pseudo,
|
|
443
|
+
rank: (0, cascade_1.rankOf)(name, conditional),
|
|
444
|
+
place: wrote++,
|
|
445
|
+
});
|
|
446
|
+
const held = node.decls.findIndex(([held]) => held === property);
|
|
447
|
+
if (held !== -1)
|
|
448
|
+
node.decls.splice(held, 1);
|
|
449
|
+
node.decls.push([property, value]);
|
|
450
|
+
}
|
|
317
451
|
});
|
|
318
452
|
}
|
|
319
453
|
});
|
|
@@ -360,9 +494,24 @@ function convertStylesheet(css) {
|
|
|
360
494
|
return ` ${key}: (${params}) => ({\n${serialise(node, ' ')}\n }),`;
|
|
361
495
|
})
|
|
362
496
|
.join('\n');
|
|
497
|
+
const bindings = [...animationNodes]
|
|
498
|
+
.filter(([binding]) => reachedAnimations.has(binding))
|
|
499
|
+
.map(([binding, node]) => `const ${binding} = css.keyframes({\n${serialise(node, ' ')}\n});\n\n`)
|
|
500
|
+
.join('');
|
|
501
|
+
for (const [name, binding] of animations) {
|
|
502
|
+
if (reachedAnimations.has(binding))
|
|
503
|
+
continue;
|
|
504
|
+
reports.push({
|
|
505
|
+
line: 0,
|
|
506
|
+
column: 0,
|
|
507
|
+
kind: 'unused-keyframes',
|
|
508
|
+
source: `@keyframes ${name}`,
|
|
509
|
+
hint: `Nothing in the stylesheet reads \`${name}\`, so it was not written.`,
|
|
510
|
+
});
|
|
511
|
+
}
|
|
363
512
|
const code = `import * as css from '@plumeria/core';
|
|
364
513
|
|
|
365
|
-
export const styles = css.create({
|
|
514
|
+
${bindings}export const styles = css.create({
|
|
366
515
|
${body}
|
|
367
516
|
});
|
|
368
517
|
`;
|
|
@@ -52,6 +52,7 @@ export declare const compositionSignature: (parts: {
|
|
|
52
52
|
export interface ThemeExtraction {
|
|
53
53
|
bindings: Record<string, Record<string, string>>;
|
|
54
54
|
globalCss: string;
|
|
55
|
+
cssByBinding: Record<string, string>;
|
|
55
56
|
reports: PlumeriaReport[];
|
|
56
57
|
}
|
|
57
58
|
export interface StaticExtraction {
|
|
@@ -63,6 +64,7 @@ export interface AnimationExtraction {
|
|
|
63
64
|
keyframes: string[];
|
|
64
65
|
viewTransitions: string[];
|
|
65
66
|
globalCss: string;
|
|
67
|
+
cssByBinding: Record<string, string>;
|
|
66
68
|
reports: PlumeriaReport[];
|
|
67
69
|
}
|
|
68
70
|
export declare const __private: {
|
|
@@ -202,6 +202,7 @@ const readThemes = (ast) => {
|
|
|
202
202
|
const values = new Map();
|
|
203
203
|
const reports = [];
|
|
204
204
|
const globalRules = [];
|
|
205
|
+
const cssByBinding = {};
|
|
205
206
|
const report = (node, kind, hint) => {
|
|
206
207
|
reports.push({
|
|
207
208
|
line: node.loc.start.line,
|
|
@@ -251,7 +252,7 @@ const readThemes = (ast) => {
|
|
|
251
252
|
continue;
|
|
252
253
|
}
|
|
253
254
|
values.set(declaration.id.name, references);
|
|
254
|
-
|
|
255
|
+
const rules = [`:where(:root) {\n${rootDeclarations.join('\n')}\n}`];
|
|
255
256
|
const target = selector.startsWith('@') ||
|
|
256
257
|
selector.startsWith('.') ||
|
|
257
258
|
selector.startsWith('#') ||
|
|
@@ -260,18 +261,21 @@ const readThemes = (ast) => {
|
|
|
260
261
|
? selector
|
|
261
262
|
: `.${selector}`;
|
|
262
263
|
if ((0, zss_engine_1.isAtRule)(target)) {
|
|
263
|
-
|
|
264
|
+
rules.push(`${target} {\n :where(:root) {\n${themeDeclarations
|
|
264
265
|
.map((line) => ` ${line}`)
|
|
265
266
|
.join('\n')}\n }\n}`);
|
|
266
267
|
}
|
|
267
268
|
else {
|
|
268
|
-
|
|
269
|
+
rules.push(`${target} {\n${themeDeclarations.join('\n')}\n}`);
|
|
269
270
|
}
|
|
271
|
+
cssByBinding[declaration.id.name] = rules.join('\n\n');
|
|
272
|
+
globalRules.push(...rules);
|
|
270
273
|
}
|
|
271
274
|
}
|
|
272
275
|
return {
|
|
273
276
|
bindings: Object.fromEntries(values),
|
|
274
277
|
globalCss: globalRules.length > 0 ? `${globalRules.join('\n\n')}\n` : '',
|
|
278
|
+
cssByBinding,
|
|
275
279
|
reports,
|
|
276
280
|
};
|
|
277
281
|
};
|
|
@@ -348,6 +352,7 @@ function extractPlumeriaAnimations(source, externalValues = {}) {
|
|
|
348
352
|
const bindings = {};
|
|
349
353
|
const reports = [];
|
|
350
354
|
const rules = [];
|
|
355
|
+
const cssByBinding = {};
|
|
351
356
|
const keyframes = [];
|
|
352
357
|
const viewTransitions = [];
|
|
353
358
|
const report = (node, kind, hint) => {
|
|
@@ -378,7 +383,9 @@ function extractPlumeriaAnimations(source, externalValues = {}) {
|
|
|
378
383
|
values.set(declaration.id.name, name);
|
|
379
384
|
bindings[declaration.id.name] = name;
|
|
380
385
|
keyframes.push(declaration.id.name);
|
|
381
|
-
|
|
386
|
+
const rule = emitGlobalObject(`@keyframes ${name}`, object);
|
|
387
|
+
cssByBinding[declaration.id.name] = rule;
|
|
388
|
+
rules.push(rule);
|
|
382
389
|
}
|
|
383
390
|
for (const declaration of declarations) {
|
|
384
391
|
if (declaration.id.type !== 'Identifier' ||
|
|
@@ -394,19 +401,24 @@ function extractPlumeriaAnimations(source, externalValues = {}) {
|
|
|
394
401
|
values.set(declaration.id.name, name);
|
|
395
402
|
bindings[declaration.id.name] = name;
|
|
396
403
|
viewTransitions.push(declaration.id.name);
|
|
404
|
+
const parts = [];
|
|
397
405
|
for (const part of ['group', 'imagePair', 'old', 'new']) {
|
|
398
406
|
const style = object[part];
|
|
399
407
|
if (style && typeof style === 'object') {
|
|
400
408
|
const pseudoPart = part === 'imagePair' ? 'image-pair' : part;
|
|
401
|
-
|
|
409
|
+
parts.push(emitGlobalObject(`::view-transition-${pseudoPart}(${name})`, style));
|
|
402
410
|
}
|
|
403
411
|
}
|
|
412
|
+
if (parts.length > 0)
|
|
413
|
+
cssByBinding[declaration.id.name] = parts.join('\n\n');
|
|
414
|
+
rules.push(...parts);
|
|
404
415
|
}
|
|
405
416
|
return {
|
|
406
417
|
bindings,
|
|
407
418
|
keyframes,
|
|
408
419
|
viewTransitions,
|
|
409
420
|
globalCss: rules.length > 0 ? `${rules.join('\n\n')}\n` : '',
|
|
421
|
+
cssByBinding,
|
|
410
422
|
reports,
|
|
411
423
|
};
|
|
412
424
|
}
|
package/package.json
CHANGED