diff --git a/build/webpack.config.base.js b/build/webpack.config.base.js index f9b2168..b975af0 100644 --- a/build/webpack.config.base.js +++ b/build/webpack.config.base.js @@ -11,6 +11,7 @@ const config = { output: { filename: 'bundle.[hash:8].js', path: path.join(__dirname, '../dist'), + publicPath: '//127.0.0.1:8000/public/', }, module: { rules: [ diff --git a/build/webpack.config.client.js b/build/webpack.config.client.js index 682fada..d094882 100644 --- a/build/webpack.config.client.js +++ b/build/webpack.config.client.js @@ -5,7 +5,8 @@ const webpack = require('webpack'); const webpackMerge = require('webpack-merge'); const HtmlPlugin = require('html-webpack-plugin'); // 将非 JS 的文件单独打包分离出来,这里主要是希望单独引入 CSS -const ExtractPlugin = require('extract-text-webpack-plugin'); +// const ExtractPlugin = require('extract-text-webpack-plugin'); +const MiniCssExtractPlugin = require('mini-css-extract-plugin'); const baseConfig = require('./webpack.config.base'); const VueClientPlugin = require('vue-server-renderer/client-plugin'); @@ -48,6 +49,7 @@ let config; if (isDev) { // webpack-merge 不影响 baseConfig,因为产生的是新结果 config = webpackMerge(baseConfig, { + mode: 'development', devtool: '#cheap-module-eval-source-map', devServer, module: { @@ -96,6 +98,7 @@ if (isDev) { }); } else { config = webpackMerge(baseConfig, { + mode: 'production', entry: { // 将类库与第三方依赖单独打包成 vendor,因为如果跟业务代码混在一起,那么由于业务经常更新所以导致这些都无法长期缓存 app: path.join(__dirname, '../client/index.js'), @@ -110,24 +113,25 @@ if (isDev) { rules: [ { test: /\.less$/, - use: ExtractPlugin.extract({ - fallback: 'vue-style-loader', - use: [ - 'css-loader', - { - loader: 'postcss-loader', - options: { - sourceMap: true, - }, + use: [ + MiniCssExtractPlugin.loader, + 'css-loader', + { + loader: 'postcss-loader', + options: { + sourceMap: true, }, - 'less-loader', - ], - }), + }, + 'less-loader', + ], }, ], }, plugins: defaultPlugins.concat([ - new ExtractPlugin('styles.[hash:8].css'), + new MiniCssExtractPlugin({ + filename: 'styles.[contentHash:8].css' + }), + // new ExtractPlugin('styles.[hash:8].css'), // 在 webpack3 是 webpack.optimize.CommonsChunkPlugin new webpack.optimize.SplitChunksPlugin({ name: 'runtime', diff --git a/build/webpack.config.server.js b/build/webpack.config.server.js index 78a30cf..67db455 100644 --- a/build/webpack.config.server.js +++ b/build/webpack.config.server.js @@ -4,7 +4,6 @@ const path = require('path'); const webpack = require('webpack'); const webpackMerge = require('webpack-merge'); // 将非 JS 的文件单独打包分离出来,这里主要是希望单独引入 CSS -const ExtractPlugin = require('extract-text-webpack-plugin'); const baseConfig = require('./webpack.config.base'); // windows npm script 设置变量是通过 set NODE_ENV=production // mac npm script 设置变量是 NODE_ENV=production NODE_ENV=production @@ -13,6 +12,7 @@ const baseConfig = require('./webpack.config.base'); const VueServerPlugin = require('vue-server-renderer/server-plugin') const config = webpackMerge(baseConfig, { + mode: 'none', entry: path.join(__dirname, '../client/server-entry.js'), target: 'node', // 服务端渲染必须传 devtool: 'source-map', @@ -31,24 +31,21 @@ const config = webpackMerge(baseConfig, { // 但是 node 端没有 dom 环境,那么就会报错 // 所以单独打包出来 test: /\.less$/, - use: ExtractPlugin.extract({ - fallback: 'vue-style-loader', - use: [ - 'css-loader', - { - loader: 'postcss-loader', - options: { - sourceMap: true, - }, + use: [ + 'vue-style-loader', + 'css-loader', + { + loader: 'postcss-loader', + options: { + sourceMap: true, }, - 'less-loader', - ], - }), + }, + 'less-loader', + ], }, ], }, plugins: [ - new ExtractPlugin('styles.[hash:8].css'), new webpack.DefinePlugin({ 'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV || 'development'), 'process.env.VUE_ENV': '"server"', diff --git a/client/config/routes.js b/client/config/routes.js index 337c82a..e734d13 100644 --- a/client/config/routes.js +++ b/client/config/routes.js @@ -8,10 +8,12 @@ export default [ }, { path: '/app', + // component: Todo, component: () => import('../views/todo/todo.vue'), }, { path: '/login', + // component: Login, component: () => import('../views/login/login.vue'), }, ] diff --git a/client/create-app.js b/client/create-app.js new file mode 100644 index 0000000..fe7a758 --- /dev/null +++ b/client/create-app.js @@ -0,0 +1,23 @@ +import Vue from 'vue' +import VueRouter from 'vue-router' +import Vuex from 'vuex' + +import App from './app.vue' +import createStore from './store/store' +import createRouter from './config/router' + +import './assets/styles/global.less' + +Vue.use(VueRouter) +Vue.use(Vuex) + +export default () => { + const router = createRouter() + const store = createStore() + const app = new Vue({ + router, + store, + render: h => h(App), + }) + return { app, router, store } +} diff --git a/client/server-entry.js b/client/server-entry.js index e69de29..35c8c9f 100644 --- a/client/server-entry.js +++ b/client/server-entry.js @@ -0,0 +1,16 @@ +import createApp from './create-app' + +// 这里的 context 等于 server-render 里的 context +export default context => { + const { app, router, store } = createApp() + return new Promise((resolve, reject) => { + router.push(context.url) // 给路由推一条记录,才能进入路由调用组件 + router.onReady(() => { + const matchedComponents = router.getMatchedComponents() + if (!matchedComponents.length) { + reject(new Error('no component matched')) + } + resolve(app) + }) + }) +} diff --git a/favicon.ico b/favicon.ico new file mode 100644 index 0000000..80403e8 Binary files /dev/null and b/favicon.ico differ diff --git a/nodemon.json b/nodemon.json new file mode 100644 index 0000000..cc5866c --- /dev/null +++ b/nodemon.json @@ -0,0 +1,16 @@ +{ + "restartable": "rs", + "ignore": [ + ".git", + "node_modules/**/node_modules", + ".eslintrc", + "client", + "build/webpack.config.client.js", + "public" + ], + "verbose": true, + "env": { + "NODE_ENV": "development" + }, + "ext": "js json ejs" +} diff --git a/package-lock.json b/package-lock.json index 014bc18..9d7db84 100644 --- a/package-lock.json +++ b/package-lock.json @@ -431,6 +431,12 @@ "integrity": "sha1-0pHGpOl5ibXGHZrPOWrk/hM6cY0=", "dev": true }, + "abbrev": { + "version": "1.1.1", + "resolved": "http://registry.npm.taobao.org/abbrev/download/abbrev-1.1.1.tgz", + "integrity": "sha1-+PLIh60Qv2f2NPAFtph/7TF5qsg=", + "dev": true + }, "accepts": { "version": "1.3.5", "resolved": "http://registry.npm.taobao.org/accepts/download/accepts-1.3.5.tgz", @@ -482,6 +488,15 @@ "integrity": "sha1-S4Mee1MUFafMUYzUBOc/YZPGNJ0=", "dev": true }, + "ansi-align": { + "version": "2.0.0", + "resolved": "http://registry.npm.taobao.org/ansi-align/download/ansi-align-2.0.0.tgz", + "integrity": "sha1-w2rsy6VjuJzrVW82kPCx2eNUf38=", + "dev": true, + "requires": { + "string-width": "^2.0.0" + } + }, "ansi-colors": { "version": "3.2.4", "resolved": "http://registry.npm.taobao.org/ansi-colors/download/ansi-colors-3.2.4.tgz", @@ -1639,6 +1654,29 @@ "integrity": "sha1-aN/1++YMUes3cl6p4+0xDcwed24=", "dev": true }, + "boxen": { + "version": "1.3.0", + "resolved": "http://registry.npm.taobao.org/boxen/download/boxen-1.3.0.tgz", + "integrity": "sha1-VcbDmouljZxhrSLNh3Uy3rZlogs=", + "dev": true, + "requires": { + "ansi-align": "^2.0.0", + "camelcase": "^4.0.0", + "chalk": "^2.0.1", + "cli-boxes": "^1.0.0", + "string-width": "^2.0.0", + "term-size": "^1.2.0", + "widest-line": "^2.0.0" + }, + "dependencies": { + "camelcase": { + "version": "4.1.0", + "resolved": "http://registry.npm.taobao.org/camelcase/download/camelcase-4.1.0.tgz", + "integrity": "sha1-1UVjW+HjPFQmScaRc+Xeas+uNN0=", + "dev": true + } + } + }, "brace-expansion": { "version": "1.1.11", "resolved": "http://registry.npm.taobao.org/brace-expansion/download/brace-expansion-1.1.11.tgz", @@ -1916,6 +1954,12 @@ "integrity": "sha1-x8L9TXEIAoTIZ33UEDaN+Ng2iP4=", "dev": true }, + "capture-stack-trace": { + "version": "1.0.1", + "resolved": "http://registry.npm.taobao.org/capture-stack-trace/download/capture-stack-trace-1.0.1.tgz", + "integrity": "sha1-psC74fOPOqC5Ijjstv9Cw0TUE10=", + "dev": true + }, "caseless": { "version": "0.12.0", "resolved": "http://registry.npm.taobao.org/caseless/download/caseless-0.12.0.tgz", @@ -2034,6 +2078,12 @@ "source-map": "~0.6.0" } }, + "cli-boxes": { + "version": "1.0.0", + "resolved": "http://registry.npm.taobao.org/cli-boxes/download/cli-boxes-1.0.0.tgz", + "integrity": "sha1-T6kXw+WclKAEzWH47lCdplFocUM=", + "dev": true + }, "cli-cursor": { "version": "2.1.0", "resolved": "http://registry.npm.taobao.org/cli-cursor/download/cli-cursor-2.1.0.tgz", @@ -2166,6 +2216,88 @@ "typedarray": "^0.0.6" } }, + "concurrently": { + "version": "4.1.0", + "resolved": "http://registry.npm.taobao.org/concurrently/download/concurrently-4.1.0.tgz", + "integrity": "sha1-F/3wZ9pxIQaF2epVRCPvI52jDTM=", + "dev": true, + "requires": { + "chalk": "^2.4.1", + "date-fns": "^1.23.0", + "lodash": "^4.17.10", + "read-pkg": "^4.0.1", + "rxjs": "^6.3.3", + "spawn-command": "^0.0.2-1", + "supports-color": "^4.5.0", + "tree-kill": "^1.1.0", + "yargs": "^12.0.1" + }, + "dependencies": { + "has-flag": { + "version": "2.0.0", + "resolved": "http://registry.npm.taobao.org/has-flag/download/has-flag-2.0.0.tgz", + "integrity": "sha1-6CB68cx7MNRGzHC3NLXovhj4jVE=", + "dev": true + }, + "pify": { + "version": "3.0.0", + "resolved": "http://registry.npm.taobao.org/pify/download/pify-3.0.0.tgz", + "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", + "dev": true + }, + "read-pkg": { + "version": "4.0.1", + "resolved": "http://registry.npm.taobao.org/read-pkg/download/read-pkg-4.0.1.tgz", + "integrity": "sha1-ljYlN48+HE1IyFhytabsfV0JMjc=", + "dev": true, + "requires": { + "normalize-package-data": "^2.3.2", + "parse-json": "^4.0.0", + "pify": "^3.0.0" + } + }, + "supports-color": { + "version": "4.5.0", + "resolved": "http://registry.npm.taobao.org/supports-color/download/supports-color-4.5.0.tgz", + "integrity": "sha1-vnoN5ITexcXN34s9WRJQRJEvY1s=", + "dev": true, + "requires": { + "has-flag": "^2.0.0" + } + } + } + }, + "configstore": { + "version": "3.1.2", + "resolved": "http://registry.npm.taobao.org/configstore/download/configstore-3.1.2.tgz", + "integrity": "sha1-xvJd767vJt8S3TNBSwAf6BpUP48=", + "dev": true, + "requires": { + "dot-prop": "^4.1.0", + "graceful-fs": "^4.1.2", + "make-dir": "^1.0.0", + "unique-string": "^1.0.0", + "write-file-atomic": "^2.0.0", + "xdg-basedir": "^3.0.0" + }, + "dependencies": { + "make-dir": { + "version": "1.3.0", + "resolved": "http://registry.npm.taobao.org/make-dir/download/make-dir-1.3.0.tgz", + "integrity": "sha1-ecEDO4BRW9bSTsmTPoYMp17ifww=", + "dev": true, + "requires": { + "pify": "^3.0.0" + } + }, + "pify": { + "version": "3.0.0", + "resolved": "http://registry.npm.taobao.org/pify/download/pify-3.0.0.tgz", + "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", + "dev": true + } + } + }, "connect-history-api-fallback": { "version": "1.6.0", "resolved": "http://registry.npm.taobao.org/connect-history-api-fallback/download/connect-history-api-fallback-1.6.0.tgz", @@ -2285,6 +2417,15 @@ "elliptic": "^6.0.0" } }, + "create-error-class": { + "version": "3.0.2", + "resolved": "http://registry.npm.taobao.org/create-error-class/download/create-error-class-3.0.2.tgz", + "integrity": "sha1-Br56vvlHo/FKMP1hBnHUAbyot7Y=", + "dev": true, + "requires": { + "capture-stack-trace": "^1.0.0" + } + }, "create-hash": { "version": "1.2.0", "resolved": "http://registry.npm.taobao.org/create-hash/download/create-hash-1.2.0.tgz", @@ -2354,6 +2495,12 @@ "randomfill": "^1.0.3" } }, + "crypto-random-string": { + "version": "1.0.0", + "resolved": "http://registry.npm.taobao.org/crypto-random-string/download/crypto-random-string-1.0.0.tgz", + "integrity": "sha1-ojD2T1aDEOFJgAmUB5DsmVRbyn4=", + "dev": true + }, "css-loader": { "version": "2.1.1", "resolved": "http://registry.npm.taobao.org/css-loader/download/css-loader-2.1.1.tgz", @@ -2413,6 +2560,12 @@ "assert-plus": "^1.0.0" } }, + "date-fns": { + "version": "1.30.1", + "resolved": "http://registry.npm.taobao.org/date-fns/download/date-fns-1.30.1.tgz", + "integrity": "sha1-LnG/CxGRU9u0zE6I2epaz7UNwFw=", + "dev": true + }, "date-now": { "version": "0.1.4", "resolved": "http://registry.npm.taobao.org/date-now/download/date-now-0.1.4.tgz", @@ -2451,6 +2604,12 @@ "integrity": "sha1-9dJgKStmDghO/0zbyfCK0yR0SLU=", "dev": true }, + "deep-extend": { + "version": "0.6.0", + "resolved": "http://registry.npm.taobao.org/deep-extend/download/deep-extend-0.6.0.tgz", + "integrity": "sha1-xPp8lUBKF6nD6Mp+FTcxK3NjMKw=", + "dev": true + }, "deep-is": { "version": "0.1.3", "resolved": "http://registry.npm.taobao.org/deep-is/download/deep-is-0.1.3.tgz", @@ -2681,6 +2840,21 @@ "domelementtype": "1" } }, + "dot-prop": { + "version": "4.2.0", + "resolved": "http://registry.npm.taobao.org/dot-prop/download/dot-prop-4.2.0.tgz", + "integrity": "sha1-HxngwuGqDjJ5fEl5nyg3rGr2nFc=", + "dev": true, + "requires": { + "is-obj": "^1.0.0" + } + }, + "duplexer3": { + "version": "0.1.4", + "resolved": "http://registry.npm.taobao.org/duplexer3/download/duplexer3-0.1.4.tgz", + "integrity": "sha1-7gHdHKwO08vH/b6jfcCo8c4ALOI=", + "dev": true + }, "duplexify": { "version": "3.7.1", "resolved": "http://registry.npm.taobao.org/duplexify/download/duplexify-3.7.1.tgz", @@ -4321,6 +4495,15 @@ } } }, + "global-dirs": { + "version": "0.1.1", + "resolved": "http://registry.npm.taobao.org/global-dirs/download/global-dirs-0.1.1.tgz", + "integrity": "sha1-sxnA3UYH81PzvpzKTHL8FIxJ9EU=", + "dev": true, + "requires": { + "ini": "^1.3.4" + } + }, "global-modules": { "version": "1.0.0", "resolved": "http://registry.npm.taobao.org/global-modules/download/global-modules-1.0.0.tgz", @@ -4372,6 +4555,33 @@ } } }, + "got": { + "version": "6.7.1", + "resolved": "http://registry.npm.taobao.org/got/download/got-6.7.1.tgz", + "integrity": "sha1-JAzQV4WpoY5WHcG0S0HHY+8ejbA=", + "dev": true, + "requires": { + "create-error-class": "^3.0.0", + "duplexer3": "^0.1.4", + "get-stream": "^3.0.0", + "is-redirect": "^1.0.0", + "is-retry-allowed": "^1.0.0", + "is-stream": "^1.0.0", + "lowercase-keys": "^1.0.0", + "safe-buffer": "^5.0.1", + "timed-out": "^4.0.0", + "unzip-response": "^2.0.1", + "url-parse-lax": "^1.0.0" + }, + "dependencies": { + "get-stream": { + "version": "3.0.0", + "resolved": "http://registry.npm.taobao.org/get-stream/download/get-stream-3.0.0.tgz", + "integrity": "sha1-jpQ9E1jcN1VQVOy+LtsFqhdO3hQ=", + "dev": true + } + } + }, "graceful-fs": { "version": "4.1.15", "resolved": "http://registry.npm.taobao.org/graceful-fs/download/graceful-fs-4.1.15.tgz", @@ -4815,6 +5025,12 @@ "integrity": "sha1-dQ49tYYgh7RzfrrIIH/9HvJ7Jfw=", "dev": true }, + "ignore-by-default": { + "version": "1.0.1", + "resolved": "http://registry.npm.taobao.org/ignore-by-default/download/ignore-by-default-1.0.1.tgz", + "integrity": "sha1-SMptcvbGo68Aqa1K5odr44ieKwk=", + "dev": true + }, "image-size": { "version": "0.5.5", "resolved": "http://registry.npm.taobao.org/image-size/download/image-size-0.5.5.tgz", @@ -4858,6 +5074,12 @@ "resolve-from": "^3.0.0" } }, + "import-lazy": { + "version": "2.1.0", + "resolved": "http://registry.npm.taobao.org/import-lazy/download/import-lazy-2.1.0.tgz", + "integrity": "sha1-BWmOPUXIjo1+nZLLBYTnfwlvPkM=", + "dev": true + }, "import-local": { "version": "2.0.0", "resolved": "http://registry.npm.taobao.org/import-local/download/import-local-2.0.0.tgz", @@ -5143,6 +5365,22 @@ "is-extglob": "^2.1.1" } }, + "is-installed-globally": { + "version": "0.1.0", + "resolved": "http://registry.npm.taobao.org/is-installed-globally/download/is-installed-globally-0.1.0.tgz", + "integrity": "sha1-Df2Y9akRFxbdU13aZJL2e/PSWoA=", + "dev": true, + "requires": { + "global-dirs": "^0.1.0", + "is-path-inside": "^1.0.0" + } + }, + "is-npm": { + "version": "1.0.0", + "resolved": "http://registry.npm.taobao.org/is-npm/download/is-npm-1.0.0.tgz", + "integrity": "sha1-8vtjpl5JBbQGyGBydloaTceTufQ=", + "dev": true + }, "is-number": { "version": "3.0.0", "resolved": "http://registry.npm.taobao.org/is-number/download/is-number-3.0.0.tgz", @@ -5163,6 +5401,12 @@ } } }, + "is-obj": { + "version": "1.0.1", + "resolved": "http://registry.npm.taobao.org/is-obj/download/is-obj-1.0.1.tgz", + "integrity": "sha1-PkcprB9f3gJc19g6iW2rn09n2w8=", + "dev": true + }, "is-path-cwd": { "version": "1.0.0", "resolved": "http://registry.npm.taobao.org/is-path-cwd/download/is-path-cwd-1.0.0.tgz", @@ -5202,6 +5446,12 @@ "integrity": "sha1-eaKp7OfwlugPNtKy87wWwf9L8/o=", "dev": true }, + "is-redirect": { + "version": "1.0.0", + "resolved": "http://registry.npm.taobao.org/is-redirect/download/is-redirect-1.0.0.tgz", + "integrity": "sha1-HQPd7VO9jbDzDCbk+V02/HyH3CQ=", + "dev": true + }, "is-regex": { "version": "1.0.4", "resolved": "http://registry.npm.taobao.org/is-regex/download/is-regex-1.0.4.tgz", @@ -5211,6 +5461,12 @@ "has": "^1.0.1" } }, + "is-retry-allowed": { + "version": "1.1.0", + "resolved": "http://registry.npm.taobao.org/is-retry-allowed/download/is-retry-allowed-1.1.0.tgz", + "integrity": "sha1-EaBgVotnM5REAz0BJaYaINVk+zQ=", + "dev": true + }, "is-stream": { "version": "1.1.0", "resolved": "http://registry.npm.taobao.org/is-stream/download/is-stream-1.1.0.tgz", @@ -5370,6 +5626,15 @@ "integrity": "sha1-ARRrNqYhjmTljzqNZt5df8b20FE=", "dev": true }, + "latest-version": { + "version": "3.1.0", + "resolved": "http://registry.npm.taobao.org/latest-version/download/latest-version-3.1.0.tgz", + "integrity": "sha1-ogU4P+oyKzO1rjsYq+4NwvNW7hU=", + "dev": true, + "requires": { + "package-json": "^4.0.0" + } + }, "lcid": { "version": "2.0.0", "resolved": "http://registry.npm.taobao.org/lcid/download/lcid-2.0.0.tgz", @@ -5595,6 +5860,12 @@ "integrity": "sha1-miyr0bno4K6ZOkv31YdcOcQujqw=", "dev": true }, + "lowercase-keys": { + "version": "1.0.1", + "resolved": "http://registry.npm.taobao.org/lowercase-keys/download/lowercase-keys-1.0.1.tgz", + "integrity": "sha1-b54wtHCE2XGnyCD/FabFFnt0wm8=", + "dev": true + }, "lru-cache": { "version": "4.1.5", "resolved": "http://registry.npm.taobao.org/lru-cache/download/lru-cache-4.1.5.tgz", @@ -5757,6 +6028,17 @@ "integrity": "sha1-CRP/CxIdtE71hIJCw4u7NdRMq94=", "dev": true }, + "mini-css-extract-plugin": { + "version": "0.5.0", + "resolved": "http://registry.npm.taobao.org/mini-css-extract-plugin/download/mini-css-extract-plugin-0.5.0.tgz", + "integrity": "sha1-rABZsCuWklFaY3EVsMyf7To1x7A=", + "dev": true, + "requires": { + "loader-utils": "^1.1.0", + "schema-utils": "^1.0.0", + "webpack-sources": "^1.1.0" + } + }, "minimalistic-assert": { "version": "1.0.1", "resolved": "http://registry.npm.taobao.org/minimalistic-assert/download/minimalistic-assert-1.0.1.tgz", @@ -5993,6 +6275,59 @@ "semver": "^5.3.0" } }, + "nodemon": { + "version": "1.18.10", + "resolved": "http://registry.npm.taobao.org/nodemon/download/nodemon-1.18.10.tgz", + "integrity": "sha1-O6Y/ZOtMKDzz5PdfMIF+nU85Ov4=", + "dev": true, + "requires": { + "chokidar": "^2.1.0", + "debug": "^3.1.0", + "ignore-by-default": "^1.0.1", + "minimatch": "^3.0.4", + "pstree.remy": "^1.1.6", + "semver": "^5.5.0", + "supports-color": "^5.2.0", + "touch": "^3.1.0", + "undefsafe": "^2.0.2", + "update-notifier": "^2.5.0" + }, + "dependencies": { + "debug": { + "version": "3.2.6", + "resolved": "http://registry.npm.taobao.org/debug/download/debug-3.2.6.tgz", + "integrity": "sha1-6D0X3hbYp++3cX7b5fsQE17uYps=", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "ms": { + "version": "2.1.1", + "resolved": "http://registry.npm.taobao.org/ms/download/ms-2.1.1.tgz", + "integrity": "sha1-MKWGTrPrsKZvLr5tcnrwagnYbgo=", + "dev": true + }, + "supports-color": { + "version": "5.5.0", + "resolved": "http://registry.npm.taobao.org/supports-color/download/supports-color-5.5.0.tgz", + "integrity": "sha1-4uaaRKyHcveKHsCzW2id9lMO/I8=", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "nopt": { + "version": "1.0.10", + "resolved": "http://registry.npm.taobao.org/nopt/download/nopt-1.0.10.tgz", + "integrity": "sha1-bd0hvSoxQXuScn3Vhfim83YI6+4=", + "dev": true, + "requires": { + "abbrev": "1" + } + }, "normalize-package-data": { "version": "2.5.0", "resolved": "http://registry.npm.taobao.org/normalize-package-data/download/normalize-package-data-2.5.0.tgz", @@ -6286,6 +6621,18 @@ "integrity": "sha1-waDxAw6X3gGLsscYkp0q9ZRj5QU=", "dev": true }, + "package-json": { + "version": "4.0.1", + "resolved": "http://registry.npm.taobao.org/package-json/download/package-json-4.0.1.tgz", + "integrity": "sha1-iGmgQBJTZhxMTKPabCEh7VVfXu0=", + "dev": true, + "requires": { + "got": "^6.7.1", + "registry-auth-token": "^3.0.1", + "registry-url": "^3.0.3", + "semver": "^5.1.0" + } + }, "pako": { "version": "1.0.10", "resolved": "http://registry.npm.taobao.org/pako/download/pako-1.0.10.tgz", @@ -6635,6 +6982,12 @@ "integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=", "dev": true }, + "prepend-http": { + "version": "1.0.4", + "resolved": "http://registry.npm.taobao.org/prepend-http/download/prepend-http-1.0.4.tgz", + "integrity": "sha1-1PRWKwzjaW5BrFLQ4ALlemNdxtw=", + "dev": true + }, "prettier": { "version": "1.16.3", "resolved": "http://registry.npm.taobao.org/prettier/download/prettier-1.16.3.tgz", @@ -6719,6 +7072,12 @@ "dev": true, "optional": true }, + "pstree.remy": { + "version": "1.1.6", + "resolved": "http://registry.npm.taobao.org/pstree.remy/download/pstree.remy-1.1.6.tgz", + "integrity": "sha1-c6VarZ4tlYFJJxMfv03Bti0ln0c=", + "dev": true + }, "public-encrypt": { "version": "4.0.3", "resolved": "http://registry.npm.taobao.org/public-encrypt/download/public-encrypt-4.0.3.tgz", @@ -6830,6 +7189,18 @@ "unpipe": "1.0.0" } }, + "rc": { + "version": "1.2.8", + "resolved": "http://registry.npm.taobao.org/rc/download/rc-1.2.8.tgz", + "integrity": "sha1-zZJL9SAKB1uDwYjNa54hG3/A0+0=", + "dev": true, + "requires": { + "deep-extend": "^0.6.0", + "ini": "~1.3.0", + "minimist": "^1.2.0", + "strip-json-comments": "~2.0.1" + } + }, "read-pkg": { "version": "2.0.0", "resolved": "http://registry.npm.taobao.org/read-pkg/download/read-pkg-2.0.0.tgz", @@ -6972,6 +7343,25 @@ "regjsparser": "^0.1.4" } }, + "registry-auth-token": { + "version": "3.4.0", + "resolved": "http://registry.npm.taobao.org/registry-auth-token/download/registry-auth-token-3.4.0.tgz", + "integrity": "sha1-10RoFUM/XV7WQxzV3KIQSPZrOX4=", + "dev": true, + "requires": { + "rc": "^1.1.6", + "safe-buffer": "^5.0.1" + } + }, + "registry-url": { + "version": "3.1.0", + "resolved": "http://registry.npm.taobao.org/registry-url/download/registry-url-3.1.0.tgz", + "integrity": "sha1-PU74cPc93h138M+aOBQyRE4XSUI=", + "dev": true, + "requires": { + "rc": "^1.0.1" + } + }, "regjsgen": { "version": "0.2.0", "resolved": "http://registry.npm.taobao.org/regjsgen/download/regjsgen-0.2.0.tgz", @@ -7275,6 +7665,15 @@ "integrity": "sha1-De4hahyUGrN+nvsXiPavxf9VN/w=", "dev": true }, + "semver-diff": { + "version": "2.1.0", + "resolved": "http://registry.npm.taobao.org/semver-diff/download/semver-diff-2.1.0.tgz", + "integrity": "sha1-S7uEN8jTfksM8aaP1ybsbWRdbTY=", + "dev": true, + "requires": { + "semver": "^5.0.3" + } + }, "send": { "version": "0.16.2", "resolved": "http://registry.npm.taobao.org/send/download/send-0.16.2.tgz", @@ -7625,6 +8024,12 @@ "integrity": "sha1-PpNdfd1zYxuXZZlW1VEo6HtQhKM=", "dev": true }, + "spawn-command": { + "version": "0.0.2-1", + "resolved": "http://registry.npm.taobao.org/spawn-command/download/spawn-command-0.0.2-1.tgz", + "integrity": "sha1-YvXpRmmBwbeW3Fkpk34RycaSG9A=", + "dev": true + }, "spdx-correct": { "version": "3.1.0", "resolved": "http://registry.npm.taobao.org/spdx-correct/download/spdx-correct-3.1.0.tgz", @@ -7947,6 +8352,49 @@ "integrity": "sha1-TSl5I8WnKkI2DeKrUtrfquwAAY4=", "dev": true }, + "term-size": { + "version": "1.2.0", + "resolved": "http://registry.npm.taobao.org/term-size/download/term-size-1.2.0.tgz", + "integrity": "sha1-RYuDiH8oj8Vtb/+/rSYuJmOO+mk=", + "dev": true, + "requires": { + "execa": "^0.7.0" + }, + "dependencies": { + "cross-spawn": { + "version": "5.1.0", + "resolved": "http://registry.npm.taobao.org/cross-spawn/download/cross-spawn-5.1.0.tgz", + "integrity": "sha1-6L0O/uWPz/b4+UUQoKVUu/ojVEk=", + "dev": true, + "requires": { + "lru-cache": "^4.0.1", + "shebang-command": "^1.2.0", + "which": "^1.2.9" + } + }, + "execa": { + "version": "0.7.0", + "resolved": "http://registry.npm.taobao.org/execa/download/execa-0.7.0.tgz", + "integrity": "sha1-lEvs00zEHuMqY6n68nrVpl/Fl3c=", + "dev": true, + "requires": { + "cross-spawn": "^5.0.1", + "get-stream": "^3.0.0", + "is-stream": "^1.1.0", + "npm-run-path": "^2.0.0", + "p-finally": "^1.0.0", + "signal-exit": "^3.0.0", + "strip-eof": "^1.0.0" + } + }, + "get-stream": { + "version": "3.0.0", + "resolved": "http://registry.npm.taobao.org/get-stream/download/get-stream-3.0.0.tgz", + "integrity": "sha1-jpQ9E1jcN1VQVOy+LtsFqhdO3hQ=", + "dev": true + } + } + }, "terser": { "version": "3.17.0", "resolved": "http://registry.npm.taobao.org/terser/download/terser-3.17.0.tgz", @@ -8002,6 +8450,12 @@ "integrity": "sha1-9d9zJFNAewkZHa5z4qjMc/OBqCY=", "dev": true }, + "timed-out": { + "version": "4.0.1", + "resolved": "http://registry.npm.taobao.org/timed-out/download/timed-out-4.0.1.tgz", + "integrity": "sha1-8y6srFoXW+ol1/q1Zas+2HQe9W8=", + "dev": true + }, "timers-browserify": { "version": "2.0.10", "resolved": "http://registry.npm.taobao.org/timers-browserify/download/timers-browserify-2.0.10.tgz", @@ -8080,6 +8534,15 @@ "integrity": "sha1-LmhELZ9k7HILjMieZEOsbKqVACk=", "dev": true }, + "touch": { + "version": "3.1.0", + "resolved": "http://registry.npm.taobao.org/touch/download/touch-3.1.0.tgz", + "integrity": "sha1-/jZfX3XsntTlaCXgu3bSSrdK+Ds=", + "dev": true, + "requires": { + "nopt": "~1.0.10" + } + }, "tough-cookie": { "version": "2.4.3", "resolved": "http://registry.npm.taobao.org/tough-cookie/download/tough-cookie-2.4.3.tgz", @@ -8100,6 +8563,12 @@ } } }, + "tree-kill": { + "version": "1.2.1", + "resolved": "http://registry.npm.taobao.org/tree-kill/download/tree-kill-1.2.1.tgz", + "integrity": "sha1-U5jzdOLykrncx7LnHjClw7tsdDo=", + "dev": true + }, "trim-right": { "version": "1.0.1", "resolved": "http://registry.npm.taobao.org/trim-right/download/trim-right-1.0.1.tgz", @@ -8168,6 +8637,15 @@ "source-map": "~0.6.1" } }, + "undefsafe": { + "version": "2.0.2", + "resolved": "http://registry.npm.taobao.org/undefsafe/download/undefsafe-2.0.2.tgz", + "integrity": "sha1-Il9rngM3Zj4Njnz9aG/Cg2zKznY=", + "dev": true, + "requires": { + "debug": "^2.2.0" + } + }, "union-value": { "version": "1.0.0", "resolved": "http://registry.npm.taobao.org/union-value/download/union-value-1.0.0.tgz", @@ -8227,6 +8705,15 @@ "imurmurhash": "^0.1.4" } }, + "unique-string": { + "version": "1.0.0", + "resolved": "http://registry.npm.taobao.org/unique-string/download/unique-string-1.0.0.tgz", + "integrity": "sha1-nhBXzKhRq7kzmPizOuGHuZyuwRo=", + "dev": true, + "requires": { + "crypto-random-string": "^1.0.0" + } + }, "unpipe": { "version": "1.0.0", "resolved": "http://registry.npm.taobao.org/unpipe/download/unpipe-1.0.0.tgz", @@ -8272,12 +8759,53 @@ } } }, + "unzip-response": { + "version": "2.0.1", + "resolved": "http://registry.npm.taobao.org/unzip-response/download/unzip-response-2.0.1.tgz", + "integrity": "sha1-0vD3N9FrBhXnKmk17QQhRXLVb5c=", + "dev": true + }, "upath": { "version": "1.1.2", "resolved": "http://registry.npm.taobao.org/upath/download/upath-1.1.2.tgz", "integrity": "sha1-PbZYYA7a7sy+bbXmhNZ+6MKs0Gg=", "dev": true }, + "update-notifier": { + "version": "2.5.0", + "resolved": "http://registry.npm.taobao.org/update-notifier/download/update-notifier-2.5.0.tgz", + "integrity": "sha1-0HRFk+E/Fh5AassdlAi3LK0Ir/Y=", + "dev": true, + "requires": { + "boxen": "^1.2.1", + "chalk": "^2.0.1", + "configstore": "^3.0.0", + "import-lazy": "^2.1.0", + "is-ci": "^1.0.10", + "is-installed-globally": "^0.1.0", + "is-npm": "^1.0.0", + "latest-version": "^3.0.0", + "semver-diff": "^2.0.0", + "xdg-basedir": "^3.0.0" + }, + "dependencies": { + "ci-info": { + "version": "1.6.0", + "resolved": "http://registry.npm.taobao.org/ci-info/download/ci-info-1.6.0.tgz", + "integrity": "sha1-LKINu5zrMtRSSmgzAzE/AwSx5Jc=", + "dev": true + }, + "is-ci": { + "version": "1.2.1", + "resolved": "http://registry.npm.taobao.org/is-ci/download/is-ci-1.2.1.tgz", + "integrity": "sha1-43ecjuF/zPQoSI9uKBGH8uYyhBw=", + "dev": true, + "requires": { + "ci-info": "^1.5.0" + } + } + } + }, "upper-case": { "version": "1.1.3", "resolved": "http://registry.npm.taobao.org/upper-case/download/upper-case-1.1.3.tgz", @@ -8338,6 +8866,15 @@ "requires-port": "^1.0.0" } }, + "url-parse-lax": { + "version": "1.0.0", + "resolved": "http://registry.npm.taobao.org/url-parse-lax/download/url-parse-lax-1.0.0.tgz", + "integrity": "sha1-evjzA2Rem9eaJy56FKxovAYJ2nM=", + "dev": true, + "requires": { + "prepend-http": "^1.0.1" + } + }, "use": { "version": "3.1.1", "resolved": "http://registry.npm.taobao.org/use/download/use-3.1.1.tgz", @@ -8855,6 +9392,15 @@ "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=", "dev": true }, + "widest-line": { + "version": "2.0.1", + "resolved": "http://registry.npm.taobao.org/widest-line/download/widest-line-2.0.1.tgz", + "integrity": "sha1-dDh2RzDsfvQ4HOTfgvuYpTFCo/w=", + "dev": true, + "requires": { + "string-width": "^2.1.1" + } + }, "wordwrap": { "version": "1.0.0", "resolved": "http://registry.npm.taobao.org/wordwrap/download/wordwrap-1.0.0.tgz", @@ -8932,6 +9478,23 @@ "mkdirp": "^0.5.1" } }, + "write-file-atomic": { + "version": "2.4.2", + "resolved": "http://registry.npm.taobao.org/write-file-atomic/download/write-file-atomic-2.4.2.tgz", + "integrity": "sha1-pxgXBt+6F4VdIhFAqcBuFfzdh7k=", + "dev": true, + "requires": { + "graceful-fs": "^4.1.11", + "imurmurhash": "^0.1.4", + "signal-exit": "^3.0.2" + } + }, + "xdg-basedir": { + "version": "3.0.0", + "resolved": "http://registry.npm.taobao.org/xdg-basedir/download/xdg-basedir-3.0.0.tgz", + "integrity": "sha1-SWsswQnsqNus/i3HK2A8F8WHCtQ=", + "dev": true + }, "xregexp": { "version": "4.0.0", "resolved": "http://registry.npm.taobao.org/xregexp/download/xregexp-4.0.0.tgz", diff --git a/package.json b/package.json index 96c88ff..9c1404b 100644 --- a/package.json +++ b/package.json @@ -11,7 +11,9 @@ "clean": "rimraf dist", "lint": "eslint --ext .js --ext .jsx --ext .vue client/", "lint-fix": "eslint --fix --ext .js --ext .jsx --ext .vue client/", - "dev": "cross-env NODE_ENV=development webpack-dev-server --config build/webpack.config.client.js" + "dev:client": "cross-env NODE_ENV=development webpack-dev-server --config build/webpack.config.client.js", + "dev:server": "nodemon server/server.js", + "dev": "concurrently \"npm run dev:client\" \"npm run dev:server\"" }, "husky": { "hooks": { @@ -39,6 +41,7 @@ "babel-plugin-syntax-jsx": "^6.18.0", "babel-plugin-transform-vue-jsx": "^3.7.0", "babel-preset-env": "^1.7.0", + "concurrently": "^4.1.0", "cross-env": "^5.2.0", "css-loader": "^2.1.1", "eslint": "^5.15.3", @@ -57,6 +60,8 @@ "less": "^3.9.0", "less-loader": "^4.1.0", "memory-fs": "^0.4.1", + "mini-css-extract-plugin": "^0.5.0", + "nodemon": "^1.18.10", "postcss-loader": "^3.0.0", "rimraf": "^2.6.3", "style-loader": "^0.23.1", diff --git a/server/routes/dev-ssr.js b/server/routes/dev-ssr.js index 388ac76..851720b 100644 --- a/server/routes/dev-ssr.js +++ b/server/routes/dev-ssr.js @@ -1,13 +1,14 @@ const router = require('express').Router() + const path = require('path') const fs = require('fs') -const ejs = require('ejs') const axios = require('axios') // memory-fs 这个库跟 fs 几乎一样,唯一区别是它不会把文件写进磁盘上,而是保存在内存中 const MemoryFS = require('memory-fs') const webpack = require('webpack') const VueServerRenderer = require('vue-server-renderer') +const serverRender = require('./server-render') const serverConfig = require('../../build/webpack.config.server') const serverCompiler = webpack(serverConfig) @@ -21,30 +22,42 @@ serverCompiler.watch({}, (err, stats) => { } stats = stats.toJson() // 如果存在 eslint 的错误,它不是放在 err 中,而是 toJson 中 - stats.hasErrors.forEach(err => console.log(err)) - stats.hasWarnings.forEach(warning => console.log(warning)) + stats.errors.forEach(err => console.log(err)) + stats.warnings.forEach(warning => console.log(warning)) const bundlePath = path.join( serverConfig.output.path, 'vue-ssr-server-bundle.json', // 这是默认的文件名 ) bundle = JSON.parse(mfs.readFileSync(bundlePath, 'utf-8')) + console.log('new bundle generated') }) -const handleSSR = async (req, res, next) => { - // 什么时候不存在,就是服务刚启动的时候,webpack 才开始打包,此时 bundle 不存在 - if (!bundle) { - res.send('先等会,别着急 ...') - } +const handleSSR = async (req, res) => { + try { + // 什么时候不存在,就是服务刚启动的时候,webpack 才开始打包,此时 bundle 不存在 + if (!bundle) { + res.send('先等会,别着急 ...') + } - const clientManifestResp = await axios.get( - 'http://127.0.0.1:8000/vue-ssr-client-manifest.json' - ) - const clientManifest = clientManifestResp.data + const clientManifestResp = await axios.get( + 'http://127.0.0.1:8000/public/vue-ssr-client-manifest.json' + ) + const clientManifest = clientManifestResp.data + + const template = fs.readFileSync(path.join(__dirname, '../server-template.ejs'), 'utf-8') + const renderer = VueServerRenderer.createBundleRenderer(bundle, { + inject: false, + clientManifest, + }) - const template = fs.readFileSync(path.join('../server-template.ejs')) - const renderer = VueServerRenderer.createBundleRenderer(bundle, { - inject: false, - clientManifest, - }) + await serverRender(req, res, renderer, template) + } catch (err) { + console.log('--------------- err in dev-ssr ---------------') + console.log(err) + } } + +router.get('*', handleSSR) + +module.exports = router diff --git a/server/routes/server-render.js b/server/routes/server-render.js new file mode 100644 index 0000000..8ba54a1 --- /dev/null +++ b/server/routes/server-render.js @@ -0,0 +1,20 @@ +const ejs = require('ejs') + +module.exports = async (req, res, renderer, template) => { + res.setHeader('Content-Type', 'text/html') + const context = { url: req.path } + try { + // renderToString 会给 context 加入很多属性,例如 renderStyles + const appString = await renderer.renderToString(context) + const html = ejs.render(template, { + appString, + style: context.renderStyles(), + scripts: context.renderScripts(), + }) + res.send(html) + } catch (err) { + console.log('--------------- err in server-render ---------------') + console.error(err) + throw err + } +} diff --git a/server/server-template.ejs b/server/server-template.ejs index a148fe6..7f5e958 100644 --- a/server/server-template.ejs +++ b/server/server-template.ejs @@ -4,9 +4,14 @@ -