1. 程式人生 > >webpack-dev-server原理分析與HMR實現

webpack-dev-server原理分析與HMR實現

建議在github閱讀,我會保證內容及時更新,並歡迎star,issue。如果你想深入瞭解webpack-dev-server的內部原理,你也可以檢視我寫的這個打包工具,通過它可以完成三種打包方式,其中devServer模式就是通過webpack-dev-server來完成的,並且支援HMR(webpack-dev-server雖然說可以支援無重新整理更新資料,但是在大多數情況下都是重新整理頁面的,而該打包工具已經無需重新整理而完成資料更新了)。對於webpack的HMR不瞭解的可以檢視這裡。其中也牽涉到webpack-dev-middleware中介軟體。希望對您有用

webpack-dev-server在我們的entry中新增的hot模組內容

看看下面的方法你就知道了,在hot模式下,我們的entry最後都會被新增兩個檔案:

module.exports = function addDevServerEntrypoints(webpackOptions, devServerOptions) {
    if(devServerOptions.inline !== false) {
        //表示是inline模式而不是iframe模式
        const domain = createDomain(devServerOptions);
        const devClient = [`${require
.resolve("../../client/")}?${domain}`]; //客戶端內容 if(devServerOptions.hotOnly) devClient.push("webpack/hot/only-dev-server"); else if(devServerOptions.hot) devClient.push("webpack/hot/dev-server"); //配置了不同的webpack而檔案到客戶端檔案中 [].concat(webpackOptions).forEach(function
(wpOpt) {
if(typeof wpOpt.entry === "object" && !Array.isArray(wpOpt.entry)) { /* entry:{ index:'./index.js', => entry:[] index1:'./index1.js' } */ Object.keys(wpOpt.entry).forEach(function(key) { wpOpt.entry[key] = devClient.concat(wpOpt.entry[key]); }); //新增我們自己的入口檔案 } else if(typeof wpOpt.entry === "function") { wpOpt.entry = wpOpt.entry(devClient); //如果entry是一個函式那麼我們把devClient陣列傳入 } else { wpOpt.entry = devClient.concat(wpOpt.entry); //陣列直接傳入 } }); } };

(1)首先看看”webpack/hot/only-dev-server”的檔案內容:

if(module.hot) {
    var lastHash;
    //___webpack_hash__
    //Access to the hash of the compilation.
    //Only available with the HotModuleReplacementPlugin or the ExtendedAPIPlugin
    var upToDate = function upToDate() {
        return lastHash.indexOf(__webpack_hash__) >= 0;
        //如果兩個hash相同那麼表示沒有更新
    };
    //檢查更新
    var check = function check() {
        //Check all currently loaded modules for updates and apply updates if found.
        module.hot.check().then(function(updatedModules) {
            //沒有更新的模組直接返回
            if(!updatedModules) {
                console.warn("[HMR] Cannot find update. Need to do a full reload!");
                console.warn("[HMR] (Probably because of restarting the webpack-dev-server)");
                return;
            }
            //apply方法:If status() != "ready" it throws an error.
            //開始更新
            return module.hot.apply({
                ignoreUnaccepted: true,
                ignoreDeclined: true,
                ignoreErrored: true,
                onUnaccepted: function(data) {
                    console.warn("Ignored an update to unaccepted module " + data.chain.join(" -> "));
                },
                onDeclined: function(data) {
                    console.warn("Ignored an update to declined module " + data.chain.join(" -> "));
                },
                onErrored: function(data) {
                    console.warn("Ignored an error while updating module " + data.moduleId + " (" + data.type + ")");
                }
             //renewedModules表示哪些模組已經更新了
            }).then(function(renewedModules) {
                if(!upToDate()) {
                    check();
                }
                //更新的模組updatedModules,renewedModules表示哪些模組已經更新了
                require("./log-apply-result")(updatedModules, renewedModules);
                if(upToDate()) {
                    console.log("[HMR] App is up to date.");
                }
            });
        }).catch(function(err) {
            var status = module.hot.status();
            if(["abort", "fail"].indexOf(status) >= 0) {
                console.warn("[HMR] Cannot check for update. Need to do a full reload!");
                console.warn("[HMR] " + err.stack || err.message);
            } else {
                console.warn("[HMR] Update check failed: " + err.stack || err.message);
            }
        });
    };
    var hotEmitter = require("./emitter");
    //emitter模組內容,也就是匯出一個events例項
    /*
    var EventEmitter = require("events");
    module.exports = new EventEmitter();
     */
    hotEmitter.on("webpackHotUpdate", function(currentHash) {
        lastHash = currentHash;
        //表示本次更新後得到的hash值
        if(!upToDate()) {
            //有更新
            var status = module.hot.status();
            if(status === "idle") {
                console.log("[HMR] Checking for updates on the server...");
                check();
            } else if(["abort", "fail"].indexOf(status) >= 0) {
                console.warn("[HMR] Cannot apply update as a previous update " + status + "ed. Need to do a full reload!");
            }
        }
    });
    console.log("[HMR] Waiting for update signal from WDS...");
} else {
    throw new Error("[HMR] Hot Module Replacement is disabled.");
}

./log-apply-result模組內容如下:

module.exports = function(updatedModules, renewedModules) {
    //renewedModules表示哪些模組被更新了,剩餘的模組表示,哪些模組由於 ignoreDeclined,ignoreUnaccepted配置沒有更新
    var unacceptedModules = updatedModules.filter(function(moduleId) {
        return renewedModules && renewedModules.indexOf(moduleId) < 0;
    });
    //哪些模組無法HMR,列印log
    if(unacceptedModules.length > 0) {
        console.warn("[HMR] The following modules couldn't be hot updated: (They would need a full reload!)");
        unacceptedModules.forEach(function(moduleId) {
            console.warn("[HMR]  - " + moduleId);
        });
    }
    //沒有模組更新,表示模組是最新的
    if(!renewedModules || renewedModules.length === 0) {
        console.log("[HMR] Nothing hot updated.");
    } else {
        console.log("[HMR] Updated modules:");
        //更新的模組
        renewedModules.forEach(function(moduleId) {
            console.log("[HMR]  - " + moduleId);
        });
        //每一個moduleId都是數字那麼建議使用NamedModulesPlugin
        var numberIds = renewedModules.every(function(moduleId) {
            return typeof moduleId === "number";
        });
        if(numberIds)
            console.log("[HMR] Consider using the NamedModulesPlugin for module names.");
    }
};

所以”webpack/hot/only-dev-server”的檔案內容就是檢查哪些模組更新了(通過webpackHotUpdate事件完成),其中哪些模組更新成功,而哪些模組由於某種原因沒有更新成功。其中沒有更新的原因可能是如下的:

    ignoreUnaccepted
    ignoreDecline
    ignoreErrored

至於模組什麼時候接受到需要更新是和webpack的打包過程有關的,這裡也給出觸發更新的時機:

 ok: function() {
        sendMsg("Ok");
        if(useWarningOverlay || useErrorOverlay) overlay.clear();
        if(initial) return initial = false;
        reloadApp();
    },
    warnings: function(warnings) {
        log("info", "[WDS] Warnings while compiling.");
        var strippedWarnings = warnings.map(function(warning) {
            return stripAnsi(warning);
        });
        sendMsg("Warnings", strippedWarnings);
        for(var i = 0; i < strippedWarnings.length; i++)
            console.warn(strippedWarnings[i]);
        if(useWarningOverlay) overlay.showMessage(warnings);

        if(initial) return initial = false;
        reloadApp();
    },
function reloadApp() {
    //如果開啟了HMR模式
    if(hot) {
        log("info", "[WDS] App hot update...");
        var hotEmitter = require("webpack/hot/emitter");
        hotEmitter.emit("webpackHotUpdate", currentHash);
        //重新啟動webpack/hot/emitter,同時設定當前hash
        if(typeof self !== "undefined" && self.window) {
            // broadcast update to window
            self.postMessage("webpackHotUpdate" + currentHash, "*");
        }
    } else {
       //如果不是Hotupdate那麼我們直接reload我們的window就可以了
        log("info", "[WDS] App updated. Reloading...");
        self.location.reload();
    }
}

也就是說當客戶端接受到伺服器端傳送的ok和warning資訊的時候,同時支援HMR的情況下就會要求檢查更新,同時傳送過來的還有伺服器端本次編譯的hash值。我們繼續深入一步,看看伺服器什麼時候傳送’ok’和’warning’訊息:

Server.prototype._sendStats = function(sockets, stats, force) {
    if(!force &&
        stats &&
        (!stats.errors || stats.errors.length === 0) &&
        stats.assets &&
        stats.assets.every(function(asset) {
            return !asset.emitted;
            //每一個asset都是沒有emitted屬性,表示沒有發生變化。如果發生變化那麼這個assets肯定有emitted屬性
        })
    )
        return this.sockWrite(sockets, "still-ok");
    this.sockWrite(sockets, "hash", stats.hash);
    //設定hash
    if(stats.errors.length > 0)
        this.sockWrite(sockets, "errors", stats.errors);
    else if(stats.warnings.length > 0)
        this.sockWrite(sockets, "warnings", stats.warnings);
    else
        this.sockWrite(sockets, "ok");
}

也就是說更新是通過上面這個方法完成的,我們看看上面這個方法什麼時候呼叫就可以了:

compiler.plugin("done", function(stats) {
        this._sendStats(this.sockets, stats.toJson(clientStats));
        this._stats = stats;
    }.bind(this));

是不是豁然開朗了,也就是每次compiler的’done’鉤子函式被呼叫的時候就會要求客戶端去檢查模組更新,進而完成HMR基本功能!

(2)再來看看webpack/hot/dev-server

if(module.hot) {
    var lastHash;
    //__webpack_hash__是每次編譯的hash值是全域性的
    //Only available with the HotModuleReplacementPlugin or the ExtendedAPIPlugin
    var upToDate = function upToDate() {
        return lastHash.indexOf(__webpack_hash__) >= 0;
    };
    var check = function check() {
   // check([autoApply], callback: (err: Error, outdatedModules: Module[]) => void
   // If autoApply is truthy the callback will be called with all modules that were disposed. apply() is automatically called with autoApply as options parameter.(傳入哪些程式碼已經被更新的模組)
   //If autoApply is not set the callback will be called with all modules that will be disposed on apply(). (不是true那麼傳入的是哪些需要被apply處理的模組)
        module.hot.check(true).then(function(updatedModules) {
            //檢查所有要更新的模組,如果沒有模組要更新那麼回撥函式就是null
            if(!updatedModules) {
                console.warn("[HMR] Cannot find update. Need to do a full reload!");
                console.warn("[HMR] (Probably because of restarting the webpack-dev-server)");
                window.location.reload();
                return;
            }
            //如果還有更新
            if(!upToDate()) {
                check();
            }
            require("./log-apply-result")(updatedModules, updatedModules);
            //已經被更新的模組都是updatedModules
            if(upToDate()) {
                console.log("[HMR] App is up to date.");
            }

        }).catch(function(err) {
            var status = module.hot.status();
            //如果報錯直接全域性reload
            if(["abort", "fail"].indexOf(status) >= 0) {
                console.warn("[HMR] Cannot apply update. Need to do a full reload!");
                console.warn("[HMR] " + err.stack || err.message);
                window.location.reload();
            } else {
                console.warn("[HMR] Update failed: " + err.stack || err.message);
            }
        });
    };
    var hotEmitter = require("./emitter");
    //獲取MyEmitter物件
    hotEmitter.on("webpackHotUpdate", function(currentHash) {
        lastHash = currentHash;
        if(!upToDate() && module.hot.status() === "idle") {
            //呼叫module.hot.status方法獲取狀態
            console.log("[HMR] Checking for updates on the server...");
            check();
        }
    });
    console.log("[HMR] Waiting for update signal from WDS...");
} else {
    throw new Error("[HMR] Hot Module Replacement is disabled.");
}

也就是說webpack/hot/dev-server相較於前面在入口檔案中新增的”webpack/hot/only-dev-server”來說,區別在於後者傳入的是哪些已經被更新的模組,也就是已經被自己模組本身dispose處理了。如下:

if (module.hot) {
    module.hot.accept();
    // dispose handler
    module.hot.dispose(() => {
        window.clearInterval(intervalId);
    });
}

(3)如果你注意到上面其實我們還添加了一個client/index.js,這個客戶端程式碼只是添加了我們的客戶端的socket.js程式碼,這時候我們客戶端就可以獲取到伺服器端傳送到的socket命令

var onSocketMsg = {
    //設定hot為true
    hot: function() {
        hot = true;
        log("info", "[WDS] Hot Module Replacement enabled.");
    },
    //列印invalid
    invalid: function() {
        log("info", "[WDS] App updated. Recompiling...");
        sendMsg("Invalid");
    },
    //設定hash
    hash: function(hash) {
        currentHash = hash;
    },
    //繼續可用
    "still-ok": function() {
        log("info", "[WDS] Nothing changed.")
        if(useWarningOverlay || useErrorOverlay) overlay.clear();
        sendMsg("StillOk");
    },
    //設定log級別
    "log-level": function(level) {
        logLevel = level;
    },
    /*
    Shows a full-screen overlay in the browser when there are compiler errors or warnings.
    Disabled by default. If you want to show only compiler errors:
    overlay: true
    If you want to show warnings as well as errors:
    overlay: {
      warnings: true,
      errors: true
    }
     */
    "overlay": function(overlay) {
        if(typeof document !== "undefined") {
            if(typeof(overlay) === "boolean") {
                useWarningOverlay = overlay;
                useErrorOverlay = overlay;
            } else if(overlay) {
                useWarningOverlay = overlay.warnings;
                useErrorOverlay = overlay.errors;
            }
        }
    },
    //ok
    ok: function() {
        sendMsg("Ok");
        if(useWarningOverlay || useErrorOverlay) overlay.clear();
        if(initial) return initial = false;
        reloadApp();
    },
    //客戶端檢測到伺服器端有更新,通過chokidar檢測到檔案的變化
    "content-changed": function() {
        log("info", "[WDS] Content base changed. Reloading...")
        self.location.reload();
    },
    warnings: function(warnings) {
        log("info", "[WDS] Warnings while compiling.");
        var strippedWarnings = warnings.map(function(warning) {
            return stripAnsi(warning);
        });
        sendMsg("Warnings", strippedWarnings);
        for(var i = 0; i < strippedWarnings.length; i++)
            console.warn(strippedWarnings[i]);
        if(useWarningOverlay) overlay.showMessage(warnings);

        if(initial) return initial = false;
        reloadApp();
    },
    errors: function(errors) {
        log("info", "[WDS] Errors while compiling. Reload prevented.");
        var strippedErrors = errors.map(function(error) {
            return stripAnsi(error);
        });
        sendMsg("Errors", strippedErrors);
        for(var i = 0; i < strippedErrors.length; i++)
            console.error(strippedErrors[i]);
        if(useErrorOverlay) overlay.showMessage(errors);
    },
    //傳送訊息close
    close: function() {
        log("error", "[WDS] Disconnected!");
        sendMsg("Close");
    }
};
socket(socketUrl, onSocketMsg);

module.hot等相關方法什麼時候被呼叫

其實通過上面的分析,我們肯定有一點疑問就是,我們的這些module.hot等方法是在什麼時候呼叫的,其實看看HotModuleReplacementPlugin就明白了,下面貼出一部分程式碼:

     parser.plugin("call module.hot.accept", function(expr) {
                if(!this.state.compilation.hotUpdateChunkTemplate) return false;
                if(expr.arguments.length >= 1) {
                    var arg = this.evaluateExpression(expr.arguments[0]);
                    var params = [],
                        requests = [];
                    if(arg.isString()) {
                        params = [arg];
                    } else if(arg.isArray()) {
                        params = arg.items.filter(function(param) {
                            return param.isString();
                        });
                    }
                    if(params.length > 0) {
                        params.forEach(function(param, idx) {
                            var request = param.string;
                            var dep = new ModuleHotAcceptDependency(request, param.range);
                            dep.optional = true;
                            dep.loc = Object.create(expr.loc);
                            dep.loc.index = idx;
                            this.state.module.addDependency(dep);
                            requests.push(request);
                        }.bind(this));
                        if(expr.arguments.length > 1)
                            this.applyPluginsBailResult("hot accept callback", expr.arguments[1], requests);
                        else
                            this.applyPluginsBailResult("hot accept without callback", expr, requests);
                    }
                }
            });
            parser.plugin("call module.hot.decline", function(expr) {
                if(!this.state.compilation.hotUpdateChunkTemplate) return false;
                if(expr.arguments.length === 1) {
                    var arg = this.evaluateExpression(expr.arguments[0]);
                    var params = [];
                    if(arg.isString()) {
                        params = [arg];
                    } else if(arg.isArray()) {
                        params = arg.items.filter(function(param) {
                            return param.isString();
                        });
                    }
                    params.forEach(function(param, idx) {
                        var dep = new ModuleHotDeclineDependency(param.string, param.range);
                        dep.optional = true;
                        dep.loc = Object.create(expr.loc);
                        dep.loc.index = idx;
                        this.state.module.addDependency(dep);
                    }.bind(this));
                }
            });
            parser.plugin("expression module.hot", function() {
                return true;
            });
        });
    });

