From c8cfd1606c92fd929a26a2a86c6123c9be5a7172 Mon Sep 17 00:00:00 2001 From: yoyo930021 Date: Mon, 11 Jul 2022 17:33:09 +0800 Subject: [PATCH] feat(vue-code-gan): add .js when import vue file for esm support --- .../vue-code-gen/src/generators/script.ts | 155 +++++++++++++++--- .../src/parsers/scriptImportRanges.ts | 35 ++++ .../vue-code-gen/src/parsers/scriptRanges.ts | 2 + .../src/parsers/scriptSetupRanges.ts | 2 + 4 files changed, 170 insertions(+), 24 deletions(-) create mode 100644 packages/vue-code-gen/src/parsers/scriptImportRanges.ts diff --git a/packages/vue-code-gen/src/generators/script.ts b/packages/vue-code-gen/src/generators/script.ts index f3bfc8067..3a0186e81 100644 --- a/packages/vue-code-gen/src/generators/script.ts +++ b/packages/vue-code-gen/src/generators/script.ts @@ -52,6 +52,7 @@ export function generate( writeScriptSrc(); writeScriptSetupImports(); + writeScriptImports(); writeScriptBeforeExportDefault(); writeScriptSetup(); writeScriptSetupTypes(); @@ -199,8 +200,25 @@ export function generate( if (!script) return; + function writeScriptRemoveImportRanges (end: number) { + if (scriptRanges?.imports.length) { + const removeTextRanges = scriptRanges?.imports?.map((r) => r.code).sort((a, b) => a.start - b.start); + + removeTextRanges.forEach((range, index) => { + const prevRangeEnd = (index !== 0) ? removeTextRanges[index - 1].end : 0; + if (prevRangeEnd >= range.start) return + addVirtualCode('script', prevRangeEnd, range.start); + }) + const lastRemoteTextRange = removeTextRanges[removeTextRanges.length - 1] + if (!lastRemoteTextRange || lastRemoteTextRange.end >= end) return + addVirtualCode('script', lastRemoteTextRange.end, end); + } else { + addVirtualCode('script', 0, end); + } + } + if (!!scriptSetup && scriptRanges?.exportDefault) { - addVirtualCode('script', 0, scriptRanges.exportDefault.expression.start); + writeScriptRemoveImportRanges(scriptRanges.exportDefault.expression.start); exportdefaultStart = codeGen.getText().length - (scriptRanges.exportDefault.expression.start - scriptRanges.exportDefault.start); } else { @@ -209,14 +227,14 @@ export function generate( isExportRawObject = script.content.substring(scriptRanges.exportDefault.expression.start, scriptRanges.exportDefault.expression.end).startsWith('{'); } if (isExportRawObject && shimComponentOptions && scriptRanges?.exportDefault) { - addVirtualCode('script', 0, scriptRanges.exportDefault.expression.start); + writeScriptRemoveImportRanges(scriptRanges.exportDefault.expression.start); codeGen.addText(`(await import('${vueLibName}')).defineComponent(`); addVirtualCode('script', scriptRanges.exportDefault.expression.start, scriptRanges.exportDefault.expression.end); codeGen.addText(`)`); addVirtualCode('script', scriptRanges.exportDefault.expression.end, script.content.length); } else { - addVirtualCode('script', 0, script.content.length); + writeScriptRemoveImportRanges(script.content.length); } } } @@ -267,29 +285,118 @@ export function generate( if (!scriptSetup) return; - if (!scriptSetupRanges) + if (!scriptSetupRanges?.imports.length) return; - codeGen.addCode( - scriptSetup.content.substring(0, scriptSetupRanges.importSectionEndOffset), - { - start: 0, - end: scriptSetupRanges.importSectionEndOffset, - }, - SourceMaps.Mode.Offset, - { - vueTag: 'scriptSetup', - capabilities: { - basic: lsType === 'script', - references: true, - definitions: lsType === 'script', - diagnostic: lsType === 'script', - rename: true, - completion: lsType === 'script', - semanticTokens: lsType === 'script', - }, - }, - ); + for (const importRange of scriptSetupRanges.imports) { + const moduleName = scriptSetup.content.substring(importRange.moduleSpecifier.start, importRange.moduleSpecifier.end) + if (moduleName.endsWith('.vue\'') || moduleName.endsWith('.vue\"')) { + codeGen.addCode( + scriptSetup.content.substring(importRange.code.start, importRange.moduleSpecifier.start), + { + start: importRange.code.start, + end: importRange.moduleSpecifier.start, + }, + SourceMaps.Mode.Offset, + { + vueTag: 'scriptSetup', + capabilities: { + basic: lsType === 'script', + references: true, + definitions: lsType === 'script', + diagnostic: lsType === 'script', + rename: true, + completion: lsType === 'script', + semanticTokens: lsType === 'script', + }, + }, + ) + const newModuleName = moduleName.replace(/\.vue(['"])$/, '.vue.js$1') + codeGen.addCode( + newModuleName, + { + start: importRange.moduleSpecifier.start, + end: importRange.moduleSpecifier.end, + }, + SourceMaps.Mode.Expand, + { + vueTag: 'scriptSetup', + capabilities: { + basic: lsType === 'script', + references: true, + definitions: lsType === 'script', + diagnostic: lsType === 'script', + rename: true, + completion: lsType === 'script', + semanticTokens: lsType === 'script', + }, + }, + ) + addVirtualCode('scriptSetup', importRange.moduleSpecifier.end, importRange.code.end) + } else { + addVirtualCode('scriptSetup', importRange.code.start, importRange.code.end) + } + codeGen.addText(`\n`); + } + } + function writeScriptImports() { + + if (!script) + return; + + if (!scriptRanges?.imports.length) + return; + + for (const importRange of scriptRanges.imports) { + const moduleName = script.content.substring(importRange.moduleSpecifier.start, importRange.moduleSpecifier.end) + if (moduleName.endsWith('.vue\'') || moduleName.endsWith('.vue\"')) { + codeGen.addCode( + script.content.substring(importRange.code.start, importRange.moduleSpecifier.start), + { + start: importRange.code.start, + end: importRange.moduleSpecifier.start, + }, + SourceMaps.Mode.Offset, + { + vueTag: 'script', + capabilities: { + basic: lsType === 'script', + references: true, + definitions: lsType === 'script', + diagnostic: lsType === 'script', + rename: true, + completion: lsType === 'script', + semanticTokens: lsType === 'script', + }, + }, + ) + const newModuleName = moduleName.replace(/.vue(['"])$/, '.vue.js$1') + codeGen.addCode( + newModuleName, + { + start: importRange.moduleSpecifier.start, + end: importRange.moduleSpecifier.end, + }, + SourceMaps.Mode.Expand, + { + vueTag: 'script', + capabilities: { + basic: lsType === 'script', + references: true, + definitions: lsType === 'script', + diagnostic: lsType === 'script', + rename: true, + completion: lsType === 'script', + semanticTokens: lsType === 'script', + }, + }, + ) + addVirtualCode('script', importRange.moduleSpecifier.end, importRange.code.end) + } else { + addVirtualCode('script', importRange.code.start, importRange.code.end) + } + codeGen.addText(`\n`); + } } function writeScriptSetup() { diff --git a/packages/vue-code-gen/src/parsers/scriptImportRanges.ts b/packages/vue-code-gen/src/parsers/scriptImportRanges.ts new file mode 100644 index 000000000..0f983c6da --- /dev/null +++ b/packages/vue-code-gen/src/parsers/scriptImportRanges.ts @@ -0,0 +1,35 @@ +import type * as ts from 'typescript/lib/tsserverlibrary'; +import { getStartEnd } from './scriptSetupRanges'; +import type { TextRange } from '../types'; + +export interface ScriptImportRanges extends ReturnType { } + +export function parseScriptImportRanges(ts: typeof import('typescript/lib/tsserverlibrary'), ast: ts.SourceFile, end: number) { + + const calls: { + code: TextRange, + // import BaseText from './vue.js' + // ^^^^^^^^^^ + moduleSpecifier: TextRange + }[] = []; + + ast.forEachChild(node => { + visitNode(node); + }); + + return calls; + + function visitNode(node: ts.Node) { + if (ts.isImportDeclaration(node)) { + const code = getStartEnd(node, ast) + if (code.start <= end) { + calls.push({ + code: code, + moduleSpecifier: getStartEnd(node.moduleSpecifier, ast) + }) + node.moduleSpecifier + } + } + node.forEachChild(child => visitNode(child)); + } +} diff --git a/packages/vue-code-gen/src/parsers/scriptRanges.ts b/packages/vue-code-gen/src/parsers/scriptRanges.ts index b5a4bb591..b7a195d3b 100644 --- a/packages/vue-code-gen/src/parsers/scriptRanges.ts +++ b/packages/vue-code-gen/src/parsers/scriptRanges.ts @@ -1,5 +1,6 @@ import { getStartEnd, parseBindingRanges } from './scriptSetupRanges'; import type { TextRange } from '../types'; +import { parseScriptImportRanges } from './scriptImportRanges'; export interface ScriptRanges extends ReturnType { } @@ -51,6 +52,7 @@ export function parseScriptRanges(ts: typeof import('typescript/lib/tsserverlibr }); return { + imports: parseScriptImportRanges(ts, ast, ast.getEnd()), exportDefault, bindings, }; diff --git a/packages/vue-code-gen/src/parsers/scriptSetupRanges.ts b/packages/vue-code-gen/src/parsers/scriptSetupRanges.ts index 93e9a5e07..723a6a744 100644 --- a/packages/vue-code-gen/src/parsers/scriptSetupRanges.ts +++ b/packages/vue-code-gen/src/parsers/scriptSetupRanges.ts @@ -1,3 +1,4 @@ +import { parseScriptImportRanges } from './scriptImportRanges'; import type * as ts from 'typescript/lib/tsserverlibrary'; import type { TextRange } from '../types'; @@ -42,6 +43,7 @@ export function parseScriptSetupRanges(ts: typeof import('typescript/lib/tsserve ast.forEachChild(child => visitNode(child, ast)); return { + imports: parseScriptImportRanges(ts, ast, importSectionEndOffset), importSectionEndOffset, notOnTopTypeExports, bindings,