Skip to content

Commit 8e21175

Browse files
committed
feat: add option to refresh once during navigation (possible fix for #320)
chore: add es build chore: global window detection chore: small refactor improvements
1 parent 087e4ab commit 8e21175

File tree

12 files changed

+145
-7
lines changed

12 files changed

+145
-7
lines changed

README.md

+6
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@
6464
- [`__dangerouslyDisableSanitizers` ([String])](#__dangerouslydisablesanitizers-string)
6565
- [`__dangerouslyDisableSanitizersByTagID` ({[String]})](#__dangerouslydisablesanitizersbytagid-string)
6666
- [`changed` (Function)](#changed-function)
67+
- [`refreshOnceOnNavigation` (Boolean)](#refreshonceonnavigation-boolean)
6768
- [How `metaInfo` is Resolved](#how-metainfo-is-resolved)
6869
- [Lists of Tags](#lists-of-tags)
6970
- [Performance](#performance)
@@ -652,6 +653,11 @@ Will be called when the client `metaInfo` updates/changes. Receives the followin
652653
}
653654
```
654655

656+
#### `refreshOnceOnNavigation` (Boolean)
657+
658+
Default `false`. If set to `true` then vue-meta will pause updating `metaInfo` during page navigation with vue-router and only refresh once when navigation has finished. It does this by adding a global beforeEach and afterEach navigation guard on the $router instance.
659+
660+
655661
### How `metaInfo` is Resolved
656662

657663
You can define a `metaInfo` property on any component in the tree. Child components that have `metaInfo` will recursively merge their `metaInfo` into the parent context, overwriting any duplicate properties. To better illustrate, consider this component heirarchy:

scripts/build.sh

+17
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
#!/usr/bin/env bash
2+
set -e
3+
4+
# Cleanup
5+
rm -rf lib es
6+
7+
echo 'Compile JS...'
8+
rollup -c scripts/rollup.config.js
9+
echo 'Done.'
10+
echo ''
11+
12+
echo 'Build ES modules...'
13+
NODE_ENV=es babel src --out-dir es --ignore 'src/browser.js'
14+
echo 'Done.'
15+
echo ''
16+
17+
echo 'Done building assets.'

src/client/$meta.js

+5-2
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,18 @@ import { pause, resume } from '../shared/pausing'
22
import refresh from './refresh'
33

44
export default function _$meta(options = {}) {
5+
const _refresh = refresh(options)
6+
const inject = () => {}
7+
58
/**
69
* Returns an injector for server-side rendering.
710
* @this {Object} - the Vue instance (a root component)
811
* @return {Object} - injector
912
*/
1013
return function $meta() {
1114
return {
12-
inject: () => {},
13-
refresh: refresh(options).bind(this),
15+
refresh: _refresh.bind(this),
16+
inject,
1417
pause: pause.bind(this),
1518
resume: resume.bind(this)
1619
}

src/client/triggerUpdate.js

+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import batchUpdate from './batchUpdate'
2+
3+
// store an id to keep track of DOM updates
4+
let batchId = null
5+
6+
export default function triggerUpdate(vm, hookName) {
7+
if (vm.$root._vueMetaInitialized && !vm.$root._vueMetaPaused) {
8+
// batch potential DOM updates to prevent extraneous re-rendering
9+
batchId = batchUpdate(batchId, () => {
10+
vm.$meta().refresh()
11+
batchId = null
12+
})
13+
}
14+
}

src/server/$meta.js

+5-2
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,18 @@ import { pause, resume } from '../shared/pausing'
33
import inject from './inject'
44

55
export default function _$meta(options = {}) {
6+
const _refresh = refresh(options)
7+
const _inject = inject(options)
8+
69
/**
710
* Returns an injector for server-side rendering.
811
* @this {Object} - the Vue instance (a root component)
912
* @return {Object} - injector
1013
*/
1114
return function $meta() {
1215
return {
13-
inject: inject(options).bind(this),
14-
refresh: refresh(options).bind(this),
16+
refresh: _refresh.bind(this),
17+
inject: _inject.bind(this),
1518
pause: pause.bind(this),
1619
resume: resume.bind(this)
1720
}

src/shared/ensure.js

+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import isArray from './isArray'
2+
import { isObject } from './typeof'
3+
4+
export function ensureIsArray(arg, key) {
5+
if (key && isObject(arg)) {
6+
if (!isArray(arg[key])) {
7+
arg[key] = []
8+
}
9+
return arg
10+
} else {
11+
return isArray(arg) ? arg : []
12+
}
13+
}
14+
15+
export function ensuredPush(object, key, el) {
16+
ensureIsArray(object, key)
17+
18+
object[key].push(el)
19+
}

src/shared/getComponentOption.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import deepmerge from 'deepmerge'
22
import uniqueId from 'lodash.uniqueid'
3-
import { isUndefined, isFunction, isObject } from '../shared/typeof'
3+
import { isUndefined, isFunction, isObject } from './typeof'
44
import uniqBy from './uniqBy'
55

66
/**

src/shared/mixin.js

+15-1
Original file line numberDiff line numberDiff line change
@@ -43,15 +43,29 @@ export default function createMixin(options) {
4343
this.$root._vueMetaInitialized = this.$isServer
4444

4545
if (!this.$root._vueMetaInitialized) {
46+
const $rootMeta = this.$root.$meta()
47+
4648
ensuredPush(this.$options, 'mounted', () => {
4749
if (!this.$root._vueMetaInitialized) {
4850
// refresh meta in nextTick so all child components have loaded
4951
this.$nextTick(function () {
50-
this.$root.$meta().refresh()
52+
$rootMeta.refresh()
5153
this.$root._vueMetaInitialized = true
5254
})
5355
}
5456
})
57+
58+
// add vue-router navigation guard to prevent multiple updates during navigation
59+
// only usefull on the client side
60+
if (options.refreshOnceOnNavigation && this.$root.$router) {
61+
const $router = this.$root.$router
62+
$router.beforeEach((to, from, next) => {
63+
$rootMeta.pause()
64+
next()
65+
})
66+
67+
$router.afterEach(() => $rootMeta.resume())
68+
}
5569
}
5670
}
5771

src/shared/options.js

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
import { isObject } from '../shared/typeof'
1+
import { isObject } from './typeof'
2+
23
import {
34
keyName,
45
attribute,

src/shared/pausing.js

+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
export function pause(refresh = true) {
2+
this.$root._vueMetaPaused = true
3+
4+
return () => resume(refresh)
5+
}
6+
7+
export function resume(refresh = true) {
8+
this.$root._vueMetaPaused = false
9+
10+
if (refresh) {
11+
return this.$root.$meta().refresh()
12+
}
13+
}

src/shared/window.js

+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import { isUndefined } from './typeof'
2+
3+
export function hasGlobalWindowFn() {
4+
try {
5+
return !isUndefined(window)
6+
} catch (e) {
7+
return false
8+
}
9+
}
10+
11+
export const hasGlobalWindow = hasGlobalWindowFn()

test/escaping.test.js

+37
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import _getMetaInfo from '../src/shared/getMetaInfo'
2+
import { defaultOptions, loadVueMetaPlugin } from './utils'
3+
4+
const getMetaInfo = (component, escapeSequences) => _getMetaInfo(defaultOptions, component, escapeSequences)
5+
6+
describe('escaping', () => {
7+
let Vue
8+
9+
beforeAll(() => (Vue = loadVueMetaPlugin()))
10+
11+
test('special chars are escaped unless disabled', () => {
12+
const component = new Vue({
13+
metaInfo: {
14+
title: 'Hello & Goodbye',
15+
script: [{ innerHTML: 'Hello & Goodbye' }],
16+
__dangerouslyDisableSanitizers: ['script']
17+
}
18+
})
19+
20+
expect(getMetaInfo(component, [[/&/g, '&']])).toEqual({
21+
title: 'Hello & Goodbye',
22+
titleChunk: 'Hello & Goodbye',
23+
titleTemplate: '%s',
24+
htmlAttrs: {},
25+
headAttrs: {},
26+
bodyAttrs: {},
27+
meta: [],
28+
base: [],
29+
link: [],
30+
style: [],
31+
script: [{ innerHTML: 'Hello & Goodbye' }],
32+
noscript: [],
33+
__dangerouslyDisableSanitizers: ['script'],
34+
__dangerouslyDisableSanitizersByTagID: {}
35+
})
36+
})
37+
})

0 commit comments

Comments
 (0)