固然這裏更可能是菜狗子的一家之言,片面不全,竟然還有一堆謬誤。歡迎斧正。javascript
既然說到熱更新,咱們不妨擴展下,補充下前端自動更新的實現。 我的才疏學淺,見過的方式大體分兩種html
bowersync
的方式,直接reload,簡單粗暴,規避了許多問題webpack-dev-server
的HMR
簡單討論下webpack-hot-middleware
到底是怎麼實現了熱更新。前端
這裏咱不討論如何替換和覆蓋以前執行的結果java
我的理解:其實就是一個簡單的事件機制node
本質就是一個鏈接往前端發數據webpack
// server 端
...
publish: function(payload) {
/** * erveryClient 就是對每一個鏈接都寫入。 * 連接報頭,確保連接不會被斷開 * Content-Type: 'text/event-stream;charset=utf-8', * Connection: 'keep-alive', 這個只會在http1開啓 */
everyClient(function(client) {
client.write('data: ' + JSON.stringify(payload) + '\n\n');
});
},
...
複製代碼
對接受到數據進行處理。固然這裏的數據其實約定過格式。因爲這部分代碼是運行在前端,剩下的就是基礎的dom操做云云了。c++
/** * @see https://github.com/webpack-contrib/webpack-hot-middleware/blob/master/client.js */
function processMessage(obj) {
switch (obj.action) {
case 'building':
if (options.log) {
console.log(
'[HMR] bundle ' +
(obj.name ? "'" + obj.name + "' " : '') +
'rebuilding'
);
}
break;
// ...
default:
if (customHandler) {
customHandler(obj);
}
}
if (subscribeAllHandler) {
subscribeAllHandler(obj);
}
}
複製代碼
以前採用 socket.io(目前webpack-dev-server你就能找到相似實現的方式),如今改用EventSource方式。git
順帶一提,裏面實現的一套提升穩定性的機制。github
// client
// 每次來信息更新最新活動時間,定時檢查是否超出超時時間
function handleOnline() {
if (options.log) console.log('[HMR] connected');
lastActivity = new Date();
}
function handleMessage(event) {
lastActivity = new Date();
for (var i = 0; i < listeners.length; i++) {
listeners[i](event);
}
}
var timer = setInterval(function() {
if (new Date() - lastActivity > options.timeout) {
handleDisconnect();
}
}, options.timeout / 2);
// server
// 定時給前端發信息,用於更新前端的活動時間
...
var interval = setInterval(function heartbeatTick() {
everyClient(function(client) {
client.write('data: \uD83D\uDC93\n\n');
});
}, heartbeat).unref();
...
複製代碼
簡單討論完前端的熱更新基礎以後,咱們來了解下node層面的熱更新。web
首先,咱們遇到第一個問題,如何將代碼更新到運行中的程序中(emm這種表述怪怪的)
evel
執行文件Function
構造函數,建立函數vm
模塊執行代碼我試驗的時候發現,其實有以下的一個小問題
const vm = require("vm");
var a = 1;
var b = 1;
var c = 1;
d = 1;
vm.runInNewContext(
` console.log('vm',d) console.log('vm',typeof a); a = 2; console.log('vm',a);`,
global
);
console.log("a", a);
console.log("-------");
eval(` console.log('eval',typeof b); b = 2; console.log('eval',b) `);
console.log("b", b);
console.log("-------");
const test = Function(
` console.log('function',d) console.log('function',typeof c); c = 2; console.log('function',c)`
);
test();
console.log("c", c);
/** * 輸出結果 vm 1 vm undefined vm 2 a 1 ------- eval number eval 2 b 2 ------- function 1 function undefined function 2 c 1 */
複製代碼
以上
Function執行結果
我發現與瀏覽器端並不一致,細究緣由: 1.Function
只能讀取全局變量與其內部的變量 2. node加載執行都會包裹一層函數,而咱們在瀏覽器中直接在全局聲明一個變量至關於爲當前全局對象附加一個屬性,值爲變量值。這裏看變量d
就能比較清楚的知道。
回到正題,其實咱們都知道關於加載模塊,其實node中自己就提供了一個超級好的方法,能夠引入一個文件(js,node等)基於這個咱們彷佛就能很簡單就能夠實現拍黃片那樣的熱更新了~咱們不妨先一塊兒看下node中是怎麼實現的require
的
Talk is cheap ,show me the code. 先扔兩個源碼地址
如今咱們深刻下,看下node是怎麼處理js的模塊的,估計我寫的也不咋地,你們能夠先看下阮一峯大佬的講解 不過好像他的版本有點點老TAT
node模塊加載部分的一些源碼,簡單講下整個程序的運行,其實就是程序引入初始化一個main module以後,require的時候每一個引用文件在新建module,同時緩存,記錄關係,最後造成一個樹形結構。整個過程能夠從這裏起步開始看。
/** * 刪除了debug的一些輸出具體,具體要看能夠看下源碼 * @link https://github.com/nodejs/node/blob/0817840f775032169ddd70c85ac059f18ffcc81c/lib/internal/modules/cjs/loader.js#L874:33 */
Module._load = function(request, parent, isMain) {
const filename = Module._resolveFilename(request, parent, isMain);
/** * 會緩存一次執行結果,因此每一個模塊引入只會被執行一次。 * 若是想再次執行須要清楚掉 * 這個cache 會被代理到require上 * 在reqire定義那能夠看到:`require.cache = Module._cache;` */
const cachedModule = Module._cache[filename];
if (cachedModule) {
// 這裏會記錄一次模塊的引用關係,對gc回收的引用有影響。
updateChildren(parent, cachedModule, true);
return cachedModule.exports;
}
const mod = NativeModule.map.get(filename);
if (mod && mod.canBeRequiredByUsers) {
return mod.compileForPublicLoader(experimentalModules);
}
/* * Don't call updateChildren(), Module constructor already does. * 上面這個是原註釋。就不翻譯了 */
const module = new Module(filename, parent);
if (isMain) {
// 要項目的引用結構其實能夠mainModule,遞歸打印
process.mainModule = module;
module.id = '.';
}
Module._cache[filename] = module;
/** * 有興趣能夠看下這個函數 * 這裏我只大概梳理下加載過程 * 1. 設定引用可能的文件夾(在module的paths裏你能夠很清楚的看到結果) * + 所在目錄 * + 所在目錄的node_modules * + /node_modules 目錄 * 2. 獲取文件後綴(最後一個「.」開始的內容,tips:以「.」不算哈,會被排除) * 3. 根據文件後綴調用預設的函數解析。 * * 其實這個過程會有一堆的嘗試操做。這裏不作過多贅述。 * 自行查看'tryPackage,tryFile,tryExtensions' * 因此爲了性能能好一丟丟,你們能夠把引用盡可能寫的清晰。 * 省去程序猜你引用的路徑,還要讀package.json云云 ***** ***** * emmm 這裏其實他還約定了一個experimentalModules * @link https://github.com/nodejs/node-eps/blob/master/002-es-modules.md */
tryModuleLoad(module, filename);
return module.exports;
};
複製代碼
這裏在簡單補充下js文件的解析過程
首先咱們的文件其實都是一些文本信息,首先會被包裹在一個函數裏。
tips:(在字符串前加上 `(function (exports, require, module, __filename, __dirname) { `尾部加上後`\n}`);
而後依據是否修改過包裹方式調用`compileFunction` 或者vm模塊的`runInThisContext`方法,
emmm這兩個方法都是c++那邊實現了,就不是很看得懂了TAT
@link https://github.com/nodejs/node/blob/5f8ccecaa2e44c4a04db95ccd278a7078c14dd77/src/node_contextify.h。
複製代碼
emmmm 到這裏咱們大概能知道node裏模塊是怎麼加載的。大概都忘記標題了吧,回顧下,咱們目前要解決的是如何把內容加載進程序。也順帶知道了這些__filename
這些常量是怎麼來的。
【FBI WARNING】這個其實很麻煩,個人作法必定不會很全很完美,可是作的很差就很容易致使內存消耗愈來愈高
舊的不去新的不來,咱們如今已經有法子引來新的,那接着來了解下node中如何去掉舊的。簡單瞭解下gc機制。
我只是菜狗子,看不懂c++源碼。webkit技術內幕啥的也沒耐着性子看完,只看了一些文章和深刻淺出nodejs,如下內容來自於概括,主要來源於深刻nodejs第五章。
裂牆安利【深刻淺出nodejs】
--max-old-space-size
和--max-new-space-size
設置最大值通過上面那一串逼逼,咱們大體明白了,要刪掉舊的,就刪除引用就玩球。因此咱們只須要
require
以後刪除require.cache
上的內容因此針對node熱更新(熱部署)我我的給如下方法
require.cache
等等,還有主要是你的引用!require.cache
等等,還有主要是你的引用!require
完結撒花