├── configs ├── settings.js ├── page-list.js ├── index.html ├── webpack.config.js ├── webpack.server.js ├── webpack.build.js └── task-render.js ├── src ├── assets │ ├── .gitkeep │ └── main.css ├── components │ ├── home.vue │ ├── about.vue │ └── container.vue ├── main.js ├── app.js ├── router.js └── store.js ├── .gitignore ├── .babelrc ├── Makefile ├── gulpfile.js ├── package.json └── README.md /configs/settings.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | node_modules 3 | /build 4 | -------------------------------------------------------------------------------- /src/assets/main.css: -------------------------------------------------------------------------------- 1 | 2 | body { 3 | background-color: hsl(200,80%,90%); 4 | } 5 | -------------------------------------------------------------------------------- /configs/page-list.js: -------------------------------------------------------------------------------- 1 | 2 | module.exports = [ 3 | '/index.html', 4 | '/about.html' 5 | ] 6 | -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | ["es2015", { "modules": false }], 4 | "stage-2" 5 | ] 6 | } 7 | -------------------------------------------------------------------------------- /configs/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Vue Pages Workflow 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /src/components/home.vue: -------------------------------------------------------------------------------- 1 | 2 | 11 | 12 | 17 | -------------------------------------------------------------------------------- /src/components/about.vue: -------------------------------------------------------------------------------- 1 | 2 | 11 | 12 | 17 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: build 2 | 3 | 4 | dev: 5 | gulp dev-html 6 | ./node_modules/.bin/webpack-dev-server --config configs/webpack.config.js --hot --inline 7 | 8 | build: 9 | gulp del 10 | ./node_modules/.bin/webpack --config configs/webpack.build.js 11 | ./node_modules/.bin/webpack --config configs/webpack.server.js 12 | gulp build-html 13 | -------------------------------------------------------------------------------- /configs/webpack.config.js: -------------------------------------------------------------------------------- 1 | 2 | module.exports = { 3 | entry: { 4 | main: './src/main' 5 | }, 6 | output: { 7 | filename: '[name].js' 8 | }, 9 | module: { 10 | rules: [ 11 | {test: /\.css$/, loader: 'style!css'}, 12 | {test: /\.vue$/, loader: 'vue'}, 13 | {test: /\.js$/, loader: 'babel'} 14 | ] 15 | }, 16 | resolve: { 17 | extensions: ['.js', '.vue'] 18 | } 19 | }; 20 | -------------------------------------------------------------------------------- /src/main.js: -------------------------------------------------------------------------------- 1 | 2 | import app from './app'; 3 | import './assets/main.css'; 4 | import store from './store'; 5 | 6 | var storeEl = document.querySelector('#store'); 7 | if (storeEl != null) { 8 | var rawData = storeEl.getAttribute('content'); 9 | 10 | console.log('store:', store); 11 | store.replaceState(JSON.parse(rawData)); 12 | 13 | console.debug('initial store:', store); 14 | } 15 | 16 | app.$mount('#app'); 17 | -------------------------------------------------------------------------------- /src/app.js: -------------------------------------------------------------------------------- 1 | 2 | import Vue from 'vue'; 3 | import Vuex from 'vuex'; 4 | import VueRouter from 'vue-router'; 5 | 6 | Vue.use(VueRouter); 7 | 8 | import Container from './components/container'; 9 | 10 | import router from './router'; 11 | import store from './store'; 12 | 13 | export default new Vue({ 14 | router: router, 15 | store: store, 16 | render: function(h) { 17 | return h(Container, {}); 18 | } 19 | }); 20 | -------------------------------------------------------------------------------- /src/router.js: -------------------------------------------------------------------------------- 1 | 2 | import VueRouter from 'vue-router'; 3 | 4 | import Home from './components/home'; 5 | import About from './components/about'; 6 | 7 | export default new VueRouter({ 8 | mode: 'history', 9 | base: __dirname, 10 | routes: [ 11 | { 12 | path: '/index.html', 13 | component: Home 14 | }, 15 | { 16 | path: '/', 17 | component: Home 18 | }, { 19 | path: '/about.html', 20 | component: About 21 | } 22 | ] 23 | }); 24 | -------------------------------------------------------------------------------- /src/components/container.vue: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 15 | 16 | 28 | -------------------------------------------------------------------------------- /configs/webpack.server.js: -------------------------------------------------------------------------------- 1 | 2 | var webpack = require('webpack'); 3 | 4 | module.exports = { 5 | target: 'node', 6 | entry: { 7 | container: './src/app.js' 8 | }, 9 | output: { 10 | path: 'build/', 11 | filename: 'app.js', 12 | libraryTarget: 'commonjs2' 13 | }, 14 | externals: Object.keys(require('../package.json').dependencies), 15 | module: { 16 | rules: [ 17 | {test: /\.vue$/, loader: 'vue', options: {}}, 18 | {test: /\.js$/, loader: 'babel?presets[]=es2015', exclude: /node_modules/}, 19 | {test: /\.css$/, loader: 'style!css'} 20 | ] 21 | }, 22 | resolve: { 23 | extensions: ['.js', '.vue',] 24 | }, 25 | plugins: [] 26 | }; 27 | -------------------------------------------------------------------------------- /src/store.js: -------------------------------------------------------------------------------- 1 | 2 | import Vue from 'vue'; 3 | import Vuex from 'vuex'; 4 | 5 | Vue.use(Vuex); 6 | 7 | var state = { 8 | count: 0 9 | }; 10 | 11 | var mutations = { 12 | increment: function(state) { 13 | state.count = state.count + 1; 14 | }, 15 | decrement: function(state) { 16 | state.count = state.count - 1; 17 | } 18 | }; 19 | 20 | var getters = { 21 | count: function(state) { 22 | return state.count; 23 | } 24 | }; 25 | 26 | var actions = { 27 | inc: function({commit}) { 28 | commit('increment'); 29 | }, 30 | dec: function(commit) { 31 | commit('increment'); 32 | } 33 | }; 34 | 35 | export default new Vuex.Store({ 36 | state: state, 37 | mutations: mutations, 38 | actions: actions, 39 | getters: getters 40 | }); 41 | -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | 2 | var fs = require('fs'); 3 | var gulp = require('gulp'); 4 | var path = require('path'); 5 | 6 | var settings = require('./configs/settings'); 7 | var taskRender = require('./configs/task-render'); 8 | 9 | gulp.task('del', function(cb) { 10 | var del = require('del'); 11 | del(['build/**/*']).then(paths => { 12 | if (paths.length > 0) { 13 | console.log('Deleted files and folders:'); 14 | console.log(paths.join('\n')); 15 | } 16 | cb(); 17 | }); 18 | }); 19 | 20 | gulp.task('build-html', taskRender); 21 | 22 | gulp.task('dev-html', function(cb) { 23 | var template = fs.readFileSync('./configs/index.html', 'utf8'); 24 | var entry1 = ''; 25 | var entry2 = ''; 26 | var html = template 27 | .replace(entry1, '') 28 | .replace(entry2, 29 | `` 30 | ); 31 | fs.writeFileSync(`./build/index.html`, html); 32 | }); 33 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vue-pages-workflow", 3 | "version": "0.1.0", 4 | "description": "", 5 | "main": "src/index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "keywords": [ 10 | "vue" 11 | ], 12 | "author": "jiyinyiyong", 13 | "license": "MIT", 14 | "devDependencies": { 15 | "babel-core": "^6.17.0", 16 | "babel-loader": "^6.2.5", 17 | "babel-preset-es2015": "^6.16.0", 18 | "babel-preset-stage-2": "^6.17.0", 19 | "css-loader": "^0.25.0", 20 | "del": "^2.2.2", 21 | "extract-text-webpack-plugin": "^2.0.0-beta.4", 22 | "gulp": "^3.9.1", 23 | "mkpath": "^1.0.0", 24 | "style-loader": "^0.13.1", 25 | "vue-loader": "^9.7.0", 26 | "vue-server-renderer": "^2.0.3", 27 | "webpack": "^2.1.0-beta.25", 28 | "webpack-dev-server": "^1.16.2" 29 | }, 30 | "dependencies": { 31 | "vue": "^2.0.3", 32 | "vue-router": "^2.0.1", 33 | "vuex": "^2.0.0" 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /configs/webpack.build.js: -------------------------------------------------------------------------------- 1 | 2 | var fs = require('fs'); 3 | var webpack = require('webpack'); 4 | 5 | var ExtractTextPlugin = require('extract-text-webpack-plugin'); 6 | 7 | module.exports = { 8 | entry: { 9 | main: './src/main', 10 | vendor: ['vue', 'vue-router', 'vuex'] 11 | }, 12 | output: { 13 | path: 'build/', 14 | filename: '[name].[hash:8].js' 15 | }, 16 | module: { 17 | rules: [ 18 | {test: /\.vue$/, loader: 'vue', 19 | options: { 20 | loaders: { 21 | css: ExtractTextPlugin.extract({ 22 | fallbackLoader: 'style-loader', 23 | loader: 'css-loader' 24 | }) 25 | } 26 | } 27 | }, 28 | {test: /\.js$/, loader: 'babel'}, 29 | {test: /\.css$/, loader: ExtractTextPlugin.extract({ 30 | fallbackLoader: 'style-loader', 31 | loader: 'css-loader' 32 | })}, 33 | ] 34 | }, 35 | resolve: { 36 | extensions: ['.js', '.vue'] 37 | }, 38 | plugins: [ 39 | new webpack.optimize.CommonsChunkPlugin('vendor'), 40 | new ExtractTextPlugin('[name].[hash:8].css'), 41 | new webpack.optimize.UglifyJsPlugin(), 42 | function() { 43 | this.plugin("done", function(stats) { 44 | var content = JSON.stringify(stats.toJson().assetsByChunkName); 45 | return fs.writeFileSync('build/assets.json', content); 46 | }); 47 | } 48 | ] 49 | }; 50 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | Vue Pages Workflow 3 | ---- 4 | 5 | Gulp rendered multi-pages template in Vue 2. 6 | 7 | [查看上手指南](https://github.com/ElemeFE/vue-pages-workflow/wiki/Quick-Start). 8 | 9 | > Status: Prototype. 10 | 11 | > Notice: if this template updates, you have to do manual updates! 12 | 13 | Demo http://vue-pages-workflow.mvc-works.org/ 14 | 15 | Try "view source" and "refresh page" to confirm is already rendered. 16 | 17 | ### Goals 18 | 19 | Build paths in router into pages, and server pages with Nginx. 20 | For several benefits: 21 | 22 | * pages look like being server-rendered 23 | * performant with Nginx static server 24 | * requests sent from browsers, fewew tricks required 25 | 26 | Ideas explained: 27 | 28 | * [“Rendering pages during assets building” as an optimization](https://medium.com/@jiyinyiyong/rendering-pages-during-assets-building-as-an-optimization-9aa1b4a5ebe2#.f67uotmpq) 29 | * [前端编译期页面渲染作为优化方案的想法](https://segmentfault.com/a/1190000007235765) 30 | 31 | ### Develop 32 | 33 | Using Vue 2 to render Vue app in Node.js or more specificly Gulp. 34 | Using Webpack 2 since Vue 2 write docs in it. 35 | 36 | * run in dev mode 37 | 38 | To start app in development environment: 39 | 40 | ```bash 41 | make dev 42 | # load build/index.html in a browser, Nginx suggested 43 | ``` 44 | 45 | * build app 46 | 47 | ```bash 48 | make build 49 | # see build/ for compiled files 50 | # load build/index.html in a browser, make sure the path is `/` 51 | ``` 52 | 53 | * add pages 54 | 55 | To add pages, you have to make sure it works on both scenarios. 56 | 57 | * For client routing, add new router in `src/router.js` with a component. 58 | * For routing on Nginx, add the path to `configs/pages-list.js` to generate an HTML file. 59 | 60 | Make sure the names are consistent and ends with `.html`, 61 | so that Nginx will resolve the file correctly and vue-router also will recognize. 62 | 63 | ### License 64 | 65 | MIT 66 | -------------------------------------------------------------------------------- /configs/task-render.js: -------------------------------------------------------------------------------- 1 | 2 | var fs = require('fs'); 3 | var gulp = require('gulp'); 4 | var path = require('path'); 5 | 6 | var settings = require('./settings'); 7 | var pageList = require('./page-list'); 8 | 9 | module.exports = function(cb) { 10 | process.env.VUE_ENV = 'server'; 11 | var Vue = require('vue'); 12 | var Vuex = require('vuex'); 13 | var VueRouter = require('vue-router'); 14 | var mkpath = require('mkpath'); 15 | var renderer = require('vue-server-renderer').createRenderer(); 16 | 17 | Vue.use(Vuex); 18 | 19 | // this file requires compilation 20 | var app = require('../build/app').default; 21 | 22 | // grab store and router from private APIs 23 | var store = app.$store; 24 | var router = app._router; 25 | // console.log(store, router); 26 | Vue.use(VueRouter); 27 | 28 | pageList.forEach(function(address) { 29 | router.push(address); 30 | renderer.renderToString(app, function(err, appHtml) { 31 | if (err != null) { 32 | throw err; 33 | } else { 34 | var template = fs.readFileSync('./configs/index.html', 'utf8'); 35 | var entry1 = ''; 36 | var entry2 = ''; 37 | var appMarkup = '
'; 38 | var assets = JSON.parse(fs.readFileSync('./build/assets.json')); 39 | var initialData = JSON.stringify(store.state) 40 | .replace(/"/g, """); 41 | var html = template 42 | .replace(entry1, 43 | ` 44 | ` 45 | ) 46 | .replace(entry2, 47 | ` 48 | ` 49 | ) 50 | .replace(appMarkup, appHtml) 51 | fs.writeFileSync(`./build/${address}`, html); 52 | } 53 | }); 54 | }); 55 | }; 56 | --------------------------------------------------------------------------------