`
)
@@ -89,8 +89,8 @@ describe('stringify static html', () => {
callee: CREATE_STATIC,
arguments: [
JSON.stringify(
- `
${repeat(
- `
1 + false`,
+ `
${repeat(
+ `1 + false`,
StringifyThresholds.ELEMENT_WITH_BINDING_COUNT
)}
`
)
diff --git a/packages/compiler-dom/__tests__/transforms/transformStyle.spec.ts b/packages/compiler-dom/__tests__/transforms/transformStyle.spec.ts
index 89121c10a26..6eee9f8ba38 100644
--- a/packages/compiler-dom/__tests__/transforms/transformStyle.spec.ts
+++ b/packages/compiler-dom/__tests__/transforms/transformStyle.spec.ts
@@ -26,17 +26,8 @@ function transformWithStyleTransform(
}
describe('compiler: style transform', () => {
- test('should transform into directive node and hoist value', () => {
- const { root, node } = transformWithStyleTransform(
- `
`
- )
- expect(root.hoists).toMatchObject([
- {
- type: NodeTypes.SIMPLE_EXPRESSION,
- content: `{"color":"red"}`,
- isStatic: false
- }
- ])
+ test('should transform into directive node', () => {
+ const { node } = transformWithStyleTransform(`
`)
expect(node.props[0]).toMatchObject({
type: NodeTypes.DIRECTIVE,
name: `bind`,
@@ -47,7 +38,7 @@ describe('compiler: style transform', () => {
},
exp: {
type: NodeTypes.SIMPLE_EXPRESSION,
- content: `_hoisted_1`,
+ content: `{"color":"red"}`,
isStatic: false
}
})
@@ -71,7 +62,7 @@ describe('compiler: style transform', () => {
},
value: {
type: NodeTypes.SIMPLE_EXPRESSION,
- content: `_hoisted_1`,
+ content: `{"color":"red"}`,
isStatic: false
}
}
diff --git a/packages/compiler-dom/package.json b/packages/compiler-dom/package.json
index 380f71148fd..2820c7c1c71 100644
--- a/packages/compiler-dom/package.json
+++ b/packages/compiler-dom/package.json
@@ -1,6 +1,6 @@
{
"name": "@vue/compiler-dom",
- "version": "3.0.0-alpha.5",
+ "version": "3.0.0-alpha.6",
"description": "@vue/compiler-dom",
"main": "index.js",
"module": "dist/compiler-dom.esm-bundler.js",
@@ -34,6 +34,6 @@
},
"homepage": "https://github.com/vuejs/vue/tree/dev/packages/compiler-dom#readme",
"dependencies": {
- "@vue/compiler-core": "3.0.0-alpha.5"
+ "@vue/compiler-core": "3.0.0-alpha.6"
}
}
diff --git a/packages/compiler-dom/src/transforms/stringifyStatic.ts b/packages/compiler-dom/src/transforms/stringifyStatic.ts
index a2efa56eec0..a4aa7745f8e 100644
--- a/packages/compiler-dom/src/transforms/stringifyStatic.ts
+++ b/packages/compiler-dom/src/transforms/stringifyStatic.ts
@@ -14,7 +14,10 @@ import {
isString,
isSymbol,
escapeHtml,
- toDisplayString
+ toDisplayString,
+ normalizeClass,
+ normalizeStyle,
+ stringifyStyle
} from '@vue/shared'
// Turn eligible hoisted static trees into stringied static nodes, e.g.
@@ -84,8 +87,15 @@ function stringifyElement(
}
} else if (p.type === NodeTypes.DIRECTIVE && p.name === 'bind') {
// constant v-bind, e.g. :foo="1"
+ let evaluated = evaluateConstant(p.exp as SimpleExpressionNode)
+ const arg = p.arg && (p.arg as SimpleExpressionNode).content
+ if (arg === 'class') {
+ evaluated = normalizeClass(evaluated)
+ } else if (arg === 'style') {
+ evaluated = stringifyStyle(normalizeStyle(evaluated))
+ }
res += ` ${(p.arg as SimpleExpressionNode).content}="${escapeHtml(
- evaluateConstant(p.exp as ExpressionNode)
+ evaluated
)}"`
}
}
@@ -151,7 +161,7 @@ function evaluateConstant(exp: ExpressionNode): string {
if (c.type === NodeTypes.TEXT) {
res += c.content
} else if (c.type === NodeTypes.INTERPOLATION) {
- res += evaluateConstant(c.content)
+ res += toDisplayString(evaluateConstant(c.content))
} else {
res += evaluateConstant(c)
}
diff --git a/packages/compiler-dom/src/transforms/transformStyle.ts b/packages/compiler-dom/src/transforms/transformStyle.ts
index 3c232db4357..765fab778e9 100644
--- a/packages/compiler-dom/src/transforms/transformStyle.ts
+++ b/packages/compiler-dom/src/transforms/transformStyle.ts
@@ -17,12 +17,11 @@ export const transformStyle: NodeTransform = (node, context) => {
node.props.forEach((p, i) => {
if (p.type === NodeTypes.ATTRIBUTE && p.name === 'style' && p.value) {
// replace p with an expression node
- const exp = context.hoist(parseInlineCSS(p.value.content, p.loc))
node.props[i] = {
type: NodeTypes.DIRECTIVE,
name: `bind`,
arg: createSimpleExpression(`style`, true, p.loc),
- exp,
+ exp: parseInlineCSS(p.value.content, p.loc),
modifiers: [],
loc: p.loc
}
@@ -45,5 +44,5 @@ function parseInlineCSS(
tmp.length > 1 && (res[tmp[0].trim()] = tmp[1].trim())
}
})
- return createSimpleExpression(JSON.stringify(res), false, loc)
+ return createSimpleExpression(JSON.stringify(res), false, loc, true)
}
diff --git a/packages/compiler-sfc/package.json b/packages/compiler-sfc/package.json
index 9bafa05aff0..71fdc411500 100644
--- a/packages/compiler-sfc/package.json
+++ b/packages/compiler-sfc/package.json
@@ -1,6 +1,6 @@
{
"name": "@vue/compiler-sfc",
- "version": "3.0.0-alpha.5",
+ "version": "3.0.0-alpha.6",
"description": "@vue/compiler-sfc",
"main": "dist/compiler-sfc.cjs.js",
"types": "dist/compiler-sfc.d.ts",
@@ -27,11 +27,11 @@
},
"homepage": "https://github.com/vuejs/vue/tree/dev/packages/compiler-sfc#readme",
"peerDependencies": {
- "vue": "3.0.0-alpha.5"
+ "vue": "3.0.0-alpha.6"
},
"dependencies": {
- "@vue/compiler-core": "3.0.0-alpha.5",
- "@vue/compiler-dom": "3.0.0-alpha.5",
+ "@vue/compiler-core": "3.0.0-alpha.6",
+ "@vue/compiler-dom": "3.0.0-alpha.6",
"consolidate": "^0.15.1",
"hash-sum": "^2.0.0",
"lru-cache": "^5.1.1",
diff --git a/packages/compiler-ssr/__tests__/ssrElement.spec.ts b/packages/compiler-ssr/__tests__/ssrElement.spec.ts
index c99d13c543a..c81d6ccb10e 100644
--- a/packages/compiler-ssr/__tests__/ssrElement.spec.ts
+++ b/packages/compiler-ssr/__tests__/ssrElement.spec.ts
@@ -101,7 +101,7 @@ describe('ssr: element', () => {
expect(
getCompiledString(`
`)
).toMatchInlineSnapshot(
- `"\`
\`"`
+ `"\`
\`"`
)
})
@@ -184,7 +184,7 @@ describe('ssr: element', () => {
)
).toMatchInlineSnapshot(`
"\`
\`"
`)
})
diff --git a/packages/compiler-ssr/__tests__/ssrVShow.spec.ts b/packages/compiler-ssr/__tests__/ssrVShow.spec.ts
index 2738bc22617..8a99a52c7d3 100644
--- a/packages/compiler-ssr/__tests__/ssrVShow.spec.ts
+++ b/packages/compiler-ssr/__tests__/ssrVShow.spec.ts
@@ -16,11 +16,9 @@ describe('ssr: v-show', () => {
.toMatchInlineSnapshot(`
"const { ssrRenderStyle: _ssrRenderStyle } = require(\\"@vue/server-renderer\\")
- const _hoisted_1 = {\\"color\\":\\"red\\"}
-
return function ssrRender(_ctx, _push, _parent) {
_push(\`
\`)
}"
@@ -48,11 +46,9 @@ describe('ssr: v-show', () => {
).toMatchInlineSnapshot(`
"const { ssrRenderStyle: _ssrRenderStyle } = require(\\"@vue/server-renderer\\")
- const _hoisted_1 = {\\"color\\":\\"red\\"}
-
return function ssrRender(_ctx, _push, _parent) {
_push(\`
\`)
@@ -69,12 +65,10 @@ describe('ssr: v-show', () => {
"const { mergeProps: _mergeProps } = require(\\"vue\\")
const { ssrRenderAttrs: _ssrRenderAttrs } = require(\\"@vue/server-renderer\\")
- const _hoisted_1 = {\\"color\\":\\"red\\"}
-
return function ssrRender(_ctx, _push, _parent) {
_push(\`
{
expect(fnSpy).toHaveBeenCalledTimes(1)
})
+ it('should trigger all effects when array lenght is set 0', () => {
+ const observed: any = reactive([1])
+ let dummy, record
+ effect(() => {
+ dummy = observed.length
+ })
+ effect(() => {
+ record = observed[0]
+ })
+ expect(dummy).toBe(1)
+ expect(record).toBe(1)
+
+ observed[1] = 2
+ expect(observed[1]).toBe(2)
+
+ observed.unshift(3)
+ expect(dummy).toBe(3)
+ expect(record).toBe(3)
+
+ observed.length = 0
+ expect(dummy).toBe(0)
+ expect(record).toBeUndefined()
+ })
+
it('should handle self dependency mutations', () => {
const count = ref(0)
effect(() => {
diff --git a/packages/reactivity/__tests__/reactive.spec.ts b/packages/reactivity/__tests__/reactive.spec.ts
index 1b18dc8055a..5d8876843b1 100644
--- a/packages/reactivity/__tests__/reactive.spec.ts
+++ b/packages/reactivity/__tests__/reactive.spec.ts
@@ -26,51 +26,6 @@ describe('reactivity/reactive', () => {
expect(Object.keys(observed)).toEqual(['foo'])
})
- test('Array', () => {
- const original = [{ foo: 1 }]
- const observed = reactive(original)
- expect(observed).not.toBe(original)
- expect(isReactive(observed)).toBe(true)
- expect(isReactive(original)).toBe(false)
- expect(isReactive(observed[0])).toBe(true)
- // get
- expect(observed[0].foo).toBe(1)
- // has
- expect(0 in observed).toBe(true)
- // ownKeys
- expect(Object.keys(observed)).toEqual(['0'])
- })
-
- test('cloned reactive Array should point to observed values', () => {
- const original = [{ foo: 1 }]
- const observed = reactive(original)
- const clone = observed.slice()
- expect(isReactive(clone[0])).toBe(true)
- expect(clone[0]).not.toBe(original[0])
- expect(clone[0]).toBe(observed[0])
- })
-
- test('Array identity methods should work with raw values', () => {
- const raw = {}
- const arr = reactive([{}, {}])
- arr.push(raw)
- expect(arr.indexOf(raw)).toBe(2)
- expect(arr.indexOf(raw, 3)).toBe(-1)
- expect(arr.includes(raw)).toBe(true)
- expect(arr.includes(raw, 3)).toBe(false)
- expect(arr.lastIndexOf(raw)).toBe(2)
- expect(arr.lastIndexOf(raw, 1)).toBe(-1)
-
- // should work also for the observed version
- const observed = arr[2]
- expect(arr.indexOf(observed)).toBe(2)
- expect(arr.indexOf(observed, 3)).toBe(-1)
- expect(arr.includes(observed)).toBe(true)
- expect(arr.includes(observed, 3)).toBe(false)
- expect(arr.lastIndexOf(observed)).toBe(2)
- expect(arr.lastIndexOf(observed, 1)).toBe(-1)
- })
-
test('nested reactives', () => {
const original = {
nested: {
@@ -97,25 +52,6 @@ describe('reactivity/reactive', () => {
expect('foo' in original).toBe(false)
})
- test('observed value should proxy mutations to original (Array)', () => {
- const original: any[] = [{ foo: 1 }, { bar: 2 }]
- const observed = reactive(original)
- // set
- const value = { baz: 3 }
- const reactiveValue = reactive(value)
- observed[0] = value
- expect(observed[0]).toBe(reactiveValue)
- expect(original[0]).toBe(value)
- // delete
- delete observed[0]
- expect(observed[0]).toBeUndefined()
- expect(original[0]).toBeUndefined()
- // mutating methods
- observed.push(value)
- expect(observed[2]).toBe(reactiveValue)
- expect(original[2]).toBe(value)
- })
-
test('setting a property with an unobserved value should wrap with reactive', () => {
const observed = reactive<{ foo?: object }>({})
const raw = {}
diff --git a/packages/reactivity/__tests__/reactiveArray.spec.ts b/packages/reactivity/__tests__/reactiveArray.spec.ts
new file mode 100644
index 00000000000..b61116e28b1
--- /dev/null
+++ b/packages/reactivity/__tests__/reactiveArray.spec.ts
@@ -0,0 +1,111 @@
+import { reactive, isReactive, toRaw } from '../src/reactive'
+import { ref, isRef } from '../src/ref'
+import { effect } from '../src/effect'
+
+describe('reactivity/reactive/Array', () => {
+ test('should make Array reactive', () => {
+ const original = [{ foo: 1 }]
+ const observed = reactive(original)
+ expect(observed).not.toBe(original)
+ expect(isReactive(observed)).toBe(true)
+ expect(isReactive(original)).toBe(false)
+ expect(isReactive(observed[0])).toBe(true)
+ // get
+ expect(observed[0].foo).toBe(1)
+ // has
+ expect(0 in observed).toBe(true)
+ // ownKeys
+ expect(Object.keys(observed)).toEqual(['0'])
+ })
+
+ test('cloned reactive Array should point to observed values', () => {
+ const original = [{ foo: 1 }]
+ const observed = reactive(original)
+ const clone = observed.slice()
+ expect(isReactive(clone[0])).toBe(true)
+ expect(clone[0]).not.toBe(original[0])
+ expect(clone[0]).toBe(observed[0])
+ })
+
+ test('observed value should proxy mutations to original (Array)', () => {
+ const original: any[] = [{ foo: 1 }, { bar: 2 }]
+ const observed = reactive(original)
+ // set
+ const value = { baz: 3 }
+ const reactiveValue = reactive(value)
+ observed[0] = value
+ expect(observed[0]).toBe(reactiveValue)
+ expect(original[0]).toBe(value)
+ // delete
+ delete observed[0]
+ expect(observed[0]).toBeUndefined()
+ expect(original[0]).toBeUndefined()
+ // mutating methods
+ observed.push(value)
+ expect(observed[2]).toBe(reactiveValue)
+ expect(original[2]).toBe(value)
+ })
+
+ test('Array identity methods should work with raw values', () => {
+ const raw = {}
+ const arr = reactive([{}, {}])
+ arr.push(raw)
+ expect(arr.indexOf(raw)).toBe(2)
+ expect(arr.indexOf(raw, 3)).toBe(-1)
+ expect(arr.includes(raw)).toBe(true)
+ expect(arr.includes(raw, 3)).toBe(false)
+ expect(arr.lastIndexOf(raw)).toBe(2)
+ expect(arr.lastIndexOf(raw, 1)).toBe(-1)
+
+ // should work also for the observed version
+ const observed = arr[2]
+ expect(arr.indexOf(observed)).toBe(2)
+ expect(arr.indexOf(observed, 3)).toBe(-1)
+ expect(arr.includes(observed)).toBe(true)
+ expect(arr.includes(observed, 3)).toBe(false)
+ expect(arr.lastIndexOf(observed)).toBe(2)
+ expect(arr.lastIndexOf(observed, 1)).toBe(-1)
+ })
+
+ test('Array identity methods should be reactive', () => {
+ const obj = {}
+ const arr = reactive([obj, {}])
+
+ let index: number = -1
+ effect(() => {
+ index = arr.indexOf(obj)
+ })
+ expect(index).toBe(0)
+ arr.reverse()
+ expect(index).toBe(1)
+ })
+
+ describe('Array methods w/ refs', () => {
+ let original: any[]
+ beforeEach(() => {
+ original = reactive([1, ref(2)])
+ })
+
+ // read + copy
+ test('read only copy methods', () => {
+ const res = original.concat([3, ref(4)])
+ const raw = toRaw(res)
+ expect(isRef(raw[1])).toBe(true)
+ expect(isRef(raw[3])).toBe(true)
+ })
+
+ // read + write
+ test('read + write mutating methods', () => {
+ const res = original.copyWithin(0, 1, 2)
+ const raw = toRaw(res)
+ expect(isRef(raw[0])).toBe(true)
+ expect(isRef(raw[1])).toBe(true)
+ })
+
+ test('read + indentity', () => {
+ const ref = original[1]
+ expect(ref).toBe(toRaw(original)[1])
+ expect(original.indexOf(ref)).toBe(1)
+ })
+ })
+})
diff --git a/packages/reactivity/__tests__/ref.spec.ts b/packages/reactivity/__tests__/ref.spec.ts
index b822e801bbf..6620f7fd9ce 100644
--- a/packages/reactivity/__tests__/ref.spec.ts
+++ b/packages/reactivity/__tests__/ref.spec.ts
@@ -1,5 +1,14 @@
-import { ref, effect, reactive, isRef, toRefs, Ref } from '../src/index'
+import {
+ ref,
+ effect,
+ reactive,
+ isRef,
+ toRefs,
+ Ref,
+ isReactive
+} from '../src/index'
import { computed } from '@vue/runtime-dom'
+import { shallowRef, unref } from '../src/ref'
describe('reactivity/ref', () => {
it('should hold a value', () => {
@@ -33,28 +42,36 @@ describe('reactivity/ref', () => {
expect(dummy).toBe(2)
})
+ it('should work without initial value', () => {
+ const a = ref()
+ let dummy
+ effect(() => {
+ dummy = a.value
+ })
+ expect(dummy).toBe(undefined)
+ a.value = 2
+ expect(dummy).toBe(2)
+ })
+
it('should work like a normal property when nested in a reactive object', () => {
const a = ref(1)
const obj = reactive({
a,
b: {
- c: a,
- d: [a]
+ c: a
}
})
let dummy1: number
let dummy2: number
- let dummy3: number
effect(() => {
dummy1 = obj.a
dummy2 = obj.b.c
- dummy3 = obj.b.d[0]
})
const assertDummiesEqualTo = (val: number) =>
- [dummy1, dummy2, dummy3].forEach(dummy => expect(dummy).toBe(val))
+ [dummy1, dummy2].forEach(dummy => expect(dummy).toBe(val))
assertDummiesEqualTo(1)
a.value++
@@ -63,8 +80,6 @@ describe('reactivity/ref', () => {
assertDummiesEqualTo(3)
obj.b.c++
assertDummiesEqualTo(4)
- obj.b.d[0]++
- assertDummiesEqualTo(5)
})
it('should unwrap nested ref in types', () => {
@@ -84,15 +99,14 @@ describe('reactivity/ref', () => {
expect(typeof (c.value.b + 1)).toBe('number')
})
- it('should properly unwrap ref types nested inside arrays', () => {
+ it('should NOT unwrap ref types nested inside arrays', () => {
const arr = ref([1, ref(1)]).value
- // should unwrap to number[]
- arr[0]++
- arr[1]++
+ ;(arr[0] as number)++
+ ;(arr[1] as Ref
).value++
const arr2 = ref([1, new Map(), ref('1')]).value
const value = arr2[0]
- if (typeof value === 'string') {
+ if (isRef(value)) {
value + 'foo'
} else if (typeof value === 'number') {
value + 1
@@ -120,8 +134,28 @@ describe('reactivity/ref', () => {
tupleRef.value[2].a++
expect(tupleRef.value[2].a).toBe(2)
expect(tupleRef.value[3]()).toBe(0)
- tupleRef.value[4]++
- expect(tupleRef.value[4]).toBe(1)
+ tupleRef.value[4].value++
+ expect(tupleRef.value[4].value).toBe(1)
+ })
+
+ test('unref', () => {
+ expect(unref(1)).toBe(1)
+ expect(unref(ref(1))).toBe(1)
+ })
+
+ test('shallowRef', () => {
+ const sref = shallowRef({ a: 1 })
+ expect(isReactive(sref.value)).toBe(false)
+
+ let dummy
+ effect(() => {
+ dummy = sref.value.a
+ })
+ expect(dummy).toBe(1)
+
+ sref.value = { a: 2 }
+ expect(isReactive(sref.value)).toBe(false)
+ expect(dummy).toBe(2)
})
test('isRef', () => {
diff --git a/packages/reactivity/package.json b/packages/reactivity/package.json
index f7c36b0b492..59ada495c01 100644
--- a/packages/reactivity/package.json
+++ b/packages/reactivity/package.json
@@ -1,6 +1,6 @@
{
"name": "@vue/reactivity",
- "version": "3.0.0-alpha.5",
+ "version": "3.0.0-alpha.6",
"description": "@vue/reactivity",
"main": "index.js",
"module": "dist/reactivity.esm-bundler.js",
diff --git a/packages/reactivity/src/baseHandlers.ts b/packages/reactivity/src/baseHandlers.ts
index 43b6df903bd..0aaeade83e4 100644
--- a/packages/reactivity/src/baseHandlers.ts
+++ b/packages/reactivity/src/baseHandlers.ts
@@ -16,20 +16,21 @@ const shallowReactiveGet = /*#__PURE__*/ createGetter(false, true)
const readonlyGet = /*#__PURE__*/ createGetter(true)
const shallowReadonlyGet = /*#__PURE__*/ createGetter(true, true)
-const arrayIdentityInstrumentations: Record = {}
+const arrayInstrumentations: Record = {}
;['includes', 'indexOf', 'lastIndexOf'].forEach(key => {
- arrayIdentityInstrumentations[key] = function(
- value: unknown,
- ...args: any[]
- ): any {
- return toRaw(this)[key](toRaw(value), ...args)
+ arrayInstrumentations[key] = function(...args: any[]): any {
+ const arr = toRaw(this) as any
+ for (let i = 0, l = (this as any).length; i < l; i++) {
+ track(arr, TrackOpTypes.GET, i + '')
+ }
+ return arr[key](...args.map(toRaw))
}
})
function createGetter(isReadonly = false, shallow = false) {
return function get(target: object, key: string | symbol, receiver: object) {
- if (isArray(target) && hasOwn(arrayIdentityInstrumentations, key)) {
- return Reflect.get(arrayIdentityInstrumentations, key, receiver)
+ if (isArray(target) && hasOwn(arrayInstrumentations, key)) {
+ return Reflect.get(arrayInstrumentations, key, receiver)
}
const res = Reflect.get(target, key, receiver)
if (isSymbol(key) && builtInSymbols.has(key)) {
@@ -40,7 +41,8 @@ function createGetter(isReadonly = false, shallow = false) {
// TODO strict mode that returns a shallow-readonly version of the value
return res
}
- if (isRef(res)) {
+ // ref unwrapping, only for Objects, not for Arrays.
+ if (isRef(res) && !isArray(target)) {
return res.value
}
track(target, TrackOpTypes.GET, key)
@@ -79,7 +81,7 @@ function createSetter(isReadonly = false, shallow = false) {
const oldValue = (target as any)[key]
if (!shallow) {
value = toRaw(value)
- if (isRef(oldValue) && !isRef(value)) {
+ if (!isArray(target) && isRef(oldValue) && !isRef(value)) {
oldValue.value = value
return true
}
@@ -91,20 +93,10 @@ function createSetter(isReadonly = false, shallow = false) {
const result = Reflect.set(target, key, value, receiver)
// don't trigger if target is something up in the prototype chain of original
if (target === toRaw(receiver)) {
- /* istanbul ignore else */
- if (__DEV__) {
- const extraInfo = { oldValue, newValue: value }
- if (!hadKey) {
- trigger(target, TriggerOpTypes.ADD, key, extraInfo)
- } else if (hasChanged(value, oldValue)) {
- trigger(target, TriggerOpTypes.SET, key, extraInfo)
- }
- } else {
- if (!hadKey) {
- trigger(target, TriggerOpTypes.ADD, key)
- } else if (hasChanged(value, oldValue)) {
- trigger(target, TriggerOpTypes.SET, key)
- }
+ if (!hadKey) {
+ trigger(target, TriggerOpTypes.ADD, key, value)
+ } else if (hasChanged(value, oldValue)) {
+ trigger(target, TriggerOpTypes.SET, key, value, oldValue)
}
}
return result
@@ -116,12 +108,7 @@ function deleteProperty(target: object, key: string | symbol): boolean {
const oldValue = (target as any)[key]
const result = Reflect.deleteProperty(target, key)
if (result && hadKey) {
- /* istanbul ignore else */
- if (__DEV__) {
- trigger(target, TriggerOpTypes.DELETE, key, { oldValue })
- } else {
- trigger(target, TriggerOpTypes.DELETE, key)
- }
+ trigger(target, TriggerOpTypes.DELETE, key, undefined, oldValue)
}
return result
}
diff --git a/packages/reactivity/src/collectionHandlers.ts b/packages/reactivity/src/collectionHandlers.ts
index cb90cf0e3c2..af4ca05d17f 100644
--- a/packages/reactivity/src/collectionHandlers.ts
+++ b/packages/reactivity/src/collectionHandlers.ts
@@ -51,12 +51,7 @@ function add(this: SetTypes, value: unknown) {
const hadKey = proto.has.call(target, value)
const result = proto.add.call(target, value)
if (!hadKey) {
- /* istanbul ignore else */
- if (__DEV__) {
- trigger(target, TriggerOpTypes.ADD, value, { newValue: value })
- } else {
- trigger(target, TriggerOpTypes.ADD, value)
- }
+ trigger(target, TriggerOpTypes.ADD, value, value)
}
return result
}
@@ -69,20 +64,10 @@ function set(this: MapTypes, key: unknown, value: unknown) {
const hadKey = proto.has.call(target, key)
const oldValue = proto.get.call(target, key)
const result = proto.set.call(target, key, value)
- /* istanbul ignore else */
- if (__DEV__) {
- const extraInfo = { oldValue, newValue: value }
- if (!hadKey) {
- trigger(target, TriggerOpTypes.ADD, key, extraInfo)
- } else if (hasChanged(value, oldValue)) {
- trigger(target, TriggerOpTypes.SET, key, extraInfo)
- }
- } else {
- if (!hadKey) {
- trigger(target, TriggerOpTypes.ADD, key)
- } else if (hasChanged(value, oldValue)) {
- trigger(target, TriggerOpTypes.SET, key)
- }
+ if (!hadKey) {
+ trigger(target, TriggerOpTypes.ADD, key, value)
+ } else if (hasChanged(value, oldValue)) {
+ trigger(target, TriggerOpTypes.SET, key, value, oldValue)
}
return result
}
@@ -96,12 +81,7 @@ function deleteEntry(this: CollectionTypes, key: unknown) {
// forward the operation before queueing reactions
const result = proto.delete.call(target, key)
if (hadKey) {
- /* istanbul ignore else */
- if (__DEV__) {
- trigger(target, TriggerOpTypes.DELETE, key, { oldValue })
- } else {
- trigger(target, TriggerOpTypes.DELETE, key)
- }
+ trigger(target, TriggerOpTypes.DELETE, key, undefined, oldValue)
}
return result
}
@@ -117,12 +97,7 @@ function clear(this: IterableCollections) {
// forward the operation before queueing reactions
const result = getProto(target).clear.call(target)
if (hadItems) {
- /* istanbul ignore else */
- if (__DEV__) {
- trigger(target, TriggerOpTypes.CLEAR, void 0, { oldTarget })
- } else {
- trigger(target, TriggerOpTypes.CLEAR)
- }
+ trigger(target, TriggerOpTypes.CLEAR, undefined, undefined, oldTarget)
}
return result
}
diff --git a/packages/reactivity/src/effect.ts b/packages/reactivity/src/effect.ts
index f4cac6d4edc..010aea0020a 100644
--- a/packages/reactivity/src/effect.ts
+++ b/packages/reactivity/src/effect.ts
@@ -165,7 +165,9 @@ export function trigger(
target: object,
type: TriggerOpTypes,
key?: unknown,
- extraInfo?: DebuggerEventExtraInfo
+ newValue?: unknown,
+ oldValue?: unknown,
+ oldTarget?: Map | Set
) {
const depsMap = targetMap.get(target)
if (depsMap === void 0) {
@@ -175,10 +177,17 @@ export function trigger(
const effects = new Set()
const computedRunners = new Set()
if (type === TriggerOpTypes.CLEAR) {
- // collection being cleared, trigger all effects for target
+ // collection being cleared
+ // trigger all effects for target
depsMap.forEach(dep => {
addRunners(effects, computedRunners, dep)
})
+ } else if (key === 'length' && isArray(target)) {
+ depsMap.forEach((dep, key) => {
+ if (key === 'length' || key >= (newValue as number)) {
+ addRunners(effects, computedRunners, dep)
+ }
+ })
} else {
// schedule runs for SET | ADD | DELETE
if (key !== void 0) {
@@ -195,7 +204,19 @@ export function trigger(
}
}
const run = (effect: ReactiveEffect) => {
- scheduleRun(effect, target, type, key, extraInfo)
+ scheduleRun(
+ effect,
+ target,
+ type,
+ key,
+ __DEV__
+ ? {
+ newValue,
+ oldValue,
+ oldTarget
+ }
+ : undefined
+ )
}
// Important: computed effects must be run first so that computed getters
// can be invalidated before any normal effects that depend on them are run.
diff --git a/packages/reactivity/src/index.ts b/packages/reactivity/src/index.ts
index 040dd8c673d..21a9eae8f8c 100644
--- a/packages/reactivity/src/index.ts
+++ b/packages/reactivity/src/index.ts
@@ -1,4 +1,4 @@
-export { ref, isRef, toRefs, Ref, UnwrapRef } from './ref'
+export { ref, unref, shallowRef, isRef, toRefs, Ref, UnwrapRef } from './ref'
export {
reactive,
isReactive,
diff --git a/packages/reactivity/src/reactive.ts b/packages/reactivity/src/reactive.ts
index f97a1e6f2d4..5f8aa579725 100644
--- a/packages/reactivity/src/reactive.ts
+++ b/packages/reactivity/src/reactive.ts
@@ -9,7 +9,7 @@ import {
mutableCollectionHandlers,
readonlyCollectionHandlers
} from './collectionHandlers'
-import { UnwrapRef, Ref } from './ref'
+import { UnwrapRef, Ref, isRef } from './ref'
import { makeMap } from '@vue/shared'
// WeakMaps that store {raw <-> observed} pairs.
@@ -50,6 +50,9 @@ export function reactive(target: object) {
if (readonlyValues.has(target)) {
return readonly(target)
}
+ if (isRef(target)) {
+ return target
+ }
return createReactiveObject(
target,
rawToReactive,
diff --git a/packages/reactivity/src/ref.ts b/packages/reactivity/src/ref.ts
index 65e1b8dd016..f88631130e7 100644
--- a/packages/reactivity/src/ref.ts
+++ b/packages/reactivity/src/ref.ts
@@ -23,19 +23,30 @@ export interface Ref {
const convert = (val: T): T =>
isObject(val) ? reactive(val) : val
-export function isRef(r: Ref | T): r is Ref
+export function isRef(r: Ref | unknown): r is Ref
export function isRef(r: any): r is Ref {
return r ? r._isRef === true : false
}
-export function ref(value: T): T
-export function ref(value: T): Ref
+export function ref(value: T): T extends Ref ? T : Ref
export function ref(): Ref
export function ref(value?: unknown) {
+ return createRef(value)
+}
+
+export function shallowRef(value: T): T extends Ref ? T : Ref
+export function shallowRef(): Ref
+export function shallowRef(value?: unknown) {
+ return createRef(value, true)
+}
+
+function createRef(value: unknown, shallow = false) {
if (isRef(value)) {
return value
}
- value = convert(value)
+ if (!shallow) {
+ value = convert(value)
+ }
const r = {
_isRef: true,
get value() {
@@ -43,7 +54,7 @@ export function ref(value?: unknown) {
return value
},
set value(newVal) {
- value = convert(newVal)
+ value = shallow ? newVal : convert(newVal)
trigger(
r,
TriggerOpTypes.SET,
@@ -55,6 +66,10 @@ export function ref(value?: unknown) {
return r
}
+export function unref(ref: T): T extends Ref ? V : T {
+ return isRef(ref) ? (ref.value as any) : ref
+}
+
export function toRefs(
object: T
): { [K in keyof T]: Ref } {
@@ -83,8 +98,6 @@ function toProxyRef(
} as any
}
-type UnwrapArray = { [P in keyof T]: UnwrapRef }
-
// corner case when use narrows type
// Ex. type RelativePath = string & { __brand: unknown }
// RelativePath extends object -> true
@@ -94,7 +107,7 @@ type BaseTypes = string | number | boolean
export type UnwrapRef = {
cRef: T extends ComputedRef ? UnwrapRef : T
ref: T extends Ref ? UnwrapRef : T
- array: T extends Array ? Array> & UnwrapArray : T
+ array: T
object: { [K in keyof T]: UnwrapRef }
}[T extends ComputedRef
? 'cRef'
diff --git a/packages/runtime-core/__tests__/apiSetupContext.spec.ts b/packages/runtime-core/__tests__/apiSetupContext.spec.ts
index bc3c2065034..8f7ab078bbe 100644
--- a/packages/runtime-core/__tests__/apiSetupContext.spec.ts
+++ b/packages/runtime-core/__tests__/apiSetupContext.spec.ts
@@ -6,7 +6,7 @@ import {
render,
serializeInner,
nextTick,
- watch,
+ watchEffect,
defineComponent,
triggerEvent,
TestElement
@@ -55,7 +55,7 @@ describe('api: setup context', () => {
const Child = defineComponent({
setup(props: { count: number }) {
- watch(() => {
+ watchEffect(() => {
dummy = props.count
})
return () => h('div', props.count)
@@ -88,7 +88,7 @@ describe('api: setup context', () => {
},
setup(props) {
- watch(() => {
+ watchEffect(() => {
dummy = props.count
})
return () => h('div', props.count)
diff --git a/packages/runtime-core/__tests__/apiTemplateRef.spec.ts b/packages/runtime-core/__tests__/apiTemplateRef.spec.ts
index 8ff6b2c59e1..68b14dd2b28 100644
--- a/packages/runtime-core/__tests__/apiTemplateRef.spec.ts
+++ b/packages/runtime-core/__tests__/apiTemplateRef.spec.ts
@@ -142,7 +142,7 @@ describe('api: template refs', () => {
foo: ref(null),
bar: ref(null)
}
- const refKey: Ref = ref('foo')
+ const refKey = ref('foo') as Ref
const Comp = {
setup() {
diff --git a/packages/runtime-core/__tests__/apiWatch.spec.ts b/packages/runtime-core/__tests__/apiWatch.spec.ts
index ef05452fd33..36db04b1996 100644
--- a/packages/runtime-core/__tests__/apiWatch.spec.ts
+++ b/packages/runtime-core/__tests__/apiWatch.spec.ts
@@ -1,4 +1,12 @@
-import { watch, reactive, computed, nextTick, ref, h } from '../src/index'
+import {
+ watch,
+ watchEffect,
+ reactive,
+ computed,
+ nextTick,
+ ref,
+ h
+} from '../src/index'
import { render, nodeOps, serializeInner } from '@vue/runtime-test'
import {
ITERATE_KEY,
@@ -13,10 +21,10 @@ import { mockWarn } from '@vue/shared'
describe('api: watch', () => {
mockWarn()
- it('watch(effect)', async () => {
+ it('effect', async () => {
const state = reactive({ count: 0 })
let dummy
- watch(() => {
+ watchEffect(() => {
dummy = state.count
})
expect(dummy).toBe(0)
@@ -117,10 +125,10 @@ describe('api: watch', () => {
expect(dummy).toMatchObject([[2, true], [1, false]])
})
- it('stopping the watcher', async () => {
+ it('stopping the watcher (effect)', async () => {
const state = reactive({ count: 0 })
let dummy
- const stop = watch(() => {
+ const stop = watchEffect(() => {
dummy = state.count
})
expect(dummy).toBe(0)
@@ -132,11 +140,32 @@ describe('api: watch', () => {
expect(dummy).toBe(0)
})
+ it('stopping the watcher (with source)', async () => {
+ const state = reactive({ count: 0 })
+ let dummy
+ const stop = watch(
+ () => state.count,
+ count => {
+ dummy = count
+ }
+ )
+
+ state.count++
+ await nextTick()
+ expect(dummy).toBe(1)
+
+ stop()
+ state.count++
+ await nextTick()
+ // should not update
+ expect(dummy).toBe(1)
+ })
+
it('cleanup registration (effect)', async () => {
const state = reactive({ count: 0 })
const cleanup = jest.fn()
let dummy
- const stop = watch(onCleanup => {
+ const stop = watchEffect(onCleanup => {
onCleanup(cleanup)
dummy = state.count
})
@@ -187,7 +216,7 @@ describe('api: watch', () => {
const Comp = {
setup() {
- watch(() => {
+ watchEffect(() => {
assertion(count.value)
})
return () => count.value
@@ -221,7 +250,7 @@ describe('api: watch', () => {
const Comp = {
setup() {
- watch(
+ watchEffect(
() => {
assertion(count.value, count2.value)
},
@@ -263,7 +292,7 @@ describe('api: watch', () => {
const Comp = {
setup() {
- watch(
+ watchEffect(
() => {
assertion(count.value)
},
@@ -363,14 +392,14 @@ describe('api: watch', () => {
expect(spy).toHaveBeenCalledTimes(3)
})
- it('warn immediate option when using effect signature', async () => {
+ it('warn immediate option when using effect', async () => {
const count = ref(0)
let dummy
- // @ts-ignore
- watch(
+ watchEffect(
() => {
dummy = count.value
},
+ // @ts-ignore
{ immediate: false }
)
expect(dummy).toBe(0)
@@ -381,6 +410,24 @@ describe('api: watch', () => {
expect(dummy).toBe(1)
})
+ it('warn and not respect deep option when using effect', async () => {
+ const arr = ref([1, [2]])
+ let spy = jest.fn()
+ watchEffect(
+ () => {
+ spy()
+ return arr
+ },
+ // @ts-ignore
+ { deep: true }
+ )
+ expect(spy).toHaveBeenCalledTimes(1)
+ ;(arr.value[1] as Array)[0] = 3
+ await nextTick()
+ expect(spy).toHaveBeenCalledTimes(1)
+ expect(`"deep" option is only respected`).toHaveBeenWarned()
+ })
+
it('onTrack', async () => {
const events: DebuggerEvent[] = []
let dummy
@@ -388,7 +435,7 @@ describe('api: watch', () => {
events.push(e)
})
const obj = reactive({ foo: 1, bar: 2 })
- watch(
+ watchEffect(
() => {
dummy = [obj.foo, 'bar' in obj, Object.keys(obj)]
},
@@ -423,7 +470,7 @@ describe('api: watch', () => {
events.push(e)
})
const obj = reactive({ foo: 1 })
- watch(
+ watchEffect(
() => {
dummy = obj.foo
},
diff --git a/packages/runtime-core/__tests__/components/Suspense.spec.ts b/packages/runtime-core/__tests__/components/Suspense.spec.ts
index a530532f077..6a115e39c25 100644
--- a/packages/runtime-core/__tests__/components/Suspense.spec.ts
+++ b/packages/runtime-core/__tests__/components/Suspense.spec.ts
@@ -9,6 +9,7 @@ import {
nextTick,
onMounted,
watch,
+ watchEffect,
onUnmounted,
onErrorCaptured
} from '@vue/runtime-test'
@@ -163,7 +164,7 @@ describe('Suspense', () => {
// extra tick needed for Node 12+
deps.push(p.then(() => Promise.resolve()))
- watch(() => {
+ watchEffect(() => {
calls.push('immediate effect')
})
@@ -265,7 +266,7 @@ describe('Suspense', () => {
const p = new Promise(r => setTimeout(r, 1))
deps.push(p)
- watch(() => {
+ watchEffect(() => {
calls.push('immediate effect')
})
diff --git a/packages/runtime-core/__tests__/errorHandling.spec.ts b/packages/runtime-core/__tests__/errorHandling.spec.ts
index 6396c6b35a4..f6907949b1f 100644
--- a/packages/runtime-core/__tests__/errorHandling.spec.ts
+++ b/packages/runtime-core/__tests__/errorHandling.spec.ts
@@ -7,7 +7,8 @@ import {
watch,
ref,
nextTick,
- defineComponent
+ defineComponent,
+ watchEffect
} from '@vue/runtime-test'
import { setErrorRecovery } from '../src/errorHandling'
import { mockWarn } from '@vue/shared'
@@ -241,7 +242,7 @@ describe('error handling', () => {
expect(fn).toHaveBeenCalledWith(err, 'ref function')
})
- test('in watch (effect)', () => {
+ test('in effect', () => {
const err = new Error('foo')
const fn = jest.fn()
@@ -257,7 +258,7 @@ describe('error handling', () => {
const Child = {
setup() {
- watch(() => {
+ watchEffect(() => {
throw err
})
return () => null
@@ -268,7 +269,7 @@ describe('error handling', () => {
expect(fn).toHaveBeenCalledWith(err, 'watcher callback')
})
- test('in watch (getter)', () => {
+ test('in watch getter', () => {
const err = new Error('foo')
const fn = jest.fn()
@@ -298,7 +299,7 @@ describe('error handling', () => {
expect(fn).toHaveBeenCalledWith(err, 'watcher getter')
})
- test('in watch (callback)', async () => {
+ test('in watch callback', async () => {
const err = new Error('foo')
const fn = jest.fn()
@@ -332,7 +333,7 @@ describe('error handling', () => {
expect(fn).toHaveBeenCalledWith(err, 'watcher callback')
})
- test('in watch cleanup', async () => {
+ test('in effect cleanup', async () => {
const err = new Error('foo')
const count = ref(0)
const fn = jest.fn()
@@ -349,7 +350,7 @@ describe('error handling', () => {
const Child = {
setup() {
- watch(onCleanup => {
+ watchEffect(onCleanup => {
count.value
onCleanup(() => {
throw err
diff --git a/packages/runtime-core/__tests__/helpers/scopeId.spec.ts b/packages/runtime-core/__tests__/helpers/scopeId.spec.ts
index 08f7b1e7d50..b14d3acbc44 100644
--- a/packages/runtime-core/__tests__/helpers/scopeId.spec.ts
+++ b/packages/runtime-core/__tests__/helpers/scopeId.spec.ts
@@ -17,6 +17,27 @@ describe('scopeId runtime support', () => {
expect(serializeInner(root)).toBe(``)
})
+ test('should attach scopeId to components in parent component', () => {
+ const Child = {
+ __scopeId: 'child',
+ render: withChildId(() => {
+ return h('div')
+ })
+ }
+ const App = {
+ __scopeId: 'parent',
+ render: withParentId(() => {
+ return h('div', [h(Child)])
+ })
+ }
+
+ const root = nodeOps.createElement('div')
+ render(h(App), root)
+ expect(serializeInner(root)).toBe(
+ ``
+ )
+ })
+
test('should work on slots', () => {
const Child = {
__scopeId: 'child',
@@ -41,7 +62,7 @@ describe('scopeId runtime support', () => {
// - scopeId from parent
// - slotted scopeId (with `-s` postfix) from child (the tree owner)
expect(serializeInner(root)).toBe(
- ``
+ ``
)
})
})
diff --git a/packages/runtime-core/package.json b/packages/runtime-core/package.json
index fe69d4ff9e2..2ba19068880 100644
--- a/packages/runtime-core/package.json
+++ b/packages/runtime-core/package.json
@@ -1,6 +1,6 @@
{
"name": "@vue/runtime-core",
- "version": "3.0.0-alpha.5",
+ "version": "3.0.0-alpha.6",
"description": "@vue/runtime-core",
"main": "index.js",
"module": "dist/runtime-core.esm-bundler.js",
@@ -31,6 +31,6 @@
},
"homepage": "https://github.com/vuejs/vue/tree/dev/packages/runtime-core#readme",
"dependencies": {
- "@vue/reactivity": "3.0.0-alpha.5"
+ "@vue/reactivity": "3.0.0-alpha.6"
}
}
diff --git a/packages/runtime-core/src/apiWatch.ts b/packages/runtime-core/src/apiWatch.ts
index da3df3033f9..71cb485a533 100644
--- a/packages/runtime-core/src/apiWatch.ts
+++ b/packages/runtime-core/src/apiWatch.ts
@@ -71,6 +71,14 @@ export type StopHandle = () => void
const invoke = (fn: Function) => fn()
+// Simple effect.
+export function watchEffect(
+ effect: WatchEffect,
+ options?: BaseWatchOptions
+): StopHandle {
+ return doWatch(effect, null, options)
+}
+
// initial value for watchers to trigger on undefined initial values
const INITIAL_WATCHER_VALUE = {}
@@ -110,6 +118,13 @@ export function watch(
// watch(source, cb)
return doWatch(effectOrSource, cbOrOptions, options)
} else {
+ // TODO remove this in the next release
+ __DEV__ &&
+ warn(
+ `\`watch(fn, options?)\` signature has been moved to a separate API. ` +
+ `Use \`watchEffect(fn, options?)\` instead. \`watch\` will only ` +
+ `support \`watch(source, cb, options?) signature in the next release.`
+ )
// watch(effect)
return doWatch(effectOrSource, null, cbOrOptions)
}
@@ -124,13 +139,13 @@ function doWatch(
if (immediate !== undefined) {
warn(
`watch() "immediate" option is only respected when using the ` +
- `watch(source, callback) signature.`
+ `watch(source, callback, options?) signature.`
)
}
if (deep !== undefined) {
warn(
`watch() "deep" option is only respected when using the ` +
- `watch(source, callback) signature.`
+ `watch(source, callback, options?) signature.`
)
}
}
@@ -171,7 +186,7 @@ function doWatch(
}
}
- if (deep) {
+ if (cb && deep) {
const baseGetter = getter
getter = () => traverse(baseGetter())
}
diff --git a/packages/runtime-core/src/componentProxy.ts b/packages/runtime-core/src/componentProxy.ts
index 9e451110523..a69d33da140 100644
--- a/packages/runtime-core/src/componentProxy.ts
+++ b/packages/runtime-core/src/componentProxy.ts
@@ -13,7 +13,8 @@ import {
isRef,
isReactive,
Ref,
- ComputedRef
+ ComputedRef,
+ unref
} from '@vue/reactivity'
import { warn } from './warning'
import { Slots } from './componentSlots'
@@ -84,8 +85,6 @@ const enum AccessTypes {
OTHER
}
-const unwrapRef = (val: unknown) => (isRef(val) ? val.value : val)
-
export const PublicInstanceProxyHandlers: ProxyHandler = {
get(target: ComponentInternalInstance, key: string) {
// fast path for unscopables when using `with` block
@@ -115,7 +114,7 @@ export const PublicInstanceProxyHandlers: ProxyHandler = {
case AccessTypes.DATA:
return data[key]
case AccessTypes.CONTEXT:
- return unwrapRef(renderContext[key])
+ return unref(renderContext[key])
case AccessTypes.PROPS:
return propsProxy![key]
// default: just fallthrough
@@ -125,7 +124,7 @@ export const PublicInstanceProxyHandlers: ProxyHandler = {
return data[key]
} else if (hasOwn(renderContext, key)) {
accessCache![key] = AccessTypes.CONTEXT
- return unwrapRef(renderContext[key])
+ return unref(renderContext[key])
} else if (type.props != null) {
// only cache other properties when instance has declared (this stable)
// props
diff --git a/packages/runtime-core/src/componentRenderUtils.ts b/packages/runtime-core/src/componentRenderUtils.ts
index 145a3697c5c..6b3be672792 100644
--- a/packages/runtime-core/src/componentRenderUtils.ts
+++ b/packages/runtime-core/src/componentRenderUtils.ts
@@ -39,6 +39,7 @@ export function renderComponentRoot(
): VNode {
const {
type: Component,
+ parent,
vnode,
proxy,
withProxy,
@@ -102,6 +103,11 @@ export function renderComponentRoot(
if (vnodeHooks !== EMPTY_OBJ) {
result = cloneVNode(result, vnodeHooks)
}
+ // inherit scopeId
+ const parentScopeId = parent && parent.type.__scopeId
+ if (parentScopeId) {
+ result = cloneVNode(result, { [parentScopeId]: '' })
+ }
// inherit directives
if (vnode.dirs != null) {
if (__DEV__ && !isElementRoot(result)) {
@@ -127,6 +133,7 @@ export function renderComponentRoot(
result = createVNode(Comment)
}
currentRenderingInstance = null
+
return result
}
diff --git a/packages/runtime-core/src/index.ts b/packages/runtime-core/src/index.ts
index b26c9a8f987..634d53b424b 100644
--- a/packages/runtime-core/src/index.ts
+++ b/packages/runtime-core/src/index.ts
@@ -3,6 +3,8 @@
export const version = __VERSION__
export {
ref,
+ unref,
+ shallowRef,
isRef,
toRefs,
reactive,
@@ -15,7 +17,7 @@ export {
markNonReactive
} from '@vue/reactivity'
export { computed } from './apiComputed'
-export { watch } from './apiWatch'
+export { watch, watchEffect } from './apiWatch'
export {
onBeforeMount,
onMounted,
diff --git a/packages/runtime-core/src/renderer.ts b/packages/runtime-core/src/renderer.ts
index 9d1c2a414a3..ab5b58cecf2 100644
--- a/packages/runtime-core/src/renderer.ts
+++ b/packages/runtime-core/src/renderer.ts
@@ -128,7 +128,7 @@ export interface RendererInternals {
c: ProcessTextOrCommentFn
}
-// These functions are created inside a closure and therefore there types cannot
+// These functions are created inside a closure and therefore their types cannot
// be directly exported. In order to avoid maintaining function signatures in
// two places, we declare them once here and use them inside the closure.
type PatchFn = (
@@ -651,7 +651,6 @@ function baseCreateRenderer<
// generated by the compiler and can take the fast path.
// in this path old node and new node are guaranteed to have the same shape
// (i.e. at the exact same position in the source template)
-
if (patchFlag & PatchFlags.FULL_PROPS) {
// element props contain dynamic keys, full diff needed
patchProps(
diff --git a/packages/runtime-dom/__tests__/modules/style.spec.ts b/packages/runtime-dom/__tests__/modules/style.spec.ts
index 28eac3b426b..08667275081 100644
--- a/packages/runtime-dom/__tests__/modules/style.spec.ts
+++ b/packages/runtime-dom/__tests__/modules/style.spec.ts
@@ -21,7 +21,7 @@ describe(`module style`, () => {
it('remove if falsy value', () => {
const el = document.createElement('div')
- patchStyle(el, { color: 'red' }, { color: null })
+ patchStyle(el, { color: 'red' }, { color: undefined })
expect(el.style.cssText.replace(/\s/g, '')).toBe('')
})
diff --git a/packages/runtime-dom/package.json b/packages/runtime-dom/package.json
index 59245312ffe..fefe3ae9208 100644
--- a/packages/runtime-dom/package.json
+++ b/packages/runtime-dom/package.json
@@ -1,6 +1,6 @@
{
"name": "@vue/runtime-dom",
- "version": "3.0.0-alpha.5",
+ "version": "3.0.0-alpha.6",
"description": "@vue/runtime-dom",
"main": "index.js",
"module": "dist/runtime-dom.esm-bundler.js",
@@ -37,7 +37,7 @@
},
"homepage": "https://github.com/vuejs/vue/tree/dev/packages/runtime-dom#readme",
"dependencies": {
- "@vue/runtime-core": "3.0.0-alpha.5",
+ "@vue/runtime-core": "3.0.0-alpha.6",
"csstype": "^2.6.8"
}
}
diff --git a/packages/runtime-test/package.json b/packages/runtime-test/package.json
index b1c62e1e5ff..4b1a83ab1bd 100644
--- a/packages/runtime-test/package.json
+++ b/packages/runtime-test/package.json
@@ -1,6 +1,6 @@
{
"name": "@vue/runtime-test",
- "version": "3.0.0-alpha.5",
+ "version": "3.0.0-alpha.6",
"description": "@vue/runtime-test",
"private": true,
"main": "index.js",
@@ -30,6 +30,6 @@
},
"homepage": "https://github.com/vuejs/vue/tree/dev/packages/runtime-test#readme",
"dependencies": {
- "@vue/runtime-core": "3.0.0-alpha.5"
+ "@vue/runtime-core": "3.0.0-alpha.6"
}
}
diff --git a/packages/server-renderer/__tests__/renderToString.spec.ts b/packages/server-renderer/__tests__/renderToString.spec.ts
index 2ac929ac1c4..c8bb79c8746 100644
--- a/packages/server-renderer/__tests__/renderToString.spec.ts
+++ b/packages/server-renderer/__tests__/renderToString.spec.ts
@@ -562,7 +562,7 @@ describe('ssr: renderToString', () => {
}
expect(await renderToString(h(Parent))).toBe(
- `slot
`
+ `slot
`
)
})
})
diff --git a/packages/server-renderer/__tests__/ssrRenderAttrs.spec.ts b/packages/server-renderer/__tests__/ssrRenderAttrs.spec.ts
index 87521a49aae..38ca843c4db 100644
--- a/packages/server-renderer/__tests__/ssrRenderAttrs.spec.ts
+++ b/packages/server-renderer/__tests__/ssrRenderAttrs.spec.ts
@@ -26,6 +26,14 @@ describe('ssr: renderAttrs', () => {
).toBe(` id="foo" title="bar"`)
})
+ test('empty value attrs', () => {
+ expect(
+ ssrRenderAttrs({
+ 'data-v-abc': ''
+ })
+ ).toBe(` data-v-abc`)
+ })
+
test('escape attrs', () => {
expect(
ssrRenderAttrs({
diff --git a/packages/server-renderer/package.json b/packages/server-renderer/package.json
index b159c111fd7..6af7ff5d5b1 100644
--- a/packages/server-renderer/package.json
+++ b/packages/server-renderer/package.json
@@ -1,6 +1,6 @@
{
"name": "@vue/server-renderer",
- "version": "3.0.0-alpha.5",
+ "version": "3.0.0-alpha.6",
"description": "@vue/server-renderer",
"main": "index.js",
"types": "dist/server-renderer.d.ts",
@@ -27,9 +27,9 @@
},
"homepage": "https://github.com/vuejs/vue/tree/dev/packages/server-renderer#readme",
"peerDependencies": {
- "vue": "3.0.0-alpha.5"
+ "vue": "3.0.0-alpha.6"
},
"dependencies": {
- "@vue/compiler-ssr": "3.0.0-alpha.5"
+ "@vue/compiler-ssr": "3.0.0-alpha.6"
}
}
diff --git a/packages/server-renderer/src/helpers/ssrRenderAttrs.ts b/packages/server-renderer/src/helpers/ssrRenderAttrs.ts
index 4a1dcb8df5f..d17c0dd11d6 100644
--- a/packages/server-renderer/src/helpers/ssrRenderAttrs.ts
+++ b/packages/server-renderer/src/helpers/ssrRenderAttrs.ts
@@ -1,11 +1,9 @@
-import { escapeHtml } from '@vue/shared'
+import { escapeHtml, stringifyStyle } from '@vue/shared'
import {
normalizeClass,
normalizeStyle,
propsToAttrMap,
- hyphenate,
isString,
- isNoUnitNumericStyleProp,
isOn,
isSSRSafeAttrName,
isBooleanAttr,
@@ -55,7 +53,9 @@ export function ssrRenderDynamicAttr(
if (isBooleanAttr(attrKey)) {
return value === false ? `` : ` ${attrKey}`
} else if (isSSRSafeAttrName(attrKey)) {
- return ` ${attrKey}="${escapeHtml(value)}"`
+ return value === ''
+ ? ` ${attrKey}`
+ : ` ${attrKey}="${escapeHtml(value)}"`
} else {
console.warn(
`[@vue/server-renderer] Skipped rendering unsafe attribute name: ${attrKey}`
@@ -93,17 +93,5 @@ export function ssrRenderStyle(raw: unknown): string {
return escapeHtml(raw)
}
const styles = normalizeStyle(raw)
- let ret = ''
- for (const key in styles) {
- const value = styles[key]
- const normalizedKey = key.indexOf(`--`) === 0 ? key : hyphenate(key)
- if (
- isString(value) ||
- (typeof value === 'number' && isNoUnitNumericStyleProp(normalizedKey))
- ) {
- // only render valid values
- ret += `${normalizedKey}:${value};`
- }
- }
- return escapeHtml(ret)
+ return escapeHtml(stringifyStyle(styles))
}
diff --git a/packages/server-renderer/src/renderToString.ts b/packages/server-renderer/src/renderToString.ts
index 2753c345538..0aac04cd14a 100644
--- a/packages/server-renderer/src/renderToString.ts
+++ b/packages/server-renderer/src/renderToString.ts
@@ -67,9 +67,11 @@ function createBuffer() {
let hasAsync = false
const buffer: SSRBuffer = []
return {
- buffer,
- hasAsync() {
- return hasAsync
+ getBuffer(): ResolvedSSRBuffer | Promise {
+ // If the current component's buffer contains any Promise from async children,
+ // then it must return a Promise too. Otherwise this is a component that
+ // contains only sync children so we can avoid the async book-keeping overhead.
+ return hasAsync ? Promise.all(buffer) : (buffer as ResolvedSSRBuffer)
},
push(item: SSRBufferItem) {
const isStringItem = isString(item)
@@ -104,28 +106,19 @@ export async function renderToString(
input: App | VNode,
context: SSRContext = {}
): Promise {
- let buffer: ResolvedSSRBuffer
if (isVNode(input)) {
// raw vnode, wrap with app (for context)
return renderToString(createApp({ render: () => input }), context)
- } else {
- // rendering an app
- const vnode = createVNode(input._component, input._props)
- vnode.appContext = input._context
- // provide the ssr context to the tree
- input.provide(ssrContextKey, context)
- buffer = await renderComponentVNode(vnode)
}
- // resolve portals
- if (context.__portalBuffers) {
- context.portals = context.portals || {}
- for (const key in context.__portalBuffers) {
- // note: it's OK to await sequentially here because the Promises were
- // created eagerly in parallel.
- context.portals[key] = unrollBuffer(await context.__portalBuffers[key])
- }
- }
+ // rendering an app
+ const vnode = createVNode(input._component, input._props)
+ vnode.appContext = input._context
+ // provide the ssr context to the tree
+ input.provide(ssrContextKey, context)
+ const buffer = await renderComponentVNode(vnode)
+
+ await resolvePortals(context)
return unrollBuffer(buffer)
}
@@ -201,7 +194,7 @@ function renderComponentSubTree(
instance: ComponentInternalInstance
): ResolvedSSRBuffer | Promise {
const comp = instance.type as Component
- const { buffer, push, hasAsync } = createBuffer()
+ const { getBuffer, push } = createBuffer()
if (isFunction(comp)) {
renderVNode(push, renderComponentRoot(instance), instance)
} else {
@@ -225,10 +218,7 @@ function renderComponentSubTree(
)
}
}
- // If the current component's buffer contains any Promise from async children,
- // then it must return a Promise too. Otherwise this is a component that
- // contains only sync children so we can avoid the async book-keeping overhead.
- return hasAsync() ? Promise.all(buffer) : (buffer as ResolvedSSRBuffer)
+ return getBuffer()
}
function renderVNode(
@@ -349,7 +339,7 @@ function renderPortal(
return []
}
- const { buffer, push, hasAsync } = createBuffer()
+ const { getBuffer, push } = createBuffer()
renderVNodeChildren(
push,
vnode.children as VNodeArrayChildren,
@@ -360,7 +350,17 @@ function renderPortal(
] as SSRContext
const portalBuffers =
context.__portalBuffers || (context.__portalBuffers = {})
- portalBuffers[target] = hasAsync()
- ? Promise.all(buffer)
- : (buffer as ResolvedSSRBuffer)
+
+ portalBuffers[target] = getBuffer()
+}
+
+async function resolvePortals(context: SSRContext) {
+ if (context.__portalBuffers) {
+ context.portals = context.portals || {}
+ for (const key in context.__portalBuffers) {
+ // note: it's OK to await sequentially here because the Promises were
+ // created eagerly in parallel.
+ context.portals[key] = unrollBuffer(await context.__portalBuffers[key])
+ }
+ }
}
diff --git a/packages/shared/package.json b/packages/shared/package.json
index b897788f6c9..cd8125af310 100644
--- a/packages/shared/package.json
+++ b/packages/shared/package.json
@@ -1,5 +1,5 @@
{
"name": "@vue/shared",
- "version": "3.0.0-alpha.5",
+ "version": "3.0.0-alpha.6",
"private": true
}
diff --git a/packages/shared/src/normalizeProp.ts b/packages/shared/src/normalizeProp.ts
index 96678e8bc94..201e49806d3 100644
--- a/packages/shared/src/normalizeProp.ts
+++ b/packages/shared/src/normalizeProp.ts
@@ -1,4 +1,5 @@
-import { isArray, isString, isObject } from './'
+import { isArray, isString, isObject, hyphenate } from './'
+import { isNoUnitNumericStyleProp } from './domAttrConfig'
export function normalizeStyle(
value: unknown
@@ -19,6 +20,27 @@ export function normalizeStyle(
}
}
+export function stringifyStyle(
+ styles: Record | undefined
+): string {
+ let ret = ''
+ if (!styles) {
+ return ret
+ }
+ for (const key in styles) {
+ const value = styles[key]
+ const normalizedKey = key.indexOf(`--`) === 0 ? key : hyphenate(key)
+ if (
+ isString(value) ||
+ (typeof value === 'number' && isNoUnitNumericStyleProp(normalizedKey))
+ ) {
+ // only render valid values
+ ret += `${normalizedKey}:${value};`
+ }
+ }
+ return ret
+}
+
export function normalizeClass(value: unknown): string {
let res = ''
if (isString(value)) {
diff --git a/packages/shared/src/patchFlags.ts b/packages/shared/src/patchFlags.ts
index 374a22b5218..9e499abf71f 100644
--- a/packages/shared/src/patchFlags.ts
+++ b/packages/shared/src/patchFlags.ts
@@ -55,7 +55,7 @@ export const enum PatchFlags {
// Indicates an element that only needs non-props patching, e.g. ref or
// directives (onVnodeXXX hooks). since every patched vnode checks for refs
- // and onVnodeXXX hooks, itt simply marks the vnode so that a parent block
+ // and onVnodeXXX hooks, it simply marks the vnode so that a parent block
// will track it.
NEED_PATCH = 1 << 9,
diff --git a/packages/size-check/package.json b/packages/size-check/package.json
index 5f8702111a4..9589206a3a0 100644
--- a/packages/size-check/package.json
+++ b/packages/size-check/package.json
@@ -1,6 +1,6 @@
{
"name": "@vue/size-check",
- "version": "3.0.0-alpha.5",
+ "version": "3.0.0-alpha.6",
"private": true,
"buildOptions": {
"name": "Vue",
diff --git a/packages/template-explorer/package.json b/packages/template-explorer/package.json
index c592497bdb7..f35f72597ca 100644
--- a/packages/template-explorer/package.json
+++ b/packages/template-explorer/package.json
@@ -1,6 +1,6 @@
{
"name": "@vue/template-explorer",
- "version": "3.0.0-alpha.5",
+ "version": "3.0.0-alpha.6",
"private": true,
"buildOptions": {
"formats": [
diff --git a/packages/vue/examples/composition/commits.html b/packages/vue/examples/composition/commits.html
index 270662fe386..711cbc67039 100644
--- a/packages/vue/examples/composition/commits.html
+++ b/packages/vue/examples/composition/commits.html
@@ -22,7 +22,7 @@ Latest Vue.js Commits