vue服务端渲染 项目改造优化
vue ssr 项目改造优化
这篇文章距离上一篇文章更新接近俩月了,主要记录下遇到的一些问题和解决办法
首先留个链接,上一篇文章:https://www.cnblogs.com/wangyongping/p/10961587.html
这个文章借鉴了 vue-hackernews-2.0-master和vue ssr官网,自行百度下。
问题1
还是element的事情,针对上一篇文章,关于element引入我用的是通过判断是否为浏览器端还是服务端引入,但是这个引入虽然不报错,却会出现好多问题,页面加载闪一下,提示服务端和浏览器端加载不一样,所以需要解决问题,毕竟element现在2.11.0出了很多新功能,真心不错。
按需引入,没错,将element按需引入
首先,安装 babel-plugin-component: npm install babel-plugin-component -D
然后,将 .babelrc 修改为:
{
"presets": [["es2015", { "modules": false }]],
"plugins": [
[
"component",
{
"libraryName": "element-ui",
"styleLibraryName": "theme-chalk"
}
]
]
}
这么引入,在main.js正常按需导入所用插件。
重点来了,我们还要配置 webpack.base.config.js,
const path = require('path'); const ExtractTextPlugin = require('extract-text-webpack-plugin'); const VueLoaderPlugin = require('vue-loader/lib/plugin');////重点!!! const webpack = require('webpack'); const FriendlyErrorsPlugin = require('friendly-errors-webpack-plugin'); const isProd = process.env.NODE_ENV === 'production'; module.exports = { devtool: isProd ? false : '#source-map', output: { path: path.resolve(__dirname, '../dist'), publicPath: '/dist/', filename: '[name].[chunkhash].js' }, resolve: { alias: { 'static': path.resolve(__dirname, '../static'), } }, module: { noParse: /es6-promise\.js$/, rules: [ { test: /\.vue$/, loader: 'vue-loader', options: { extractCSS: isProd } },
//////重点!!!!!!! { test: /\.js$/, loader: 'babel-loader', exclude: /node_modules/, options:{ plugins:['syntax-dynamic-import'] }, }, { test: /\.(png|jpg|gif|svg)$/, loader: 'url-loader', options: { limit: 10000, name: '[name].[ext]?[hash]' } }, { test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/, loader: 'url-loader', query: { limit: 10000, name: 'fonts/[name].[hash:7].[ext]' } },
//////重点!!!!!!!! { test: /\.css$/, use: isProd ? ExtractTextPlugin.extract({ use: 'css-loader', fallback: 'vue-style-loader' }) : ['vue-style-loader', 'css-loader'] }, ] }, plugins: isProd ? [ new VueLoaderPlugin(),/////重点 new webpack.optimize.UglifyJsPlugin({ compress: { warnings: false }, sourceMap: true, parallel: true, }), new webpack.optimize.ModuleConcatenationPlugin(), new ExtractTextPlugin({ filename: 'common.[chunkhash].css' }) ] : [ new VueLoaderPlugin(),/////重点 new FriendlyErrorsPlugin() ], performance: { hints: process.env.NODE_ENV === 'production' ? 'warning' : false, maxAssetSize: 30000000, maxEntrypointSize: 50000000, assetFilter: function(assetFilename) { return assetFilename.endsWith('.css') || assetFilename.endsWith('.js'); } } };
这段代码里有几个重点:一个是css的引入,一个是js的引入,这两个引入错了,按需引入element也会行不通的,
看好所用的插件,缺啥下啥,还有一个就是 vue-loader 引入,插件版本不一样,引入的方式也不一样,这里的版本是15.7.0 ,之前有个低版本的,这么引入报错,不记得错误什么样了,自行测试吧。
问题2
这个错误从一开始就犯了,导致研究了好久,最后发现是路由的错。
错误现象:首次加载不出来,刷新就好了;刷新500。
错误码:
Cannot read property '__esModule' of undefined
原来项目改造前用这种懒加载方式:
{path: '/my',name: 'my', component: resolve => require(['../page/my/my.vue'], resolve), },
苦苦找了好久这个问题,竟然是路由。包括子组件都不能这么引入。
正确引入,官网:
{ path: '/', name: 'index',
component: () => import('../views/index.vue')},
问题3
关于 entry-client.js,这个是浏览器端入口。但是发现一个问题,用这种方式的时候有一个bug,在首次加载
或者刷新的时候出现数据后退,回到上一个路由的数据,造成页面不美,用户体验不好。基本再刷新时候出现,在网上
有人做个判断路由,试了一下,有点麻烦,不知道怎么解决好。上代码:
import Vue from 'vue' import { createApp } from './main' import 'es6-promise/auto' const { app, router, store } = createApp(); import { Loading } from 'element-ui'; Vue.mixin({ beforeRouteUpdate (to, from, next) { const { asyncData } = this.$options; if (asyncData) { asyncData({ store: this.$store, route: to }).then(next).catch(next) } else { next() } } }); if (window.__INITIAL_STATE__) { store.replaceState(window.__INITIAL_STATE__) } router.onReady(() => { router.beforeResolve((to, from, next) => { const matched = router.getMatchedComponents(to); const prevMatched = router.getMatchedComponents(from); let diffed = false; const activated = matched.filter((c, i) => { return diffed || (diffed = (prevMatched[i] !== c)) }); const asyncDataHooks = activated.map(c => c.asyncData).filter(_ => _); if (!asyncDataHooks.length) { return next() } let loadingInstance = Loading.service({ background:'rgba(220,220,220,0.5)', fullscreen: true, text: '加载中...', }); Promise.all(asyncDataHooks.map(hook => hook({ store, route: to}))) .then(() => { loadingInstance.close(); next() }).catch(next) }); app.$mount('#app'); });
后来尝试改下导航首位方式,问题解决了,但是新的问题出现,那就是页面首次加载,请求后端两次,上代码:
import Vue from 'vue' import { createApp } from './main' import 'es6-promise/auto' const { app, router, store } = createApp(); import { Loading } from 'element-ui'; Vue.mixin({ beforeMount () { const { asyncData } = this.$options; if(asyncData){ let loadingInstance = Loading.service({ background:'rgba(220,220,220,0.5)', fullscreen: true, text: '加载中...', }); this.dataPromise = asyncData({ store: this.$store, route: this.$route }); this.dataPromise.then(()=>{ loadingInstance.close(); }).catch(e=>{ loadingInstance.close(); }) } } }); if (window.__INITIAL_STATE__) { store.replaceState(window.__INITIAL_STATE__) } router.onReady(() => { app.$mount('#app'); });
这两种方法引入,虽然在直接上没有错误,但是整体上并不完美,并不能解决根本问题,于是仔细思考,这不是路由的问题,是数据缓存的问题,
仔细看代码,恍然大悟。在多模块下,要将vuex代码分离。
在*****.vue import sgnavSM from '../../../store/modules/sgnav';////引用 asyncData ({ store , route }) { store.registerModule('sgnav', sgnavSM);////注册 return Promise.all([ store.dispatch('gsnavList'), store.dispatch('ghnavList'), store.dispatch('gxnavList'), store.dispatch('getidssgnav',{id:6}) ]); }, computed: { navlist() { return this.$store.state.sgnav.hidnav;/////使用 } }, destroyed () { this.$store.unregisterModule('sgnav');/////消灭 },
彻底解决路由变换,信息后退问题!!!!
问题4
解决404,新建个error.vue,加点内容;修改router/index.js。
import Vue from 'vue' import Router from 'vue-router' import {router} from "../main"; Vue.use(Router); export function createRouter () { const router = new Router({ mode: 'history', scrollBehavior (to, from, savedPosition) { if (savedPosition) { return savedPosition } else { return { x: 0, y: 0 } } }, routes: [ { path: '/', name: 'index', component: () => import('../views/index.vue')}, ////404等错误 {path: '/error', name: 'error', component: () => import('../views/error.vue')}, ], }); router.beforeEach((to, from, next) => { if (to.matched.length === 0) { from.name ? next({ name: from.name }) : next('/error'); } else { next(); } }); return router }
问题5
cookie的使用。
在 /server.js
引入 const cookieParser = require('cookie-parser'); 当然提前下好插件。
使用 app.use(cookieParser());
传出去:
const context = { title:'', keywords:'', description:'' , author:'星涑', cookies:req.cookies, url: req.url, }; // const stream = renderer.renderToStream(context); // stream.on('error', errorHandler); // stream.pipe(res) renderer.renderToString(context, (err, html) => { if (err) { return errorHandler(err) } res.send(html); if (!isProd) { console.log('>>>>>|success|<<<<<') } res.end(); })
修改 entry-server.js ,将cookie存到vuex
if (context.cookies) {
store.commit('SET_TOKEN', context.cookies)
}
问题6
这个涉及到接收json数据,如果从后端接收过来的数据有点多。使页面比较乱可以处理下。
const olddata=接收的json; const newdata =[]; olddata.list.forEach(function(item){ newdata.push({ id:item.id, title:item.title, stitle:item.stitle, createTime:item.createTime, userImg:item.userImg, readNum:item.readNum, }) }) const wandata ={ pages: olddata.pages, total: olddata.total, pageNum:olddata.pageNum, pageSize:olddata.pageSize, list:newdata, }; commit('SET_ARTICLE', wandata); }
思路就是把有用的展示到前端。当然也可以后端处理。
问题7
这个问题也挺重要的,就是细节问题,在测试时候代码请求数据请求一次,渲染页面,但是打包上线后就请求了两次
,研究了一下发现,在代码添加vue-ssr-client-manifest.json之后,需要将webpack.client.config.js的 prod 的HtmlWebpackPlugin 去除,否则就会请求多次
,造成数据压力增大,页面变慢。
今天就更新这些,希望对你有帮助,给个推荐吧。
还是墨迹那句话,希望有更多的人支持我,点个关注。我会努力发表更好的文章
QQ:1763907618------备注:博客园