也就是我們關注的這些module.hot.decline方法都是在Parser上封裝的!

如何寫出支援HMR的程式碼

這裡就是一個例子,你也可以檢視這個倉庫,然後克隆下來,執行”node ./bin/wcf –dev”命令,你就會發現訪問localhost:8080的時候程式碼是可以支援HMR(你可以修改test目錄下的所有的檔案),而不會出現頁面重新整理的情況。

import * as dom from './dom';
import * as time from './time';
import pulse from './pulse';
require('./styles.scss');
const UPDATE_INTERVAL = 1000; // milliseconds
const intervalId = window.setInterval(() => {
    dom.writeTextToElement('upTime', time.getElapsedSeconds() + ' seconds');
    dom.writeTextToElement('lastPulse', pulse());
}, UPDATE_INTERVAL);
// Activate Webpack HMR
if (module.hot) {
    module.hot.accept();
    // dispose handler
    module.hot.dispose(() => {
        window.clearInterval(intervalId);
    });
}

其中accept函式簽名如下:

accept(dependencies: string[], callback: (updatedDependencies) => void) => void
accept(dependency: string, callback: () => void) => void
//直接接受當前模組某一個依賴模組的HMR

此時表示,我們這個模組支援HMR,任何其依賴的模組變化都會被捕捉到。當依賴的模組更新後回撥函式被呼叫。當然,如果是下面這種方式:

