@weapp-vite/volar 2.1.0 → 2.1.2

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 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. 相关链接
@@ -1,4 +1,5 @@
1
1
  export declare const BLOCK_TYPE = "json";
2
+ export declare const DEFINE_OPTIONS_CACHE_MAX = 128;
2
3
  export declare const JS_LANG = "js";
3
4
  export declare const JSONC_LANG = "jsonc";
4
5
  export declare const JSON_LANG = "json";
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(code, tsModule, lang) {
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(code, tsModule, lang) {
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
- setBindingType(functionBindings, name, tsModule.isPropertyAssignment(entry) && (tsModule.isArrowFunction(entry.initializer) || tsModule.isFunctionExpression(entry.initializer)) ? getFunctionSignatureTypeText(entry.initializer, sourceFile) : tsModule.isMethodDeclaration(entry) ? getFunctionSignatureTypeText(entry, sourceFile) : "(...args: any[]) => any");
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
- setBindingType(valueBindings, name, tsModule.isPropertyAssignment(property) ? getExpressionTypeText(property.initializer, tsModule, sourceFile) : "(...args: any[]) => any");
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 topLevelBindings = collectTopLevelBindingNames(code, tsModule, lang);
497
- const bindings = collectDefineOptionsTemplateBindings(code, tsModule, lang);
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
- let tsModule = ctx?.modules?.typescript;
534
- if (!tsModule) try {
535
- tsModule = require$1("typescript");
536
- } catch {
537
- tsModule = void 0;
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 = parseVueSfc(content, fileName);
546
- if (!parsed) return;
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(code, tsModule, lang) {
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(code, tsModule, lang) {
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
- setBindingType(functionBindings, name, tsModule.isPropertyAssignment(entry) && (tsModule.isArrowFunction(entry.initializer) || tsModule.isFunctionExpression(entry.initializer)) ? getFunctionSignatureTypeText(entry.initializer, sourceFile) : tsModule.isMethodDeclaration(entry) ? getFunctionSignatureTypeText(entry, sourceFile) : "(...args: any[]) => any");
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
- setBindingType(valueBindings, name, tsModule.isPropertyAssignment(property) ? getExpressionTypeText(property.initializer, tsModule, sourceFile) : "(...args: any[]) => any");
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 topLevelBindings = collectTopLevelBindingNames(code, tsModule, lang);
473
- const bindings = collectDefineOptionsTemplateBindings(code, tsModule, lang);
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
- let tsModule = ctx?.modules?.typescript;
510
- if (!tsModule) try {
511
- tsModule = require("typescript");
512
- } catch {
513
- tsModule = void 0;
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 = parseVueSfc(content, fileName);
522
- if (!parsed) return;
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,10 @@
1
+ import type { SFCParseResult, VueLanguagePlugin } from '@vue/language-core';
2
+ type CompilerDOM = Parameters<VueLanguagePlugin>[0]['modules']['@vue/compiler-dom'];
3
+ export interface SfcTextChange {
4
+ start: number;
5
+ end: number;
6
+ newText: string;
7
+ }
8
+ export declare function parseSfc(compilerDom: CompilerDOM, source: string, filename: string): SFCParseResult;
9
+ export declare function updateSfc(sfc: SFCParseResult, change: SfcTextChange): SFCParseResult | undefined;
10
+ export {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@weapp-vite/volar",
3
- "version": "2.1.0",
3
+ "version": "2.1.2",
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",