Browse Source

better template compilation error warnings

dev
Evan You 9 years ago
parent
commit
b02b1e5ba4
  1. 1
      flow/compiler.js
  2. 38
      src/compiler/error-detector.js
  3. 1
      src/compiler/helpers.js
  4. 8
      src/compiler/index.js
  5. 13
      src/compiler/parser/index.js
  6. 35
      src/entries/web-compiler.js
  7. 2
      src/entries/web-runtime-with-compiler.js
  8. 9
      test/unit/features/options/template.spec.js

1
flow/compiler.js

@ -100,6 +100,7 @@ declare type ASTElement = {
declare type ASTExpression = {
type: 2,
expression: string,
text: string,
static?: boolean
}

38
src/compiler/error-detector.js

@ -0,0 +1,38 @@
/* @flow */
import { dirRE } from './parser/index'
// detect problematic expressions in a template
export function detectErrors (ast: ASTNode): Array<string> {
const errors: Array<string> = []
checkNode(ast, errors)
return errors
}
function checkNode (node: ASTNode, errors: Array<string>) {
if (node.type === 1) {
for (const name in node.attrsMap) {
if (dirRE.test(name)) {
const value = node.attrsMap[name]
if (value) {
checkExpression(value, `${name}="${value}"`, errors)
}
}
}
if (node.children) {
for (let i = 0; i < node.children.length; i++) {
checkNode(node.children[i], errors)
}
}
} else if (node.type === 2) {
checkExpression(node.expression, node.text, errors)
}
}
function checkExpression (exp: string, text: string, errors: Array<string>) {
try {
new Function(exp)
} catch (e) {
errors.push(`- invalid expression: ${text}`)
}
}

1
src/compiler/helpers.js

@ -82,7 +82,6 @@ export function getBindingAttr (
export function getAndRemoveAttr (el: ASTElement, name: string): ?string {
let val
if ((val = el.attrsMap[name]) != null) {
el.attrsMap[name] = null
const list = el.attrsList
for (let i = 0, l = list.length; i < l; i++) {
if (list[i].name === name) {

8
src/compiler/index.js

@ -11,10 +11,16 @@ export function compile (
template: string,
options: CompilerOptions
): {
ast: ?ASTElement,
render: string,
staticRenderFns: Array<string>
} {
const ast = parse(template.trim(), options)
optimize(ast, options)
return generate(ast, options)
const code = generate(ast, options)
return {
ast,
render: code.render,
staticRenderFns: code.staticRenderFns
}
}

13
src/compiler/parser/index.js

@ -15,7 +15,7 @@ import {
baseWarn
} from '../helpers'
const dirRE = /^v-|^@|^:/
export const dirRE = /^v-|^@|^:/
const bindRE = /^:|^v-bind:/
const onRE = /^@|^v-on:/
const argRE = /:(.*)$/
@ -194,9 +194,16 @@ export function parse (
if (text) {
let expression
if (!inPre && text !== ' ' && (expression = parseText(text, delimiters))) {
currentParent.children.push({ type: 2, expression })
currentParent.children.push({
type: 2,
expression,
text
})
} else {
currentParent.children.push({ type: 3, text })
currentParent.children.push({
type: 3,
text
})
}
}
}

35
src/entries/web-compiler.js

@ -1,8 +1,9 @@
/* @flow */
import { extend, genStaticKeys } from 'shared/util'
import { extend, genStaticKeys, noop } from 'shared/util'
import { warn } from 'core/util/debug'
import { compile as baseCompile } from 'compiler/index'
import { detectErrors } from 'compiler/error-detector'
import modules from 'web/compiler/modules/index'
import directives from 'web/compiler/directives/index'
import { isIE, isReservedTag, isUnaryTag, mustUseProp, getTagNamespace } from 'web/util/index'
@ -49,7 +50,11 @@ export const baseOptions: CompilerOptions = {
export function compile (
template: string,
options?: CompilerOptions
): { render: string, staticRenderFns: Array<string> } {
): {
ast: ?ASTElement,
render: string,
staticRenderFns: Array<string>
} {
options = options
? extend(extend({}, baseOptions), options)
: baseOptions
@ -58,7 +63,8 @@ export function compile (
export function compileToFunctions (
template: string,
options?: CompilerOptions
options?: CompilerOptions,
vm: Component
): CompiledFunctions {
const cache = options && options.preserveWhitespace === false ? cache1 : cache2
const key = options && options.delimiters
@ -69,11 +75,30 @@ export function compileToFunctions (
}
const res = {}
const compiled = compile(template, options)
res.render = new Function(compiled.render)
res.render = makeFunction(compiled.render)
const l = compiled.staticRenderFns.length
res.staticRenderFns = new Array(l)
for (let i = 0; i < l; i++) {
res.staticRenderFns[i] = new Function(compiled.staticRenderFns[i])
res.staticRenderFns[i] = makeFunction(compiled.staticRenderFns[i])
}
if (process.env.NODE_ENV !== 'production') {
if (res.render === noop || res.staticRenderFns.some(fn => fn === noop)) {
const errors = compiled.ast ? detectErrors(compiled.ast, warn) : []
warn(
`failed to compile template:\n\n${template}\n\n` +
errors.join('\n') +
'\n\n',
vm
)
}
}
return (cache[key] = res)
}
function makeFunction (code) {
try {
return new Function(code)
} catch (e) {
return noop
}
}

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

@ -42,7 +42,7 @@ Vue.prototype.$mount = function (
preserveWhitespace: config.preserveWhitespace,
delimiters: options.delimiters,
warn
})
}, this)
options.render = render
options.staticRenderFns = staticRenderFns
}

9
test/unit/features/options/template.spec.js

@ -50,4 +50,13 @@ describe('Options template', () => {
}).$mount()
expect('invalid template option').toHaveBeenWarned()
})
it('warn error in generated function', () => {
new Vue({
template: '<div v-if="!@">{{ a"" }}</div>'
}).$mount()
expect('failed to compile template').toHaveBeenWarned()
expect('invalid expression: v-if="!@"').toHaveBeenWarned()
expect('invalid expression: {{ a"" }}').toHaveBeenWarned()
})
})

Loading…
Cancel
Save