accept([errHandler]) => void

那麼表示我們接受當前模組所有依賴的模組的程式碼更新,而且這種更新不會冒泡到父級中去。這當我們模組沒有匯出任何東西的情況下有用(比如entry)。

其中decline函式

上面的例子中我們的dom.js是如下方式寫的:

import $ from 'jquery';
export function writeTextToElement(id, text) {
    $('#' + id).text(text);
}
if (module.hot) {
    module.hot.decline('jquery');//不接受jquery更新
}

其中decline方法簽名如下:

decline(dependencies: string[]) => void
decline(dependency: string) => void

這表明我們不會接受特定模組的更新,如果該模組更新了,那麼更新失敗同時失敗程式碼為”decline”。而上面的程式碼表明我們不會接受jquery模組的更新。當前也可以是如下模式:

decline() => void

這表明我們當前的模組是不會更新的,也就是不會HMR。如果更新了那麼錯誤程式碼為”decline”;

其中dispose函式

函式簽名如下:

dispose(callback: (data: object) => void) => void
addDisposeHandler(callback: (data: object) => void) => void

這表示我們會新增一個一次性的處理函式,這個函式在當前模組更新後會被呼叫。此時,你需要移除或者銷燬一些持久的資源,如果你想將當前的狀態資訊轉移到更新後的模組中,此時可以新增到data物件中,以後可以通過module.hot.data訪問。如下面的例子用於儲存指定模組例項化的時間,從而防止模組更新後資料丟失(重新整理後還是會丟失的)。

