构建专栏系列目录入口javascript
刘崇桢,微医前端技术部门户支撑组html
Webpack
的使用目前已是前端开发工程师必备技能之一。如果想在本地环境启动一个开发服务快速开发咱们的应用(而不是每次 coding 完,手动执行 run build,全量打包),你们只需在 Webpack
的配置中,增长 devServer 的配置便可。它的做用主要是用来伺服资源文件。 webpack-dev-server(如下简称 wds)
已经为咱们封装好了全面、丰富且可配置化的功能,配置工程师们只需经过 webpack.config
和 命令行参数
便可知足开发所需。 然而配置工程师们,发现 wds
的 hot
、live reload
实际上至关于启用了一个 express
的 Http 服务器
+ webpack-dev-middleware(如下简称 wdm)
等中间件。这个 Http 服务器
和 用户访问服务的 client
能够经过 websocket
通信协议创建长链接,在 webpack
'watch' 到原始文件做出改动后,wds
会使用 webpack
的实时编译,再用 wdm
将 webpack
编译后文件会输出到内存中。每当应用程序请求一个文件时,wdm
匹配到了就把内存中缓存的对应结果以文件的格式返回给 client
,反之则进入到下一个中间件。 若是想要使用更多 wds
提供的配置功能,好比 proxy
、static
、open
等, 在 server
端增长中间件便可,这样配置工程师摇身一变,配置开发工程师! 项目中的 devServer
咱们更可能是使用 webpack
+ express
+ webpack-dev-middleware
+ webpack-hot-middleware
的组合来完成 HMR
。在系列文章中,有更加具体详细的学习分享来介绍这些,这里收缩一下,咱们只关注 webpack-dev-server
。 前端
Use webpack with a development server that provides live reloading. This should be used for development only. It uses webpack-dev-middleware under the hood, which provides fast in-memory access to the webpack assets. java
webpack 配合一个开发服务器,能够提供热重载功能。但只用于开发模式下。 wds 的底层,集成了 wdm,能够提供快速内存访问打包资源的功能。node
以上是 wds 对本身的一个简短自我介绍,咱们来搞清楚它这么归纳的点:webpack
webpack 能够经过 watch mode 的方式启动,指示 webpack 'watch' 依赖图中全部文件的更改,而且自动打包。可是每次打包后的结果将会存储到本地硬盘中,而 IO 操做是很是耗资源时间的,没法知足本地开发调试需求。 wds 则能够提供一个开发服务器,而且提供 live reloading(实时重载)功能,在打包完成后通知客户端,刷新页面从新加载资源。git
wdm 能够将 webpack 编译后的资源输出到内存中,当应用程序请求资源时,能够直接从内存中进行响应。 开发中,咱们还会注意到,在编译期间,客户端的请求会被 delay 到最新的编译结果完成以后才会去响应。github
「wdm」: wait until bundle finished: /myapp/
复制代码
平常开发修改完代码后,你有没有傻乎乎地,手动刷新调试页面,来验证如今的 bug 仍是不是以前的那个 bug? 其实 HMR 会在应用程序运行过程当中,替换、添加或删除模块,而无需从新加载整个页面。 能够经过如下几种方式,来显著提速开发效率:web
有时候服务启动后,会打开 localhost:8080,或者打开浏览器的多个页签ajax
前端项目中,借助 history api,能够作到改变视图而不向后端发出请求。若是你手动刷新一个路由中匹配不到的页面,同时你的项目中没有配置 404 页面兜底逻辑,那就真的 404 Not Found 了。 wds 配置中有一项配置 historyApiFallback,能够配置一个页面代替全部的 404 响应。
「wds」: 404s will fallback to /index.html
复制代码
wds 的 proxy 配置使用方法,能够详见 Webpack-dev-server 的 proxy 用法。
控制代码打包编译时出现警告和错误时,是否在页面上显示错误信息
output.path 打包产物的绝对路径,没有什么疑问。对于 output.publicPath、devServer.publicPath,仍是有点疑惑不解吧? 假若有一个域名 example.com,可是大家应用部署在 example.com/myapp/ 。没有指定 output.publicPath,默认为 '/',这时 index.html 引用其余模块的 url 会是 /bundle.xxxhashxxx.js,这时这个资源的 url 就变成了 example.com/bundle.xxxhashxxx.js,毫无疑问,这个资源会 404 Not Found。若是指定 output.publicPath: '/myapp/',那么 index.html 中资源的 url 就变成了 '/myapp/bundle.xxxhashxxx.js'。 同理 wds 中,指定 devServer.publicPath: '/myapp/',devServer 就会在 http://localhost:8080/myapp/ 下伺服资源访问。模拟生产环境下的运维配置。
「wds」: webpack output is served from /myapp/
复制代码
contentBase 呢?它只做用于 wds,只有你想要伺服静态资源文件的时候使用。换句话说,wds 会加载这个文件夹下的静态资源到服务器,而不须要 bundle 这个文件夹。 假如,你的 app 中须要加载一些 mp4 文件,这些文件基本不会被改动,因此你没必要把这些资源打包到 /dist 文件下,能够把这些文件维护在 /src、/dist 的同级目录下的 /static。而后设置 contentBase: path.join(__dirname, 'static'),而后就能够在代码中这样引用静态资源了 。
「wds」: Content not from webpack is served from /Volumes/bomb/git/webpack-learning/webpack-demo/static
复制代码
为了验证咱们构建的自洽模型,可以自洽,咱们须要一个参照物来进行修正。 咱们使用 devServer 官方配置,来伺服资源文件。 为了避免影响体验,自洽模型 和 参考物 的代码都维护在第四节的参考,有兴趣的能够本身 debugger 一下。
首先咱们使用 express 启动一个本地 server,让浏览器能够访问本地的静态资源。
// wds.server.js
const app = express();
const listeningApp = http.createServer(app);
listeningApp.listen('8888', '127.0.0.1', (err) => {
createSocketServer();
});
复制代码
这里建立 http 服务器,没有使用 app.listen('8888', callback),而是使用 http.createServer(app) 的缘由有两点:
wds 调用 webpack api 对文件系统进行 'watch',当文件发生改变后,webpack 会从新对文件进行编译打包,而后保存到内存中。 这一系列操做,主要有两点:一、watch 文件更改;二、内存响应。所幸,wdm 完成了这部分功能,咱们在自洽模型中直接引用 wdm 。
// wds.server.js
const webpack = require('webpack');
const wdm = require('webpack-dev-middleware');
const config = require('./webpack.config.js');
const compiler = webpack(config); // 将 webpack.config.js 配置文件做为基础配置
const app = express();
app.use(wdm(compiler)) // 告知 express 使用 webpack-dev-middleware
复制代码
这里不难看出,wdm(compiler) 的执行结果返回的是一个中间件,它将 webpack 编译后的文件存储到内存中,而后在用户访问 express 服务时,将内存中对应的资源输出返回。 那么 wdm 内部是如何实现的呢? wdm 的源码并很少,其核心只有 /index.js,/lib/middleware。
// webpack-dev-middleware/index.js
// 在 compiler 的 invalid、run、done、watchRun 这 4 个编译生命周期上,注册对应的处理方法。
// 经过 tapable 来调用插件功能,主要是 report 编译的状态信息以及执行 context.callbacks 回调函数
const context = createContext(compiler, options);
...
// 以监控的方式启动 webpack,调用 compiler 的 watch 方法,以后 webpack 便会监听文件变动,一旦检测到文件变动,就会从新执行编译。
context.watching = compiler.watch(options.watchOptions, (err) => { ... });
...
// 使用 memory-fs,将 webpack 的编译内容,输出至内存中
setFs(context, compiler);
复制代码
// webpack-dev-middleware/lib/middleware
// 核心逻辑是:针对 request 请求,根据各类条件判断,最终返回对应的文件
module.exports = function wrapper(context) {
// 返回 express 中间件函数的包装函数
return function middleware(req, res, next) {
// 若是不是 SSR,直接 next,流转到下一个中间件
// 若是是 SSR,调用 util/ready,根据 state 判断执行回调 fn,仍是将 fn 存储到 callbacks 队列中
// ready 也是“在编译期间,中止提供旧版的 bundle 而且将请求延迟到最新的编译结果完成以后”的实现
function goNext() { ... }
// 根据请求的 req.url 地址,在 compiler 的内存文件系统中查找对应的文件,若查找不到,则直接调用 goNext() 方法处理请求
let filename = getFilenameFromUrl( ... )
if (filename === false) {
return goNext();
}
// 根据上文找到的 filename 路径获取到对应的文件内容,并构造 response 对象返回
// 最后也是调用 ready
return new Promise((resolve) => {
handleRequest(context, filename, processRequest, req);
function processRequest() {
...
}
})
}
}
复制代码
使用 HMR 的过程当中,经过 network 咱们知道 client 端 是经过 websocket 和 server 端 进行通讯的。client 端 和 server 端 之间创建一个 websocket 长链接,将 webpack 编译打包的各个阶段的状态信息告知 client 端。最关键的仍是 wds 注册 compiler hooks(compile、done、watchRun 等),当进入 webpack compile 生命周期时调用 hooks 回调注册方法。 compilation done 时,server 端 传递的最主要的信息是 'stats.hash' 和 'ok',而后 client 端 根据 hash 进行模块热更新。
// wds.server.js
let connect = null // 长链接实例
// 调用 webpack api 监听 compile 的 done 事件
// 注册 compiler hooks -- done
const { done } = compiler.hooks
done.tap('myappPligins', (stats) => {
if (connect) {
let _stats = stats.toJson({...})
// 将编译打包后的新模块 hash 值发送到 client 端
connect.write(JSON.stringify({
"type": "hash",
"data": _stats.hash
}))
// 通知 client 端编译完成,能够进行 reloadApp 操做
connect.write(JSON.stringify({
"type": "ok"
}))
}
});
// 建立 websocket server(wss)
// 目前 webpack-dev-server@4.X 使用 sockjs 会出错,webpack-dev-server@3.X 使用 ws 会报错
function createSocketServer () {
let socket = sockjs.createServer({
sockjs_url: './sockjs-client'
});
// 复用 http 服务器实例 listeningApp
socket.installHandlers(listeningApp, {
prefix: '/sockjs-node',
});
socket.on('connection', (connection) => {
connect = connection
...
});
}
复制代码
咱们在业务代码中并无添加接收 wss 消息的代码,那 client 端 的逻辑怎么实现的呢? 其实 wds 修改了 webpack.config.js 的基础配置,它会往 chunk 中偷偷塞入两个文件 webpack-dev-server/lib/client/index.js 和 webpack/hot/dev-server。 咱们在自洽模型中也这么操做,这样这两段代码就植入到 client 端了。
// wds.server.js
config.entry.app = [require.resolve('webpack/hot/dev-server'), './wds.client.js', config.entry.app]
// HMR 做为一个 Webpack 内置的功能,能够经过 HotModuleReplacementPlugin 开启
config.plugins.push(
new webpack.HotModuleReplacementPlugin()
)
复制代码
client 端 经过 websocket 接收 server 端 最新编辑后的模块 hash 值,这个值会被存起来(currentHash),在接收到 ok 后才会 reloadApp。 若是配置了 hot,开启 HMR,会把程序控制权交给 webpack 的客户端代码进行 HMR。若是没有开启,就直接调用 location.reload() 刷新页面。
// wds.client.js
const SockJS = require('./sockjs-client')
const socketUrl = 'http://127.0.0.1:8888/sockjs-node'
let currentHash = '' // 最新代码模块的 hash 值
function reloadApp () {
if (options.hot) {
let hotEmitter = require('webpack/hot/emitter');
// webpackHotUpdate 是 webpack 在 webpack/hot/dev-server.js 定义的一个事件,事件回调是获取这次编译的最新代码
hotEmitter.emit('webpackHotUpdate', currentHash);
} else if (options.liveReload) { // 没有配置 hmr,就直接 live reload 刷新页面
location.reload();
}
}
// 处理 wss 通知
const onSocketMessage = {
...
hash: function hash(_hash) {
currentHash = _hash; // wss 端 通知 client 端 最新编辑后的模块 hash 值,这个值会被存起来(currentHash),在接收到 ok 后才会 reloadApp
},
ok: function ok() {
reloadApp();
}
};
const socket = (url, handlers) => {
client = new SockJS(url)
...
client.onmessage = function (data) { // 接收 wss 通知
var msg = JSON.parse(data.data);
if (handlers[msg.type]) {
handlers[msg.type](msg.data);
}
}
...
}
socket(socketUrl, onSocketMessage)
复制代码
当 client 端 收到 ok 的通知后,开启 hot 的 wds,会执行 reload 方法,而后调用 webpackHotUpdate
// wds.client.js
let hotEmitter = require('webpack/hot/emitter');
hotEmitter.emit('webpackHotUpdate', currentHash);
复制代码
而后程序被 client 端 的 webpack 接管(第四步中咱们注入到 plugins 中的 webpack.HotModuleReplacementPlugin 就派上用场了),webpack 监听到 webpackHotUpdate
事件,并获取到最新的 hash 值,而后开始检查更新。
// webpack/hot/dev-server.js
var hotEmitter = require("./emitter");
hotEmitter.on("webpackHotUpdate", function (currentHash) {
lastHash = currentHash;
...
check();
});
// 检查更新
var check = function check() {
module.hot.check(true)
.then(updatedModules => {
...
})
}
复制代码
源码中,追踪到 module.hot.check,就不知道路该怎么走了,hot.check 是哪里来的? 系列文章中有单独介绍 HMR 的一章,这里咱们就偷个懒,粗线条的勾勒一下大体过程。 hot.check 来自于 /webpack/lib/hmr/HotModuleReplacement.runtime.js
。
hash
值,调用 hotDownloadManifest
发送 xxx.hash.hot-update.json
的 ajax
请求c: chunkIds m: removedChunks r: removedModules
chunkId.hash.hot-update.js
请求。下面就是拿到的 hot-update.js 的内容。 JSONP 返回的 js 文件当即执行,会调用
window.webpackHotUpdatewebpack_demo
方法。此方法会把更新的模块 moreModules
(图中入参的第二个参数对象)赋值给全局全量 currentUpdate
。
hotApply
进行热更新模块替换// wds.server.js
// 添加中间件 connect-history-api-fallback,解决 history api 降级
app.use(historyApiFallback({
htmlAcceptHeaders: ['text/html', 'application/xhtml+xml'], // 只对这些类型的请求进行 rewrite
rewrites: [
{ from: /./, to: '/myapp/index.html' }
]
}))
复制代码
proxy 配置,咱们常用,那 node 是如何代理请求的呢?
在 wds 中,借助建立的 http 服务器,其 proxy 功能的实现就是解析配置项,并挂载 http-proxy-middleware
中间件到 http 服务上。结合 proxy 的用法,wds/lib/Server.js 中的代码显得一目了然。 http-proxy-middleware 则借助于 node-http-proxy,用于将 node 服务端接收到的请求,转发到目标服务器,实现代理服务器的功能。 能够预见,整个流程的大体实现思路就是,经过配置项注册全局的请求转发规则,在中间件中拦截客户端的 request 请求匹配转发规则,而后调用 node-http-proxy 的 .web
、.ws
方法进行转发请求。 http-proxy-middleware 将转发规则分为两大类进行配置,context 和 options。
// Proxy middleware configuration.
var proxy = require('http-proxy-middleware');
var apiProxy = proxy('/api', { target: 'http://www.example.org' });
// \____/ \_____________________________/
// | |
// context options
// 'apiProxy' is now ready to be used as middleware in a server.
复制代码
context 用于匹配须要进行转发的客户端请求,默认值是 '/',客户端发起的全部请求都会被转发;也能够字符串 url、字符串 url 数组、通配符或者个性化方法,来决定哪些请求会被代理转发。http-proxy-middleware 使用 options 中
// rewrite path
pathRewrite: {'^/old/api' : '/new/api'}
// remove path
pathRewrite: {'^/remove/api' : ''}
// add base path
pathRewrite: {'^/' : '/basepath/'}
// custom rewriting
pathRewrite: function (path, req) {}
router: {
'integration.localhost:3000' : 'http://localhost:8001', // host only
'staging.localhost:3000' : 'http://localhost:8002', // host only
'localhost:3000/api' : 'http://localhost:8003', // host + path
'/rest' : 'http://localhost:8004' // path only
}
复制代码
// http-proxy-middleware/lib/index.js
function HttpProxyMiddleware(context, opts) {
...
var config = configFactory.createConfig(context, opts) // 解析获取 context、options
...
var proxy = httpProxy.createProxyServer({}) // 建立代理服务器,由这个服务器进行转发请求
...
var pathRewriter = PathRewriter.create(proxyOptions.pathRewrite) // 将客户端请求路径转化为目标服务器的路径(pathname 部分),既能够是 key-value,也能够函数。
...
function shouldProxy(context, req) { // 判断请求是否须要转发
var path = req.originalUrl || req.url
return contextMatcher.match(context, path, req) // 经过多种匹配方法校验客户端 req 是否须要转发
}
function prepareProxyRequest(req) {
req.url = req.originalUrl || req.url
var originalPath = req.url
var newProxyOptions = _.assign({}, proxyOptions)
__applyRouter(req, newProxyOptions) // 遍历 options.router,校验是否匹配客户端 req,匹配的话就改写此次请求的 host
__applyPathRewrite(req, pathRewriter) // 若是有 pathRewriter,就匹配当前请求,匹配的话就将设置的目标服务器路径写入 req.url
return newProxyOptions
}
...
function middleware(req, res, next) { // 真正的代理中间件
if (shouldProxy(config.context, req)) {
var activeProxyOptions = prepareProxyRequest(req)
proxy.web(req, res, activeProxyOptions) // node-http-proxy 进行代理转发
} else {
next()
}
}
...
return middleware
}
复制代码
这样看来,http-proxy-middleware 主要作的是解析转发规则、最终把代理转发的事情交给了 node-http-proxy,同时配置了相关的 Logger、绑定事件。
本文从开发过程当中遇到的痛点出发,梳理了现代打包工具对咱们平常开发的帮助和提效,并自娱自乐的结合表现和源码,照虎画猫完成了所谓的自洽模型。其实自洽模型画的远不如猫,最多就是一个四支腿生物的简笔画了。权当梳理了一遍 wds 的工做流程,更加细节的东西,还须要你们一块儿动手才能挖掘出来,但愿能对你的理解过程起到必定的帮助做用。
"webpack": "5.24.0", "webpack-cli": "4.5.0", "webpack-dev-server": "^3.11.2"
// webpack.config.js 使用通用的 Vue 项目配置
module.exports = {
mode: 'development',
entry: {
app: './src/app.js'
},
output: {
filename: '[name].bundle.js',
path: path.resolve(__dirname, 'dist'),
publicPath: '/myapp/'
},
devtool: 'inline-source-map',
plugins: [
...some plugins
],
module: {
rules: [ ...some loaders ]
}
};
复制代码
而后把 devServer 的配置单独维护在 webpack.dev.js
// webpack.dev.js devServer 配置
module.exports = merge([
base, // webpack.config.js
{
mode: 'development',
devServer: {
host: '127.0.0.1', // 服务器 host,默认为 localhost
port: 7777, // 服务器端口号,默认为 8080
open: true, // string | boolean,启动后是否打开浏览器,当为字符串时,打开指定浏览器
openPage: 'myapp/', // string | Array<string>, ['', 'index.html'], 'index.html', 打开浏览器后默认打开的页面,Array 打开多个页面
compress: true,
hot: true, // 是否启动热更新(HMR),热更新使用的是 webpack 中 HotModuleReplacementPlugin
http2: false, // 是否设置 HTTP/2 服务器,为 true,则默认使用 https 做为服务
// https: {
// key: '',//fs.readFileSync('/path/to/server.key'),
// cert: '',//fs.readFileSync('/path/to/server.crt'),
// ca: '',//fs.readFileSync('/path/to/ca.pem')
// },
proxy: {
'/api': {
target: 'http://localhost:7777',
pathRewrite: { '^/api': '' },
secure: false // HTTPS 设置为无效证书
}
},
// 静态文件属性
publicPath: '/myapp/', // 挂载到服务器中间件的可访问虚拟地址
contentBase: path.join(__dirname, 'static'), // devServer 伺服这个文件夹下的静态资源。换句话说会加载本地 /static 目录下的静态文件到服务器
stats: 'minimal',
// 设置编译出错或警告后,页面是否会直接显示信息, boolean | {}
// 默认为 false,当失败后会显示空白页
// 设置为 true 后,编译失败会显示错误/警告的覆盖层,也能够设置为 object,显示多种类型信息
overlay: {
warnings: true,
errors: true
},
injectClient: true, // 是否要注入 WebSocket 客户端,将此属性设置为 false,那么 hot、overlay 等功能都会失效
injectHot: true, // 是否注入 HMR, 这个属性是 injectClient 的子集。只影响热更新
liveReload: false, // 是否开启自动刷新浏览器功能,优先级低于 hot
// 是否将全部 404 页面都跳转到 index.html,当此属性设置为 true 或为 object 时而且使用 history API 时全部 404 页面会跳转到 index.html 或指定的页面
historyApiFallback: {
rewrites: [
{ from: /./, to: '/myapp/index.html' },
]
},
// 设置 WebSocket,设置使用的 WebSocket 库,内置为 sockjs 或 ws
transportMode: {
// 目前 webpack-dev-server@4.X 使用 sockjs 会出错,webpack-dev-server@3.X 使用 ws 会报错
server: 'sockjs'
}
}
}
])
复制代码
package.json
// package.json
"scripts": {
"start": "webpack serve --config webpack.dev.js",
"start:dev": "node wds.server.js",
}
复制代码
// wds.server.js
const express = require('express');
const webpack = require('webpack');
const http = require('http');
const webpackDevMiddleware = require('webpack-dev-middleware');
const historyApiFallback = require('connect-history-api-fallback');
const sockjs = require('sockjs');
const app = express();
const config = require('./webpack.config.js');
config.entry.app = [require.resolve('webpack/hot/dev-server'), './wds.client.js', config.entry.app]
config.plugins.push(
new webpack.HotModuleReplacementPlugin()
)
// 告知 express 使用 webpack-dev-middleware,
// 以及将 webpack.config.js 配置文件做为基础配置。
const compiler = webpack(config)
// historyApiFallback
app.use(historyApiFallback({
htmlAcceptHeaders: ['text/html', 'application/xhtml+xml'],
rewrites: [
{ from: /./, to: '/myapp/index.html' }
]
}))
app.use(
webpackDevMiddleware(compiler, {
publicPath: config.output.publicPath,
})
)
let connect = null
const { done } = compiler.hooks
done.tap('myappPligins', (stats) => {
if (connect) {
let _stats = stats.toJson({
all: false,
hash: true,
assets: true,
warnings: true,
errors: true,
errorDetails: false,
})
connect.write(JSON.stringify({
"type": "hash",
"data": _stats.hash
}))
connect.write(JSON.stringify({
"type": "ok"
}))
}
});
const listeningApp = http.createServer(app);
function createSocketServer () {
let socket = sockjs.createServer({
sockjs_url: './sockjs-client'
});
socket.installHandlers(listeningApp, {
prefix: '/sockjs-node',
});
socket.on('connection', (connection) => {
if (!connection) {
return;
}
connect = connection
// 通知 client enable 了哪些功能
connection.write(JSON.stringify({
"type": "hot"
}))
});
}
listeningApp.listen('8888', '127.0.0.1', (err) => {
console.log('Example app listening on port 8888!\n');
createSocketServer();
});
listeningApp.on('error', (err) => {
console.error(err);
});
复制代码
// wds.client.js
console.log('this is from client.')
const SockJS = require('./sockjs-client')
const socketUrl = 'http://127.0.0.1:8888/sockjs-node'
const options = {
hot: true,
hotReload: true,
liveReload: false,
initial: true,
useWarningOverlay: false,
useErrorOverlay: false,
useProgress: false
}
let currentHash = ''
function reloadApp () {
if (options.hot) {
console.log('[WDS] App hot update...');
let hotEmitter = require('webpack/hot/emitter');
hotEmitter.emit('webpackHotUpdate', currentHash);
// broadcast update to window
window.postMessage("webpackHotUpdate".concat(currentHash), '*');
} else if (options.liveReload) {
location.reload();
}
}
const onSocketMessage = {
hot: function hot() {
options.hot = true;
console.info('[WDS] Hot Module Replacement enabled.')
},
liveReload: function liveReload() {
options.liveReload = true;
console.info('[WDS] Live Reloading enabled.')
},
invalid: function invalid() {
console.info('[WDS] App updated. Recompiling...')
},
hash: function hash(_hash) {
currentHash = _hash;
},
ok: function ok() {
reloadApp();
},
close: function close() {
console.error('[WDS] Disconnected!');
}
};
let retries = 0
let client = null
const socket = (url, handlers) => {
client = new SockJS(url)
client.onopen = function () {
retries = 0
}
client.onmessage = function (data) {
var msg = JSON.parse(data.data);
if (handlers[msg.type]) {
handlers[msg.type](msg.data);
}
}
client.onclose = function () {
if (retries === 0) {
handlers.close();
} // Try to reconnect.
client = null; // After 10 retries stop trying, to prevent logspam.
if (retries <= 10) {
var retryInMs = 1000 * Math.pow(2, retries) + Math.random() * 100;
retries += 1;
setTimeout(function () {
socket(url, handlers);
}, retryInMs);
}
}
}
socket(socketUrl, onSocketMessage)
复制代码