首页 > Web开发 > 详细

webpack-dev-server原理解析

时间:2020-03-09 13:30:41      阅读:255      评论:0      收藏:0      [点我收藏+]

webpack-dev-server 为你提供了一个简单的 web 服务器,能够实时重新加载。以下内容将主要介绍它是如何实现实现静态资源服务以及热更新的。

静态资源服务

webpack-dev-server 会使用当前的路径作为请求的资源路径 ,就是我们运行webpack-dev-server命令的路径。可以通过指定 content-base 来修改这个默认行为,这个路径标识的是静态资源的路径 。

contentBase只和我们的静态资源相关也就是图片,数据等,需要和output.publicPath和output.path做一个区分。后面两者指定的是我们打包出文件存放的路径,output.path是我们实际的存放路径,设置的output.publicPath会在我们打包出的html用以替换path路径,但是它所指向的也是我们的output.path打包的文件。

例如我们有这么一个配置:

output: {
        filename: ‘[name].[hash].js‘, //打包后的文件名称
        path: path.resolve(__dirname, ‘.hmbird‘), //打包后的路径,resolve拼接绝对路劲
        publicPath: ‘http://localhost:9991/‘ 
    },

打包出的html模块

技术分享图片

有一个疑问就是我们contentBase指定的静态资源路径下有一个index.html,并且打包出的结果页也有一个index.html,也就是两个文件路径访问的路径相同的话,会返回哪一个文件?

  结果就是会返回我们打包出的结果页面,静态资源的优先级是低于打包出的文件的。

接下来介绍的是我们的webpack-dev-server是如何提供静态资源服务的。原理其实就是启动一个express服务器,调用app.static方法。

源码如下:

setupStaticFeature() {
    const contentBase = this.options.contentBase;
    const contentBasePublicPath = this.options.contentBasePublicPath;
    if (Array.isArray(contentBase)) {
//1.数组

      contentBase.forEach((item) => {
        this.app.use(contentBasePublicPath, express.static(item));
      });
    } else if (isAbsoluteUrl(String(contentBase))) {
        //2.绝对的url(例如http://www.58.com/src) 不推荐使用,建议通过proxy来进行设置
      this.log.warn(
        ‘Using a URL as contentBase is deprecated and will be removed in the next major version. Please use the proxy option instead.‘
      );

      this.log.warn(
        ‘proxy: {\n\t"*": "<your current contentBase configuration>"\n}‘
      );

      // 重定向我们的请求到contentBase
      this.app.get(‘*‘, (req, res) => {
        res.writeHead(302, {
          Location: contentBase + req.path + (req._parsedUrl.search || ‘‘),
        });

        res.end();
      });
    } else if (typeof contentBase === ‘number‘) {
       //3.数字 不推荐使用
      this.log.warn(
        ‘Using a number as contentBase is deprecated and will be removed in the next major version. Please use the proxy option instead.‘
      );

      this.log.warn(
        ‘proxy: {\n\t"*": "//localhost:<your current contentBase configuration>"\n}‘
      );

      // Redirect every request to the port contentBase
      this.app.get(‘*‘, (req, res) => {
        res.writeHead(302, {
          Location: `//localhost:${contentBase}${req.path}${req._parsedUrl
            .search || ‘‘}`,
        });

        res.end();
      });
    } else {
       //4.字符串
      // route content request
      this.app.use(
        contentBasePublicPath,
        express.static(contentBase, this.options.staticOptions)
      );
    }
  }

热更新

通过建立websocket实现服务端和客户端的双向通讯,当我们的服务端发生变化时可以通知客户端进行页面的刷新。

实现的方式主要有两种iframe modeinline mode

