Service Worker主要用于拦截并修改访问和资源请求,细粒度地缓存资源。它运行浏览器在后台,运行环境与普通页面脚本不同,所以不能直接参与页面交互。出于安全考虑,service worker只能运行在HTTPS上,防止被人从中攻击。
4、创建服务端渲染器(server.js)
constfs = require('fs') constpath = require('path') constLRU = require('lru-cache') constexpress = require('express') constcompression = require('compression') constresolve= file => path.resolve(__dirname,file) const{ createBundleRenderer } = require('vue-server-renderer') constisProd = process.env.NODE_ENV ==='production'|| process.env.NODE_ENV ==='beta' constuseMicroCache = process.env.MICRO_CACHE !=='false' constserverInfo = `express/${require('express/package.json').version}`+ `vue-server-renderer/${require('vue-server-renderer/package.json').version}` constapp = express() consttemplate = fs.readFileSync(resolve('./src/index.template.html'),'utf-8') functioncreateRenderer(bundle,options) { returncreateBundleRenderer(bundle,Object.assign(options,{ template, cache: LRU({ max:1000, maxAge:1000*60*15 }), basedir: resolve('./dist'), runInNewContext:false })) } letrenderer letreadyPromise if(isProd) { constbundle = require('./dist/vue-ssr-server-bundle.json') constclientManifest = require('./dist/vue-ssr-client-manifest.json') renderer = createRenderer(bundle,{ clientManifest }) }else{ readyPromise = require('./build/setup-dev-server')(app,(bundle,options) => { renderer = createRenderer(bundle,options) }) } constserve= (path,cache) => express.static(resolve(path),{ maxAge: cache && isProd ?1000*60*60*24*30:0 }) app.use(compression({threshold:0})) app.use('/dist',serve('./dist',true)) app.use('/static',serve('./static',true)) app.use('/service-worker.js',serve('./dist/service-worker.js')) constmicroCache = LRU({ max:100, maxAge:1000 }) constisCacheable= req => useMicroCache functionrender(req,res) { consts = Date.now() res.setHeader("Content-Type","text/html") res.setHeader("Server",serverInfo) consthandleError= err => { if(err.url) { res.redirect(err.url) }else if(err.code ===404) { res.status(404).end('404 | Page Not Found') }else{ // Render Error Page or Redirect res.status(500).end('500 | Internal Server Error') console.error(`error during render :${req.url}`) console.error(err.stack) } } constcacheable = isCacheable(req) if(cacheable) { consthit = microCache.get(req.url) if(hit) { if(!isProd) { console.log(`cache hit!`) } returnres.end(hit) } } constcontext = { title:'Vue DB',// default title url: req.url } renderer.renderToString(context,(err,html) => { if(err) { returnhandleError(err) } res.end(html) if(cacheable) { microCache.set(req.url,html) } if(!isProd) { console.log(`whole request:${Date.now() - s}ms`) } }) } app.get('*',isProd ? render : (req,res) => { readyPromise.then(() => render(req,res)) }) constport = process.env.PORT ||8888 app.listen(port,() => { console.log(`server started at localhost:${port}`) })
5、客户端api文件create-api-client.js
/** * Created by lin on 2017/8/25. */ import axios from 'axios'; let api; axios.defaults.baseURL = process.env.API_URL; axios.defaults.timeout = 10000; axios.interceptors.response.use((res) => { if (res.status >= 200 && res.status < 300) { return res; } return Promise.reject(res); }, (error) => { return Promise.reject({message: '网络异常,请刷新重试', err: error}); }); if (process.__API__) { api = process.__API__; } else { api = { get: function(url) { return new Promise((resolve, reject) => { axios.get(url).then(res => { resolve(res); }).catch((error) => { reject(error); }); }); }, post: function(target, options = {}) { return new Promise((resolve, reject) => { axios.post(target, options).then(res => { resolve(res); }).catch((error) => { reject(error); }); }); } }; } export default api;
6、服务端api文件create-api-server.js
/** * Created by lin on 2017/8/25. */ import axios from 'axios'; let cook = process.__COOKIE__ || ''; let api; axios.defaults.baseURL = 'https://api.douban.com/v2/'; axios.defaults.timeout = 10000; axios.interceptors.response.use((res) => { if (res.status >= 200 && res.status < 300) { return Promise.resolve(res); } return Promise.reject(res); }, (error) => { // 网络异常 return Promise.reject({message: '网络异常,请刷新重试', err: error, type: 1}); }); if (process.__API__) { api = process.__API__; } else { api = { get: function(target) { return new Promise((resolve, reject) => { axios.request({ url: encodeURI(target), method: 'get', headers: { 'Cookie': cook } }).then(res => { resolve(res); }).catch((error) => { reject(error); }); }); }, post: function(target, options = {}) { return new Promise((resolve, reject) => { axios.request({ url: target, method: 'post', headers: { 'Cookie': cook }, params: options }).then(res => { resolve(res); }).catch((error) => { reject(error); }); }); } }; } export default api;
六、那些年遇到的那些坑
问题1、window is not defined
答案1:给用到浏览器对象的地方加if (typeof window !== 'undefined') {},有一些插件里也用到了浏览器对象,在使用的地方也加一个条件判断:
if (typeofwindow !== 'undefined') { Vue.use(VueAnalytics, { id: process.env.UA_TRACKING_ID, router }) }
问题2:用到非Vue系列的插件,如hello.all.js(三方登录的插件),需要用的地方才引用,报的错和问题1一样。
答案2:这个时候不能再用import导入,需要使用require,
let hello
if (typeof window !== 'undefined') { hello = require('hello') }
问题3:引用bootstrap
答案3:将bootstrap.css和bootstrap.js加入webpack.base.config.js的entry中的vendor中
问题6:bootstap需要jquery,此时把jQuery加在vendor中没用。
答案6:给webpack.base.config.js的plugins添加一个插件,如:
newwebpack.ProvidePlugin({ $ : "jquery", jQuery : "jquery", "window.jQuery" :"jquery" })
七、例子
https://github.com/linmoer/ssr-vue这是一个服务端渲的例子