Skip to content

Commit

Permalink
feat: add option to refresh once during navigation (possible fix for #…
Browse files Browse the repository at this point in the history
…320)

chore: add es build

chore: global window detection

chore: small refactor improvements
  • Loading branch information
pimlie committed Feb 20, 2019
1 parent 087e4ab commit 8e21175
Show file tree
Hide file tree
Showing 12 changed files with 145 additions and 7 deletions.
6 changes: 6 additions & 0 deletions README.md
Expand Up @@ -64,6 +64,7 @@
- [`__dangerouslyDisableSanitizers` ([String])](#__dangerouslydisablesanitizers-string)
- [`__dangerouslyDisableSanitizersByTagID` ({[String]})](#__dangerouslydisablesanitizersbytagid-string)
- [`changed` (Function)](#changed-function)
- [`refreshOnceOnNavigation` (Boolean)](#refreshonceonnavigation-boolean)
- [How `metaInfo` is Resolved](#how-metainfo-is-resolved)
- [Lists of Tags](#lists-of-tags)
- [Performance](#performance)
Expand Down Expand Up @@ -652,6 +653,11 @@ Will be called when the client `metaInfo` updates/changes. Receives the followin
}
```

#### `refreshOnceOnNavigation` (Boolean)

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.


### How `metaInfo` is Resolved

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:
Expand Down
17 changes: 17 additions & 0 deletions scripts/build.sh
@@ -0,0 +1,17 @@
#!/usr/bin/env bash
set -e

# Cleanup
rm -rf lib es

echo 'Compile JS...'
rollup -c scripts/rollup.config.js
echo 'Done.'
echo ''

echo 'Build ES modules...'
NODE_ENV=es babel src --out-dir es --ignore 'src/browser.js'
echo 'Done.'
echo ''

echo 'Done building assets.'
7 changes: 5 additions & 2 deletions src/client/$meta.js
Expand Up @@ -2,15 +2,18 @@ import { pause, resume } from '../shared/pausing'
import refresh from './refresh'

export default function _$meta(options = {}) {
const _refresh = refresh(options)
const inject = () => {}

/**
* Returns an injector for server-side rendering.
* @this {Object} - the Vue instance (a root component)
* @return {Object} - injector
*/
return function $meta() {
return {
inject: () => {},
refresh: refresh(options).bind(this),
refresh: _refresh.bind(this),
inject,
pause: pause.bind(this),
resume: resume.bind(this)
}
Expand Down
14 changes: 14 additions & 0 deletions src/client/triggerUpdate.js
@@ -0,0 +1,14 @@
import batchUpdate from './batchUpdate'

// store an id to keep track of DOM updates
let batchId = null

export default function triggerUpdate(vm, hookName) {
if (vm.$root._vueMetaInitialized && !vm.$root._vueMetaPaused) {
// batch potential DOM updates to prevent extraneous re-rendering
batchId = batchUpdate(batchId, () => {
vm.$meta().refresh()
batchId = null
})
}
}
7 changes: 5 additions & 2 deletions src/server/$meta.js
Expand Up @@ -3,15 +3,18 @@ import { pause, resume } from '../shared/pausing'
import inject from './inject'

export default function _$meta(options = {}) {
const _refresh = refresh(options)
const _inject = inject(options)

/**
* Returns an injector for server-side rendering.
* @this {Object} - the Vue instance (a root component)
* @return {Object} - injector
*/
return function $meta() {
return {
inject: inject(options).bind(this),
refresh: refresh(options).bind(this),
refresh: _refresh.bind(this),
inject: _inject.bind(this),
pause: pause.bind(this),
resume: resume.bind(this)
}
Expand Down
19 changes: 19 additions & 0 deletions src/shared/ensure.js
@@ -0,0 +1,19 @@
import isArray from './isArray'
import { isObject } from './typeof'

export function ensureIsArray(arg, key) {
if (key && isObject(arg)) {
if (!isArray(arg[key])) {
arg[key] = []
}
return arg
} else {
return isArray(arg) ? arg : []
}
}

export function ensuredPush(object, key, el) {
ensureIsArray(object, key)

object[key].push(el)
}
2 changes: 1 addition & 1 deletion src/shared/getComponentOption.js
@@ -1,6 +1,6 @@
import deepmerge from 'deepmerge'
import uniqueId from 'lodash.uniqueid'
import { isUndefined, isFunction, isObject } from '../shared/typeof'
import { isUndefined, isFunction, isObject } from './typeof'
import uniqBy from './uniqBy'

/**
Expand Down
16 changes: 15 additions & 1 deletion src/shared/mixin.js
Expand Up @@ -43,15 +43,29 @@ export default function createMixin(options) {
this.$root._vueMetaInitialized = this.$isServer

if (!this.$root._vueMetaInitialized) {
const $rootMeta = this.$root.$meta()

ensuredPush(this.$options, 'mounted', () => {
if (!this.$root._vueMetaInitialized) {
// refresh meta in nextTick so all child components have loaded
this.$nextTick(function () {
this.$root.$meta().refresh()
$rootMeta.refresh()
this.$root._vueMetaInitialized = true
})
}
})

// add vue-router navigation guard to prevent multiple updates during navigation
// only usefull on the client side
if (options.refreshOnceOnNavigation && this.$root.$router) {
const $router = this.$root.$router
$router.beforeEach((to, from, next) => {
$rootMeta.pause()
next()
})

$router.afterEach(() => $rootMeta.resume())
}
}
}

Expand Down
3 changes: 2 additions & 1 deletion src/shared/options.js
@@ -1,4 +1,5 @@
import { isObject } from '../shared/typeof'
import { isObject } from './typeof'

import {
keyName,
attribute,
Expand Down
13 changes: 13 additions & 0 deletions src/shared/pausing.js
@@ -0,0 +1,13 @@
export function pause(refresh = true) {
this.$root._vueMetaPaused = true

return () => resume(refresh)
}

export function resume(refresh = true) {
this.$root._vueMetaPaused = false

if (refresh) {
return this.$root.$meta().refresh()
}
}
11 changes: 11 additions & 0 deletions src/shared/window.js
@@ -0,0 +1,11 @@
import { isUndefined } from './typeof'

export function hasGlobalWindowFn() {
try {
return !isUndefined(window)
} catch (e) {
return false
}
}

export const hasGlobalWindow = hasGlobalWindowFn()
37 changes: 37 additions & 0 deletions test/escaping.test.js
@@ -0,0 +1,37 @@
import _getMetaInfo from '../src/shared/getMetaInfo'
import { defaultOptions, loadVueMetaPlugin } from './utils'

const getMetaInfo = (component, escapeSequences) => _getMetaInfo(defaultOptions, component, escapeSequences)

describe('escaping', () => {
let Vue

beforeAll(() => (Vue = loadVueMetaPlugin()))

test('special chars are escaped unless disabled', () => {
const component = new Vue({
metaInfo: {
title: 'Hello & Goodbye',
script: [{ innerHTML: 'Hello & Goodbye' }],
__dangerouslyDisableSanitizers: ['script']
}
})

expect(getMetaInfo(component, [[/&/g, '&']])).toEqual({
title: 'Hello & Goodbye',
titleChunk: 'Hello & Goodbye',
titleTemplate: '%s',
htmlAttrs: {},
headAttrs: {},
bodyAttrs: {},
meta: [],
base: [],
link: [],
style: [],
script: [{ innerHTML: 'Hello & Goodbye' }],
noscript: [],
__dangerouslyDisableSanitizers: ['script'],
__dangerouslyDisableSanitizersByTagID: {}
})
})
})

0 comments on commit 8e21175

Please sign in to comment.