commit
bad07e32f2
23 changed files with 8419 additions and 0 deletions
@ -0,0 +1,8 @@ |
|||||
|
{ |
||||
|
"presets": [ |
||||
|
["@babel/preset-env", { "modules": false }] |
||||
|
], |
||||
|
"plugins": [ |
||||
|
"syntax-dynamic-import" |
||||
|
] |
||||
|
} |
@ -0,0 +1 @@ |
|||||
|
node_modules |
@ -0,0 +1,95 @@ |
|||||
|
const fs = require("fs"); |
||||
|
const path = require("path"); |
||||
|
const MFS = require("memory-fs"); |
||||
|
const webpack = require("webpack"); |
||||
|
const chokidar = require("chokidar"); |
||||
|
const clientConfig = require("./webpack.client.config"); |
||||
|
const serverConfig = require("./webpack.server.config"); |
||||
|
|
||||
|
/** |
||||
|
* 读取文件 |
||||
|
*/ |
||||
|
const readFile = (fs, file) => { |
||||
|
try { |
||||
|
return fs.readFileSync(path.join(clientConfig.output.path, file), "utf-8"); |
||||
|
} catch (e) {} |
||||
|
}; |
||||
|
|
||||
|
module.exports = function setupDevServer(app, templatePath, cb) { |
||||
|
let bundle; |
||||
|
let template; |
||||
|
let clientManifest; |
||||
|
|
||||
|
let ready; |
||||
|
const readyPromise = new Promise((r) => { |
||||
|
ready = r; |
||||
|
}); |
||||
|
const update = () => { |
||||
|
if (bundle && clientManifest) { |
||||
|
ready(); |
||||
|
cb(bundle, { |
||||
|
template, |
||||
|
clientManifest, |
||||
|
}); |
||||
|
} |
||||
|
}; |
||||
|
|
||||
|
// read template from disk and watch
|
||||
|
template = fs.readFileSync(templatePath, "utf-8"); |
||||
|
chokidar.watch(templatePath).on("change", () => { |
||||
|
template = fs.readFileSync(templatePath, "utf-8"); |
||||
|
console.log("index.html template updated."); |
||||
|
update(); |
||||
|
}); |
||||
|
|
||||
|
// modify client config to work with hot middleware
|
||||
|
clientConfig.entry.app = [ |
||||
|
"webpack-hot-middleware/client", |
||||
|
clientConfig.entry.app, |
||||
|
]; |
||||
|
clientConfig.output.filename = "[name].js"; |
||||
|
clientConfig.plugins.push( |
||||
|
new webpack.HotModuleReplacementPlugin(), |
||||
|
new webpack.NoEmitOnErrorsPlugin() |
||||
|
); |
||||
|
|
||||
|
const clientCompiler = webpack(clientConfig); |
||||
|
const devMiddleware = require("webpack-dev-middleware")(clientCompiler, { |
||||
|
publicPath: clientConfig.output.publicPath, |
||||
|
}); |
||||
|
|
||||
|
// dev middleware
|
||||
|
app.use(devMiddleware); |
||||
|
clientCompiler.hooks.done.tap("done", (stats) => { |
||||
|
console.log("编译完成"); |
||||
|
stats = stats.toJson(); |
||||
|
stats.errors.forEach((err) => console.error(err)); |
||||
|
stats.warnings.forEach((err) => console.warn(err)); |
||||
|
if (stats.errors.length) return; |
||||
|
clientManifest = JSON.parse( |
||||
|
readFile(devMiddleware.context.outputFileSystem, "vue-ssr-client-manifest.json") |
||||
|
); |
||||
|
update(); |
||||
|
}); |
||||
|
|
||||
|
// hot middleware
|
||||
|
app.use( |
||||
|
require("webpack-hot-middleware")(clientCompiler, { heartbeat: 5000 }) |
||||
|
); |
||||
|
|
||||
|
// watch and update server renderer
|
||||
|
const serverCompiler = webpack(serverConfig); |
||||
|
const mfs = new MFS(); |
||||
|
serverCompiler.outputFileSystem = mfs; |
||||
|
serverCompiler.watch({}, (err, stats) => { |
||||
|
if (err) throw err; |
||||
|
stats = stats.toJson(); |
||||
|
if (stats.errors.length) return; |
||||
|
|
||||
|
// read bundle generated by vue-ssr-webpack-plugin
|
||||
|
bundle = JSON.parse(readFile(mfs, "vue-ssr-server-bundle.json")); |
||||
|
update(); |
||||
|
}); |
||||
|
|
||||
|
return readyPromise; |
||||
|
}; |
@ -0,0 +1,80 @@ |
|||||
|
const path = require("path"); |
||||
|
|
||||
|
const { VueLoaderPlugin } = require("vue-loader"); |
||||
|
const FriendlyErrorsPlugin = require("friendly-errors-webpack-plugin"); |
||||
|
|
||||
|
module.exports = { |
||||
|
output: { |
||||
|
path: path.resolve(__dirname, "../dist"), |
||||
|
publicPath: "/dist/", |
||||
|
filename: "[name].[chunkhash].js", |
||||
|
}, |
||||
|
resolve: { |
||||
|
alias: { |
||||
|
public: path.resolve(__dirname, "../public"), |
||||
|
}, |
||||
|
}, |
||||
|
devtool: "#cheap-module-source-map", |
||||
|
module: { |
||||
|
noParse: /es6-promise\.js$/, // avoid webpack shimming process
|
||||
|
rules: [ |
||||
|
{ |
||||
|
test: /\.vue$/, |
||||
|
loader: "vue-loader", |
||||
|
options: { |
||||
|
compilerOptions: { |
||||
|
preserveWhitespace: false, |
||||
|
}, |
||||
|
}, |
||||
|
}, |
||||
|
{ |
||||
|
test: /\.js$/, |
||||
|
loader: "babel-loader", |
||||
|
exclude: /node_modules/, |
||||
|
}, |
||||
|
{ |
||||
|
test: /\.(png|jpg|gif|svg)$/, |
||||
|
loader: "url-loader", |
||||
|
options: { |
||||
|
limit: 10000, |
||||
|
name: "[name].[ext]?[hash]", |
||||
|
}, |
||||
|
}, |
||||
|
{ |
||||
|
test: /\.css?$/, |
||||
|
use: [ |
||||
|
'vue-style-loader', |
||||
|
'css-loader' |
||||
|
], |
||||
|
}, |
||||
|
{ |
||||
|
test: /\.less?$/, |
||||
|
use: [ |
||||
|
'vue-style-loader', |
||||
|
'css-loader', |
||||
|
'less-loader' |
||||
|
], |
||||
|
}, |
||||
|
{ |
||||
|
test: /\.scss?$/, |
||||
|
use: [ |
||||
|
'vue-style-loader', |
||||
|
'css-loader', |
||||
|
{ |
||||
|
loader: 'sass-loader', |
||||
|
options: { |
||||
|
// 你也可以从一个文件读取,例如 `variables.scss`
|
||||
|
// 如果 sass-loader 版本 = 8,这里使用 `prependData` 字段
|
||||
|
// 如果 sass-loader 版本 < 8,这里使用 `data` 字段
|
||||
|
additionalData: `$color: red;` |
||||
|
} |
||||
|
} |
||||
|
], |
||||
|
}, |
||||
|
], |
||||
|
}, |
||||
|
plugins: [new VueLoaderPlugin(), new FriendlyErrorsPlugin()], |
||||
|
performance: { |
||||
|
hints: false, |
||||
|
}, |
||||
|
}; |
@ -0,0 +1,29 @@ |
|||||
|
const webpack = require("webpack"); |
||||
|
const { merge } = require('webpack-merge'); |
||||
|
const base = require("./webpack.base.config"); |
||||
|
const VueSSRClientPlugin = require("vue-server-renderer/client-plugin"); |
||||
|
|
||||
|
module.exports = merge(base, { |
||||
|
mode: process.env.NODE_ENV || "development", |
||||
|
entry: { |
||||
|
app: "./src/entry-client.js", |
||||
|
}, |
||||
|
plugins: [ |
||||
|
// strip dev-only code in Vue source
|
||||
|
new webpack.DefinePlugin({ |
||||
|
"process.env.NODE_ENV": JSON.stringify( |
||||
|
process.env.NODE_ENV || "development" |
||||
|
), |
||||
|
"process.env.VUE_ENV": '"client"', |
||||
|
}), |
||||
|
new VueSSRClientPlugin(), |
||||
|
], |
||||
|
|
||||
|
optimization: { |
||||
|
splitChunks: { |
||||
|
chunks: "all", |
||||
|
minChunks: 1, |
||||
|
name: "manifest" |
||||
|
}, |
||||
|
}, |
||||
|
}); |
@ -0,0 +1,41 @@ |
|||||
|
const webpack = require('webpack') |
||||
|
const { merge } = require('webpack-merge'); |
||||
|
const base = require('./webpack.base.config') |
||||
|
const nodeExternals = require('webpack-node-externals') |
||||
|
const VueSSRServerPlugin = require('vue-server-renderer/server-plugin') |
||||
|
|
||||
|
module.exports = merge(base, { |
||||
|
mode: process.env.NODE_ENV || "development", |
||||
|
// 这允许 webpack 以 Node 适用方式(Node-appropriate fashion)处理动态导入(dynamic import),
|
||||
|
// 并且还会在编译 Vue 组件时,
|
||||
|
// 告知 `vue-loader` 输送面向服务器代码(server-oriented code)。
|
||||
|
target: 'node', |
||||
|
// 对 bundle renderer 提供 source map 支持
|
||||
|
devtool: 'source-map', |
||||
|
// 将 entry 指向应用程序的 server entry 文件
|
||||
|
entry: './src/entry-server.js', |
||||
|
// 此处告知 server bundle 使用 Node 风格导出模块(Node-style exports)
|
||||
|
output: { |
||||
|
filename: 'server-bundle.js', |
||||
|
libraryTarget: 'commonjs2' |
||||
|
}, |
||||
|
// https://webpack.js.org/configuration/externals/#function
|
||||
|
// https://github.com/liady/webpack-node-externals
|
||||
|
// 外置化应用程序依赖模块。可以使服务器构建速度更快,
|
||||
|
// 并生成较小的 bundle 文件。
|
||||
|
externals: nodeExternals({ |
||||
|
// do not externalize CSS files in case we need to import it from a dep
|
||||
|
allowlist : [/\.css$/, /\?vue&type=style/] |
||||
|
}), |
||||
|
|
||||
|
// 这是将服务器的整个输出
|
||||
|
// 构建为单个 JSON 文件的插件。
|
||||
|
// 默认文件名为 `vue-ssr-server-bundle.json`
|
||||
|
plugins: [ |
||||
|
new webpack.DefinePlugin({ |
||||
|
'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV || 'development'), |
||||
|
'process.env.VUE_ENV': '"server"' |
||||
|
}), |
||||
|
new VueSSRServerPlugin() |
||||
|
] |
||||
|
}) |
@ -0,0 +1,2 @@ |
|||||
|
!function(e){function t(t){for(var r,a,c=t[0],l=t[1],i=t[2],s=0,f=[];s<c.length;s++)a=c[s],Object.prototype.hasOwnProperty.call(o,a)&&o[a]&&f.push(o[a][0]),o[a]=0;for(r in l)Object.prototype.hasOwnProperty.call(l,r)&&(e[r]=l[r]);for(p&&p(t);f.length;)f.shift()();return u.push.apply(u,i||[]),n()}function n(){for(var e,t=0;t<u.length;t++){for(var n=u[t],r=!0,c=1;c<n.length;c++){var l=n[c];0!==o[l]&&(r=!1)}r&&(u.splice(t--,1),e=a(a.s=n[0]))}return e}var r={},o={0:0},u=[];function a(t){if(r[t])return r[t].exports;var n=r[t]={i:t,l:!1,exports:{}};return e[t].call(n.exports,n,n.exports,a),n.l=!0,n.exports}a.m=e,a.c=r,a.d=function(e,t,n){a.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:n})},a.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},a.t=function(e,t){if(1&t&&(e=a(e)),8&t)return e;if(4&t&&"object"==typeof e&&e&&e.__esModule)return e;var n=Object.create(null);if(a.r(n),Object.defineProperty(n,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var r in e)a.d(n,r,function(t){return e[t]}.bind(null,r));return n},a.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return a.d(t,"a",t),t},a.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},a.p="/dist/";var c=window.webpackJsonp=window.webpackJsonp||[],l=c.push.bind(c);c.push=t,c=c.slice();for(var i=0;i<c.length;i++)t(c[i]);var p=l;u.push([11,1]),n()}([,function(e,t,n){var r=n(7);r.__esModule&&(r=r.default),"string"==typeof r&&(r=[[e.i,r,""]]),r.locals&&(e.exports=r.locals);(0,n(12).default)("895d7142",r,!0,{})},,,,,function(e,t,n){"use strict";n(1)},function(e,t,n){"use strict";n.r(t);var r=n(2),o=n.n(r),u=n(3),a=n.n(u)()(o.a);a.push([e.i,"*[data-v-d2943b3a] {\n color: rebeccapurple;\n}\n","",{version:3,sources:["webpack://./src/App.vue"],names:[],mappings:"AAAA;EACE,oBAAoB;AACtB",sourcesContent:["*[data-v-d2943b3a] {\n color: rebeccapurple;\n}\n"],sourceRoot:""}]),t.default=a},,,,function(e,t,n){"use strict";n.r(t);var r=n(5),o={data:function(){return{}},mounted:function(){console.log("asd")}},u=(n(6),n(4)),a=Object(u.a)(o,(function(){var e=this.$createElement;return(this._self._c||e)("div",{attrs:{id:"app"}},[this._v("\n hello world!!!\n")])}),[],!1,null,"d2943b3a",null).exports;({app:new r.a({render:function(e){return e(a)}})}).app.$mount("#app")}]); |
||||
|
//# sourceMappingURL=app.861ecf6e9a9de782243f.js.map
|
@ -0,0 +1 @@ |
|||||
|
{"version":3,"file":"app.861ecf6e9a9de782243f.js","sources":["webpack:///app.861ecf6e9a9de782243f.js"],"mappings":"AAAA","sourceRoot":""} |
File diff suppressed because one or more lines are too long
@ -0,0 +1 @@ |
|||||
|
{"version":3,"file":"manifest.d2d3e546256e88a4c784.js","sources":["webpack:///manifest.d2d3e546256e88a4c784.js"],"mappings":"AAAA;;;;;;AA+PA","sourceRoot":""} |
@ -0,0 +1,68 @@ |
|||||
|
{ |
||||
|
"publicPath": "/dist/", |
||||
|
"all": [ |
||||
|
"app.861ecf6e9a9de782243f.js", |
||||
|
"app.861ecf6e9a9de782243f.js.map", |
||||
|
"manifest.d2d3e546256e88a4c784.js", |
||||
|
"manifest.d2d3e546256e88a4c784.js.map" |
||||
|
], |
||||
|
"initial": [ |
||||
|
"manifest.d2d3e546256e88a4c784.js", |
||||
|
"app.861ecf6e9a9de782243f.js" |
||||
|
], |
||||
|
"async": [], |
||||
|
"modules": { |
||||
|
"336a7835": [ |
||||
|
2, |
||||
|
3 |
||||
|
], |
||||
|
"165589ea": [ |
||||
|
0, |
||||
|
1 |
||||
|
], |
||||
|
"98dff2f8": [ |
||||
|
2, |
||||
|
3 |
||||
|
], |
||||
|
"08194f7f": [ |
||||
|
2, |
||||
|
3 |
||||
|
], |
||||
|
"b1fe4c2a": [ |
||||
|
2, |
||||
|
3 |
||||
|
], |
||||
|
"9dec38a2": [ |
||||
|
2, |
||||
|
3 |
||||
|
], |
||||
|
"1ab9b00f": [ |
||||
|
0, |
||||
|
1 |
||||
|
], |
||||
|
"388fc7b2": [ |
||||
|
0, |
||||
|
1 |
||||
|
], |
||||
|
"4dc8caee": [ |
||||
|
2, |
||||
|
3 |
||||
|
], |
||||
|
"4544517c": [ |
||||
|
2, |
||||
|
3 |
||||
|
], |
||||
|
"fe4c40fa": [ |
||||
|
2, |
||||
|
3 |
||||
|
], |
||||
|
"5163fcfe": [ |
||||
|
0, |
||||
|
1 |
||||
|
], |
||||
|
"3ae59d9f": [ |
||||
|
2, |
||||
|
3 |
||||
|
] |
||||
|
} |
||||
|
} |
File diff suppressed because one or more lines are too long
@ -0,0 +1,37 @@ |
|||||
|
{ |
||||
|
"name": "Vue Hackernews 2.0", |
||||
|
"short_name": "Vue HN", |
||||
|
"icons": [{ |
||||
|
"src": "/public/logo-120.png", |
||||
|
"sizes": "120x120", |
||||
|
"type": "image/png" |
||||
|
}, { |
||||
|
"src": "/public/logo-144.png", |
||||
|
"sizes": "144x144", |
||||
|
"type": "image/png" |
||||
|
}, { |
||||
|
"src": "/public/logo-152.png", |
||||
|
"sizes": "152x152", |
||||
|
"type": "image/png" |
||||
|
}, { |
||||
|
"src": "/public/logo-192.png", |
||||
|
"sizes": "192x192", |
||||
|
"type": "image/png" |
||||
|
}, { |
||||
|
"src": "/public/logo-256.png", |
||||
|
"sizes": "256x256", |
||||
|
"type": "image/png" |
||||
|
}, { |
||||
|
"src": "/public/logo-384.png", |
||||
|
"sizes": "384x384", |
||||
|
"type": "image/png" |
||||
|
}, { |
||||
|
"src": "/public/logo-512.png", |
||||
|
"sizes": "512x512", |
||||
|
"type": "image/png" |
||||
|
}], |
||||
|
"start_url": "/", |
||||
|
"background_color": "#f2f3f5", |
||||
|
"display": "standalone", |
||||
|
"theme_color": "#f60" |
||||
|
} |
File diff suppressed because it is too large
@ -0,0 +1,51 @@ |
|||||
|
{ |
||||
|
"name": "ssr-demo", |
||||
|
"version": "1.0.0", |
||||
|
"description": "", |
||||
|
"main": "index.js", |
||||
|
"scripts": { |
||||
|
"start": "node server.js", |
||||
|
"build": "rimraf dist && npm run build:client && npm run build:server", |
||||
|
"build:client": "cross-env NODE_ENV=production webpack --config build/webpack.client.config.js --progress", |
||||
|
"build:server": "cross-env NODE_ENV=production webpack --config build/webpack.server.config.js --progress", |
||||
|
// npm install 之后会自动运行 |
||||
|
"postinstall": "npm run build" |
||||
|
}, |
||||
|
"keywords": [], |
||||
|
"author": "", |
||||
|
"license": "ISC", |
||||
|
"dependencies": { |
||||
|
"express": "^4.17.1", |
||||
|
"less-loader": "^5.0.0", |
||||
|
"vue": "^2.6.13" |
||||
|
}, |
||||
|
"devDependencies": { |
||||
|
"@babel/core": "^7.14.3", |
||||
|
"@babel/preset-env": "^7.14.4", |
||||
|
"babel-loader": "^8.2.2", |
||||
|
"babel-plugin-syntax-dynamic-import": "^6.18.0", |
||||
|
"chokidar": "^3.5.1", |
||||
|
"compression": "^1.7.4", |
||||
|
"cross-env": "^7.0.3", |
||||
|
"css-loader": "^5.2.6", |
||||
|
"friendly-errors-webpack-plugin": "^1.7.0", |
||||
|
"less": "^4.1.1", |
||||
|
"lru-cache": "^6.0.0", |
||||
|
"memory-fs": "^0.5.0", |
||||
|
"node-sass": "^6.0.0", |
||||
|
"rimraf": "^3.0.2", |
||||
|
"route-cache": "^0.4.5", |
||||
|
"sass-loader": "^12.0.0", |
||||
|
"url-loader": "^4.1.1", |
||||
|
"vue-loader": "^15.9.7", |
||||
|
"vue-server-renderer": "^2.6.13", |
||||
|
"vue-style-loader": "^4.1.3", |
||||
|
"vue-template-compiler": "^2.6.13", |
||||
|
"webpack": "^4.46.0", |
||||
|
"webpack-cli": "^4.7.0", |
||||
|
"webpack-dev-middleware": "^5.0.0", |
||||
|
"webpack-hot-middleware": "^2.25.0", |
||||
|
"webpack-merge": "^5.7.3", |
||||
|
"webpack-node-externals": "^3.0.0" |
||||
|
} |
||||
|
} |
After Width: | Height: | Size: 3.0 KiB |
@ -0,0 +1 @@ |
|||||
|
## [文档](https://ssr.vuejs.org/zh/#%E4%BB%80%E4%B9%88%E6%98%AF%E6%9C%8D%E5%8A%A1%E5%99%A8%E7%AB%AF%E6%B8%B2%E6%9F%93-ssr-%EF%BC%9F) |
@ -0,0 +1,103 @@ |
|||||
|
// server.js
|
||||
|
const express = require('express') |
||||
|
const path = require('path') |
||||
|
const LRU = require('lru-cache') |
||||
|
const microcache = require('route-cache') |
||||
|
const compression = require('compression') |
||||
|
const resolve = file => path.resolve(__dirname, file) |
||||
|
const { createBundleRenderer } = require('vue-server-renderer') |
||||
|
|
||||
|
const useMicroCache = process.env.MICRO_CACHE !== 'false' |
||||
|
|
||||
|
const serverInfo = |
||||
|
`express/${require('express/package.json').version} ` + |
||||
|
`vue-server-renderer/${require('vue-server-renderer/package.json').version}` |
||||
|
|
||||
|
const app = express() |
||||
|
|
||||
|
function createRenderer (bundle, options) { |
||||
|
// https://github.com/vuejs/vue/blob/dev/packages/vue-server-renderer/README.md#why-use-bundlerenderer
|
||||
|
return createBundleRenderer(bundle, Object.assign(options, { |
||||
|
// for component caching
|
||||
|
// cache: LRU({
|
||||
|
// max: 1000,
|
||||
|
// maxAge: 1000 * 60 * 15
|
||||
|
// }),
|
||||
|
// this is only needed when vue-server-renderer is npm-linked
|
||||
|
basedir: resolve('./dist'), |
||||
|
// recommended for performance
|
||||
|
runInNewContext: false |
||||
|
})) |
||||
|
} |
||||
|
|
||||
|
|
||||
|
let renderer |
||||
|
let readyPromise |
||||
|
const templatePath = resolve('./src/index.template.html') |
||||
|
|
||||
|
readyPromise = require('./build/setup-dev-server')( |
||||
|
app, |
||||
|
templatePath, |
||||
|
(bundle, options) => { |
||||
|
renderer = createRenderer(bundle, options) |
||||
|
} |
||||
|
) |
||||
|
|
||||
|
const serve = (path, cache) => express.static(resolve(path), { |
||||
|
maxAge: cache ? 1000 * 60 * 60 * 24 * 30 : 0 |
||||
|
}) |
||||
|
|
||||
|
app.use(compression({ threshold: 0 })) |
||||
|
app.use('/dist', serve('./dist', true)) |
||||
|
app.use('/public', serve('./public', true)) |
||||
|
app.use('/manifest.json', serve('./manifest.json', true)) |
||||
|
app.use('/service-worker.js', serve('./dist/service-worker.js')) |
||||
|
app.use(microcache.cacheSeconds(1, req => useMicroCache && req.originalUrl)) |
||||
|
|
||||
|
function render (req, res) { |
||||
|
const s = Date.now() |
||||
|
|
||||
|
res.setHeader("Content-Type", "text/html") |
||||
|
res.setHeader("Server", serverInfo) |
||||
|
|
||||
|
const handleError = err => { |
||||
|
if (err.url) { |
||||
|
res.redirect(err.url) |
||||
|
} else if(err.code === 404) { |
||||
|
res.status(404).send('404 | Page Not Found') |
||||
|
} else { |
||||
|
// Render Error Page or Redirect
|
||||
|
res.status(500).send('500 | Internal Server Error') |
||||
|
console.error(`error during render : ${req.url}`) |
||||
|
console.error(err.stack) |
||||
|
} |
||||
|
} |
||||
|
const context = { |
||||
|
title: 'hsello', |
||||
|
meta: ` |
||||
|
<meta charset="UTF-8"> |
||||
|
<meta http-equiv="X-UA-Compatible" content="IE=edge"> |
||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0"> |
||||
|
<meta name="keyword" content="vue,ssr"> |
||||
|
<meta name="description" content="vue srr demo"> |
||||
|
`,
|
||||
|
url: req.url |
||||
|
} |
||||
|
console.log(renderer); |
||||
|
renderer.renderToString(context, (err, html) => { |
||||
|
if (err) { |
||||
|
return handleError(err) |
||||
|
} |
||||
|
res.send(html) |
||||
|
console.log(`whole request: ${Date.now() - s}ms`) |
||||
|
}) |
||||
|
} |
||||
|
|
||||
|
app.get('*', (req, res) => { |
||||
|
readyPromise.then(() => render(req, res)) |
||||
|
}) |
||||
|
|
||||
|
const port = process.env.PORT || 8080 |
||||
|
app.listen(port, () => { |
||||
|
console.log(`server started at localhost:${port}`) |
||||
|
}) |
@ -0,0 +1,23 @@ |
|||||
|
<template> |
||||
|
<div id="app"> |
||||
|
hello world!!! |
||||
|
</div> |
||||
|
</template> |
||||
|
|
||||
|
<script> |
||||
|
export default { |
||||
|
data(){ |
||||
|
return { |
||||
|
|
||||
|
} |
||||
|
}, |
||||
|
mounted(){ |
||||
|
console.log('asd'); |
||||
|
} |
||||
|
} |
||||
|
</script> |
||||
|
<style scoped lang="less"> |
||||
|
*{ |
||||
|
color: red; |
||||
|
} |
||||
|
</style> |
@ -0,0 +1,12 @@ |
|||||
|
import Vue from 'vue' |
||||
|
import App from './App.vue' |
||||
|
|
||||
|
// 导出一个工厂函数,用于创建新的
|
||||
|
// 应用程序、router 和 store 实例
|
||||
|
export function createApp () { |
||||
|
const app = new Vue({ |
||||
|
// 根实例简单的渲染应用程序组件。
|
||||
|
render: h => h(App) |
||||
|
}) |
||||
|
return { app } |
||||
|
} |
@ -0,0 +1,8 @@ |
|||||
|
import { createApp } from './app' |
||||
|
|
||||
|
// 客户端特定引导逻辑……
|
||||
|
|
||||
|
const { app } = createApp() |
||||
|
|
||||
|
// 这里假定 App.vue 模板中根元素具有 `id="app"`
|
||||
|
app.$mount('#app') |
@ -0,0 +1,6 @@ |
|||||
|
import { createApp } from './app' |
||||
|
|
||||
|
export default context => { |
||||
|
const { app } = createApp() |
||||
|
return app |
||||
|
} |
@ -0,0 +1,16 @@ |
|||||
|
<!DOCTYPE html> |
||||
|
<html lang="en"> |
||||
|
<head> |
||||
|
<title>{{ title }}</title> |
||||
|
{{{ meta }}} |
||||
|
<link rel="manifest" href="/manifest.json"> |
||||
|
<style> |
||||
|
#skip a { position:absolute; left:-10000px; top:auto; width:1px; height:1px; overflow:hidden; } |
||||
|
#skip a:focus { position:static; width:auto; height:auto; } |
||||
|
</style> |
||||
|
</head> |
||||
|
<body> |
||||
|
<div id="skip"><a href="#app">skip to content</a></div> |
||||
|
<!--vue-ssr-outlet--> |
||||
|
</body> |
||||
|
</html> |
Loading…
Reference in new issue