vue ssr 项目改造经历

vue ssr 项目改造经历

由于工作项目需求,需要将原有的项目改造,vue ssr 没有用到nuxt,因为vue ssr更利于seo,没办法,一个小白的改造经历,

首先说明一下,小白可以借鉴,高手也可以点评一下,因为,我写的不一定准确,只是针对我的项目。

下面先说一下大致:

原有项目有用到element,在改造ssr过程中,是很坑的。如果可以的话,还是强烈建议你重新改写成nuxt项目。由于我是小白,所以开始时候备份了一下项目,然后开始网上查找相关文章。

1.首先是这位大神的文章https://segmentfault.com/a/1190000012440041,笔名  右三。

2.然后是https://www.cnblogs.com/xiaohuochai/p/9158675.html,一个小火柴项目的改造过程。

3.https://segmentfault.com/a/1190000016637877  ,五步学会基础。

我列举他们三个,是因为,刚开始以为项目直接改改代码就可以,于是按照他们所说改写,发现,处处是坑,总之,他们的说法并不适合我的项目,于是苦思冥想,去看官网,再结合他们文章,开始大刀阔斧改造。

请您备份好:

先上一张原有项目图纸,就是普通的cli2构造出来的,其中theme是element主题,可不用理会。

接下来开始改造:

是不是不可思议,不敢整的,可以看文末怎么解决的一些坑。我接着分析

删除完以后,在build里添加四个文件:

1.    setup-dev-server.js

const path = require(‘path‘)
const webpack = require(‘webpack‘)
const MFS = require(‘memory-fs‘)
const clientConfig = require(‘./webpack.client.config‘)
const serverConfig = require(‘./webpack.server.config‘)

module.exports = function setupDevServer (app, cb) {
  let bundle
  let template

  // 修改客户端配置添加 热更新中间件
  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) // 执行webpack
  const devMiddleware = require(‘webpack-dev-middleware‘)(clientCompiler, {
    publicPath: clientConfig.output.publicPath,
    stats: {
      colors: true,
      chunks: false
    }
  })
  app.use(devMiddleware)

  clientCompiler.plugin(‘done‘, () => {
    const fs = devMiddleware.fileSystem
    // 模板为打包后的html文件
    const filePath = path.join(clientConfig.output.path, ‘index.html‘)
    if (fs.existsSync(filePath)) {
      template = fs.readFileSync(filePath, ‘utf-8‘)
      console.log("执行4")
      if (bundle) {
        console.log("执行1")
        cb(bundle, template)
      }
    }
  })

  app.use(require(‘webpack-hot-middleware‘)(clientCompiler))
  // 监听 server renderer
  const serverCompiler = webpack(serverConfig)
  const mfs = new MFS() // 内存文件系统,在JavaScript对象中保存数据。
  serverCompiler.outputFileSystem = mfs
  serverCompiler.watch({}, (err, stats) => {
    if (err) throw err
    stats = stats.toJson()
    stats.errors.forEach(err => console.error(err))
    stats.warnings.forEach(err => console.warn(err))
    // 读取使用vue-ssr-webpack-plugin生成的bundle(vue-ssr-bundle.json)
    const bundlePath = path.join(serverConfig.output.path, ‘vue-ssr-bundle.json‘)
    bundle = JSON.parse(mfs.readFileSync(bundlePath, ‘utf-8‘))
    console.log("执行3")
    if (template) {
      console.log("执行2")
      cb(bundle, template)
    }
  })
}

2.     webpack.base.config.js

const path = require(‘path‘)
const ExtractTextPlugin = require(‘extract-text-webpack-plugin‘)
const extractCSS = new ExtractTextPlugin(‘stylesheets/[name]-one.css‘);

// 这样我们在开发过程中仍然可以热重载,CSS 提取应该只用于生产环境
const isProduction = process.env.NODE_ENV === ‘production‘

