diff --git a/samples/vue/src/components/Styleguide/Styleguide-Layout-Tabs-Tab.vue b/samples/vue/src/components/Styleguide/Styleguide-Layout-Tabs-Tab.vue
index ea76ff6253..96179e2d0e 100644
--- a/samples/vue/src/components/Styleguide/Styleguide-Layout-Tabs-Tab.vue
+++ b/samples/vue/src/components/Styleguide/Styleguide-Layout-Tabs-Tab.vue
@@ -22,6 +22,9 @@ export default {
rendering: {
type: Object,
},
+ params: {
+ type: Object,
+ },
},
components: {
ScText: Text,
@@ -29,7 +32,7 @@ export default {
},
computed: {
isEditing() {
- // this.$jss is defined on the Vue instance by the SitecoreJssPlugin and provides
+ // this.$jss is defined on the App instance by the SitecoreJssPlugin and provides
// reactive access to the `sitecoreContext` provided in layout service data.
return this.$jss.sitecoreContext().pageEditing;
},
diff --git a/samples/vue/src/components/Styleguide/Styleguide-Layout-Tabs.vue b/samples/vue/src/components/Styleguide/Styleguide-Layout-Tabs.vue
index 7f9ce3648e..410b3206d1 100644
--- a/samples/vue/src/components/Styleguide/Styleguide-Layout-Tabs.vue
+++ b/samples/vue/src/components/Styleguide/Styleguide-Layout-Tabs.vue
@@ -14,12 +14,16 @@
Instead, we show the tab header inline with the tab contents (see Styleguide-Layout-Tabs-Tab).
-->
@@ -31,7 +35,11 @@
to simplify editing.
Note: additional props passed to the `
diff --git a/samples/vue/src/components/Styleguide/Styleguide-RouteFields.vue b/samples/vue/src/components/Styleguide/Styleguide-RouteFields.vue
index 909cdecea8..ab6ae6f982 100644
--- a/samples/vue/src/components/Styleguide/Styleguide-RouteFields.vue
+++ b/samples/vue/src/components/Styleguide/Styleguide-RouteFields.vue
@@ -37,7 +37,7 @@ export default {
},
computed: {
routeData() {
- // this.$jss is defined on the Vue instance by the SitecoreJssPlugin and provides
+ // this.$jss is defined on the App instance by the SitecoreJssPlugin and provides
// reactive access to the root level route data provided in layout service data.
return this.$jss.routeData();
},
diff --git a/samples/vue/src/components/Styleguide/Styleguide-Section.vue b/samples/vue/src/components/Styleguide/Styleguide-Section.vue
index 850a647a1d..e073085c2b 100644
--- a/samples/vue/src/components/Styleguide/Styleguide-Section.vue
+++ b/samples/vue/src/components/Styleguide/Styleguide-Section.vue
@@ -22,6 +22,9 @@ export default {
fields: {
type: Object,
},
+ params: {
+ type: Object,
+ },
},
components: {
ScPlaceholder: Placeholder,
diff --git a/samples/vue/src/components/Styleguide/Styleguide-SitecoreContext.vue b/samples/vue/src/components/Styleguide/Styleguide-SitecoreContext.vue
index aac2031493..ad2c18fb94 100644
--- a/samples/vue/src/components/Styleguide/Styleguide-SitecoreContext.vue
+++ b/samples/vue/src/components/Styleguide/Styleguide-SitecoreContext.vue
@@ -6,7 +6,7 @@
The current Sitecore Context is...
diff --git a/samples/vue/src/components/Styleguide/Styleguide-Specimen.vue b/samples/vue/src/components/Styleguide/Styleguide-Specimen.vue
index c72cce9595..7fb12e2a1f 100644
--- a/samples/vue/src/components/Styleguide/Styleguide-Specimen.vue
+++ b/samples/vue/src/components/Styleguide/Styleguide-Specimen.vue
@@ -31,6 +31,9 @@ export default {
rendering: {
type: Object,
},
+ params: {
+ type: Object,
+ },
},
components: {
ScText: Text,
diff --git a/samples/vue/src/createApp.js b/samples/vue/src/createApp.js
index 44a5b44ef3..7514f1fc38 100644
--- a/samples/vue/src/createApp.js
+++ b/samples/vue/src/createApp.js
@@ -1,7 +1,11 @@
-import Vue from 'vue';
-import Meta from 'vue-meta';
-import VueApollo from 'vue-apollo';
+import { createApp as createVueApp, h, createSSRApp } from 'vue';
import { SitecoreJssPlaceholderPlugin } from '@sitecore-jss/sitecore-jss-vue';
+import { DefaultApolloClient } from '@vue/apollo-composable/dist/useApolloClient';
+import {
+ createMetaManager as createVueMetaManager,
+ defaultConfig,
+ deepestResolver,
+} from 'vue-meta';
import AppRoot from './AppRoot';
import { createRouter } from './router';
import SitecoreJssStorePlugin from './lib/SitecoreJssStorePlugin';
@@ -9,34 +13,40 @@ import GraphQLClientFactory from './lib/GraphQLClientFactory';
import config from './temp/config';
import componentFactory from './temp/componentFactory';
-Vue.use(Meta);
-Vue.use(SitecoreJssStorePlugin);
-Vue.use(SitecoreJssPlaceholderPlugin, { componentFactory });
-Vue.use(VueApollo);
+const createMetaManager = (isSSR = false) =>
+ createVueMetaManager(
+ isSSR,
+ {
+ ...defaultConfig,
+ },
+ deepestResolver
+ );
// createApp is invoked by both the main and SSR entry points, so the two entry points can use the same app creation process.
-export function createApp(initialState, i18n) {
- Vue.config.productionTip = false;
-
- const router = createRouter();
+export function createApp(initialState, i18n, isSSR) {
+ const router = createRouter(isSSR);
+ const metaManager = createMetaManager(isSSR);
const graphQLProvider = createGraphQLProvider(initialState);
const vueOptions = {
- apolloProvider: graphQLProvider,
router,
-
- render: (createElement) => createElement(AppRoot),
+ render: () => h(AppRoot),
+ i18n,
};
- // conditionally add i18n to the Vue instance if it is defined
- if (i18n) {
- vueOptions.i18n = i18n;
- }
- const app = new Vue(vueOptions);
+ const app = isSSR ? createSSRApp(vueOptions) : createVueApp(vueOptions);
+
+ app.provide(DefaultApolloClient, graphQLProvider);
+
+ app.use(router);
+ app.use(SitecoreJssStorePlugin);
+ app.use(SitecoreJssPlaceholderPlugin, { componentFactory });
+ app.use(i18n);
+ app.use(metaManager);
// if there is an initial state defined, push it into the store, where it can be referenced by interested components.
if (initialState) {
- app.$jss.store.setSitecoreData(initialState);
+ app.config.globalProperties.$jss.store.setSitecoreData(initialState);
}
return { app, router, graphQLProvider };
@@ -48,9 +58,5 @@ export function createGraphQLProvider(initialState) {
? GraphQLClientFactory(config.graphQLEndpoint, false, initialState.APOLLO_STATE)
: GraphQLClientFactory(config.graphQLEndpoint, true);
- const provider = new VueApollo({
- defaultClient: client,
- });
-
- return provider;
+ return client;
}
diff --git a/samples/vue/src/i18n.js b/samples/vue/src/i18n.js
index c70c2fac30..7591efde4a 100644
--- a/samples/vue/src/i18n.js
+++ b/samples/vue/src/i18n.js
@@ -1,87 +1,47 @@
-import i18n from 'i18next';
import 'cross-fetch/polyfill';
-import fetchBackend from 'i18next-fetch-backend';
-import Vue from 'vue';
-import VueI18n from '@panter/vue-i18next';
-import config from './temp/config';
-
-Vue.use(VueI18n);
-
-// SSR i18n instance
-let serverI18n = undefined;
+import { createI18n } from 'vue-i18n';
+import { dictionaryServiceFactory } from './lib/dictionary-service-factory';
/**
- * Initializes the i18next library to provide a translation dictionary to the app.
+ * Initializes the vue-i18n library to provide a translation dictionary to the app.
* If your app is not multilingual, this file and references to it can be removed.
- * Elsewhere in the app to use the dictionary `import { t } from 'i18next'; ... t('key')`
+ * Elsewhere in the app to use the dictionary `{{ $t('styleguide-sample') }}`
+ * If you want to use translation inside the function use:
+ * ```
+ * import { useI18n } from 'vue-i18n';
+ * const { t } = useI18n();
+ * const text = t('page-not-found');
+ * ```
* @param {string} language Optional, the initial language. Only used for SSR; otherwise language set in RouteHandler.
* @param {*} dictionary Optional, the dictionary to load. Only used for SSR; otherwise, the dictionary is loaded via JSS dictionary service.
*/
export default function i18nInit(language, dictionary) {
- return new Promise((resolve, reject) => {
- const options = {
- debug: false,
- lng: language,
- fallbackLng: false, // fallback to keys
- load: 'currentOnly', // e.g. don't load 'es' when requesting 'es-MX' -- Sitecore config should handle this
- useCookie: false, // using URLs and Sitecore to store language context, don't need a cookie
-
- interpolation: {
- escapeValue: false, // not needed for Vue
- },
- };
-
+ return new Promise((resolve) => {
+ // We are in SSR, dictionary is preloaded. Iniitialize it
if (dictionary) {
- // if we got dictionary passed, that means we're in a SSR context with a server-provided dictionary
- // so we do not want a backend, because we already know all possible keys
-
- const appendResource = () =>
- i18n.addResourceBundle(language, 'translation', dictionary, true, true);
-
- if (!i18n.isInitialized) {
- i18n.init(options, (error) => {
- if (error) reject(error);
-
- appendResource();
-
- serverI18n = new VueI18n(i18n);
-
- resolve(serverI18n);
- });
- } else {
- const resolveInstance = () => {
- appendResource();
- resolve(serverI18n);
- };
-
- if (i18n.language === language) {
- return resolveInstance();
- }
-
- i18n.changeLanguage(language).then(resolveInstance);
- }
- } else {
- // We're running client-side, so we get translation data from the Sitecore dictionary API using fetch backend
- // For higher performance (but less simplicity), consider adding the i18n chained backend to a local cache option like the local storage backend.
-
- // prettier-ignore
- const dictionaryServicePath = `${config.sitecoreApiHost}/sitecore/api/jss/dictionary/${config.jssAppName}/{{lng}}?sc_apikey=${config.sitecoreApiKey}`;
-
- options.backend = {
- loadPath: dictionaryServicePath,
- parse: (data) => {
- const parsedData = JSON.parse(data);
- if (parsedData.phrases) {
- return parsedData.phrases;
- }
- return parsedData;
+ const i18n = createI18n({
+ fallbackLocale: false,
+ messages: {
+ [language]: dictionary,
},
- };
-
- i18n.use(fetchBackend).init(options, (error) => {
- if (error) reject(error);
+ locale: language,
+ });
- resolve(new VueI18n(i18n));
+ resolve(i18n);
+ } else {
+ // initialize an instance of the dictionary service
+ const dictionaryServiceInstance = dictionaryServiceFactory.create();
+
+ dictionaryServiceInstance.fetchDictionaryData(language).then((phrases) => {
+ resolve(
+ createI18n({
+ fallbackLocale: false,
+ messages: {
+ [language]: phrases,
+ },
+ locale: language,
+ })
+ );
});
}
});
diff --git a/samples/vue/src/lib/GraphQLClientFactory.js b/samples/vue/src/lib/GraphQLClientFactory.js
index 6de6fd43e9..56aad43976 100644
--- a/samples/vue/src/lib/GraphQLClientFactory.js
+++ b/samples/vue/src/lib/GraphQLClientFactory.js
@@ -1,6 +1,5 @@
import 'cross-fetch/polyfill'; // Apollo uses `fetch`, which needs a polyfill for node and older browsers.
-import { ApolloClient } from 'apollo-client';
-import { InMemoryCache, IntrospectionFragmentMatcher } from 'apollo-cache-inmemory';
+import { InMemoryCache, ApolloClient } from '@apollo/client/core';
/*
INTROSPECTION DATA
@@ -15,13 +14,9 @@ import introspectionQueryResultData from '../temp/GraphQLFragmentTypes.json';
A link is transport which GraphQL queries are pushed across.
You have many choices.
See the apollo-link documentation for more details.
-
- NOTE: to use Sitecore Experience Editor it is essential that your
- link passes cookies along with requests (credentials: 'include').
*/
// choose between a basic HTTP link to run queries...
-// import { createHttpLink } from 'apollo-link-http';
// ...or a batched link (multiple queries within 10ms all go in one HTTP request)
import { BatchHttpLink } from 'apollo-link-batch-http';
@@ -30,25 +25,28 @@ import { BatchHttpLink } from 'apollo-link-batch-http';
// the APQ link is _chained_ behind another link that performs the actual HTTP calls, so you can choose
// APQ + batched, or APQ + http links for example.
import { createPersistedQueryLink } from 'apollo-link-persisted-queries';
+import config from '../temp/config';
export default function(endpoint, ssr, initialCacheState) {
/* HTTP link selection: default to batched + APQ */
const link = createPersistedQueryLink().concat(
new BatchHttpLink({
uri: endpoint,
- credentials: 'include',
headers: {
connection: 'keep-alive',
+ sc_apikey: config.sitecoreApiKey,
},
})
);
- // basic HTTP link
- // const link = createHttpLink({ uri: endpoint, credentials: 'include' });
+
+ const possibleTypes = {};
+
+ introspectionQueryResultData.__schema.types.forEach((supertype) => {
+ possibleTypes[supertype.name] = supertype.possibleTypes.map((subtype) => subtype.name);
+ });
const cache = new InMemoryCache({
- fragmentMatcher: new IntrospectionFragmentMatcher({
- introspectionQueryResultData,
- }),
+ possibleTypes,
});
return new ApolloClient({
diff --git a/samples/vue/src/lib/SitecoreJssStorePlugin.js b/samples/vue/src/lib/SitecoreJssStorePlugin.js
index 8428c539dc..2ced74214b 100644
--- a/samples/vue/src/lib/SitecoreJssStorePlugin.js
+++ b/samples/vue/src/lib/SitecoreJssStorePlugin.js
@@ -22,16 +22,10 @@ const store = {
};
// Vue plugins must export a function named 'install'
-function install(Vue) {
- // "standard" convention for Vue plugins to ensure they are only installed once.
- if (install.installed) {
- return;
- }
- install.installed = true;
-
- Vue.prototype.$jss = {
+function install(App) {
+ App.config.globalProperties.$jss = {
// there may be other JSS plugins installed, merge existing properties
- ...Vue.prototype.$jss,
+ ...(App.config.globalProperties.$jss || {}),
store,
sitecoreContext() {
// this is intended only as a convenience function for easier access to the current context.
diff --git a/samples/vue/src/lib/dictionary-service-factory.graphql.js b/samples/vue/src/lib/dictionary-service-factory.graphql.js
new file mode 100644
index 0000000000..4f0b28e551
--- /dev/null
+++ b/samples/vue/src/lib/dictionary-service-factory.graphql.js
@@ -0,0 +1,20 @@
+import { GraphQLDictionaryService } from '@sitecore-jss/sitecore-jss-vue';
+import config from '../temp/config';
+
+export class DictionaryServiceFactory {
+ create() {
+ return new GraphQLDictionaryService({
+ endpoint: config.graphQLEndpoint,
+ apiKey: config.sitecoreApiKey,
+ siteName: config.jssAppName,
+ /*
+ The Dictionary Service needs a root item ID in order to fetch dictionary phrases for the current
+ app. If your Sitecore instance only has 1 JSS App, you can specify the root item ID here;
+ otherwise, the service will attempt to figure out the root item for the current JSS App using GraphQL and app name.
+ rootItemId: '{GUID}'
+ */
+ });
+ }
+}
+
+export const dictionaryServiceFactory = new DictionaryServiceFactory();
diff --git a/samples/vue/src/lib/dictionary-service-factory.js b/samples/vue/src/lib/dictionary-service-factory.js
new file mode 100644
index 0000000000..d09d02b859
--- /dev/null
+++ b/samples/vue/src/lib/dictionary-service-factory.js
@@ -0,0 +1,14 @@
+import { RestDictionaryService } from '@sitecore-jss/sitecore-jss-vue';
+import config from '../temp/config';
+
+export class DictionaryServiceFactory {
+ create() {
+ return new RestDictionaryService({
+ apiHost: config.sitecoreApiHost,
+ apiKey: config.sitecoreApiKey,
+ siteName: config.jssAppName,
+ });
+ }
+}
+
+export const dictionaryServiceFactory = new DictionaryServiceFactory();
diff --git a/samples/vue/src/lib/layout-service-factory.graphql.js b/samples/vue/src/lib/layout-service-factory.graphql.js
new file mode 100644
index 0000000000..56c421c441
--- /dev/null
+++ b/samples/vue/src/lib/layout-service-factory.graphql.js
@@ -0,0 +1,14 @@
+import { GraphQLLayoutService } from '@sitecore-jss/sitecore-jss-vue';
+import config from '../temp/config';
+
+export class LayoutServiceFactory {
+ create() {
+ return new GraphQLLayoutService({
+ endpoint: config.graphQLEndpoint,
+ apiKey: config.sitecoreApiKey,
+ siteName: config.jssAppName,
+ });
+ }
+}
+
+export const layoutServiceFactory = new LayoutServiceFactory();
diff --git a/samples/vue/src/lib/layout-service-factory.js b/samples/vue/src/lib/layout-service-factory.js
new file mode 100644
index 0000000000..69ec5c2e8b
--- /dev/null
+++ b/samples/vue/src/lib/layout-service-factory.js
@@ -0,0 +1,15 @@
+import { RestLayoutService } from '@sitecore-jss/sitecore-jss-vue';
+import config from '../temp/config';
+
+export class LayoutServiceFactory {
+ create() {
+ return new RestLayoutService({
+ apiHost: config.sitecoreApiHost,
+ apiKey: config.sitecoreApiKey,
+ siteName: config.jssAppName,
+ configurationName: 'default',
+ });
+ }
+}
+
+export const layoutServiceFactory = new LayoutServiceFactory();
diff --git a/samples/vue/src/main.js b/samples/vue/src/main.js
index fab2b09ad1..26c3b4dd4d 100644
--- a/samples/vue/src/main.js
+++ b/samples/vue/src/main.js
@@ -35,5 +35,5 @@ i18ninit(initLanguage).then((i18n) => {
const initialState = __JSS_STATE__ || null;
const { app } = createApp(initialState, i18n);
- app.$mount(rootElement);
+ app.mount(rootElement);
});
diff --git a/samples/vue/src/router.js b/samples/vue/src/router.js
index 636e377c12..1726d5fe90 100644
--- a/samples/vue/src/router.js
+++ b/samples/vue/src/router.js
@@ -1,22 +1,19 @@
-import Vue from 'vue';
-import Router from 'vue-router';
+import { createRouter as createVueRouter, createWebHistory, createMemoryHistory } from 'vue-router';
import RouteHandler from './RouteHandler.vue';
-Vue.use(Router);
-
// support languages in the URL prefix
// e.g. /da-DK/path, or /en/path, or /path
export const routePatterns = [
- '/:lang([a-z]{2}-[A-Z]{2})/:sitecoreRoute*',
- '/:lang([a-z]{2})/:sitecoreRoute*',
- '/:sitecoreRoute*',
+ '/:lang([a-z]{2}-[A-Z]{2})/:sitecoreRoute(.*)*',
+ '/:lang([a-z]{2})/:sitecoreRoute(.*)*',
+ '/:sitecoreRoute(.*)*',
];
-export function createRouter() {
+export function createRouter(isSSR) {
// create an instance of vue-router and configure routes to use the RouteHandler component
- const router = new Router({
- mode: 'history',
+ return createVueRouter({
+ history: isSSR ? createMemoryHistory() : createWebHistory(),
routes: routePatterns.map((routePattern) => {
return {
path: routePattern,
@@ -27,5 +24,4 @@ export function createRouter() {
};
}),
});
- return router;
}
diff --git a/samples/vue/vue.config.js b/samples/vue/vue.config.js
index 8caf61b604..5e869cead8 100644
--- a/samples/vue/vue.config.js
+++ b/samples/vue/vue.config.js
@@ -1,4 +1,6 @@
let vueConfig = {};
+const path = require('path');
+const { constants } = require('@sitecore-jss/sitecore-jss-vue');
if (process.env.BUILD_TARGET_ENV === 'server') {
const serverConfig = require('./server/server.vue.config');
@@ -32,7 +34,7 @@ if (process.env.BUILD_TARGET_ENV === 'server') {
vueConfig.devServer = {
port: process.env.PORT || 3000,
proxy:
- process.env.JSS_MODE === 'disconnected'
+ process.env.JSS_MODE === constants.JSS_MODE.DISCONNECTED
? `http://localhost:${process.env.PROXY_PORT || 3042}`
: undefined,
};
@@ -57,6 +59,9 @@ vueConfig.configureWebpack = (config) => {
config.resolve.extensions.splice(indexOfMjs, 1);
}
+ // We should import same Vue instance, even if we are using symlink with sitecore-jss-vue
+ config.resolve.alias.vue = path.resolve(__dirname, 'node_modules/vue');
+
config.module.rules.push({
test: /\.(graphql|gql)$/,
exclude: /node_modules/,