在終端里輸入 npm start 后都發(fā)生了啥
前言
在前面的內(nèi)容純屬胡說八道,如果想要看正文,請直接滾動條往下拉,以省下寶貴的時間繼續(xù)卷。
要想對 JavaScript 代碼進行打包,我們可以依賴 webpack 對我們的幫助我們完成這一件事情。要想使用 webpack,首先需要我們安裝 webpack,首先對項目進行初始化:
npm init -y
生成配置配置文件:
圖片
要想使用 webpack,首先需要安裝 webpack 以及 webpack-cli,這里還有 html-webpack-plugin,用于生成 html 模板,具體命令如下:
npm install webpack webpack-cli html-webpack-plugin -D
依賴安裝完成之后,我們需要在跟目錄上面創(chuàng)建一個名為 webpack.config.js,當然,你也可以創(chuàng)建其他文件名的 js 文件,并添加以下配置:
const path = require("path");
const HtmlWebpackPlugin = require("html-webpack-plugin");
module.exports = {
entry: "./src/index.js",
output: {
filename: "bundle.js",
path: path.resolve(__dirname, "./dist"),
},
mode: "production",
plugins: [
new HtmlWebpackPlugin({
template: "./index.html",
}),
],
};
為了能執(zhí)行打包命令,我們在 package.json 文件中的 script 中添加這一段命令:
"build": "webpack"
如果你使用的 webpack 配置文件名為其他的則需要在該命令中添加相對應的路徑,否則在執(zhí)行命令的時候 webpack-cli 則無法找到相關的配置。
接下來我們創(chuàng)建在根目錄下創(chuàng)建一個 src 目錄,在目錄下面創(chuàng)建一個 index.js 文件,作為整個項目的入口,并在文件中添加一些自己想寫的代碼:
console.log("hello webpack");
此時,在命令行中輸入一下命令:
npm run build
此時文件被輸出出來了:
圖片
生成的文件是根據(jù)我們前面的 webpack 配置文件中生成的,通過運行 index.html 文件,hello webpack 也被輸出在瀏覽器控制臺上面:
圖片
但是這個方法存在弊端,當我們對源代碼進行修改的時候,它并不會對我所修改的代碼進行重新編譯,要想能夠在瀏覽器中看到最新效果,你必須通過重新執(zhí)行 npm run build 打包才可以,開發(fā)效率極其低下。
watch
webpack 有一個 watch 屬性可以監(jiān)聽文件的變化,當監(jiān)聽到文件變化,當它們修改后會重新編譯,要啟用 watch,你只需要在 webpack.config.js 文件中設置 watch:true即可,詳情如下:
module.exports = {
...,
watch: true,
};
或者在 package.json 文件中 script 下修改添加 --watch.代碼如下所示:
"build": "webpack --watch"
控制臺再次執(zhí)行 npm run build,你會發(fā)現(xiàn)這次控制臺不會結(jié)束了,會一直開啟著,詳情請看下圖:
圖片
當我們修改文件內(nèi)容的時候,watch 會監(jiān)聽著文件變化,如下圖終端所示:
圖片
并且瀏覽器上的內(nèi)容也會隨著變化而變化,你也可以配置 watchOptions 來使你的項目更快,例如:
watchOptions: {
aggregateTimeout: 6000,
ignored: /node_modules/,
},
在上面的配置中,當?shù)谝粋€文件更改,會在重新構(gòu)建前增加延遲,它會將這段時間內(nèi)的所有更改都聚合到一次重新構(gòu)建中,以毫秒為單位。對于某些系統(tǒng),監(jiān)聽大量文件會導致大量的 CPU 或內(nèi)存占用??梢允褂谜齽t排除像 node_modules 如此龐大的文件夾。
盡管有這些配置,效率仍然不高,編譯成功后,都會生成新的文件,并且需要開啟 live-server,但是這個插件屬于 vscode 的,但是在其他編輯器上并沒有,并不屬于 webpack。
live-server 每次都會重新刷新整個頁面,并不能保存當前頁面的狀態(tài),還會編譯所有的代碼。
webpack-dev-server的基本使用
webpack-dev-server(簡稱 WDS) 為你提供了一個基本的 web server,并且具有 live reloading(實時重新加載)功能。要想使用使用你首先要安裝該依賴:
npm install --save-dev webpack webpack-dev-server
繼續(xù)在 package.json 文件中 script 下修改添加一段命令.代碼如下所示:
"start": "webpack serve --open"
完成之后,在終端下輸入以下命令執(zhí)行:
npm start
WDS 會自動為為你自動開啟電腦上的默認瀏覽器,并且默認的端口為 http://localhost:8080/,在終端里有如下輸出:
圖片
在瀏覽器上有如下輸出:
圖片
默認開啟 live Reload ,當代碼發(fā)生改變時會自動重新對代碼進行編譯。
WDS 原理
webpack-dev-server 啟動了一個使用 express 的HTTP服務,這個服務器與客戶端采用 WebSocket 通信協(xié)議,當原始文件發(fā)生改變,webpack-dev-server 會實時編譯,但是對 index.html 的修改不會做出處理。
通過查看 webpack-dev-server 源碼中的 package.json 文件,我們發(fā)現(xiàn)這里定義了一個 bin 字段,那么這個 bin 有什么用呢?
bin 字段是命令名到本地文件名的映射。當我們使用 npm 或者 yarn 命令安裝包時,如果該包的 package.json 文件有 bin 字段,就會在 node_modules 文件夾下面的 .bin 目錄中復制了 bin 字段鏈接的執(zhí)行文件。我們在調(diào)用執(zhí)行文件時,可以不帶路徑,直接使用命令名來執(zhí)行相對應的執(zhí)行文件。
也就是說,當我們在終端中輸入 npm install webpack-dev-server -D 的時候,會在 node_modules/.bin 目錄下生成了三個文件:
圖片
這三個文件中,其中的 ·cmd 是 windows 中默認的可執(zhí)行文件,當我們不添加后綴名時,自動根據(jù) pathext 查找文件。
當我們執(zhí)行 npm start 的時候,會在 node_modules/.bin 目錄下找到 webpack-dev-server.cmd 目錄,因為這個文件是 windows 的批處理腳本:
@ECHO off
GOTO start
:find_dp0
SET dp0=%~dp0
EXIT /b
:start
SETLOCAL
CALL :find_dp0
IF EXIST "%dp0%\node.exe" (
SET "_prog=%dp0%\node.exe"
) ELSE (
SET "_prog=node"
SET PATHEXT=%PATHEXT:;.JS;=;%
)
endLocal & goto #_undefined_# 2>NUL || title %COMSPEC% & "%_prog%" "%dp0%..\webpack-dev-server\bin\webpack-dev-server.js" %*
在這寫代碼里的最后一行 "%dp0%..\webpack-dev-server\bin\webpack-dev-server.js" 命令中,通過軟連接鏈接到 node_modules/webpack-dev-server/bin 中的 webpack-dev-server.js 目錄。
所以當我們運行 npm start 的時候,也就是相當于運行 node_modules/.bin/webpack-dev-server.cmd serve 命令,并最終以 webpack-dev-server/bin/webpack-dev-server.js 就是整個命令行的入口,通過這里,它會自動給你開啟 webpack-cli,詳情請看下圖:
圖片
所以當我們沒有安裝 webpack-cli 的時候運行 npm start 時會有以下提示:
圖片
在這里 webpack 就會基于我們 webpack.config.js 里創(chuàng)建一個 compiler,然后基于 compiler 和 devServer 相關配置生成一個 WebpackDevServer 實例,該實例會啟動一個expores 服務來幫我們監(jiān)聽靜態(tài)資源變化并更新。
在 webpack-dev-server 源代碼中,有一個 Server.js 的目錄,創(chuàng)建了一個 Server 類,用于啟動的是 start(...) 方法中通過 socket 監(jiān)聽一個端口,默認使用的是 8080,初始化 client 和 dev-server,以 p[lugin 的形式掛載到 compiler 上,添加 hooks 插件,實例化 express 服務等等,有以下代碼,詳情可自行查看,可以通過安裝依賴的方式,也可以到 GitHub:
圖片
接下來我們看看 await this initialize(...) 都干了些啥?詳情請看下列代碼(省略了后面部分):
async initialize() {
if (this.options.webSocketServer) {
const compilers =
/** @type {MultiCompiler} */
(this.compiler).compilers || [this.compiler];
compilers.forEach((compiler) => {
this.addAdditionalEntries(compiler);
const webpack = compiler.webpack || require("webpack");
new webpack.ProvidePlugin({
__webpack_dev_server_client__: this.getClientTransport(),
}).apply(compiler);
compiler.options.plugins = compiler.options.plugins || [];
if (this.options.hot) {
const HMRPluginExists = compiler.options.plugins.find(
(p) => p.constructor === webpack.HotModuleReplacementPlugin
);
if (HMRPluginExists) {
this.logger.warn(
`"hot: true" automatically applies HMR plugin, you don't have to add it manually to your webpack configuration.`
);
} else {
// Apply the HMR plugin
const plugin = new webpack.HotModuleReplacementPlugin();
plugin.apply(compiler);
}
}
});
if (
this.options.client &&
/** @type {ClientConfiguration} */ (this.options.client).progress
) {
this.setupProgressPlugin();
}
}
在 initialize() 方法中還有以下方法的調(diào)用:
this.setupHooks();
this.setupApp();
this.setupHostHeaderCheck();
this.setupDevMiddleware();
this.setupBuiltInRoutes();
this.setupWatchFiles();
this.setupWatchStaticFiles();
this.setupMiddlewares();
this.createServer();
在這里,主要做的事情是將 client 以 plugin 的形式掛載到 compiler,如果存在 HMR,則開啟 HMR,通過 this.setupWatchFiles() 監(jiān)聽文件變化。
在 this.setupHooks() 中,主要做的事情就是在 webpack 的 done 鉤子上掛了個給客戶端廣播消息的回調(diào),通過這個回調(diào)就知道工程代碼有更新,這時候客戶端就會發(fā)送請求給 express 服務去請求最新的 webpack 打包的代碼,詳情請看以下代碼:
setupHooks() {
this.compiler.hooks.invalid.tap("webpack-dev-server", () => {
if (this.webSocketServer) {
this.sendMessage(this.webSocketServer.clients, "invalid");
}
});
this.compiler.hooks.done.tap(
"webpack-dev-server",
/**
* @param {Stats | MultiStats} stats
*/
(stats) => {
if (this.webSocketServer) {
this.sendStats(this.webSocketServer.clients, this.getStats(stats));
}
/**
* @private
* @type {Stats | MultiStats}
*/
this.stats = stats;
}
);
}
當客戶端接收到 websocket 廣播的消息后,會觸發(fā)reloadApp方法(webpack打包時注入進去的)reloadApp會根據(jù)廣播消息里的更新類型選擇是頁面更新 liveReload 還是模塊更新 HMR,通過測試,發(fā)現(xiàn)每次修改正是都會經(jīng)過 sendMessage 方法。
圖片
上圖正是 webpack-dev-server 的整個流程圖,到這來,這篇文章的內(nèi)容也就講完了,如有錯誤,煩請批評指出。
本文轉(zhuǎn)載于:https://juejin.cn/post/7187368174952644665
參考文獻
- # webpack-dev-server運行原理[1]
- WDS源碼[2]