let moduleStartTime = getCurrentSeconds();
function getCurrentSeconds() {
    return Math.round(new Date().getTime() / 1000);
    // return new Date().getTime() / 1000;
}
export function getElapsedSeconds() {
    return getCurrentSeconds() - moduleStartTime;
}
// Activate Webpack HMR
if (module.hot) {
    const data = module.hot.data || {};
    // Update our moduleStartTime if we are in the process of reloading
    if (data.moduleStartTime)
        moduleStartTime = data.moduleStartTime;
    // dispose handler to pass our moduleStart time to the next version of our module
    // 首次進入我們把當前時間儲存到moduleStartTime中以後就可以直接訪問
    module.hot.dispose((data) => {
        data.moduleStartTime = moduleStartTime;
    });
}

hotUpdateChunkFilename vs hotUpdateMainFilename

當你修改了test目錄下的檔案的時候,比如修改了scss檔案,此時你會發現在頁面中多出了一個script元素,內容如下:

<script type="text/javascript" charset="utf-8" src="0.188304c98f697ecd01b3.hot-update.js"></script>

其中內容是:

webpackHotUpdate(0,{
/***/ 15:
/***/ (function(module, exports, __webpack_require__) {
exports = module.exports = __webpack_require__(46)();
// imports
// module
exports.push([module.i, "html {\n  border: 1px solid yellow;\n  background-color: pink; }\n\nbody {\n  background-color: lightgray;\n  color: black; }\n  body div {\n    font-weight: bold; }\n    body div span {\n      font-weight: normal; }\n", ""]);
// exports

/***/ })
})
//# sourceMappingURL=0.188304c98f697ecd01b3.hot-update.js.map

