/
parseSfc.ts
122 lines (118 loc) · 3 KB
/
parseSfc.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
import type { CompilerError, SFCDescriptor, SFCBlock, SFCStyleBlock, SFCScriptBlock, SFCTemplateBlock, SFCParseResult } from '@vue/compiler-sfc';
import type { ElementNode, SourceLocation } from '@vue/compiler-dom';
import * as compiler from '@vue/compiler-dom';
export function parse(source: string): SFCParseResult {
const errors: CompilerError[] = [];
const ast = compiler.parse(source, {
// there are no components at SFC parsing level
isNativeTag: () => true,
// preserve all whitespaces
isPreTag: () => true,
parseMode: 'sfc',
onError: e => {
errors.push(e);
},
comments: true,
});
const descriptor: SFCDescriptor = {
filename: 'anonymous.vue',
source,
template: null,
script: null,
scriptSetup: null,
styles: [],
customBlocks: [],
cssVars: [],
slotted: false,
shouldForceReload: () => false,
};
ast.children.forEach(node => {
if (node.type !== compiler.NodeTypes.ELEMENT) {
return;
}
switch (node.tag) {
case 'template':
descriptor.template = createBlock(node, source) as SFCTemplateBlock;
break;
case 'script':
const scriptBlock = createBlock(node, source) as SFCScriptBlock;
const isSetup = !!scriptBlock.attrs.setup;
if (isSetup && !descriptor.scriptSetup) {
descriptor.scriptSetup = scriptBlock;
break;
}
if (!isSetup && !descriptor.script) {
descriptor.script = scriptBlock;
break;
}
break;
case 'style':
const styleBlock = createBlock(node, source) as SFCStyleBlock;
descriptor.styles.push(styleBlock);
break;
default:
descriptor.customBlocks.push(createBlock(node, source));
break;
}
});
return {
descriptor,
errors,
};
}
function createBlock(node: ElementNode, source: string) {
const type = node.tag;
let { start, end } = node.loc;
let content = '';
if (node.children.length) {
start = node.children[0].loc.start;
end = node.children[node.children.length - 1].loc.end;
content = source.slice(start.offset, end.offset);
}
else {
const offset = node.loc.source.indexOf(`</`);
if (offset > -1) {
start = {
line: start.line,
column: start.column + offset,
offset: start.offset + offset
};
}
end = Object.assign({}, start);
}
const loc: SourceLocation = {
source: content,
start,
end
};
const attrs: Record<string, any> = {};
const block: SFCBlock & Pick<SFCStyleBlock, 'scoped' | 'module'> & Pick<SFCScriptBlock, 'setup'> = {
type,
content,
loc,
attrs
};
node.props.forEach(p => {
if (p.type === compiler.NodeTypes.ATTRIBUTE) {
attrs[p.name] = p.value ? p.value.content || true : true;
if (p.name === 'lang') {
block.lang = p.value && p.value.content;
}
else if (p.name === 'src') {
block.src = p.value && p.value.content;
}
else if (type === 'style') {
if (p.name === 'scoped') {
block.scoped = true;
}
else if (p.name === 'module') {
block.module = attrs[p.name];
}
}
else if (type === 'script' && p.name === 'setup') {
block.setup = attrs.setup;
}
}
});
return block;
}