Skip to content

Commit ff7f231

Browse files
posvayyx990803
authored andcommitted
Support custom blocks in SFC parser (vuejs#4157)
This allow to use other block appart from `template`, `script` or `style` in the SFC parser. This allows such things as writing tests or examples directly into the SFC file. Those are meant to be handled by programs others than vue-loader like vue-play.
1 parent 8bf2653 commit ff7f231

File tree

3 files changed

+85
-15
lines changed

3 files changed

+85
-15
lines changed

flow/compiler.js

+10
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,16 @@ declare type SFCDescriptor = {
145145
template: ?SFCBlock;
146146
script: ?SFCBlock;
147147
styles: Array<SFCBlock>;
148+
customBlocks: Array<SFCCustomBlock>;
149+
}
150+
151+
declare type SFCCustomBlock = {
152+
type: string;
153+
content: string;
154+
start?: number;
155+
end?: number;
156+
src?: string;
157+
attrs: {[attribute:string]: string};
148158
}
149159

150160
declare type SFCBlock = {

src/sfc/parser.js

+29-15
Original file line numberDiff line numberDiff line change
@@ -22,10 +22,11 @@ export function parseComponent (
2222
const sfc: SFCDescriptor = {
2323
template: null,
2424
script: null,
25-
styles: []
25+
styles: [],
26+
customBlocks: []
2627
}
2728
let depth = 0
28-
let currentBlock: ?SFCBlock = null
29+
let currentBlock: ?(SFCBlock | SFCCustomBlock) = null
2930

3031
function start (
3132
tag: string,
@@ -34,17 +35,30 @@ export function parseComponent (
3435
start: number,
3536
end: number
3637
) {
37-
if (isSpecialTag(tag) && depth === 0) {
38-
currentBlock = {
39-
type: tag,
40-
content: '',
41-
start: end
42-
}
43-
checkAttrs(currentBlock, attrs)
44-
if (tag === 'style') {
45-
sfc.styles.push(currentBlock)
46-
} else {
47-
sfc[tag] = currentBlock
38+
if (depth === 0) {
39+
if (isSpecialTag(tag)) {
40+
currentBlock = {
41+
type: tag,
42+
content: '',
43+
start: end
44+
}
45+
checkAttrs(currentBlock, attrs)
46+
if (tag === 'style') {
47+
sfc.styles.push(currentBlock)
48+
} else {
49+
sfc[tag] = currentBlock
50+
}
51+
} else { // custom blocks
52+
currentBlock = {
53+
type: tag,
54+
content: '',
55+
start: end,
56+
attrs: attrs.reduce((cumulated, { name, value }) => {
57+
cumulated[name] = value
58+
return cumulated
59+
}, Object.create(null))
60+
}
61+
sfc.customBlocks.push(currentBlock)
4862
}
4963
}
5064
if (!unary) {
@@ -71,7 +85,7 @@ export function parseComponent (
7185
}
7286

7387
function end (tag: string, start: number, end: number) {
74-
if (isSpecialTag(tag) && depth === 1 && currentBlock) {
88+
if (depth === 1 && currentBlock) {
7589
currentBlock.end = start
7690
let text = deindent(content.slice(currentBlock.start, currentBlock.end))
7791
// pad content so that linters and pre-processors can output correct
@@ -85,7 +99,7 @@ export function parseComponent (
8599
depth--
86100
}
87101

88-
function padContent (block: SFCBlock) {
102+
function padContent (block: SFCBlock | SFCCustomBlock) {
89103
const offset = content.slice(0, block.start).split(splitRE).length
90104
const padChar = block.type === 'script' && !block.lang
91105
? '//\n'

test/unit/modules/sfc/sfc-parser.spec.js

+46
Original file line numberDiff line numberDiff line change
@@ -77,4 +77,50 @@ describe('Single File Component parser', () => {
7777
`)
7878
expect(res.template.content.trim()).toBe(`div\n h1(v-if='1 < 2') hello`)
7979
})
80+
81+
it('should handle custom blocks without without parsing them', () => {
82+
const res = parseComponent(`
83+
<template>
84+
<div></div>
85+
</template>
86+
<example name="simple">
87+
<my-button ref="button">Hello</my-button>
88+
</example>
89+
<example name="with props">
90+
<my-button color="red">Hello</my-button>
91+
</example>
92+
<test name="simple" foo="bar">
93+
export default function simple (vm) {
94+
describe('Hello', () => {
95+
it('should display Hello', () => {
96+
this.vm.$refs.button.$el.innerText.should.equal('Hello')
97+
}))
98+
}))
99+
}
100+
</test>
101+
`)
102+
expect(res.customBlocks.length).toBe(3)
103+
104+
const simpleExample = res.customBlocks[0]
105+
expect(simpleExample.type).toBe('example')
106+
expect(simpleExample.content.trim()).toBe('<my-button ref="button">Hello</my-button>')
107+
expect(simpleExample.attrs.name).toBe('simple')
108+
109+
const withProps = res.customBlocks[1]
110+
expect(withProps.type).toBe('example')
111+
expect(withProps.content.trim()).toBe('<my-button color="red">Hello</my-button>')
112+
expect(withProps.attrs.name).toBe('with props')
113+
114+
const simpleTest = res.customBlocks[2]
115+
expect(simpleTest.type).toBe('test')
116+
expect(simpleTest.content.trim()).toBe(`export default function simple (vm) {
117+
describe('Hello', () => {
118+
it('should display Hello', () => {
119+
this.vm.$refs.button.$el.innerText.should.equal('Hello')
120+
}))
121+
}))
122+
}`)
123+
expect(simpleTest.attrs.name).toBe('simple')
124+
expect(simpleTest.attrs.foo).toBe('bar')
125+
})
80126
})

0 commit comments

Comments
 (0)