@symbiote-native/vue 3.3.0 → 3.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/build/components/flat-list/index.d.ts +4 -3
- package/build/components/flat-list/index.js +18 -21
- package/build/components/input-accessory-view-props.d.ts +4 -4
- package/build/components/keyboard-avoiding-view.d.ts +11 -3
- package/build/components/keyboard-avoiding-view.js +38 -69
- package/build/components/layout-conformance-props.d.ts +4 -0
- package/build/components/layout-conformance-props.js +1 -0
- package/build/components/modal/index.d.ts +18 -5
- package/build/components/modal/index.js +53 -24
- package/build/components/refresh-control-props.d.ts +7 -7
- package/build/components/scroll-view/scroll-view-props.d.ts +8 -5
- package/build/components/section-list/index.d.ts +1 -1
- package/build/components/section-list/index.js +2 -6
- package/build/components/text-props.d.ts +3 -3
- package/build/components/touchable-props.d.ts +6 -6
- package/build/components/virtualized-list/index.d.ts +5 -75
- package/build/components/virtualized-list/index.js +130 -558
- package/build/components/virtualized-list/list-children.d.ts +18 -0
- package/build/components/virtualized-list/list-children.js +81 -0
- package/build/components/virtualized-list/list-elements.d.ts +7 -0
- package/build/components/virtualized-list/list-elements.js +29 -0
- package/build/components/virtualized-list/list-scroll-props.d.ts +20 -0
- package/build/components/virtualized-list/list-scroll-props.js +9 -0
- package/build/components/virtualized-list/narrow-props.d.ts +79 -0
- package/build/components/virtualized-list/narrow-props.js +89 -0
- package/build/components/virtualized-list/nested-scope.d.ts +3 -0
- package/build/components/virtualized-list/nested-scope.js +12 -0
- package/build/components/virtualized-list/user-handlers.d.ts +3 -0
- package/build/components/virtualized-list/user-handlers.js +24 -0
- package/build/components/virtualized-list/virtualized-list-props.d.ts +82 -0
- package/build/components/virtualized-list/virtualized-list-props.js +41 -0
- package/build/components/virtualized-section-list/index.d.ts +20 -5
- package/build/components/virtualized-section-list/index.js +81 -95
- package/build/components/virtualized-section-list/section-item-cell.d.ts +18 -0
- package/build/components/virtualized-section-list/section-item-cell.js +52 -0
- package/build/composables/use-animated-value.d.ts +7 -0
- package/build/composables/use-animated-value.js +14 -0
- package/build/composables/use-color-scheme.d.ts +1 -1
- package/build/create-transition/index.d.ts +2 -2
- package/build/create-transition/index.js +1 -1
- package/build/index.d.ts +13 -4
- package/build/index.js +16 -31
- package/build/modules/animated/index.d.ts +54 -41
- package/build/modules/animated/index.js +2 -26
- package/build/modules/app-registry/index.d.ts +3 -3
- package/build/modules/app-registry/index.js +2 -2
- package/build/modules/status-bar.js +22 -24
- package/metro-vue-transformer.cjs +165 -224
- package/package.json +6 -6
|
@@ -1,14 +1,11 @@
|
|
|
1
|
-
// Metro babel transformer that
|
|
2
|
-
// plugin
|
|
3
|
-
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
|
|
7
|
-
//
|
|
8
|
-
//
|
|
9
|
-
// Ships as a package-level export (`@symbiote-native/vue/metro-vue-transformer`) rather than
|
|
10
|
-
// living in each consuming app: a consumer's own metro.config.js just points
|
|
11
|
-
// babelTransformerPath at it.
|
|
1
|
+
// Metro babel transformer that reads Vue SFCs (.vue), the Metro twin of @vitejs/plugin-vue
|
|
2
|
+
// Metro has no Vue plugin, so the single-pass compile happens here before RN's own transformer
|
|
3
|
+
|
|
4
|
+
// Imports of 'vue' go to `@symbiote-native/vue/runtime-helpers`, not `@vue/runtime-core`
|
|
5
|
+
// That shim supplies our own `vShow`, which compiled v-show imports by name
|
|
6
|
+
|
|
7
|
+
// Ships as the package export `@symbiote-native/vue/metro-vue-transformer`
|
|
8
|
+
// A consumer's metro.config.js points `babelTransformerPath` at it
|
|
12
9
|
|
|
13
10
|
const nodeFs = require('fs');
|
|
14
11
|
const {
|
|
@@ -23,32 +20,26 @@ const {
|
|
|
23
20
|
createSimpleExpression,
|
|
24
21
|
} = require('@vue/compiler-core');
|
|
25
22
|
|
|
26
|
-
// A bare-specifier type import
|
|
27
|
-
//
|
|
28
|
-
// option only reads paths it's already given, it can't resolve. registerTS (the same hook
|
|
29
|
-
// @vitejs/plugin-vue and vue-tsc use) hands the compiler a lazy `typescript` loader that resolves
|
|
30
|
-
// via `ts.resolveModuleName` and self-supplies `ts.sys` as the fs fallback.
|
|
23
|
+
// A bare-specifier type import needs node_modules resolution that the `fs` option cannot do
|
|
24
|
+
// `registerTS` gives the compiler a lazy `typescript` loader that resolves it, like plugin-vue
|
|
31
25
|
registerTS(() => require('typescript'));
|
|
32
|
-
// Required directly
|
|
33
|
-
// so it resolves from this package's own node_modules under pnpm
|
|
26
|
+
// Required directly, not via the ./metro-css-parser subpath that exists for CONSUMERS
|
|
27
|
+
// so it resolves from this package's own node_modules under pnpm
|
|
34
28
|
const {
|
|
35
29
|
compile: compilePreprocessor,
|
|
36
|
-
compileCssFile,
|
|
37
30
|
compileCssModule,
|
|
38
31
|
compileCssToRules,
|
|
39
32
|
compileScopedCss,
|
|
33
|
+
createCssMetroTransformer,
|
|
40
34
|
hashFilePath,
|
|
41
|
-
|
|
35
|
+
relabelForUpstream,
|
|
42
36
|
resolveUpstreamTransformer,
|
|
43
37
|
} = require('@symbiote-native/css-parser');
|
|
44
38
|
|
|
45
39
|
const upstreamTransformer = resolveUpstreamTransformer();
|
|
46
40
|
|
|
47
|
-
// compileScript
|
|
48
|
-
//
|
|
49
|
-
// @vue/compiler-sfc doesn't auto-detect without registerTS) throws "No fs option provided ...
|
|
50
|
-
// in non-Node environment". Wiring Node's own `fs` straight through is simpler than a second
|
|
51
|
-
// registerTS-style loader.
|
|
41
|
+
// `compileScript` reads other files to resolve a type-only `defineProps<ISomeProps>()`
|
|
42
|
+
// Metro's worker is not detected as Node, so without this it throws "No fs option provided"
|
|
52
43
|
const compileScriptFs = {
|
|
53
44
|
fileExists: file => nodeFs.existsSync(file),
|
|
54
45
|
readFile: file => {
|
|
@@ -60,29 +51,23 @@ const compileScriptFs = {
|
|
|
60
51
|
},
|
|
61
52
|
};
|
|
62
53
|
|
|
63
|
-
//
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
//
|
|
73
|
-
//
|
|
74
|
-
|
|
75
|
-
//
|
|
76
|
-
//
|
|
77
|
-
|
|
78
|
-
//
|
|
79
|
-
//
|
|
80
|
-
// our transform runs, Vue's own transformExpression has already turned prop.exp into either a
|
|
81
|
-
// COMPOUND_EXPRESSION (type 8) or, for a bare identifier binding, a SIMPLE_EXPRESSION (type 4).
|
|
82
|
-
// createCompoundExpression wraps the original exp node as-is inside
|
|
83
|
-
// renameClassTokens(<original>, __scopedClassNames), so codegen reproduces the original
|
|
84
|
-
// expression unchanged - no per-shape branching needed, and a fully opaque runtime value
|
|
85
|
-
// (`:class="dynamicClass"`) still resolves against the same map at runtime.
|
|
54
|
+
// Shared with ./babel-jsx.cjs: Vue's two compilers must give one answer to element-vs-component
|
|
55
|
+
const INTRINSIC_TAGS = require('./intrinsic-tags.cjs');
|
|
56
|
+
|
|
57
|
+
const isIntrinsicTag = tag => INTRINSIC_TAGS.has(tag);
|
|
58
|
+
|
|
59
|
+
const CLASS_PROP = 'class';
|
|
60
|
+
const IMPORT_DECLARATION = 'ImportDeclaration';
|
|
61
|
+
const PLAIN_CSS_LANG = 'css';
|
|
62
|
+
|
|
63
|
+
// Rewrites every `class` and `:class` binding to the name lightningcss renamed it to
|
|
64
|
+
// It works on the AST because Vue merges a static and a dynamic class into one codegen entry
|
|
65
|
+
|
|
66
|
+
// `renames` is the `compileScopedCss` map of authored name to renamed name
|
|
67
|
+
// A token it lacks belongs to another file (App.css, `:global()`, a parent) and passes through
|
|
68
|
+
|
|
69
|
+
// A dynamic `:class` is wrapped as `__scopeClass(<original>, __scopedClassNames)` by Vue's codegen
|
|
70
|
+
// So any expression shape, even an opaque runtime value, resolves against the same map
|
|
86
71
|
function createScopeClassNodeTransform(renames) {
|
|
87
72
|
return function scopeClassNodeTransform(node) {
|
|
88
73
|
if (node.type !== 1 /* NodeTypes.ELEMENT */) return;
|
|
@@ -90,7 +75,7 @@ function createScopeClassNodeTransform(renames) {
|
|
|
90
75
|
for (const prop of node.props) {
|
|
91
76
|
if (
|
|
92
77
|
prop.type === 6 /* NodeTypes.ATTRIBUTE */ &&
|
|
93
|
-
prop.name ===
|
|
78
|
+
prop.name === CLASS_PROP &&
|
|
94
79
|
prop.value
|
|
95
80
|
) {
|
|
96
81
|
prop.value.content = prop.value.content
|
|
@@ -101,14 +86,7 @@ function createScopeClassNodeTransform(renames) {
|
|
|
101
86
|
continue;
|
|
102
87
|
}
|
|
103
88
|
|
|
104
|
-
if (
|
|
105
|
-
prop.type === 7 /* NodeTypes.DIRECTIVE */ &&
|
|
106
|
-
prop.name === 'bind' &&
|
|
107
|
-
prop.arg &&
|
|
108
|
-
prop.arg.type === 4 /* NodeTypes.SIMPLE_EXPRESSION */ &&
|
|
109
|
-
prop.arg.content === 'class' &&
|
|
110
|
-
prop.exp
|
|
111
|
-
) {
|
|
89
|
+
if (isClassBinding(prop)) {
|
|
112
90
|
prop.exp = createCompoundExpression(
|
|
113
91
|
['__scopeClass(', prop.exp, ', __scopedClassNames)'],
|
|
114
92
|
prop.exp.loc,
|
|
@@ -118,14 +96,23 @@ function createScopeClassNodeTransform(renames) {
|
|
|
118
96
|
};
|
|
119
97
|
}
|
|
120
98
|
|
|
121
|
-
//
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
99
|
+
// By now Vue's `transformExpression` made `prop.exp` a compound or a simple expression
|
|
100
|
+
function isClassBinding(prop) {
|
|
101
|
+
return (
|
|
102
|
+
prop.type === 7 /* NodeTypes.DIRECTIVE */ &&
|
|
103
|
+
prop.name === 'bind' &&
|
|
104
|
+
prop.arg &&
|
|
105
|
+
prop.arg.type === 4 /* NodeTypes.SIMPLE_EXPRESSION */ &&
|
|
106
|
+
prop.arg.content === CLASS_PROP &&
|
|
107
|
+
Boolean(prop.exp)
|
|
108
|
+
);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// The parser's `condense()` collapses multi-line whitespace to one space but never trims it
|
|
112
|
+
// `<text>\n plain\n</text>` compiles to " plain ", a real space on the rendered RN Text
|
|
113
|
+
|
|
114
|
+
// Babel's JSX whitespace cleaner trims it for every other adapter, so we do it once per template
|
|
115
|
+
// Only a text node whose source crosses a line break is touched, as Babel does
|
|
129
116
|
function trimReflowedTextNodeTransform(node) {
|
|
130
117
|
if (node.type !== 1 /* NodeTypes.ELEMENT */ || node.children.length === 0)
|
|
131
118
|
return;
|
|
@@ -147,12 +134,9 @@ function trimReflowedTextNodeTransform(node) {
|
|
|
147
134
|
}
|
|
148
135
|
}
|
|
149
136
|
|
|
150
|
-
// Shared with ./babel-jsx.cjs: Vue's two compilers must give one answer to element-vs-component.
|
|
151
|
-
const INTRINSIC_TAGS = require('./intrinsic-tags.cjs');
|
|
152
|
-
|
|
153
137
|
const SYMBIOTE_SOURCE_PREFIX = '@symbiote-native/';
|
|
154
138
|
|
|
155
|
-
// Local names the file imports from any `@symbiote-native/*` package (`FlatList`, `Animated`)
|
|
139
|
+
// Local names the file imports from any `@symbiote-native/*` package (`FlatList`, `Animated`)
|
|
156
140
|
function symbioteImportsOf(descriptor) {
|
|
157
141
|
const locals = new Set();
|
|
158
142
|
for (const block of [descriptor.script, descriptor.scriptSetup]) {
|
|
@@ -161,7 +145,7 @@ function symbioteImportsOf(descriptor) {
|
|
|
161
145
|
const program = babelParse(block.content, { sourceType: 'module', plugins }).program;
|
|
162
146
|
for (const statement of program.body) {
|
|
163
147
|
if (
|
|
164
|
-
statement.type !==
|
|
148
|
+
statement.type !== IMPORT_DECLARATION ||
|
|
165
149
|
!statement.source.value.startsWith(SYMBIOTE_SOURCE_PREFIX)
|
|
166
150
|
)
|
|
167
151
|
continue;
|
|
@@ -171,9 +155,8 @@ function symbioteImportsOf(descriptor) {
|
|
|
171
155
|
return locals;
|
|
172
156
|
}
|
|
173
157
|
|
|
174
|
-
// Vue casts a bare attribute to `true` only for a
|
|
175
|
-
//
|
|
176
|
-
// `String cannot be cast to Boolean`. The app's own components keep Vue's semantics.
|
|
158
|
+
// Vue casts a bare attribute to `true` only for a declared Boolean prop, and our tags declare none
|
|
159
|
+
// So `nested-scroll-enabled` reached Android as "" and threw `String cannot be cast to Boolean`
|
|
177
160
|
function createBareBooleanNodeTransform(symbioteLocals) {
|
|
178
161
|
const isOurs = tag =>
|
|
179
162
|
INTRINSIC_TAGS.has(tag) || symbioteLocals.has(tag.split('.')[0]);
|
|
@@ -195,32 +178,26 @@ function createBareBooleanNodeTransform(symbioteLocals) {
|
|
|
195
178
|
};
|
|
196
179
|
}
|
|
197
180
|
|
|
198
|
-
|
|
199
|
-
//
|
|
200
|
-
// scoped styles - built on css-parser's shared hashFilePath so the algorithm isn't duplicated
|
|
201
|
-
// against the standalone .module.css compiler's identical need.
|
|
181
|
+
// A short, stable id per file, used as the SFC scope id whether or not it has scoped styles
|
|
182
|
+
// Built on css-parser's `hashFilePath`, shared with the standalone .module.css compiler
|
|
202
183
|
function scopeIdFor(filename) {
|
|
203
184
|
return 'data-v-' + hashFilePath(filename);
|
|
204
185
|
}
|
|
205
186
|
|
|
206
|
-
// The local the compiled component lands in when a <style module> block
|
|
207
|
-
//
|
|
187
|
+
// The local the compiled component lands in when a <style module> block hangs `__cssModules` on it
|
|
188
|
+
// It is `genDefaultAs` of `compileScript`, the same role as plugin-vue's `_sfc_main`
|
|
208
189
|
const SFC_COMPONENT_VAR = '__sfc__';
|
|
209
190
|
|
|
210
|
-
//
|
|
211
|
-
//
|
|
212
|
-
// reordering equally-specific rules. Renumbering on append gives the file one monotonic sequence,
|
|
213
|
-
// which is what a single stylesheet holding the concatenated blocks would have produced.
|
|
191
|
+
// Each block restarts its rules at order 0, so a later one would tie an earlier one
|
|
192
|
+
// Renumbering on append gives the file one monotonic sequence, as one stylesheet would
|
|
214
193
|
function appendRules(target, rules) {
|
|
215
194
|
for (const rule of rules) {
|
|
216
195
|
target.push({ ...rule, order: target.length });
|
|
217
196
|
}
|
|
218
197
|
}
|
|
219
198
|
|
|
220
|
-
// `combinators` is compile-time-only
|
|
221
|
-
//
|
|
222
|
-
// serializeRules (core/css-parser/src/metro-css-module/index.ts), which does this for a standalone
|
|
223
|
-
// style file.
|
|
199
|
+
// `combinators` is compile-time-only, as the registry matches by token subset, so it is not shipped
|
|
200
|
+
// Twin of css-parser's `serializeRules` (metro-css-module/index.ts) for a standalone style file
|
|
224
201
|
function serializeRules(rules) {
|
|
225
202
|
return JSON.stringify(
|
|
226
203
|
rules.map(({ tokens, specificity, order, style }) => ({
|
|
@@ -232,9 +209,8 @@ function serializeRules(rules) {
|
|
|
232
209
|
);
|
|
233
210
|
}
|
|
234
211
|
|
|
235
|
-
//
|
|
236
|
-
//
|
|
237
|
-
// lookup rather than reusing detectLanguage(), which is extension-keyed.
|
|
212
|
+
// A style block's `lang` names a preprocessor (`lang="scss"`), where a file has an extension
|
|
213
|
+
// So this is its own small lookup, not `detectLanguage()`, which is extension-keyed
|
|
238
214
|
const SFC_STYLE_LANG_TO_PREPROCESSOR = new Map([
|
|
239
215
|
['scss', 'scss'],
|
|
240
216
|
['sass', 'scss'],
|
|
@@ -242,10 +218,9 @@ const SFC_STYLE_LANG_TO_PREPROCESSOR = new Map([
|
|
|
242
218
|
['stylus', 'stylus'],
|
|
243
219
|
]);
|
|
244
220
|
|
|
245
|
-
// Reduces one <style> block
|
|
246
|
-
// through unchanged. Anything outside the four recognized preprocessor langs throws.
|
|
221
|
+
// Reduces one <style> block to plain CSS: lang-less and `lang="css"` pass through, others compile
|
|
247
222
|
async function compileStyleBlockContent(style, filename) {
|
|
248
|
-
if (style.lang == null || style.lang ===
|
|
223
|
+
if (style.lang == null || style.lang === PLAIN_CSS_LANG) return style.content;
|
|
249
224
|
|
|
250
225
|
const preprocessorLang = SFC_STYLE_LANG_TO_PREPROCESSOR.get(style.lang);
|
|
251
226
|
if (!preprocessorLang) {
|
|
@@ -254,38 +229,22 @@ async function compileStyleBlockContent(style, filename) {
|
|
|
254
229
|
);
|
|
255
230
|
}
|
|
256
231
|
|
|
257
|
-
//
|
|
258
|
-
//
|
|
259
|
-
// style block has no file of its own, so a synthetic `.sass`-suffixed path is the only way to
|
|
260
|
-
// select the indented syntax. Every other preprocessor only uses the path for relative-import
|
|
261
|
-
// resolution (dirname), where the real .vue file's own path is correct as-is.
|
|
232
|
+
// `.sass` and `.scss` share one compiler entry that picks the syntax by file extension
|
|
233
|
+
// An inline block has no file, so a synthetic `.sass` path selects the indented syntax
|
|
262
234
|
const syntheticPath = style.lang === 'sass' ? `${filename}.sass` : filename;
|
|
263
235
|
return compilePreprocessor(style.content, preprocessorLang, syntheticPath);
|
|
264
236
|
}
|
|
265
237
|
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
// tree and generation dies with `Codegen node is missing for element/if/for node`.
|
|
271
|
-
//
|
|
272
|
-
// Metro compiles each file once per build, so the cache buys this caller nothing; the only place
|
|
273
|
-
// it was ever exercised is css-parser's golden-corpus determinism check, which compiles one
|
|
274
|
-
// source twice on purpose. That is the ONLY test anywhere that can see this, which is why a
|
|
275
|
-
// transform bug surfaced as a failure in another package's corpus.
|
|
276
|
-
//
|
|
277
|
-
// `clear()` rather than deleting our one key: the key is `source + JSON.stringify(options)`
|
|
278
|
-
// (`@vue/shared`'s genCacheKey), so reproducing it here would couple this file to an internal
|
|
279
|
-
// format across every Vue bump. Clearing a pure memo is always safe — the only cost is a
|
|
280
|
-
// re-parse, which is what we need anyway.
|
|
281
|
-
|
|
238
|
+
// `parse` memoizes its descriptor and the scope-class transform mutates its AST in place
|
|
239
|
+
// A second compile of one source would walk a half-transformed tree and die in codegen
|
|
240
|
+
// `clear()` rather than deleting our key, as the key format is internal to `@vue/shared`
|
|
241
|
+
function parseDescriptor(src, filename) {
|
|
282
242
|
parseCache.clear();
|
|
283
|
-
//
|
|
284
|
-
//
|
|
285
|
-
// is fixed. Without the answer here, `<pressable>` stays a component: `resolveComponent(...)`.
|
|
243
|
+
// The parser decides element vs component, and `compileScript` reuses that parsed AST
|
|
244
|
+
// Without `isCustomElement` here, `<pressable>` stays a component: `resolveComponent(...)`
|
|
286
245
|
const { descriptor, errors } = parse(src, {
|
|
287
246
|
filename,
|
|
288
|
-
templateParseOptions: { isCustomElement:
|
|
247
|
+
templateParseOptions: { isCustomElement: isIntrinsicTag },
|
|
289
248
|
});
|
|
290
249
|
if (errors && errors.length > 0) {
|
|
291
250
|
throw new Error(
|
|
@@ -297,124 +256,113 @@ async function compileSfc(src, filename) {
|
|
|
297
256
|
`Vue SFC ${filename} has no <script> / <script setup> block`,
|
|
298
257
|
);
|
|
299
258
|
}
|
|
259
|
+
return descriptor;
|
|
260
|
+
}
|
|
300
261
|
|
|
301
|
-
|
|
262
|
+
// `<style scoped>` and `<style module>` are one mechanism, lightningcss' CSS-Modules renaming
|
|
263
|
+
// Only the pattern differs, and a plain `<style>` is not renamed, so its classes register globally
|
|
264
|
+
|
|
265
|
+
// Renaming stands in for Vue's `data-v-hash` attribute, as there is no DOM to match one against
|
|
266
|
+
// `:global(...)` needs no handling here, since a name lightningcss did not rename is global
|
|
267
|
+
function compileSfcStyleBlock({ style, content, filename, scopeId }) {
|
|
268
|
+
if (style.module) {
|
|
269
|
+
const compiled = compileCssModule(content, filename);
|
|
270
|
+
const bindingName = typeof style.module === 'string' ? style.module : '$style';
|
|
271
|
+
return { rules: compiled.rules, binding: [bindingName, compiled.classMap] };
|
|
272
|
+
}
|
|
273
|
+
if (style.scoped) {
|
|
274
|
+
const compiled = compileScopedCss(content, {
|
|
275
|
+
filename,
|
|
276
|
+
pattern: `[local]__${scopeId}`,
|
|
277
|
+
});
|
|
278
|
+
return { rules: compiled.rules, names: compiled.names };
|
|
279
|
+
}
|
|
280
|
+
return { rules: compileCssToRules(content, { filename }).rules };
|
|
281
|
+
}
|
|
302
282
|
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
//
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
// <style module> [local]__module__<hash> compileCssModule - the same call a standalone
|
|
311
|
-
// .module.css takes, so the two cannot diverge
|
|
312
|
-
// <style> not renamed at all compileCssToRules, classes register globally
|
|
313
|
-
//
|
|
314
|
-
// Renaming is our equivalent of Vue's `data-v-hash` ATTRIBUTE (we have no DOM and no
|
|
315
|
-
// attribute-selector matching, so a scope can only be expressed in the name), and `:global(...)`
|
|
316
|
-
// needs no handling here at all: a name lightningcss did not rename is global by definition.
|
|
317
|
-
//
|
|
318
|
-
// Multiple blocks cascade last-block-wins, same as CSS, after each block is scoped
|
|
319
|
-
// independently — carried by the rules' `order`, which appendRules renumbers across blocks.
|
|
320
|
-
//
|
|
321
|
-
// A module block's classes are NEVER auto-applied to a literal class="..." (opt-in via $style.x
|
|
322
|
-
// only), so they stay out of the template rewriter's name map; its output is the name->scopedName
|
|
323
|
-
// map instead, emitted as a preamble const AND attached to the component as __cssModules.
|
|
283
|
+
// Blocks cascade last-wins like CSS, carried by the rules' `order` that `appendRules` renumbers
|
|
284
|
+
// A module block's classes never apply to a literal `class`, only through `$style.x`
|
|
285
|
+
async function compileStyles(descriptor, filename, scopeId) {
|
|
286
|
+
// A preprocessor block is reduced to plain CSS first, as the renaming only ever sees plain CSS
|
|
287
|
+
const contents = await Promise.all(
|
|
288
|
+
descriptor.styles.map(style => compileStyleBlockContent(style, filename)),
|
|
289
|
+
);
|
|
324
290
|
const rules = [];
|
|
325
291
|
const scopedClassNames = new Map();
|
|
326
292
|
const cssModuleBindings = new Map();
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
293
|
+
descriptor.styles.forEach((style, index) => {
|
|
294
|
+
const block = compileSfcStyleBlock({
|
|
295
|
+
style,
|
|
296
|
+
content: contents[index],
|
|
297
|
+
filename,
|
|
298
|
+
scopeId,
|
|
299
|
+
});
|
|
300
|
+
appendRules(rules, block.rules);
|
|
301
|
+
for (const [name, renamed] of block.names ?? [])
|
|
302
|
+
scopedClassNames.set(name, renamed);
|
|
303
|
+
if (block.binding) {
|
|
304
|
+
const [bindingName, classMap] = block.binding;
|
|
338
305
|
cssModuleBindings.set(bindingName, {
|
|
339
306
|
...cssModuleBindings.get(bindingName),
|
|
340
|
-
...
|
|
341
|
-
});
|
|
342
|
-
} else if (style.scoped) {
|
|
343
|
-
const compiled = compileScopedCss(content, {
|
|
344
|
-
filename,
|
|
345
|
-
pattern: `[local]__${scopeId}`,
|
|
307
|
+
...classMap,
|
|
346
308
|
});
|
|
347
|
-
appendRules(rules, compiled.rules);
|
|
348
|
-
for (const [name, renamed] of compiled.names)
|
|
349
|
-
scopedClassNames.set(name, renamed);
|
|
350
|
-
} else {
|
|
351
|
-
appendRules(rules, compileCssToRules(content, { filename }).rules);
|
|
352
309
|
}
|
|
353
|
-
}
|
|
310
|
+
});
|
|
311
|
+
return { rules, scopedClassNames, cssModuleBindings };
|
|
312
|
+
}
|
|
354
313
|
|
|
355
|
-
|
|
356
|
-
|
|
314
|
+
// Scoped-class rewriting is skipped when nothing in the file is scoped, so it adds no runtime cost
|
|
315
|
+
function templateOptionsFor(descriptor, scopedClassNames) {
|
|
357
316
|
const nodeTransforms = [
|
|
358
317
|
trimReflowedTextNodeTransform,
|
|
359
318
|
createBareBooleanNodeTransform(symbioteImportsOf(descriptor)),
|
|
360
319
|
];
|
|
361
320
|
if (scopedClassNames.size > 0)
|
|
362
321
|
nodeTransforms.push(createScopeClassNodeTransform(scopedClassNames));
|
|
363
|
-
//
|
|
364
|
-
//
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
compilerOptions: {
|
|
368
|
-
...(nodeTransforms.length > 0 ? { nodeTransforms } : {}),
|
|
369
|
-
isCustomElement: tag => INTRINSIC_TAGS.has(tag),
|
|
370
|
-
},
|
|
371
|
-
};
|
|
322
|
+
// Every intrinsic, not just the imported ones: a narrower list leaves `<pressable>` a blank
|
|
323
|
+
// `resolveComponent(...)` and makes `<switch v-model>` a compile error
|
|
324
|
+
return { compilerOptions: { nodeTransforms, isCustomElement: isIntrinsicTag } };
|
|
325
|
+
}
|
|
372
326
|
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
// module> block can hang __cssModules off the component OPTIONS object before the module
|
|
378
|
-
// exports it (the same thing @vitejs/plugin-vue does). Vue resolves a template's `$style` /
|
|
379
|
-
// `classes` off `instance.type.__cssModules`, NOT off module scope, so without this the
|
|
380
|
-
// emitted const is unreachable from the template and `$style.card` throws at render. Only
|
|
381
|
-
// requested when a module block exists, so every other .vue file keeps identical output.
|
|
382
|
-
const hasCssModules = cssModuleBindings.size > 0;
|
|
327
|
+
// `inlineTemplate` folds the render fn into `setup()`, giving one module and one export default
|
|
328
|
+
// `genDefaultAs` makes it a const so `__cssModules` can hang off it, as Vue reads `$style` there
|
|
329
|
+
function compileComponentScript(descriptor, scopeId, styles) {
|
|
330
|
+
const hasCssModules = styles.cssModuleBindings.size > 0;
|
|
383
331
|
const compiled = compileScript(descriptor, {
|
|
384
332
|
id: scopeId,
|
|
385
333
|
inlineTemplate: true,
|
|
386
|
-
templateOptions,
|
|
334
|
+
templateOptions: templateOptionsFor(descriptor, styles.scopedClassNames),
|
|
387
335
|
fs: compileScriptFs,
|
|
388
336
|
...(hasCssModules ? { genDefaultAs: SFC_COMPONENT_VAR } : {}),
|
|
389
337
|
});
|
|
390
|
-
//
|
|
391
|
-
|
|
392
|
-
const code = compiled.content.replace(
|
|
338
|
+
// Every Vue import, injected helpers and the user's own, goes to the shim: no runtime-dom
|
|
339
|
+
return compiled.content.replace(
|
|
393
340
|
/from\s*(['"])vue\1/g,
|
|
394
341
|
'from "@symbiote-native/vue/runtime-helpers"',
|
|
395
342
|
);
|
|
343
|
+
}
|
|
396
344
|
|
|
345
|
+
function withStyleRegistration(code, { rules, scopedClassNames, cssModuleBindings }) {
|
|
346
|
+
const hasCssModules = cssModuleBindings.size > 0;
|
|
397
347
|
if (rules.length === 0 && !hasCssModules) return code;
|
|
398
348
|
|
|
399
|
-
// Only a scoped file needs the runtime rename helper and its per-file map
|
|
400
|
-
|
|
401
|
-
const engineImports =
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
: 'registerRules';
|
|
349
|
+
// Only a scoped file needs the runtime rename helper and its per-file map
|
|
350
|
+
const isScoped = scopedClassNames.size > 0;
|
|
351
|
+
const engineImports = isScoped
|
|
352
|
+
? 'registerRules, renameClassTokens as __scopeClass'
|
|
353
|
+
: 'registerRules';
|
|
405
354
|
|
|
406
355
|
const preamble = [
|
|
407
356
|
`import { ${engineImports} } from '@symbiote-native/engine';`,
|
|
408
357
|
`registerRules(${serializeRules(rules)});`,
|
|
409
358
|
];
|
|
410
|
-
if (
|
|
359
|
+
if (isScoped) {
|
|
411
360
|
preamble.push(
|
|
412
361
|
`const __scopedClassNames = ${JSON.stringify(Object.fromEntries(scopedClassNames))};`,
|
|
413
362
|
);
|
|
414
363
|
}
|
|
415
|
-
// Each <style module> binding
|
|
416
|
-
//
|
|
417
|
-
// <script setup> code itself; the __cssModules tail below is what makes the TEMPLATE see it.
|
|
364
|
+
// Each <style module> binding is a top-level const placed before the component, so <script setup>
|
|
365
|
+
// can close over it; the `__cssModules` tail below is what makes the TEMPLATE see it
|
|
418
366
|
for (const [bindingName, classMap] of cssModuleBindings) {
|
|
419
367
|
preamble.push(`const ${bindingName} = ${JSON.stringify(classMap)};`);
|
|
420
368
|
}
|
|
@@ -433,39 +381,32 @@ async function compileSfc(src, filename) {
|
|
|
433
381
|
return parts.join('\n') + '\n';
|
|
434
382
|
}
|
|
435
383
|
|
|
384
|
+
async function compileSfc(src, filename) {
|
|
385
|
+
const descriptor = parseDescriptor(src, filename);
|
|
386
|
+
const scopeId = scopeIdFor(filename);
|
|
387
|
+
const styles = await compileStyles(descriptor, filename, scopeId);
|
|
388
|
+
const code = compileComponentScript(descriptor, scopeId, styles);
|
|
389
|
+
return withStyleRegistration(code, styles);
|
|
390
|
+
}
|
|
391
|
+
|
|
436
392
|
// Exported separately from `transform` so tests can assert on the compiled SFC output
|
|
437
|
-
// (imports, injected `registerRules` call) without driving
|
|
393
|
+
// (imports, injected `registerRules` call) without driving RN's whole Babel preset
|
|
438
394
|
module.exports.compileSfc = compileSfc;
|
|
439
395
|
|
|
440
|
-
//
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
//
|
|
444
|
-
// content-hash-cached call.
|
|
396
|
+
// A standalone style file, not the inline `<style>` of a .vue, is compiled by this shared wrapper
|
|
397
|
+
const styleFileTransformer = createCssMetroTransformer(upstreamTransformer);
|
|
398
|
+
|
|
399
|
+
// Async throughout, as `compileSfc` awaits the style preprocessors and Metro awaits `transform`
|
|
445
400
|
module.exports.transform = async function transform(params) {
|
|
446
401
|
if (params.filename.endsWith('.vue')) {
|
|
447
402
|
const code = await compileSfc(params.src, params.filename);
|
|
448
|
-
// Re-label as .tsx so RN's transformer strips TS from
|
|
449
|
-
// tracks the real path separately
|
|
450
|
-
return upstreamTransformer.transform(
|
|
451
|
-
...params,
|
|
452
|
-
src: code,
|
|
453
|
-
filename: params.filename + '.tsx',
|
|
454
|
-
});
|
|
455
|
-
}
|
|
456
|
-
// A standalone style file (not a .vue file's inline <style> block) via the framework-agnostic
|
|
457
|
-
// compileCssFile path. isStyleFile recognizes .css/.scss/.sass/.less/.styl/.stylus (+ .module.* twins).
|
|
458
|
-
if (isStyleFile(params.filename)) {
|
|
459
|
-
const { code } = await compileCssFile(params.src, params.filename);
|
|
460
|
-
return upstreamTransformer.transform({
|
|
461
|
-
...params,
|
|
462
|
-
src: code,
|
|
463
|
-
filename: params.filename + '.js',
|
|
464
|
-
});
|
|
403
|
+
// Re-label as .tsx so RN's transformer strips TS from `<script setup lang="ts">`
|
|
404
|
+
// Metro tracks the real path separately
|
|
405
|
+
return upstreamTransformer.transform(relabelForUpstream(params, code, '.tsx'));
|
|
465
406
|
}
|
|
466
|
-
return
|
|
407
|
+
return styleFileTransformer.transform(params);
|
|
467
408
|
};
|
|
468
409
|
|
|
469
|
-
// Surface the upstream cache key so RN preset changes still bust Metro's cache
|
|
470
|
-
// step itself is invalidated by `--reset-cache`
|
|
410
|
+
// Surface the upstream cache key so RN preset changes still bust Metro's cache
|
|
411
|
+
// The SFC step itself is invalidated by `--reset-cache`
|
|
471
412
|
module.exports.getCacheKey = upstreamTransformer.getCacheKey;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@symbiote-native/vue",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.4.0",
|
|
4
4
|
"description": "SymbioteNative's Vue adapter — a @vue/runtime-core createRenderer driving real native iOS/Android views through the same engine as the React adapter.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -70,18 +70,18 @@
|
|
|
70
70
|
"@vue/compiler-sfc": "^3.5.13",
|
|
71
71
|
"@vue/runtime-core": "^3.5.13",
|
|
72
72
|
"typescript": "~6.0.0",
|
|
73
|
-
"@symbiote-native/components": "3.
|
|
74
|
-
"@symbiote-native/css-parser": "0.
|
|
73
|
+
"@symbiote-native/components": "3.3.0",
|
|
74
|
+
"@symbiote-native/css-parser": "0.6.0"
|
|
75
75
|
},
|
|
76
76
|
"peerDependencies": {
|
|
77
77
|
"vue": ">=3.5.0",
|
|
78
78
|
"react-native": ">=0.86",
|
|
79
|
-
"@symbiote-native/engine": "^1.
|
|
79
|
+
"@symbiote-native/engine": "^1.7.0"
|
|
80
80
|
},
|
|
81
81
|
"devDependencies": {
|
|
82
82
|
"@babel/core": "^7.25.2",
|
|
83
|
-
"@symbiote-native/engine": "1.
|
|
84
|
-
"@symbiote-native/test-utils": "0.
|
|
83
|
+
"@symbiote-native/engine": "1.7.0",
|
|
84
|
+
"@symbiote-native/test-utils": "0.5.0"
|
|
85
85
|
},
|
|
86
86
|
"scripts": {
|
|
87
87
|
"typecheck": "tsc --build",
|