├── static └── .gitkeep ├── .eslintignore ├── public └── pages │ └── index │ ├── components │ ├── not-found.vue │ ├── Tab2.vue │ ├── Tab3.vue │ └── Tab1.vue │ ├── assets │ └── img │ │ └── logo.png │ ├── vuex │ ├── mutation-types.js │ ├── actions.js │ ├── modules │ │ ├── tab1.js │ │ ├── tab2.js │ │ └── tab3.js │ └── store.js │ ├── main.js │ ├── App.vue │ └── route-config.js ├── server ├── views │ ├── error.ejs │ ├── page │ │ └── index.ejs │ └── layout.ejs ├── middlewares │ └── hashedAssets.js ├── routes │ └── index.js └── app.js ├── .babelrc ├── .gitignore ├── .editorconfig ├── .eslintrc.js ├── README.md ├── config └── index.js ├── tools ├── list-entries.js ├── webpack.prod.conf.js ├── build.js ├── webpack.dev.conf.js ├── utils.js └── webpack.base.conf.js ├── bin └── www └── package.json /static/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | build/*.js 2 | config/*.js 3 | -------------------------------------------------------------------------------- /public/pages/index/components/not-found.vue: -------------------------------------------------------------------------------- 1 | 4 | -------------------------------------------------------------------------------- /server/views/error.ejs: -------------------------------------------------------------------------------- 1 |

<%= message %>

2 |

<%= error.status %>

3 |
<%= error.stack %>
4 | -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["es2015", "stage-2"], 3 | "plugins": ["transform-runtime"], 4 | "comments": false 5 | } 6 | -------------------------------------------------------------------------------- /public/pages/index/assets/img/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lanhaoxiang/vue-webpack/HEAD/public/pages/index/assets/img/logo.png -------------------------------------------------------------------------------- /public/pages/index/vuex/mutation-types.js: -------------------------------------------------------------------------------- 1 | // counter 2 | export const INCREMENT = 'INCREMENT' 3 | export const DECREMENT = 'DECREMENT' 4 | -------------------------------------------------------------------------------- /server/views/page/index.ejs: -------------------------------------------------------------------------------- 1 | 2 |
3 | 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules/ 3 | dist/ 4 | server/logs/ 5 | .idea 6 | npm-debug.log 7 | selenium-debug.log 8 | test/unit/coverage 9 | test/e2e/reports 10 | webpack-assets.json 11 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | indent_style = space 6 | indent_size = 2 7 | end_of_line = lf 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | -------------------------------------------------------------------------------- /server/middlewares/hashedAssets.js: -------------------------------------------------------------------------------- 1 | var fs = require('fs'); 2 | 3 | module.exports = function(jsonPath) { 4 | return function(req, res, next) { 5 | res.locals.__assets = JSON.parse(fs.readFileSync(jsonPath)) 6 | next(); 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /public/pages/index/vuex/actions.js: -------------------------------------------------------------------------------- 1 | import * as types from './mutation-types' 2 | 3 | export const incrementAsync = ({dispatch}) => { 4 | setTimeout(() => { 5 | dispatch(types.INCREMENT) 6 | }, 1000) 7 | } 8 | 9 | export const decrementAsync = ({dispatch}) => { 10 | setTimeout(() => { 11 | dispatch(types.DECREMENT) 12 | }, 1000) 13 | } 14 | -------------------------------------------------------------------------------- /public/pages/index/vuex/modules/tab1.js: -------------------------------------------------------------------------------- 1 | import * as types from '../mutation-types' 2 | 3 | const state = { 4 | count: 0 5 | } 6 | const mutations = { 7 | [types.INCREMENT](state) { 8 | state.count++ 9 | }, 10 | [types.DECREMENT](state) { 11 | state.count-- 12 | }, 13 | } 14 | 15 | export default { 16 | state, 17 | mutations 18 | } 19 | -------------------------------------------------------------------------------- /public/pages/index/vuex/modules/tab2.js: -------------------------------------------------------------------------------- 1 | import * as types from '../mutation-types' 2 | 3 | const state = { 4 | count: 0 5 | } 6 | const mutations = { 7 | [types.INCREMENT](state) { 8 | state.count++ 9 | }, 10 | [types.DECREMENT](state) { 11 | state.count-- 12 | }, 13 | } 14 | 15 | export default { 16 | state, 17 | mutations 18 | } 19 | -------------------------------------------------------------------------------- /public/pages/index/vuex/modules/tab3.js: -------------------------------------------------------------------------------- 1 | import * as types from '../mutation-types' 2 | 3 | const state = { 4 | count: 0 5 | } 6 | const mutations = { 7 | [types.INCREMENT](state) { 8 | state.count++ 9 | }, 10 | [types.DECREMENT](state) { 11 | state.count-- 12 | }, 13 | } 14 | 15 | export default { 16 | state, 17 | mutations 18 | } 19 | -------------------------------------------------------------------------------- /server/routes/index.js: -------------------------------------------------------------------------------- 1 | var express = require('express'); 2 | var router = express.Router(); 3 | /* GET home page. */ 4 | router.get('/', function(req, res, next) { 5 | res.render('page/index', { 6 | title:'Vue webpack', 7 | description:'This is a site using Vue.js with webpack' 8 | }); 9 | }); 10 | 11 | module.exports = router; 12 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | parserOptions: { 4 | sourceType: 'module' 5 | }, 6 | // extends: 'airbnb-base', 7 | // required to lint *.vue files 8 | plugins: [ 9 | 'html' 10 | ], 11 | // add your custom rules here 12 | 'rules': { 13 | 'import/no-unresolved': 0, 14 | // allow debugger during development 15 | 'no-debugger': process.env.NODE_ENV === 'production' ? 2 : 0 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /public/pages/index/main.js: -------------------------------------------------------------------------------- 1 | import { configRouter } from './route-config' 2 | import Vue from 'vue' 3 | import VueRouter from 'vue-router'; 4 | 5 | Vue.use(VueRouter); 6 | 7 | // create router 8 | const router = new VueRouter({ 9 | history:true, 10 | linkActiveClass:'active', 11 | saveScrollPosition: true 12 | }) 13 | 14 | configRouter(router); 15 | 16 | const App = Vue.extend(require('./App.vue')) 17 | router.start(App, '#app'); 18 | 19 | 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 介绍 2 | 3 | > Vue服务端路由(express router)+前端路由(vue-router)结合的Demo,支持多页面和前端单页 4 | 5 | ## 使用方式 6 | 7 | ``` bash 8 | # 安装依赖 9 | npm install -g babel 10 | npm install -g webpack 11 | npm install 12 | 13 | # 启动调试 14 | npm run dev 15 | 16 | # 打开浏览器访问 http://localhost:3000, 支持hot reload 17 | 18 | # 编译工程 19 | npm run build:prod 20 | 21 | ``` 22 | 23 | > 注意: 多页面的情况下是共享vendor.js的,所以建议将一些比较大的js库放在cdn上单独加载, debug模式下编译出来的vendor是比较大的, build:prod后会小很多 24 | 25 | -------------------------------------------------------------------------------- /config/index.js: -------------------------------------------------------------------------------- 1 | // see http://vuejs-templates.github.io/webpack for documentation. 2 | var path = require('path') 3 | 4 | /*TODO: Assign a production public path*/ 5 | var publicPath = process.env.NODE_ENV=='production'?'/':'/'; 6 | 7 | module.exports = { 8 | assetsRoot: path.resolve(__dirname, '../dist'), 9 | assetsSubDirectory: 'assets', 10 | assetsPublicPath: publicPath, 11 | sourceMap: process.env.NODE_ENV!=='production' //do not use source map in production environment 12 | } 13 | -------------------------------------------------------------------------------- /tools/list-entries.js: -------------------------------------------------------------------------------- 1 | var fs=require('fs'); 2 | var path=require('path'); 3 | 4 | /** 5 | * load entries from dir with format{ foo:'bar/main.js'} 6 | * warning: please notice that the name of mian.js is important 7 | * @param dir 8 | * @param entries 9 | * @returns {*|Array} 10 | */ 11 | function loadEntries (dir){ 12 | var files = fs.readdirSync(dir); 13 | var entries ={}; 14 | files.forEach((filename) => { 15 | var filePath = path.join(dir, filename); 16 | if (fs.statSync(filePath).isDirectory()) { 17 | entries[filename]=filePath+'/main.js'; 18 | } 19 | }); 20 | return entries; 21 | }; 22 | 23 | module.exports=loadEntries; 24 | -------------------------------------------------------------------------------- /public/pages/index/components/Tab2.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | 28 | 29 | 30 | 33 | -------------------------------------------------------------------------------- /public/pages/index/components/Tab3.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | 28 | 29 | 30 | 33 | -------------------------------------------------------------------------------- /tools/webpack.prod.conf.js: -------------------------------------------------------------------------------- 1 | var path = require('path') 2 | var config = require('../config') 3 | var utils = require('./utils') 4 | var webpack = require('webpack') 5 | var merge = require('webpack-merge') 6 | var baseWebpackConfig = require('./webpack.base.conf') 7 | var ExtractTextPlugin = require('extract-text-webpack-plugin') 8 | 9 | var webpackConfig = merge(baseWebpackConfig, { 10 | devtool: false, 11 | output: { 12 | path: config.assetsRoot, 13 | filename: utils.assetsPath('js/[name].[chunkhash].js'), 14 | chunkFilename: utils.assetsPath('js/[id].[chunkhash].js') 15 | }, 16 | vue: { 17 | loaders: utils.cssLoaders({ 18 | sourceMap: config.sourceMap, 19 | extract: true 20 | }) 21 | }, 22 | plugins: [ 23 | new webpack.optimize.UglifyJsPlugin({ 24 | compress: { 25 | warnings: false 26 | } 27 | }) 28 | ] 29 | }) 30 | 31 | module.exports = webpackConfig 32 | -------------------------------------------------------------------------------- /public/pages/index/components/Tab1.vue: -------------------------------------------------------------------------------- 1 | 16 | 17 | 30 | 31 | 32 | 36 | -------------------------------------------------------------------------------- /public/pages/index/App.vue: -------------------------------------------------------------------------------- 1 | 2 | 22 | 28 | 29 | 34 | -------------------------------------------------------------------------------- /tools/build.js: -------------------------------------------------------------------------------- 1 | // https://github.com/shelljs/shelljs 2 | require('shelljs/global') 3 | var path = require('path') 4 | var config = require('../config') 5 | var ora = require('ora') 6 | var webpack = require('webpack') 7 | var ENV_CONFIG_MAP={ 8 | development:'./webpack.dev.conf', 9 | production:'./webpack.prod.conf' 10 | } 11 | var webpackConfig = require(ENV_CONFIG_MAP[process.env.NODE_ENV]||'./webpack.dev.conf') 12 | 13 | var spinner = ora('building for '+process.env.NODE_ENV+'...') 14 | spinner.start() 15 | 16 | var assetsPath = path.join(config.assetsRoot, config.assetsSubDirectory) 17 | rm('-rf', assetsPath) 18 | mkdir('-p', assetsPath) 19 | cp('-R', 'static/', assetsPath) 20 | 21 | webpack(webpackConfig, function (err, stats) { 22 | spinner.stop() 23 | if (err) throw err 24 | process.stdout.write(stats.toString({ 25 | colors: true, 26 | modules: false, 27 | children: false, 28 | chunks: false, 29 | chunkModules: false 30 | }) + '\n') 31 | }) 32 | -------------------------------------------------------------------------------- /public/pages/index/vuex/store.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import Vuex from 'vuex' 3 | import createLogger from 'vuex/logger' 4 | 5 | import tab1 from './modules/tab1' 6 | import tab2 from './modules/tab2' 7 | import tab3 from './modules/tab3' 8 | 9 | Vue.use(Vuex) 10 | 11 | const middlewares = [] 12 | 13 | if (__DEV__) { 14 | middlewares.push(createLogger()) 15 | } 16 | 17 | const store = new Vuex.Store({ 18 | modules: { 19 | tab1, 20 | tab2, 21 | tab3 22 | }, 23 | middlewares 24 | }) 25 | 26 | export default store 27 | 28 | if (module.hot) { 29 | module.hot.accept([ 30 | './modules/tab1', 31 | './modules/tab2', 32 | './modules/tab3' 33 | ], () => { 34 | try { 35 | store.hotUpdate({ 36 | modules: { 37 | tab1: require('./modules/tab1').default, 38 | tab2: require('./modules/tab2').default, 39 | tab3: require('./modules/tab3').default 40 | } 41 | }) 42 | } catch (e) { 43 | console.log(e.stack) 44 | } 45 | }) 46 | } 47 | -------------------------------------------------------------------------------- /tools/webpack.dev.conf.js: -------------------------------------------------------------------------------- 1 | var path = require('path') 2 | var config = require('../config') 3 | var webpack = require('webpack') 4 | var merge = require('webpack-merge') 5 | var utils = require('./utils') 6 | var baseWebpackConfig = require('./webpack.base.conf') 7 | var hotMiddlewareScript = 'webpack-hot-middleware/client?path=/__webpack_hmr&timeout=20000&reload=true'; 8 | 9 | // add hot-reload related code to entry chunks 10 | Object.keys(baseWebpackConfig.entry).forEach(function (name) { 11 | baseWebpackConfig.entry[name] = [baseWebpackConfig.entry[name],hotMiddlewareScript] 12 | }) 13 | 14 | module.exports = merge(baseWebpackConfig, { 15 | output: { 16 | path: config.assetsRoot, 17 | filename: utils.assetsPath('js/[name].[hash].js'), 18 | chunkFilename: utils.assetsPath('js/[id].[hash].js') 19 | }, 20 | devtool: 'eval-source-map',// eval-source-map is faster for development 21 | plugins: [ 22 | // https://github.com/glenjamin/webpack-hot-middleware#installation--usage 23 | new webpack.HotModuleReplacementPlugin(), 24 | new webpack.NoErrorsPlugin() 25 | ] 26 | }) 27 | -------------------------------------------------------------------------------- /public/pages/index/route-config.js: -------------------------------------------------------------------------------- 1 | import store from './vuex/store' 2 | import { sync } from 'vuex-router-sync' 3 | 4 | export function configRouter (router) { 5 | router.hashbang=true; 6 | // normal routes 7 | router.map({ 8 | '/tab1': { 9 | name:'tab1', 10 | component: require('./components/Tab1.vue') 11 | }, 12 | 13 | '/tab2':{ 14 | name:'tab2', 15 | component: require('./components/Tab2.vue') 16 | }, 17 | 18 | '/tab3':{ 19 | name:'tab3', 20 | component: require('./components/Tab3.vue') 21 | }, 22 | 23 | '*': { 24 | name:'not-found', 25 | component: require('./components/not-found.vue') 26 | } 27 | }) 28 | 29 | router.redirect({ 30 | '/':'/tab1' 31 | }) 32 | 33 | // global before 34 | // 3 options: 35 | // 1. return a boolean 36 | // 2. return a Promise that resolves to a boolean 37 | // 3. call transition.next() or transition.abort() 38 | router.beforeEach((transition) => { 39 | if (transition.to.path === '/forbidden') { 40 | router.app.authenticating = true 41 | setTimeout(() => { 42 | router.app.authenticating = false 43 | alert('this route is forbidden by a global before hook') 44 | transition.abort() 45 | }, 3000) 46 | } else { 47 | $.init(); 48 | transition.next() 49 | } 50 | }) 51 | 52 | sync(store, router) // done. 53 | } 54 | -------------------------------------------------------------------------------- /server/views/layout.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | <%= title%> 11 | 12 | 13 | 16 | 17 | 18 | 19 | 20 | 21 | <%- body%> 22 | 23 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /tools/utils.js: -------------------------------------------------------------------------------- 1 | var path = require('path') 2 | var config = require('../config') 3 | var ExtractTextPlugin = require('extract-text-webpack-plugin') 4 | 5 | exports.assetsPath = function (_path) { 6 | return path.posix.join(config.assetsSubDirectory, _path) 7 | } 8 | 9 | exports.cssLoaders = function (options) { 10 | options = options || {} 11 | // generate loader string to be used with extract text plugin 12 | function generateLoaders (loaders) { 13 | var sourceLoader = loaders.map(function (loader) { 14 | var extraParamChar 15 | if (/\?/.test(loader)) { 16 | loader = loader.replace(/\?/, '-loader?') 17 | extraParamChar = '&' 18 | } else { 19 | loader = loader + '-loader' 20 | extraParamChar = '?' 21 | } 22 | return loader + (options.sourceMap ? extraParamChar + 'sourceMap' : '') 23 | }).join('!') 24 | 25 | if (options.extract) { 26 | return ExtractTextPlugin.extract('vue-style-loader', sourceLoader) 27 | } else { 28 | return ['vue-style-loader', sourceLoader].join('!') 29 | } 30 | } 31 | 32 | // http://vuejs.github.io/vue-loader/configurations/extract-css.html 33 | return { 34 | css: generateLoaders(['css']), 35 | postcss: generateLoaders(['css']), 36 | less: generateLoaders(['css', 'less']), 37 | sass: generateLoaders(['css', 'sass?indentedSyntax']), 38 | scss: generateLoaders(['css', 'sass']), 39 | stylus: generateLoaders(['css', 'stylus']), 40 | styl: generateLoaders(['css', 'stylus']) 41 | } 42 | } 43 | 44 | // Generate loaders for standalone style files (outside of .vue) 45 | exports.styleLoaders = function (options) { 46 | var output = [] 47 | var loaders = exports.cssLoaders(options) 48 | for (var extension in loaders) { 49 | var loader = loaders[extension] 50 | output.push({ 51 | test: new RegExp('\\.' + extension + '$'), 52 | loader: loader 53 | }) 54 | } 55 | return output 56 | } 57 | -------------------------------------------------------------------------------- /bin/www: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | /** 4 | * Module dependencies. 5 | */ 6 | 7 | var app = require('../server/app'); 8 | var debug = require('debug')('vue-webpack:server'); 9 | var http = require('http'); 10 | 11 | /** 12 | * Get port from environment and store in Express. 13 | */ 14 | 15 | var port = normalizePort(process.env.PORT || '3000'); 16 | app.set('port', port); 17 | 18 | /** 19 | * Create HTTP server. 20 | */ 21 | 22 | var server = http.createServer(app); 23 | 24 | /** 25 | * Listen on provided port, on all network interfaces. 26 | */ 27 | 28 | server.listen(port); 29 | server.on('error', onError); 30 | server.on('listening', onListening); 31 | 32 | /** 33 | * Normalize a port into a number, string, or false. 34 | */ 35 | 36 | function normalizePort(val) { 37 | var port = parseInt(val, 10); 38 | 39 | if (isNaN(port)) { 40 | // named pipe 41 | return val; 42 | } 43 | 44 | if (port >= 0) { 45 | // port number 46 | return port; 47 | } 48 | 49 | return false; 50 | } 51 | 52 | /** 53 | * Event listener for HTTP server "error" event. 54 | */ 55 | 56 | function onError(error) { 57 | if (error.syscall !== 'listen') { 58 | throw error; 59 | } 60 | 61 | var bind = typeof port === 'string' 62 | ? 'Pipe ' + port 63 | : 'Port ' + port; 64 | 65 | // handle specific listen errors with friendly messages 66 | switch (error.code) { 67 | case 'EACCES': 68 | console.error(bind + ' requires elevated privileges'); 69 | process.exit(1); 70 | break; 71 | case 'EADDRINUSE': 72 | console.error(bind + ' is already in use'); 73 | process.exit(1); 74 | break; 75 | default: 76 | throw error; 77 | } 78 | } 79 | 80 | /** 81 | * Event listener for HTTP server "listening" event. 82 | */ 83 | 84 | function onListening() { 85 | var addr = server.address(); 86 | var bind = typeof addr === 'string' 87 | ? 'pipe ' + addr 88 | : 'port ' + addr.port; 89 | debug('Listening on ' + bind); 90 | } 91 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vue-webpack", 3 | "version": "1.0.0", 4 | "description": "A Vue.js project", 5 | "author": "blank", 6 | "private": true, 7 | "scripts": { 8 | "dev": "NODE_ENV=development PORT=3000 node bin/www", 9 | "build:dev": "npm run clean && NODE_ENV=development node tools/build.js", 10 | "build:prod": " npm run clean && NODE_ENV=production node tools/build.js", 11 | "clean": "rm -rf dist" 12 | }, 13 | "dependencies": { 14 | "async": "^1.5.2", 15 | "babel-runtime": "^6.0.0", 16 | "body-parser": "~1.13.2", 17 | "compression": "^1.6.1", 18 | "cookie-parser": "~1.3.5", 19 | "debug": "^2.2.0", 20 | "ejs": "~2.3.3", 21 | "express": "~4.13.1", 22 | "express-ejs-layouts": "^2.0.0", 23 | "file-stream-rotator": "0.0.6", 24 | "lodash": "^4.6.1", 25 | "morgan": "~1.6.1", 26 | "serve-favicon": "~2.3.0", 27 | "superagent": "^1.8.0", 28 | "vue": "^1.0.21", 29 | "vue-resource": "^0.9.3", 30 | "vue-router": "^0.7.13", 31 | "vuex": "^0.8.2", 32 | "vuex-router-sync": "^2.1.1" 33 | }, 34 | "devDependencies": { 35 | "assets-webpack-plugin": "^3.4.0", 36 | "babel-core": "^6.0.0", 37 | "babel-loader": "^6.0.0", 38 | "babel-plugin-transform-runtime": "^6.0.0", 39 | "babel-preset-es2015": "^6.0.0", 40 | "babel-preset-stage-2": "^6.0.0", 41 | "browser-sync": "^2.13.0", 42 | "compression-webpack-plugin": "^0.3.1", 43 | "connect-history-api-fallback": "^1.1.0", 44 | "css-loader": "^0.23.0", 45 | "eslint": "^2.10.2", 46 | "eslint-config-airbnb-base": "^3.0.1", 47 | "eslint-friendly-formatter": "^2.0.5", 48 | "eslint-loader": "^1.3.0", 49 | "eslint-plugin-html": "^1.3.0", 50 | "eslint-plugin-import": "^1.8.1", 51 | "eventsource-polyfill": "^0.9.6", 52 | "express": "^4.13.3", 53 | "extract-text-webpack-plugin": "^1.0.1", 54 | "file-loader": "^0.8.4", 55 | "function-bind": "^1.0.2", 56 | "html-webpack-plugin": "^2.8.1", 57 | "http-proxy-middleware": "^0.12.0", 58 | "json-loader": "^0.5.4", 59 | "ora": "^0.2.0", 60 | "shelljs": "^0.6.0", 61 | "url-loader": "^0.5.7", 62 | "vue-hot-reload-api": "^1.2.0", 63 | "vue-html-loader": "^1.0.0", 64 | "vue-loader": "^8.3.0", 65 | "vue-style-loader": "^1.0.0", 66 | "webpack": "^1.12.2", 67 | "webpack-dev-middleware": "^1.4.0", 68 | "webpack-hot-middleware": "^2.6.0", 69 | "webpack-merge": "^0.8.3" 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /server/app.js: -------------------------------------------------------------------------------- 1 | var express = require('express'); 2 | var path = require('path'); 3 | var favicon = require('serve-favicon'); 4 | var logger = require('morgan'); 5 | var fs = require('fs') 6 | var cookieParser = require('cookie-parser'); 7 | var bodyParser = require('body-parser'); 8 | var expressLayouts = require('express-ejs-layouts'); 9 | var config=require('../config') 10 | var staticPath = path.posix.join(config.assetsPublicPath, config.assetsSubDirectory) 11 | var routes = require('./routes/index'); 12 | 13 | var app = express(); 14 | 15 | // view engine setup 16 | app.set('views', path.join(__dirname, 'views')); 17 | app.set('view engine', 'ejs'); 18 | app.use(expressLayouts); 19 | app.set('layout', 'layout') 20 | 21 | // middlewares setup 22 | if(app.get('env')==='development') { 23 | app.use(logger('dev')); 24 | 25 | var webpack = require('webpack'); 26 | var webpackConfig = require('../tools/webpack.dev.conf'); 27 | var compiler = webpack(webpackConfig); 28 | app.use(require('webpack-dev-middleware')(compiler, { 29 | publicPath: webpackConfig.output.publicPath, 30 | lazy:false, 31 | // noInfo:true, 32 | stats: { 33 | colors: true, 34 | chunks: false 35 | } 36 | })) 37 | app.use(require('webpack-hot-middleware')(compiler,{ 38 | log: console.log, path: '/__webpack_hmr', heartbeat: 10 * 1000 39 | })) 40 | 41 | app.use(staticPath, express.static('./static')) 42 | } 43 | else{ 44 | var FileStreamRotator = require('file-stream-rotator'); 45 | var compression = require('compression') 46 | var logDirectory = __dirname + '/logs'; 47 | fs.existsSync(logDirectory) || fs.mkdirSync(logDirectory) 48 | var accessLogStream = FileStreamRotator.getStream({ 49 | date_format: 'YYYYMMDD', 50 | filename: logDirectory + '/access-%DATE%.log', 51 | frequency: 'daily', 52 | verbose: false 53 | }) 54 | app.use(compression()); 55 | app.use(logger('combined', {stream: accessLogStream})); 56 | app.use(express.static(path.join(__dirname, '../dist'))); 57 | } 58 | 59 | app.use(require('connect-history-api-fallback')({ 60 | index:'/', 61 | rewrites:[{from:'/tab',to:'/'}], 62 | logger:app.get('env')=='development'?console.log:null 63 | })) 64 | app.use(bodyParser.json()); 65 | app.use(bodyParser.urlencoded({ extended: false })); 66 | app.use(cookieParser()); 67 | 68 | app.use('*',function(req,res,next){ 69 | if(app.get('env')=='development'){ 70 | console.log('>>>>>:',req.baseUrl); 71 | } 72 | res.locals.title='Vue webpack'; 73 | res.locals.description='Vue webpack web'; 74 | next(); 75 | }) 76 | 77 | 78 | //install hashed assets middleware 79 | app.use(require('./middlewares/hashedAssets')(path.resolve(__dirname,'../config/webpack-assets.json'))); 80 | 81 | app.use('/',routes); 82 | 83 | // catch 404 and forward to error handler 84 | app.use(function(req, res, next) { 85 | var err = new Error('Not Found'); 86 | err.status = 404; 87 | next(err); 88 | }); 89 | 90 | // error handlers 91 | 92 | // development error handler 93 | // will print stacktrace 94 | if (app.get('env') === 'development') { 95 | app.use(function(err, req, res, next) { 96 | res.status(err.status || 500); 97 | res.render('error', { 98 | message: err.message, 99 | error: err 100 | }); 101 | }); 102 | } 103 | 104 | // production error handler 105 | // no stacktraces leaked to user 106 | app.use(function(err, req, res, next) { 107 | res.status(err.status || 500); 108 | res.render('error', { 109 | message: err.message, 110 | error: {} 111 | }); 112 | }); 113 | 114 | module.exports = app; 115 | -------------------------------------------------------------------------------- /tools/webpack.base.conf.js: -------------------------------------------------------------------------------- 1 | var path = require('path') 2 | var config = require('../config') 3 | var utils = require('./utils') 4 | var SaveHashes = require('assets-webpack-plugin') 5 | var ExtractTextPlugin = require('extract-text-webpack-plugin') 6 | var projectRoot = path.resolve(__dirname, '../') 7 | var webpack=require('webpack'); 8 | var env=process.env.NODE_ENV||'development'; 9 | module.exports = { 10 | entry: require('./list-entries')(path.resolve(__dirname,'../public/pages')), 11 | output:{ 12 | publicPath: config.assetsPublicPath 13 | }, 14 | vue: { 15 | loaders: utils.cssLoaders({ 16 | sourceMap: config.sourceMap, 17 | extract: true 18 | }) 19 | }, 20 | resolve: { 21 | extensions: ['', '.js', '.vue'], 22 | //A directory (or array of directories absolute paths), in which webpack should look for modules that weren’t found in resolve.root or resolve.modulesDirectories. 23 | fallback: [path.join(__dirname, '../node_modules')], 24 | alias: { 25 | 26 | } 27 | }, 28 | resolveLoader: { 29 | fallback: [path.join(__dirname, '../node_modules')] 30 | }, 31 | plugins:[ 32 | new webpack.DefinePlugin({ 33 | '__DEV__':env=='development', 34 | 'process.env': { 35 | NODE_ENV:JSON.stringify(env) 36 | } 37 | }), 38 | new webpack.optimize.OccurenceOrderPlugin(), 39 | new SaveHashes({path: path.join(__dirname, '../config')}), 40 | // extract css into its own file 41 | new ExtractTextPlugin(utils.assetsPath('css/[name].[contenthash].css')), 42 | 43 | // split vendor js into its own file 44 | new webpack.optimize.CommonsChunkPlugin({ 45 | name: 'vendor', 46 | minChunks: function (module, count) { 47 | // any required modules inside node_modules are extracted to vendor 48 | return ( 49 | module.resource && 50 | /\.js$/.test(module.resource) && 51 | module.resource.indexOf( 52 | path.join(__dirname, '../node_modules') 53 | ) === 0 54 | ) 55 | } 56 | }), 57 | // extract webpack runtime and module manifest to its own file in order to 58 | // prevent vendor hash from being updated whenever app bundle is updated 59 | new webpack.optimize.CommonsChunkPlugin({ 60 | name: 'manifest', 61 | chunks: ['vendor'] 62 | }) 63 | ], 64 | module: { 65 | preLoaders: [ 66 | { 67 | test: /\.vue$/, 68 | loader: 'eslint', 69 | include: projectRoot, 70 | exclude: /node_modules/ 71 | }, 72 | { 73 | test: /\.js$/, 74 | loader: 'eslint', 75 | include: projectRoot, 76 | exclude: /node_modules/ 77 | } 78 | ], 79 | loaders: utils.styleLoaders({ sourceMap: config.sourceMap, extract: true }).concat([ 80 | { 81 | test: /\.vue$/, 82 | loader: 'vue' 83 | }, 84 | { 85 | test: /\.js$/, 86 | loader: 'babel', 87 | include: projectRoot, 88 | exclude: /node_modules/ 89 | }, 90 | { 91 | test: /\.json$/, 92 | loader: 'json' 93 | }, 94 | { 95 | test: /\.html$/, 96 | loader: 'vue-html' 97 | }, 98 | { 99 | test: /\.(png|jpe?g|gif|svg)(\?.*)?$/, 100 | loader: 'url', 101 | query: { 102 | limit: 10000, 103 | name: utils.assetsPath('img/[name].[hash:7].[ext]') 104 | } 105 | }, 106 | { 107 | test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/, 108 | loader: 'url', 109 | query: { 110 | limit: 10000, 111 | name: utils.assetsPath('fonts/[name].[hash:7].[ext]') 112 | } 113 | } 114 | ]) 115 | }, 116 | eslint: { 117 | formatter: require('eslint-friendly-formatter') 118 | } 119 | } 120 | --------------------------------------------------------------------------------