从0到1构建vueSSR项目之node以及vue-cli3的配置

news/2024/7/10 2:34:02 标签: vue, node

前言

上一次做了路由的相关配置,原本计划今天要做vuex部分,但是想了想,发现vuex单独的客户端部分穿插解释起来很麻烦,所以今天改做服务端部分。

服务端部分做完,再去做vuex的部分,这样就会很清晰。

vue ssr是分两个端,一个是客户端,一个是服务端。

所以要做两个cli3的配置。

那么下面就直接开始做吧。

修改package.json的命令

//package.json :client代表客户端 :server代表服务端
//使用VUE_NODE来作为运行环境是node的标识
//cli3内置 命令 --no-clean 不会清除dist文件
  "scripts": {
    "serve:client": " vue-cli-service serve",
    "build":"npm run build:server -- --silent && npm run build:client -- --no-clean --silent",
    "build:client": "vue-cli-service build",
    "build:server": "cross-env VUE_NODE=node vue-cli-service build",
    "start:server": "cross-env NODE_ENV=production nodemon nodeScript/index"
  }

修改vue.config.js配置

添加完相关脚本命令之后,我们开始改造cli3配置。
首先要require(‘vue-server-renderer’)
然后再根据VUE_NODE环境变量来决定编译的走向以及生成不同的环境清单

先做cli3服务端的入口文件

// src/entry/server.js
import {
  createApp
} from '../main.js'
 
export default context => {
 
  return new Promise((resolve, reject) => {
    const {
      app,
      router
    } = createApp(context.data)
    //根据node传过来的路由 来调用router路由的指向
    router.push(context.url)
 
    router.onReady(() => {
      //获取当前路由匹配的组件数组。
      const matchedComponents = router.getMatchedComponents()
      //长度为0就是没找到该路由所匹配的组件
      //可以路由设置重定向或者传回node node来操作也可以
      if (!matchedComponents.length) {
 
        return reject({
          code: 404
        })
      }
 
      resolve(app)
 
    }, reject)
  })
}

这里是cli3的配置

//vue.config.js
 
const ServerPlugin = require('vue-server-renderer/server-plugin'),//生成服务端清单
   ClientPlugin = require('vue-server-renderer/client-plugin'),//生成客户端清单
   nodeExternals = require('webpack-node-externals'),//忽略node_modules文件夹中的所有模块
   VUE_NODE = process.env.VUE_NODE === 'node',
   entry = VUE_NODE ? 'server' : 'client';//根据环境变量来指向入口
 
module.exports = {
  css: {
    extract: false//关闭提取css,不关闭 node渲染会报错
  },
  configureWebpack: () => ({
    entry: `./src/entry/${entry}`,
    output: {
      filename: 'js/[name].js',
      chunkFilename: 'js/[name].js',
      libraryTarget: VUE_NODE ? 'commonjs2' : undefined
    },
    target: VUE_NODE ? 'node' : 'web',
    externals: VUE_NODE ? nodeExternals({
      //设置白名单
      whitelist: /\.css$/
    }) : undefined,
    plugins: [//根据环境来生成不同的清单。
      VUE_NODE ? new ServerPlugin() : new ClientPlugin()
    ]
  }),
  chainWebpack: config => {
    config.resolve
      .alias
        .set('vue$', 'vue/dist/vue.esm.js')
    config.module
      .rule('vue')
        .use('vue-loader')
          .tap(options => {
            options.optimizeSSR = false;
            return options;
          });
    config.module
      .rule('images')
        .use('url-loader')
          .tap(options => {
            options = {
              limit: 1024,
              fallback:'file-loader?name=img/[path][name].[ext]'
            }
            return options;
          });
  }
}

node相关配置

用于node渲染 必然要拦截get请求的。然后根据get请求地址来进行要渲染的页面。

官方提供了vue-server-renderer插件

大概的方式就是 node拦截所有的get请求,然后将获取到的路由地址,传给前台,然后使用router实例进行push

再往下面看之前 先看一下官方文档

创建BundleRenderer
createBundleRenderer

将 Vue 实例渲染为字符串。
renderToString

渲染应用程序的模板
template

生成所需要的客户端或服务端清单
clientManifest

先创建 服务端所需要的模板