從內容你也可以看出,只是將我們修改的模組push到exports物件中!而hotUpdateChunkFilename就是為了讓你能夠執行script的src中的值的!而同樣的hotUpdateMainFilename是一個json檔案用於指定哪些模組發生了變化,在output目錄下。

webpack和webpack-dev-server關係

webpack首先打包成檔案放在具體的目錄下,並通過publicPath配置成了虛擬路徑,而當通過URL訪問伺服器的時候就會從req.url尋找了具體的輸出檔案,最後得到這個輸出檔案並原樣傳送到客戶端。看下面的方法你就明白了:

var pathJoin = require("./PathJoin");
var urlParse = require("url").parse;
function getFilenameFromUrl(publicPath, outputPath, url) {
    var filename;
    // localPrefix is the folder our bundle should be in
    // 第二個引數如果為false那麼查詢字串不會被decode或者解析
    // 第三個引數為true,那麼//foo/bar被解析為{host: 'foo', pathname: '/bar'},也就是第一個"//"後,'/'前解析為host
    // 如配置為 publicPath: "/assets/"將會得到下面的結果:
    /*
     Url {
      protocol: null,
      slashes: null,
      auth: null,
      host: null,
      port: null,
      hostname: null,
      hash: null,
      search: null,
      query: null,
      pathname: '/assets/
      path: '/assets/',
      href: '/assets/' 
     }
     */
    var localPrefix = urlParse(publicPath || "/", false, true);
    var urlObject = urlParse(url);
    //URL是http請求的真實路徑,如http://localhost:1337/hello/world,那麼req.url得到的就是/hello/world
    // publicPath has the hostname that is not the same as request url's, should fail
    // 訪問的url的hostname和publicPath中配置的host不一致,直接返回。這隻有在publicPath是絕對URL的情況下出現
    if(localPrefix.hostname !== null && urlObject.hostname !== null &&
        localPrefix.hostname !== urlObject.hostname) {
        return false;
    }
    // publicPath is not in url, so it should fail
    // publicPath和req.url必須一樣
    if(publicPath && localPrefix.hostname === urlObject.hostname && url.indexOf(publicPath) !== 0) {
        return false;
    }
    // strip localPrefix from the start of url
    // 如果url中的pathname和publicPath一致,那麼請求成功,檔名為urlObject中除去publicPath那一部分的結果
    // 如上面/hello/world表示req.url,而且publicPath為/hello/那麼得到的檔名就是world
    if(urlObject.pathname.indexOf(localPrefix.pathname) === 0) {
        filename = urlObject.pathname.substr(localPrefix.pathname.length);
    }

    if(!urlObject.hostname && localPrefix.hostname &&
        url.indexOf(localPrefix.path) !== 0) {
        return false;
    }
    // and if not match, use outputPath as filename
    //如果有檔名那麼從output.path中獲取該檔案,檔名為我們獲取到的檔名。否則返回我們的outputPath
    //也就是說:如果沒有filename那麼我們直接獲取到我們的output.path這個目錄
    return filename ? pathJoin(outputPath, filename) : outputPath;
}
module.exports = getFilenameFromUrl;

