refactor: SSR
This commit is contained in:
@@ -76,6 +76,7 @@ module.exports = {
|
||||
children: [
|
||||
'apollo-provider',
|
||||
'dollar-apollo',
|
||||
'ssr',
|
||||
]
|
||||
},
|
||||
{
|
||||
|
||||
@@ -45,101 +45,3 @@ new Vue({
|
||||
render: h => h(App),
|
||||
})
|
||||
```
|
||||
|
||||
## Methods
|
||||
|
||||
### provide
|
||||
|
||||
Use this to inject the provider into an app:
|
||||
|
||||
```js
|
||||
new Vue({
|
||||
el: '#app',
|
||||
provide: apolloProvider.provide(),
|
||||
render: h => h(App),
|
||||
})
|
||||
```
|
||||
|
||||
### prefetchAll
|
||||
|
||||
(SSR) Prefetch all queued component definitions and returns a promise resolved when all corresponding apollo data is ready.
|
||||
|
||||
```js
|
||||
await apolloProvider.prefetchAll (context, componentDefs, options)
|
||||
```
|
||||
|
||||
`context` is passed as the argument to the `prefetch` options inside the smart queries. It may contain the route and the store.
|
||||
|
||||
`options` defaults to:
|
||||
|
||||
```js
|
||||
{
|
||||
// Include components outside of the routes
|
||||
// that are registered with `willPrefetch`
|
||||
includeGlobal: true,
|
||||
}
|
||||
```
|
||||
|
||||
### getStates
|
||||
|
||||
(SSR) Returns the apollo stores states as JavaScript objects.
|
||||
|
||||
```js
|
||||
const states = apolloProvider.getStates(options)
|
||||
```
|
||||
|
||||
`options` defaults to:
|
||||
|
||||
```js
|
||||
{
|
||||
// Prefix for the keys of each apollo client state
|
||||
exportNamespace: '',
|
||||
}
|
||||
```
|
||||
|
||||
### exportStates
|
||||
|
||||
(SSR) Returns the apollo stores states as JavaScript code inside a String. This code can be directly injected to the page HTML inside a `<script>` tag.
|
||||
|
||||
```js
|
||||
const js = apolloProvider.exportStates(options)
|
||||
```
|
||||
|
||||
`options` defaults to:
|
||||
|
||||
```js
|
||||
{
|
||||
// Global variable name
|
||||
globalName: '__APOLLO_STATE__',
|
||||
// Global object on which the variable is set
|
||||
attachTo: 'window',
|
||||
// Prefix for the keys of each apollo client state
|
||||
exportNamespace: '',
|
||||
}
|
||||
```
|
||||
|
||||
## Other methods
|
||||
|
||||
### willPrefetch
|
||||
|
||||
Tells vue-apollo that some components not used in a `router-view` (and thus, not in vue-router `matchedComponents`) need to be prefetched, with the `willPrefetch` method:
|
||||
|
||||
```js
|
||||
import { willPrefetch } from 'vue-apollo'
|
||||
|
||||
export default willPrefetch({
|
||||
apollo: {
|
||||
allPosts: {
|
||||
query: gql`query AllPosts {
|
||||
allPosts {
|
||||
id
|
||||
imageUrl
|
||||
description
|
||||
}
|
||||
}`,
|
||||
prefetch: true, // Don't forget this
|
||||
}
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
# ApolloSSR
|
||||
|
||||
## Usage
|
||||
|
||||
See [SSR guide](../guide/ssr.md).
|
||||
|
||||
## Methods
|
||||
|
||||
### prefetchAll
|
||||
|
||||
Prefetches all queued component definitions and returns a promise resolved when all corresponding apollo data is ready.
|
||||
|
||||
```js
|
||||
await ApolloSSR.prefetchAll (apolloProvider, componentDefs, context)
|
||||
```
|
||||
|
||||
`context` is passed as the argument to the `prefetch` options inside the smart queries. It may contain the route and the store.
|
||||
|
||||
### getStates
|
||||
|
||||
Returns the apollo stores states as JavaScript objects.
|
||||
|
||||
```js
|
||||
const states = ApolloSSR.getStates(apolloProvider, options)
|
||||
```
|
||||
|
||||
`options` defaults to:
|
||||
|
||||
```js
|
||||
{
|
||||
// Prefix for the keys of each apollo client state
|
||||
exportNamespace: '',
|
||||
}
|
||||
```
|
||||
|
||||
### exportStates
|
||||
|
||||
Returns the apollo stores states as JavaScript code inside a String. This code can be directly injected to the page HTML inside a `<script>` tag.
|
||||
|
||||
```js
|
||||
const js = ApolloSSR.exportStates(apolloProvider, options)
|
||||
```
|
||||
|
||||
`options` defaults to:
|
||||
|
||||
```js
|
||||
{
|
||||
// Global variable name
|
||||
globalName: '__APOLLO_STATE__',
|
||||
// Global object on which the variable is set
|
||||
attachTo: 'window',
|
||||
// Prefix for the keys of each apollo client state
|
||||
exportNamespace: '',
|
||||
}
|
||||
```
|
||||
+64
-43
@@ -5,12 +5,12 @@
|
||||
On the queries you want to prefetch on the server, add the `prefetch` option. It can either be:
|
||||
- a variables object,
|
||||
- a function that gets the context object (which can contain the URL for example) and return a variables object,
|
||||
- `true` (query's `variables` is reused).
|
||||
- `false` to disable prefetching for this query.
|
||||
|
||||
If you are returning a variables object in the `prefetch` option, make sure it matches the result of the `variables` option. If they do not match, the query's data property will not be populated while rendering the template server-side.
|
||||
|
||||
::: danger
|
||||
You don't have access to the component instance when doing prefetching on the server. Don't use `this` in `prefetch`!
|
||||
You don't have access to the component instance when doing prefetching on the server.
|
||||
:::
|
||||
|
||||
Example:
|
||||
@@ -19,6 +19,7 @@ Example:
|
||||
export default {
|
||||
apollo: {
|
||||
allPosts: {
|
||||
// This will be prefetched
|
||||
query: gql`query AllPosts {
|
||||
allPosts {
|
||||
id
|
||||
@@ -26,7 +27,6 @@ export default {
|
||||
description
|
||||
}
|
||||
}`,
|
||||
prefetch: true,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -60,12 +60,12 @@ export default {
|
||||
}
|
||||
```
|
||||
|
||||
You can also tell vue-apollo that some components not used in a `router-view` (and thus, not in vue-router `matchedComponents`) need to be prefetched, with the `willPrefetch` method:
|
||||
### Skip prefetching
|
||||
|
||||
Example that doesn't prefetch the query:
|
||||
|
||||
```js
|
||||
import { willPrefetch } from 'vue-apollo'
|
||||
|
||||
export default willPrefetch({
|
||||
export default {
|
||||
apollo: {
|
||||
allPosts: {
|
||||
query: gql`query AllPosts {
|
||||
@@ -75,27 +75,60 @@ export default willPrefetch({
|
||||
description
|
||||
}
|
||||
}`,
|
||||
prefetch: true, // Don't forget this
|
||||
// Don't prefetch
|
||||
prefetch: false,
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
The second parameter is optional: it's a callback that gets the context and should return a boolean indicating if the component should be prefetched:
|
||||
If you want to skip prefetching all the queries for a specific component, use the `$prefetch` option:
|
||||
|
||||
```js
|
||||
willPrefetch({
|
||||
// Component definition...
|
||||
}, context => context.url === '/foo')
|
||||
export default {
|
||||
apollo: {
|
||||
// Don't prefetch any query
|
||||
$prefetch: false,
|
||||
allPosts: {
|
||||
query: gql`query AllPosts {
|
||||
allPosts {
|
||||
id
|
||||
imageUrl
|
||||
description
|
||||
}
|
||||
}`,
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
You can also put a `no-prefetch` attribute on any component so it will be ignored while walking the tree to gather the Apollo queries:
|
||||
|
||||
```vue
|
||||
<ApolloQuery no-prefetch>
|
||||
```
|
||||
|
||||
## On the server
|
||||
|
||||
To prefetch all the apollo queries you marked, use the `apolloProvider.prefetchAll` method. The first argument is the context object passed to the `prefetch` hooks (see above). It is recommended to pass the vue-router `currentRoute` object. The second argument is the array of component definition to include (e.g. from `router.getMatchedComponents` method). The third argument is an optional `options` object. It returns a promise resolved when all the apollo queries are loaded.
|
||||
In the server entry, you need to install `ApolloSSR` plugin into Vue:
|
||||
|
||||
```js
|
||||
import Vue from 'vue'
|
||||
import ApolloSSR from 'vue-apollo/ssr'
|
||||
|
||||
Vue.use(ApolloSSR)
|
||||
```
|
||||
|
||||
To prefetch all the apollo queries you marked, use the `ApolloSSR.prefetchAll` method. The first argument is the `apolloProvider`. The second argument is the array of component definition to include (e.g. from `router.getMatchedComponents` method). The third argument is the context object passed to the `prefetch` hooks (see above). It is recommended to pass the vue-router `currentRoute` object. It returns a promise resolved when all the apollo queries are loaded.
|
||||
|
||||
Here is an example with vue-router and a Vuex store:
|
||||
|
||||
```js
|
||||
import Vue from 'vue'
|
||||
import ApolloSSR from 'vue-apollo/ssr'
|
||||
|
||||
Vue.use(ApolloSSR)
|
||||
|
||||
export default () => new Promise((resolve, reject) => {
|
||||
const { app, router, store, apolloProvider } = CreateApp({
|
||||
ssr: true,
|
||||
@@ -119,16 +152,16 @@ export default () => new Promise((resolve, reject) => {
|
||||
// A preFetch hook dispatches a store action and returns a Promise,
|
||||
// which is resolved when the action is complete and store state has been
|
||||
// updated.
|
||||
Promise.all([
|
||||
// Vuex Store prefetch
|
||||
...matchedComponents.map(component => {
|
||||
return component.preFetch && component.preFetch(store)
|
||||
}),
|
||||
// Apollo prefetch
|
||||
apolloProvider.prefetchAll({
|
||||
route: router.currentRoute,
|
||||
}, matchedComponents),
|
||||
]).then(() => {
|
||||
|
||||
// Vuex Store prefetch
|
||||
Promise.all(matchedComponents.map(component => {
|
||||
return component.preFetch && component.preFetch(store)
|
||||
})
|
||||
// Apollo prefetch
|
||||
.then(() => ApolloSSR.prefetchAll(apolloProvider, matchedComponents, {
|
||||
route: router.currentRoute,
|
||||
})
|
||||
.then(() => {
|
||||
// Inject the Vuex state and the Apollo cache on the page.
|
||||
// This will prevent unnecessary queries.
|
||||
|
||||
@@ -136,7 +169,7 @@ export default () => new Promise((resolve, reject) => {
|
||||
js += `window.__INITIAL_STATE__=${JSON.stringify(store.state)};`
|
||||
|
||||
// Apollo
|
||||
js += apolloProvider.exportStates()
|
||||
js += ApolloSSR.exportStates(apolloProvider)
|
||||
|
||||
resolve({
|
||||
app,
|
||||
@@ -147,17 +180,7 @@ export default () => new Promise((resolve, reject) => {
|
||||
})
|
||||
```
|
||||
|
||||
The `options` argument defaults to:
|
||||
|
||||
```js
|
||||
{
|
||||
// Include components outside of the routes
|
||||
// that are registered with `willPrefetch`
|
||||
includeGlobal: true,
|
||||
}
|
||||
```
|
||||
|
||||
Use the `apolloProvider.exportStates` method to get the JavaScript code you need to inject into the generated page to pass the apollo cache data to the client.
|
||||
Use the `ApolloSSR.exportStates(apolloProvider, options)` method to get the JavaScript code you need to inject into the generated page to pass the apollo cache data to the client.
|
||||
|
||||
It takes an `options` argument which defaults to:
|
||||
|
||||
@@ -172,7 +195,7 @@ It takes an `options` argument which defaults to:
|
||||
}
|
||||
```
|
||||
|
||||
You can also use the `apolloProvider.getStates` method to get the JS object instead of the script string.
|
||||
You can also use the `ApolloSSR.getStates(apolloProvider, options)` method to get the JS object instead of the script string.
|
||||
|
||||
It takes an `options` argument which defaults to:
|
||||
|
||||
@@ -242,13 +265,8 @@ Example for common `CreateApp` method:
|
||||
|
||||
```js
|
||||
import Vue from 'vue'
|
||||
|
||||
import VueRouter from 'vue-router'
|
||||
Vue.use(VueRouter)
|
||||
|
||||
import Vuex from 'vuex'
|
||||
Vue.use(Vuex)
|
||||
|
||||
import { sync } from 'vuex-router-sync'
|
||||
|
||||
import VueApollo from 'vue-apollo'
|
||||
@@ -258,6 +276,9 @@ import App from './ui/App.vue'
|
||||
import routes from './routes'
|
||||
import storeOptions from './store'
|
||||
|
||||
Vue.use(VueRouter)
|
||||
Vue.use(Vuex)
|
||||
|
||||
function createApp (context) {
|
||||
const router = new VueRouter({
|
||||
mode: 'history',
|
||||
@@ -281,7 +302,7 @@ function createApp (context) {
|
||||
el: '#app',
|
||||
router,
|
||||
store,
|
||||
provide: apolloProvider.provide(),
|
||||
apolloProvider,
|
||||
...App,
|
||||
}),
|
||||
router,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
export const VUE_APOLLO_QUERY_KEYWORDS = [
|
||||
exports.VUE_APOLLO_QUERY_KEYWORDS = [
|
||||
'variables',
|
||||
'watch',
|
||||
'update',
|
||||
@@ -1,27 +1,27 @@
|
||||
import * as TD from 'throttle-debounce'
|
||||
const TD = require('throttle-debounce')
|
||||
|
||||
export const Globals = {}
|
||||
const Globals = exports.Globals = {}
|
||||
|
||||
function factory (action) {
|
||||
return (cb, time) => action(time, cb)
|
||||
}
|
||||
|
||||
export const throttle = factory(TD.throttle)
|
||||
exports.throttle = factory(TD.throttle)
|
||||
|
||||
export const debounce = factory(TD.debounce)
|
||||
exports.debounce = factory(TD.debounce)
|
||||
|
||||
export function getMergedDefinition (def) {
|
||||
exports.getMergedDefinition = function (def) {
|
||||
return Globals.Vue.util.mergeOptions({}, def)
|
||||
}
|
||||
|
||||
export function reapply (options, context) {
|
||||
exports.reapply = function (options, context) {
|
||||
while (typeof options === 'function') {
|
||||
options = options.call(context)
|
||||
}
|
||||
return options
|
||||
}
|
||||
|
||||
export function omit (obj, properties) {
|
||||
exports.omit = function (obj, properties) {
|
||||
return Object.entries(obj)
|
||||
.filter(([key]) => !properties.includes(key))
|
||||
.reduce((c, [key, val]) => {
|
||||
@@ -30,8 +30,10 @@ export function omit (obj, properties) {
|
||||
}, {})
|
||||
}
|
||||
|
||||
export function addGqlError (error) {
|
||||
exports.addGqlError = function (error) {
|
||||
if (error.graphQLErrors && error.graphQLErrors.length) {
|
||||
error.gqlError = error.graphQLErrors[0]
|
||||
}
|
||||
}
|
||||
|
||||
exports.noop = () => {}
|
||||
+2
-1
@@ -12,7 +12,7 @@
|
||||
"build:es": "rollup --config build/rollup.config.es.js",
|
||||
"build:umd": "rollup --config build/rollup.config.umd.js",
|
||||
"prepublishOnly": "npm run test && npm run build",
|
||||
"dev": "nodemon --exec 'npm run build:es' --watch src",
|
||||
"dev": "nodemon --exec 'npm run build:es && npm run build:umd' --watch src",
|
||||
"test": "npm run test:eslint && npm run test:types",
|
||||
"test:eslint": "eslint --ext .js src && eslint --ext .js,.vue,.gql tests/demo/src",
|
||||
"test:types": "tsc -p types/test",
|
||||
@@ -38,6 +38,7 @@
|
||||
"apollo-client": "^2.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"chalk": "^2.4.1",
|
||||
"throttle-debounce": "^2.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
||||
@@ -1,6 +1,3 @@
|
||||
import { VUE_APOLLO_QUERY_KEYWORDS } from './consts'
|
||||
import { getMergedDefinition, omit } from './utils'
|
||||
|
||||
export class ApolloProvider {
|
||||
constructor (options) {
|
||||
if (!options) {
|
||||
@@ -21,178 +18,4 @@ export class ApolloProvider {
|
||||
[key]: this,
|
||||
}
|
||||
}
|
||||
|
||||
addQueryToPrefetch (queryOptions, client) {
|
||||
this.prefetchQueries.push({
|
||||
queryOptions,
|
||||
client,
|
||||
})
|
||||
}
|
||||
|
||||
prefetchComponent (component, context) {
|
||||
component = getMergedDefinition(component)
|
||||
const apolloOptions = component.apollo
|
||||
|
||||
if (!apolloOptions) {
|
||||
return
|
||||
}
|
||||
|
||||
const componentClient = apolloOptions.$client
|
||||
for (let key in apolloOptions) {
|
||||
const options = apolloOptions[key]
|
||||
if (
|
||||
key.charAt(0) !== '$' && (
|
||||
!options.query || (
|
||||
(typeof options.ssr === 'undefined' || options.ssr) &&
|
||||
(typeof options.prefetch !== 'undefined' && options.prefetch)
|
||||
)
|
||||
)
|
||||
) {
|
||||
this.addQueryToPrefetch(options, options.client || componentClient)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
prefetchComponents (definitions) {
|
||||
for (const def of definitions) {
|
||||
this.prefetchComponent(def)
|
||||
}
|
||||
}
|
||||
|
||||
prefetchAll (context, components, options) {
|
||||
// Optional components argument
|
||||
if (!options && components && !Array.isArray(components)) {
|
||||
options = components
|
||||
components = undefined
|
||||
}
|
||||
|
||||
const finalOptions = Object.assign({}, {
|
||||
includeGlobal: true,
|
||||
}, options)
|
||||
|
||||
if (components) {
|
||||
this.prefetchComponents(components)
|
||||
}
|
||||
|
||||
if (finalOptions.includeGlobal) {
|
||||
this.prefetchComponents(globalPrefetchs.filter(
|
||||
({ component, contextCallback }) => {
|
||||
let result = true
|
||||
if (typeof contextCallback === 'function') {
|
||||
result = !!contextCallback(context)
|
||||
}
|
||||
return result
|
||||
}
|
||||
).map(
|
||||
({ component }) => component
|
||||
), context)
|
||||
}
|
||||
|
||||
return Promise.all(this.prefetchQueries.map(
|
||||
o => this.prefetchQuery(o.queryOptions, context, o.client)
|
||||
))
|
||||
}
|
||||
|
||||
prefetchQuery (queryOptions, context, client) {
|
||||
let variables
|
||||
|
||||
// Client
|
||||
if (!client) {
|
||||
client = this.defaultClient
|
||||
} else if (typeof client === 'string') {
|
||||
client = this.clients[client]
|
||||
if (!client) {
|
||||
throw new Error(`[vue-apollo] Missing client '${client}' in 'apolloProvider'`)
|
||||
}
|
||||
}
|
||||
|
||||
// Simple query
|
||||
if (!queryOptions.query) {
|
||||
queryOptions = {
|
||||
query: queryOptions,
|
||||
}
|
||||
} else {
|
||||
const prefetch = queryOptions.prefetch
|
||||
const prefetchType = typeof prefetch
|
||||
|
||||
// Resolve variables
|
||||
if (prefetchType !== 'undefined') {
|
||||
let result
|
||||
if (prefetchType === 'function') {
|
||||
result = prefetch(context)
|
||||
} else {
|
||||
result = prefetch
|
||||
}
|
||||
|
||||
if (!result) {
|
||||
return Promise.resolve()
|
||||
} else if (prefetchType === 'boolean') {
|
||||
const optVariables = queryOptions.variables
|
||||
if (typeof optVariables !== 'undefined') {
|
||||
// Reuse `variables` option with `prefetch: true`
|
||||
if (typeof optVariables === 'function') {
|
||||
variables = optVariables.call(context)
|
||||
} else {
|
||||
variables = optVariables
|
||||
}
|
||||
} else {
|
||||
variables = undefined
|
||||
}
|
||||
} else {
|
||||
variables = result
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Query
|
||||
if (typeof queryOptions.query === 'function') {
|
||||
queryOptions.query = queryOptions.query(context)
|
||||
}
|
||||
return new Promise((resolve, reject) => {
|
||||
const options = omit(queryOptions, [
|
||||
...VUE_APOLLO_QUERY_KEYWORDS,
|
||||
'fetchPolicy',
|
||||
])
|
||||
options.variables = variables
|
||||
options.fetchPolicy = 'network-only'
|
||||
client.query(options).then(resolve, reject)
|
||||
})
|
||||
}
|
||||
|
||||
getStates (options) {
|
||||
const finalOptions = Object.assign({}, {
|
||||
exportNamespace: '',
|
||||
}, options)
|
||||
const states = {}
|
||||
for (const key in this.clients) {
|
||||
const client = this.clients[key]
|
||||
const state = client.cache.extract()
|
||||
states[`${finalOptions.exportNamespace}${key}`] = state
|
||||
}
|
||||
return states
|
||||
}
|
||||
|
||||
exportStates (options) {
|
||||
const finalOptions = Object.assign({}, {
|
||||
globalName: '__APOLLO_STATE__',
|
||||
attachTo: 'window',
|
||||
}, options)
|
||||
const states = this.getStates(finalOptions)
|
||||
const js = `${finalOptions.attachTo}.${finalOptions.globalName} = ${JSON.stringify(states)};`
|
||||
return js
|
||||
}
|
||||
}
|
||||
|
||||
const globalPrefetchs = []
|
||||
|
||||
export function willPrefetch (component, contextCallback = null) {
|
||||
globalPrefetchs.push({ component, contextCallback })
|
||||
return component
|
||||
}
|
||||
|
||||
// Global access for libraries
|
||||
if (typeof window !== 'undefined') {
|
||||
window.vueApolloWillPrefetch = willPrefetch
|
||||
} else if (typeof global !== 'undefined') {
|
||||
global.vueApolloWillPrefetch = willPrefetch
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { addGqlError } from '../utils'
|
||||
import { addGqlError } from '../../lib/utils'
|
||||
|
||||
export default {
|
||||
props: {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import SmartQuery from './smart-query'
|
||||
import SmartSubscription from './smart-subscription'
|
||||
import { reapply } from './utils'
|
||||
import { reapply } from '../lib/utils'
|
||||
|
||||
export class DollarApollo {
|
||||
constructor (vm) {
|
||||
|
||||
+1
-2
@@ -6,7 +6,7 @@ import CApolloSubscribeToMore from './components/ApolloSubscribeToMore'
|
||||
import CApolloMutation from './components/ApolloMutation'
|
||||
|
||||
import { installMixin } from './mixin'
|
||||
import { Globals, omit } from './utils'
|
||||
import { Globals, omit } from '../lib/utils'
|
||||
|
||||
const keywords = [
|
||||
'$subscribe',
|
||||
@@ -66,7 +66,6 @@ plugin.version = VERSION
|
||||
|
||||
// Apollo provider
|
||||
export const ApolloProvider = plugin
|
||||
export { willPrefetch } from './apollo-provider'
|
||||
|
||||
// Components
|
||||
export const ApolloQuery = CApolloQuery
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
import { Globals } from './utils'
|
||||
import { Globals } from '../lib/utils'
|
||||
|
||||
function hasProperty (holder, key) {
|
||||
return typeof holder !== 'undefined' && Object.prototype.hasOwnProperty.call(holder, key)
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
import { throttle, debounce, omit, addGqlError } from './utils'
|
||||
import { throttle, debounce, omit, addGqlError } from '../lib/utils'
|
||||
|
||||
export default class SmartApollo {
|
||||
type = null
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
import SmartApollo from './smart-apollo'
|
||||
import { VUE_APOLLO_QUERY_KEYWORDS } from './consts'
|
||||
import { VUE_APOLLO_QUERY_KEYWORDS } from '../lib/consts'
|
||||
|
||||
export default class SmartQuery extends SmartApollo {
|
||||
type = 'query'
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
exports.VM_HELPERS = [
|
||||
'_ssrAttr',
|
||||
'_ssrNode',
|
||||
'_ssrEscape',
|
||||
'_ssrList',
|
||||
'_o',
|
||||
'_n',
|
||||
'_s',
|
||||
'_l',
|
||||
'_t',
|
||||
'_q',
|
||||
'_i',
|
||||
'_m',
|
||||
'_f',
|
||||
'_k',
|
||||
'_b',
|
||||
'_v',
|
||||
'_e',
|
||||
'_g',
|
||||
]
|
||||
|
||||
exports.COMPONENT_BLACKLIST = [
|
||||
'router-link',
|
||||
'router-view',
|
||||
'router-multi-view',
|
||||
'apollo-mutation',
|
||||
'apollo-subscribe',
|
||||
'apollo-subscribe-to-more',
|
||||
]
|
||||
+305
@@ -0,0 +1,305 @@
|
||||
const chalk = require('chalk')
|
||||
const { VUE_APOLLO_QUERY_KEYWORDS } = require('../lib/consts')
|
||||
const { VM_HELPERS, COMPONENT_BLACKLIST } = require('./consts')
|
||||
const { Globals, getMergedDefinition, omit, noop } = require('../lib/utils')
|
||||
|
||||
exports.install = function (Vue) {
|
||||
Globals.Vue = Vue
|
||||
}
|
||||
|
||||
exports.prefetchAll = function (apolloProvider, components, context) {
|
||||
return exports.getQueriesFromTree(components, context)
|
||||
.then(queries => Promise.all(queries.map(
|
||||
query => prefetchQuery(apolloProvider, query, context)
|
||||
)))
|
||||
}
|
||||
|
||||
exports.getQueriesFromTree = function (components, context) {
|
||||
const queries = []
|
||||
return Promise.all(
|
||||
components.map(component => walkTree(component, {}, {}, [], context, queries, components))
|
||||
).then(() => queries)
|
||||
}
|
||||
|
||||
function walkTree (component, data, parent, children, context, queries, components) {
|
||||
component = getMergedDefinition(component)
|
||||
return new Promise((resolve, reject) => {
|
||||
const queue = []
|
||||
const vm = createFakeInstance(component, data, parent, children, context)
|
||||
vm.$createElement = (el, data, children) => {
|
||||
if (typeof data === 'string' || Array.isArray(data)) {
|
||||
children = data
|
||||
data = {}
|
||||
}
|
||||
|
||||
// No Prefetch flag
|
||||
if (data && data.attrs && data.attrs['no-prefetch']) return
|
||||
|
||||
queue.push(resolveComponent(el, component).then(resolvedComponent => {
|
||||
let child
|
||||
if (resolvedComponent && !components.includes(resolvedComponent)) {
|
||||
child = {
|
||||
component: resolvedComponent,
|
||||
data,
|
||||
children,
|
||||
}
|
||||
}
|
||||
return child
|
||||
}))
|
||||
}
|
||||
|
||||
prefetchComponent(component, vm, queries)
|
||||
|
||||
try {
|
||||
component.render.call(vm, vm.$createElement)
|
||||
} catch (e) {
|
||||
console.log(chalk.red(`Error while rendering ${component.name || component.__file}`))
|
||||
console.log(e.stack)
|
||||
}
|
||||
|
||||
Promise.all(queue).then(queue => queue.filter(child => !!child).map(
|
||||
child => walkTree(child.component, child.data, vm, child.children, context, queries, components)
|
||||
)).then(() => resolve())
|
||||
})
|
||||
}
|
||||
|
||||
function prefetchComponent (component, vm, queries) {
|
||||
const apolloOptions = component.apollo
|
||||
|
||||
if (!apolloOptions) return
|
||||
if (apolloOptions.$prefetch === false) return
|
||||
|
||||
const componentClient = apolloOptions.$client
|
||||
for (let key in apolloOptions) {
|
||||
const options = apolloOptions[key]
|
||||
if (
|
||||
key.charAt(0) !== '$' && (
|
||||
!options.query || (
|
||||
(typeof options.ssr === 'undefined' || options.ssr) &&
|
||||
(typeof options.prefetch !== 'undefined' && options.prefetch)
|
||||
)
|
||||
)
|
||||
) {
|
||||
queries.push({
|
||||
queryOptions: options,
|
||||
client: options.client || componentClient,
|
||||
vm,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function createFakeInstance (options, data, parent, children, context) {
|
||||
const vm = {
|
||||
...data.attrs,
|
||||
...data.props,
|
||||
$parent: parent,
|
||||
$children: children,
|
||||
$slots: {},
|
||||
$scopedSlots: {},
|
||||
$set: Globals.Vue.set,
|
||||
$delete: Globals.Vue.delete,
|
||||
$route: context.route,
|
||||
$store: context.store,
|
||||
$apollo: {
|
||||
queries: {},
|
||||
loading: false,
|
||||
},
|
||||
$apolloData: {
|
||||
loading: false,
|
||||
},
|
||||
_self: {},
|
||||
_staticTrees: [],
|
||||
_u: resolveScopedSlots,
|
||||
}
|
||||
|
||||
// Render and other helpers
|
||||
VM_HELPERS.forEach(helper => vm[helper] = noop)
|
||||
|
||||
// Scoped slots
|
||||
if (data.scopedSlots) {
|
||||
vm.$scopedSlots = data.scopedSlots
|
||||
}
|
||||
|
||||
// Route props
|
||||
if (context && context.route) {
|
||||
const { route } = context
|
||||
const matchedRoute = findRouteMatch(options, route)
|
||||
if (matchedRoute && matchedRoute.props) {
|
||||
const { props } = matchedRoute
|
||||
if (props === true) {
|
||||
Object.assign(vm, matchedRoute.params)
|
||||
} else if (typeof props === 'function') {
|
||||
Object.assign(vm, props(matchedRoute))
|
||||
} else if (typeof props === 'object') {
|
||||
Object.assign(vm, props)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Data
|
||||
const localData = options.data
|
||||
if (typeof localData === 'function') {
|
||||
Object.assign(vm, localData(vm))
|
||||
} else if (typeof localData === 'object') {
|
||||
Object.assign(vm, localData)
|
||||
}
|
||||
|
||||
// Prefetch state
|
||||
const prefetch = options.prefetch
|
||||
if (typeof prefetch === 'function') {
|
||||
Object.assign(vm, prefetch(context))
|
||||
} else if (typeof prefetch === 'object') {
|
||||
Object.assign(vm, prefetch)
|
||||
}
|
||||
|
||||
return vm
|
||||
}
|
||||
|
||||
function findRouteMatch (component, route) {
|
||||
for (const r of route.matched) {
|
||||
for (const key in r.components) {
|
||||
if (r.components[key] === component) {
|
||||
return r
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function resolveComponent (name, options) {
|
||||
return new Promise((resolve) => {
|
||||
if (options.components) {
|
||||
if (options.components[name]) {
|
||||
return resolve(options.components[name])
|
||||
}
|
||||
}
|
||||
return resolve(Globals.Vue.options.components[name])
|
||||
}).then(component => {
|
||||
if (component) {
|
||||
component = getMergedDefinition(component)
|
||||
if (!component.functional && (
|
||||
!component.name ||
|
||||
!COMPONENT_BLACKLIST.includes(component.name)
|
||||
)) {
|
||||
return component
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function resolveScopedSlots (fns, res) {
|
||||
res = res || {}
|
||||
for (var i = 0; i < fns.length; i++) {
|
||||
if (Array.isArray(fns[i])) {
|
||||
resolveScopedSlots(fns[i], res)
|
||||
} else {
|
||||
res[fns[i].key] = fns[i].fn
|
||||
}
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
function prefetchQuery (apolloProvider, query, context) {
|
||||
try {
|
||||
let variables
|
||||
|
||||
let { queryOptions, client, vm } = query
|
||||
|
||||
// Client
|
||||
if (typeof client === 'function') {
|
||||
client = client.call(vm)
|
||||
}
|
||||
if (!client) {
|
||||
client = apolloProvider.defaultClient
|
||||
} else if (typeof client === 'string') {
|
||||
client = apolloProvider.clients[client]
|
||||
if (!client) {
|
||||
throw new Error(`[vue-apollo] Missing client '${client}' in 'apolloProvider'`)
|
||||
}
|
||||
}
|
||||
|
||||
// Function query
|
||||
if (typeof queryOptions === 'function') {
|
||||
queryOptions = queryOptions.call(vm)
|
||||
}
|
||||
|
||||
// Simple query
|
||||
if (!queryOptions.query) {
|
||||
queryOptions = {
|
||||
query: queryOptions,
|
||||
}
|
||||
} else {
|
||||
const prefetch = queryOptions.prefetch
|
||||
const prefetchType = typeof prefetch
|
||||
|
||||
// Resolve variables
|
||||
let prefetchResult
|
||||
if (prefetchType !== 'undefined') {
|
||||
if (prefetchType === 'function') {
|
||||
prefetchResult = prefetch.call(vm, context)
|
||||
} else {
|
||||
prefetchResult = prefetch
|
||||
}
|
||||
|
||||
if (prefetchType === 'boolean' && prefetchResult === false) {
|
||||
return Promise.resolve()
|
||||
}
|
||||
}
|
||||
|
||||
if (prefetchResult) {
|
||||
variables = prefetchResult
|
||||
} else {
|
||||
const optVariables = queryOptions.variables
|
||||
if (typeof optVariables !== 'undefined') {
|
||||
// Reuse `variables` option with `prefetch: true`
|
||||
if (typeof optVariables === 'function') {
|
||||
variables = optVariables.call(vm)
|
||||
} else {
|
||||
variables = optVariables
|
||||
}
|
||||
} else {
|
||||
variables = undefined
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Query
|
||||
if (typeof queryOptions.query === 'function') {
|
||||
queryOptions.query = queryOptions.query.call(vm)
|
||||
}
|
||||
|
||||
const options = omit(queryOptions, [
|
||||
...VUE_APOLLO_QUERY_KEYWORDS,
|
||||
'fetchPolicy',
|
||||
])
|
||||
options.variables = variables
|
||||
options.fetchPolicy = 'network-only'
|
||||
|
||||
return client.query(options)
|
||||
} catch (e) {
|
||||
console.log(e.stack)
|
||||
}
|
||||
}
|
||||
|
||||
exports.getStates = function (apolloProvider, options) {
|
||||
const finalOptions = Object.assign({}, {
|
||||
exportNamespace: '',
|
||||
}, options)
|
||||
const states = {}
|
||||
for (const key in apolloProvider.clients) {
|
||||
const client = apolloProvider.clients[key]
|
||||
const state = client.cache.extract()
|
||||
states[`${finalOptions.exportNamespace}${key}`] = state
|
||||
}
|
||||
return states
|
||||
}
|
||||
|
||||
exports.exportStates = function (apolloProvider, options) {
|
||||
const finalOptions = Object.assign({}, {
|
||||
globalName: '__APOLLO_STATE__',
|
||||
attachTo: 'window',
|
||||
}, options)
|
||||
const states = exports.getStates(apolloProvider, finalOptions)
|
||||
const js = `${finalOptions.attachTo}.${finalOptions.globalName} = ${JSON.stringify(states)};`
|
||||
return js
|
||||
}
|
||||
Reference in New Issue
Block a user