1. <strong id="7actg"></strong>
    2. <table id="7actg"></table>

    3. <address id="7actg"></address>
      <address id="7actg"></address>
      1. <object id="7actg"><tt id="7actg"></tt></object>

        徹底搞懂并實(shí)現(xiàn) webpack 熱更新原理

        共 24056字,需瀏覽 49分鐘

         ·

        2021-10-28 18:09

        點(diǎn)擊上方 前端瓶子君,關(guān)注公眾號(hào)

        回復(fù)算法,加入前端編程面試算法每日一題群

        目錄

        • HMR是什么

          • 使用場(chǎng)景
        • 配置使用HMR

          • 配置webpack
          • 解析webpack打包后的文件內(nèi)容
          • 配置HMR
        • HMR原理

        • debug服務(wù)端源碼

          • 服務(wù)端簡(jiǎn)易實(shí)現(xiàn)
          • 服務(wù)端調(diào)試階段
        • debug客戶端源碼

          • 客戶端簡(jiǎn)易實(shí)現(xiàn)
          • 客戶端調(diào)試階段
        • 問(wèn)題

        • 總結(jié)

        • 招聘

        HMR是什么

        HMRHot Module Replacement是指當(dāng)你對(duì)代碼修改并保存后,webpack將會(huì)對(duì)代碼進(jìn)行重新打包,并將改動(dòng)的模塊發(fā)送到瀏覽器端,瀏覽器用新的模塊替換掉舊的模塊,去實(shí)現(xiàn)局部更新頁(yè)面而非整體刷新頁(yè)面。接下來(lái)將從使用到實(shí)現(xiàn)一版簡(jiǎn)易功能帶領(lǐng)大家深入淺出HMR。

        文章首發(fā)于@careteen/webpack-hmr,轉(zhuǎn)載請(qǐng)注明來(lái)源即可。

        使用場(chǎng)景

        scenario

        如上圖所示,一個(gè)注冊(cè)頁(yè)面包含用戶名密碼、郵箱三個(gè)必填輸入框,以及一個(gè)提交按鈕,當(dāng)你在調(diào)試郵箱模塊改動(dòng)了代碼時(shí),沒(méi)做任何處理情況下是會(huì)刷新整個(gè)頁(yè)面,頻繁的改動(dòng)代碼會(huì)浪費(fèi)你大量時(shí)間去重新填寫內(nèi)容。預(yù)期是保留用戶名、密碼的輸入內(nèi)容,而只替換郵箱這一模塊。這一訴求就需要借助webpack-dev-server的熱模塊更新功能。

        相對(duì)于live reload整體刷新頁(yè)面的方案,HMR的優(yōu)點(diǎn)在于可以保存應(yīng)用的狀態(tài),提高開發(fā)效率。

        配置使用HMR

        配置webpack

        首先借助webpack搭建項(xiàng)目

        • 初識(shí)化項(xiàng)目并導(dǎo)入依賴
        mkdir webpack-hmr && cd webpack-hmr
        npm i -y
        npm i -S webpack webpack-cli webpack-dev-server html-webpack-plugin
        • 配置文件webpack.config.js
        const path = require('path')
        const webpack = require('webpack')
        const htmlWebpackPlugin = require('html-webpack-plugin')

        module.exports = {
          mode'development'// 開發(fā)模式不壓縮代碼,方便調(diào)試
          entry'./src/index.js'// 入口文件
          output: {
            path: path.join(__dirname, 'dist'),
            filename'main.js'
          },
          devServer: {
            contentBase: path.join(__dirname, 'dist')
          },
          plugins: [
            new htmlWebpackPlugin({
              template'./src/index.html',
              filename'index.html'
            })
          ]
        }
        • 新建src/index.html模板文件
        <!DOCTYPE html>
        <html lang="en">
        <head>
          <meta charset="UTF-8">
          <meta name="viewport" content="width=device-width, initial-scale=1.0">
          <meta http-equiv="X-UA-Compatible" content="ie=edge">
          <title>Webpack Hot Module Replacement</title>
        </head>
        <body>
          <div id="root"></div>
        </body>
        </html>
        • 新建src/index.js入口文件編寫簡(jiǎn)單邏輯
        var root = document.getElementById('root')
        function render () {
          root.innerHTML = require('./content.js')
        }
        render()
        • 新建依賴文件src/content.js導(dǎo)出字符供index渲染頁(yè)面
        var ret = 'Hello Webpack Hot Module Replacement'
        module.exports = ret
        // export default ret
        • 配置package.json
         "scripts": {
            "dev""webpack-dev-server",
            "build""webpack"
          }
        • 然后npm run dev即可啟動(dòng)項(xiàng)目
        • 通過(guò)npm run build打包生成靜態(tài)資源到dist目錄

        接下來(lái)先分析下dist目錄中的文件

        解析webpack打包后的文件內(nèi)容

        • webpack自己實(shí)現(xiàn)的一套commonjs規(guī)范講解
        • 區(qū)分commonjs和esmodule

        dist目錄結(jié)構(gòu)

        .
        ├── index.html
        └── main.js

        其中index.html內(nèi)容如下

        <!-- ... -->
        <div id="root"></div>
        <script type="text/javascript" src="main.js"></script></body>
        <!-- ... -->

        使用html-webpack-plugin插件將入口文件及其依賴通過(guò)script標(biāo)簽引入

        先對(duì)main.js內(nèi)容去掉注釋和無(wú)關(guān)內(nèi)容進(jìn)行分析

        (function (modules// webpackBootstrap
          // ...
        })
        ({
          "./src/content.js":
            (function (module, exports{
              eval("var ret = 'Hello Webpack Hot Module Replacement'\n\nmodule.exports = ret\n// export default ret\n\n");
            }),
          "./src/index.js": (function (module, exports, __webpack_require__{
            eval("var root = document.getElementById('root')\nfunction render () {\n  root.innerHTML = __webpack_require__(/*! ./content.js */ \"./src/content.js\")\n}\nrender()\n\n\n");
          })
        });

        可見(jiàn)webpack打包后會(huì)產(chǎn)出一個(gè)自執(zhí)行函數(shù),其參數(shù)為一個(gè)對(duì)象

        "./src/content.js": (function (module, exports{
          eval("...")
        }

        鍵為入口文件或依賴文件相對(duì)于根目錄的相對(duì)路徑,值則是一個(gè)函數(shù),其中使用eval執(zhí)行文件的內(nèi)容字符。

        • 再進(jìn)入自執(zhí)行函數(shù)體內(nèi),可見(jiàn)webpack自己實(shí)現(xiàn)了一套commonjs規(guī)范
        (function (modules{
          // 模塊緩存
          var installedModules = {};
          function __webpack_require__(moduleId{
            // 判斷是否有緩存
            if (installedModules[moduleId]) {
              return installedModules[moduleId].exports;
            }
            // 沒(méi)有緩存則創(chuàng)建一個(gè)模塊對(duì)象并將其放入緩存
            var module = installedModules[moduleId] = {
              i: moduleId,
              lfalse// 是否已加載
              exports: {}
            };
            // 執(zhí)行模塊函數(shù)
            modules[moduleId].call(module.exports, modulemodule.exports, __webpack_require__);
            // 將狀態(tài)置為已加載
            module.l = true;
            // 返回模塊對(duì)象
            return module.exports;
          }
          // ...
          // 加載入口文件
          return __webpack_require__(__webpack_require__.s = "./src/index.js");
        })

        如果對(duì)上面commonjs規(guī)范感興趣可以前往我的另一篇文章手摸手帶你實(shí)現(xiàn)commonjs規(guī)范

        給出上面代碼主要是先對(duì)webpack的產(chǎn)出文件混個(gè)眼熟,不要懼怕。其實(shí)任何一個(gè)不管多復(fù)雜的事物都是由更小更簡(jiǎn)單的東西組成,剖開它認(rèn)識(shí)它愛(ài)上它。

        配置HMR

        接下來(lái)配置并感受一下熱更新帶來(lái)的便捷開發(fā)

        webpack.config.js配置

         // ...
          devServer: {
            hottrue
          }
          // ...

        ./src/index.js配置

        // ...
        if (module.hot) {
          module.hot.accept(['./content.js'], () => {
            render()
          })
        }

        當(dāng)更改./content.js的內(nèi)容并保存時(shí),可以看到頁(yè)面沒(méi)有刷新,但是內(nèi)容已經(jīng)被替換了。

        這對(duì)提高開發(fā)效率意義重大。接下來(lái)將一層層剖開它,認(rèn)識(shí)它的實(shí)現(xiàn)原理。

        HMR原理

        core

        如上圖所示,右側(cè)Server端使用webpack-dev-server去啟動(dòng)本地服務(wù),內(nèi)部實(shí)現(xiàn)主要使用了webpack、express、websocket。

        • 使用express啟動(dòng)本地服務(wù),當(dāng)瀏覽器訪問(wèn)資源時(shí)對(duì)此做響應(yīng)。

        • 服務(wù)端和客戶端使用websocket實(shí)現(xiàn)長(zhǎng)連接

        • webpack監(jiān)聽源文件的變化,即當(dāng)開發(fā)者保存文件時(shí)觸發(fā)webpack的重新編譯。

          • 每次編譯都會(huì)生成hash值、已改動(dòng)模塊的json文件、已改動(dòng)模塊代碼的js文件
          • 編譯完成后通過(guò)socket向客戶端推送當(dāng)前編譯的hash戳
        • 客戶端的websocket監(jiān)聽到有文件改動(dòng)推送過(guò)來(lái)的hash戳,會(huì)和上一次對(duì)比

          • 一致則走緩存
          • 不一致則通過(guò)ajaxjsonp向服務(wù)端獲取最新資源
        • 使用內(nèi)存文件系統(tǒng)去替換有修改的內(nèi)容實(shí)現(xiàn)局部刷新

        上圖先只看個(gè)大概,下面將從服務(wù)端和客戶端兩個(gè)方面進(jìn)行詳細(xì)分析

        debug服務(wù)端源碼

        core

        現(xiàn)在也只需要關(guān)注上圖的右側(cè)服務(wù)端部分,左側(cè)可以暫時(shí)忽略。下面步驟主要是debug服務(wù)端源碼分析其詳細(xì)思路,也給出了代碼所處的具體位置,感興趣的可以先行定位到下面的代碼處設(shè)置斷點(diǎn),然后觀察數(shù)據(jù)的變化情況。也可以先跳過(guò)閱讀此步驟。

        1. 啟動(dòng)webpack-dev-server服務(wù)器,源代碼地址@webpack-dev-server/webpack-dev-server.js#L173

        2. 創(chuàng)建webpack實(shí)例,源代碼地址@webpack-dev-server/webpack-dev-server.js#L89

        3. 創(chuàng)建Server服務(wù)器,源代碼地址@webpack-dev-server/webpack-dev-server.js#L107

        4. 添加webpack的done事件回調(diào),源代碼地址@webpack-dev-server/Server.js#L122

          1. 編譯完成向客戶端發(fā)送消息,源代碼地址@webpack-dev-server/Server.js#L184
        5. 創(chuàng)建express應(yīng)用app,源代碼地址@webpack-dev-server/Server.js#L123

        6. 設(shè)置文件系統(tǒng)為內(nèi)存文件系統(tǒng),源代碼地址@webpack-dev-middleware/fs.js#L115

        7. 添加webpack-dev-middleware中間件,源代碼地址@webpack-dev-server/Server.js#L125

          1. 中間件負(fù)責(zé)返回生成的文件,源代碼地址@webpack-dev-middleware/middleware.js#L20
        8. 啟動(dòng)webpack編譯,源代碼地址@webpack-dev-middleware/index.js#L51

        9. 創(chuàng)建http服務(wù)器并啟動(dòng)服務(wù),源代碼地址@webpack-dev-server/Server.js#L135

        10. 使用sockjs在瀏覽器端和服務(wù)端之間建立一個(gè) websocket 長(zhǎng)連接,源代碼地址@webpack-dev-server/Server.js#L745

        11. 創(chuàng)建socket服務(wù)器,源代碼地址@webpack-dev-server/SockJSServer.js#L34

        服務(wù)端簡(jiǎn)易實(shí)現(xiàn)

        上面是我通過(guò)debug得出dev-server運(yùn)行流程比較核心的幾個(gè)點(diǎn),下面將其抽象整合到一個(gè)文件中。

        啟動(dòng)webpack-dev-server服務(wù)器

        先導(dǎo)入所有依賴

        const path = require('path'// 解析文件路徑
        const express = require('express'// 啟動(dòng)本地服務(wù)
        const mime = require('mime'// 獲取文件類型 實(shí)現(xiàn)一個(gè)靜態(tài)服務(wù)器
        const webpack = require('webpack'// 讀取配置文件進(jìn)行打包
        const MemoryFileSystem = require('memory-fs'// 使用內(nèi)存文件系統(tǒng)更快,文件生成在內(nèi)存中而非真實(shí)文件
        const config = require('./webpack.config'// 獲取webpack配置文件

        創(chuàng)建webpack實(shí)例

        const compiler = webpack(config)

        compiler代表整個(gè)webpack編譯任務(wù),全局只有一個(gè)

        創(chuàng)建Server服務(wù)器

        class Server {
          constructor(compiler) {
            this.compiler = compiler
          }
          listen(port) {
            this.server.listen(port, () => {
              console.log(`服務(wù)器已經(jīng)在${port}端口上啟動(dòng)了`)
            })
          }
        }
        let server = new Server(compiler)
        server.listen(8000)

        在后面是通過(guò)express來(lái)當(dāng)啟動(dòng)服務(wù)的

        添加webpack的done事件回調(diào)

         constructor(compiler) {
            let sockets = []
            let lasthash
            compiler.hooks.done.tap('webpack-dev-server', (stats) => {
              lasthash = stats.hash
              // 每當(dāng)新一個(gè)編譯完成后都會(huì)向客戶端發(fā)送消息
              sockets.forEach(socket => {
                socket.emit('hash', stats.hash) // 先向客戶端發(fā)送最新的hash值
                socket.emit('ok'// 再向客戶端發(fā)送一個(gè)ok
              })
            })
          }

        webpack編譯后提供提供了一系列鉤子函數(shù),以供插件能訪問(wèn)到它的各個(gè)生命周期節(jié)點(diǎn),并對(duì)其打包內(nèi)容做修改。compiler.hooks.done則是插件能修改其內(nèi)容的最后一個(gè)節(jié)點(diǎn)。

        編譯完成通過(guò)socket向客戶端發(fā)送消息,推送每次編譯產(chǎn)生的hash。另外如果是熱更新的話,還會(huì)產(chǎn)出二個(gè)補(bǔ)丁文件,里面描述了從上一次結(jié)果到這一次結(jié)果都有哪些chunk和模塊發(fā)生了變化。

        使用let sockets = []數(shù)組去存放當(dāng)打開了多個(gè)Tab時(shí)每個(gè)Tab的socket實(shí)例。

        創(chuàng)建express應(yīng)用app

        let app = new express()

        設(shè)置文件系統(tǒng)為內(nèi)存文件系統(tǒng)

        let fs = new MemoryFileSystem()

        使用MemoryFileSystemcompiler的產(chǎn)出文件打包到內(nèi)存中。

        添加webpack-dev-middleware中間件

         function middleware(req, res, next{
            if (req.url === '/favicon.ico') {
              return res.sendStatus(404)
            }
            // /index.html   dist/index.html
            let filename = path.join(config.output.path, req.url.slice(1))
            let stat = fs.statSync(filename)
            if (stat.isFile()) { // 判斷是否存在這個(gè)文件,如果在的話直接把這個(gè)讀出來(lái)發(fā)給瀏覽器
              let content = fs.readFileSync(filename)
              let contentType = mime.getType(filename)
              res.setHeader('Content-Type', contentType)
              res.statusCode = res.statusCode || 200
              res.send(content)
            } else {
              return res.sendStatus(404)
            }
          }
          app.use(middleware)

        使用expres啟動(dòng)了本地開發(fā)服務(wù)后,使用中間件去為其構(gòu)造一個(gè)靜態(tài)服務(wù)器,并使用了內(nèi)存文件系統(tǒng),使讀取文件后存放到內(nèi)存中,提高讀寫效率,最終返回生成的文件。

        啟動(dòng)webpack編譯

         compiler.watch({}, err => {
            console.log('又一次編譯任務(wù)成功完成了')
          })

        以監(jiān)控的模式啟動(dòng)一次webpack編譯,當(dāng)編譯成功之后執(zhí)行回調(diào)

        創(chuàng)建http服務(wù)器并啟動(dòng)服務(wù)

         constructor(compiler) {
            // ...
            this.server = require('http').createServer(app)
            // ...
          }
          listen(port) {
            this.server.listen(port, () => {
              console.log(`服務(wù)器已經(jīng)在${port}端口上啟動(dòng)了`)
            })
          }

        使用sockjs在瀏覽器端和服務(wù)端之間建立一個(gè) websocket 長(zhǎng)連接

         constructor(compiler) {
            // ...
            this.server = require('http').createServer(app)
            let io = require('socket.io')(this.server)
            io.on('connection', (socket) => {
              sockets.push(socket)
              socket.emit('hash', lastHash)
              socket.emit('ok')
            })
          }

        啟動(dòng)一個(gè) websocket服務(wù)器,然后等待連接來(lái)到,連接到來(lái)之后存進(jìn)sockets池

        當(dāng)有文件改動(dòng),webpack重新編譯時(shí),向客戶端推送hashok兩個(gè)事件

        服務(wù)端調(diào)試階段

        感興趣的可以根據(jù)上面debug服務(wù)端源碼所帶的源碼位置,并在瀏覽器的調(diào)試模式下設(shè)置斷點(diǎn)查看每個(gè)階段的值。

        node dev-server.js

        使用我們自己編譯的dev-server.js啟動(dòng)服務(wù),可看到頁(yè)面可以正常展示,但還沒(méi)有實(shí)現(xiàn)熱更新。

        下面將調(diào)式客戶端的源代碼分析其實(shí)現(xiàn)流程。

        debug客戶端源碼

        core

        現(xiàn)在也只需要關(guān)注上圖的左側(cè)客戶端部分,右側(cè)可以暫時(shí)忽略。下面步驟主要是debug客戶端源碼分析其詳細(xì)思路,也給出了代碼所處的具體位置,感興趣的可以先行定位到下面的代碼處設(shè)置斷點(diǎn),然后觀察數(shù)據(jù)的變化情況。也可以先跳過(guò)閱讀此步驟。

        debug客戶端源碼分析其詳細(xì)思路

        1. webpack-dev-server/client端會(huì)監(jiān)聽到此hash消息,源代碼地址@webpack-dev-server/index.js#L54
        2. 客戶端收到ok的消息后會(huì)執(zhí)行reloadApp方法進(jìn)行更新,源代碼地址index.js#L101
        3. 在reloadApp中會(huì)進(jìn)行判斷,是否支持熱更新,如果支持的話發(fā)射webpackHotUpdate事件,如果不支持則直接刷新瀏覽器,源代碼地址reloadApp.js#L7
        4. 在webpack/hot/dev-server.js會(huì)監(jiān)聽webpackHotUpdate事件,源代碼地址dev-server.js#L55
        5. 在check方法里會(huì)調(diào)用module.hot.check方法,源代碼地址dev-server.js#L13
        6. HotModuleReplacement.runtime請(qǐng)求Manifest,源代碼地址HotModuleReplacement.runtime.js#L180
        7. 它通過(guò)調(diào)用 JsonpMainTemplate.runtime的hotDownloadManifest方法,源代碼地址JsonpMainTemplate.runtime.js#L23
        8. 調(diào)用JsonpMainTemplate.runtime的hotDownloadUpdateChunk方法通過(guò)JSONP請(qǐng)求獲取到最新的模塊代碼,源代碼地址JsonpMainTemplate.runtime.js#L14
        9. 補(bǔ)丁JS取回來(lái)后會(huì)調(diào)用JsonpMainTemplate.runtime.js的webpackHotUpdate方法,源代碼地址JsonpMainTemplate.runtime.js#L8
        10. 然后會(huì)調(diào)用HotModuleReplacement.runtime.js的hotAddUpdateChunk方法動(dòng)態(tài)更新模塊代碼,源代碼地址HotModuleReplacement.runtime.js#L222
        11. 然后調(diào)用hotApply方法進(jìn)行熱更新,源代碼地址HotModuleReplacement.runtime.js#L257、HotModuleReplacement.runtime.js#L278

        客戶端簡(jiǎn)易實(shí)現(xiàn)

        上面是我通過(guò)debug得出dev-server運(yùn)行流程比較核心的幾個(gè)點(diǎn),下面將其抽象整合成一個(gè)文件。

        webpack-dev-server/client端會(huì)監(jiān)聽到此hash消息

        在開發(fā)客戶端功能之前,需要在src/index.html中引入socket.io

        <script src="/socket.io/socket.io.js"></script>

        下面連接socket并接受消息

        let socket = io('/')
        socket.on('connect', onConnected)
        const onConnected = () => {
          console.log('客戶端連接成功')
        }
        let hotCurrentHash // lastHash 上一次 hash值 
        let currentHash // 這一次的hash值
        socket.on('hash', (hash) => {
          currentHash = hash
        })

        將服務(wù)端webpack每次編譯所產(chǎn)生hash進(jìn)行緩存

        客戶端收到ok的消息后會(huì)執(zhí)行reloadApp方法進(jìn)行更新

        socket.on('ok', () => {
          reloadApp(true)
        })

        reloadApp中判斷是否支持熱更新

        // 當(dāng)收到ok事件后,會(huì)重新刷新app
        function reloadApp(hot{
          if (hot) { // 如果hot為true 走熱更新的邏輯
            hotEmitter.emit('webpackHotUpdate')
          } else { // 如果不支持熱更新,則直接重新加載
            window.location.reload()
          }
        }

        在reloadApp中會(huì)進(jìn)行判斷,是否支持熱更新,如果支持的話發(fā)射webpackHotUpdate事件,如果不支持則直接刷新瀏覽器。

        在webpack/hot/dev-server.js會(huì)監(jiān)聽webpackHotUpdate事件

        首先需要一個(gè)發(fā)布訂閱去綁定事件并在合適的時(shí)機(jī)觸發(fā)。

        class Emitter {
          constructor() {
            this.listeners = {}
          }
          on(type, listener) {
            this.listeners[type] = listener
          }
          emit(type) {
            this.listeners[type] && this.listeners[type]()
          }
        }
        let hotEmitter = new Emitter()
        hotEmitter.on('webpackHotUpdate', () => {
          if (!hotCurrentHash || hotCurrentHash == currentHash) {
            return hotCurrentHash = currentHash
          }
          hotCheck()
        })

        會(huì)判斷是否為第一次進(jìn)入頁(yè)面和代碼是否有更新。

        上面的發(fā)布訂閱較為簡(jiǎn)單,且只支持先發(fā)布后訂閱功能。對(duì)于一些較為復(fù)雜的場(chǎng)景可能需要先訂閱后發(fā)布,此時(shí)可以移步@careteen/event-emitter。其實(shí)現(xiàn)原理也挺簡(jiǎn)單,需要維護(hù)一個(gè)離線事件棧存放還沒(méi)發(fā)布就訂閱的事件,等到訂閱時(shí)可以取出所有事件執(zhí)行。

        在check方法里會(huì)調(diào)用module.hot.check方法

        function hotCheck() {
          hotDownloadManifest().then(update => {
            let chunkIds = Object.keys(update.c)
            chunkIds.forEach(chunkId => {
              hotDownloadUpdateChunk(chunkId)
            })
          })
        }

        上面也提到過(guò)webpack每次編譯都會(huì)產(chǎn)生hash值、已改動(dòng)模塊的json文件已改動(dòng)模塊代碼的js文件,

        此時(shí)先使用ajax請(qǐng)求Manifest即服務(wù)器這一次編譯相對(duì)于上一次編譯改變了哪些module和chunk。

        然后再通過(guò)jsonp獲取這些已改動(dòng)的module和chunk的代碼。

        調(diào)用hotDownloadManifest方法

        function hotDownloadManifest() {
          return new Promise(function (resolve{
            let request = new XMLHttpRequest()
            //hot-update.json文件里存放著從上一次編譯到這一次編譯 取到差異
            let requestPath = '/' + hotCurrentHash + ".hot-update.json"
            request.open('GET', requestPath, true)
            request.onreadystatechange = function () {
              if (request.readyState === 4) {
                let update = JSON.parse(request.responseText)
                resolve(update)
              }
            }
            request.send()
          })
        }

        調(diào)用hotDownloadUpdateChunk方法通過(guò)JSONP請(qǐng)求獲取到最新的模塊代碼

        function hotDownloadUpdateChunk(chunkId{
          let script = document.createElement('script')
          script.charset = 'utf-8'
          // /main.xxxx.hot-update.js
          script.src = '/' + chunkId + "." + hotCurrentHash + ".hot-update.js"
          document.head.appendChild(script)
        }

        這里解釋下為什么使用JSONP獲取而不直接利用socket獲取最新代碼?主要是因?yàn)?code style="font-size: 14px;border-radius: 4px;font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(155, 110, 35);background-color: rgb(255, 245, 227);padding: 3px;margin: 3px;">JSONP獲取的代碼可以直接執(zhí)行。

        調(diào)用webpackHotUpdate方法

        當(dāng)客戶端把最新的代碼拉到瀏覽之后

        window.webpackHotUpdate = function (chunkId, moreModules{
          // 循環(huán)新拉來(lái)的模塊
          for (let moduleId in moreModules) {
            // 從模塊緩存中取到老的模塊定義
            let oldModule = __webpack_require__.c[moduleId]
            // parents哪些模塊引用這個(gè)模塊 children這個(gè)模塊引用了哪些模塊
            // parents=['./src/index.js']
            let {
              parents,
              children
            } = oldModule
            // 更新緩存為最新代碼 緩存進(jìn)行更新
            let module = __webpack_require__.c[moduleId] = {
              i: moduleId,
              lfalse,
              exports: {},
              parents,
              children,
              hotwindow.hotCreateModule(moduleId)
            }
            moreModules[moduleId].call(module.exports, modulemodule.exports, __webpack_require__)
            module.l = true // 狀態(tài)變?yōu)榧虞d就是給module.exports 賦值了
            parents.forEach(parent => {
              // parents=['./src/index.js']
              let parentModule = __webpack_require__.c[parent]
              // _acceptedDependencies={'./src/title.js',render}
              parentModule && parentModule.hot && parentModule.hot._acceptedDependencies[moduleId] && parentModule.hot._acceptedDependencies[moduleId]()
            })
            hotCurrentHash = currentHash
          }
        }

        hotCreateModule的實(shí)現(xiàn)

        實(shí)現(xiàn)我們可以在業(yè)務(wù)代碼中定義需要熱更新的模塊以及回調(diào)函數(shù),將其存放在hot._acceptedDependencies中。

        window.hotCreateModule = function () {
          let hot = {
            _acceptedDependencies: {},
            dispose() {
              // 銷毀老的元素
            },
            acceptfunction (deps, callback{
              for (let i = 0; i < deps.length; i++) {
                // hot._acceptedDependencies={'./title': render}
                hot._acceptedDependencies[deps[i]] = callback
              }
            }
          }
          return hot
        }

        然后在webpackHotUpdate中進(jìn)行調(diào)用

         parents.forEach(parent => {
              // parents=['./src/index.js']
              let parentModule = __webpack_require__.c[parent]
              // _acceptedDependencies={'./src/title.js',render}
              parentModule && parentModule.hot && parentModule.hot._acceptedDependencies[moduleId] && parentModule.hot._acceptedDependencies[moduleId]()
            })

        最后調(diào)用hotApply方法進(jìn)行熱更新

        客戶端調(diào)試階段

        經(jīng)過(guò)上述實(shí)現(xiàn)了一個(gè)基本版的HMR,可更改代碼保存的同時(shí)查看瀏覽器并非整體刷新,而是局部更新代碼進(jìn)而更新視圖。在涉及到大量表單的需求時(shí)大大提高了開發(fā)效率。

        問(wèn)題

        • 如何實(shí)現(xiàn)commonjs規(guī)范?

        感興趣的可前往debug CommonJs規(guī)范了解其實(shí)現(xiàn)原理。

        • webpack實(shí)現(xiàn)流程以及各個(gè)生命周期的作用是什么?

        webpack主要借助了tapable這個(gè)庫(kù)所提供的一系列同步/異步鉤子函數(shù)貫穿整個(gè)生命周期。

        基于此我實(shí)現(xiàn)了一版簡(jiǎn)易的webpack,源碼100+行,食用時(shí)伴著注釋很容易消化,感興趣的可前往看個(gè)思路。


        • 發(fā)布訂閱的使用和實(shí)現(xiàn),并且如何實(shí)現(xiàn)一個(gè)可先訂閱后發(fā)布的機(jī)制?

        上面也提到需要使用到發(fā)布訂閱模式,且只支持先發(fā)布后訂閱功能。對(duì)于一些較為復(fù)雜的場(chǎng)景可能需要先訂閱后發(fā)布,此時(shí)可以移步@careteen/event-emitter。其實(shí)現(xiàn)原理也挺簡(jiǎn)單,需要維護(hù)一個(gè)離線事件棧存放還沒(méi)發(fā)布就訂閱的事件,等到訂閱時(shí)可以取出所有事件執(zhí)行。

        • 為什么使用JSONP而不用socke通信獲取更新過(guò)的代碼?

        因?yàn)橥ㄟ^(guò)socket通信獲取的是一串字符串需要再做處理。而通過(guò)JSONP獲取的代碼可以直接執(zhí)行。

        引用

        • 模塊熱替換 - webpack官網(wǎng)

        招聘

        急缺前端,對(duì)搜狐焦點(diǎn)感興趣的直接簡(jiǎn)歷發(fā)我郵件[email protected]或加我vx: Careteen

        關(guān)于本文

        來(lái)源:careteenL

        https://segmentfault.com/a/1190000020310371


        最后

        歡迎關(guān)注【前端瓶子君】??ヽ(°▽°)ノ?
        回復(fù)「算法」,加入前端編程源碼算法群,每日一道面試題(工作日),第二天瓶子君都會(huì)很認(rèn)真的解答喲!
        回復(fù)「交流」,吹吹水、聊聊技術(shù)、吐吐槽!
        回復(fù)「閱讀」,每日刷刷高質(zhì)量好文!
        如果這篇文章對(duì)你有幫助,在看」是最大的支持
         》》面試官也在看的算法資料《《
        “在看和轉(zhuǎn)發(fā)”就是最大的支持


        瀏覽 41
        點(diǎn)贊
        評(píng)論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報(bào)
        評(píng)論
        圖片
        表情
        推薦
        點(diǎn)贊
        評(píng)論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報(bào)
        1. <strong id="7actg"></strong>
        2. <table id="7actg"></table>

        3. <address id="7actg"></address>
          <address id="7actg"></address>
          1. <object id="7actg"><tt id="7actg"></tt></object>
            gay男男屁股眼扒开来露出来 | www.6969成人片亚洲 | 亚洲国产免费视频 | 亚洲xxxx天美 | 午夜爱爱爱爱爽爽爽爽视频网站 | 美女高潮喷水视频 | 爱爱综合在线 | 啊轻点灬太粗嗯太深小蓝视频 | 特级丰满少妇一级AAAA爱毛片 | 久久精品久久久精品美女 |