@symbiote-native/vue 3.3.0 → 3.5.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 +177 -229
- 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,10 @@ function trimReflowedTextNodeTransform(node) {
|
|
|
147
134
|
}
|
|
148
135
|
}
|
|
149
136
|
|
|
150
|
-
//
|
|
151
|
-
const
|
|
152
|
-
|
|
153
|
-
const SYMBIOTE_SOURCE_PREFIX = '@symbiote-native/';
|
|
137
|
+
// The scope of this package's own name, `@symbiote-native/`, so the prefix has one source
|
|
138
|
+
const SYMBIOTE_SOURCE_PREFIX = `${require('./package.json').name.split('/')[0]}/`;
|
|
154
139
|
|
|
155
|
-
// Local names the file imports from any `@symbiote-native/*` package (`FlatList`, `Animated`)
|
|
140
|
+
// Local names the file imports from any `@symbiote-native/*` package (`FlatList`, `Animated`)
|
|
156
141
|
function symbioteImportsOf(descriptor) {
|
|
157
142
|
const locals = new Set();
|
|
158
143
|
for (const block of [descriptor.script, descriptor.scriptSetup]) {
|
|
@@ -161,7 +146,7 @@ function symbioteImportsOf(descriptor) {
|
|
|
161
146
|
const program = babelParse(block.content, { sourceType: 'module', plugins }).program;
|
|
162
147
|
for (const statement of program.body) {
|
|
163
148
|
if (
|
|
164
|
-
statement.type !==
|
|
149
|
+
statement.type !== IMPORT_DECLARATION ||
|
|
165
150
|
!statement.source.value.startsWith(SYMBIOTE_SOURCE_PREFIX)
|
|
166
151
|
)
|
|
167
152
|
continue;
|
|
@@ -171,9 +156,8 @@ function symbioteImportsOf(descriptor) {
|
|
|
171
156
|
return locals;
|
|
172
157
|
}
|
|
173
158
|
|
|
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.
|
|
159
|
+
// Vue casts a bare attribute to `true` only for a declared Boolean prop, and our tags declare none
|
|
160
|
+
// So `nested-scroll-enabled` reached Android as "" and threw `String cannot be cast to Boolean`
|
|
177
161
|
function createBareBooleanNodeTransform(symbioteLocals) {
|
|
178
162
|
const isOurs = tag =>
|
|
179
163
|
INTRINSIC_TAGS.has(tag) || symbioteLocals.has(tag.split('.')[0]);
|
|
@@ -195,32 +179,26 @@ function createBareBooleanNodeTransform(symbioteLocals) {
|
|
|
195
179
|
};
|
|
196
180
|
}
|
|
197
181
|
|
|
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.
|
|
182
|
+
// A short, stable id per file, used as the SFC scope id whether or not it has scoped styles
|
|
183
|
+
// Built on css-parser's `hashFilePath`, shared with the standalone .module.css compiler
|
|
202
184
|
function scopeIdFor(filename) {
|
|
203
185
|
return 'data-v-' + hashFilePath(filename);
|
|
204
186
|
}
|
|
205
187
|
|
|
206
|
-
// The local the compiled component lands in when a <style module> block
|
|
207
|
-
//
|
|
188
|
+
// The local the compiled component lands in when a <style module> block hangs `__cssModules` on it
|
|
189
|
+
// It is `genDefaultAs` of `compileScript`, the same role as plugin-vue's `_sfc_main`
|
|
208
190
|
const SFC_COMPONENT_VAR = '__sfc__';
|
|
209
191
|
|
|
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.
|
|
192
|
+
// Each block restarts its rules at order 0, so a later one would tie an earlier one
|
|
193
|
+
// Renumbering on append gives the file one monotonic sequence, as one stylesheet would
|
|
214
194
|
function appendRules(target, rules) {
|
|
215
195
|
for (const rule of rules) {
|
|
216
196
|
target.push({ ...rule, order: target.length });
|
|
217
197
|
}
|
|
218
198
|
}
|
|
219
199
|
|
|
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.
|
|
200
|
+
// `combinators` is compile-time-only, as the registry matches by token subset, so it is not shipped
|
|
201
|
+
// Twin of css-parser's `serializeRules` (metro-css-module/index.ts) for a standalone style file
|
|
224
202
|
function serializeRules(rules) {
|
|
225
203
|
return JSON.stringify(
|
|
226
204
|
rules.map(({ tokens, specificity, order, style }) => ({
|
|
@@ -232,9 +210,8 @@ function serializeRules(rules) {
|
|
|
232
210
|
);
|
|
233
211
|
}
|
|
234
212
|
|
|
235
|
-
//
|
|
236
|
-
//
|
|
237
|
-
// lookup rather than reusing detectLanguage(), which is extension-keyed.
|
|
213
|
+
// A style block's `lang` names a preprocessor (`lang="scss"`), where a file has an extension
|
|
214
|
+
// So this is its own small lookup, not `detectLanguage()`, which is extension-keyed
|
|
238
215
|
const SFC_STYLE_LANG_TO_PREPROCESSOR = new Map([
|
|
239
216
|
['scss', 'scss'],
|
|
240
217
|
['sass', 'scss'],
|
|
@@ -242,10 +219,9 @@ const SFC_STYLE_LANG_TO_PREPROCESSOR = new Map([
|
|
|
242
219
|
['stylus', 'stylus'],
|
|
243
220
|
]);
|
|
244
221
|
|
|
245
|
-
// Reduces one <style> block
|
|
246
|
-
// through unchanged. Anything outside the four recognized preprocessor langs throws.
|
|
222
|
+
// Reduces one <style> block to plain CSS: lang-less and `lang="css"` pass through, others compile
|
|
247
223
|
async function compileStyleBlockContent(style, filename) {
|
|
248
|
-
if (style.lang == null || style.lang ===
|
|
224
|
+
if (style.lang == null || style.lang === PLAIN_CSS_LANG) return style.content;
|
|
249
225
|
|
|
250
226
|
const preprocessorLang = SFC_STYLE_LANG_TO_PREPROCESSOR.get(style.lang);
|
|
251
227
|
if (!preprocessorLang) {
|
|
@@ -254,167 +230,146 @@ async function compileStyleBlockContent(style, filename) {
|
|
|
254
230
|
);
|
|
255
231
|
}
|
|
256
232
|
|
|
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.
|
|
233
|
+
// `.sass` and `.scss` share one compiler entry that picks the syntax by file extension
|
|
234
|
+
// An inline block has no file, so a synthetic `.sass` path selects the indented syntax
|
|
262
235
|
const syntheticPath = style.lang === 'sass' ? `${filename}.sass` : filename;
|
|
263
236
|
return compilePreprocessor(style.content, preprocessorLang, syntheticPath);
|
|
264
237
|
}
|
|
265
238
|
|
|
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
|
-
|
|
239
|
+
// `parse` memoizes its descriptor and the scope-class transform mutates its AST in place
|
|
240
|
+
// A second compile of one source would walk a half-transformed tree and die in codegen
|
|
241
|
+
// `clear()` rather than deleting our key, as the key format is internal to `@vue/shared`
|
|
242
|
+
function parseDescriptor(src, filename) {
|
|
282
243
|
parseCache.clear();
|
|
283
|
-
//
|
|
284
|
-
//
|
|
285
|
-
// is fixed. Without the answer here, `<pressable>` stays a component: `resolveComponent(...)`.
|
|
244
|
+
// The parser decides element vs component, and `compileScript` reuses that parsed AST
|
|
245
|
+
// Without `isCustomElement` here, `<pressable>` stays a component: `resolveComponent(...)`
|
|
286
246
|
const { descriptor, errors } = parse(src, {
|
|
287
247
|
filename,
|
|
288
|
-
templateParseOptions: { isCustomElement:
|
|
248
|
+
templateParseOptions: { isCustomElement: isIntrinsicTag },
|
|
289
249
|
});
|
|
290
250
|
if (errors && errors.length > 0) {
|
|
291
251
|
throw new Error(
|
|
292
252
|
`Vue SFC parse error in ${filename}:\n${errors.map(String).join('\n')}`,
|
|
293
253
|
);
|
|
294
254
|
}
|
|
295
|
-
if (descriptor.scriptSetup
|
|
296
|
-
|
|
297
|
-
`Vue SFC ${filename} has no <script> / <script setup> block`,
|
|
298
|
-
);
|
|
255
|
+
if (descriptor.scriptSetup != null || descriptor.script != null) {
|
|
256
|
+
return descriptor;
|
|
299
257
|
}
|
|
258
|
+
if (descriptor.template == null) {
|
|
259
|
+
throw new Error(`Vue SFC ${filename} has no <template> / <script> block`);
|
|
260
|
+
}
|
|
261
|
+
// `compileScript` refuses a file without a script, yet a static template is a valid component
|
|
262
|
+
// A `<script setup>` appended after the source keeps every line number of the file
|
|
263
|
+
// It carries a comment since the parser drops a blank block, which would recurse forever
|
|
264
|
+
return parseDescriptor(
|
|
265
|
+
`${src}\n<script setup>/* static template */</script>\n`,
|
|
266
|
+
filename,
|
|
267
|
+
);
|
|
268
|
+
}
|
|
300
269
|
|
|
301
|
-
|
|
270
|
+
// `<style scoped>` and `<style module>` are one mechanism, lightningcss' CSS-Modules renaming
|
|
271
|
+
// Only the pattern differs, and a plain `<style>` is not renamed, so its classes register globally
|
|
302
272
|
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
//
|
|
273
|
+
// Renaming stands in for Vue's `data-v-hash` attribute, as there is no DOM to match one against
|
|
274
|
+
// `:global(...)` needs no handling here, since a name lightningcss did not rename is global
|
|
275
|
+
function compileSfcStyleBlock({ style, content, filename, scopeId }) {
|
|
276
|
+
if (style.module) {
|
|
277
|
+
const compiled = compileCssModule(content, filename);
|
|
278
|
+
const bindingName = typeof style.module === 'string' ? style.module : '$style';
|
|
279
|
+
return { rules: compiled.rules, binding: [bindingName, compiled.classMap] };
|
|
280
|
+
}
|
|
281
|
+
if (style.scoped) {
|
|
282
|
+
return compileScopedCss(content, {
|
|
283
|
+
filename,
|
|
284
|
+
pattern: `[local]__${scopeId}`,
|
|
285
|
+
});
|
|
286
|
+
}
|
|
287
|
+
return { rules: compileCssToRules(content, { filename }).rules };
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
// Blocks cascade last-wins like CSS, carried by the rules' `order` that `appendRules` renumbers
|
|
291
|
+
// A module block's classes never apply to a literal `class`, only through `$style.x`
|
|
292
|
+
async function compileStyles(descriptor, filename, scopeId) {
|
|
293
|
+
// A preprocessor block is reduced to plain CSS first, as the renaming only ever sees plain CSS
|
|
294
|
+
const contents = await Promise.all(
|
|
295
|
+
descriptor.styles.map(style => compileStyleBlockContent(style, filename)),
|
|
296
|
+
);
|
|
324
297
|
const rules = [];
|
|
325
298
|
const scopedClassNames = new Map();
|
|
326
299
|
const cssModuleBindings = new Map();
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
300
|
+
descriptor.styles.forEach((style, index) => {
|
|
301
|
+
const block = compileSfcStyleBlock({
|
|
302
|
+
style,
|
|
303
|
+
content: contents[index],
|
|
304
|
+
filename,
|
|
305
|
+
scopeId,
|
|
306
|
+
});
|
|
307
|
+
appendRules(rules, block.rules);
|
|
308
|
+
for (const [name, renamed] of block.names ?? [])
|
|
309
|
+
scopedClassNames.set(name, renamed);
|
|
310
|
+
if (block.binding) {
|
|
311
|
+
const [bindingName, classMap] = block.binding;
|
|
338
312
|
cssModuleBindings.set(bindingName, {
|
|
339
313
|
...cssModuleBindings.get(bindingName),
|
|
340
|
-
...
|
|
341
|
-
});
|
|
342
|
-
} else if (style.scoped) {
|
|
343
|
-
const compiled = compileScopedCss(content, {
|
|
344
|
-
filename,
|
|
345
|
-
pattern: `[local]__${scopeId}`,
|
|
314
|
+
...classMap,
|
|
346
315
|
});
|
|
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
316
|
}
|
|
353
|
-
}
|
|
317
|
+
});
|
|
318
|
+
return { rules, scopedClassNames, cssModuleBindings };
|
|
319
|
+
}
|
|
354
320
|
|
|
355
|
-
|
|
356
|
-
|
|
321
|
+
// Scoped-class rewriting is skipped when nothing in the file is scoped, so it adds no runtime cost
|
|
322
|
+
function templateOptionsFor(descriptor, scopedClassNames) {
|
|
357
323
|
const nodeTransforms = [
|
|
358
324
|
trimReflowedTextNodeTransform,
|
|
359
325
|
createBareBooleanNodeTransform(symbioteImportsOf(descriptor)),
|
|
360
326
|
];
|
|
361
327
|
if (scopedClassNames.size > 0)
|
|
362
328
|
nodeTransforms.push(createScopeClassNodeTransform(scopedClassNames));
|
|
363
|
-
//
|
|
364
|
-
//
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
compilerOptions: {
|
|
368
|
-
...(nodeTransforms.length > 0 ? { nodeTransforms } : {}),
|
|
369
|
-
isCustomElement: tag => INTRINSIC_TAGS.has(tag),
|
|
370
|
-
},
|
|
371
|
-
};
|
|
329
|
+
// Every intrinsic, not just the imported ones: a narrower list leaves `<pressable>` a blank
|
|
330
|
+
// `resolveComponent(...)` and makes `<switch v-model>` a compile error
|
|
331
|
+
return { compilerOptions: { nodeTransforms, isCustomElement: isIntrinsicTag } };
|
|
332
|
+
}
|
|
372
333
|
|
|
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;
|
|
334
|
+
// `inlineTemplate` folds the render fn into `setup()`, giving one module and one export default
|
|
335
|
+
// `genDefaultAs` makes it a const so `__cssModules` can hang off it, as Vue reads `$style` there
|
|
336
|
+
function compileComponentScript(descriptor, scopeId, styles) {
|
|
337
|
+
const hasCssModules = styles.cssModuleBindings.size > 0;
|
|
383
338
|
const compiled = compileScript(descriptor, {
|
|
384
339
|
id: scopeId,
|
|
385
340
|
inlineTemplate: true,
|
|
386
|
-
templateOptions,
|
|
341
|
+
templateOptions: templateOptionsFor(descriptor, styles.scopedClassNames),
|
|
387
342
|
fs: compileScriptFs,
|
|
388
343
|
...(hasCssModules ? { genDefaultAs: SFC_COMPONENT_VAR } : {}),
|
|
389
344
|
});
|
|
390
|
-
//
|
|
391
|
-
|
|
392
|
-
const code = compiled.content.replace(
|
|
345
|
+
// Every Vue import, injected helpers and the user's own, goes to the shim: no runtime-dom
|
|
346
|
+
return compiled.content.replace(
|
|
393
347
|
/from\s*(['"])vue\1/g,
|
|
394
348
|
'from "@symbiote-native/vue/runtime-helpers"',
|
|
395
349
|
);
|
|
350
|
+
}
|
|
396
351
|
|
|
352
|
+
function withStyleRegistration(code, { rules, scopedClassNames, cssModuleBindings }) {
|
|
353
|
+
const hasCssModules = cssModuleBindings.size > 0;
|
|
397
354
|
if (rules.length === 0 && !hasCssModules) return code;
|
|
398
355
|
|
|
399
|
-
// Only a scoped file needs the runtime rename helper and its per-file map
|
|
400
|
-
|
|
401
|
-
const engineImports =
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
: 'registerRules';
|
|
356
|
+
// Only a scoped file needs the runtime rename helper and its per-file map
|
|
357
|
+
const isScoped = scopedClassNames.size > 0;
|
|
358
|
+
const engineImports = isScoped
|
|
359
|
+
? 'registerRules, renameClassTokens as __scopeClass'
|
|
360
|
+
: 'registerRules';
|
|
405
361
|
|
|
406
362
|
const preamble = [
|
|
407
363
|
`import { ${engineImports} } from '@symbiote-native/engine';`,
|
|
408
364
|
`registerRules(${serializeRules(rules)});`,
|
|
409
365
|
];
|
|
410
|
-
if (
|
|
366
|
+
if (isScoped) {
|
|
411
367
|
preamble.push(
|
|
412
368
|
`const __scopedClassNames = ${JSON.stringify(Object.fromEntries(scopedClassNames))};`,
|
|
413
369
|
);
|
|
414
370
|
}
|
|
415
|
-
// Each <style module> binding
|
|
416
|
-
//
|
|
417
|
-
// <script setup> code itself; the __cssModules tail below is what makes the TEMPLATE see it.
|
|
371
|
+
// Each <style module> binding is a top-level const placed before the component, so <script setup>
|
|
372
|
+
// can close over it; the `__cssModules` tail below is what makes the TEMPLATE see it
|
|
418
373
|
for (const [bindingName, classMap] of cssModuleBindings) {
|
|
419
374
|
preamble.push(`const ${bindingName} = ${JSON.stringify(classMap)};`);
|
|
420
375
|
}
|
|
@@ -433,39 +388,32 @@ async function compileSfc(src, filename) {
|
|
|
433
388
|
return parts.join('\n') + '\n';
|
|
434
389
|
}
|
|
435
390
|
|
|
391
|
+
async function compileSfc(src, filename) {
|
|
392
|
+
const descriptor = parseDescriptor(src, filename);
|
|
393
|
+
const scopeId = scopeIdFor(filename);
|
|
394
|
+
const styles = await compileStyles(descriptor, filename, scopeId);
|
|
395
|
+
const code = compileComponentScript(descriptor, scopeId, styles);
|
|
396
|
+
return withStyleRegistration(code, styles);
|
|
397
|
+
}
|
|
398
|
+
|
|
436
399
|
// Exported separately from `transform` so tests can assert on the compiled SFC output
|
|
437
|
-
// (imports, injected `registerRules` call) without driving
|
|
400
|
+
// (imports, injected `registerRules` call) without driving RN's whole Babel preset
|
|
438
401
|
module.exports.compileSfc = compileSfc;
|
|
439
402
|
|
|
440
|
-
//
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
//
|
|
444
|
-
// content-hash-cached call.
|
|
403
|
+
// A standalone style file, not the inline `<style>` of a .vue, is compiled by this shared wrapper
|
|
404
|
+
const styleFileTransformer = createCssMetroTransformer(upstreamTransformer);
|
|
405
|
+
|
|
406
|
+
// Async throughout, as `compileSfc` awaits the style preprocessors and Metro awaits `transform`
|
|
445
407
|
module.exports.transform = async function transform(params) {
|
|
446
408
|
if (params.filename.endsWith('.vue')) {
|
|
447
409
|
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
|
-
});
|
|
410
|
+
// Re-label as .tsx so RN's transformer strips TS from `<script setup lang="ts">`
|
|
411
|
+
// Metro tracks the real path separately
|
|
412
|
+
return upstreamTransformer.transform(relabelForUpstream(params, code, '.tsx'));
|
|
465
413
|
}
|
|
466
|
-
return
|
|
414
|
+
return styleFileTransformer.transform(params);
|
|
467
415
|
};
|
|
468
416
|
|
|
469
|
-
// Surface the upstream cache key so RN preset changes still bust Metro's cache
|
|
470
|
-
// step itself is invalidated by `--reset-cache`
|
|
417
|
+
// Surface the upstream cache key so RN preset changes still bust Metro's cache
|
|
418
|
+
// The SFC step itself is invalidated by `--reset-cache`
|
|
471
419
|
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.5.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",
|