1. iframe mode  我们的页面被嵌套在一个iframe中,当资源改变的时候会重新加载。只需要在路径中加入webpack-dev-server就可以了,不需要其他的任何处理。(http://localhost:9991/webpack-dev-server/index.html)

技术分享图片

2. inline mode,不再单独引入一个js,而是将创建客户端soket.io的代码一同打包进我们的js中。

webpack-dev-server如何实现HMR(模块热更新)呢?也就是在不刷新页面的情况下实现页面的局部刷新。

首先介绍一下使用方式:

第一步:

devServer: {
        hot: true
    }

第二步:

if (module.hot) { module.hot.accept(); }
//这段代码用于标志哪个模块接收热加载,如果是代码入口模块的话,就是入口模块接收

Webpack 会从修改模块开始根据依赖关系往入口方向查找热加载接收代码。如果没有找到的话,默认是会刷新整个页面的。如果找到的话,会替换那个修改模块的代码为修改后的代码,并且从修改模块到接收热加载之间的模块的相关依赖模块都会重新执行返回新模块值,替换点模块缓存。

简单来说就是,有一个index.js引入了一个文件home.js,如果我们修改了home.js内容,热加载模块如在home.js则只更新home.js,如果在index.js则更新index.js和home.js两个文件的内容。如果两个文件都没有热更新模块,则刷新整个页面。

(由于 Webpack 的热加载会重新执行模块,如果是使用 React,并且模块热加载写在入口模块里,那么代码调整后就会重新执行 render。但由于组件模块重新执行返回了新的组件,这时前面挂在的组件状态就不能保留了,效果就等于刷新页面。

需要保留组件状态的话,需要使用 react-hot-loader 来处理。)

webpack-dev-server在我们的entry中添加的hot模块内容

//webpack-dev-server/utils/lib/addEntries.js  
if (options.hotOnly) {
hotEntry = require.resolve(‘webpack/hot/only-dev-server‘);
} else if (options.hot) {
hotEntry = require.resolve(‘webpack/hot/dev-server‘);
}

在我们的入口文件下添加了两个webpack的文件 

1. only-dev-server  :检查模块的更新

2. dev-server :模块热替换的相关内容 

HMR原理

技术分享图片

上图注释:

绿色是webpack控制区域,蓝色是webpack-dev-server控制区域,红色是文件系统,青色是我们项目本身。 

第一步:webpack监听文件变化并打包(1,2)

webpack-dev-middleware 调用 webpack 的 api 对文件系统 watch,当文件发生改变后,webpack 重新对文件进行编译打包,然后保存到内存中。 打包到了内存中,不生成文件的原因就在于访问内存中的代码比访问文件系统中的文件更快,而且也减少了代码写入文件的开销

第二步: webpack-dev-middleware对静态文件的监听(3)

webpack-dev-server 对文件变化的一个监控,这一步不同于第一步,并不是监控代码变化重新打包。当我们在配置文件中配置了devServer.watchContentBase 为 true 的时候,Server 会监听这些配置文件夹中静态文件的变化,变化后会通知浏览器端对应用进行 live reload。注意,这儿是浏览器刷新,和 HMR 是两个概念  

第三步:devServer 通知浏览器端文件发生改变(4)

sockjs 在服务端和浏览器端建立了一个 webSocket 长连接,以便将 webpack 编译和打包的各个阶段状态告知浏览器,最关键的步骤还是 webpack-dev-server 调用 webpack api 监听 compile的 done 事件,当compile 完成后,webpack-dev-server通过 _sendStatus 方法将编译打包后的新模块 hash 值发送到浏览器端。  

第四步:webpack 接收到最新 hash 值验证并请求模块代码(5,6)

webpack-dev-server/client 端并不能够请求更新的代码,也不会执行热更模块操作,而把这些工作又交回给了 webpack,webpack/hot/dev-server 的工作就是根据 webpack-dev-server/client 传给它的信息以及 dev-server 的配置决定是刷新浏览器呢还是进行模块热更新。当然如果仅仅是刷新浏览器(执行步骤11),也就没有后面那些步骤了。

第五步:HotModuleReplacement.runtime 对模块进行热更新(7,8,9)

是客户端 HMR 的中枢,它接收到上一步传递给他的新模块的 hash 值,它通过 JsonpMainTemplate.runtime 向 server 端发送 Ajax 请求,服务端返回一个 json,该 json 包含了所有要更新的模块的 hash 值,获取到更新列表后,该模块再次通过 jsonp 请求,获取到最新的模块代码。  

第六步:HotModulePlugin 将会对新旧模块进行对比(10)

HotModulePlugin 将会对新旧模块进行对比,决定是否更新模块,在决定更新模块后,检查模块之间的依赖关系,更新模块的同时更新模块间的依赖引用 ,第一个阶段是找出 outdatedModules 和 outdatedDependencies。第二个阶段从缓存中删除过期的模块和依赖。第三个阶段是将新的模块添加到 modules 中,当下次调用 __webpack_require__ (webpack 重写的 require 方法)方法的时候,就是获取到了新的模块代码了。

webpack-dev-server原理解析

原文:https://www.cnblogs.com/longlongdan/p/12391740.html

(0)
(0)
   
举报
评论 一句话评论(0
关于我们 - 联系我们 - 留言反馈 - 联系我们:wmxa8@hotmail.com
© 2014 bubuko.com 版权所有
打开技术之扣,分享程序人生!