diff --git a/README.md b/README.md index dfd8f4f58e..2f2219b0fc 100644 --- a/README.md +++ b/README.md @@ -129,6 +129,7 @@ You can find some tutorials and explainations on our [YouTube channel](https://w ### Vue Storefront core and themes * [Working with themes](https://github.com/DivanteLtd/vue-storefront/blob/master/doc/themes/Working%20with%20themes.md) +* [Layouts and advanced output operations](https://github.com/DivanteLtd/vue-storefront/blob/develop/doc/Layouts%20and%20advanced%20output%20operations.md) * [Working with Vue Storefront core components](https://github.com/DivanteLtd/vue-storefront/blob/master/doc/components/Working%20with%20components.md) * [Working with UI Store (interface state)](https://github.com/DivanteLtd/vue-storefront/blob/master/doc/Working%20with%20UI%20Store%20(interface%20state).md) * [Working with translations](https://github.com/DivanteLtd/vue-storefront/blob/master/doc/i18n/Working%20with%20translations.md) @@ -153,6 +154,7 @@ Tutorial series on creating themes for Vue Storefront: * [Working with extensions](https://github.com/DivanteLtd/vue-storefront/blob/master/doc/extensions/Working%20with%20extensions.md) * [Adding custom Server API methods](https://github.com/DivanteLtd/vue-storefront/blob/master/doc/Extending%20vue-storefront-api.md) * [Extending UI from extensions](https://github.com/DivanteLtd/vue-storefront/blob/master/doc/components/Extending%20UI%20from%20extensions.md) +* [Adding Express routes and middlewares](https://github.com/DivanteLtd/vue-storefront/blob/master/doc/Extending%20Express.js%20server%20side%20routes.md) ### Integrations * [Vue Storefront + Magento](https://github.com/DivanteLtd/mage2vuestorefront) diff --git a/config/default.json b/config/default.json index 2d31e01326..7fa3a7245c 100644 --- a/config/default.json +++ b/config/default.json @@ -31,6 +31,11 @@ "ssrTimeout": 1000 }, "ssr": { + "templates": { + "default": "dist/index.html", + "minimal": "dist/index.minimal.html", + "basic": "dist/index.basic.html" + }, "executeMixedinAsyncData": true, "initialStateFilter": ["config", "__DEMO_MODE__", "version", "storeView"], "useInitialStateFilter": true diff --git a/core/app.ts b/core/app.ts index eb82e47300..abe406ec33 100755 --- a/core/app.ts +++ b/core/app.ts @@ -110,7 +110,6 @@ export function createApp (serverContext = null): { app: Vue, router: any, store provide: apolloProvider, render: h => h(App) }) - registerExtensions( union(extensionEntryPoints, themeExtensionEntryPoints), app, diff --git a/core/build/webpack.base.config.js b/core/build/webpack.base.config.js index f12e159cfc..023c2320f1 100644 --- a/core/build/webpack.base.config.js +++ b/core/build/webpack.base.config.js @@ -4,6 +4,7 @@ const fs = require('fs') const CaseSensitivePathsPlugin = require('case-sensitive-paths-webpack-plugin') const VueLoaderPlugin = require('vue-loader/lib/plugin') const autoprefixer = require('autoprefixer') +const HTMLPlugin = require('html-webpack-plugin') fs.writeFileSync( path.resolve(__dirname, './config.json'), @@ -17,6 +18,9 @@ const themeRoot = require('./theme-path') const themeResources = themeRoot + '/resource' const themeCSS = themeRoot + '/css' const themeApp = themeRoot + '/App.vue' +const themedIndex = path.join(themeRoot, 'index.template.html') +const themedIndexMinimal = path.join(themeRoot, 'index.minimal.template.html') +const themedIndexBasic= path.join(themeRoot, 'index.basic.template.html') const translationPreprocessor = require('@vue-storefront/i18n/scripts/translation.preprocessor.js') translationPreprocessor([ @@ -40,7 +44,19 @@ const postcssConfig = { module.exports = { plugins: [ new CaseSensitivePathsPlugin(), - new VueLoaderPlugin() + new VueLoaderPlugin(), + // generate output HTML + new HTMLPlugin({ + template: fs.existsSync(themedIndex) ? themedIndex : 'src/index.template.html' + }), + new HTMLPlugin({ + template: fs.existsSync(themedIndex) ? themedIndexMinimal : 'src/index.minimal.template.html', + filename: 'index.minimal.html' + }), + new HTMLPlugin({ + template: fs.existsSync(themedIndex) ? themedIndexBasic: 'src/index.basic.template.html', + filename: 'index.basic.html' + }) ], devtool: 'source-map', entry: { diff --git a/core/build/webpack.client.config.js b/core/build/webpack.client.config.js index daa6deffa8..5866e087d9 100644 --- a/core/build/webpack.client.config.js +++ b/core/build/webpack.client.config.js @@ -1,11 +1,7 @@ const webpack = require('webpack') const merge = require('webpack-merge') const base = require('./webpack.base.config') -const HTMLPlugin = require('html-webpack-plugin') const path = require('path') -const fs = require('fs') -const themeRoot = require('./theme-path') -const themedIndex = path.join(themeRoot, 'index.template.html') const config = merge(base, { output: { @@ -35,10 +31,6 @@ const config = merge(base, { // strip dev-only code in Vue source new webpack.DefinePlugin({ 'process.env.VUE_ENV': '"client"' - }), - // generate output HTML - new HTMLPlugin({ - template: fs.existsSync(themedIndex) ? themedIndex : 'src/index.template.html' }) ] }) diff --git a/core/components/blocks/Microcart/Product.js b/core/components/blocks/Microcart/Product.js index 13f66e4866..e511259993 100644 --- a/core/components/blocks/Microcart/Product.js +++ b/core/components/blocks/Microcart/Product.js @@ -30,7 +30,7 @@ export default { }, methods: { removeItem () { - this.$store.dispatch('cart/removeItem', this.product) + this.$store.dispatch('cart/removeItem', { product: this.product }) }, onProductChanged (event) { if (event.item.sku === this.product.sku) { diff --git a/core/modules/cart/features/removeFromCart.ts b/core/modules/cart/features/removeFromCart.ts index 24337d15cb..e67d229749 100644 --- a/core/modules/cart/features/removeFromCart.ts +++ b/core/modules/cart/features/removeFromCart.ts @@ -11,7 +11,7 @@ import CartItem from '../types/CartItem' export const removeFromCart = { methods: { removeFromCart (item: CartItem) { - this.$store.dispatch('cart/removeItem', item) + this.$store.dispatch('cart/removeItem', { product: item }) } } } diff --git a/core/scripts/server.js b/core/scripts/server.js index a23f4052d8..72642d36fb 100755 --- a/core/scripts/server.js +++ b/core/scripts/server.js @@ -6,7 +6,11 @@ const resolve = file => path.resolve(rootPath, file) const config = require('config') const TagCache = require('redis-tag-cache').default const utils = require('./server/utils') - +const compile = require('lodash.template') +const compileOptions = { + escape: /{{([^{][\s\S]+?[^}])}}/g, + interpolate: /{{{([\s\S]+?)}}}/g +} const isProd = process.env.NODE_ENV === 'production' process.noDeprecation = true @@ -21,7 +25,16 @@ if (config.server.useOutputCache) { console.log('Redis cache set', config.redis) } +const templatesCache = {} let renderer +for (const tplName of Object.keys(config.ssr.templates)) { + const fileName = resolve(config.ssr.templates[tplName]) + if (fs.existsSync(fileName)) { + const template = fs.readFileSync(fileName, 'utf-8') + templatesCache[tplName] = compile(template, compileOptions) + } +} + if (isProd) { // In production: create server renderer using server bundle and index HTML // template from real fs. @@ -29,20 +42,20 @@ if (isProd) { const bundle = require(resolve('dist/vue-ssr-bundle.json')) // src/index.template.html is processed by html-webpack-plugin to inject // build assets and output as dist/index.html. - const template = fs.readFileSync(resolve('dist/index.html'), 'utf-8') - renderer = createRenderer(bundle, template) + // TODO: Add dynamic templates loading from (config based?) list + renderer = createRenderer(bundle) } else { // In development: setup the dev server with watch and hot-reload, // and create a new renderer on bundle / index template update. require(resolve('core/build/dev-server'))(app, (bundle, template) => { - renderer = createRenderer(bundle, template) + templatesCache['default'] = compile(template, compileOptions) // Important Notice: template switching doesn't work with dev server because of the HMR + renderer = createRenderer(bundle) }) } function createRenderer (bundle, template) { // https://github.com/vuejs/vue/blob/dev/packages/vue-server-renderer/README.md#why-use-bundlerenderer return require('vue-server-renderer').createBundleRenderer(bundle, { - template, cache: require('lru-cache')({ max: 1000, maxAge: 1000 * 60 * 15 @@ -115,7 +128,7 @@ app.get('*', (req, res, next) => { } else { // Render Error Page or Redirect // TODO: Add error page handler - res.status(500).end('500 | Internal Server Error') + res.status(500).end('500 | Internal Server Error. Check Server console for details.') console.error(`Error during render : ${req.url}`) console.error(err) next() @@ -137,35 +150,50 @@ app.get('*', (req, res, next) => { ' ') return next() } - const context = { url: req.url, storeCode: req.header('x-vs-store-code') ? req.header('x-vs-store-code') : process.env.STORE_CODE, server: { app: app, res: res, req: req } } - if (config.server.useOutputCacheTagging) { - renderer.renderToString(context).then(output => { + const context = { + url: req.url, + renderPrepend: (context) => { return '' }, // these functions can be replaced in the Vue components to append or prepend some content AFTER all other things are rendered. So in this function You may call: renderPrepend() { return context.renderStyles() } to attach styles + renderAppend: (context) => { return '' }, + serverOutputTemplate: 'default', + meta: null, + currentRoute: null/** will be set by Vue */, + storeCode: req.header('x-vs-store-code') ? req.header('x-vs-store-code') : process.env.STORE_CODE, + app: app, + response: res, + request: req + } + renderer.renderToString(context).then(output => { + if (!res.get('content-type')) { + res.setHeader('Content-Type', 'text/html') + } + if (config.server.useOutputCacheTagging) { const tagsArray = Array.from(context.state.requestContext.outputCacheTags) const cacheTags = tagsArray.join(' ') - res.setHeader('Content-Type', 'text/html') res.setHeader('X-VS-Cache-Tags', cacheTags) - res.end(output) - console.log(`cache tags for the request: ${cacheTags}`) - console.log(`whole request [${req.url}]: ${Date.now() - s}ms`) + const contentPrepend = (typeof context.renderPrepend === 'function') ? context.renderPrepend(context) : '' + const contentAppend = (typeof context.renderAppend === 'function') ? context.renderAppend(context) : '' + + output = contentPrepend + output + contentAppend + if (context.serverOutputTemplate) { // case when we've got the template name back from vue app + if (templatesCache[context.serverOutputTemplate]) { // please look at: https://github.com/vuejs/vue/blob/79cabadeace0e01fb63aa9f220f41193c0ca93af/src/server/template-renderer/index.js#L87 for reference + output = templatesCache[context.serverOutputTemplate](context).replace('', output) + } else { + throw new Error(`The given template name ${context.serverOutputTemplate} does not exist`) + } + } if (config.server.useOutputCache && cache) { cache.set( 'page:' + req.url, - output, + { headers: res.getHeaders(), body: output }, tagsArray ).catch(errorHandler) } - next() - }).catch(errorHandler) - } else { - res.setHeader('Content-Type', 'text/html') - renderer.renderToStream(context) // TODO: pass the store code from the headers - .on('error', errorHandler) - .on('end', () => { - console.log(`whole request: ${Date.now() - s}ms`) - next() - }) - .pipe(res) - } + console.log(`cache tags for the request: ${cacheTags}`) + } + res.end(output) + console.log(`whole request [${req.url}]: ${Date.now() - s}ms`) + next() + }).catch(errorHandler) } if (config.server.useOutputCache && cache) { @@ -173,8 +201,18 @@ app.get('*', (req, res, next) => { 'page:' + req.url ).then(output => { if (output !== null) { - res.setHeader('Content-Type', 'text/html') + if (output.headers) { + for (const header of Object.keys(output.headers)) { + res.setHeader(header, output.headers[header]) + } + } res.setHeader('X-VS-Cache', 'Hit') + if (output.body) { + res.end(output.body) + } else { + res.setHeader('Content-Type', 'text/html') + res.end(output.body) + } res.end(output) console.log(`cache hit [${req.url}], cached request: ${Date.now() - s}ms`) next() diff --git a/core/server-entry.ts b/core/server-entry.ts index 6f2eac7d7a..a62371c8ab 100755 --- a/core/server-entry.ts +++ b/core/server-entry.ts @@ -19,7 +19,8 @@ function _ssrHydrateSubcomponents (components, store, router, resolve, reject, a if (SubComponent.asyncData) { return SubComponent.asyncData({ store, - route: router.currentRoute + route: router.currentRoute, + context }) } })).then(() => { @@ -46,6 +47,7 @@ export default context => { if (store.state.config.storeViews.multistore === true) { let storeCode = context.storeCode // this is from http header or env variable if (router.currentRoute) { // this is from url + context.currentRoute = router.currentRoute storeCode = storeCodeFromRoute(router.currentRoute) } if (storeCode !== '' && storeCode !== null) { @@ -64,7 +66,7 @@ export default context => { } }) if (Component.asyncData) { - Component.asyncData({ store, route: router.currentRoute }).then((result) => { // always execute the asyncData() from the top most component first + Component.asyncData({ store, route: router.currentRoute, context: context }).then((result) => { // always execute the asyncData() from the top most component first console.debug('Top-most asyncData executed') _ssrHydrateSubcomponents(components, store, router, resolve, reject, app, context) }).catch((err) => { diff --git a/core/store/modules/cart/actions.ts b/core/store/modules/cart/actions.ts index eb703a9037..deb9fe66ac 100644 --- a/core/store/modules/cart/actions.ts +++ b/core/store/modules/cart/actions.ts @@ -291,13 +291,9 @@ const actions: ActionTree = { }) } }, - removeItem ({ commit, dispatch }, product) { - commit(types.CART_DEL_ITEM, { product }) + removeItem ({ commit, dispatch }, { product, removeByParentSku = true }) { + commit(types.CART_DEL_ITEM, { product, removeByParentSku }) if (rootStore.state.config.cart.synchronize && product.server_item_id) { - /* dispatch('serverDeleteItem', { - sku: product.sku, - item_id: product.server_item_id - }) */ dispatch('serverPull', { forceClientState: true }) } }, @@ -615,13 +611,13 @@ const actions: ActionTree = { rootStore.dispatch('cart/updateItem', { product: { qty: cartItem.prev_qty } }, { root: true }) // update the server_id reference Vue.prototype.$bus.$emit('cart-after-itemchanged', { item: cartItem }) } else { - rootStore.dispatch('cart/removeItem', { product: cartItem }, { root: true }) // update the server_id reference + rootStore.dispatch('cart/removeItem', { product: cartItem, removeByParentSku: false }, { root: true }) // update the server_id reference } } }) } else { console.log('Removing product from the cart', originalCartItem) - rootStore.commit('cart/' + types.CART_DEL_ITEM, { product: originalCartItem }, {root: true}) + rootStore.commit('cart/' + types.CART_DEL_ITEM, { product: originalCartItem, removeByParentSku: false }, {root: true}) } } else { const isThisNewItemAddedToTheCart = (!originalCartItem || !originalCartItem.item_id) diff --git a/core/store/modules/cart/mutations.ts b/core/store/modules/cart/mutations.ts index 075521744e..294e44e837 100644 --- a/core/store/modules/cart/mutations.ts +++ b/core/store/modules/cart/mutations.ts @@ -26,9 +26,9 @@ const mutations: MutationTree = { Vue.prototype.$bus.$emit('cart-before-save', { items: state.cartItems }) state.cartSavedAt = Date.now() }, - [types.CART_DEL_ITEM] (state, { product }) { + [types.CART_DEL_ITEM] (state, { product, removeByParentSku = true }) { Vue.prototype.$bus.$emit('cart-before-delete', { items: state.cartItems }) - state.cartItems = state.cartItems.filter(p => p.sku !== product.sku && p.parentSku !== product.sku) + state.cartItems = state.cartItems.filter(p => p.sku !== product.sku && (p.parentSku !== product.sku || removeByParentSku === false)) Vue.prototype.$bus.$emit('cart-after-delete', { items: state.cartItems }) state.cartSavedAt = Date.now() }, diff --git a/doc/Layouts and advanced output operations.md b/doc/Layouts and advanced output operations.md new file mode 100644 index 0000000000..274f9bb85d --- /dev/null +++ b/doc/Layouts and advanced output operations.md @@ -0,0 +1,171 @@ +# Layouts and advanced output operations + +Starting from version 1.4.0 Vue Storefront allows You to switch the html templates and layouts dynamically in the SSR mode. + +This feature can be very usefull for non-standard rendering scenarios like: +- generating the XML output, +- generating the AMPHTML pages, +- generating widgets without `` section +... + +## How it works + +Before 1.4.0 Vue Storefront generated the output by mix of: +- taking the base HTML template `src/index.template.html`, +- rendering the `src/themes/default/App.vue` root component, +- injecting the Vue SSR output into the template + adding CSS styles, Script references etc. [Read more on Vue SSR Styles and Scripts injection](https://ssr.vuejs.org/guide/build-config.html#client-config) + +This mode is still in place and it's enabled by default. +What we've changed is **You can now select which html template + layout Your app is routing in per-route manner**. + +## Changelog + +The changes we've introduced: +- now distinct routes can set `context.serverOutputTemplate` in `asyncData` method. By doing so You can skip using `dist/index.html` (which contains typical HTML5 elements - like `` tags) - either xml files - You name it +- distinct routes can set `meta.layout` and by doing so switch the previously constant App.vue layout file - this is what @mercs600 proposed some time ago in slightly changed form +- You've got access to server `context` object in `asyncData` and two new features - `renderPrepend` and `renderAppend` have been created to allow You control the rendering flow of the template + +## Templates + +We've added two new HTML templates + two Vue layouts. +Templates: +- `index.basic.template.html` - which contains the standard HTML markup + CSS injection +- `index.minimal.template.html` - which contains the standard HTML markup without any additional injects - so when You render a Vue component it's output will be pasted into `` and that's all. Probably good starting point for [AMPHTML implementation](https://www.ampstart.com/) + +You can add more templates. All You need is to set the proper `config.ssr.templates` variable: + +```json + "ssr": { + "templates": { + "default": "dist/index.html", + "minimal": "dist/index.minimal.html", + "basic": "dist/index.basic.html" + }, + "executeMixedinAsyncData": true, + "initialStateFilter": ["config", "__DEMO_MODE__", "version", "storeView"], + "useInitialStateFilter": true + }, +``` +The templates paths are relative to root folder of `vue-storefront`. +You can also set the template to none (`""`) to skip it. This option can be usefull for XML / JSON / widgets rendering that do not require the whole HTML layout. + +## Examples + +You can find some examples in the `src/extensions/raw-output-example` example. + +### Generating the XML output +Example URL: `http://localhost:3000/raw-output-example.xml` + +Route setup to switch the Vue layout: + +```js + { path: '/raw-output-example.xml', component: RawOutputExample, meta: { layout: 'empty' } } +``` + +Vue component to render the XML: + +```js + + + +``` + +The key part is: + +```js + context.response.setHeader('Content-Type', 'text/xml') + context.serverOutputTemplate = '' +``` +These two statements: +- set the HTTP header (by accessing ExpressJS response object - `context.response`. There is also `context.request` and `context.app` - the ExpressJS application)- set `serverOutputTemplate` to none which will cause to skip the HTML template rendering at all. + +### Switching off layout + injecting dynamic content +Example URL: `http://localhost:3000/append-prepend.html` + +Route setup to switch the Vue layout: + +```js + { path: '/append-prepend.html', component: NoLayoutAppendPrependExample, meta: { layout: 'empty' } }, +``` + +Vue component to render the XML: +```js + + + +``` + +The key part is: + +```js + context.serverOutputTemplate = '' + context.renderAppend = (context) => { + return '
This content has been dynamically appended
' + } + context.renderPrepend = (context) => { + return '
this content has been dynamically prepended
' + } +``` +These two statements: +- set `serverOutputTemplate` to none which will cause to skip the HTML template rendering at all. +- adds the `renderAppend` and `renderPrepend` methods to the server context. + +The output will be generated with this logic: +```js + const contentPrepend = (typeof context.renderPrepend === 'function') ? context.renderPrepend(context) : '' + const contentAppend = (typeof context.renderAppend === 'function') ? context.renderAppend(context) : '' + output = contentPrepend + output + contentAppend +``` + +Please note, that the `context` contains lot of interesting features You can use to control the CSS, SCRIPT and META injection. [Read more on Vue SSR Styles and Scripts injection](https://ssr.vuejs.org/guide/build-config.html#client-config) + +**Note: [The context object = Vue.prototype.$ssrContext](https://ssr.vuejs.org/guide/head.html)** + +## Upgrade notes diff --git a/package.json b/package.json index 543a37fdd1..90320011ca 100755 --- a/package.json +++ b/package.json @@ -59,6 +59,7 @@ "he": "^1.1.1", "isomorphic-fetch": "^2.2.1", "localforage": "^1.7.2", + "lodash-template": "^1.0.0", "lru-cache": "^4.0.1", "object-hash": "^1.2.0", "pm2": "^2.10.4", diff --git a/src/extensions/index.js b/src/extensions/index.js index 0298bde655..92df945195 100644 --- a/src/extensions/index.js +++ b/src/extensions/index.js @@ -10,6 +10,7 @@ if (!Vue.prototype.$isServer) { // extensions that are not required in the SSR m extensionList.push(require('@vue-storefront/extension-mailchimp-subscribe/index.js')) extensionList.push(require('@vue-storefront/extension-template/index.js')) } +extensionList.push(require('@vue-storefront/raw-output-example/index.js')) extensionList.push(require('@vue-storefront/extension-payment-backend-methods/index.js')) extensionList.push(require('@vue-storefront/extension-payment-cash-on-delivery/index.js')) extensionList.push(require('vsf-payment-stripe/index.js')) diff --git a/src/extensions/raw-output-example/index.js b/src/extensions/raw-output-example/index.js new file mode 100755 index 0000000000..1489fc9b79 --- /dev/null +++ b/src/extensions/raw-output-example/index.js @@ -0,0 +1,11 @@ +import extensionStore from './store' +import extensionRoutes from './router' + +const EXTENSION_KEY = 'raw_content_extension' + +export default function (app, router, store, config, serverContext) { + router.addRoutes(extensionRoutes) // add custom routes + store.registerModule(EXTENSION_KEY, extensionStore) // add custom store + + return { EXTENSION_KEY, extensionRoutes, extensionStore } +} diff --git a/src/extensions/raw-output-example/package.json b/src/extensions/raw-output-example/package.json new file mode 100644 index 0000000000..9ac1da9082 --- /dev/null +++ b/src/extensions/raw-output-example/package.json @@ -0,0 +1,13 @@ +{ + "name": "@vue-storefront/raw-output-example", + "version": "1.3.0", + "description": "Extension on how to generate raw output for Vue Storefront", + "license": "MIT", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "publishConfig": { + "access": "public" + } +} diff --git a/src/extensions/raw-output-example/pages/NoJSExample.vue b/src/extensions/raw-output-example/pages/NoJSExample.vue new file mode 100755 index 0000000000..dc0d38f785 --- /dev/null +++ b/src/extensions/raw-output-example/pages/NoJSExample.vue @@ -0,0 +1,33 @@ + + + + + + diff --git a/src/extensions/raw-output-example/pages/NoLayoutAppendPrependExample.vue b/src/extensions/raw-output-example/pages/NoLayoutAppendPrependExample.vue new file mode 100755 index 0000000000..e4c5cc486a --- /dev/null +++ b/src/extensions/raw-output-example/pages/NoLayoutAppendPrependExample.vue @@ -0,0 +1,33 @@ + + + + + + diff --git a/src/extensions/raw-output-example/pages/RawOutputExample.vue b/src/extensions/raw-output-example/pages/RawOutputExample.vue new file mode 100755 index 0000000000..f742098eb1 --- /dev/null +++ b/src/extensions/raw-output-example/pages/RawOutputExample.vue @@ -0,0 +1,30 @@ + + + + + + diff --git a/src/extensions/raw-output-example/router.js b/src/extensions/raw-output-example/router.js new file mode 100644 index 0000000000..3e03b8000c --- /dev/null +++ b/src/extensions/raw-output-example/router.js @@ -0,0 +1,9 @@ +import NoJSExample from './pages/NoJSExample.vue' +import RawOutputExample from './pages/RawOutputExample.vue' +import NoLayoutAppendPrependExample from './pages/NoLayoutAppendPrependExample.vue' + +export default [ + { path: '/raw-output-example.xml', component: RawOutputExample, meta: { layout: 'empty' } }, + { path: '/append-prepend.html', component: NoLayoutAppendPrependExample, meta: { layout: 'empty' } }, + { path: '/no-js.html', component: NoJSExample, meta: { layout: 'default' } } +] diff --git a/src/extensions/raw-output-example/store.js b/src/extensions/raw-output-example/store.js new file mode 100644 index 0000000000..17f957b64d --- /dev/null +++ b/src/extensions/raw-output-example/store.js @@ -0,0 +1,20 @@ +const state = { +} + +const getters = { +} + +// actions +const actions = { +} + +// mutations +const mutations = { +} + +export default { + state, + getters, + actions, + mutations +} diff --git a/src/index.basic.template.html b/src/index.basic.template.html new file mode 100755 index 0000000000..541312bdcd --- /dev/null +++ b/src/index.basic.template.html @@ -0,0 +1,26 @@ + + + + {{{ meta.inject().title.text() }}} + {{{ meta.inject().meta.text() }}} + {{{ meta.inject().link.text() }}} + + {{{ renderStyles() }}} + + + + + diff --git a/src/index.minimal.template.html b/src/index.minimal.template.html new file mode 100755 index 0000000000..3508f20ede --- /dev/null +++ b/src/index.minimal.template.html @@ -0,0 +1,11 @@ + + + + {{{ meta.inject().title.text() }}} + {{{ meta.inject().meta.text() }}} + {{{ meta.inject().link.text() }}} + + + + + diff --git a/src/index.template.html b/src/index.template.html index 717cb4ef37..e88f2a8d9f 100755 --- a/src/index.template.html +++ b/src/index.template.html @@ -23,8 +23,12 @@ for (var file of chunk.files) { if (file.match(/\.(js|css)$/)) { %> <% }}} %> - + {{{ renderResourceHints() }}} + {{{ renderStyles() }}} + - + + {{{ renderState() }}} + {{{ renderScripts() }}} diff --git a/src/themes/default/App.vue b/src/themes/default/App.vue index 2826f82acc..0a335f68de 100755 --- a/src/themes/default/App.vue +++ b/src/themes/default/App.vue @@ -1,51 +1,13 @@ - - diff --git a/src/themes/default/index.basic.template.html b/src/themes/default/index.basic.template.html new file mode 100755 index 0000000000..541312bdcd --- /dev/null +++ b/src/themes/default/index.basic.template.html @@ -0,0 +1,26 @@ + + + + {{{ meta.inject().title.text() }}} + {{{ meta.inject().meta.text() }}} + {{{ meta.inject().link.text() }}} + + {{{ renderStyles() }}} + + + + + diff --git a/src/themes/default/index.minimal.template.html b/src/themes/default/index.minimal.template.html new file mode 100755 index 0000000000..3508f20ede --- /dev/null +++ b/src/themes/default/index.minimal.template.html @@ -0,0 +1,11 @@ + + + + {{{ meta.inject().title.text() }}} + {{{ meta.inject().meta.text() }}} + {{{ meta.inject().link.text() }}} + + + + + diff --git a/src/themes/default/index.template.html b/src/themes/default/index.template.html index e8dcb26c36..a01da667ea 100755 --- a/src/themes/default/index.template.html +++ b/src/themes/default/index.template.html @@ -24,8 +24,12 @@ for (var file of chunk.files) { if (file.match(/\.(js|css)$/)) { %> <% }}} %> - + {{{ renderResourceHints() }}} + {{{ renderStyles() }}} + - + + {{{ renderState() }}} + {{{ renderScripts() }}} diff --git a/src/themes/default/layouts/Default.vue b/src/themes/default/layouts/Default.vue new file mode 100644 index 0000000000..ea36090538 --- /dev/null +++ b/src/themes/default/layouts/Default.vue @@ -0,0 +1,106 @@ + + + + + diff --git a/src/themes/default/layouts/Empty.vue b/src/themes/default/layouts/Empty.vue new file mode 100644 index 0000000000..083049bd4e --- /dev/null +++ b/src/themes/default/layouts/Empty.vue @@ -0,0 +1,3 @@ + diff --git a/yarn.lock b/yarn.lock index 42282fe7ea..6524412913 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5345,6 +5345,10 @@ lodash-es@^4.17.10: version "4.17.10" resolved "https://registry.yarnpkg.com/lodash-es/-/lodash-es-4.17.10.tgz#62cd7104cdf5dd87f235a837f0ede0e8e5117e05" +lodash-template@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/lodash-template/-/lodash-template-1.0.0.tgz#8d08d9301473aaa861e3a2673794383f183fcad9" + lodash._reinterpolate@~3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/lodash._reinterpolate/-/lodash._reinterpolate-3.0.0.tgz#0ccf2d89166af03b3663c796538b75ac6e114d9d"