From f66028b9cd863b1212516f3d46c637422bf91a27 Mon Sep 17 00:00:00 2001 From: Evan You Date: Fri, 24 Feb 2017 16:59:41 -0500 Subject: [PATCH] introduce tip + make v-for component key warning a tip + refactor web compiler entry --- flow/compiler.js | 1 + src/compiler/codegen/index.js | 3 +- src/core/util/debug.js | 11 +- src/entries/web-runtime-with-compiler.js | 1 - src/platforms/web/compiler/index.js | 124 +++++++++++++--------- test/unit/features/directives/for.spec.js | 8 +- 6 files changed, 90 insertions(+), 58 deletions(-) diff --git a/flow/compiler.js b/flow/compiler.js index 3a925be4..18ed2855 100644 --- a/flow/compiler.js +++ b/flow/compiler.js @@ -24,6 +24,7 @@ declare type CompiledResult = { render: string; staticRenderFns: Array; errors?: Array; + tips?: Array; } declare type CompiledFunctionResult = { diff --git a/src/compiler/codegen/index.js b/src/compiler/codegen/index.js index c64ae9d2..83d772d5 100644 --- a/src/compiler/codegen/index.js +++ b/src/compiler/codegen/index.js @@ -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 */ ) } diff --git a/src/core/util/debug.js b/src/core/util/debug.js index ebdd81d8..98de0976 100644 --- a/src/core/util/debug.js +++ b/src/core/util/debug.js @@ -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 '' @@ -47,4 +56,4 @@ if (process.env.NODE_ENV !== 'production') { } } -export { warn, formatComponentName } +export { warn, tip, formatComponentName } diff --git a/src/entries/web-runtime-with-compiler.js b/src/entries/web-runtime-with-compiler.js index 60bc0d99..5e94d9f7 100644 --- a/src/entries/web-runtime-with-compiler.js +++ b/src/entries/web-runtime-with-compiler.js @@ -62,7 +62,6 @@ Vue.prototype.$mount = function ( } const { render, staticRenderFns } = compileToFunctions(template, { - warn: msg => warn(msg, this), shouldDecodeNewlines, delimiters: options.delimiters }, this) diff --git a/src/platforms/web/compiler/index.js b/src/platforms/web/compiler/index.js index 99efe4ce..38f75b81 100644 --- a/src/platforms/web/compiler/index.js +++ b/src/platforms/web/compiler/index.js @@ -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 } } diff --git a/test/unit/features/directives/for.spec.js b/test/unit/features/directives/for.spec.js index 89122aba..db92426b 100644 --- a/test/unit/features/directives/for.spec.js +++ b/test/unit/features/directives/for.spec.js @@ -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: `
`, components: { @@ -414,8 +416,10 @@ describe('Directive v-for', () => { } } }).$mount() - expect(': component lists rendered with v-for should have explicit keys') - .toHaveBeenWarned() + expect(console.warn.calls.argsFor(0)[0]).toContain( + `: component lists rendered with v-for should have explicit keys` + ) + console.warn = warn }) it('multi nested array reactivity', done => {