上面這個從URL到路徑的轉化就是通過webpack-dev-middleware來完成的。

參考資料:

相關推薦

webpack-dev-server原理分析HMR實現

建議在github閱讀,我會保證內容及時更新,並歡迎star,issue。如果你想深入瞭解webpack-dev-server的內部原理,你也可以檢視我寫的這個打包工具,通過它可以完成三種打包方式,其中devServer模式就是通過webpack-dev-ser

tomcat原理分析簡單實現

一、思路概述 1.tomcat實際是執行在jvm中的一個程序。我們把它定義為【中介軟體】,顧名思義,他是一個在java專案與jvm之間的 中間容器。我們的web專案沒有入口方法(main方法),那麼他是如何執行起來併為客戶端返回資料的呢? 2.web專案[就javaee而講]

機器學習系列文章:Apriori關聯規則分析演算法原理分析程式碼實現

1.關聯規則淺談     關聯規則(Association Rules)是反映一個事物與其他事物之間的相互依存性和關聯性,如果兩個或多個事物之間存在一定的關聯關係,那麼,其中一個事物就能通過其他事物預測到。關聯規則是資料探勘的一個重要技術,用於從大量資料中挖掘出有價值的資料

歸併排序演算法原理分析程式碼實現

  歸併排序是建立在歸併操作上的一種有效的排序演算法。該演算法是採用分治法(Divide and Conquer)的一個非常典型的應用,歸併排序將兩個已排序的表合併成一個表。 歸併排序基本原理

