Skip to content

Commit e236f80

Browse files
committed
parser for single-file components
1 parent b6fb485 commit e236f80

File tree

14 files changed

+255
-112
lines changed

14 files changed

+255
-112
lines changed

build/build.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ var builds = [
6565
{
6666
entry: 'src/entries/web-compiler.js',
6767
format: 'cjs',
68-
external: ['entities'],
68+
external: ['entities', 'de-indent'],
6969
out: 'dist/compiler.js'
7070
},
7171
// Web server renderer (CommonJS).

flow/compiler.js

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,18 @@ declare type CompilerOptions = {
1616
delimiters?: [string, string] // template delimiters
1717
}
1818

19+
declare type CompiledResult = {
20+
ast: ?ASTElement,
21+
render: string,
22+
staticRenderFns: Array<string>,
23+
errors?: Array<string>
24+
}
25+
26+
declare type CompiledFunctionResult = {
27+
render: Function,
28+
staticRenderFns: Array<Function>
29+
}
30+
1931
declare type ModuleOptions = {
2032
transformNode: (el: ASTElement) => void, // transform an element's AST node
2133
genData: (el: ASTElement) => string, // generate extra data string for an element
@@ -112,3 +124,27 @@ declare type ASTText = {
112124
text: string,
113125
static?: boolean
114126
}
127+
128+
// SFC-parser related declarations
129+
130+
declare module 'de-indent' {
131+
declare var exports: {
132+
(str: string): string;
133+
}
134+
}
135+
136+
// an object format describing a single-file component.
137+
declare type SFCDescriptor = {
138+
template: ?SFCBlock,
139+
script: ?SFCBlock,
140+
styles: Array<SFCBlock>
141+
}
142+
143+
declare type SFCBlock = {
144+
type: "template" | "script" | "style",
145+
content: string,
146+
lang?: string,
147+
scoped?: boolean,
148+
src?: boolean,
149+
map?: Object
150+
}

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@
5151
"chromedriver": "^2.21.2",
5252
"codecov.io": "^0.1.6",
5353
"cross-spawn": "^4.0.0",
54+
"de-indent": "^1.0.2",
5455
"entities": "^1.1.1",
5556
"eslint": "^2.11.0",
5657
"eslint-config-vue": "^1.0.3",

src/compiler/error-detector.js

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,11 @@
33
import { dirRE } from './parser/index'
44

55
// detect problematic expressions in a template
6-
export function detectErrors (ast: ASTNode): Array<string> {
6+
export function detectErrors (ast: ?ASTNode): Array<string> {
77
const errors: Array<string> = []
8-
checkNode(ast, errors)
8+
if (ast) {
9+
checkNode(ast, errors)
10+
}
911
return errors
1012
}
1113

src/compiler/index.js

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,7 @@ import { generate } from './codegen'
1010
export function compile (
1111
template: string,
1212
options: CompilerOptions
13-
): {
14-
ast: ?ASTElement,
15-
render: string,
16-
staticRenderFns: Array<string>
17-
} {
13+
): CompiledResult {
1814
const ast = parse(template.trim(), options)
1915
optimize(ast, options)
2016
const code = generate(ast, options)

src/compiler/parser/html-parser.js

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
*/
1111

1212
import { decodeHTML } from 'entities'
13-
import { makeMap } from 'shared/util'
13+
import { makeMap, no } from 'shared/util'
1414
import { isNonPhrasingTag, canBeLeftOpenTag } from 'web/util/index'
1515

1616
// Regular Expressions for parsing tags and attributes
@@ -61,12 +61,13 @@ export function parseHTML (html, handler) {
6161
const stack = []
6262
const attribute = attrForHandler(handler)
6363
const expectHTML = handler.expectHTML
64-
const isUnaryTag = handler.isUnaryTag || (() => false)
64+
const isUnaryTag = handler.isUnaryTag || no
65+
const isSpecialTag = handler.isSpecialTag || special
6566
let last, prevTag, nextTag, lastTag
6667
while (html) {
6768
last = html
6869
// Make sure we're not in a script or style element
69-
if (!lastTag || !special(lastTag)) {
70+
if (!lastTag || !isSpecialTag(lastTag)) {
7071
const textEnd = html.indexOf('<')
7172
if (textEnd === 0) {
7273
// Comment:

src/compiler/parser/sfc-parser.js

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
/* @flow */
2+
3+
import { parseHTML } from './html-parser'
4+
import { makeMap } from 'shared/util'
5+
import deindent from 'de-indent'
6+
7+
const isSpecialTag = makeMap('script,style,template', true)
8+
9+
/**
10+
* Parse a single-file component (*.vue) file into an SFC Descriptor Object.
11+
*/
12+
export function parseSFC (content: string): SFCDescriptor {
13+
const sfc: SFCDescriptor = {
14+
template: null,
15+
script: null,
16+
styles: []
17+
}
18+
let depth = 0
19+
let currentBlock
20+
21+
function start (tag, attrs) {
22+
depth++
23+
if (depth > 1) {
24+
return
25+
}
26+
if (isSpecialTag(tag)) {
27+
const block: SFCBlock = currentBlock = {
28+
type: tag,
29+
content: ''
30+
}
31+
for (let i = 0; i < attrs.length; i++) {
32+
const attr = attrs[i]
33+
if (attr.name === 'lang') {
34+
block.lang = attr.value
35+
}
36+
if (attr.name === 'scoped') {
37+
block.scoped = true
38+
}
39+
if (attr.name === 'src') {
40+
block.src = attr.value
41+
}
42+
}
43+
if (tag === 'style') {
44+
sfc.styles.push(block)
45+
} else {
46+
sfc[tag] = block
47+
}
48+
}
49+
}
50+
51+
function end () {
52+
depth--
53+
currentBlock = null
54+
}
55+
56+
function chars (text) {
57+
if (currentBlock) {
58+
currentBlock.content = deindent(text)
59+
}
60+
}
61+
62+
parseHTML(content, {
63+
isSpecialTag,
64+
start,
65+
end,
66+
chars
67+
})
68+
69+
return sfc
70+
}

src/entries/web-compiler.js

Lines changed: 12 additions & 97 deletions
Original file line numberDiff line numberDiff line change
@@ -1,104 +1,19 @@
1-
/* @flow */
2-
3-
import { extend, genStaticKeys, noop } from 'shared/util'
4-
import { warn } from 'core/util/debug'
5-
import { compile as baseCompile } from 'compiler/index'
1+
import { compile as baseCompile } from 'web/compiler/index'
62
import { detectErrors } from 'compiler/error-detector'
7-
import modules from 'web/compiler/modules/index'
8-
import directives from 'web/compiler/directives/index'
9-
import { isIE, isReservedTag, isUnaryTag, mustUseProp, getTagNamespace } from 'web/util/index'
103

11-
// detect possible CSP restriction
12-
/* istanbul ignore if */
13-
if (process.env.NODE_ENV !== 'production') {
14-
try {
15-
new Function('return 1')
16-
} catch (e) {
17-
if (e.toString().match(/unsafe-eval|CSP/)) {
18-
warn(
19-
'It seems you are using the standalone build of Vue.js in an ' +
20-
'environment with Content Security Policy that prohibits unsafe-eval. ' +
21-
'The template compiler cannot work in this environment. Consider ' +
22-
'relaxing the policy to allow unsafe-eval or pre-compiling your ' +
23-
'templates into render functions.'
24-
)
25-
}
26-
}
27-
}
28-
29-
type CompiledFunctions = {
30-
render: Function,
31-
staticRenderFns: Array<Function>
32-
}
33-
34-
const cache1: { [key: string]: CompiledFunctions } = Object.create(null)
35-
const cache2: { [key: string]: CompiledFunctions } = Object.create(null)
36-
37-
export const baseOptions: CompilerOptions = {
38-
isIE,
39-
expectHTML: true,
40-
preserveWhitespace: true,
41-
modules,
42-
staticKeys: genStaticKeys(modules),
43-
directives,
44-
isReservedTag,
45-
isUnaryTag,
46-
mustUseProp,
47-
getTagNamespace
48-
}
4+
export { parseSFC as parseComponent } from 'compiler/parser/sfc-parser'
5+
export { compileToFunctions } from 'web/compiler/index'
496

507
export function compile (
518
template: string,
52-
options?: CompilerOptions
53-
): {
54-
ast: ?ASTElement,
55-
render: string,
56-
staticRenderFns: Array<string>
57-
} {
58-
options = options
59-
? extend(extend({}, baseOptions), options)
60-
: baseOptions
61-
return baseCompile(template, options)
62-
}
63-
64-
export function compileToFunctions (
65-
template: string,
66-
options?: CompilerOptions,
67-
vm: Component
68-
): CompiledFunctions {
69-
const cache = options && options.preserveWhitespace === false ? cache1 : cache2
70-
const key = options && options.delimiters
71-
? String(options.delimiters) + template
72-
: template
73-
if (cache[key]) {
74-
return cache[key]
75-
}
76-
const res = {}
77-
const compiled = compile(template, options)
78-
res.render = makeFunction(compiled.render)
79-
const l = compiled.staticRenderFns.length
80-
res.staticRenderFns = new Array(l)
81-
for (let i = 0; i < l; i++) {
82-
res.staticRenderFns[i] = makeFunction(compiled.staticRenderFns[i])
83-
}
84-
if (process.env.NODE_ENV !== 'production') {
85-
if (res.render === noop || res.staticRenderFns.some(fn => fn === noop)) {
86-
const errors = compiled.ast ? detectErrors(compiled.ast, warn) : []
87-
warn(
88-
`failed to compile template:\n\n${template}\n\n` +
89-
errors.join('\n') +
90-
'\n\n',
91-
vm
92-
)
9+
options?: Object
10+
): CompiledResult {
11+
const errors = []
12+
const compiled = baseCompile(template, {
13+
warn: msg => {
14+
errors.push(msg)
9315
}
94-
}
95-
return (cache[key] = res)
96-
}
97-
98-
function makeFunction (code) {
99-
try {
100-
return new Function(code)
101-
} catch (e) {
102-
return noop
103-
}
16+
})
17+
compiled.errors = errors.concat(detectErrors(compiled.ast))
18+
return compiled
10419
}

src/entries/web-runtime-with-compiler.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import Vue from './web-runtime'
44
import config from 'core/config'
55
import { warn, cached } from 'core/util/index'
66
import { query } from 'web/util/index'
7-
import { compileToFunctions } from './web-compiler'
7+
import { compileToFunctions } from 'web/compiler/index'
88

99
const idToTemplate = cached(id => {
1010
const el = query(id)

0 commit comments

Comments
 (0)