2
0
mirror of https://github.com/tenrok/vue-meta.git synced 2026-06-23 22:40:34 +03:00

feat: add support for computed metadata

feat: show active metadata and add some styling

chore: code cleanup
This commit is contained in:
pimlie
2021-02-28 22:58:18 +01:00
parent 498d747301
commit 3e1a0da9e4
12 changed files with 876 additions and 327 deletions
+44 -6
View File
@@ -7,16 +7,54 @@ html, body {
padding: 0 20px; padding: 0 20px;
} }
ul {
line-height: 1.5em;
padding-left: 1.5em;
}
a { a {
color: #7f8c8d; color: #2c3e50;
text-decoration: none; text-decoration: none;
} }
a:hover { a:hover {
color: #4fc08d; color: #4fc08d;
} }
ul.menu {
list-style-type: none;
display: flex;
padding-left: 15px;
}
ul.menu li {
margin: 0;
padding-right: 15px;
}
.page {
border: 1px solid #2c3e50;
border-radius: 1rem;
padding: 15px;
}
.metadata {
background-color: #fafafa;
border-radius: 1rem;
margin-top: 30px;
padding-bottom: 15px;
}
.metadata h4 {
padding: 15px;
border-top-left-radius: 1rem;
border-top-right-radius: 1rem;
background-color: #f7f7f7;
border-bottom: 1px solid #f0f0f0;
}
.metadata p {
font-size: 75%;
white-space: pre;
overflow-y: scroll;
height: 400px;
max-height: 50%;
padding-left: 15px;
padding-right: 15px;
margin: 0;
}
+9 -4
View File
@@ -177,16 +177,21 @@ export default {
<div id="app"> <div id="app">
<h1>vue-router</h1> <h1>vue-router</h1>
<router-link to="/">Home</router-link> <ul class="menu">
<router-link to="/about">About</router-link> <li><router-link to="/">Home</router-link></li>
<li><router-link to="/about">About</router-link></li>
</ul>
<router-view v-slot="{ Component }"> <router-view v-slot="{ Component }" class="page">
<transition name="page" mode="out-in"> <transition name="page" mode="out-in">
<component :is="Component" /> <component :is="Component" />
</transition> </transition>
</router-view> </router-view>
<p>Inspect Element to see the meta info</p> <div class="metadata">
<h4>Active Metainfo:</h4>
<p>{{ JSON.stringify(metadata, null, 2)}}</p>
</div>
</div> </div>
` `
} }
+27 -22
View File
@@ -1,40 +1,45 @@
import { defineComponent, reactive, toRefs } from 'vue' import { defineComponent, reactive, computed, toRefs, onUnmounted } from 'vue'
import { useRoute } from 'vue-router'
import { useMeta } from 'vue-meta' import { useMeta } from 'vue-meta'
const metaUpdated = 'no' // TODO: afterNavigation hook? let metaUpdated = 'no'
export default defineComponent({ export default defineComponent({
name: 'ChildComponent', name: 'ChildComponent',
props: { setup () {
page: { const route = useRoute()
type: String,
required: true
}
},
setup (props) {
const state = reactive({ const state = reactive({
date: null, date: null,
metaUpdated metaUpdated
}) })
const title = props.page[0].toUpperCase() + props.page.slice(1) const metaConfig = computed(() => ({
console.log('ChildComponent Setup')
useMeta({
charset: 'utf16', charset: 'utf16',
title, title: route.name[0].toUpperCase() + route.name.slice(1),
description: 'Description ' + props.page, description: 'Description ' + route.name,
og: { og: {
title: 'Og Title ' + props.page title: 'Og Title ' + route.name
} }
}))
const { onRemoved } = useMeta(metaConfig)
const pageName = computed(() => route.name)
onUnmounted(() => (metaUpdated = 'yes'))
onRemoved(() => {
console.log('Meta was removed', pageName.value)
}) })
return toRefs(state) return {
...toRefs(state),
pageName
}
}, },
template: ` template: `<div>
<div> <h3>You're looking at the <strong>{{ pageName }}</strong> page</h3>
<h3>You're looking at the <strong>{{ page }}</strong> page</h3>
<p>Has metaInfo been updated due to navigation? {{ metaUpdated }}</p> <p>Has metaInfo been updated due to navigation? {{ metaUpdated }}</p>
</div> </div>`
`
}) })
+5 -9
View File
@@ -4,12 +4,9 @@ import { createMetaManager, defaultConfig, resolveOption, useMeta } from 'vue-me
import App from './App' import App from './App'
import ChildComponent from './Child' import ChildComponent from './Child'
function createView (page) { function createComponent () {
return { return {
name: `section-${page}`, render: () => h(ChildComponent)
render () {
return h(ChildComponent, { page })
}
} }
} }
@@ -41,14 +38,13 @@ useMeta(
const createRouter = (base, isSSR) => createVueRouter({ const createRouter = (base, isSSR) => createVueRouter({
history: isSSR ? createMemoryHistory(base) : createWebHistory(base), history: isSSR ? createMemoryHistory(base) : createWebHistory(base),
routes: [ routes: [
{ name: 'home', path: '/', component: createView('home') }, { name: 'home', path: '/', component: createComponent() },
{ name: 'about', path: '/about', component: createView('about') } { name: 'about', path: '/about', component: createComponent() }
] ]
}) })
export { export {
App, App,
metaManager, metaManager,
createRouter, createRouter
createView
} }
+23 -21
View File
@@ -53,36 +53,37 @@
"vue": "^3.0.0" "vue": "^3.0.0"
}, },
"devDependencies": { "devDependencies": {
"@babel/core": "^7.12.10", "@babel/core": "^7.12.16",
"@babel/plugin-transform-modules-commonjs": "^7.12.1", "@babel/plugin-transform-modules-commonjs": "^7.12.13",
"@babel/preset-typescript": "^7.12.7", "@babel/preset-typescript": "^7.12.16",
"@nuxtjs/eslint-config-typescript": "^5.0.0", "@nuxtjs/eslint-config-typescript": "^5.0.0",
"@rollup/plugin-alias": "^3.1.1", "@rollup/plugin-alias": "^3.1.2",
"@rollup/plugin-commonjs": "^17.0.0", "@rollup/plugin-commonjs": "^17.1.0",
"@rollup/plugin-node-resolve": "^11.1.0", "@rollup/plugin-node-resolve": "^11.2.0",
"@rollup/plugin-replace": "^2.3.4", "@rollup/plugin-replace": "^2.3.4",
"@types/webpack": "^4.41.26", "@types/webpack": "^4.41.26",
"@types/webpack-env": "^1.16.0", "@types/webpack-env": "^1.16.0",
"@typescript-eslint/eslint-plugin": "^4.14.0", "@typescript-eslint/eslint-plugin": "^4.15.0",
"@typescript-eslint/parser": "^4.14.0", "@typescript-eslint/parser": "^4.15.0",
"@vue/compiler-sfc": "^3.0.5", "@vue/compiler-sfc": "^3.0.5",
"@vue/server-renderer": "^3.0.5", "@vue/server-renderer": "^3.0.5",
"@vue/server-test-utils": "^1.1.2", "@vue/server-test-utils": "^1.1.3",
"@vue/test-utils": "^1.1.2", "@vue/test-utils": "^1.1.3",
"@wishy-gift/html-include-chunks-webpack-plugin": "^0.1.5", "@wishy-gift/html-include-chunks-webpack-plugin": "^0.1.5",
"babel-jest": "^26.6.3", "babel-jest": "^26.6.3",
"babel-loader": "^8.2.2", "babel-loader": "^8.2.2",
"babel-plugin-dynamic-import-node": "^2.3.3", "babel-plugin-dynamic-import-node": "^2.3.3",
"babel-plugin-global-define": "^1.0.3", "babel-plugin-global-define": "^1.0.3",
"babel-plugin-module-resolver": "^4.1.0", "babel-plugin-module-resolver": "^4.1.0",
"babel-preset-vue": "^2.0.2",
"browserstack-local": "^1.4.8", "browserstack-local": "^1.4.8",
"chromedriver": "^88.0.0", "chromedriver": "^88.0.0",
"codecov": "^3.8.1", "codecov": "^3.8.1",
"consola": "^2.15.0", "consola": "^2.15.3",
"eslint": "^7.18.0", "eslint": "^7.20.0",
"express-urlrewrite": "^1.4.0", "express-urlrewrite": "^1.4.0",
"geckodriver": "^1.21.1", "geckodriver": "^1.22.1",
"html-webpack-plugin": "^4.5.1", "html-webpack-plugin": "^5.1.0",
"jest": "^26.6.3", "jest": "^26.6.3",
"jest-environment-jsdom": "^26.6.2", "jest-environment-jsdom": "^26.6.2",
"jest-environment-jsdom-global": "^2.0.4", "jest-environment-jsdom-global": "^2.0.4",
@@ -90,25 +91,26 @@
"jsdom": "^16.4.0", "jsdom": "^16.4.0",
"lodash": "^4.17.20", "lodash": "^4.17.20",
"node-env-file": "^0.1.8", "node-env-file": "^0.1.8",
"puppeteer-core": "^5.5.0", "puppeteer-core": "^7.1.0",
"rimraf": "^3.0.2", "rimraf": "^3.0.2",
"rollup": "^2.38.0", "rollup": "^2.39.0",
"rollup-plugin-dts": "^2.0.1", "rollup-plugin-dts": "^2.0.1",
"rollup-plugin-terser": "^7.0.2", "rollup-plugin-terser": "^7.0.2",
"rollup-plugin-typescript2": "^0.29.0", "rollup-plugin-typescript2": "^0.29.0",
"selenium-webdriver": "^4.0.0-alpha.8", "selenium-webdriver": "^4.0.0-alpha.8",
"standard-version": "^9.1.0", "standard-version": "^9.1.0",
"tib": "^0.7.5", "tib": "^0.7.5",
"ts-jest": "^26.4.4", "ts-jest": "^26.5.1",
"ts-loader": "^8.0.14", "ts-loader": "^8.0.17",
"typescript": "^4.1.3", "typescript": "^4.1.5",
"vite": "^2.0.4",
"vue": "^3.0.5", "vue": "^3.0.5",
"vue-jest": "^3.0.7", "vue-jest": "^3.0.7",
"vue-loader": "^16.0.0", "vue-loader": "^16.0.0",
"vue-router": "next", "vue-router": "next",
"webpack": "^5.17.0", "webpack": "^5.21.2",
"webpack-bundle-analyzer": "^4.4.0", "webpack-bundle-analyzer": "^4.4.0",
"webpack-cli": "^4.4.0", "webpack-cli": "^4.5.0",
"webpack-dev-server": "^3.11.2", "webpack-dev-server": "^3.11.2",
"webpackbar": "^4.0.0" "webpackbar": "^4.0.0"
} }
+171 -102
View File
@@ -1,7 +1,7 @@
import { h, reactive, onUnmounted, Teleport, Comment, getCurrentInstance } from 'vue' import { h, reactive, onUnmounted, App, Teleport, Comment, getCurrentInstance, ComponentInternalInstance, Slots } from 'vue'
import type { VNode } from 'vue' import type { VNode, ComponentPublicInstance } from 'vue'
import { isArray, isFunction } from '@vue/shared' import { isArray, isFunction } from '@vue/shared'
import { createMergedObject } from './object-merge' import { createMergedObject, MergedObjectBuilder } from './object-merge'
import { renderMeta } from './render' import { renderMeta } from './render'
import { metaActiveKey } from './symbols' import { metaActiveKey } from './symbols'
import { Metainfo } from './Metainfo' import { Metainfo } from './Metainfo'
@@ -9,10 +9,14 @@ import type { ResolveMethod } from './object-merge'
import type { import type {
MetaActive, MetaActive,
MetaConfig, MetaConfig,
MetaManager, MetaGuards,
MetaGuardRemoved,
MetaResolver, MetaResolver,
MetaResolveContext, MetaResolveContext,
MetaTeleports MetaTeleports,
MetaSource,
MetaResolverSetup,
MetaProxy
} from './types' } from './types'
export const ssrAttribute = 'data-vm-ssr' export const ssrAttribute = 'data-vm-ssr'
@@ -46,127 +50,192 @@ export function addVnode (teleports: MetaTeleports, to: string, vnodes: VNode |
teleports[to].push(...nodes) teleports[to].push(...nodes)
} }
export function createMetaManager (config: MetaConfig, resolver: MetaResolver | ResolveMethod): MetaManager { // eslint-disable-next-line no-use-before-define
let cleanedUpSsr = false export type createMetaManagerMethod = (config: MetaConfig, resolver: MetaResolver | ResolveMethod) => MetaManager
const resolve: ResolveMethod = (options, contexts, active, key, pathSegments) => { export const createMetaManager: createMetaManagerMethod = (config, resolver) => MetaManager.create(config, resolver)
if (isFunction(resolver)) {
return resolver(options, contexts, active, key, pathSegments) export class MetaManager {
config: MetaConfig
target: MergedObjectBuilder
resolver?: MetaResolverSetup
ssrCleanedUp: boolean = false
constructor (config: MetaConfig, target: MergedObjectBuilder, resolver: MetaResolver | ResolveMethod) {
this.config = config
this.target = target
if (resolver && 'setup' in resolver && isFunction(resolver.setup)) {
this.resolver = resolver as unknown as MetaResolverSetup
} }
return resolver.resolve(options, contexts, active, key, pathSegments)
} }
const { addSource, delSource } = createMergedObject(resolve, active) static create: createMetaManagerMethod = (config, resolver) => {
const resolve: ResolveMethod = (options, contexts, active, key, pathSegments) => {
// TODO: validate resolver if (isFunction(resolver)) {
const manager: MetaManager = { return resolver(options, contexts, active, key, pathSegments)
config,
install (app) {
app.component('Metainfo', Metainfo)
app.config.globalProperties.$metaManager = manager
app.provide(metaActiveKey, active)
},
addMeta (metaObj, vm) {
if (!vm) {
vm = getCurrentInstance() || undefined
} }
const resolveContext: MetaResolveContext = { vm } return resolver.resolve(options, contexts, active, key, pathSegments)
if (resolver && 'setup' in resolver && isFunction(resolver.setup)) { }
resolver.setup(resolveContext)
}
// TODO: optimize initial compute const mergedObject = createMergedObject(resolve, active)
const meta = addSource(metaObj, resolveContext, true)
const unmount = () => delSource(meta) // TODO: validate resolver
if (vm) { const manager = new MetaManager(config, mergedObject, resolver)
onUnmounted(unmount) return manager
} }
return { install (app: App): void {
meta, app.component('Metainfo', Metainfo)
unmount
}
},
render ({ slots } = {}) { app.config.globalProperties.$metaManager = this
// cleanup ssr tags if not yet done app.provide(metaActiveKey, active)
if (__BROWSER__ && !cleanedUpSsr) { }
cleanedUpSsr = true
// Listen for DOM loaded because tags in the body couldnt addMeta (metadata: MetaSource, vm?: ComponentInternalInstance): MetaProxy {
// have loaded yet once the manager does it first render if (!vm) {
// (preferable there should only be one meta render on hydration) vm = getCurrentInstance() || undefined
window.addEventListener('DOMContentLoaded', () => { }
const ssrTags = document.querySelectorAll(`[${ssrAttribute}]`)
if (ssrTags && ssrTags.length) { const metaGuards: MetaGuards = ({
Array.from(ssrTags).forEach(el => el.parentNode && el.parentNode.removeChild(el)) removed: []
})
const resolveContext: MetaResolveContext = { vm }
if (this.resolver) {
this.resolver.setup(resolveContext)
}
// TODO: optimize initial compute (once)
const meta = this.target.addSource(metadata, resolveContext, true)
const onRemoved = (removeGuard: MetaGuardRemoved) => metaGuards.removed.push(removeGuard)
const unmount = (ignoreGuards?: boolean) => this.unmount(!!ignoreGuards, meta, metaGuards, vm)
if (vm) {
onUnmounted(unmount)
}
return {
meta,
onRemoved,
unmount
}
}
private unmount (ignoreGuards: boolean, meta: any, metaGuards: MetaGuards, vm?: ComponentInternalInstance) {
if (vm) {
const { $el } = vm.proxy as unknown as ComponentPublicInstance
// Wait for element to be removed from DOM
if ($el && $el.offsetParent) {
let observer: MutationObserver | undefined = new MutationObserver((records) => {
for (const { removedNodes } of records) {
if (!removedNodes) {
continue
}
removedNodes.forEach((el) => {
if (el === $el && observer) {
observer.disconnect()
observer = undefined
this.reallyUnmount(ignoreGuards, meta, metaGuards)
}
})
} }
}) })
observer.observe($el.parentNode, { childList: true })
return
}
}
this.reallyUnmount(ignoreGuards, meta, metaGuards)
}
private async reallyUnmount (ignoreGuards: boolean, meta: any, metaGuards: MetaGuards): Promise<void> {
this.target.delSource(meta)
if (!ignoreGuards && metaGuards) {
await Promise.all(metaGuards.removed.map(removeGuard => removeGuard()))
}
}
render ({ slots }: { slots?: Slots } = {}): VNode[] {
// TODO: clean this method
// cleanup ssr tags if not yet done
if (__BROWSER__ && !this.ssrCleanedUp) {
this.ssrCleanedUp = true
// Listen for DOM loaded because tags in the body couldnt
// have loaded yet once the manager does it first render
// (preferable there should only be one meta render on hydration)
window.addEventListener('DOMContentLoaded', () => {
const ssrTags = document.querySelectorAll(`[${ssrAttribute}]`)
if (ssrTags && ssrTags.length) {
Array.from(ssrTags).forEach(el => el.parentNode && el.parentNode.removeChild(el))
}
})
}
const teleports: MetaTeleports = {}
for (const key in active) {
const config = this.config[key] || {}
let renderedNodes = renderMeta(
{ metainfo: active, slots },
key,
active[key],
config
)
if (!renderedNodes) {
continue
} }
const teleports: MetaTeleports = {} if (!isArray(renderedNodes)) {
renderedNodes = [renderedNodes]
}
for (const key in active) { let defaultTo = key !== 'base' && active[key].to
const config = this.config[key] || {}
let renderedNodes = renderMeta( if (!defaultTo && 'to' in config) {
{ metainfo: active, slots }, defaultTo = config.to
key, }
active[key],
config
)
if (!renderedNodes) { if (!defaultTo && 'attributesFor' in config) {
defaultTo = key
}
for (const { to, vnode } of renderedNodes) {
addVnode(teleports, to || defaultTo || 'head', vnode)
}
}
if (slots) {
for (const slotName in slots) {
const tagName = slotName === 'default' ? 'head' : slotName
// Only teleport the contents of head/body slots
if (tagName !== 'head' && tagName !== 'body') {
continue continue
} }
if (!isArray(renderedNodes)) { const slot = slots[slotName]
renderedNodes = [renderedNodes] if (isFunction(slot)) {
} addVnode(teleports, tagName, slot({ metainfo: active }))
let defaultTo = key !== 'base' && active[key].to
if (!defaultTo && 'to' in config) {
defaultTo = config.to
}
if (!defaultTo && 'attributesFor' in config) {
defaultTo = key
}
for (const { to, vnode } of renderedNodes) {
addVnode(teleports, to || defaultTo || 'head', vnode)
} }
} }
if (slots) {
for (const slotName in slots) {
const tagName = slotName === 'default' ? 'head' : slotName
// Only teleport the contents of head/body slots
if (tagName !== 'head' && tagName !== 'body') {
continue
}
const slot = slots[slotName]
if (isFunction(slot)) {
addVnode(teleports, tagName, slot({ metainfo: active }))
}
}
}
return Object.keys(teleports).map((to) => {
return h(Teleport, { to }, teleports[to])
})
} }
}
return manager return Object.keys(teleports).map((to) => {
return h(Teleport, { to }, teleports[to])
})
}
} }
+2 -2
View File
@@ -1,5 +1,5 @@
import { h } from 'vue' import { h } from 'vue'
import { isArray, isString } from '@vue/shared' import { isArray, isFunction, isString } from '@vue/shared'
import { getTagConfigItem } from './config' import { getTagConfigItem } from './config'
import type { import type {
MetaConfigSectionAttribute, MetaConfigSectionAttribute,
@@ -279,7 +279,7 @@ export function getSlotContent (
groupConfig?: MetaGroupConfig groupConfig?: MetaGroupConfig
): string { ): string {
const slot = slots && slots[slotName] const slot = slots && slots[slotName]
if (!slot) { if (!slot || !isFunction(slot)) {
return content return content
} }
+1 -1
View File
@@ -10,5 +10,5 @@ export const PolySymbol = (name: string) =>
: (__DEV__ ? '[vue-meta]: ' : '_vm_') + name : (__DEV__ ? '[vue-meta]: ' : '_vm_') + name
export const metaActiveKey = /*#__PURE__*/ PolySymbol( export const metaActiveKey = /*#__PURE__*/ PolySymbol(
__DEV__ ? 'active_meta' : 'am' __DEV__ ? 'meta_active' : 'ma'
) as InjectionKey<MetaActive> ) as InjectionKey<MetaActive>
+19 -14
View File
@@ -1,9 +1,11 @@
import type { App, VNode, Slots, ComponentInternalInstance } from 'vue' import type { VNode, Slots, ComponentInternalInstance } from 'vue'
import type { MergedObject, ResolveContext, ResolveMethod } from '../object-merge' import type { MergedObject, ResolveContext, ResolveMethod } from '../object-merge'
import type { MetaConfig } from './config' import type { MetaManager } from '../manager'
export * from './config' export * from './config'
export type Modify<T, R> = Omit<T, keyof R> & R;
export type TODO = any export type TODO = any
/** /**
@@ -18,12 +20,15 @@ export type MetaSource = {
[key: string]: TODO [key: string]: TODO
} }
export type MetaGuardRemoved = () => void | Promise<void>
/** /**
* Return value of the useMeta api * Return value of the useMeta api
*/ */
export type MetaProxy = { export type MetaProxy = {
meta: MetaSourceProxy meta: MetaSourceProxy
unmount: Function | false onRemoved: (removeGuard: MetaGuardRemoved) => void
unmount: (ignoreGuards?: boolean) => void
} }
/** /**
@@ -47,17 +52,9 @@ export type MetaResolver = {
resolve: ResolveMethod resolve: ResolveMethod
} }
/** export type MetaResolverSetup = Modify<MetaResolver, {
* The meta manager setup: MetaResolveSetup
*/ }>
export type MetaManager = {
readonly config: MetaConfig
install(app: App): void
addMeta(source: MetaSource, vm?: ComponentInternalInstance): MetaProxy
render(ctx?: { slots?: Slots }): Array<VNode>
}
/** /**
* @internal * @internal
@@ -66,6 +63,13 @@ export type MetaTeleports = {
[key: string]: Array<VNode> [key: string]: Array<VNode>
} }
/**
* @internal
*/
export interface MetaGuards {
removed: Array<MetaGuardRemoved>
}
/** /**
* @internal * @internal
*/ */
@@ -110,5 +114,6 @@ export type MetaRendered = Array<MetaRenderedNode>
declare module '@vue/runtime-core' { declare module '@vue/runtime-core' {
interface ComponentInternalInstance { interface ComponentInternalInstance {
$metaManager: MetaManager $metaManager: MetaManager
$metaGuards: MetaGuards
} }
} }
+16 -5
View File
@@ -1,6 +1,8 @@
import { inject, getCurrentInstance, ComponentInternalInstance } from 'vue' import { inject, getCurrentInstance, ComponentInternalInstance, isProxy, watch } from 'vue'
import type { MetaManager } from './manager'
import { metaActiveKey } from './symbols' import { metaActiveKey } from './symbols'
import type { MetaManager, MetaActive, MetaSource, MetaProxy } from './types' import type { MetaActive, MetaSource, MetaProxy } from './types'
import { applyDifference } from './utils/diff'
export function getCurrentManager (vm?: ComponentInternalInstance): MetaManager | undefined { export function getCurrentManager (vm?: ComponentInternalInstance): MetaManager | undefined {
if (!vm) { if (!vm) {
@@ -15,18 +17,27 @@ export function getCurrentManager (vm?: ComponentInternalInstance): MetaManager
} }
export function useMeta (source: MetaSource, manager?: MetaManager): MetaProxy { export function useMeta (source: MetaSource, manager?: MetaManager): MetaProxy {
const vm = getCurrentInstance() const vm = getCurrentInstance() || undefined
if (!manager && vm) { if (!manager && vm) {
manager = getCurrentManager(vm) manager = getCurrentManager(vm)
} }
if (!manager) { if (!manager) {
// oopsydoopsy
throw new Error('No manager or current instance') throw new Error('No manager or current instance')
} }
return manager.addMeta(source, vm || undefined) if (isProxy(source)) {
watch(source, (newSource, oldSource) => {
// We only care about first level props, second+ level will already be changed by the merge proxy
applyDifference(metaProxy.meta, newSource, oldSource)
})
source = source.value
}
const metaProxy = manager.addMeta(source, vm)
return metaProxy
} }
export function useActiveMeta (): MetaActive { export function useActiveMeta (): MetaActive {
+31
View File
@@ -0,0 +1,31 @@
import { isObject } from '@vue/shared'
type AnyObject = { [key: string] : any }
/**
* Apply the differences between newSource & oldSource to target
*/
export function applyDifference (target: AnyObject, newSource: AnyObject, oldSource: AnyObject) {
for (const key in newSource) {
if (!(key in oldSource)) {
target[key] = newSource[key]
continue
}
// We dont care about nested objects here , these changes
// should already have been tracked by the MergeProxy
if (isObject(target[key])) {
continue
}
if (newSource[key] !== oldSource[key]) {
target[key] = newSource[key]
}
}
for (const key in oldSource) {
if (!(key in newSource)) {
delete target[key]
}
}
}
+528 -141
View File
File diff suppressed because it is too large Load Diff