@symbiote-native/vue 0.3.8 → 0.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/README.md +6 -6
- package/build/bootstrap.js +1 -1
- package/build/components/flat-list/index.js +32 -39
- package/build/components/image-background.js +8 -19
- package/build/components/image.js +12 -21
- package/build/components/keyboard-avoiding-view.js +47 -31
- package/build/components/modal/index.d.ts +4 -4
- package/build/components/modal/index.js +5 -2
- package/build/components/pressable.d.ts +1 -1
- package/build/components/pressable.js +25 -32
- package/build/components/refresh-control.js +12 -18
- package/build/components/scroll-view/index.android.d.ts +1 -1
- package/build/components/scroll-view/index.android.js +24 -34
- package/build/components/scroll-view/index.ios.d.ts +1 -1
- package/build/components/scroll-view/index.ios.js +3 -1
- package/build/components/scroll-view/shared.js +93 -123
- package/build/components/scroll-view/sticky-header.js +59 -64
- package/build/components/section-list/index.js +16 -21
- package/build/components/switch/shared.js +29 -32
- package/build/components/text-input/index.js +36 -51
- package/build/components/touchable-native-feedback.js +3 -1
- package/build/components/touchable.d.ts +7 -1
- package/build/components/touchable.js +181 -33
- package/build/components/virtualized-list/index.js +57 -18
- package/build/components/virtualized-section-list/index.d.ts +5 -0
- package/build/components/virtualized-section-list/index.js +50 -29
- package/build/components.js +9 -6
- package/build/create-portal/index.d.ts +28 -0
- package/build/create-portal/index.js +42 -0
- package/build/host-instance/index.js +5 -5
- package/build/index.d.ts +9 -6
- package/build/index.js +18 -46
- package/build/modules/animated/create-animated-component.js +46 -101
- package/build/modules/animated/index.d.ts +2 -0
- package/build/modules/animated/index.js +26 -17
- package/build/modules/app-registry/index.js +11 -13
- package/build/modules/status-bar.js +12 -17
- package/build/render.d.ts +3 -1
- package/build/render.js +62 -1
- package/build/renderer/index.js +11 -6
- package/build/runtime-helpers/index.d.ts +44 -24
- package/build/runtime-helpers/index.js +116 -47
- package/build/utils/normalize-attrs.js +3 -1
- package/metro-css-parser.cjs +9 -14
- package/metro-vue-transformer.cjs +196 -178
- package/package.json +5 -5
|
@@ -1,55 +1,48 @@
|
|
|
1
|
-
// Metro babel transformer that teaches the bundler to read Vue SFCs (.vue). Metro has no
|
|
2
|
-
//
|
|
3
|
-
//
|
|
4
|
-
// component module, then hand the JS to RN's own babel transformer
|
|
5
|
-
//
|
|
6
|
-
//
|
|
7
|
-
// vue/runtime-dom.
|
|
1
|
+
// Metro babel transformer that teaches the bundler to read Vue SFCs (.vue). Metro has no Vue
|
|
2
|
+
// plugin (unplugin-vue only ships vite/webpack/esbuild/rollup adapters), so we do the
|
|
3
|
+
// single-pass compile here: parse the SFC, compile <script setup> + <template> into one
|
|
4
|
+
// component module, then hand the JS to RN's own babel transformer - the Metro twin of
|
|
5
|
+
// @vitejs/plugin-vue. Imports get retargeted from 'vue' to @symbiote-native/vue/runtime-helpers
|
|
6
|
+
// (not bare @vue/runtime-core) because that shim also supplies our own `vShow` - compiled
|
|
7
|
+
// v-show imports it by name, and only @vue/runtime-dom's DOM-based version exists otherwise.
|
|
8
8
|
//
|
|
9
|
-
//
|
|
10
|
-
//
|
|
11
|
-
//
|
|
12
|
-
//
|
|
13
|
-
// Ships as a package-level export (`@symbiote-native/vue/metro-vue-transformer`) rather than living
|
|
14
|
-
// in each consuming app: a consumer's own metro.config.js just points babelTransformerPath at it.
|
|
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.
|
|
15
12
|
|
|
16
13
|
const nodeFs = require('fs');
|
|
17
14
|
const { parse, compileScript, registerTS } = require('@vue/compiler-sfc');
|
|
18
15
|
const { createCompoundExpression } = require('@vue/compiler-core');
|
|
19
16
|
|
|
20
|
-
// A bare-specifier type import (`import type { X } from '@symbiote-native/navigation/vue'
|
|
21
|
-
//
|
|
22
|
-
//
|
|
23
|
-
//
|
|
24
|
-
//
|
|
25
|
-
// lazy loader for the real `typescript` package, which resolves the specifier via
|
|
26
|
-
// `ts.resolveModuleName` and self-supplies `ts.sys` as the fs fallback.
|
|
17
|
+
// A bare-specifier type import (`import type { X } from '@symbiote-native/navigation/vue'`) needs
|
|
18
|
+
// real node_modules resolution to turn the specifier into a file path - compileScript's own `fs`
|
|
19
|
+
// option only reads paths it's already given, it can't resolve. registerTS (the same hook
|
|
20
|
+
// @vitejs/plugin-vue and vue-tsc use) hands the compiler a lazy `typescript` loader that resolves
|
|
21
|
+
// via `ts.resolveModuleName` and self-supplies `ts.sys` as the fs fallback.
|
|
27
22
|
registerTS(() => require('typescript'));
|
|
28
|
-
//
|
|
29
|
-
//
|
|
30
|
-
// straight from this package's own node_modules under pnpm.
|
|
23
|
+
// Required directly (not via the ./metro-css-parser public subpath, which exists for CONSUMERS)
|
|
24
|
+
// so it resolves from this package's own node_modules under pnpm.
|
|
31
25
|
const {
|
|
32
26
|
compile: compilePreprocessor,
|
|
33
27
|
compileCssFile,
|
|
34
|
-
|
|
28
|
+
compileCssModule,
|
|
29
|
+
compileCssToRules,
|
|
30
|
+
compileScopedCss,
|
|
35
31
|
hashFilePath,
|
|
36
32
|
isStyleFile,
|
|
37
|
-
kebabToCamel,
|
|
38
|
-
parseCSS,
|
|
39
33
|
resolveUpstreamTransformer,
|
|
40
34
|
} = require('@symbiote-native/css-parser');
|
|
41
35
|
|
|
42
36
|
const upstreamTransformer = resolveUpstreamTransformer();
|
|
43
37
|
|
|
44
38
|
// compileScript needs file system access to resolve a type-only `defineProps<ISomeProps>()`
|
|
45
|
-
//
|
|
46
|
-
//
|
|
47
|
-
//
|
|
48
|
-
//
|
|
49
|
-
// as a loader (registerTS), and this file already runs in Node at Metro build time.
|
|
39
|
+
// imported from another file - without it, Metro's worker process (a real Node process, but one
|
|
40
|
+
// @vue/compiler-sfc doesn't auto-detect without registerTS) throws "No fs option provided ...
|
|
41
|
+
// in non-Node environment". Wiring Node's own `fs` straight through is simpler than a second
|
|
42
|
+
// registerTS-style loader.
|
|
50
43
|
const compileScriptFs = {
|
|
51
|
-
fileExists:
|
|
52
|
-
readFile:
|
|
44
|
+
fileExists: file => nodeFs.existsSync(file),
|
|
45
|
+
readFile: file => {
|
|
53
46
|
try {
|
|
54
47
|
return nodeFs.readFileSync(file, 'utf-8');
|
|
55
48
|
} catch {
|
|
@@ -58,47 +51,43 @@ const compileScriptFs = {
|
|
|
58
51
|
},
|
|
59
52
|
};
|
|
60
53
|
|
|
61
|
-
// Rewrites a Vue template AST so every `class`/`:class` binding
|
|
62
|
-
//
|
|
63
|
-
//
|
|
64
|
-
//
|
|
65
|
-
//
|
|
66
|
-
//
|
|
67
|
-
//
|
|
54
|
+
// Rewrites a Vue template AST so every `class`/`:class` binding names a class by the name
|
|
55
|
+
// lightningcss RENAMED it to. AST-level, not a raw-text regex: Vue merges a static class= and a
|
|
56
|
+
// dynamic :class= on the same element into ONE codegen entry, and text substitution can't
|
|
57
|
+
// reproduce that merge safely - letting Vue's own transformElement do the merge on our rewritten
|
|
58
|
+
// nodes reuses that logic.
|
|
59
|
+
//
|
|
60
|
+
// `renames` is compileScopedCss's own name map (authored name -> renamed name), NOT a set of
|
|
61
|
+
// names this file re-suffixes. That is the point of the migration: the rewriter and the style
|
|
62
|
+
// compiler used to derive the scoped name independently, so any disagreement between them was
|
|
63
|
+
// silent. Now there is one source and this half only reads it. One entry per class, keyed as
|
|
64
|
+
// AUTHORED — `class="section-label"` is the lookup, `sectionLabel` is not a second spelling of it.
|
|
68
65
|
//
|
|
69
|
-
// A static class="foo bar"
|
|
70
|
-
//
|
|
71
|
-
//
|
|
72
|
-
// FIRST (kebabToCamel) — a template may write class="section-label" (idiomatic CSS) instead of
|
|
73
|
-
// class="sectionLabel"; the css-parser always registers the CAMEL form, and localNames (built
|
|
74
|
-
// from that same registration) is camelCase-keyed, so the scoping check below only recognizes a
|
|
75
|
-
// token once it is in that form. The emitted string is always camelCase, suffixed or not.
|
|
66
|
+
// A static class="foo bar" (AttributeNode, prop.type === 6) resolves to its final string here
|
|
67
|
+
// at compile time - no runtime call needed. A token the map does not carry belongs to another
|
|
68
|
+
// file (App.css, a `:global()` escape hatch, a parent's class) and passes through untouched.
|
|
76
69
|
//
|
|
77
|
-
// A dynamic :class="expr"
|
|
78
|
-
//
|
|
79
|
-
//
|
|
80
|
-
//
|
|
81
|
-
//
|
|
82
|
-
//
|
|
83
|
-
//
|
|
84
|
-
|
|
85
|
-
// codegen emits the call with the original expression source reproduced unchanged inside it —
|
|
86
|
-
// this needs no per-shape branching, and correctly defers even a fully opaque runtime value
|
|
87
|
-
// (`:class="dynamicClass"`) to scopeClassName's own runtime token-matching, so there is no
|
|
88
|
-
// unresolved gap for dynamic scoped classes.
|
|
89
|
-
function createScopeClassNodeTransform(localNames, scopeId) {
|
|
70
|
+
// A dynamic :class="expr" (bind DirectiveNode, prop.type === 7) targeting `class`: by the time
|
|
71
|
+
// our transform runs, Vue's own transformExpression has already turned prop.exp into either a
|
|
72
|
+
// COMPOUND_EXPRESSION (type 8) or, for a bare identifier binding, a SIMPLE_EXPRESSION (type 4).
|
|
73
|
+
// createCompoundExpression wraps the original exp node as-is inside
|
|
74
|
+
// renameClassTokens(<original>, __scopedClassNames), so codegen reproduces the original
|
|
75
|
+
// expression unchanged - no per-shape branching needed, and a fully opaque runtime value
|
|
76
|
+
// (`:class="dynamicClass"`) still resolves against the same map at runtime.
|
|
77
|
+
function createScopeClassNodeTransform(renames) {
|
|
90
78
|
return function scopeClassNodeTransform(node) {
|
|
91
79
|
if (node.type !== 1 /* NodeTypes.ELEMENT */) return;
|
|
92
80
|
|
|
93
81
|
for (const prop of node.props) {
|
|
94
|
-
if (
|
|
82
|
+
if (
|
|
83
|
+
prop.type === 6 /* NodeTypes.ATTRIBUTE */ &&
|
|
84
|
+
prop.name === 'class' &&
|
|
85
|
+
prop.value
|
|
86
|
+
) {
|
|
95
87
|
prop.value.content = prop.value.content
|
|
96
88
|
.split(/\s+/)
|
|
97
89
|
.filter(Boolean)
|
|
98
|
-
.map((token)
|
|
99
|
-
const camelToken = kebabToCamel(token);
|
|
100
|
-
return localNames.has(camelToken) ? `${camelToken}__${scopeId}` : camelToken;
|
|
101
|
-
})
|
|
90
|
+
.map(token => renames.get(token) ?? token)
|
|
102
91
|
.join(' ');
|
|
103
92
|
continue;
|
|
104
93
|
}
|
|
@@ -112,7 +101,7 @@ function createScopeClassNodeTransform(localNames, scopeId) {
|
|
|
112
101
|
prop.exp
|
|
113
102
|
) {
|
|
114
103
|
prop.exp = createCompoundExpression(
|
|
115
|
-
['__scopeClass(', prop.exp, ',
|
|
104
|
+
['__scopeClass(', prop.exp, ', __scopedClassNames)'],
|
|
116
105
|
prop.exp.loc,
|
|
117
106
|
);
|
|
118
107
|
}
|
|
@@ -120,14 +109,42 @@ function createScopeClassNodeTransform(localNames, scopeId) {
|
|
|
120
109
|
};
|
|
121
110
|
}
|
|
122
111
|
|
|
123
|
-
// A short, stable id per file, used as the SFC scope id
|
|
124
|
-
//
|
|
125
|
-
//
|
|
126
|
-
// standalone .module.css compiler's identical need (metro-css-module.ts).
|
|
112
|
+
// A short, stable id per file, used as the SFC scope id regardless of whether the file has
|
|
113
|
+
// scoped styles - built on css-parser's shared hashFilePath so the algorithm isn't duplicated
|
|
114
|
+
// against the standalone .module.css compiler's identical need.
|
|
127
115
|
function scopeIdFor(filename) {
|
|
128
116
|
return 'data-v-' + hashFilePath(filename);
|
|
129
117
|
}
|
|
130
118
|
|
|
119
|
+
// The local the compiled component lands in when a <style module> block needs __cssModules hung
|
|
120
|
+
// off it (compileScript's genDefaultAs). Same role as @vitejs/plugin-vue's own `_sfc_main`.
|
|
121
|
+
const SFC_COMPONENT_VAR = '__sfc__';
|
|
122
|
+
|
|
123
|
+
// Blocks CONCATENATE - each is compiled on its own, so every block's rules restart at order 0 and
|
|
124
|
+
// a later block would tie with an earlier one on the cascade's source-order tie-break, silently
|
|
125
|
+
// reordering equally-specific rules. Renumbering on append gives the file one monotonic sequence,
|
|
126
|
+
// which is what a single stylesheet holding the concatenated blocks would have produced.
|
|
127
|
+
function appendRules(target, rules) {
|
|
128
|
+
for (const rule of rules) {
|
|
129
|
+
target.push({ ...rule, order: target.length });
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// `combinators` is compile-time-only - the registry matches a rule by token SUBSET and never reads
|
|
134
|
+
// it - so it is stripped rather than shipped in every app bundle. Twin of css-parser's own
|
|
135
|
+
// serializeRules (core/css-parser/src/metro-css-module/index.ts), which does this for a standalone
|
|
136
|
+
// style file.
|
|
137
|
+
function serializeRules(rules) {
|
|
138
|
+
return JSON.stringify(
|
|
139
|
+
rules.map(({ tokens, specificity, order, style }) => ({
|
|
140
|
+
tokens,
|
|
141
|
+
specificity,
|
|
142
|
+
order,
|
|
143
|
+
style,
|
|
144
|
+
})),
|
|
145
|
+
);
|
|
146
|
+
}
|
|
147
|
+
|
|
131
148
|
// An SFC style block's `lang` attribute names a preprocessor language directly (`lang="scss"`),
|
|
132
149
|
// unlike a standalone file, which is identified by its extension — so this is its own small
|
|
133
150
|
// lookup rather than reusing detectLanguage(), which is extension-keyed.
|
|
@@ -139,15 +156,15 @@ const SFC_STYLE_LANG_TO_PREPROCESSOR = new Map([
|
|
|
139
156
|
]);
|
|
140
157
|
|
|
141
158
|
// Reduces one <style> block down to plain CSS text. A lang-less or lang="css" block passes
|
|
142
|
-
// through unchanged
|
|
143
|
-
// (a typo, or a genuinely unsupported lang) still throws, same message as before this feature
|
|
144
|
-
// existed.
|
|
159
|
+
// through unchanged. Anything outside the four recognized preprocessor langs throws.
|
|
145
160
|
async function compileStyleBlockContent(style, filename) {
|
|
146
161
|
if (style.lang == null || style.lang === 'css') return style.content;
|
|
147
162
|
|
|
148
163
|
const preprocessorLang = SFC_STYLE_LANG_TO_PREPROCESSOR.get(style.lang);
|
|
149
164
|
if (!preprocessorLang) {
|
|
150
|
-
throw new Error(
|
|
165
|
+
throw new Error(
|
|
166
|
+
`SFC style lang="${style.lang}" not supported yet — plain CSS only`,
|
|
167
|
+
);
|
|
151
168
|
}
|
|
152
169
|
|
|
153
170
|
// Sass' `.sass` indented syntax and `.scss` syntax share one compiler entry point that picks
|
|
@@ -162,172 +179,173 @@ async function compileStyleBlockContent(style, filename) {
|
|
|
162
179
|
async function compileSfc(src, filename) {
|
|
163
180
|
const { descriptor, errors } = parse(src, { filename });
|
|
164
181
|
if (errors && errors.length > 0) {
|
|
165
|
-
throw new Error(
|
|
182
|
+
throw new Error(
|
|
183
|
+
`Vue SFC parse error in ${filename}:\n${errors.map(String).join('\n')}`,
|
|
184
|
+
);
|
|
166
185
|
}
|
|
167
186
|
if (descriptor.scriptSetup == null && descriptor.script == null) {
|
|
168
|
-
throw new Error(
|
|
187
|
+
throw new Error(
|
|
188
|
+
`Vue SFC ${filename} has no <script> / <script setup> block`,
|
|
189
|
+
);
|
|
169
190
|
}
|
|
170
191
|
|
|
171
192
|
const scopeId = scopeIdFor(filename);
|
|
172
193
|
|
|
173
|
-
//
|
|
174
|
-
//
|
|
175
|
-
// to re-extract style blocks with a regex.
|
|
194
|
+
// descriptor.styles is already parsed by @vue/compiler-sfc (one entry per <style> block,
|
|
195
|
+
// content pre-trimmed, scoped as a plain boolean) - no need to re-extract with a regex.
|
|
176
196
|
//
|
|
177
|
-
//
|
|
178
|
-
//
|
|
179
|
-
// own `.card` without colliding in the shared global registry — mirroring what Vue's own
|
|
180
|
-
// `data-v-hash` attribute does for DOM targets, just as a name suffix instead of an attribute
|
|
181
|
-
// selector (we have neither DOM nor attribute-selector matching). An unscoped block's classes
|
|
182
|
-
// register exactly as before: no suffix, globally shared. `:global(...)` selectors inside a
|
|
183
|
-
// scoped block are the one exception — @symbiote-native/css-parser already unwraps them to their
|
|
184
|
-
// plain class name (`:global(.reset)` parses like `.reset`), so `globalClassNamesIn` re-scans
|
|
185
|
-
// the block's own raw text to find which specific keys should be exempted from suffixing.
|
|
197
|
+
// Both scoped forms are ONE mechanism, lightningcss's CSS-Modules renaming, differing only in
|
|
198
|
+
// the pattern string (core/css-parser/src/scoped-classes.ts):
|
|
186
199
|
//
|
|
187
|
-
//
|
|
188
|
-
//
|
|
200
|
+
// <style scoped> [local]__data-v-<hash> compileScopedCss - `.card` -> `card__data-v-h`
|
|
201
|
+
// <style module> [local]__module__<hash> compileCssModule - the same call a standalone
|
|
202
|
+
// .module.css takes, so the two cannot diverge
|
|
203
|
+
// <style> not renamed at all compileCssToRules, classes register globally
|
|
189
204
|
//
|
|
190
|
-
//
|
|
191
|
-
//
|
|
192
|
-
//
|
|
193
|
-
//
|
|
194
|
-
//
|
|
195
|
-
//
|
|
196
|
-
//
|
|
197
|
-
//
|
|
198
|
-
//
|
|
199
|
-
//
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
const styles = {};
|
|
203
|
-
const localScopedNames = new Set();
|
|
205
|
+
// Renaming is our equivalent of Vue's `data-v-hash` ATTRIBUTE (we have no DOM and no
|
|
206
|
+
// attribute-selector matching, so a scope can only be expressed in the name), and `:global(...)`
|
|
207
|
+
// needs no handling here at all: a name lightningcss did not rename is global by definition.
|
|
208
|
+
//
|
|
209
|
+
// Multiple blocks cascade last-block-wins, same as CSS, after each block is scoped
|
|
210
|
+
// independently — carried by the rules' `order`, which appendRules renumbers across blocks.
|
|
211
|
+
//
|
|
212
|
+
// A module block's classes are NEVER auto-applied to a literal class="..." (opt-in via $style.x
|
|
213
|
+
// only), so they stay out of the template rewriter's name map; its output is the name->scopedName
|
|
214
|
+
// map instead, emitted as a preamble const AND attached to the component as __cssModules.
|
|
215
|
+
const rules = [];
|
|
216
|
+
const scopedClassNames = new Map();
|
|
204
217
|
const cssModuleBindings = new Map();
|
|
205
218
|
|
|
206
219
|
for (const style of descriptor.styles) {
|
|
207
|
-
// Reduces a preprocessor block
|
|
208
|
-
//
|
|
209
|
-
// plain-CSS output, same as it always did for a lang-less/lang="css" block. Anything other
|
|
210
|
-
// than the four recognized preprocessor langs (a typo, or a genuinely unsupported lang)
|
|
211
|
-
// still throws, unchanged from before.
|
|
220
|
+
// Reduces a preprocessor block to plain CSS BEFORE the renaming below runs - the renaming is
|
|
221
|
+
// language-agnostic, it only ever sees plain CSS.
|
|
212
222
|
const content = await compileStyleBlockContent(style, filename);
|
|
213
|
-
const parsed = parseCSS(content, { filename });
|
|
214
223
|
|
|
215
224
|
if (style.module) {
|
|
216
|
-
const bindingName =
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
const isExempt = exemptFromScope.has(className);
|
|
225
|
-
const registeredName = isExempt ? className : `${className}__module__${scopeId}`;
|
|
226
|
-
classMap[className] = registeredName;
|
|
227
|
-
styles[registeredName] = { ...styles[registeredName], ...props };
|
|
228
|
-
}
|
|
229
|
-
cssModuleBindings.set(bindingName, classMap);
|
|
225
|
+
const bindingName =
|
|
226
|
+
typeof style.module === 'string' ? style.module : '$style';
|
|
227
|
+
const compiled = compileCssModule(content, filename);
|
|
228
|
+
appendRules(rules, compiled.rules);
|
|
229
|
+
cssModuleBindings.set(bindingName, {
|
|
230
|
+
...cssModuleBindings.get(bindingName),
|
|
231
|
+
...compiled.classMap,
|
|
232
|
+
});
|
|
230
233
|
} else if (style.scoped) {
|
|
231
|
-
const
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
234
|
+
const compiled = compileScopedCss(content, {
|
|
235
|
+
filename,
|
|
236
|
+
pattern: `[local]__${scopeId}`,
|
|
237
|
+
});
|
|
238
|
+
appendRules(rules, compiled.rules);
|
|
239
|
+
for (const [name, renamed] of compiled.names)
|
|
240
|
+
scopedClassNames.set(name, renamed);
|
|
238
241
|
} else {
|
|
239
|
-
|
|
240
|
-
styles[className] = { ...styles[className], ...props };
|
|
241
|
-
}
|
|
242
|
+
appendRules(rules, compileCssToRules(content, { filename }).rules);
|
|
242
243
|
}
|
|
243
244
|
}
|
|
244
245
|
|
|
245
|
-
//
|
|
246
|
-
//
|
|
247
|
-
// even passed to the compiler) when nothing in this file is scoped, so a .vue with only
|
|
248
|
-
// unscoped/no styles compiles exactly as before, zero added runtime cost or behavior change.
|
|
246
|
+
// Skipped entirely (not even passed to the compiler) when nothing in this file is scoped, so
|
|
247
|
+
// a .vue with only unscoped/no styles compiles with zero added runtime cost.
|
|
249
248
|
const templateOptions =
|
|
250
|
-
|
|
249
|
+
scopedClassNames.size > 0
|
|
251
250
|
? {
|
|
252
251
|
compilerOptions: {
|
|
253
|
-
nodeTransforms: [createScopeClassNodeTransform(
|
|
252
|
+
nodeTransforms: [createScopeClassNodeTransform(scopedClassNames)],
|
|
254
253
|
},
|
|
255
254
|
}
|
|
256
255
|
: undefined;
|
|
257
256
|
|
|
258
257
|
// inlineTemplate folds the <template> render fn into setup(): one module, one `export
|
|
259
258
|
// default`. Only valid with <script setup>, which the canary uses.
|
|
259
|
+
//
|
|
260
|
+
// genDefaultAs turns that `export default {...}` into `const __sfc__ = {...}`, so a <style
|
|
261
|
+
// module> block can hang __cssModules off the component OPTIONS object before the module
|
|
262
|
+
// exports it (the same thing @vitejs/plugin-vue does). Vue resolves a template's `$style` /
|
|
263
|
+
// `classes` off `instance.type.__cssModules`, NOT off module scope, so without this the
|
|
264
|
+
// emitted const is unreachable from the template and `$style.card` throws at render. Only
|
|
265
|
+
// requested when a module block exists, so every other .vue file keeps identical output.
|
|
266
|
+
const hasCssModules = cssModuleBindings.size > 0;
|
|
260
267
|
const compiled = compileScript(descriptor, {
|
|
261
268
|
id: scopeId,
|
|
262
269
|
inlineTemplate: true,
|
|
263
270
|
templateOptions,
|
|
264
271
|
fs: compileScriptFs,
|
|
272
|
+
...(hasCssModules ? { genDefaultAs: SFC_COMPONENT_VAR } : {}),
|
|
265
273
|
});
|
|
266
|
-
//
|
|
267
|
-
//
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
274
|
+
// Retargets every Vue import (compiler-injected helpers AND the user's own `from 'vue'`) at
|
|
275
|
+
// the runtime-helpers shim - no vue/runtime-dom in a native bundle.
|
|
276
|
+
const code = compiled.content.replace(
|
|
277
|
+
/from\s*(['"])vue\1/g,
|
|
278
|
+
'from "@symbiote-native/vue/runtime-helpers"',
|
|
279
|
+
);
|
|
271
280
|
|
|
272
|
-
if (
|
|
281
|
+
if (rules.length === 0 && !hasCssModules) return code;
|
|
273
282
|
|
|
274
|
-
// Only a scoped file needs
|
|
275
|
-
//
|
|
276
|
-
// localScopedNames is non-empty, so these stay unimported/undeclared (and absent from the
|
|
277
|
-
// bundle) for every non-scoped .vue file.
|
|
283
|
+
// Only a scoped file needs the runtime rename helper and its per-file map, so these stay
|
|
284
|
+
// unimported for every non-scoped .vue file.
|
|
278
285
|
const engineImports =
|
|
279
|
-
|
|
286
|
+
scopedClassNames.size > 0
|
|
287
|
+
? 'registerRules, renameClassTokens as __scopeClass'
|
|
288
|
+
: 'registerRules';
|
|
280
289
|
|
|
281
|
-
const preamble = [
|
|
282
|
-
|
|
290
|
+
const preamble = [
|
|
291
|
+
`import { ${engineImports} } from '@symbiote-native/engine';`,
|
|
292
|
+
`registerRules(${serializeRules(rules)});`,
|
|
293
|
+
];
|
|
294
|
+
if (scopedClassNames.size > 0) {
|
|
283
295
|
preamble.push(
|
|
284
|
-
`const
|
|
285
|
-
`const __scopeId = ${JSON.stringify(scopeId)};`,
|
|
296
|
+
`const __scopedClassNames = ${JSON.stringify(Object.fromEntries(scopedClassNames))};`,
|
|
286
297
|
);
|
|
287
298
|
}
|
|
288
|
-
// Each <style module> binding becomes a
|
|
289
|
-
//
|
|
290
|
-
//
|
|
291
|
-
// usable both from the inlined template (`:class="$style.card"`) and from <script setup> code
|
|
292
|
-
// itself (`$style.card`), no extra wiring needed on either side.
|
|
299
|
+
// Each <style module> binding becomes a top-level const holding its name->scopedName map,
|
|
300
|
+
// placed before the component so it's a closed-over module-scope variable usable from
|
|
301
|
+
// <script setup> code itself; the __cssModules tail below is what makes the TEMPLATE see it.
|
|
293
302
|
for (const [bindingName, classMap] of cssModuleBindings) {
|
|
294
303
|
preamble.push(`const ${bindingName} = ${JSON.stringify(classMap)};`);
|
|
295
304
|
}
|
|
296
305
|
|
|
297
|
-
|
|
306
|
+
const parts = [...preamble, code];
|
|
307
|
+
if (hasCssModules) {
|
|
308
|
+
const bindings = [...cssModuleBindings.keys()]
|
|
309
|
+
.map(name => `${JSON.stringify(name)}: ${name}`)
|
|
310
|
+
.join(', ');
|
|
311
|
+
parts.push(
|
|
312
|
+
`${SFC_COMPONENT_VAR}.__cssModules = { ${bindings} };`,
|
|
313
|
+
`export default ${SFC_COMPONENT_VAR};`,
|
|
314
|
+
);
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
return parts.join('\n') + '\n';
|
|
298
318
|
}
|
|
299
319
|
|
|
300
320
|
// Exported separately from `transform` so tests can assert on the compiled SFC output
|
|
301
|
-
// (imports, injected `
|
|
321
|
+
// (imports, injected `registerRules` call) without driving the full upstream RN Babel preset.
|
|
302
322
|
module.exports.compileSfc = compileSfc;
|
|
303
323
|
|
|
304
|
-
// Async uniformly, including
|
|
305
|
-
//
|
|
306
|
-
//
|
|
307
|
-
//
|
|
308
|
-
//
|
|
309
|
-
// exercised shape, not a hack. A sync fast-path for
|
|
310
|
-
// the no-preprocessor branches would fork this function into two shapes to save a single
|
|
311
|
-
// microtask on a call that only ever runs at Metro build time, content-hash-cached — not worth
|
|
312
|
-
// the duplication.
|
|
324
|
+
// Async uniformly, including branches that never touch a preprocessor: compileSfc() itself is
|
|
325
|
+
// async now (a scss/sass/less/stylus block awaits preprocessors.ts's compile()), and Metro's own
|
|
326
|
+
// metro-transform-worker already awaits transformer.transform(...), so this is a supported
|
|
327
|
+
// shape - not worth forking a sync fast-path to save one microtask on a build-time,
|
|
328
|
+
// content-hash-cached call.
|
|
313
329
|
module.exports.transform = async function transform(params) {
|
|
314
330
|
if (params.filename.endsWith('.vue')) {
|
|
315
331
|
const code = await compileSfc(params.src, params.filename);
|
|
316
|
-
// Re-label as .tsx so RN's transformer strips
|
|
317
|
-
//
|
|
332
|
+
// Re-label as .tsx so RN's transformer strips TS from <script setup lang="ts">. Metro
|
|
333
|
+
// tracks the real path separately.
|
|
318
334
|
return upstreamTransformer.transform({
|
|
319
335
|
...params,
|
|
320
336
|
src: code,
|
|
321
337
|
filename: params.filename + '.tsx',
|
|
322
338
|
});
|
|
323
339
|
}
|
|
324
|
-
// A standalone style file (
|
|
325
|
-
//
|
|
326
|
-
// .vue files exactly like from any other adapter's example. isStyleFile recognizes
|
|
327
|
-
// .css/.scss/.sass/.less/.styl/.stylus (+ each .module.* twin).
|
|
340
|
+
// A standalone style file (not a .vue file's inline <style> block) via the framework-agnostic
|
|
341
|
+
// compileCssFile path. isStyleFile recognizes .css/.scss/.sass/.less/.styl/.stylus (+ .module.* twins).
|
|
328
342
|
if (isStyleFile(params.filename)) {
|
|
329
343
|
const { code } = await compileCssFile(params.src, params.filename);
|
|
330
|
-
return upstreamTransformer.transform({
|
|
344
|
+
return upstreamTransformer.transform({
|
|
345
|
+
...params,
|
|
346
|
+
src: code,
|
|
347
|
+
filename: params.filename + '.js',
|
|
348
|
+
});
|
|
331
349
|
}
|
|
332
350
|
return upstreamTransformer.transform(params);
|
|
333
351
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@symbiote-native/vue",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.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": {
|
|
@@ -46,8 +46,8 @@
|
|
|
46
46
|
"@vue/compiler-sfc": "^3.5.13",
|
|
47
47
|
"@vue/runtime-core": "^3.5.13",
|
|
48
48
|
"typescript": "~6.0.0",
|
|
49
|
-
"@symbiote-native/components": "0.
|
|
50
|
-
"@symbiote-native/css-parser": "0.
|
|
49
|
+
"@symbiote-native/components": "0.5.0",
|
|
50
|
+
"@symbiote-native/css-parser": "0.4.0"
|
|
51
51
|
},
|
|
52
52
|
"peerDependencies": {
|
|
53
53
|
"@symbiote-native/engine": ">=0.1.7",
|
|
@@ -55,8 +55,8 @@
|
|
|
55
55
|
"react-native": ">=0.86"
|
|
56
56
|
},
|
|
57
57
|
"devDependencies": {
|
|
58
|
-
"@symbiote-native/engine": "0.
|
|
59
|
-
"@symbiote-native/test-utils": "0.
|
|
58
|
+
"@symbiote-native/engine": "0.3.0",
|
|
59
|
+
"@symbiote-native/test-utils": "0.2.0"
|
|
60
60
|
},
|
|
61
61
|
"scripts": {
|
|
62
62
|
"typecheck": "tsc --build",
|