第五篇:樸素貝葉斯分類演算法原理分析程式碼實現

1 #==================================== 2 # 輸入: 3 # 空 4 # 輸出: 5 # postingList: 文件列表 6 # classVec: 分類標籤列表 7 #=

詳情介紹webpack-dev-server,iframeinline的區別

webpack-dev-server用法 記錄下 webpack-dev-server 的用法. 首先,我們來看看基本的 webpack.config.js 的寫法 module.exports = { entry: './src/js/index.j

C++智慧指標原理分析簡單實現

一個簡單智慧指標實現的思路如下:   智慧指標,簡單來講是使用引用計數的方法,來跟蹤監控指標。當引用計數為0時就delete 所跟蹤的目標指標,釋放記憶體   智慧指標將一個指標封裝到一個類中,當呼叫

Spring裝配基本屬性的原理分析程式碼實現

<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-ins

Apriori 關聯分析算法原理分析代碼實現

muc items blog 具體實現 itblog run 任務 name subset 轉自穆晨 閱讀目錄 前言 關聯分析領域的一些概念 Apriori算法基本原理 頻繁項集檢索實現思路與實現代碼 關聯規則學習實現思路與實現代碼 小結 回到頂部 前言

K-Means 聚類算法原理分析代碼實現

oat 得到 ssi targe fan readline txt __name__ 輸出 轉自穆晨 閱讀目錄 前言 現實中的聚類分析問題 - 總統大選 K-Means 聚類算法 K-Means性能優化 二分K-Means算法 小結 回到頂部 前言 在

webpack-dev-server 小記 原理介紹 概念解讀

style 事情 可能 asc 監聽文件 地址 dev ces 內容 使用 DevServer 提供 HTTP 服務而不是使用本地文件預覽 監聽文件的變化並自動刷新網頁,做到實時預覽 支持 Source Map,以方便調試   對於這些,Webpack 都為我們考慮好了。W

webpack4 系列教程(十五):開發模式webpack-dev-server

作者按:因為教程所示圖片使用的是 github 倉庫圖片,網速過慢的朋友請移步《webpack4 系列教程(十五):開發模式與 webpack-dev-server》原文地址。更歡迎來我的小站看更多原創內容:godbmw.com,進行“姿勢”交流 ♪(^∇^*) 0. 課程介紹和資料 &g

webpack-dev-server實現專案熱部署

文章目錄 webpack-dev-server webpack-dev-server作用 新增webpack-dev-server 修改原始碼 執行結果 webpack-dev-server webp

webpack-dev-server啟動後,localhost:8080返回index.html的原理

webpack-dev-server是一個採用Node.js Express實現的微型伺服器, 內部使用webpack-dev-middleware來響應傳送到伺服器監聽單口的HTTP請求。 webpack-dev-server主要用於前端專案的本地開發和除錯。 具體使用,只需要在package.json

使用webpack-dev-server自動打包並實現debug除錯

webpack-dev-server 是一個開發伺服器,它的功能就是可以實現熱載入,並且自動重新整理瀏覽器 準備工作: 建立一個程式目錄test,將html頁面拷貝進來,在目錄下新建src、dist目錄 將main.js(入口js檔案)、vue.min.js以及mode

Vue系列之 => 使用webpack-dev-server工具實現自動打包編譯

安裝webpack-dev-server (webpack版本3.6.0,webpack-dev-server版本2.11.3)注意版本相容問題,不然會有N多錯誤。  1 npm i [email protected] -D //安裝到本地依賴   webpack

選擇排序原理分析java程式碼實現

1、選擇排序改進了氣泡排序,將必要的交換次數從 O(N^2 )減少到 O(N)次(理解選擇排序可以先看一下我的上一篇氣泡排序的部落格)不幸的是比較次數仍然保持為 O(N^2 )。然而,選擇排序仍然為大記錄量的排序提出了一個非常重要的改進,因為這些大量的記錄需要在記憶體中移動,

淺析資料庫連線池原理分析實現

1 動機在專案初期對於資料庫的使用就是開啟一個連線並進行使用,使用過後關閉連線釋放資源,並且在後臺簡單測試中,並沒有出現問題。但是在與前端對接之後,發現頻繁地開啟和關閉連線會對效能造成很大的影響,而且之前假設的情況是接受的請求都是同步的,但是前端可能傳送非同步請求,當兩個請求

webpack-dev-server live reloading 技術實現

.html sock 系統監控 master 文件管理器 另一個 des file fse webpack-dev-server live reloading https://github.com/webpack/webpack-dev-server Use we

webpack-dev-server啟動失敗

target href -s 失敗 http ima pack pac html 學習webpack-dev-server過程中,項目路徑下執行webpack-dev-server,老是報錯,原來是配置項在colors在webpack2.0以上版本不需要進行配置了,