module.exports = {
  devtool: ‘#source-map‘,
  entry: {
    app: ‘./src/entry-client.js‘,
    //app: ["babel-polyfill", "./src/entry-client.js"],///解决ie关键
    vendor: [
      ‘vue‘,
      ‘vue-router‘,
      ‘vuex‘
    ]
  },
  output: {
    path: path.resolve(__dirname, ‘../dist‘),
    publicPath: ‘/dist/‘,
    filename: ‘[name].[chunkhash].js‘
  },
  resolve: {
    alias: {
      ‘static‘: path.resolve(__dirname, ‘../static‘),
      // ‘@‘: path.resolve(‘src‘),
    }
  },
  module: {
    noParse: /es6-promise\.js$/, // avoid webpack shimming process
    rules: [
      {
        test: /\.vue$/,
        loader: ‘vue-loader‘,
        options: {
          extractCSS: isProduction,
          preserveWhitespace: false,
          postcss: [
            require(‘autoprefixer‘)({
              browsers: [‘last 3 versions‘]
            })
          ]
        }
      },

      {
        test: /\.js$/,
        loader: ‘buble-loader‘,
        exclude: /node_modules/,
        options: {
          objectAssign: ‘Object.assign‘
        }
      },
      {
        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: isProduction
          ? ExtractTextPlugin.extract({
            use: ‘css-loader‘,
            fallback: ‘vue-style-loader‘
          })
          : [‘vue-style-loader‘, ‘css-loader‘]
      }
    ]
  },
  plugins: isProduction
    // 确保添加了此插件!
    ? [new ExtractTextPlugin({ filename: ‘common.[chunkhash].css‘ })]
    : [],
  performance: {
    hints: process.env.NODE_ENV === ‘production‘ ? ‘warning‘ : false,
    maxAssetSize: 30000000, // 整数类型(以字节为单位)
    maxEntrypointSize: 50000000, // 整数类型(以字节为单位)
    assetFilter: function(assetFilename) {
      // 提供资源文件名的断言函数
      return assetFilename.endsWith(‘.css‘) || assetFilename.endsWith(‘.js‘);
    }

  }
}

3.     webpack.client.config.js

const webpack = require(‘webpack‘)
const merge = require(‘webpack-merge‘)
const base = require(‘./webpack.base.config‘)
const HTMLPlugin = require(‘html-webpack-plugin‘)
const SWPrecachePlugin = require(‘sw-precache-webpack-plugin‘)

const config = merge(base, {
  plugins: [
    // 全局变量
    new webpack.DefinePlugin({
      ‘process.env.NODE_ENV‘: JSON.stringify(process.env.NODE_ENV || ‘development‘),
      ‘process.env.VUE_ENV‘: ‘"client"‘
    }),
    // 将依赖模块提取到 vendor chunk 以获得更好的缓存,是很常见的做法。
    new webpack.optimize.CommonsChunkPlugin({
      name: ‘vendor‘,
      minChunks: function (module) {
        return (
          // 如果它在 node_modules 中
          /node_modules/.test(module.context) &&
          // 如果 request 是一个 CSS 文件,则无需外置化提取
          !/\.css$/.test(module.request)
        )
      }
    }),
    // 提取 webpack 运行时和 manifest
    new webpack.optimize.CommonsChunkPlugin({
      name: ‘manifest‘
    }),
    // html模板
    new HTMLPlugin({
      template: ‘index.html‘
    })
  ]
})

if (process.env.NODE_ENV === ‘production‘) {
  config.plugins.push(
    // 生产环境下 - 压缩js
    new webpack.optimize.UglifyJsPlugin({
      compress: {
        warnings: false
      }
    }),
    // 用于使用service worker来缓存外部项目依赖项。
    new SWPrecachePlugin({
      cacheId: ‘vue-hn‘,
      filename: ‘service-worker.js‘,
      dontCacheBustUrlsMatching: /./,
      staticFileGlobsIgnorePatterns: [/index\.html$/, /\.map$/]
    })
  )
}

module.exports = config

4.    webpack.server.config.js

const webpack = require(‘webpack‘)
const merge = require(‘webpack-merge‘)
const base = require(‘./webpack.base.config‘)
const VueSSRPlugin = require(‘vue-ssr-webpack-plugin‘)
const nodeExternals = require(‘webpack-node-externals‘)