//public/index.nodeTempalte.html
<!DOCTYPE html>
<html>
  <head>
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width,initial-scale=1.0">
    <link rel="icon" href="/favicon.ico" rel="external nofollow" >
    <meta charset="utf-8">
    <title>vuessr</title>
  </head>
  <body>
    <!--vue-ssr-outlet-->
  </body>
</html>

node部分

先创建三个文件

  • index.js //入口
  • proxy.js //代理
  • server.js //主要配置
//server.js
const fs = require('fs');
const { resolve } = require('path');
const express = require('express');
const app = express();
const proxy = require('./proxy');
const { createBundleRenderer } = require('vue-server-renderer')
 
//模板地址
const templatePath = resolve(__dirname, '../public/index.nodeTempalte.html')
//客户端渲染清单
const clientManifest = require('../dist/vue-ssr-client-manifest.json')
//服务端渲染清单
const bundle = require('../dist/vue-ssr-server-bundle.json')
//读取模板
const template = fs.readFileSync(templatePath, 'utf-8')
const renderer = createBundleRenderer(bundle,{
  template,
  clientManifest,
  runInNewContext: false
})
 
 
//代理相关
proxy(app);
//请求静态资源相关配置
app.use('/js', express.static(resolve(__dirname, '../dist/js')))
app.use('/css', express.static(resolve(__dirname, '../dist/css')))
app.use('/font', express.static(resolve(__dirname, '../dist/font')))
app.use('/img', express.static(resolve(__dirname, '../dist/img')))
app.use('*.ico', express.static(resolve(__dirname, '../dist')))
 
 
//路由请求
app.get('*', (req, res) => {
  res.setHeader("Content-Type", "text/html")
  //传入路由 src/entry/server.js会接收到 使用vueRouter实例进行push
  const context = {
    url: req.url
  }
 
  renderer.renderToString(context, (err, html) => {
    if (err) {
      if (err.url) {
        res.redirect(err.url)
      } else {
        res.status(500).end('500 | 服务器错误');
        console.error(`${req.url}: 渲染错误 `);
        console.error(err.stack)
      }
    }
    res.status(context.HTTPStatus || 200)
    res.send(html)
  })
})
module.exports = app;
 
 
 
//proxy.js
 
const proxy = require('http-proxy-middleware');
 
function proxyConfig(obj){
  return {
    target:'localhost:8081',
    changeOrigin:true,
    ...obj
  }
}
module.exports = (app) => {
  //代理开发环境
  if (process.env.NODE_ENV !== 'production') {
    app.use('/js/main*', proxy(proxyConfig()));
    app.use('/*hot-update*',proxy(proxyConfig()));
    app.use('/sockjs-node',proxy(proxyConfig({ws:true})));
  }
}
 
 
//index.js
const app = require('./server')
 
app.listen(8080, () => {
 console.log('\033[42;37m DONE \033[40;33m localhost:8080 服务已启动\033[0m')
})

做完这一步之后,就可以预览基本的服务渲染了。
后面就只差开发环境的配置,以及到node数据的传递(vuex)

最终目录解构:

|-- vuessr
  |-- .gitignore
  |-- babel.config.js
  |-- package-lock.json
  |-- package.json
  |-- README.md
  |-- vue.config.js
  |-- nodeScript //node 渲染配置
  |  |-- index.js
  |  |-- proxy.js
  |  |-- server.js
  |-- public//模板文件
  |  |-- favicon.ico
  |  |-- index.html
  |  |-- index.nodeTempalte.html
  |-- src
    |-- App.vue
    |-- main.js
    |-- router.config.js//路由集合
    |-- store.config.js//vuex 集合
    |-- assets//全局静态资源源码
    |  |-- 备注.txt
    |  |-- img
    |    |-- logo.png
    |-- components//全局组件
    |  |-- Head
    |    |-- index.js
    |    |-- index.scss
    |    |-- index.vue
    |    |-- img
    |      |-- logo.png
    |-- entry//cli3入口
    |  |-- client.js
    |  |-- server.js
    |  |-- 备注.txt
    |-- methods//公共方法
    |  |-- 备注.txt
    |  |-- mixin
    |    |-- index.js
    |-- pages//源码目录
    |  |-- home
    |  |  |-- index.js
    |  |  |-- index.scss
    |  |  |-- index.vue
    |  |  |-- img
    |  |  |  |-- flow.png
    |  |  |  |-- head_portrait.jpg
    |  |  |  |-- logo.png
    |  |  |  |-- vuessr.png
    |  |  |-- vue
    |  |  |  |-- index.js
    |  |  |  |-- index.scss
    |  |  |  |-- index.vue
    |  |  |-- vueCli3
    |  |  |  |-- index.js
    |  |  |  |-- index.scss
    |  |  |  |-- index.vue
    |  |  |-- vueSSR
    |  |  |  |-- index.js
    |  |  |  |-- index.scss
    |  |  |  |-- index.vue
    |  |  |-- vuex
    |  |    |-- index.js
    |  |    |-- index.scss
    |  |    |-- index.vue
    |  |-- router//路由配置
    |  |  |-- index.js
    |  |-- store//vuex配置
    |    |-- all.js
    |    |-- gather.js
    |    |-- index.js
    |-- static//cdn资源
      |-- 备注.txt

