mirror of
https://github.com/tenrok/vue-meta.git
synced 2026-06-23 14:50:34 +03:00
Merge pull request #324 from pimlie/feat-more-refactor
feat: refresh once on navigation & es build
This commit is contained in:
@@ -1,3 +1,13 @@
|
|||||||
{
|
{
|
||||||
"presets": ["@babel/preset-env"]
|
"env": {
|
||||||
|
"test": {
|
||||||
|
"presets": [
|
||||||
|
[ "@babel/env", {
|
||||||
|
"targets": {
|
||||||
|
"node": "current"
|
||||||
|
}
|
||||||
|
}]
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -39,6 +39,7 @@ package-lock.json
|
|||||||
|
|
||||||
# built code
|
# built code
|
||||||
lib
|
lib
|
||||||
|
es
|
||||||
|
|
||||||
# examples yarn lock
|
# examples yarn lock
|
||||||
examples/yarn.lock
|
examples/yarn.lock
|
||||||
|
|||||||
@@ -6,12 +6,6 @@
|
|||||||
Manage page meta info in Vue 2.0 components. SSR + Streaming supported. Inspired by <a href="https://github.com/nfl/react-helmet">react-helmet</a>.
|
Manage page meta info in Vue 2.0 components. SSR + Streaming supported. Inspired by <a href="https://github.com/nfl/react-helmet">react-helmet</a>.
|
||||||
</h5>
|
</h5>
|
||||||
|
|
||||||
<p align="center">
|
|
||||||
<a href="https://github.com/feross/standard">
|
|
||||||
<img src="https://cdn.rawgit.com/feross/standard/master/badge.svg" alt="Standard - JavaScript Style">
|
|
||||||
</a>
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<p align="center">
|
<p align="center">
|
||||||
<a href="https://github.com/nuxt/vue-meta/releases/latest"><img src="https://img.shields.io/github/release/nuxt/vue-meta.svg" alt="github release"></a> <a href="http://npmjs.org/package/vue-meta"><img src="https://img.shields.io/npm/v/vue-meta.svg" alt="npm version"></a> <a href="https://circleci.com/gh/nuxt/vue-meta/"><img src="https://badgen.net/circleci/github/nuxt/vue-meta" alt="Build Status"></a> <a href="https://codecov.io/gh/nuxt/vue-meta"><img src="https://codecov.io/gh/nuxt/vue-meta/branch/master/graph/badge.svg" alt="codecov"></a><br>
|
<a href="https://github.com/nuxt/vue-meta/releases/latest"><img src="https://img.shields.io/github/release/nuxt/vue-meta.svg" alt="github release"></a> <a href="http://npmjs.org/package/vue-meta"><img src="https://img.shields.io/npm/v/vue-meta.svg" alt="npm version"></a> <a href="https://circleci.com/gh/nuxt/vue-meta/"><img src="https://badgen.net/circleci/github/nuxt/vue-meta" alt="Build Status"></a> <a href="https://codecov.io/gh/nuxt/vue-meta"><img src="https://codecov.io/gh/nuxt/vue-meta/branch/master/graph/badge.svg" alt="codecov"></a><br>
|
||||||
<a href="https://david-dm.org/nuxt/vue-meta"><img src="https://david-dm.org/nuxt/vue-meta/status.svg" alt="dependencies Status"></a> <a href="https://david-dm.org/nuxt/vue-meta?type=dev"><img src="https://david-dm.org/nuxt/vue-meta/dev-status.svg" alt="devDependencies Status"></a><br>
|
<a href="https://david-dm.org/nuxt/vue-meta"><img src="https://david-dm.org/nuxt/vue-meta/status.svg" alt="dependencies Status"></a> <a href="https://david-dm.org/nuxt/vue-meta?type=dev"><img src="https://david-dm.org/nuxt/vue-meta/dev-status.svg" alt="devDependencies Status"></a><br>
|
||||||
@@ -70,6 +64,8 @@
|
|||||||
- [`__dangerouslyDisableSanitizers` ([String])](#__dangerouslydisablesanitizers-string)
|
- [`__dangerouslyDisableSanitizers` ([String])](#__dangerouslydisablesanitizers-string)
|
||||||
- [`__dangerouslyDisableSanitizersByTagID` ({[String]})](#__dangerouslydisablesanitizersbytagid-string)
|
- [`__dangerouslyDisableSanitizersByTagID` ({[String]})](#__dangerouslydisablesanitizersbytagid-string)
|
||||||
- [`changed` (Function)](#changed-function)
|
- [`changed` (Function)](#changed-function)
|
||||||
|
- [`refreshOnceOnNavigation` (Boolean)](#refreshonceonnavigation-boolean)
|
||||||
|
- [`afterNavigation` (Function)](#afternavigation-function)
|
||||||
- [How `metaInfo` is Resolved](#how-metainfo-is-resolved)
|
- [How `metaInfo` is Resolved](#how-metainfo-is-resolved)
|
||||||
- [Lists of Tags](#lists-of-tags)
|
- [Lists of Tags](#lists-of-tags)
|
||||||
- [Performance](#performance)
|
- [Performance](#performance)
|
||||||
@@ -658,6 +654,29 @@ 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 and only refresh once when navigation has finished. It does this by adding a global beforeEach and afterEach navigation guard on the vue-router instance.
|
||||||
|
|
||||||
|
#### `afterNavigation` (Function)
|
||||||
|
|
||||||
|
Will be called when the client `metaInfo` has changed after navigation occured. Receives the following parameters:
|
||||||
|
- `newInfo` (Object) - The new state of the `metaInfo` object.
|
||||||
|
|
||||||
|
> :warning: This option only works when `refreshOnceOnNavigation: true`. Please see the [vue-router example](./examples/vue-router)
|
||||||
|
|
||||||
|
`this` context is the component instance `afterNavigation` is defined on.
|
||||||
|
|
||||||
|
```js
|
||||||
|
{
|
||||||
|
metaInfo: {
|
||||||
|
afterNavigation (newInfo) {
|
||||||
|
console.log('Meta info update finished after navigation!')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
### How `metaInfo` is Resolved
|
### 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:
|
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:
|
||||||
|
|||||||
Generated
-7010
File diff suppressed because it is too large
Load Diff
@@ -19,21 +19,21 @@
|
|||||||
},
|
},
|
||||||
"homepage": "https://github.com/nuxt/vue-meta#readme",
|
"homepage": "https://github.com/nuxt/vue-meta#readme",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/core": "^7.2.2",
|
"@babel/core": "^7.3.3",
|
||||||
"@babel/node": "^7.2.2",
|
"@babel/node": "^7.2.2",
|
||||||
"@babel/preset-env": "^7.3.1",
|
"@babel/preset-env": "^7.3.1",
|
||||||
"babel-loader": "^8.0.5",
|
"babel-loader": "^8.0.5",
|
||||||
"cross-env": "^5.2.0",
|
"cross-env": "^5.2.0",
|
||||||
"express": "^4.16.4",
|
"express": "^4.16.4",
|
||||||
"express-urlrewrite": "^1.2.0",
|
"express-urlrewrite": "^1.2.0",
|
||||||
"vue": "^2.6.3",
|
"vue": "^2.6.6",
|
||||||
"vue-loader": "^15.6.2",
|
"vue-loader": "^15.6.4",
|
||||||
"vue-meta": "^1.5.8",
|
"vue-meta": "^1.5.8",
|
||||||
"vue-router": "^3.0.2",
|
"vue-router": "^3.0.2",
|
||||||
"vue-template-compiler": "^2.6.3",
|
"vue-template-compiler": "^2.6.6",
|
||||||
"vuex": "^3.1.0",
|
"vuex": "^3.1.0",
|
||||||
"webpack": "^4.26.1",
|
"webpack": "^4.29.5",
|
||||||
"webpack-dev-server": "^3.1.10",
|
"webpack-dev-server": "^3.2.0",
|
||||||
"webpackbar": "^3.1.5"
|
"webpackbar": "^3.1.5"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,20 +3,30 @@ import VueMeta from 'vue-meta'
|
|||||||
import Router from 'vue-router'
|
import Router from 'vue-router'
|
||||||
|
|
||||||
Vue.use(Router)
|
Vue.use(Router)
|
||||||
Vue.use(VueMeta)
|
Vue.use(VueMeta, {
|
||||||
|
refreshOnceOnNavigation: true
|
||||||
|
})
|
||||||
|
|
||||||
|
let metaUpdated = 'no'
|
||||||
const ChildComponent = {
|
const ChildComponent = {
|
||||||
name: `child-component`,
|
name: `child-component`,
|
||||||
props: ['page'],
|
props: ['page'],
|
||||||
template: `<h3>You're looking at the <strong>{{ page }}</strong> page</h3>`,
|
template: `<div>
|
||||||
|
<h3>You're looking at the <strong>{{ page }}</strong> page</h3>
|
||||||
|
<p>Has metaInfo been updated? {{ metaUpdated }}</p>
|
||||||
|
</div>`,
|
||||||
metaInfo () {
|
metaInfo () {
|
||||||
return {
|
return {
|
||||||
title: `${this.page} - ${this.date && this.date.toTimeString()}`
|
title: `${this.page} - ${this.date && this.date.toTimeString()}`,
|
||||||
|
afterNavigation() {
|
||||||
|
metaUpdated = 'yes'
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
date: null
|
date: null,
|
||||||
|
metaUpdated
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
@@ -28,7 +38,7 @@ const ChildComponent = {
|
|||||||
|
|
||||||
// this wrapper function is not a requirement for vue-router,
|
// this wrapper function is not a requirement for vue-router,
|
||||||
// just a demonstration that render-function style components also work.
|
// just a demonstration that render-function style components also work.
|
||||||
// See https://github.com/declandewet/vue-meta/issues/9 for more info.
|
// See https://github.com/nuxt/vue-meta/issues/9 for more info.
|
||||||
function view (page) {
|
function view (page) {
|
||||||
return {
|
return {
|
||||||
name: `section-${page}`,
|
name: `section-${page}`,
|
||||||
|
|||||||
+2
-7
@@ -22,8 +22,7 @@ module.exports = {
|
|||||||
],
|
],
|
||||||
|
|
||||||
testPathIgnorePatterns: [
|
testPathIgnorePatterns: [
|
||||||
'node_modules',
|
'node_modules'
|
||||||
'old'
|
|
||||||
],
|
],
|
||||||
|
|
||||||
transformIgnorePatterns: [
|
transformIgnorePatterns: [
|
||||||
@@ -39,9 +38,5 @@ module.exports = {
|
|||||||
'ts',
|
'ts',
|
||||||
'js',
|
'js',
|
||||||
'json'
|
'json'
|
||||||
],
|
]
|
||||||
|
|
||||||
reporters: [
|
|
||||||
'default'
|
|
||||||
].concat(process.env.JEST_JUNIT_OUTPUT ? ['jest-junit'] : [])
|
|
||||||
}
|
}
|
||||||
|
|||||||
+76
-71
@@ -1,73 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "vue-meta",
|
"name": "vue-meta",
|
||||||
"description": "Manage page meta info in Vue 2.0 server-rendered apps",
|
|
||||||
"version": "1.5.8",
|
"version": "1.5.8",
|
||||||
"author": "Declan de Wet <declandewet@me.com>",
|
"description": "Manage page meta info in Vue 2.0 server-rendered apps",
|
||||||
"bugs": "https://github.com/nuxt/vue-meta/issues",
|
|
||||||
"scripts": {
|
|
||||||
"build": "rimraf lib && rollup -c scripts/rollup.config.js",
|
|
||||||
"codecov": "codecov",
|
|
||||||
"deploy": "npm version",
|
|
||||||
"dev": "cd examples && npm run dev && cd ..",
|
|
||||||
"lint": "eslint src test",
|
|
||||||
"postdeploy": "git push origin master --follow-tags && npm run release",
|
|
||||||
"postversion": "npm run update-cdn && git add . && git commit -m \":ship: CDN update\"",
|
|
||||||
"predeploy": "git checkout master && git pull -r",
|
|
||||||
"prerelease": "npm run build",
|
|
||||||
"preversion": "npm run toc",
|
|
||||||
"release": "npm publish",
|
|
||||||
"test": "jest",
|
|
||||||
"toc": "doctoc README.md --title '# Table of Contents'",
|
|
||||||
"update-cdn": "babel-node scripts/update-cdn.js"
|
|
||||||
},
|
|
||||||
"dependencies": {
|
|
||||||
"deepmerge": "^3.0.0",
|
|
||||||
"lodash.isplainobject": "^4.0.6",
|
|
||||||
"lodash.uniqueid": "^4.0.1"
|
|
||||||
},
|
|
||||||
"devDependencies": {
|
|
||||||
"@babel/core": "^7.1.6",
|
|
||||||
"@babel/node": "^7.2.2",
|
|
||||||
"@babel/preset-env": "^7.1.6",
|
|
||||||
"@nuxtjs/eslint-config": "^0.0.1",
|
|
||||||
"@vue/server-test-utils": "^1.0.0-beta.29",
|
|
||||||
"@vue/test-utils": "^1.0.0-beta.29",
|
|
||||||
"babel-core": "^7.0.0-bridge",
|
|
||||||
"babel-eslint": "^10.0.1",
|
|
||||||
"babel-jest": "^24.1.0",
|
|
||||||
"babel-loader": "^8.0.4",
|
|
||||||
"codecov": "^3.1.0",
|
|
||||||
"doctoc": "^1.4.0",
|
|
||||||
"eslint": "^5.13.0",
|
|
||||||
"eslint-config-standard": "^12.0.0",
|
|
||||||
"eslint-plugin-import": "^2.16.0",
|
|
||||||
"eslint-plugin-jest": "^22.2.2",
|
|
||||||
"eslint-plugin-node": "^8.0.1",
|
|
||||||
"eslint-plugin-promise": "^4.0.1",
|
|
||||||
"eslint-plugin-standard": "^4.0.0",
|
|
||||||
"eslint-plugin-vue": "^5.1.0",
|
|
||||||
"jest": "^24.1.0",
|
|
||||||
"jsdom": "^13.2.0",
|
|
||||||
"jsdom-global": "^3.0.2",
|
|
||||||
"rimraf": "^2.6.2",
|
|
||||||
"rollup": "^1.0.0",
|
|
||||||
"rollup-plugin-buble": "^0.19.4",
|
|
||||||
"rollup-plugin-commonjs": "^9.2.0",
|
|
||||||
"rollup-plugin-json": "^3.1.0",
|
|
||||||
"rollup-plugin-node-resolve": "^4.0.0",
|
|
||||||
"rollup-plugin-terser": "^4.0.4",
|
|
||||||
"update-section": "^0.3.3",
|
|
||||||
"vue": "^2.6.3",
|
|
||||||
"vue-jest": "^3.0.2",
|
|
||||||
"vue-server-renderer": "^2.6.3",
|
|
||||||
"vue-template-compiler": "^2.6.3"
|
|
||||||
},
|
|
||||||
"files": [
|
|
||||||
"lib",
|
|
||||||
"types/index.d.ts",
|
|
||||||
"types/vue.d.ts"
|
|
||||||
],
|
|
||||||
"homepage": "https://github.com/nuxt/vue-meta",
|
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"attribute",
|
"attribute",
|
||||||
"google",
|
"google",
|
||||||
@@ -82,13 +16,84 @@
|
|||||||
"universal",
|
"universal",
|
||||||
"vue"
|
"vue"
|
||||||
],
|
],
|
||||||
|
"homepage": "https://github.com/nuxt/vue-meta",
|
||||||
|
"bugs": "https://github.com/nuxt/vue-meta/issues",
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "git@github.com/nuxt/vue-meta.git"
|
||||||
|
},
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"author": "Declan de Wet <declandewet@me.com>",
|
||||||
|
"files": [
|
||||||
|
"lib",
|
||||||
|
"es",
|
||||||
|
"types/index.d.ts",
|
||||||
|
"types/vue.d.ts"
|
||||||
|
],
|
||||||
"main": "lib/vue-meta.common.js",
|
"main": "lib/vue-meta.common.js",
|
||||||
"web": "lib/vue-meta.js",
|
"web": "lib/vue-meta.js",
|
||||||
"module": "src/index.js",
|
"module": "es/index.js",
|
||||||
"typings": "types/index.d.ts",
|
"typings": "types/index.d.ts",
|
||||||
"repository": {
|
"scripts": {
|
||||||
"url": "git@github.com/nuxt/vue-meta.git",
|
"build": "yarn build:other && yarn build:es",
|
||||||
"type": "git"
|
"build:es": "rimraf es && babel src --env-name es --out-dir es --ignore 'src/browser.js'",
|
||||||
|
"build:other": "rimraf lib && rollup -c scripts/rollup.config.js",
|
||||||
|
"codecov": "codecov",
|
||||||
|
"predeploy": "git checkout master && git pull -r",
|
||||||
|
"deploy": "npm version",
|
||||||
|
"postdeploy": "git push origin master --follow-tags && npm run release",
|
||||||
|
"dev": "cd examples && npm run dev && cd ..",
|
||||||
|
"lint": "eslint src test",
|
||||||
|
"prerelease": "npm run build",
|
||||||
|
"release": "npm publish",
|
||||||
|
"test": "jest",
|
||||||
|
"toc": "doctoc README.md --title '# Table of Contents'",
|
||||||
|
"update-cdn": "babel-node scripts/update-cdn.js",
|
||||||
|
"preversion": "npm run toc",
|
||||||
|
"postversion": "npm run update-cdn && git add . && git commit -m \":ship: CDN update\""
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"deepmerge": "^3.2.0",
|
||||||
|
"lodash.isplainobject": "^4.0.6",
|
||||||
|
"lodash.uniqueid": "^4.0.1"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@babel/cli": "^7.2.3",
|
||||||
|
"@babel/core": "^7.3.3",
|
||||||
|
"@babel/node": "^7.2.2",
|
||||||
|
"@babel/preset-env": "^7.3.1",
|
||||||
|
"@nuxtjs/eslint-config": "^0.0.1",
|
||||||
|
"@vue/server-test-utils": "^1.0.0-beta.29",
|
||||||
|
"@vue/test-utils": "^1.0.0-beta.29",
|
||||||
|
"babel-core": "^7.0.0-bridge",
|
||||||
|
"babel-eslint": "^10.0.1",
|
||||||
|
"babel-jest": "^24.1.0",
|
||||||
|
"babel-loader": "^8.0.5",
|
||||||
|
"codecov": "^3.2.0",
|
||||||
|
"doctoc": "^1.4.0",
|
||||||
|
"eslint": "^5.14.1",
|
||||||
|
"eslint-config-standard": "^12.0.0",
|
||||||
|
"eslint-plugin-import": "^2.16.0",
|
||||||
|
"eslint-plugin-jest": "^22.3.0",
|
||||||
|
"eslint-plugin-node": "^8.0.1",
|
||||||
|
"eslint-plugin-promise": "^4.0.1",
|
||||||
|
"eslint-plugin-standard": "^4.0.0",
|
||||||
|
"eslint-plugin-vue": "^5.2.2",
|
||||||
|
"esm": "^3.2.5",
|
||||||
|
"jest": "^24.1.0",
|
||||||
|
"jsdom": "^13.2.0",
|
||||||
|
"jsdom-global": "^3.0.2",
|
||||||
|
"rimraf": "^2.6.3",
|
||||||
|
"rollup": "^1.2.2",
|
||||||
|
"rollup-plugin-buble": "^0.19.6",
|
||||||
|
"rollup-plugin-commonjs": "^9.2.0",
|
||||||
|
"rollup-plugin-json": "^3.1.0",
|
||||||
|
"rollup-plugin-node-resolve": "^4.0.0",
|
||||||
|
"rollup-plugin-terser": "^4.0.4",
|
||||||
|
"update-section": "^0.3.3",
|
||||||
|
"vue": "^2.6.6",
|
||||||
|
"vue-jest": "^3.0.3",
|
||||||
|
"vue-server-renderer": "^2.6.6",
|
||||||
|
"vue-template-compiler": "^2.6.6"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -48,7 +48,6 @@ export default [{
|
|||||||
output: {
|
output: {
|
||||||
...baseConfig.output,
|
...baseConfig.output,
|
||||||
file: pkg.main,
|
file: pkg.main,
|
||||||
intro: 'var window',
|
|
||||||
format: 'cjs'
|
format: 'cjs'
|
||||||
},
|
},
|
||||||
external: Object.keys(pkg.dependencies)
|
external: Object.keys(pkg.dependencies)
|
||||||
|
|||||||
+9
-6
@@ -3,25 +3,28 @@ import createMixin from './shared/mixin'
|
|||||||
import setOptions from './shared/options'
|
import setOptions from './shared/options'
|
||||||
import { isUndefined } from './shared/typeof'
|
import { isUndefined } from './shared/typeof'
|
||||||
import $meta from './client/$meta'
|
import $meta from './client/$meta'
|
||||||
|
import { hasMetaInfo } from './shared/hasMetaInfo'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Plugin install function.
|
* Plugin install function.
|
||||||
* @param {Function} Vue - the Vue constructor.
|
* @param {Function} Vue - the Vue constructor.
|
||||||
*/
|
*/
|
||||||
function VueMeta(Vue, options = {}) {
|
function install(Vue, options = {}) {
|
||||||
options = setOptions(options)
|
options = setOptions(options)
|
||||||
|
|
||||||
Vue.prototype.$meta = $meta(options)
|
Vue.prototype.$meta = $meta(options)
|
||||||
|
|
||||||
Vue.mixin(createMixin(options))
|
Vue.mixin(createMixin(Vue, options))
|
||||||
}
|
}
|
||||||
|
|
||||||
VueMeta.version = version
|
|
||||||
|
|
||||||
// automatic install
|
// automatic install
|
||||||
if (!isUndefined(window) && !isUndefined(window.Vue)) {
|
if (!isUndefined(window) && !isUndefined(window.Vue)) {
|
||||||
/* istanbul ignore next */
|
/* istanbul ignore next */
|
||||||
Vue.use(VueMeta)
|
install(window.Vue)
|
||||||
}
|
}
|
||||||
|
|
||||||
export default VueMeta
|
export default {
|
||||||
|
version,
|
||||||
|
install,
|
||||||
|
hasMetaInfo
|
||||||
|
}
|
||||||
|
|||||||
+8
-2
@@ -1,6 +1,10 @@
|
|||||||
|
import { pause, resume } from '../shared/pausing'
|
||||||
import refresh from './refresh'
|
import refresh from './refresh'
|
||||||
|
|
||||||
export default function _$meta(options = {}) {
|
export default function _$meta(options = {}) {
|
||||||
|
const _refresh = refresh(options)
|
||||||
|
const inject = () => {}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns an injector for server-side rendering.
|
* Returns an injector for server-side rendering.
|
||||||
* @this {Object} - the Vue instance (a root component)
|
* @this {Object} - the Vue instance (a root component)
|
||||||
@@ -8,8 +12,10 @@ export default function _$meta(options = {}) {
|
|||||||
*/
|
*/
|
||||||
return function $meta() {
|
return function $meta() {
|
||||||
return {
|
return {
|
||||||
inject: () => {},
|
refresh: _refresh.bind(this),
|
||||||
refresh: refresh(options).bind(this)
|
inject,
|
||||||
|
pause: pause.bind(this),
|
||||||
|
resume: resume.bind(this)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import { isUndefined } from '../shared/typeof'
|
import { hasGlobalWindow } from '../shared/window'
|
||||||
|
|
||||||
// fallback to timers if rAF not present
|
// fallback to timers if rAF not present
|
||||||
const stopUpdate = (!isUndefined(window) ? window.cancelAnimationFrame : null) || clearTimeout
|
const stopUpdate = (hasGlobalWindow ? window.cancelAnimationFrame : null) || clearTimeout
|
||||||
const startUpdate = (!isUndefined(window) ? window.requestAnimationFrame : null) || (cb => setTimeout(cb, 0))
|
const startUpdate = (hasGlobalWindow ? window.requestAnimationFrame : null) || (cb => setTimeout(cb, 0))
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Performs a batched update. Uses requestAnimationFrame to prevent
|
* Performs a batched update. Uses requestAnimationFrame to prevent
|
||||||
@@ -15,7 +15,10 @@ const startUpdate = (!isUndefined(window) ? window.requestAnimationFrame : null)
|
|||||||
* @return {Number} id - a new ID
|
* @return {Number} id - a new ID
|
||||||
*/
|
*/
|
||||||
export default function batchUpdate(id, callback) {
|
export default function batchUpdate(id, callback) {
|
||||||
stopUpdate(id)
|
if (id) {
|
||||||
|
stopUpdate(id)
|
||||||
|
}
|
||||||
|
|
||||||
return startUpdate(() => {
|
return startUpdate(() => {
|
||||||
id = null
|
id = null
|
||||||
callback()
|
callback()
|
||||||
|
|||||||
+10
-3
@@ -3,6 +3,14 @@ import { isFunction } from '../shared/typeof'
|
|||||||
import updateClientMetaInfo from './updateClientMetaInfo'
|
import updateClientMetaInfo from './updateClientMetaInfo'
|
||||||
|
|
||||||
export default function _refresh(options = {}) {
|
export default function _refresh(options = {}) {
|
||||||
|
const escapeSequences = [
|
||||||
|
[/&/g, '\u0026'],
|
||||||
|
[/</g, '\u003c'],
|
||||||
|
[/>/g, '\u003e'],
|
||||||
|
[/"/g, '\u0022'],
|
||||||
|
[/'/g, '\u0027']
|
||||||
|
]
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* When called, will update the current meta info with new meta info.
|
* When called, will update the current meta info with new meta info.
|
||||||
* Useful when updating meta info as the result of an asynchronous
|
* Useful when updating meta info as the result of an asynchronous
|
||||||
@@ -14,15 +22,14 @@ export default function _refresh(options = {}) {
|
|||||||
* @return {Object} - new meta info
|
* @return {Object} - new meta info
|
||||||
*/
|
*/
|
||||||
return function refresh() {
|
return function refresh() {
|
||||||
const metaInfo = getMetaInfo(options, this.$root)
|
const metaInfo = getMetaInfo(options, this.$root, escapeSequences)
|
||||||
|
|
||||||
const tags = updateClientMetaInfo(options, metaInfo)
|
const tags = updateClientMetaInfo(options, metaInfo)
|
||||||
|
|
||||||
// emit "event" with new info
|
// emit "event" with new info
|
||||||
if (tags && isFunction(metaInfo.changed)) {
|
if (tags && isFunction(metaInfo.changed)) {
|
||||||
metaInfo.changed.call(this, metaInfo, tags.addedTags, tags.removedTags)
|
metaInfo.changed.call(this, metaInfo, tags.addedTags, tags.removedTags)
|
||||||
}
|
}
|
||||||
|
|
||||||
return metaInfo
|
return { vm: this, metaInfo, tags }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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._vueMeta.initialized && !vm.$root._vueMeta.paused) {
|
||||||
|
// batch potential DOM updates to prevent extraneous re-rendering
|
||||||
|
batchId = batchUpdate(batchId, () => {
|
||||||
|
vm.$meta().refresh()
|
||||||
|
batchId = null
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
+8
-5
@@ -2,19 +2,22 @@ import { version } from '../package.json'
|
|||||||
import createMixin from './shared/mixin'
|
import createMixin from './shared/mixin'
|
||||||
import setOptions from './shared/options'
|
import setOptions from './shared/options'
|
||||||
import $meta from './server/$meta'
|
import $meta from './server/$meta'
|
||||||
|
import { hasMetaInfo } from './shared/hasMetaInfo'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Plugin install function.
|
* Plugin install function.
|
||||||
* @param {Function} Vue - the Vue constructor.
|
* @param {Function} Vue - the Vue constructor.
|
||||||
*/
|
*/
|
||||||
function VueMeta(Vue, options = {}) {
|
function install(Vue, options = {}) {
|
||||||
options = setOptions(options)
|
options = setOptions(options)
|
||||||
|
|
||||||
Vue.prototype.$meta = $meta(options)
|
Vue.prototype.$meta = $meta(options)
|
||||||
|
|
||||||
Vue.mixin(createMixin(options))
|
Vue.mixin(createMixin(Vue, options))
|
||||||
}
|
}
|
||||||
|
|
||||||
VueMeta.version = version
|
export default {
|
||||||
|
version,
|
||||||
export default VueMeta
|
install,
|
||||||
|
hasMetaInfo
|
||||||
|
}
|
||||||
|
|||||||
+8
-2
@@ -1,7 +1,11 @@
|
|||||||
import refresh from '../client/refresh'
|
import refresh from '../client/refresh'
|
||||||
|
import { pause, resume } from '../shared/pausing'
|
||||||
import inject from './inject'
|
import inject from './inject'
|
||||||
|
|
||||||
export default function _$meta(options = {}) {
|
export default function _$meta(options = {}) {
|
||||||
|
const _refresh = refresh(options)
|
||||||
|
const _inject = inject(options)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns an injector for server-side rendering.
|
* Returns an injector for server-side rendering.
|
||||||
* @this {Object} - the Vue instance (a root component)
|
* @this {Object} - the Vue instance (a root component)
|
||||||
@@ -9,8 +13,10 @@ export default function _$meta(options = {}) {
|
|||||||
*/
|
*/
|
||||||
return function $meta() {
|
return function $meta() {
|
||||||
return {
|
return {
|
||||||
inject: inject(options).bind(this),
|
refresh: _refresh.bind(this),
|
||||||
refresh: refresh(options).bind(this)
|
inject: _inject.bind(this),
|
||||||
|
pause: pause.bind(this),
|
||||||
|
resume: resume.bind(this)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,14 @@ import { metaInfoOptionKeys } from '../shared/constants'
|
|||||||
import generateServerInjector from './generateServerInjector'
|
import generateServerInjector from './generateServerInjector'
|
||||||
|
|
||||||
export default function _inject(options = {}) {
|
export default function _inject(options = {}) {
|
||||||
|
const escapeSequences = [
|
||||||
|
[/&/g, '&'],
|
||||||
|
[/</g, '<'],
|
||||||
|
[/>/g, '>'],
|
||||||
|
[/"/g, '"'],
|
||||||
|
[/'/g, ''']
|
||||||
|
]
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Converts the state of the meta info object such that each item
|
* Converts the state of the meta info object such that each item
|
||||||
* can be compiled to a tag string on the server
|
* can be compiled to a tag string on the server
|
||||||
@@ -10,10 +18,9 @@ export default function _inject(options = {}) {
|
|||||||
* @this {Object} - Vue instance - ideally the root component
|
* @this {Object} - Vue instance - ideally the root component
|
||||||
* @return {Object} - server meta info with `toString` methods
|
* @return {Object} - server meta info with `toString` methods
|
||||||
*/
|
*/
|
||||||
|
|
||||||
return function inject() {
|
return function inject() {
|
||||||
// get meta info with sensible defaults
|
// get meta info with sensible defaults
|
||||||
const metaInfo = getMetaInfo(options, this.$root)
|
const metaInfo = getMetaInfo(options, this.$root, escapeSequences)
|
||||||
|
|
||||||
// generate server injectors
|
// generate server injectors
|
||||||
for (const key in metaInfo) {
|
for (const key in metaInfo) {
|
||||||
|
|||||||
@@ -0,0 +1,19 @@
|
|||||||
|
import isArray from './isArray'
|
||||||
|
import { isObject } from './typeof'
|
||||||
|
|
||||||
|
export function ensureIsArray(arg, key) {
|
||||||
|
if (!key || !isObject(arg)) {
|
||||||
|
return isArray(arg) ? arg : []
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isArray(arg[key])) {
|
||||||
|
arg[key] = []
|
||||||
|
}
|
||||||
|
return arg
|
||||||
|
}
|
||||||
|
|
||||||
|
export function ensuredPush(object, key, el) {
|
||||||
|
ensureIsArray(object, key)
|
||||||
|
|
||||||
|
object[key].push(el)
|
||||||
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
import deepmerge from 'deepmerge'
|
import deepmerge from 'deepmerge'
|
||||||
import uniqueId from 'lodash.uniqueid'
|
import uniqueId from 'lodash.uniqueid'
|
||||||
import { isUndefined, isFunction, isObject } from '../shared/typeof'
|
import { isUndefined, isFunction, isObject } from './typeof'
|
||||||
import uniqBy from './uniqBy'
|
import uniqBy from './uniqBy'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -1,25 +1,9 @@
|
|||||||
import deepmerge from 'deepmerge'
|
import deepmerge from 'deepmerge'
|
||||||
import isPlainObject from 'lodash.isplainobject'
|
import isPlainObject from 'lodash.isplainobject'
|
||||||
import { isUndefined, isFunction, isString } from '../shared/typeof'
|
import { isFunction, isString } from './typeof'
|
||||||
import isArray from './isArray'
|
import isArray from './isArray'
|
||||||
import getComponentOption from './getComponentOption'
|
import getComponentOption from './getComponentOption'
|
||||||
|
|
||||||
const escapeHTML = str => isUndefined(window)
|
|
||||||
// server-side escape sequence
|
|
||||||
? String(str)
|
|
||||||
.replace(/&/g, '&')
|
|
||||||
.replace(/</g, '<')
|
|
||||||
.replace(/>/g, '>')
|
|
||||||
.replace(/"/g, '"')
|
|
||||||
.replace(/'/g, ''')
|
|
||||||
// client-side escape sequence
|
|
||||||
: String(str)
|
|
||||||
.replace(/&/g, '\u0026')
|
|
||||||
.replace(/</g, '\u003c')
|
|
||||||
.replace(/>/g, '\u003e')
|
|
||||||
.replace(/"/g, '\u0022')
|
|
||||||
.replace(/'/g, '\u0027')
|
|
||||||
|
|
||||||
const applyTemplate = (component, template, chunk) =>
|
const applyTemplate = (component, template, chunk) =>
|
||||||
isFunction(template) ? template.call(component, chunk) : template.replace(/%s/g, chunk)
|
isFunction(template) ? template.call(component, chunk) : template.replace(/%s/g, chunk)
|
||||||
|
|
||||||
@@ -30,7 +14,7 @@ const applyTemplate = (component, template, chunk) =>
|
|||||||
* @param {Object} component - the Vue instance to get meta info from
|
* @param {Object} component - the Vue instance to get meta info from
|
||||||
* @return {Object} - returned meta info
|
* @return {Object} - returned meta info
|
||||||
*/
|
*/
|
||||||
export default function getMetaInfo({ keyName, tagIDKeyName, metaTemplateKeyName, contentKeyName } = {}, component) {
|
export default function getMetaInfo({ keyName, tagIDKeyName, metaTemplateKeyName, contentKeyName } = {}, component, escapeSequences = []) {
|
||||||
// set some sane defaults
|
// set some sane defaults
|
||||||
const defaultInfo = {
|
const defaultInfo = {
|
||||||
title: '',
|
title: '',
|
||||||
@@ -139,7 +123,7 @@ export default function getMetaInfo({ keyName, tagIDKeyName, metaTemplateKeyName
|
|||||||
|
|
||||||
if (!isDisabled) {
|
if (!isDisabled) {
|
||||||
if (isString(val)) {
|
if (isString(val)) {
|
||||||
escaped[key] = escapeHTML(val)
|
escaped[key] = escapeSequences.reduce((val, [v, r]) => val.replace(v, r), val)
|
||||||
} else if (isPlainObject(val)) {
|
} else if (isPlainObject(val)) {
|
||||||
escaped[key] = escape(val)
|
escaped[key] = escape(val)
|
||||||
} else if (isArray(val)) {
|
} else if (isArray(val)) {
|
||||||
|
|||||||
@@ -0,0 +1,3 @@
|
|||||||
|
export function hasMetaInfo(vm = this) {
|
||||||
|
return vm && !!vm._vueMeta
|
||||||
|
}
|
||||||
+63
-83
@@ -1,28 +1,36 @@
|
|||||||
import batchUpdate from '../client/batchUpdate'
|
import triggerUpdate from '../client/triggerUpdate'
|
||||||
import { isUndefined, isFunction } from '../shared/typeof'
|
import { isUndefined, isFunction } from './typeof'
|
||||||
|
import { ensuredPush } from './ensure'
|
||||||
export default function createMixin(options) {
|
|
||||||
// store an id to keep track of DOM updates
|
|
||||||
let batchID = null
|
|
||||||
|
|
||||||
|
export default function createMixin(Vue, options) {
|
||||||
// for which Vue lifecycle hooks should the metaInfo be refreshed
|
// for which Vue lifecycle hooks should the metaInfo be refreshed
|
||||||
const updateOnLifecycleHook = ['activated', 'deactivated', 'beforeMount']
|
const updateOnLifecycleHook = ['activated', 'deactivated', 'beforeMount']
|
||||||
|
|
||||||
const triggerUpdate = (vm) => {
|
|
||||||
if (vm.$root._vueMetaInitialized) {
|
|
||||||
// batch potential DOM updates to prevent extraneous re-rendering
|
|
||||||
batchID = batchUpdate(batchID, () => vm.$meta().refresh())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// watch for client side component updates
|
// watch for client side component updates
|
||||||
return {
|
return {
|
||||||
beforeCreate() {
|
beforeCreate() {
|
||||||
|
Object.defineProperty(this, '_hasMetaInfo', {
|
||||||
|
get() {
|
||||||
|
// Show deprecation warning once when devtools enabled
|
||||||
|
if (Vue.config.devtools && !this.$root._vueMeta.hasMetaInfoDeprecationWarningShown) {
|
||||||
|
console.warn('VueMeta DeprecationWarning: _hasMetaInfo has been deprecated and will be removed in a future version. Please import hasMetaInfo and use hasMetaInfo(vm) instead') // eslint-disable-line no-console
|
||||||
|
this.$root._vueMeta.hasMetaInfoDeprecationWarningShown = true
|
||||||
|
}
|
||||||
|
return !!this._vueMeta
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
// Add a marker to know if it uses metaInfo
|
// Add a marker to know if it uses metaInfo
|
||||||
// _vnode is used to know that it's attached to a real component
|
// _vnode is used to know that it's attached to a real component
|
||||||
// useful if we use some mixin to add some meta tags (like nuxt-i18n)
|
// useful if we use some mixin to add some meta tags (like nuxt-i18n)
|
||||||
if (!isUndefined(this.$options[options.keyName]) && this.$options[options.keyName] !== null) {
|
if (!isUndefined(this.$options[options.keyName]) && this.$options[options.keyName] !== null) {
|
||||||
this._hasMetaInfo = true
|
if (!this.$root._vueMeta) {
|
||||||
|
this.$root._vueMeta = {}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this._vueMeta) {
|
||||||
|
this._vueMeta = true
|
||||||
|
}
|
||||||
|
|
||||||
// coerce function-style metaInfo to a computed prop so we can observe
|
// coerce function-style metaInfo to a computed prop so we can observe
|
||||||
// it on creation
|
// it on creation
|
||||||
@@ -36,41 +44,62 @@ export default function createMixin(options) {
|
|||||||
// if computed $metaInfo exists, watch it for updates & trigger a refresh
|
// if computed $metaInfo exists, watch it for updates & trigger a refresh
|
||||||
// when it changes (i.e. automatically handle async actions that affect metaInfo)
|
// when it changes (i.e. automatically handle async actions that affect metaInfo)
|
||||||
// credit for this suggestion goes to [Sébastien Chopin](https://github.com/Atinux)
|
// credit for this suggestion goes to [Sébastien Chopin](https://github.com/Atinux)
|
||||||
this.$options.created = this.$options.created || []
|
ensuredPush(this.$options, 'created', () => {
|
||||||
this.$options.created.push(() => {
|
this.$watch('$metaInfo', function () {
|
||||||
this.$watch('$metaInfo', () => triggerUpdate(this))
|
triggerUpdate(this, 'watcher')
|
||||||
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
updateOnLifecycleHook.forEach((lifecycleHook) => {
|
|
||||||
this.$options[lifecycleHook] = this.$options[lifecycleHook] || []
|
|
||||||
this.$options[lifecycleHook].push(() => triggerUpdate(this))
|
|
||||||
})
|
|
||||||
|
|
||||||
// force an initial refresh on page load and prevent other lifecycleHooks
|
// force an initial refresh on page load and prevent other lifecycleHooks
|
||||||
// to triggerUpdate until this initial refresh is finished
|
// to triggerUpdate until this initial refresh is finished
|
||||||
// this is to make sure that when a page is opened in an inactive tab which
|
// this is to make sure that when a page is opened in an inactive tab which
|
||||||
// has throttled rAF/timers we still immeditately set the page title
|
// has throttled rAF/timers we still immeditately set the page title
|
||||||
if (isUndefined(this.$root._vueMetaInitialized)) {
|
if (isUndefined(this.$root._vueMeta.initialized)) {
|
||||||
this.$root._vueMetaInitialized = false
|
this.$root._vueMeta.initialized = this.$isServer
|
||||||
|
|
||||||
this.$root.$options.mounted = this.$root.$options.mounted || []
|
if (!this.$root._vueMeta.initialized) {
|
||||||
this.$root.$options.mounted.push(() => {
|
const $rootMeta = this.$root.$meta()
|
||||||
if (!this.$root._vueMetaInitialized) {
|
|
||||||
this.$nextTick(function () {
|
ensuredPush(this.$options, 'mounted', () => {
|
||||||
this.$root.$meta().refresh()
|
if (!this.$root._vueMeta.initialized) {
|
||||||
this.$root._vueMetaInitialized = true
|
// refresh meta in nextTick so all child components have loaded
|
||||||
|
this.$nextTick(function () {
|
||||||
|
$rootMeta.refresh()
|
||||||
|
this.$root._vueMeta.initialized = 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(() => {
|
||||||
|
const { vm, metaInfo } = $rootMeta.resume()
|
||||||
|
if (metaInfo && metaInfo.afterNavigation && isFunction(metaInfo.afterNavigation)) {
|
||||||
|
metaInfo.afterNavigation.call(vm, metaInfo)
|
||||||
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
})
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// do not trigger refresh on the server side
|
// do not trigger refresh on the server side
|
||||||
if (!this.$isServer) {
|
if (!this.$isServer) {
|
||||||
|
// no need to add this hooks on server side
|
||||||
|
updateOnLifecycleHook.forEach((lifecycleHook) => {
|
||||||
|
ensuredPush(this.$options, lifecycleHook, () => triggerUpdate(this, lifecycleHook))
|
||||||
|
})
|
||||||
|
|
||||||
// re-render meta data when returning from a child component to parent
|
// re-render meta data when returning from a child component to parent
|
||||||
this.$options.destroyed = this.$options.destroyed || []
|
ensuredPush(this.$options, 'destroyed', () => {
|
||||||
this.$options.destroyed.push(() => {
|
|
||||||
// Wait that element is hidden before refreshing meta tags (to support animations)
|
// Wait that element is hidden before refreshing meta tags (to support animations)
|
||||||
const interval = setInterval(() => {
|
const interval = setInterval(() => {
|
||||||
if (this.$el && this.$el.offsetParent !== null) {
|
if (this.$el && this.$el.offsetParent !== null) {
|
||||||
@@ -83,60 +112,11 @@ export default function createMixin(options) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
triggerUpdate(this)
|
triggerUpdate(this, 'destroyed')
|
||||||
}, 50)
|
}, 50)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/* Not yet removed
|
|
||||||
created() {
|
|
||||||
// if computed $metaInfo exists, watch it for updates & trigger a refresh
|
|
||||||
// when it changes (i.e. automatically handle async actions that affect metaInfo)
|
|
||||||
// credit for this suggestion goes to [Sébastien Chopin](https://github.com/Atinux)
|
|
||||||
if (!this.$isServer && this.$metaInfo) {
|
|
||||||
this.$watch('$metaInfo', () => triggerUpdate(this))
|
|
||||||
}
|
|
||||||
|
|
||||||
},
|
|
||||||
activated() {
|
|
||||||
if (this._hasMetaInfo) {
|
|
||||||
triggerUpdate(this)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
deactivated() {
|
|
||||||
if (this._hasMetaInfo) {
|
|
||||||
triggerUpdate(this)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
beforeMount() {
|
|
||||||
if (this._hasMetaInfo) {
|
|
||||||
triggerUpdate(this)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
destroyed() {
|
|
||||||
// do not trigger refresh on the server side
|
|
||||||
if (this.$isServer) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// re-render meta data when returning from a child component to parent
|
|
||||||
if (this._hasMetaInfo) {
|
|
||||||
// Wait that element is hidden before refreshing meta tags (to support animations)
|
|
||||||
const interval = setInterval(() => {
|
|
||||||
if (this.$el && this.$el.offsetParent !== null) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
clearInterval(interval)
|
|
||||||
|
|
||||||
if (!this.$parent) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
triggerUpdate(this)
|
|
||||||
}, 50)
|
|
||||||
}
|
|
||||||
}/**/
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+12
-1
@@ -1,4 +1,5 @@
|
|||||||
import { isObject } from '../shared/typeof'
|
import { isObject, isFunction } from './typeof'
|
||||||
|
|
||||||
import {
|
import {
|
||||||
keyName,
|
keyName,
|
||||||
attribute,
|
attribute,
|
||||||
@@ -28,5 +29,15 @@ export default function setOptions(options) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (options.afterNavigation && !isFunction(options.afterNavigation)) {
|
||||||
|
console.warn(`afterNavigation should be a function, received ${typeof options.afterNavigation} instead`) // eslint-disable-line no-console
|
||||||
|
options.afterNavigation = void 0
|
||||||
|
return options
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options.afterNavigation && !options.refreshOnceOnNavigation) {
|
||||||
|
options.refreshOnceOnNavigation = true
|
||||||
|
}
|
||||||
|
|
||||||
return options
|
return options
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,13 @@
|
|||||||
|
export function pause(refresh = true) {
|
||||||
|
this.$root._vueMeta.paused = true
|
||||||
|
|
||||||
|
return () => resume(refresh)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function resume(refresh = true) {
|
||||||
|
this.$root._vueMeta.paused = false
|
||||||
|
|
||||||
|
if (refresh) {
|
||||||
|
return this.$root.$meta().refresh()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,4 +1,3 @@
|
|||||||
|
|
||||||
export function isUndefined(arg) {
|
export function isUndefined(arg) {
|
||||||
return typeof arg === 'undefined'
|
return typeof arg === 'undefined'
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,11 @@
|
|||||||
|
import { isUndefined } from './typeof'
|
||||||
|
|
||||||
|
export function hasGlobalWindowFn() {
|
||||||
|
try {
|
||||||
|
return !isUndefined(window)
|
||||||
|
} catch (e) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const hasGlobalWindow = hasGlobalWindowFn()
|
||||||
@@ -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: {}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -1,5 +1,9 @@
|
|||||||
import { mount, defaultOptions, VueMetaBrowserPlugin, loadVueMetaPlugin } from './utils'
|
import triggerUpdate from '../src/client/triggerUpdate'
|
||||||
|
import batchUpdate from '../src/client/batchUpdate'
|
||||||
|
import { mount, defaultOptions, vmTick, VueMetaBrowserPlugin, loadVueMetaPlugin } from './utils'
|
||||||
|
|
||||||
|
jest.mock('../src/client/triggerUpdate')
|
||||||
|
jest.mock('../src/client/batchUpdate')
|
||||||
jest.mock('../package.json', () => ({
|
jest.mock('../package.json', () => ({
|
||||||
version: 'test-version'
|
version: 'test-version'
|
||||||
}))
|
}))
|
||||||
@@ -7,6 +11,7 @@ jest.mock('../package.json', () => ({
|
|||||||
describe('plugin', () => {
|
describe('plugin', () => {
|
||||||
let Vue
|
let Vue
|
||||||
|
|
||||||
|
beforeEach(() => jest.clearAllMocks())
|
||||||
beforeAll(() => (Vue = loadVueMetaPlugin(true)))
|
beforeAll(() => (Vue = loadVueMetaPlugin(true)))
|
||||||
|
|
||||||
test('is loaded', () => {
|
test('is loaded', () => {
|
||||||
@@ -35,4 +40,67 @@ describe('plugin', () => {
|
|||||||
test('plugin sets package version', () => {
|
test('plugin sets package version', () => {
|
||||||
expect(VueMetaBrowserPlugin.version).toBe('test-version')
|
expect(VueMetaBrowserPlugin.version).toBe('test-version')
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test('updates can be paused and resumed', async () => {
|
||||||
|
const _triggerUpdate = jest.requireActual('../src/client/triggerUpdate').default
|
||||||
|
const _batchUpdate = jest.requireActual('../src/client/batchUpdate').default
|
||||||
|
|
||||||
|
const triggerUpdateSpy = triggerUpdate.mockImplementation(_triggerUpdate)
|
||||||
|
const batchUpdateSpy = batchUpdate.mockImplementation(_batchUpdate)
|
||||||
|
|
||||||
|
const Component = Vue.component('test-component', {
|
||||||
|
metaInfo() {
|
||||||
|
return {
|
||||||
|
title: this.title
|
||||||
|
}
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
title: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
}
|
||||||
|
},
|
||||||
|
template: '<div>Test</div>'
|
||||||
|
})
|
||||||
|
|
||||||
|
let title = 'first title'
|
||||||
|
const wrapper = mount(Component, {
|
||||||
|
localVue: Vue,
|
||||||
|
propsData: {
|
||||||
|
title
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// no batchUpdate on initialization
|
||||||
|
expect(wrapper.vm.$root._vueMeta.initialized).toBe(false)
|
||||||
|
expect(wrapper.vm.$root._vueMeta.paused).toBeFalsy()
|
||||||
|
expect(triggerUpdateSpy).toHaveBeenCalledTimes(1)
|
||||||
|
expect(batchUpdateSpy).not.toHaveBeenCalled()
|
||||||
|
jest.clearAllMocks()
|
||||||
|
await vmTick(wrapper.vm)
|
||||||
|
|
||||||
|
title = 'second title'
|
||||||
|
wrapper.setProps({ title })
|
||||||
|
|
||||||
|
// batchUpdate on normal update
|
||||||
|
expect(wrapper.vm.$root._vueMeta.initialized).toBe(true)
|
||||||
|
expect(wrapper.vm.$root._vueMeta.paused).toBeFalsy()
|
||||||
|
expect(triggerUpdateSpy).toHaveBeenCalledTimes(1)
|
||||||
|
expect(batchUpdateSpy).toHaveBeenCalledTimes(1)
|
||||||
|
jest.clearAllMocks()
|
||||||
|
|
||||||
|
wrapper.vm.$meta().pause()
|
||||||
|
title = 'third title'
|
||||||
|
wrapper.setProps({ title })
|
||||||
|
|
||||||
|
// no batchUpdate when paused
|
||||||
|
expect(wrapper.vm.$root._vueMeta.initialized).toBe(true)
|
||||||
|
expect(wrapper.vm.$root._vueMeta.paused).toBe(true)
|
||||||
|
expect(triggerUpdateSpy).toHaveBeenCalledTimes(1)
|
||||||
|
expect(batchUpdateSpy).not.toHaveBeenCalled()
|
||||||
|
jest.clearAllMocks()
|
||||||
|
|
||||||
|
const { metaInfo } = wrapper.vm.$meta().resume()
|
||||||
|
expect(metaInfo.title).toBe(title)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ jest.mock('../package.json', () => ({
|
|||||||
describe('plugin', () => {
|
describe('plugin', () => {
|
||||||
let Vue
|
let Vue
|
||||||
|
|
||||||
|
beforeEach(() => jest.clearAllMocks())
|
||||||
beforeAll(() => (Vue = loadVueMetaPlugin()))
|
beforeAll(() => (Vue = loadVueMetaPlugin()))
|
||||||
|
|
||||||
test('is loaded', () => {
|
test('is loaded', () => {
|
||||||
@@ -29,4 +30,43 @@ describe('plugin', () => {
|
|||||||
test('plugin sets package version', () => {
|
test('plugin sets package version', () => {
|
||||||
expect(VueMetaServerPlugin.version).toBe('test-version')
|
expect(VueMetaServerPlugin.version).toBe('test-version')
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test('prints deprecation warning once when using _hasMetaInfo', () => {
|
||||||
|
const warn = jest.spyOn(console, 'warn').mockImplementation(() => {})
|
||||||
|
|
||||||
|
const Component = Vue.component('test-component', {
|
||||||
|
template: '<div>Test</div>',
|
||||||
|
[defaultOptions.keyName]: {
|
||||||
|
title: 'Hello World'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
Vue.config.devtools = true
|
||||||
|
const { vm } = mount(Component, { localVue: Vue })
|
||||||
|
|
||||||
|
expect(vm._hasMetaInfo).toBe(true)
|
||||||
|
expect(warn).toHaveBeenCalledTimes(1)
|
||||||
|
|
||||||
|
expect(vm._hasMetaInfo).toBe(true)
|
||||||
|
expect(warn).toHaveBeenCalledTimes(1)
|
||||||
|
warn.mockRestore()
|
||||||
|
})
|
||||||
|
|
||||||
|
test('can use hasMetaInfo export', () => {
|
||||||
|
const warn = jest.spyOn(console, 'warn').mockImplementation(() => {})
|
||||||
|
|
||||||
|
const Component = Vue.component('test-component', {
|
||||||
|
template: '<div>Test</div>',
|
||||||
|
[defaultOptions.keyName]: {
|
||||||
|
title: 'Hello World'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const { vm } = mount(Component, { localVue: Vue })
|
||||||
|
|
||||||
|
expect(VueMetaServerPlugin.hasMetaInfo(vm)).toBe(true)
|
||||||
|
expect(warn).not.toHaveBeenCalled()
|
||||||
|
|
||||||
|
warn.mockRestore()
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -41,3 +41,9 @@ export function loadVueMetaPlugin(browser, options, localVue = getVue()) {
|
|||||||
|
|
||||||
return localVue
|
return localVue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const vmTick = (vm) => {
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
vm.$nextTick(resolve)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|||||||
Vendored
+2
@@ -46,4 +46,6 @@ export interface MetaInfo {
|
|||||||
__dangerouslyDisableSanitizersByTagID?: string[]
|
__dangerouslyDisableSanitizersByTagID?: string[]
|
||||||
|
|
||||||
changed?: <T extends object>(newInfo: T, addedTags: HTMLElement[], removedTags: HTMLElement[]) => void
|
changed?: <T extends object>(newInfo: T, addedTags: HTMLElement[], removedTags: HTMLElement[]) => void
|
||||||
|
afterNavigation?: <T extends object>(vm: Vue, newInfo: T) => void
|
||||||
|
refreshOnceOnNavigation?: boolean
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user