module.exports = merge(base, {
  target: ‘node‘,
  entry: ‘./src/entry-server.js‘,
  devtool: ‘source-map‘,
  output: {
    filename: ‘server-bundle.js‘,
    libraryTarget: ‘commonjs2‘
  },

  externals: nodeExternals({
    // do not externalize CSS files in case we need to import it from a dep
    whitelist: /\.css$/,
    "jquery": "$",
    ‘Vue‘: true,
    ‘VueLazyload‘: true,
    ‘$‘: true,
    ‘vue-router‘: ‘VueRouter‘,
    ‘vuex‘:‘Vuex‘,
    ‘axios‘: ‘axios‘,
    // ‘element-ui‘:‘ELEMENT‘,
  }),
  plugins: [
    new webpack.DefinePlugin({
      ‘process.env.NODE_ENV‘: JSON.stringify(process.env.NODE_ENV || ‘development‘),
      ‘process.env.VUE_ENV‘: ‘"server"‘
    }),
    /*
    使用webpack按需代码分割的特性的时候(require.ensure或动态import)结果就是服务端bundle会包含很多分开的文件。
    ‘vue-ssr-webpack-plugin‘作用是将其打包为一个单独的JSON文件,这个文件可以传入到bundleRenderer中(server.js),可以极大地简化了工作流。
    默认文件名为 `vue-ssr-server-bundle.json`,也可以参数形式传入其他名称
    */
    new VueSSRPlugin()
  ]
})

加完这些,大概是这样子

显然,加如这些不够。

我们还需要在根目录添加

server.js

const fs = require(‘fs‘)
const path = require(‘path‘)
const express = require(‘express‘)
const compression = require(‘compression‘) // 开启gzip压缩
const resolve = file => path.resolve(__dirname, file)

const isProd = process.env.NODE_ENV === ‘production‘
const serverInfo = `express/${require(‘express/package.json‘).version} ` +
  `vue-server-renderer/${require(‘vue-server-renderer/package.json‘).version}`
const app = express()
function createRenderer (bundle, template) {
  return require(‘vue-server-renderer‘).createBundleRenderer(bundle, {
    template, // 缓存
    cache: require(‘lru-cache‘)({
      max: 1000,
      maxAge: 1000 * 60 * 15
    })
  })
}
let renderer
if (isProd) {
  const bundle = require(‘./dist/vue-ssr-bundle.json‘)
  const template = fs.readFileSync(resolve(‘./dist/index.html‘), ‘utf-8‘)
  renderer = createRenderer(bundle, template)
} else {
  require(‘./build/setup-dev-server‘)(app, (bundle, template) => {
    renderer = createRenderer(bundle, template)
  })
}
const serve = (path, cache) => express.static(resolve(path), {
  maxAge: cache && isProd ? 60 * 60 * 24 * 30 : 0 // 静态资源设置缓存
})

app.use(compression({ threshold: 0 })) // gzip压缩
app.use(‘/dist‘, serve(‘./dist‘, true)) // 静态资源
app.use(‘/static‘, serve(‘./static‘, true)) // 静态资源 (如:http://localhost:8080/public/logo-120.png)
app.use(‘/manifest.json‘, serve(‘./manifest.json‘, true))
app.use(‘/service-worker.js‘, serve(‘./dist/service-worker.js‘))

app.get(‘*‘, (req, res) => {
  if (!renderer) {
    return res.end(‘未渲染成功||wei cheng gong‘)
  }
  const s = Date.now()
  res.setHeader("Content-Type", "text/html")
  res.setHeader("Server", serverInfo)
  const errorHandler = err => {
    if (err && err.code === 404) {
      console.log(404)
      res.status(404).end(‘404 | Page Not Found‘)
    } else {
      res.status(500).end(‘500 | Internal Server Error‘)
      console.error(`error during render : ${req.url}`)
      console.error(err)
    }
  }

  var title = ‘测试-首页‘ // 自定义变量(此处用于title)
  var author =‘Anne‘ // 默认author
  var keywords =‘我是keywords‘ // 默认keywords
  var description =‘我是description‘ //默认description
  renderer.renderToStream({title,author,keywords,description, url: req.url})

    .on(‘error‘, errorHandler)
    .on(‘end‘, () => console.log(`整体请求: ${Date.now() - s}ms`))
    .pipe(res)
})

const port = process.env.PORT || 3026

app.listen(port, () => {
  console.log(`localhost:${port}`)
})

  

在src目录下还有俩:

这里注意一下我的入口文件为main.js。

app.js和utils.js文件夹,可不用理会,是项目单分出来的接口文件用axio。我们获取数据接口将在vuex里面写。也就是store文件夹。

entry-client.js:

// entry-client.js 客户端渲染入口文件
import Vue from ‘vue‘
import { app, store, router } from ‘./main‘

/*Vue-SSR 根据访问的路由会调用当前路由组件中的asyncData方法由服务端调用相关接口,根据数据
生成首屏对应的html,并在返回的html中写入 window.__INITIAL_STATE__ = {服务端请求到的数据}
不需要服务端渲染的数据则在 mounted 中请求接口。*/

/*路由切换时组件的asyncData方法并不会被调用,若该组件存在服务端渲染方法asyncData,可通过下面
三种方式客户端调用,并进行客户端渲染*/

//(1)
// 全局mixin,beforeRouteEnter,切换路由时,调用asyncData方法拉取数据进行客户端渲染
// 注意beforeRouteEnter无法直接获取到当前组件this,需使用next((vm)=>{ vm即为this }) 获取

/*Vue.mixin({
  beforeRouteEnter (to, from, next) {
    console.log(‘beforeRouteEnter1‘)
    next((vm)=>{
        const {asyncData} = vm.$options
    console.log(‘beforeRouteEnter1‘+ asyncData)
        if (asyncData) {
            asyncData(vm.$store, vm.$route).then(next).catch(next)
        } else {
            next()
        }
    })

  }
})*/

//(2)
// 全局mixin,beforeRouteUpdate,切换路由时,调用asyncData方法拉取数据进行客户端渲染
// beforeRouteUpdate可直接获取到this对象(2.2版本以上)
/*Vue.mixin({
  beforeRouteUpdate (to, from, next) {
    console.log(‘beforeRouteUpdate2‘)
    const { asyncData } = this.$options
    if (asyncData) {
        // 传入store与route
        asyncData(this.$store, this.$route).then(next).catch(next)
    } else {
        next()
    }
  }
})*/

// (3)
// 注册全局mixin,所有组件beforeMount时,如果根组件_isMounted为真(即根实例已mounte,该钩子函数是由路由跳转触发的)
// 调用asyncData方法拉取数据进行客户端渲染
Vue.mixin({
  data(){ //全局mixin一个loading
    return {
      //loading:false
    }
  },
  beforeMount () {
    const { asyncData } = this.$options;
    let data=null; //把数据在computed的名称固定为data,防止重复渲染
    try{
      data=this.data; //通过try/catch包裹取值,防止data为空报错
    }catch(e){}
    if(asyncData&&!data){ //如果拥有asyncData和data为空的时候,进行数据加载
      //触发loading加载为true,显示加载器不显示实际内容
      //this.loading=true;
      //为当前组件的dataPromise赋值为这个返回的promise,通过判断这个的运行情况来改变loading状态或者进行数据的处理 (在组件内通过this.dataPromise.then保证数据存在)
      this.dataPromise=asyncData({store,route:router.currentRoute})
      // this.dataPromise.then(()=>{
      //   //this.loading=false;
      // }).catch(e=>{
      //  // this.loading=false;
      // })
    }else if(asyncData){
      //如果存在asyncData但是已经有数据了,也就是首屏情况的话返回一个成功函数,防止组件内因为判断then来做的操作因为没有promise报错
      this.dataPromise=Promise.resolve();
    }
  }
})
// 使用 window.__INITIAL_STATE__ 中的数据替换store中的数据
if (window.__INITIAL_STATE__) {
  store.replaceState(window.__INITIAL_STATE__)
}