最后

为了帮助大家让学习变得轻松、高效,给大家免费分享一大批资料,帮助大家在成为全栈工程师,乃至架构师的路上披荆斩棘。在这里给大家推荐一个前端全栈学习交流圈:???866109386.欢迎大家进群交流讨论,学习交流,共同进步。

当真正开始学习的时候难免不知道从哪入手,导致效率低下影响继续学习的信心。

但最重要的是不知道哪些技术需要重点掌握,学习时频繁踩坑,最终浪费大量时间,所以有有效资源还是很有必要的。

最后祝福所有遇到瓶疾且不知道怎么办的前端程序员们,祝福大家在往后的工作与面试中一切顺利。


http://www.niftyadmin.cn/n/1300777.html

相关文章

函数中定义静态变量,虽多次调用函数,但只定义一次!!!!

函数中定义静态变量&#xff0c;虽多次调用函数&#xff0c;但只定义一次&#xff01;&#xff01;&#xff01;&#xff01; 少废话&#xff0c;先上代码 #include<iostream> using namespace std;class A { public:A(int a1) :a(a1) {}int a; };//多次调用&#xff…

多线程下vector的安全问题

STL文档说了——vector它不支持多线程,利用以下的代码亲测, 如果是仅仅读取vector中的数据,或者是改变元素的值 多线程没有问题。 如果是添加或删除元素,则多线程肯定出问题,需要自己同步。 #include <windows.h> #include <process.h> #include <vecto…

Vue指令指令大全

1. v-text v-text主要用来更新textContent,可以等同于JS的text属性。 <span v-text="msg"></span>这两者等价: <span>{{msg}}</span>2. v-html 双大括号的方式会将数据解释为纯文本,而非HTML。为了输出真正的HTML,可以用v-html指令。…

不加锁的多线程处理方式

多线程中加锁似乎已经是业内普遍做法,其实也可以不加锁,比如设计每个线程有自己的专属队列,不存在多个线程访问同一个队列而引发的问题,因此无需加锁,以下代码即可证明,但貌似有时会崩溃,大牛们可以分析分析哪里有问题! #include <windows.h> #include <proce…

浅谈vue中的MVVM实现原理

现成MVVM 菜单教程 <!DOCTYPE html> <html><head><meta charset="utf-8"><title>Vue 测试实例 - 菜鸟教程(runoob.com)</title><script src="https://unpkg.com/vue/dist/vue.js"></script> </head&…

Windows 各种计算时间函数总结

标准C++的函数(Windows与Linux均可用) 头文件:#include <time.h>time_t time(time_t *timer);

JS module的导出和导入的实现代码

ES6在语言标准的层面上&#xff0c;实现了模块功能&#xff0c;成为浏览器和服务器通用的模块解决方案&#xff0c;完全可以取代 CommonJS 和 AMD 规范&#xff0c;基本特点如下&#xff1a; 每一个模块只加载一次&#xff0c; 每一个JS只执行一次&#xff0c; 如果下次再去加…

更改Select模型64数量的方法

我们知道Select模型的长度有限制为64,在winsock.h与winsock2.h头文件中: #ifndef FD_SETSIZE#define FD_SETSIZE 64 #endif /* FD_SETSIZE */ typedef struct fd_set { u_int fd_count; /* how many are SET? */ SOCKET fd_array[FD_SETSIZE]; /* …