当前位置: 首页 > news >正文

谷歌搜索引擎营销怎么去优化关键词

谷歌搜索引擎营销,怎么去优化关键词,dw 做网站图片之间的链接,python做网站服务器操作系统本文和个人博客同步发表 更多优质文章查看个人博客 前言 手动搭建 vue ssr 一直是一些前端开发者的噩梦,因为其中牵扯到很多依赖包之间的配置以及webpack在node中的使用。就拿webpack配置来说,很多前端开发者还是喜欢用webpack-cli脚手架搭建项目。导致…

本文和个人博客同步发表
更多优质文章查看个人博客

前言

手动搭建 vue ssr 一直是一些前端开发者的噩梦,因为其中牵扯到很多依赖包之间的配置以及webpack在node中的使用。就拿webpack配置来说,很多前端开发者还是喜欢用webpack-cli脚手架搭建项目。导致这样的原因之一无外乎学习成本高,软件复杂等。这也是有些前端开发者直接拥抱nuxt.js的部分原因。这篇博客使用vue2,以步骤为主,来展示如何创建完整ssr开发环境。

主要参考文章

  1. vue2 ssr 中文官网
  2. webpack 中文官网
  3. webpack-dev-middleware
  4. webpack-hot-middleware
  5. webpack tapable
  6. memory-fs
  7. express
  8. vue2 ssr 官方参考案例

构建ssr所需依赖包

{"devDependencies": {"chokidar": "^3.5.3","css-loader": "^6.7.3","memory-fs": "^0.5.0","vue-loader": "^15.9.8","vue-style-loader": "^4.1.3","webpack": "^5.54.0","webpack-dev-middleware": "^5.2.1","webpack-hot-middleware": "^2.25.1","webpack-node-externals": "^3.0.0"},"scripts": {"dev": "node ./server/index.cjs"},"dependencies": {"express": "^4.18.2","vue": "^2.6.14","vue-router": "^3.5.2","vue-server-renderer": "^2.6.14","vue-template-compiler": "^2.6.14","vuex": "^3.6.2","vuex-router-sync": "^5.0.0"}
}

目录结构

需要创建如下图的目录结构,方能进行后面的代码编写
请添加图片描述

ssr是如何生成的?

了解完目录架构之后,首先需要知道ssr是如何生成的是至关重要的,只有这样我们才了解后续通过什么样的操作来构建ssr。首先看一张官方给出的构建图。
请添加图片描述

从图中可以看出,想要实现ssr,必选通过webpack构建生成的服务端bundle文件和客户端bundle文件,服务端bundle在bundle renderer的作用下生成html字符串,并发送给浏览器端并且和客户端bundle一起作用下激活,最终实现ssr。代码中创建html字符串和发送到浏览器的实现如下所示

