introduce tip + make v-for component key warning a tip + refactor web compiler entry

This commit is contained in:
Evan You 2017-02-24 16:59:41 -05:00
parent 7d3309deed
commit f66028b9cd
6 changed files with 90 additions and 58 deletions

View File

@ -24,6 +24,7 @@ declare type CompiledResult = {
render: string;
staticRenderFns: Array<string>;
errors?: Array<string>;
tips?: Array<string>;
}
declare type CompiledFunctionResult = {

View File

@ -152,7 +152,8 @@ function genFor (el: any): string {
warn(
`<${el.tag} v-for="${alias} in ${exp}">: component lists rendered with ` +
`v-for should have explicit keys. ` +
`See https://vuejs.org/guide/list.html#key for more info.`
`See https://vuejs.org/guide/list.html#key for more info.`,
true /* tip */
)
}

View File

@ -2,6 +2,7 @@ import config from '../config'
import { noop } from 'shared/util'
let warn = noop
let tip = noop
let formatComponentName
if (process.env.NODE_ENV !== 'production') {
@ -19,6 +20,14 @@ if (process.env.NODE_ENV !== 'production') {
}
}
tip = (msg, vm) => {
if (hasConsole && (!config.silent)) {
console.warn(`[Vue tip]: ${msg} ` + (
vm ? formatLocation(formatComponentName(vm)) : ''
))
}
}
formatComponentName = (vm, includeFile) => {
if (vm.$root === vm) {
return '<Root>'
@ -47,4 +56,4 @@ if (process.env.NODE_ENV !== 'production') {
}
}
export { warn, formatComponentName }
export { warn, tip, formatComponentName }

View File

@ -62,7 +62,6 @@ Vue.prototype.$mount = function (
}
const { render, staticRenderFns } = compileToFunctions(template, {
warn: msg => warn(msg, this),
shouldDecodeNewlines,
delimiters: options.delimiters
}, this)

View File

@ -1,7 +1,7 @@
/* @flow */
import { isUnaryTag } from './util'
import { warn } from 'core/util/debug'
import { warn, tip } from 'core/util/debug'
import { detectErrors } from 'compiler/error-detector'
import { compile as baseCompile } from 'compiler/index'
import { extend, genStaticKeys, noop } from 'shared/util'
@ -24,43 +24,43 @@ export const baseOptions: CompilerOptions = {
isPreTag
}
function compileWithOptions (
template: string,
options?: CompilerOptions
): CompiledResult {
options = options
? extend(extend({}, baseOptions), options)
: baseOptions
return baseCompile(template, options)
}
export function compile (
template: string,
options?: CompilerOptions
): CompiledResult {
options = options || {}
const finalOptions = Object.create(baseOptions)
const errors = []
// allow injecting modules/directives
const baseModules = baseOptions.modules || []
const modules = options.modules
? baseModules.concat(options.modules)
: baseModules
const directives = options.directives
? extend(extend({}, baseOptions.directives), options.directives)
: baseOptions.directives
const compiled = compileWithOptions(template, {
modules,
directives,
preserveWhitespace: options.preserveWhitespace,
warn: msg => {
errors.push(msg)
}
})
if (process.env.NODE_ENV !== 'production') {
compiled.errors = errors.concat(detectErrors(compiled.ast))
} else {
compiled.errors = errors
const tips = []
finalOptions.warn = (msg, tip) => {
(tip ? tips : errors).push(msg)
}
if (options) {
// merge custom modules
if (options.modules) {
finalOptions.modules = (baseOptions.modules || []).concat(options.modules)
}
// merge custom directives
if (options.directives) {
finalOptions.directives = extend(
Object.create(baseOptions.directives),
options.directives
)
}
// copy other options
for (const key in options) {
if (key !== 'modules' && key !== 'directives') {
finalOptions[key] = options[key]
}
}
}
const compiled = baseCompile(template, finalOptions)
if (process.env.NODE_ENV !== 'production') {
errors.push.apply(errors, detectErrors(compiled.ast))
}
compiled.errors = errors
compiled.tips = tips
return compiled
}
@ -70,20 +70,15 @@ export function compileToFunctions (
vm?: Component
): CompiledFunctionResult {
options = extend({}, options)
const _warn = options.warn || warn
const errors = []
/* istanbul ignore if */
if (process.env.NODE_ENV !== 'production') {
options.warn = msg => {
errors.push(msg)
}
// detect possible CSP restriction
try {
new Function('return 1')
} catch (e) {
if (e.toString().match(/unsafe-eval|CSP/)) {
_warn(
warn(
'It seems you are using the standalone build of Vue.js in an ' +
'environment with Content Security Policy that prohibits unsafe-eval. ' +
'The template compiler cannot work in this environment. Consider ' +
@ -93,41 +88,64 @@ export function compileToFunctions (
}
}
}
// check cache
const key = options.delimiters
? String(options.delimiters) + template
: template
if (cache[key]) {
return cache[key]
}
// compile
const compiled = compile(template, options)
// check compilation errors/tips
if (process.env.NODE_ENV !== 'production') {
if (compiled.errors && compiled.errors.length) {
warn(
`Error compiling template:\n\n${template}\n\n` +
compiled.errors.map(e => `- ${e}`).join('\n') + '\n',
vm
)
}
if (compiled.tips && compiled.tips.length) {
compiled.tips.forEach(msg => tip(msg, vm))
}
}
// turn code into functions
const res = {}
const compiled = compileWithOptions(template, options)
res.render = makeFunction(compiled.render)
const fnGenErrors = []
res.render = makeFunction(compiled.render, fnGenErrors)
const l = compiled.staticRenderFns.length
res.staticRenderFns = new Array(l)
for (let i = 0; i < l; i++) {
res.staticRenderFns[i] = makeFunction(compiled.staticRenderFns[i])
res.staticRenderFns[i] = makeFunction(compiled.staticRenderFns[i], fnGenErrors)
}
// check function generation errors.
// this should only happen if there is a bug in the compiler itself.
// mostly for codegen development use
/* istanbul ignore if */
if (process.env.NODE_ENV !== 'production') {
if (
errors.length ||
res.render === noop ||
res.staticRenderFns.some(fn => fn === noop)
) {
const allErrors = errors.concat(detectErrors(compiled.ast))
_warn(
`Error compiling template:\n\n${template}\n\n` +
(allErrors.length ? allErrors.map(e => `- ${e}`).join('\n') + '\n' : ''),
if ((!compiled.errors || !compiled.errors.length) && fnGenErrors.length) {
warn(
`Failed to generate render function:\n\n` +
fnGenErrors.map(({ err, code }) => `${err.toString()} in\n\n${code}\n`).join('\n'),
vm
)
}
}
return (cache[key] = res)
}
function makeFunction (code) {
function makeFunction (code, errors) {
try {
return new Function(code)
} catch (e) {
} catch (err) {
errors.push({ err, code })
return noop
}
}

View File

@ -406,6 +406,8 @@ describe('Directive v-for', () => {
})
it('should warn component v-for without keys', () => {
const warn = console.warn
console.warn = jasmine.createSpy()
new Vue({
template: `<div><test v-for="i in 3"></test></div>`,
components: {
@ -414,8 +416,10 @@ describe('Directive v-for', () => {
}
}
}).$mount()
expect('<test v-for="i in 3">: component lists rendered with v-for should have explicit keys')
.toHaveBeenWarned()
expect(console.warn.calls.argsFor(0)[0]).toContain(
`<test v-for="i in 3">: component lists rendered with v-for should have explicit keys`
)
console.warn = warn
})
it('multi nested array reactivity', done => {