├── 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 |
3 |
4 |
This is home
5 |
a router
6 |
7 | go to about
8 |
9 |
10 |
11 |
12 |
17 |
--------------------------------------------------------------------------------
/src/components/about.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
Page about
5 |
another router
6 |
7 | go to home
8 |
9 |
10 |
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 |
3 |
4 |
Demo app
5 |
Description here
6 |
7 |
8 |
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 |
--------------------------------------------------------------------------------