let devServerPromise = devServer((serverBundle, options) => {// 服务端生成的bundele和客户端生成的clientManifest结合,并返回rendererrenderer = createBundleRenderer(serverBundle, Object.assign(options, {runInNewContext: false,}))
});ROUTER.get('*', (req, res) => {const context = {url: req.url}devServerPromise.then((random) => {// 将 Vue 实例渲染为字符串,发送给客户端renderer.renderToString(context).then(html => {res.send(html)}).catch(err => {console.log('err',req.url,err)})})
})

其实在构建图中还少一个关键点,如下所示
请添加图片描述

webpack在编译客户端时会生成客户端构建清单(clientManifest),清单里面的内容其实是当html字符串在浏览器中解析时要获取的资源内容(也可简单理解为client bundle),当解析html时根据内容去请求client bundle。

好!到目前为止,实现vue ssr思路已经很清晰了。在这里简单梳理一下。

需要
需要
依赖
依赖
生成
生成
生成
生成html字符串
webapck
clientBundle
renderToString函数
serverBundle
clientManifest

根据上面的图可知,想要实现ssr,就是需要serverBundle、clientBundle、clientManifest文件。而这三种文件又是通过webpack生成的。所以现在的要面临的问题就是配置webpack,生成这三种文件,然后通过renderToString函数实现ssr。话不多说开始配置。

配置 webpack

在配置webpack之前,需要先创建要打包的代码、app.js、entry-client.js和entry-server.js。创建这些内容的作用是为后续webpack打包做准备。

1. 创建UAC(Universal Application Code)相关文件

请添加图片描述

在ssr-demo文件夹下的client文件夹中,分别创建router、store和views文件夹和相关内容。

1.1 router文件夹和相关内容


// 路径 client/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:'/',component: () => import('../views/index.vue')},]})
}

1.2 store文件夹和相关内容


// 路径 client/store/index.js
import Vue from 'vue'
import Vuex from 'vuex'Vue.use(Vuex);export function createStore(){return new Vuex.Store({state:{},actions:{},mutations:{}})
}

1.3 view文件夹和相关内容


<!-- 路径 client/views/index.vue -->
<template><div>kinghiee ssr test</div>
</template><script>
export default {}
</script><style></style>

2. 创建app.js文件

请添加图片描述


// 路径 client/app.js
import Vue from 'vue';
import App from './App.vue';
import { createRouter } from './router/index';
import { createStore } from './store';
import { sync } from 'vuex-router-sync';// 简单工厂模式创建vue实例
export function createApp() {const router = createRouter();const store = createStore();sync(store, router);const app =  new Vue({router,store,render: h => h(App),})return {app,router,store}
};

app.js文件的作用: 使用简单工厂模式,创建vue实例,为每个请求创建新的应用程序实例,避免状态单例。更加详细解释请查看官网;

3. 创建entry-client.js文件

请添加图片描述


// 路径 client/entry-client.js
import { createApp } from './app';
import Vue from 'vue';const { app, router, store } = createApp();Vue.mixin({/*** 路由更新触发组件内异步获取数据方法* @param {*} to* @param {*} from* @param {*} next*/beforeRouteUpdate(to, from, next) {const { asyncData } = this.$optionsif (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))})if (!activated.length) {return next();}Promise.all(activated.map(c => {if (c.asyncData) {return c.asyncData({ store, route: to })}})).then(() => {next();}).catch(next)})app.$mount('#app')
})

4. 创建entry-server.js文件

请添加图片描述