router.onReady(() => {
  app.$mount(‘#app‘)
})

entry-server.js:

// entry-server.js
import { app, router, store } from ‘./main‘

const isDev = process.env.NODE_ENV !== ‘production‘ // 开发模式 || 生产模式
export default context => {
  const s = isDev && Date.now()

  // 因为有可能会是异步路由钩子函数或组件,所以我们将返回一个 Promise,
  // 以便服务器能够等待所有的内容在渲染前,
  // 就已经准备就绪。
  return new Promise((resolve, reject) => {
    console.log(context)
    // push对应访问路径
    router.push(context.url)

    // 等到 router 将可能的异步组件和钩子函数解析完
    router.onReady(() => {
      const matchedComponents = router.getMatchedComponents() // 返回当前路径匹配到的组件

      // 匹配不到的路由,reject(),返回 404
      if (!matchedComponents.length) {
        reject({ code: 404 })
      }
      // Promise.all 组件的 asyncData 方法 拿数据 全部数据返回后 为window.__INITIAL_STATE__赋值并 resolve(app)
      Promise.all(matchedComponents.map(component => {
        if (component.asyncData) {
          return component.asyncData({store, route: router.currentRoute})
        }
      }))
        .then(() => {
          isDev && console.log(`数据预取: ${Date.now() - s}ms`)
          context.state = store.state
          resolve(app)
        }).catch(reject)
    })

  })
}

同时main.js也需要改:

import Vue from ‘vue‘

import App from ‘./App.vue‘

import { createRouter } from ‘./router/index‘
import { createStore } from ‘./store/index‘
import {sync} from ‘vuex-router-sync‘

//时间过滤器
import ‘./utils/jsontime.js‘
//title
//import titleMixin from ‘./utils/title‘
//ie
//import ‘babel-polyfill‘
require("babel-polyfill");
import axios from ‘axios‘
import VueAxios from ‘vue-axios‘
//import Vuex from‘vuex‘
//import MetaInfo from ‘vue-meta-info‘

Vue.prototype.filterHtml = function (msg) {
  if (msg) {
    return msg.replace(/<img/g, "<img style=‘max-width: 800px;max-height: 500px;margin:10px 30px;‘")
  }
  return ‘‘
};

if (typeof window !== ‘undefined‘) {
  require(‘element-ui/lib/theme-chalk/index.css‘);
  const ElementUI = require(‘element-ui‘);
  Vue.use(ElementUI);
}
// if (process.browser) {
//   //console.log(‘浏览器端渲染‘);
//   Vue.use(require(‘element-ui‘),require(‘element-ui/lib/theme-chalk/index.css‘))
// } else {
//   //console.log("非浏览器端渲染");
// }

Vue.config.productionTip = false;
//Vue.mixin(titleMixin),
// Vue.use(Vuex);
  Vue.use(VueAxios, axios);
//Vue.use(MetaInfo);

const router = createRouter();
const store = createStore();
sync(store, router);

const app = new Vue({
  router,
  store,
  render: h => h(App)
});

export { app, router, store }

这里主要部分还是这几句:其他的一会简单说明,

import Vue from ‘vue‘
import App from ‘./App.vue‘
import { createRouter } from ‘./router/index‘
import { createStore } from ‘./store/index‘
import {sync} from ‘vuex-router-sync‘

Vue.config.productionTip = false;
const router = createRouter();
const store = createStore();
sync(store, router);
const app = new Vue({
  router,
  store,
  render: h => h(App)
});
export { app, router, store }

还有一个文件:差点忘了那个模板index,html

<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width,initial-scale=1.0">
  <title>{{title}}</title>
  <meta name="keywords" content=‘{{keywords}}‘>
  <meta name="description" content=‘{{description}}‘>
  <link rel="shortcut icon" type="image/x-icon" href="/static/favicon.ico">

  <style>
    .Tmain{
      margin: 0px;
    }
  </style>
</head>
<body class="Tmain">
 <!--vue-ssr-outlet-->
</body>
</html>

接下来看路由文件:router/index.js

import Vue from ‘vue‘
import Router from ‘vue-router‘

Vue.use(Router)

export function createRouter() {
  return new Router({
    mode: ‘history‘,
    routes: [
      {
        path: ‘/‘, name: ‘index‘,
        component: () =>import(‘../views/index.vue‘)
      },
      {
        path: ‘/articlex/:id‘, name: ‘articlex‘,
        component: resolve => require([‘../views/main/article/articlex.vue‘], resolve)
      },
      {path: "*", redirect: "/"}
    ]
  })
}

这里第一个路由是ssr的异步加载,第二个为懒加载写法,第三个为404,返回到原页面。

还有store/index.js文件,问什么把这个放到最后呢,因为这个文件可以模块化,也可以写到一个里。

在一个里面写:

import Vue from ‘vue‘
import Vuex from ‘vuex‘
import axios from ‘axios‘

Vue.use(Vuex)

// 数据
let state = {
  lists: [], // 文章列表
  detail: {} // 文章详情
}

// 事件
let actions = {
  // 获取文章列表
  fetchLists ({ commit }, data) {
    return axios.get(‘https://xxxx/api/v1/topics?page=‘ + data.page)
    .then((res) => {
      if (res.data.success) {
        commit(‘setLists‘, res.data.data)
      }
    })
  },
  // 获取文章详情
  fetchDetail ({ commit }, data) {
    return axios.get(‘https://xxxx/api/v1/topic/‘ + data.id)
    .then((res) => {
      if (res.data.success) {
        commit(‘setDetail‘, res.data.data)
      }
    })
  }
}

// 改变
let mutations = {
  setLists (state, data) {
    state.lists = data
  },
  setDetail (state, data) {
    state.detail = data
  }
}

// 获取
let getters = {
  getLists: state => {
    return state.lists
  },
  getDetail: state => {
    return state.detail
  }
}

export function createStore () {
  return new Vuex.Store({
    state,
    actions,
    mutations,
    getters
  })
}

在模块化的话结构这样:

index.js:

import Vue from ‘vue‘
import vuex from ‘vuex‘
import user from ‘./modules/user‘
import getters from ‘./getters‘

Vue.use(vuex);
export function createStore() {
  return new vuex.Store({
    modules: {
      user,
    },
    getters
  });

}

getters.js

const getters = {
  lists: state => state.user.lists,
  detail: state => state.detail,

};
export default getters

modules/user.js

import axios from ‘axios‘

const user = {
  state: {
    lists: [],
    detail:{},
  },

  mutations: {
    SET_LISTS: (state, lists) => {
      state.lists = lists;
    },
    SET_DETAIL: (state, detail) => {
      state.detail = detail;
    },

  },

  actions: {

// 获取文章列表
  fetchLists ({ commit }, data) {
    return axios.get(‘https://XXX/api/v1/topics?page=‘ + data.page)
    .then((res) => {
      if (res.data.success) {
        commit(‘SET_LISTS‘, res.data.data)
      }
    })
  },
  // 获取文章详情
  fetchDetail ({ commit }, data) {
    return axios.get(XXXX/api/v1/topic/‘ + data.id)
    .then((res) => {
      if (res.data.success) {
        commit(‘SET_DETAIL‘, res.data.data)
      }
    })
  }
}

    // 前端 登出
    FedLogOut({commit}) {
      return new Promise(resolve => {
        commit(‘SET_LISTS‘, "");
        commit(‘SET_DETAIL‘, "");
        resolve();
      });
    },
  }
};

export default user;

写到这也算完成一半多了,继续往下看

一个页面怎么才算是开始渲染,用到了asyncData,

由于项目保密原因我只展示一页内容仅供参考:index.vue

<template>
    <div>
        {{lists.id}}
    </div>
</template>
<script>
    import axios from ‘axios‘
    export default {
        /**
         * [SSR获取所有组件的asyncData并执行获得初始数据]
         * @param  {[Object]} store [Vuex Store]
         * 此函数会在组件实例化之前调用,所以它无法访问 this。需要将 store 和路由信息作为参数传递进去:
         */
        asyncData (store, route) {
            return store.dispatch(‘fetchLists‘ ) // 服务端渲染触发
        },
        name: "home",
        // 数据
        data() {
            return {

            }
        },
        // 计算属性
        computed: {
               lists () {
               return this.$store.getters.lists // 文章列表
              },
        },

        mounted() {

        },
        // 方法
        methods: {

        },
        // 子组件
        components: {
        }
    }
</script>
<!--当前组件的样式 -->
<style scoped>

</style>

最后启动了,改为

package.json

 "scripts": {
    "dev": "node server",
    "start": "cross-env NODE_ENV=production node server",
    "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
// 开发模式
npm run dev
// 生产模式
npm run build
npm run start

这样,就算是结束一个简单的渲染了,下面我将介绍我在开发中遇到的问题以及解决方法:



1.当我写完这些后发现一个大的问题,路径问题:之前写的项目有的路径用@/api/sre.js等。

由于重新写完发现@不好使了,没太深究直接../../绝对路径。



2.npm install 安装依赖插件。都安什么呢?

如果少个依赖,看报错信息《Can‘t find xxxx  dependence》。

我这里大致总结了写=些,项目直接码上:

npm install -g npm (更新npm)

npm i vue-server-renderer(一定要和vue版本一致,别问为啥,官方大大)

npm install babel-plugin-component --save-dev(element)

npm i axios buble buble-loader compression cross-env es6-promise express http-proxy-middleware lru-cache serve-favicon sw-precache-webpack-plugin vue-ssr-webpack-plugin vue-style-loader vuex vuex-router-sync webpack-hot-middleware webpack-merge webpack-node-externals

(lru-cache4.0.2,这个插件报错,就换这个版本)

npm install --save babel-polyfill   (ie兼容)



3。安装element-ui,这个大坑,趟浑水了。

1.    npm i element-ui -S

2.    main.js里这样引入就可以了。原因自己猜去吧

if (typeof window !== ‘undefined‘) {
  require(‘element-ui/lib/theme-chalk/index.css‘);
  const ElementUI = require(‘element-ui‘);
  Vue.use(ElementUI);
}
// if (process.browser) {
//   //console.log(‘浏览器端渲染‘);
//   Vue.use(require(‘element-ui‘),require(‘element-ui/lib/theme-chalk/index.css‘))
// } else {
//   //console.log("非浏览器端渲染");
// }


3.发现一个问题没有过多探究,好像首页和他的组件不能用懒加载方式。有待研究,因为我用懒加载,报错                                   error during render : / Error: stream.push() after EOF at readableAddChun



4.ie浏览器不兼容问题。

首页模板index.html 添加

<meta http-equiv="X-UA-Compatible" content="IE=7,IE=9">
<meta http-equiv="X-UA-Compatible" content="IE=7,9">
<meta http-equiv="X-UA-Compatible" content="IE=Edge,chrome=1">
#以上代码IE=edge告诉IE使用最新的引擎渲染网页,chrome=1则可以激活Chrome Frame.

npm install --save babel-polyfill

main.js里加

require("babel-polyfill");//或者import "babel-polyfill";//注意放的位置

webpack.base.config.js里加

module.exports = {

  entry: ["babel-polyfill", "./app/js"]

};


5.假如asyncData 调用多个vuex怎么写,这样就能并发

asyncData ({ store , route }) {
let data1= Promise.all([
  store.dispatch(‘ghnavList‘),
  store.dispatch(‘gxnavList‘),
  store.dispatch(‘getdonglist‘,{artdongid:21}),
  store.dispatch(‘getyoulist‘,{artyouid:22}),
  store.dispatch(‘getzhilist‘,{artzhiid:23})
    ])
 return data1
},

6.第一次完成项目的时候,发现页面渲染了两次,就是一个页面显示两个一样的版块,很是揪心,于是我将index.html里的文件搞成这样:

<body >
<div id="app">
 <!--vue-ssr-outlet-->
</div>
</body>

指标不治本,下次完善。



现在时间24:00整,有很多东西还没有想到,来不及整理了,希望有更多的人看到我文章,对其进行评价补充,我将不胜感激。

还是墨迹那句话,希望有更多的人支持我,点个关注。我会努力发表更好的文章

QQ:1763907618------备注:博客园



vue ssr 项目改造经历

原文地址:https://www.cnblogs.com/wangyongping/p/10961587.html

时间: 2024-10-02 23:35:13

vue ssr 项目改造经历的相关文章

vue.js项目构建基础

这里构建的vue.js项目依赖node服务器运行. 项目搭建完整步骤: 安装node.js ,转至nodeJs网站http://nodejs.cn/ 下载nodeJs进行安装. 安装完毕检查nodeJs安装是否成功? nodeJs安装完成,自带npm,可以检查npm是否已经安装 安装webpack. webpack是一个模块加载器兼打包工具,在vue项目中,为了更好的管理代码使用模块系统,使用webpack打包. 安装webpack  查看webpack是否安装成功? 安装 vue-cli 脚手

vue+webpack项目结构介绍

对vue已经有了一定的了解,所以希望可以在github上找到一些项目实践,但是无奈不知道vue的整体架构是如何的,往往不知道该如何着手看,所以这里在网上找了相关的文章作为参考,对结构有一个大致的了解.参考文章如下: 用vue+webpack搭建的前端项目结构 vue+webpack项目实践 这里插一句话,搜索谷歌做的确实好,支持原创,搜索精准,不知道比度娘高到哪里去~

Vue 实际项目中你可能会遇见的坑

纸上得来终觉浅,绝知此事要躬行! Vue的文档和教程看的太多,小的demo做的多,也不如自己实际的进行一个完整项目的开发.只有做了才知道原来问题这么多,这里列举了一些你做demo教程可能不会遇见的坑. 一.项目的登录拦截及用户权限访问控制问题. 一个很常见的需求就是对未登录的用户进行路由拦截和用户的权限访问,如果你的项目之前是通过后台控制访问路由的,那么之前的后端现在可能需要在前端也需要控制了,由于我们用vue脚手架搭建的都是纯前端的路由访问,先看看我们的一般的项目路径: 你可能会注意到Vue的

vue新建项目

一直都被如何用vue.js新建一个项目的问题困扰着,经过好久的实践,终于搞清楚如何用vue新建项目了: 1.官网对于vue-cli介绍: Vue.js provides an official CLI for quickly scaffolding ambitious Single Page Applications. It provides batteries-included build setups for a modern frontend workflow. It takes only

vue.js项目构建之vue-router2.0的使用

vue-router2.0官方文档地址:http://router.vuejs.org/zh-cn/index.html 单页应用? 单页应用程序(SPA,single page web application). SPA其实就是整个网站只有一个页面,只改变页面的显示内容,不需要改变整个网页. vue.js 的单页面应用是基于路由和组件的.路由用于设定访问路径,并将路径和组件映射起来.页面内容切换这里就是组件的切换. vue-router是vue.js官方的路由插件,适合构建单页面应用. vue

Vue简单项目(页面跳转,参数传递)

1.和上篇文章一样的建文件的步骤 2.建立成功之后,在src文件夹下面添加新的文件夹pages 3.在pages里面添加新的文件Home.Vue和Detail.Vue 4.设Home.Vue为项目启动的初始页,在routes下的index.js里面添加以下代码,这时即可设为Home为初始页(App.Vue不可删除) 5.在Home.Vue中实现跳转的代码 <router-link to="/Detail">跳转到详情页</router-link> 6.启动项目,

Vue 实际项目中你可能会遇见问题

纸上得来终觉浅,绝知此事要躬行! Vue的文档和教程看的太多,小的demo做的多,也不如自己实际的进行一个完整项目的开发.只有做了才知道原来问题这么多,这里列举了一些你做demo教程可能不会遇见的坑. 一.项目的登录拦截及用户权限访问控制问题. 一个很常见的需求就是对未登录的用户进行路由拦截和用户的权限访问,如果你的项目之前是通过后台控制访问路由的,那么之前的后端现在可能需要在前端也需要控制了,由于我们用vue脚手架搭建的都是纯前端的路由访问,先看看我们的一般的项目路径: 你可能会注意到Vue的

使用vue做项目如何提高代码效率

最近做了两个vue项目,算上之前做的两个项目,总共有四个vue项目的经验了,但是总体来说写的代码质量不是很高,体现在以下几点 1.代码没有高效的复用 自从使用vue做项目之后,以前使用面向过程变成的习惯随之被面向对象取代了,这是一个很好的转变,让代码看起来不是那么混乱了,但是不混乱并不代表质量高,比如实现一个检验输入是否有效的功能写的代码很长,而且重复代码很多例如: function checkInput(data) {let isTrue;if(!data.date) { this.$mess

如何把vue.js项目部署到服务器上

如何把vue.js项目部署到服务器上面,我用的是tomcat服务器 1-改一下config/index.js文件,如下图,把assetsPublicPath: './', productionSourceMap: false 2-运行npm run build 然后在项目路径中找到打包后的文件,在项目文件的dist文件夹下面的static和index.html就是打包后的文件.然后我自己创建了cccBlog文件夹,把static和index.html拷贝到cccBlog下面,然后向服务器上传这个