Skip to content

Commit f66028b

Browse files
committed
introduce tip + make v-for component key warning a tip + refactor web compiler entry
1 parent 7d3309d commit f66028b

File tree

6 files changed

+88
-56
lines changed

6 files changed

+88
-56
lines changed

Diff for: flow/compiler.js

+1
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ declare type CompiledResult = {
2424
render: string;
2525
staticRenderFns: Array<string>;
2626
errors?: Array<string>;
27+
tips?: Array<string>;
2728
}
2829

2930
declare type CompiledFunctionResult = {

Diff for: src/compiler/codegen/index.js

+2-1
Original file line numberDiff line numberDiff line change
@@ -152,7 +152,8 @@ function genFor (el: any): string {
152152
warn(
153153
`<${el.tag} v-for="${alias} in ${exp}">: component lists rendered with ` +
154154
`v-for should have explicit keys. ` +
155-
`See https://vuejs.org/guide/list.html#key for more info.`
155+
`See https://vuejs.org/guide/list.html#key for more info.`,
156+
true /* tip */
156157
)
157158
}
158159

Diff for: src/core/util/debug.js

+10-1
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import config from '../config'
22
import { noop } from 'shared/util'
33

44
let warn = noop
5+
let tip = noop
56
let formatComponentName
67

78
if (process.env.NODE_ENV !== 'production') {
@@ -19,6 +20,14 @@ if (process.env.NODE_ENV !== 'production') {
1920
}
2021
}
2122

23+
tip = (msg, vm) => {
24+
if (hasConsole && (!config.silent)) {
25+
console.warn(`[Vue tip]: ${msg} ` + (
26+
vm ? formatLocation(formatComponentName(vm)) : ''
27+
))
28+
}
29+
}
30+
2231
formatComponentName = (vm, includeFile) => {
2332
if (vm.$root === vm) {
2433
return '<Root>'
@@ -47,4 +56,4 @@ if (process.env.NODE_ENV !== 'production') {
4756
}
4857
}
4958

50-
export { warn, formatComponentName }
59+
export { warn, tip, formatComponentName }

Diff for: src/entries/web-runtime-with-compiler.js

-1
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,6 @@ Vue.prototype.$mount = function (
6262
}
6363

6464
const { render, staticRenderFns } = compileToFunctions(template, {
65-
warn: msg => warn(msg, this),
6665
shouldDecodeNewlines,
6766
delimiters: options.delimiters
6867
}, this)

Diff for: src/platforms/web/compiler/index.js

+69-51
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
/* @flow */
22

33
import { isUnaryTag } from './util'
4-
import { warn } from 'core/util/debug'
4+
import { warn, tip } from 'core/util/debug'
55
import { detectErrors } from 'compiler/error-detector'
66
import { compile as baseCompile } from 'compiler/index'
77
import { extend, genStaticKeys, noop } from 'shared/util'
@@ -24,43 +24,43 @@ export const baseOptions: CompilerOptions = {
2424
isPreTag
2525
}
2626

27-
function compileWithOptions (
28-
template: string,
29-
options?: CompilerOptions
30-
): CompiledResult {
31-
options = options
32-
? extend(extend({}, baseOptions), options)
33-
: baseOptions
34-
return baseCompile(template, options)
35-
}
36-
3727
export function compile (
3828
template: string,
3929
options?: CompilerOptions
4030
): CompiledResult {
41-
options = options || {}
31+
const finalOptions = Object.create(baseOptions)
4232
const errors = []
43-
// allow injecting modules/directives
44-
const baseModules = baseOptions.modules || []
45-
const modules = options.modules
46-
? baseModules.concat(options.modules)
47-
: baseModules
48-
const directives = options.directives
49-
? extend(extend({}, baseOptions.directives), options.directives)
50-
: baseOptions.directives
51-
const compiled = compileWithOptions(template, {
52-
modules,
53-
directives,
54-
preserveWhitespace: options.preserveWhitespace,
55-
warn: msg => {
56-
errors.push(msg)
33+
const tips = []
34+
finalOptions.warn = (msg, tip) => {
35+
(tip ? tips : errors).push(msg)
36+
}
37+
38+
if (options) {
39+
// merge custom modules
40+
if (options.modules) {
41+
finalOptions.modules = (baseOptions.modules || []).concat(options.modules)
42+
}
43+
// merge custom directives
44+
if (options.directives) {
45+
finalOptions.directives = extend(
46+
Object.create(baseOptions.directives),
47+
options.directives
48+
)
49+
}
50+
// copy other options
51+
for (const key in options) {
52+
if (key !== 'modules' && key !== 'directives') {
53+
finalOptions[key] = options[key]
54+
}
5755
}
58-
})
56+
}
57+
58+
const compiled = baseCompile(template, finalOptions)
5959
if (process.env.NODE_ENV !== 'production') {
60-
compiled.errors = errors.concat(detectErrors(compiled.ast))
61-
} else {
62-
compiled.errors = errors
60+
errors.push.apply(errors, detectErrors(compiled.ast))
6361
}
62+
compiled.errors = errors
63+
compiled.tips = tips
6464
return compiled
6565
}
6666

@@ -70,20 +70,15 @@ export function compileToFunctions (
7070
vm?: Component
7171
): CompiledFunctionResult {
7272
options = extend({}, options)
73-
const _warn = options.warn || warn
74-
const errors = []
73+
7574
/* istanbul ignore if */
7675
if (process.env.NODE_ENV !== 'production') {
77-
options.warn = msg => {
78-
errors.push(msg)
79-
}
80-
8176
// detect possible CSP restriction
8277
try {
8378
new Function('return 1')
8479
} catch (e) {
8580
if (e.toString().match(/unsafe-eval|CSP/)) {
86-
_warn(
81+
warn(
8782
'It seems you are using the standalone build of Vue.js in an ' +
8883
'environment with Content Security Policy that prohibits unsafe-eval. ' +
8984
'The template compiler cannot work in this environment. Consider ' +
@@ -93,41 +88,64 @@ export function compileToFunctions (
9388
}
9489
}
9590
}
91+
92+
// check cache
9693
const key = options.delimiters
9794
? String(options.delimiters) + template
9895
: template
9996
if (cache[key]) {
10097
return cache[key]
10198
}
99+
100+
// compile
101+
const compiled = compile(template, options)
102+
103+
// check compilation errors/tips
104+
if (process.env.NODE_ENV !== 'production') {
105+
if (compiled.errors && compiled.errors.length) {
106+
warn(
107+
`Error compiling template:\n\n${template}\n\n` +
108+
compiled.errors.map(e => `- ${e}`).join('\n') + '\n',
109+
vm
110+
)
111+
}
112+
if (compiled.tips && compiled.tips.length) {
113+
compiled.tips.forEach(msg => tip(msg, vm))
114+
}
115+
}
116+
117+
// turn code into functions
102118
const res = {}
103-
const compiled = compileWithOptions(template, options)
104-
res.render = makeFunction(compiled.render)
119+
const fnGenErrors = []
120+
res.render = makeFunction(compiled.render, fnGenErrors)
105121
const l = compiled.staticRenderFns.length
106122
res.staticRenderFns = new Array(l)
107123
for (let i = 0; i < l; i++) {
108-
res.staticRenderFns[i] = makeFunction(compiled.staticRenderFns[i])
124+
res.staticRenderFns[i] = makeFunction(compiled.staticRenderFns[i], fnGenErrors)
109125
}
126+
127+
// check function generation errors.
128+
// this should only happen if there is a bug in the compiler itself.
129+
// mostly for codegen development use
130+
/* istanbul ignore if */
110131
if (process.env.NODE_ENV !== 'production') {
111-
if (
112-
errors.length ||
113-
res.render === noop ||
114-
res.staticRenderFns.some(fn => fn === noop)
115-
) {
116-
const allErrors = errors.concat(detectErrors(compiled.ast))
117-
_warn(
118-
`Error compiling template:\n\n${template}\n\n` +
119-
(allErrors.length ? allErrors.map(e => `- ${e}`).join('\n') + '\n' : ''),
132+
if ((!compiled.errors || !compiled.errors.length) && fnGenErrors.length) {
133+
warn(
134+
`Failed to generate render function:\n\n` +
135+
fnGenErrors.map(({ err, code }) => `${err.toString()} in\n\n${code}\n`).join('\n'),
120136
vm
121137
)
122138
}
123139
}
140+
124141
return (cache[key] = res)
125142
}
126143

127-
function makeFunction (code) {
144+
function makeFunction (code, errors) {
128145
try {
129146
return new Function(code)
130-
} catch (e) {
147+
} catch (err) {
148+
errors.push({ err, code })
131149
return noop
132150
}
133151
}

Diff for: test/unit/features/directives/for.spec.js

+6-2
Original file line numberDiff line numberDiff line change
@@ -406,6 +406,8 @@ describe('Directive v-for', () => {
406406
})
407407

408408
it('should warn component v-for without keys', () => {
409+
const warn = console.warn
410+
console.warn = jasmine.createSpy()
409411
new Vue({
410412
template: `<div><test v-for="i in 3"></test></div>`,
411413
components: {
@@ -414,8 +416,10 @@ describe('Directive v-for', () => {
414416
}
415417
}
416418
}).$mount()
417-
expect('<test v-for="i in 3">: component lists rendered with v-for should have explicit keys')
418-
.toHaveBeenWarned()
419+
expect(console.warn.calls.argsFor(0)[0]).toContain(
420+
`<test v-for="i in 3">: component lists rendered with v-for should have explicit keys`
421+
)
422+
console.warn = warn
419423
})
420424

421425
it('multi nested array reactivity', done => {

0 commit comments

Comments
 (0)