// 路径 client/entry-server.js
import { createApp } from './app'export default context => {return new Promise((resolve, reject) => {const { app, router, store } = createApp()router.push(context.url)router.onReady(() => {const matchedComponents = router.getMatchedComponents()if (!matchedComponents.length) {return reject({ code: 404 })}// 对所有匹配的路由组件调用 `asyncData()`Promise.all(matchedComponents.map(Component => {if (Component.asyncData) {return Component.asyncData({store,route: router.currentRoute})}})).then(() => {// 在所有预取钩子(preFetch hook) resolve 后,// 我们的 store 现在已经填充入渲染应用程序所需的状态。// 当我们将状态附加到上下文,// 并且 `template` 选项用于 renderer 时,// 状态将自动序列化为 `window.__INITIAL_STATE__`,并注入 HTML。context.state = store.stateresolve(app)}).catch(reject)}, reject)})
}

注:entry-client.js和entry-server.js文件以及文件中为什么这么写在官网中都能找的到,这里只说明搭建步骤 官网

相关代码创建完毕之后,client文件夹内容大致如下

请添加图片描述

到此为止,webpack打包前期的准备工作已经结束。接下来开始配置webpack打包

5. webpack配置

请添加图片描述
在ssr webpack打包配置中分为客户端和服务端配置。客户端打包配置主要生成后面用到的客户端bundle和clientManifest,而服务端打包配置主要生成后面用到的服务端bundle,这三种文件正是ssr所需的关键文件。

5.1 客户端打包配置

// 路径 build/webpack.client.dev.js
const { resolve: RESOLVE } = require('path');
const WEBPACK = require('webpack');
const { VueLoaderPlugin: VUELOADERPLUGIN } = require('vue-loader');
const VUESSRCLIENTPLUGIN = require('vue-server-renderer/client-plugin')module.exports = {mode: 'development',entry: { app: RESOLVE(__dirname, '../client/entry-client.js') },output: {path: RESOLVE(__dirname, '../dist'),filename: 'src/[name].[contenthash:6].js',publicPath: '/dist/'},module: {rules: [{test: /\.vue$/,loader: 'vue-loader'},{test: /\.css$/i,use: ["vue-style-loader", "css-loader"],},]},resolve: {extensions: ['.js', '.ts', '.vue', '.json'],alias: {'@client':RESOLVE(__dirname,'../client')}},plugins: [new VUELOADERPLUGIN(),new VUESSRCLIENTPLUGIN({filename: 'src/vue-ssr-client-manifest.json'})]
}

5.2 服务端打包配置


// 路径 build/webpack.server.dev.js
const { resolve: RESOLVE } = require('path');
const { VueLoaderPlugin: VUELOADERPLUGIN } = require('vue-loader');
const VUESERVERPlUGINSSR = require('vue-server-renderer/server-plugin')
const NODEEETERNALS = require('webpack-node-externals');module.exports = {target: 'node',devtool: 'eval-cheap-source-map',entry: RESOLVE(__dirname, '../client/entry-server.js'),output: {path: RESOLVE(__dirname, '../dist'),filename: 'server-bundle.js',libraryTarget: 'commonjs2'},module: {rules: [{test: /\.vue$/,loader: 'vue-loader'},{test: /\.css$/i,use: ["vue-style-loader", "css-loader"],},]},externalsPresets: { node: true }, // in order to ignore built-in modules like path, fs, etc.externals: [NODEEETERNALS()], // in order to ignore all modules in node_modules folderplugins: [new VUELOADERPLUGIN(),new VUESERVERPlUGINSSR({filename: 'src/vue-ssr-server-bundle.json'})]
}

webpack 客户端和服务端都已配置好了,那如何生成相应的三种文件呐?其实生成这三种文件需要在webpack编译阶段,而对于配置开发环境来说,一般还用到热更新和webpack dev中间件,所以webpack编译和热更新常在一起出现。目前为止该准备的都已经到位了,现在就可以开始webpack编译和配置热更新操作了。

webpack编译和配置热更新

在server文件夹内创建如下文件夹和文件
请添加图片描述
请添加图片描述

1. webpack编译

本博客给出的代码示例和官方的示例组织上有不同的地方,但功能上一样。本博客按照功能的不同对代码进行了合理的拆分和封装,而不是把全部功能写到一个函数下面。这样做的目的是关注点单一、功能单一、便于开发和维护。

1.1 编译客户端

// 路径 dev/clientCompile.cjs
let webpack = require('webpack');
let path = require('path');module.exports = function clientCompile(clientConfig, clientManifestCb) {// 向客户端 webpack 修改配置clientConfig.entry.app = [ 'webpack-hot-middleware/client?path=/__webpack_hmr&timeout=2000&reload=true', clientConfig.entry.app ];clientConfig.output.filename = '[name].[contenthash:6].js';clientConfig.plugins.push(new webpack.HotModuleReplacementPlugin(),new webpack.NoEmitOnErrorsPlugin(),); // 编译客户端 webpack 配置let clientCompiler = webpack(clientConfig); // 获取 compiler 实例let devMiddleware = require('webpack-dev-middleware')(clientCompiler, {publicPath: clientConfig.output.publicPath,serverSideRender: true,stats: {//可选colors: true,modules: true,},});// done是AsyncSeriesHook类型钩子clientCompiler.hooks.done.tap('done', stats => {stats = stats.toJson({stats:'errors-warnings'});// 如果客户端编译完毕,有错误或者警告会打印到控制台stats.errors.forEach(err => console.error(err));stats.warnings.forEach(err => console.warn(err));// 有错误后续不生成 manifest 文件if (stats.errors.length) return;console.log('\n客户端更新...\n');let manifestContent = devMiddleware.context.outputFileSystem.readFileSync(path.resolve(clientConfig.output.path, 'src/vue-ssr-client-manifest.json'),'utf-8');clientManifestCb(JSON.parse(manifestContent));});let hotMiddleware = require('webpack-hot-middleware')(clientCompiler); return {devMiddleware,hotMiddleware}
}

如上代码所示,把编译客户端的代码写到一个文件内,并导处客户端编译函数,在其他地方用。

1.2 编译服务端

// 路径 dev/serverCompile.cjs
let webpack = require('webpack');
let path = require('path');
const MFS = require('memory-fs');module.exports = function serverCompile(serverConfig,serverBundleCb
) {let serverCompiler = webpack(serverConfig); let mfs = new MFS();serverCompiler.outputFileSystem = mfs; // 把 webpack 默认的普通文件系统更换为内存文件系统serverCompiler.watch({ ignored: /node_modules/, }, (err, stats) => {if (err) throw err;stats = stats.toJson();// 有错误后续不执行if (stats.errors.length) return;console.log('\n服务端更新...\n');// 获取服务端bundle文件路径let bundlePath = path.resolve(serverConfig.output.path,'src/vue-ssr-server-bundle.json');serverBundleCb(JSON.parse(mfs.readFileSync(bundlePath, 'utf-8')))});
}

写完客户端和服务端编译后,需要把函数导出来在后面的地方使用。代码如下

2. webpack编译和热更新配置

dev.cjs文件如下

// 路由 router/dev.cjs
const SERVER = require('express');
const ROUTER = SERVER.Router();
const FS = require('fs');
const PATH = require('path');
let clientConfig = require('../../build/webpack.client.dev');
let serverConfig = require('../../build/webpack.server.dev'); 
let templatePath =  PATH.resolve(__dirname, '../server.template.html');
let { createBundleRenderer } = require('vue-server-renderer')
let serverCompile = require('../dev/serverCompile.cjs');
let clientCompile = require('../dev/clientCompile.cjs');
let tempWatch = require('../dev/tempWatch.cjs');let renderer;const devServer = (cb) => {let clientManifest, serverBundle, readyResolve, templateContent;templateContent = FS.readFileSync(templatePath, 'utf-8');let readyPromise = new Promise(resolve => readyResolve = resolve );// 更新客户端和服务端内容let updateClientAndServer = () => {// 只有构建清单文件都存在时,执行更新操作if(clientManifest && serverBundle) {readyResolve(); // 把promise resolve掉cb(serverBundle, {template: templateContent,clientManifest})}};// 监听模板文件tempWatch(templatePath, () => {updateClientAndServer();});// 客户端 编译let { devMiddleware, hotMiddleware} = clientCompile(clientConfig, (clientManifestContent) => {clientManifest = clientManifestContent;updateClientAndServer();})ROUTER.use(devMiddleware);ROUTER.use(hotMiddleware); // 服务端 编译serverCompile(serverConfig, (serverBundleContent) => {serverBundle = serverBundleContent;updateClientAndServer();})return readyPromise;
}let devServerPromise = devServer((serverBundle, options) => {renderer = createBundleRenderer(serverBundle, Object.assign(options, {runInNewContext: false,}))
});

在devServer函数中,分别使用clientCompile函数,编译客户端。在回调函数把生成的clientManifestContent内容赋值给clientManifest,然后通知updateClientAndServer函数完成后续内容。同时clientCompile函数也返回了两个中间件并放入use函数中,完成后续的热更新和webpack dev server. 和clientCompile函数类似,serverCompile函数,它也是在回调函数中把生成的serverBundleContent赋值给serverBundle,并通知updateClientAndServer函数完成其他内容。在updateClientAndServer函数中,当clientManifest和serverBundle内容都有时,就可以把promise resolve掉,进而可以调用renderToString函数生成html字符串,发送给浏览器,最后实现ssr。

在devServer函数中还是用了tempWatch,该函数的作用是当模板文件发生变化时,更新相关内容。代码如下

// 路径 dev/tempWatch.cjs
let fs = require('fs');
let chokidar = require('chokidar');module.exports = function tempWatch(templatePath, watchCb) {// 监听模板html文件 changechokidar.watch(templatePath).on('change', () => {console.log('模板更新中...');templateContent = fs.readFileSync(templatePath, 'utf-8');console.log('模板更新成功!');// 更新模块watchCb();});
}

到此为止,vue ssr的配置和生成基本结束。但是现在通过浏览器还是访问不了,还需要最后一步配置服务器

配置服务器提供访问

在dev.cjs中添加路由配置,然后导处路由,在程序入口处使用。

// 路径 router/dev.cjs
const SERVER = require('express');
const ROUTER = SERVER.Router();
const FS = require('fs');
const PATH = require('path');
let clientConfig = require('../../build/webpack.client.dev');
let serverConfig = require('../../build/webpack.server.dev'); 
let templatePath =  PATH.resolve(__dirname, '../server.template.html');
let { createBundleRenderer } = require('vue-server-renderer')
let serverCompile = require('../dev/serverCompile.cjs');
let clientCompile = require('../dev/clientCompile.cjs');
let tempWatch = require('../dev/tempWatch.cjs');let renderer;const devServer = (cb) => {let clientManifest, serverBundle, readyResolve, templateContent;templateContent = FS.readFileSync(templatePath, 'utf-8');let readyPromise = new Promise(resolve => readyResolve = resolve );// 更新客户端和服务端内容let updateClientAndServer = () => {// 只有构建清单文件都存在时,执行更新操作if(clientManifest && serverBundle) {readyResolve(); // 把promise resolve掉cb(serverBundle, {template: templateContent,clientManifest})}};// 监听模板文件tempWatch(templatePath, () => {updateClientAndServer();});// 客户端 编译let { devMiddleware, hotMiddleware} = clientCompile(clientConfig, (clientManifestContent) => {clientManifest = clientManifestContent;updateClientAndServer();})ROUTER.use(devMiddleware);ROUTER.use(hotMiddleware); // 服务端 编译serverCompile(serverConfig, (serverBundleContent) => {serverBundle = serverBundleContent;updateClientAndServer();})return readyPromise;
}let devServerPromise = devServer((serverBundle, options) => {renderer = createBundleRenderer(serverBundle, Object.assign(options, {runInNewContext: false,}))
});ROUTER.get('*', (req, res) => {const context = {url: req.url}devServerPromise.then(() => {renderer.renderToString(context).then(html => {res.send(html)}).catch(err => {console.log('err',req.url,err)})})
})module.exports = ROUTER;

在程序入口处使用该路由


// 路径 server/index.cjs
const SERVER = require('express')();
const SSRROUTER = require('./router/dev.cjs');
const PORT = 8000;SERVER.use(SSRROUTER);SERVER.listen(PORT,() => {console.log(`app listening at port ${PORT}`);
});

最后输入npm run dev启动项目,结果如下

请添加图片描述

注: 配置ssr的过程有点繁琐,如果途中有配置错的地方可以查看我的github ssr demo

如果博客中有什么不理解的或者错误内容,欢迎指出,及时更正

http://www.zhongyajixie.com/news/139.html

相关文章:

  • 个人网站备案通过做淘客西安疫情最新通知
  • 电商网站会员体制怎么做seo关键词首页排名
  • 网站建设的战略作用个人如何做网络推广
  • 秦皇岛北京网站建设seo综合查询平台官网
  • 重庆有的设计网站大全足球世界排名一览表
  • 北京社保网站做社保增减员拼多多标题关键词优化方法
  • 吴江做网站公司百度浏览器打开
  • 无锡哪里有做网站公众号推广一个6元
  • 建湖网站建设网络优化网站
  • 互联网是什么工作网站seo怎么做
  • 二合一收款码免费制作网站seo就业哪家好
  • 平台网站建设在哪里seo顾问张智伟
  • 业务员怎样找客户 新手seo设置是什么
  • 如何做网站流量分析报表怎么开发自己的小程序
  • 关于政府网站集约化建设的通知seo是什么技术
  • 网站建设与维护asp双11销量数据
  • 网站建设色常见的网络营销推广方式有哪些
  • 有哪些靠谱的建站公司广州 关于进一步优化
  • 做爰直播网站中国国家培训网是真的吗
  • 电商网站搭建流程如何制作网址
  • 在荔浦找事情做投简历那个网站seo可以提升企业网站的
  • 页面布局在哪里seo网站优化培训怎么样
  • 网站后台信息管理怎么做制作一个网站的基本步骤
  • 克拉玛依做网站长沙网站制作费用
  • 天津南开做网站白帽seo是什么
  • 做挖机配件销售的网站西点培训学校
  • 手机做炫光头像图的网站建立一个网站需要花多少钱
  • 对接标准做好门户网站建设长沙网站推广服务公司
  • 厦门外贸网站建设网站推广计划书
  • 番禺建设银行网站首页seo推广方法有哪些