@weapp-vite/volar 2.1.0 → 2.1.1
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 +7 -0
- package/dist/constants.d.ts +1 -0
- package/dist/index.cjs +217 -35
- package/dist/index.mjs +217 -35
- package/dist/parseSfc.d.ts +9 -0
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -56,6 +56,13 @@ pnpm add -D @weapp-vite/volar @weapp-core/schematics
|
|
|
56
56
|
pnpm --filter @weapp-vite/volar build
|
|
57
57
|
pnpm --filter @weapp-vite/volar test
|
|
58
58
|
pnpm --filter @weapp-vite/volar typecheck
|
|
59
|
+
pnpm --filter @weapp-vite/volar benchmark
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
benchmark 会为普通 SFC 和 `defineOptions` SFC 创建全新的 content 与 snapshot,并强制生成 service script 和全部 embedded code。对比修改前构建时可传入基线插件:
|
|
63
|
+
|
|
64
|
+
```bash
|
|
65
|
+
pnpm --filter @weapp-vite/volar benchmark -- --baseline /path/to/baseline/dist/index.mjs
|
|
59
66
|
```
|
|
60
67
|
|
|
61
68
|
## 7. 相关链接
|
package/dist/constants.d.ts
CHANGED
package/dist/index.cjs
CHANGED
|
@@ -232,6 +232,157 @@ function resolveEmbeddedJsonBlock(fileName, sfc, embeddedCode, tsModule, hasSche
|
|
|
232
232
|
]);
|
|
233
233
|
}
|
|
234
234
|
//#endregion
|
|
235
|
+
//#region src/parseSfc.ts
|
|
236
|
+
function parseAttr(property, node) {
|
|
237
|
+
if (!property.value) return true;
|
|
238
|
+
const { source, start } = property.value.loc;
|
|
239
|
+
const quote = source[0];
|
|
240
|
+
const quoted = (quote === "\"" || quote === "'") && source.endsWith(quote);
|
|
241
|
+
return {
|
|
242
|
+
text: quoted ? source.slice(1, -1) : source,
|
|
243
|
+
offset: (quoted ? start.offset + 1 : start.offset) - node.loc.start.offset
|
|
244
|
+
};
|
|
245
|
+
}
|
|
246
|
+
function repairIncompleteTemplate(descriptor, errors) {
|
|
247
|
+
const template = descriptor.template;
|
|
248
|
+
if (!template) return;
|
|
249
|
+
for (const error of errors) {
|
|
250
|
+
if (!("code" in error) || error.code !== 24 || error.loc?.start.line !== template.loc.start.line) continue;
|
|
251
|
+
const endTagOffset = template.content.lastIndexOf("<");
|
|
252
|
+
const endTagText = template.content.slice(endTagOffset).trimEnd();
|
|
253
|
+
if ("</template>".startsWith(endTagText)) {
|
|
254
|
+
template.loc.end.offset = template.loc.start.offset + endTagOffset;
|
|
255
|
+
template.content = template.content.slice(0, endTagOffset);
|
|
256
|
+
template.loc.source = template.content;
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
function createBlock(compilerDom, node, source) {
|
|
261
|
+
const type = node.tag;
|
|
262
|
+
let { start, end } = node.loc;
|
|
263
|
+
let content = "";
|
|
264
|
+
if (node.children.length) {
|
|
265
|
+
start = node.children[0].loc.start;
|
|
266
|
+
end = node.children[node.children.length - 1].loc.end;
|
|
267
|
+
content = source.slice(start.offset, end.offset);
|
|
268
|
+
} else {
|
|
269
|
+
const offset = node.loc.source.indexOf("</");
|
|
270
|
+
if (offset > -1) start = {
|
|
271
|
+
line: start.line,
|
|
272
|
+
column: start.column + offset,
|
|
273
|
+
offset: start.offset + offset
|
|
274
|
+
};
|
|
275
|
+
end = { ...start };
|
|
276
|
+
}
|
|
277
|
+
const attrs = {};
|
|
278
|
+
const block = {
|
|
279
|
+
type,
|
|
280
|
+
content,
|
|
281
|
+
loc: {
|
|
282
|
+
source: content,
|
|
283
|
+
start,
|
|
284
|
+
end
|
|
285
|
+
},
|
|
286
|
+
attrs
|
|
287
|
+
};
|
|
288
|
+
for (const property of node.props) {
|
|
289
|
+
if (property.type !== compilerDom.NodeTypes.ATTRIBUTE) continue;
|
|
290
|
+
attrs[property.name] = property.value ? property.value.content || true : true;
|
|
291
|
+
if (property.name === "lang") block.lang = property.value?.content;
|
|
292
|
+
else if (property.name === "src") block.__src = parseAttr(property, node);
|
|
293
|
+
else if (type === "script") {
|
|
294
|
+
const scriptBlock = block;
|
|
295
|
+
if (property.name === "vapor") {
|
|
296
|
+
scriptBlock.setup ??= attrs[property.name];
|
|
297
|
+
scriptBlock.__generic ??= true;
|
|
298
|
+
} else if (property.name === "setup") scriptBlock.setup = attrs[property.name];
|
|
299
|
+
else if (property.name === "generic") scriptBlock.__generic = parseAttr(property, node);
|
|
300
|
+
} else if (type === "style") {
|
|
301
|
+
const styleBlock = block;
|
|
302
|
+
if (property.name === "scoped") styleBlock.scoped = true;
|
|
303
|
+
else if (property.name === "module") styleBlock.__module = parseAttr(property, node);
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
return block;
|
|
307
|
+
}
|
|
308
|
+
function parseSfc(compilerDom, source, filename) {
|
|
309
|
+
const errors = [];
|
|
310
|
+
const ast = compilerDom.parse(source, {
|
|
311
|
+
isNativeTag: () => true,
|
|
312
|
+
isPreTag: () => true,
|
|
313
|
+
parseMode: "sfc",
|
|
314
|
+
onError: (error) => errors.push(error),
|
|
315
|
+
comments: true
|
|
316
|
+
});
|
|
317
|
+
const descriptor = {
|
|
318
|
+
filename,
|
|
319
|
+
source,
|
|
320
|
+
comments: [],
|
|
321
|
+
template: null,
|
|
322
|
+
script: null,
|
|
323
|
+
scriptSetup: null,
|
|
324
|
+
styles: [],
|
|
325
|
+
customBlocks: [],
|
|
326
|
+
cssVars: [],
|
|
327
|
+
slotted: false,
|
|
328
|
+
shouldForceReload: () => false
|
|
329
|
+
};
|
|
330
|
+
for (const node of ast.children) {
|
|
331
|
+
if (node.type === compilerDom.NodeTypes.COMMENT) {
|
|
332
|
+
descriptor.comments.push(node.content);
|
|
333
|
+
continue;
|
|
334
|
+
}
|
|
335
|
+
if (node.type !== compilerDom.NodeTypes.ELEMENT) continue;
|
|
336
|
+
if (node.tag === "template") {
|
|
337
|
+
descriptor.template = createBlock(compilerDom, node, source);
|
|
338
|
+
continue;
|
|
339
|
+
}
|
|
340
|
+
if (node.tag === "script") {
|
|
341
|
+
const scriptBlock = createBlock(compilerDom, node, source);
|
|
342
|
+
if (scriptBlock.setup && !descriptor.scriptSetup) descriptor.scriptSetup = scriptBlock;
|
|
343
|
+
else if (!scriptBlock.setup && !descriptor.script) descriptor.script = scriptBlock;
|
|
344
|
+
continue;
|
|
345
|
+
}
|
|
346
|
+
if (node.tag === "style") {
|
|
347
|
+
descriptor.styles.push(createBlock(compilerDom, node, source));
|
|
348
|
+
continue;
|
|
349
|
+
}
|
|
350
|
+
descriptor.customBlocks.push(createBlock(compilerDom, node, source));
|
|
351
|
+
}
|
|
352
|
+
repairIncompleteTemplate(descriptor, errors);
|
|
353
|
+
return {
|
|
354
|
+
descriptor,
|
|
355
|
+
errors
|
|
356
|
+
};
|
|
357
|
+
}
|
|
358
|
+
function updateSfc(sfc, change) {
|
|
359
|
+
const blocks = [
|
|
360
|
+
sfc.descriptor.template,
|
|
361
|
+
sfc.descriptor.script,
|
|
362
|
+
sfc.descriptor.scriptSetup,
|
|
363
|
+
...sfc.descriptor.styles,
|
|
364
|
+
...sfc.descriptor.customBlocks
|
|
365
|
+
].filter((block) => block !== null);
|
|
366
|
+
const hitBlock = blocks.find((block) => change.start >= block.loc.start.offset && change.end <= block.loc.end.offset);
|
|
367
|
+
if (!hitBlock) return;
|
|
368
|
+
const oldContent = hitBlock.content;
|
|
369
|
+
const relativeStart = change.start - hitBlock.loc.start.offset;
|
|
370
|
+
const relativeEnd = change.end - hitBlock.loc.start.offset;
|
|
371
|
+
const newContent = oldContent.slice(0, relativeStart) + change.newText + oldContent.slice(relativeEnd);
|
|
372
|
+
const endTagRE = new RegExp(`</\\s*${hitBlock.type}\\s*>`);
|
|
373
|
+
if (endTagRE.test(oldContent) !== endTagRE.test(newContent)) return;
|
|
374
|
+
hitBlock.content = newContent;
|
|
375
|
+
hitBlock.loc.source = newContent;
|
|
376
|
+
const lengthDiff = change.newText.length - (change.end - change.start);
|
|
377
|
+
for (const block of blocks) {
|
|
378
|
+
if (block.loc.start.offset > change.end) block.loc.start.offset += lengthDiff;
|
|
379
|
+
if (block.loc.end.offset >= change.end) block.loc.end.offset += lengthDiff;
|
|
380
|
+
}
|
|
381
|
+
const source = sfc.descriptor.source;
|
|
382
|
+
sfc.descriptor.source = source.slice(0, change.start) + change.newText + source.slice(change.end);
|
|
383
|
+
return sfc;
|
|
384
|
+
}
|
|
385
|
+
//#endregion
|
|
235
386
|
//#region src/scriptSetup.ts
|
|
236
387
|
function collectWxsModuleNames(templateContent) {
|
|
237
388
|
if (!templateContent) return [];
|
|
@@ -302,9 +453,7 @@ function collectBindingNames(name, tsModule, bindings) {
|
|
|
302
453
|
}
|
|
303
454
|
for (const element of name.elements) if (tsModule.isBindingElement(element)) collectBindingNames(element.name, tsModule, bindings);
|
|
304
455
|
}
|
|
305
|
-
function collectTopLevelBindingNames(
|
|
306
|
-
const scriptKind = lang === "ts" ? 3 : 1;
|
|
307
|
-
const sourceFile = tsModule.createSourceFile(`bindings.${lang}`, code, 99, true, scriptKind);
|
|
456
|
+
function collectTopLevelBindingNames(sourceFile, tsModule) {
|
|
308
457
|
const bindings = /* @__PURE__ */ new Set();
|
|
309
458
|
for (const statement of sourceFile.statements) {
|
|
310
459
|
if (tsModule.isVariableStatement(statement)) {
|
|
@@ -428,9 +577,7 @@ function getFunctionLikeReturnTypeText(node, tsModule, sourceFile) {
|
|
|
428
577
|
const returnedExpression = tsModule.isBlock(node.body) ? findReturnedExpression(node.body, tsModule) : findReturnedExpression(node.body, tsModule);
|
|
429
578
|
return returnedExpression ? getExpressionTypeText(returnedExpression, tsModule, sourceFile) : "any";
|
|
430
579
|
}
|
|
431
|
-
function collectDefineOptionsTemplateBindings(
|
|
432
|
-
const scriptKind = lang === "ts" ? 3 : 1;
|
|
433
|
-
const sourceFile = tsModule.createSourceFile(`script-setup.${lang}`, code, 99, true, scriptKind);
|
|
580
|
+
function collectDefineOptionsTemplateBindings(sourceFile, tsModule) {
|
|
434
581
|
const valueBindings = /* @__PURE__ */ new Map();
|
|
435
582
|
const functionBindings = /* @__PURE__ */ new Map();
|
|
436
583
|
const visit = (node) => {
|
|
@@ -445,7 +592,8 @@ function collectDefineOptionsTemplateBindings(code, tsModule, lang) {
|
|
|
445
592
|
if (!tsModule.isPropertyAssignment(entry) && !tsModule.isMethodDeclaration(entry) && !tsModule.isShorthandPropertyAssignment(entry)) continue;
|
|
446
593
|
const name = getPropertyNameText(entry.name, tsModule);
|
|
447
594
|
if (!name || !isIdentifierName(name)) continue;
|
|
448
|
-
|
|
595
|
+
const typeText = tsModule.isPropertyAssignment(entry) && (tsModule.isArrowFunction(entry.initializer) || tsModule.isFunctionExpression(entry.initializer)) ? getFunctionSignatureTypeText(entry.initializer, sourceFile) : tsModule.isMethodDeclaration(entry) ? getFunctionSignatureTypeText(entry, sourceFile) : "(...args: any[]) => any";
|
|
596
|
+
setBindingType(functionBindings, name, typeText);
|
|
449
597
|
}
|
|
450
598
|
continue;
|
|
451
599
|
}
|
|
@@ -479,7 +627,8 @@ function collectDefineOptionsTemplateBindings(code, tsModule, lang) {
|
|
|
479
627
|
if (!tsModule.isPropertyAssignment(property) && !tsModule.isMethodDeclaration(property) && !tsModule.isShorthandPropertyAssignment(property) && !tsModule.isGetAccessorDeclaration(property)) continue;
|
|
480
628
|
const name = getPropertyNameText(property.name, tsModule);
|
|
481
629
|
if (!name || !isIdentifierName(name)) continue;
|
|
482
|
-
|
|
630
|
+
const typeText = tsModule.isPropertyAssignment(property) ? getExpressionTypeText(property.initializer, tsModule, sourceFile) : "(...args: any[]) => any";
|
|
631
|
+
setBindingType(valueBindings, name, typeText);
|
|
483
632
|
}
|
|
484
633
|
}
|
|
485
634
|
}
|
|
@@ -493,8 +642,10 @@ function collectDefineOptionsTemplateBindings(code, tsModule, lang) {
|
|
|
493
642
|
};
|
|
494
643
|
}
|
|
495
644
|
function createDefineOptionsTemplateDeclarations(code, tsModule, lang) {
|
|
496
|
-
const
|
|
497
|
-
const
|
|
645
|
+
const scriptKind = lang === "ts" ? 3 : 1;
|
|
646
|
+
const sourceFile = tsModule.createSourceFile(`script-setup.${lang}`, code, 99, true, scriptKind);
|
|
647
|
+
const topLevelBindings = collectTopLevelBindingNames(sourceFile, tsModule);
|
|
648
|
+
const bindings = collectDefineOptionsTemplateBindings(sourceFile, tsModule);
|
|
498
649
|
const declarations = [];
|
|
499
650
|
for (const [name, typeText] of bindings.values) {
|
|
500
651
|
if (topLevelBindings.has(name)) continue;
|
|
@@ -519,43 +670,74 @@ try {
|
|
|
519
670
|
} catch {
|
|
520
671
|
hasSchematicsTypes = false;
|
|
521
672
|
}
|
|
522
|
-
function parseVueSfc(content, filename = "component.vue") {
|
|
523
|
-
try {
|
|
524
|
-
return require$1("@vue/compiler-sfc").parse(content, { filename });
|
|
525
|
-
} catch {
|
|
526
|
-
return;
|
|
527
|
-
}
|
|
528
|
-
}
|
|
529
673
|
/**
|
|
530
674
|
* Volar 语言插件:为 weapp 配置块提供类型与 schema 提示。
|
|
531
675
|
*/
|
|
532
676
|
const plugin = (ctx) => {
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
677
|
+
const tsModule = ctx?.modules?.typescript;
|
|
678
|
+
const compilerDom = ctx?.modules?.["@vue/compiler-dom"];
|
|
679
|
+
const rawSfcStates = /* @__PURE__ */ new WeakMap();
|
|
680
|
+
const defineOptionsDeclarationsCache = /* @__PURE__ */ new Map();
|
|
681
|
+
function getDefineOptionsDeclarations(code, lang) {
|
|
682
|
+
if (!tsModule) return "";
|
|
683
|
+
const key = `${lang}\0${code}`;
|
|
684
|
+
const cached = defineOptionsDeclarationsCache.get(key);
|
|
685
|
+
if (cached !== void 0) {
|
|
686
|
+
defineOptionsDeclarationsCache.delete(key);
|
|
687
|
+
defineOptionsDeclarationsCache.set(key, cached);
|
|
688
|
+
return cached;
|
|
689
|
+
}
|
|
690
|
+
const declarations = createDefineOptionsTemplateDeclarations(code, tsModule, lang);
|
|
691
|
+
if (defineOptionsDeclarationsCache.size >= 128) {
|
|
692
|
+
const oldestKey = defineOptionsDeclarationsCache.keys().next().value;
|
|
693
|
+
if (oldestKey !== void 0) defineOptionsDeclarationsCache.delete(oldestKey);
|
|
694
|
+
}
|
|
695
|
+
defineOptionsDeclarationsCache.set(key, declarations);
|
|
696
|
+
return declarations;
|
|
697
|
+
}
|
|
698
|
+
function restoreRawSfc(parsed) {
|
|
699
|
+
const state = rawSfcStates.get(parsed);
|
|
700
|
+
const scriptSetup = parsed.descriptor.scriptSetup;
|
|
701
|
+
if (state && !state.hadScriptSetup) parsed.descriptor.scriptSetup = null;
|
|
702
|
+
else if (state && scriptSetup && state.scriptSetupContent !== void 0) {
|
|
703
|
+
scriptSetup.content = state.scriptSetupContent;
|
|
704
|
+
scriptSetup.loc.source = state.scriptSetupSource ?? state.scriptSetupContent;
|
|
705
|
+
}
|
|
706
|
+
}
|
|
707
|
+
function enhanceSfc(parsed) {
|
|
708
|
+
const descriptor = parsed.descriptor;
|
|
709
|
+
const scriptSetup = descriptor.scriptSetup;
|
|
710
|
+
rawSfcStates.set(parsed, {
|
|
711
|
+
hadScriptSetup: Boolean(scriptSetup),
|
|
712
|
+
scriptSetupContent: scriptSetup?.content,
|
|
713
|
+
scriptSetupSource: scriptSetup?.loc.source
|
|
714
|
+
});
|
|
715
|
+
const wxsModuleNames = collectWxsModuleNames(descriptor.template?.content);
|
|
716
|
+
const scriptSetupLang = resolveScriptSetupLang(scriptSetup?.lang);
|
|
717
|
+
const defineOptionsDeclarations = scriptSetup?.content.includes("defineOptions") ? getDefineOptionsDeclarations(scriptSetup.content, scriptSetupLang) : "";
|
|
718
|
+
if (!wxsModuleNames.length && !defineOptionsDeclarations) return;
|
|
719
|
+
if (scriptSetup) {
|
|
720
|
+
scriptSetup.content = appendScriptSetupDeclarations(appendWxsDeclarations(scriptSetup.content, wxsModuleNames), defineOptionsDeclarations);
|
|
721
|
+
syncScriptBlockSource(scriptSetup);
|
|
722
|
+
} else descriptor.scriptSetup = createSyntheticScriptSetup(wxsModuleNames);
|
|
538
723
|
}
|
|
539
724
|
return {
|
|
540
725
|
name,
|
|
541
726
|
version: PLUGIN_VERSION,
|
|
542
727
|
order: -1,
|
|
543
728
|
parseSFC2(fileName, languageId, content) {
|
|
544
|
-
if (languageId !== "vue") return;
|
|
545
|
-
const parsed =
|
|
546
|
-
|
|
547
|
-
const descriptor = parsed.descriptor;
|
|
548
|
-
const wxsModuleNames = collectWxsModuleNames(descriptor.template?.content);
|
|
549
|
-
const scriptSetup = descriptor.scriptSetup;
|
|
550
|
-
const scriptSetupLang = resolveScriptSetupLang(scriptSetup?.lang);
|
|
551
|
-
const defineOptionsDeclarations = scriptSetup?.content && tsModule ? createDefineOptionsTemplateDeclarations(scriptSetup.content, tsModule, scriptSetupLang) : "";
|
|
552
|
-
if (!wxsModuleNames.length && !defineOptionsDeclarations) return parsed;
|
|
553
|
-
if (descriptor.scriptSetup) {
|
|
554
|
-
descriptor.scriptSetup.content = appendScriptSetupDeclarations(appendWxsDeclarations(descriptor.scriptSetup.content, wxsModuleNames), defineOptionsDeclarations);
|
|
555
|
-
syncScriptBlockSource(descriptor.scriptSetup);
|
|
556
|
-
} else descriptor.scriptSetup = createSyntheticScriptSetup(wxsModuleNames);
|
|
729
|
+
if (languageId !== "vue" || !compilerDom) return;
|
|
730
|
+
const parsed = parseSfc(compilerDom, content, fileName);
|
|
731
|
+
enhanceSfc(parsed);
|
|
557
732
|
return parsed;
|
|
558
733
|
},
|
|
734
|
+
updateSFC(oldResult, change) {
|
|
735
|
+
restoreRawSfc(oldResult);
|
|
736
|
+
const updated = updateSfc(oldResult, change);
|
|
737
|
+
if (!updated) return;
|
|
738
|
+
enhanceSfc(updated);
|
|
739
|
+
return updated;
|
|
740
|
+
},
|
|
559
741
|
getEmbeddedCodes(_, sfc) {
|
|
560
742
|
return getEmbeddedCodesFromCustomBlocks(sfc);
|
|
561
743
|
},
|
package/dist/index.mjs
CHANGED
|
@@ -208,6 +208,157 @@ function resolveEmbeddedJsonBlock(fileName, sfc, embeddedCode, tsModule, hasSche
|
|
|
208
208
|
]);
|
|
209
209
|
}
|
|
210
210
|
//#endregion
|
|
211
|
+
//#region src/parseSfc.ts
|
|
212
|
+
function parseAttr(property, node) {
|
|
213
|
+
if (!property.value) return true;
|
|
214
|
+
const { source, start } = property.value.loc;
|
|
215
|
+
const quote = source[0];
|
|
216
|
+
const quoted = (quote === "\"" || quote === "'") && source.endsWith(quote);
|
|
217
|
+
return {
|
|
218
|
+
text: quoted ? source.slice(1, -1) : source,
|
|
219
|
+
offset: (quoted ? start.offset + 1 : start.offset) - node.loc.start.offset
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
function repairIncompleteTemplate(descriptor, errors) {
|
|
223
|
+
const template = descriptor.template;
|
|
224
|
+
if (!template) return;
|
|
225
|
+
for (const error of errors) {
|
|
226
|
+
if (!("code" in error) || error.code !== 24 || error.loc?.start.line !== template.loc.start.line) continue;
|
|
227
|
+
const endTagOffset = template.content.lastIndexOf("<");
|
|
228
|
+
const endTagText = template.content.slice(endTagOffset).trimEnd();
|
|
229
|
+
if ("</template>".startsWith(endTagText)) {
|
|
230
|
+
template.loc.end.offset = template.loc.start.offset + endTagOffset;
|
|
231
|
+
template.content = template.content.slice(0, endTagOffset);
|
|
232
|
+
template.loc.source = template.content;
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
function createBlock(compilerDom, node, source) {
|
|
237
|
+
const type = node.tag;
|
|
238
|
+
let { start, end } = node.loc;
|
|
239
|
+
let content = "";
|
|
240
|
+
if (node.children.length) {
|
|
241
|
+
start = node.children[0].loc.start;
|
|
242
|
+
end = node.children[node.children.length - 1].loc.end;
|
|
243
|
+
content = source.slice(start.offset, end.offset);
|
|
244
|
+
} else {
|
|
245
|
+
const offset = node.loc.source.indexOf("</");
|
|
246
|
+
if (offset > -1) start = {
|
|
247
|
+
line: start.line,
|
|
248
|
+
column: start.column + offset,
|
|
249
|
+
offset: start.offset + offset
|
|
250
|
+
};
|
|
251
|
+
end = { ...start };
|
|
252
|
+
}
|
|
253
|
+
const attrs = {};
|
|
254
|
+
const block = {
|
|
255
|
+
type,
|
|
256
|
+
content,
|
|
257
|
+
loc: {
|
|
258
|
+
source: content,
|
|
259
|
+
start,
|
|
260
|
+
end
|
|
261
|
+
},
|
|
262
|
+
attrs
|
|
263
|
+
};
|
|
264
|
+
for (const property of node.props) {
|
|
265
|
+
if (property.type !== compilerDom.NodeTypes.ATTRIBUTE) continue;
|
|
266
|
+
attrs[property.name] = property.value ? property.value.content || true : true;
|
|
267
|
+
if (property.name === "lang") block.lang = property.value?.content;
|
|
268
|
+
else if (property.name === "src") block.__src = parseAttr(property, node);
|
|
269
|
+
else if (type === "script") {
|
|
270
|
+
const scriptBlock = block;
|
|
271
|
+
if (property.name === "vapor") {
|
|
272
|
+
scriptBlock.setup ??= attrs[property.name];
|
|
273
|
+
scriptBlock.__generic ??= true;
|
|
274
|
+
} else if (property.name === "setup") scriptBlock.setup = attrs[property.name];
|
|
275
|
+
else if (property.name === "generic") scriptBlock.__generic = parseAttr(property, node);
|
|
276
|
+
} else if (type === "style") {
|
|
277
|
+
const styleBlock = block;
|
|
278
|
+
if (property.name === "scoped") styleBlock.scoped = true;
|
|
279
|
+
else if (property.name === "module") styleBlock.__module = parseAttr(property, node);
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
return block;
|
|
283
|
+
}
|
|
284
|
+
function parseSfc(compilerDom, source, filename) {
|
|
285
|
+
const errors = [];
|
|
286
|
+
const ast = compilerDom.parse(source, {
|
|
287
|
+
isNativeTag: () => true,
|
|
288
|
+
isPreTag: () => true,
|
|
289
|
+
parseMode: "sfc",
|
|
290
|
+
onError: (error) => errors.push(error),
|
|
291
|
+
comments: true
|
|
292
|
+
});
|
|
293
|
+
const descriptor = {
|
|
294
|
+
filename,
|
|
295
|
+
source,
|
|
296
|
+
comments: [],
|
|
297
|
+
template: null,
|
|
298
|
+
script: null,
|
|
299
|
+
scriptSetup: null,
|
|
300
|
+
styles: [],
|
|
301
|
+
customBlocks: [],
|
|
302
|
+
cssVars: [],
|
|
303
|
+
slotted: false,
|
|
304
|
+
shouldForceReload: () => false
|
|
305
|
+
};
|
|
306
|
+
for (const node of ast.children) {
|
|
307
|
+
if (node.type === compilerDom.NodeTypes.COMMENT) {
|
|
308
|
+
descriptor.comments.push(node.content);
|
|
309
|
+
continue;
|
|
310
|
+
}
|
|
311
|
+
if (node.type !== compilerDom.NodeTypes.ELEMENT) continue;
|
|
312
|
+
if (node.tag === "template") {
|
|
313
|
+
descriptor.template = createBlock(compilerDom, node, source);
|
|
314
|
+
continue;
|
|
315
|
+
}
|
|
316
|
+
if (node.tag === "script") {
|
|
317
|
+
const scriptBlock = createBlock(compilerDom, node, source);
|
|
318
|
+
if (scriptBlock.setup && !descriptor.scriptSetup) descriptor.scriptSetup = scriptBlock;
|
|
319
|
+
else if (!scriptBlock.setup && !descriptor.script) descriptor.script = scriptBlock;
|
|
320
|
+
continue;
|
|
321
|
+
}
|
|
322
|
+
if (node.tag === "style") {
|
|
323
|
+
descriptor.styles.push(createBlock(compilerDom, node, source));
|
|
324
|
+
continue;
|
|
325
|
+
}
|
|
326
|
+
descriptor.customBlocks.push(createBlock(compilerDom, node, source));
|
|
327
|
+
}
|
|
328
|
+
repairIncompleteTemplate(descriptor, errors);
|
|
329
|
+
return {
|
|
330
|
+
descriptor,
|
|
331
|
+
errors
|
|
332
|
+
};
|
|
333
|
+
}
|
|
334
|
+
function updateSfc(sfc, change) {
|
|
335
|
+
const blocks = [
|
|
336
|
+
sfc.descriptor.template,
|
|
337
|
+
sfc.descriptor.script,
|
|
338
|
+
sfc.descriptor.scriptSetup,
|
|
339
|
+
...sfc.descriptor.styles,
|
|
340
|
+
...sfc.descriptor.customBlocks
|
|
341
|
+
].filter((block) => block !== null);
|
|
342
|
+
const hitBlock = blocks.find((block) => change.start >= block.loc.start.offset && change.end <= block.loc.end.offset);
|
|
343
|
+
if (!hitBlock) return;
|
|
344
|
+
const oldContent = hitBlock.content;
|
|
345
|
+
const relativeStart = change.start - hitBlock.loc.start.offset;
|
|
346
|
+
const relativeEnd = change.end - hitBlock.loc.start.offset;
|
|
347
|
+
const newContent = oldContent.slice(0, relativeStart) + change.newText + oldContent.slice(relativeEnd);
|
|
348
|
+
const endTagRE = new RegExp(`</\\s*${hitBlock.type}\\s*>`);
|
|
349
|
+
if (endTagRE.test(oldContent) !== endTagRE.test(newContent)) return;
|
|
350
|
+
hitBlock.content = newContent;
|
|
351
|
+
hitBlock.loc.source = newContent;
|
|
352
|
+
const lengthDiff = change.newText.length - (change.end - change.start);
|
|
353
|
+
for (const block of blocks) {
|
|
354
|
+
if (block.loc.start.offset > change.end) block.loc.start.offset += lengthDiff;
|
|
355
|
+
if (block.loc.end.offset >= change.end) block.loc.end.offset += lengthDiff;
|
|
356
|
+
}
|
|
357
|
+
const source = sfc.descriptor.source;
|
|
358
|
+
sfc.descriptor.source = source.slice(0, change.start) + change.newText + source.slice(change.end);
|
|
359
|
+
return sfc;
|
|
360
|
+
}
|
|
361
|
+
//#endregion
|
|
211
362
|
//#region src/scriptSetup.ts
|
|
212
363
|
function collectWxsModuleNames(templateContent) {
|
|
213
364
|
if (!templateContent) return [];
|
|
@@ -278,9 +429,7 @@ function collectBindingNames(name, tsModule, bindings) {
|
|
|
278
429
|
}
|
|
279
430
|
for (const element of name.elements) if (tsModule.isBindingElement(element)) collectBindingNames(element.name, tsModule, bindings);
|
|
280
431
|
}
|
|
281
|
-
function collectTopLevelBindingNames(
|
|
282
|
-
const scriptKind = lang === "ts" ? 3 : 1;
|
|
283
|
-
const sourceFile = tsModule.createSourceFile(`bindings.${lang}`, code, 99, true, scriptKind);
|
|
432
|
+
function collectTopLevelBindingNames(sourceFile, tsModule) {
|
|
284
433
|
const bindings = /* @__PURE__ */ new Set();
|
|
285
434
|
for (const statement of sourceFile.statements) {
|
|
286
435
|
if (tsModule.isVariableStatement(statement)) {
|
|
@@ -404,9 +553,7 @@ function getFunctionLikeReturnTypeText(node, tsModule, sourceFile) {
|
|
|
404
553
|
const returnedExpression = tsModule.isBlock(node.body) ? findReturnedExpression(node.body, tsModule) : findReturnedExpression(node.body, tsModule);
|
|
405
554
|
return returnedExpression ? getExpressionTypeText(returnedExpression, tsModule, sourceFile) : "any";
|
|
406
555
|
}
|
|
407
|
-
function collectDefineOptionsTemplateBindings(
|
|
408
|
-
const scriptKind = lang === "ts" ? 3 : 1;
|
|
409
|
-
const sourceFile = tsModule.createSourceFile(`script-setup.${lang}`, code, 99, true, scriptKind);
|
|
556
|
+
function collectDefineOptionsTemplateBindings(sourceFile, tsModule) {
|
|
410
557
|
const valueBindings = /* @__PURE__ */ new Map();
|
|
411
558
|
const functionBindings = /* @__PURE__ */ new Map();
|
|
412
559
|
const visit = (node) => {
|
|
@@ -421,7 +568,8 @@ function collectDefineOptionsTemplateBindings(code, tsModule, lang) {
|
|
|
421
568
|
if (!tsModule.isPropertyAssignment(entry) && !tsModule.isMethodDeclaration(entry) && !tsModule.isShorthandPropertyAssignment(entry)) continue;
|
|
422
569
|
const name = getPropertyNameText(entry.name, tsModule);
|
|
423
570
|
if (!name || !isIdentifierName(name)) continue;
|
|
424
|
-
|
|
571
|
+
const typeText = tsModule.isPropertyAssignment(entry) && (tsModule.isArrowFunction(entry.initializer) || tsModule.isFunctionExpression(entry.initializer)) ? getFunctionSignatureTypeText(entry.initializer, sourceFile) : tsModule.isMethodDeclaration(entry) ? getFunctionSignatureTypeText(entry, sourceFile) : "(...args: any[]) => any";
|
|
572
|
+
setBindingType(functionBindings, name, typeText);
|
|
425
573
|
}
|
|
426
574
|
continue;
|
|
427
575
|
}
|
|
@@ -455,7 +603,8 @@ function collectDefineOptionsTemplateBindings(code, tsModule, lang) {
|
|
|
455
603
|
if (!tsModule.isPropertyAssignment(property) && !tsModule.isMethodDeclaration(property) && !tsModule.isShorthandPropertyAssignment(property) && !tsModule.isGetAccessorDeclaration(property)) continue;
|
|
456
604
|
const name = getPropertyNameText(property.name, tsModule);
|
|
457
605
|
if (!name || !isIdentifierName(name)) continue;
|
|
458
|
-
|
|
606
|
+
const typeText = tsModule.isPropertyAssignment(property) ? getExpressionTypeText(property.initializer, tsModule, sourceFile) : "(...args: any[]) => any";
|
|
607
|
+
setBindingType(valueBindings, name, typeText);
|
|
459
608
|
}
|
|
460
609
|
}
|
|
461
610
|
}
|
|
@@ -469,8 +618,10 @@ function collectDefineOptionsTemplateBindings(code, tsModule, lang) {
|
|
|
469
618
|
};
|
|
470
619
|
}
|
|
471
620
|
function createDefineOptionsTemplateDeclarations(code, tsModule, lang) {
|
|
472
|
-
const
|
|
473
|
-
const
|
|
621
|
+
const scriptKind = lang === "ts" ? 3 : 1;
|
|
622
|
+
const sourceFile = tsModule.createSourceFile(`script-setup.${lang}`, code, 99, true, scriptKind);
|
|
623
|
+
const topLevelBindings = collectTopLevelBindingNames(sourceFile, tsModule);
|
|
624
|
+
const bindings = collectDefineOptionsTemplateBindings(sourceFile, tsModule);
|
|
474
625
|
const declarations = [];
|
|
475
626
|
for (const [name, typeText] of bindings.values) {
|
|
476
627
|
if (topLevelBindings.has(name)) continue;
|
|
@@ -495,43 +646,74 @@ try {
|
|
|
495
646
|
} catch {
|
|
496
647
|
hasSchematicsTypes = false;
|
|
497
648
|
}
|
|
498
|
-
function parseVueSfc(content, filename = "component.vue") {
|
|
499
|
-
try {
|
|
500
|
-
return require("@vue/compiler-sfc").parse(content, { filename });
|
|
501
|
-
} catch {
|
|
502
|
-
return;
|
|
503
|
-
}
|
|
504
|
-
}
|
|
505
649
|
/**
|
|
506
650
|
* Volar 语言插件:为 weapp 配置块提供类型与 schema 提示。
|
|
507
651
|
*/
|
|
508
652
|
const plugin = (ctx) => {
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
653
|
+
const tsModule = ctx?.modules?.typescript;
|
|
654
|
+
const compilerDom = ctx?.modules?.["@vue/compiler-dom"];
|
|
655
|
+
const rawSfcStates = /* @__PURE__ */ new WeakMap();
|
|
656
|
+
const defineOptionsDeclarationsCache = /* @__PURE__ */ new Map();
|
|
657
|
+
function getDefineOptionsDeclarations(code, lang) {
|
|
658
|
+
if (!tsModule) return "";
|
|
659
|
+
const key = `${lang}\0${code}`;
|
|
660
|
+
const cached = defineOptionsDeclarationsCache.get(key);
|
|
661
|
+
if (cached !== void 0) {
|
|
662
|
+
defineOptionsDeclarationsCache.delete(key);
|
|
663
|
+
defineOptionsDeclarationsCache.set(key, cached);
|
|
664
|
+
return cached;
|
|
665
|
+
}
|
|
666
|
+
const declarations = createDefineOptionsTemplateDeclarations(code, tsModule, lang);
|
|
667
|
+
if (defineOptionsDeclarationsCache.size >= 128) {
|
|
668
|
+
const oldestKey = defineOptionsDeclarationsCache.keys().next().value;
|
|
669
|
+
if (oldestKey !== void 0) defineOptionsDeclarationsCache.delete(oldestKey);
|
|
670
|
+
}
|
|
671
|
+
defineOptionsDeclarationsCache.set(key, declarations);
|
|
672
|
+
return declarations;
|
|
673
|
+
}
|
|
674
|
+
function restoreRawSfc(parsed) {
|
|
675
|
+
const state = rawSfcStates.get(parsed);
|
|
676
|
+
const scriptSetup = parsed.descriptor.scriptSetup;
|
|
677
|
+
if (state && !state.hadScriptSetup) parsed.descriptor.scriptSetup = null;
|
|
678
|
+
else if (state && scriptSetup && state.scriptSetupContent !== void 0) {
|
|
679
|
+
scriptSetup.content = state.scriptSetupContent;
|
|
680
|
+
scriptSetup.loc.source = state.scriptSetupSource ?? state.scriptSetupContent;
|
|
681
|
+
}
|
|
682
|
+
}
|
|
683
|
+
function enhanceSfc(parsed) {
|
|
684
|
+
const descriptor = parsed.descriptor;
|
|
685
|
+
const scriptSetup = descriptor.scriptSetup;
|
|
686
|
+
rawSfcStates.set(parsed, {
|
|
687
|
+
hadScriptSetup: Boolean(scriptSetup),
|
|
688
|
+
scriptSetupContent: scriptSetup?.content,
|
|
689
|
+
scriptSetupSource: scriptSetup?.loc.source
|
|
690
|
+
});
|
|
691
|
+
const wxsModuleNames = collectWxsModuleNames(descriptor.template?.content);
|
|
692
|
+
const scriptSetupLang = resolveScriptSetupLang(scriptSetup?.lang);
|
|
693
|
+
const defineOptionsDeclarations = scriptSetup?.content.includes("defineOptions") ? getDefineOptionsDeclarations(scriptSetup.content, scriptSetupLang) : "";
|
|
694
|
+
if (!wxsModuleNames.length && !defineOptionsDeclarations) return;
|
|
695
|
+
if (scriptSetup) {
|
|
696
|
+
scriptSetup.content = appendScriptSetupDeclarations(appendWxsDeclarations(scriptSetup.content, wxsModuleNames), defineOptionsDeclarations);
|
|
697
|
+
syncScriptBlockSource(scriptSetup);
|
|
698
|
+
} else descriptor.scriptSetup = createSyntheticScriptSetup(wxsModuleNames);
|
|
514
699
|
}
|
|
515
700
|
return {
|
|
516
701
|
name,
|
|
517
702
|
version: PLUGIN_VERSION,
|
|
518
703
|
order: -1,
|
|
519
704
|
parseSFC2(fileName, languageId, content) {
|
|
520
|
-
if (languageId !== "vue") return;
|
|
521
|
-
const parsed =
|
|
522
|
-
|
|
523
|
-
const descriptor = parsed.descriptor;
|
|
524
|
-
const wxsModuleNames = collectWxsModuleNames(descriptor.template?.content);
|
|
525
|
-
const scriptSetup = descriptor.scriptSetup;
|
|
526
|
-
const scriptSetupLang = resolveScriptSetupLang(scriptSetup?.lang);
|
|
527
|
-
const defineOptionsDeclarations = scriptSetup?.content && tsModule ? createDefineOptionsTemplateDeclarations(scriptSetup.content, tsModule, scriptSetupLang) : "";
|
|
528
|
-
if (!wxsModuleNames.length && !defineOptionsDeclarations) return parsed;
|
|
529
|
-
if (descriptor.scriptSetup) {
|
|
530
|
-
descriptor.scriptSetup.content = appendScriptSetupDeclarations(appendWxsDeclarations(descriptor.scriptSetup.content, wxsModuleNames), defineOptionsDeclarations);
|
|
531
|
-
syncScriptBlockSource(descriptor.scriptSetup);
|
|
532
|
-
} else descriptor.scriptSetup = createSyntheticScriptSetup(wxsModuleNames);
|
|
705
|
+
if (languageId !== "vue" || !compilerDom) return;
|
|
706
|
+
const parsed = parseSfc(compilerDom, content, fileName);
|
|
707
|
+
enhanceSfc(parsed);
|
|
533
708
|
return parsed;
|
|
534
709
|
},
|
|
710
|
+
updateSFC(oldResult, change) {
|
|
711
|
+
restoreRawSfc(oldResult);
|
|
712
|
+
const updated = updateSfc(oldResult, change);
|
|
713
|
+
if (!updated) return;
|
|
714
|
+
enhanceSfc(updated);
|
|
715
|
+
return updated;
|
|
716
|
+
},
|
|
535
717
|
getEmbeddedCodes(_, sfc) {
|
|
536
718
|
return getEmbeddedCodesFromCustomBlocks(sfc);
|
|
537
719
|
},
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type * as CompilerDOM from '@vue/compiler-dom';
|
|
2
|
+
import type { SFCParseResult } from '@vue/language-core';
|
|
3
|
+
export interface SfcTextChange {
|
|
4
|
+
start: number;
|
|
5
|
+
end: number;
|
|
6
|
+
newText: string;
|
|
7
|
+
}
|
|
8
|
+
export declare function parseSfc(compilerDom: typeof CompilerDOM, source: string, filename: string): SFCParseResult;
|
|
9
|
+
export declare function updateSfc(sfc: SFCParseResult, change: SfcTextChange): SFCParseResult | undefined;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@weapp-vite/volar",
|
|
3
|
-
"version": "2.1.
|
|
3
|
+
"version": "2.1.1",
|
|
4
4
|
"description": "Volar plugin for weapp-vite - Provides IntelliSense and type checking for WeChat mini-program config blocks",
|
|
5
5
|
"author": "ice breaker <1324318532@qq.com>",
|
|
6
6
|
"license": "MIT",
|
|
@@ -43,6 +43,7 @@
|
|
|
43
43
|
"@weapp-core/schematics": "6.0.4"
|
|
44
44
|
},
|
|
45
45
|
"scripts": {
|
|
46
|
+
"benchmark": "node --import tsx benchmark/sfc.ts",
|
|
46
47
|
"dev": "tsdown -w --sourcemap",
|
|
47
48
|
"build": "tsdown --no-dts && tsc -p tsconfig.build.json",
|
|
48
49
|
"test": "vitest run",
|