webpack內存讀取技術調研
最近參與到一個項目,需要在線上快速打包和快速讀取,為了提高速率,當時我們想到了webpack dev模式下打包文件是臨時貯存在內存中的,想學習一下webpack的這種技術是怎么實現的,好應用到項目中。
1.webpack原理
https://juejin.im/entry/5b0e3eba5188251534379615
看這篇文章就夠了,很細致,這篇博客主要講下webpack的memory-fs系統,整體原理就不多說了。
2.Webpack HMR 原理解析
這個一點是最有意思的,Hot Module Replacement ,類似熱更新,就是因為這個我們保存代碼后瀏覽器沒有刷新前提下自動改變對應的變化,且狀態不丟失。
總流程就是下面這樣,使用了sockjs(websocket)雙工通信,稍有點復雜,參考這篇文章:https://juejin.im/entry/5a0278fe6fb9a045076f15b9
講的很詳細了。

這里我最關心的是中間過程中wenpack打包的文件跑去哪里了,你在磁盤里是找不到output.path 目錄的,這里也就是我需要調研的東西:memory-fs。
3.memory-fs內存文件系統
memory-fs:NodeJS原生fs模塊內存版(in-memory)的完整功能實現。相比於從磁盤讀寫數據,memory-fs是內存緩存和快速數據處理(fast data processing)的完美替代方案。
webpack 通過自己實現的memory-fs將 bundle.js 文件打包到了內存中,訪問內存中的代碼文件也就更快,也減少了代碼寫入文件的開銷。
memory-fs 是 webpack-dev-middleware 的一個依賴庫,webpack-dev-middleware 將 webpack 原本的 outputFileSystem (node的fs系統)替換成了MemoryFileSystem 實例,這樣代碼就將輸出到內存中。webpack-dev-middleware 中該部分源碼如下:
// webpack-dev-middleware/lib/Shared.js
var isMemoryFs = !compiler.compilers && compiler.outputFileSystem instanceof MemoryFileSystem;
if(isMemoryFs) {
fs = compiler.outputFileSystem;
} else {
fs = compiler.outputFileSystem = new MemoryFileSystem();
}
只要你項目中使用了webpack,上面代碼和MemoryFileSystem關鍵字你都可以搜索到。
首先判斷當前 fileSystem 是否已經是 MemoryFileSystem 的實例,如果不是,用 MemoryFileSystem 的實例替換 compiler 之前的 outputFileSystem。這樣 bundle.js 文件代碼就作為一個簡單 javascript 對象保存在了內存中,當瀏覽器請求 bundle.js 文件時,devServer就直接去內存中找到上面保存的 javascript 對象返回給瀏覽器端。
4.memort-fs使用
這個模塊我在npm.js里面沒有找到,google中找到了webpack組織中把這個模塊單獨放在了一個倉庫中,但是並沒有在npm上發布。
memory-fs官方鏈接
我們使用需要自己引用其中相關的源碼,主要代碼在MemoryFileSystem.js,看源碼可知里面實現了大部分node的fs函數,但是都是Sync版,即同步版,數據直接返回,沒有回調,所以直接用變量接受函數即可。(node的fs同步版用法也是如此,沒有回調函數,非同步版可以改寫promise形式)。
簡單使用:
var MemoryFileSystem = require("./MemoryFileSystem");
var fs = new MemoryFileSystem(); // Optionally pass a javascript object
const fs2 = require('fs');
//創建 /tmp/a/apple 目錄,不管 `/tmp` 和 /tmp/a 目錄是否存在。 node v10的版本才有
fs2.mkdir('/tmp/a/apple', { recursive: true }, (err) => {
if (err) throw err;
});
fs.mkdirpSync("/a/test/dir");
fs.writeFileSync("/a/test/dir/file2.txt", "Hello World2");
console.log(fs.readFileSync("/a/test/dir/file2.txt",'utf-8'));// returns Buffer("Hello World"))
// fs.readFileSync("/a/test/dir/file2.txt",function(err,data) {
// console.log(data);
// })
fs2.mkdir('ass/dasdsa', { recursive: true }, (err) => {
if (err) throw err;
});
寫了一些小demo:鏈接
注意這里和node的mkdir區別,v8版本的mkdir需要創建前面的文件夾才能到創建下層,但是v10支持了一下子多層創建,memory-fs這里也支持。且memory-fs需要在最前面加一個/,writeFileSync后,發現磁盤並沒有相關文件,因為是寫在了內存里,用readFileSync可從內存中讀取。
5. 對源碼的擴充
雖然實現大部門原生fs的函數,但是還是一些沒有實現,比如復制文件夾,而且根據業務需求,我們需要從磁盤中復制某個文件夾,並把這個文件夾的所有內容拷貝到內存中,然后再沖內存中讀。
這里我自己實現的函數:
var MemoryFileSystem = require("./MemoryFileSystem");
var fs = new MemoryFileSystem();
const _fs = require("fs")
function copyDir(from, to) {
if (!fs.existsSync(to)) {
fs.mkdirSync(to);
}
const paths = _fs.readdirSync(from);
paths.forEach((path) => {
var src = `${from}/${path}`;
var dist = `${to}/${path}`;
const res = _fs.statSync(src);
// _fs.stat(src, function (err, stat) {
if (res.isFile()) {
fs.writeFileSync(dist, _fs.readFileSync(src));
// console.log(chalk.magenta(`🏇 copy ${src} `));
} else if (res.isDirectory()) {
copyDir(src, dist);
}
// })
});
}
//
copyDir("from","/to");
console.log(fs.readFileSync("/to/file2.txt",'utf-8'));
console.log(fs.readFileSync("/to/from2/file1.txt",'utf-8'));
如果添加到MemoryFileSystem",需要在源碼中引入fs,並把實例變量改成this,調用自己方法即可。
6. 其他有趣的
在做件事的時候,自己也用node寫了一些其他小功能,供以后使用或者練手。
- 用node命令來復制文件夾(在磁盤里)
const fs = require("fs");
function copyIt(from, to) {
fs.writeFileSync(to, fs.readFileSync(from));
//fs.createReadStream(src).pipe(fs.createWriteStream(dst));大文件復制
}
// copyIt("./public/from.txt","./public/to.txt");
//獲取node執行的參數
// var arguments = process.argv.splice(2);
// console.log(process.argv);
const child_process = require('child_process');
function copyIt(from, to) {
child_process.spawn('cp', ['-r', from, to]);
}
copyIt("from","to");
//
- 復制文件夾,內存到內存:
var MemoryFileSystem = require("./MemoryFileSystem");
var fs = new MemoryFileSystem(); // Optionally pass a javascript object
//
//
fs.mkdirpSync("/from");
fs.mkdirpSync("/from/from2");
fs.writeFileSync("/from/file2.txt", "這里是from的文件");
fs.writeFileSync("/from/from2/file1.txt", "這里是from/from2的文件");
console.log(fs.readFileSync("/from/file2.txt",'utf-8'));// returns Buffer("Hello World"))
console.log(fs.readFileSync("/from/from2/file1.txt",'utf-8'));// returns Buffer("Hello World"))
// fs.readFileSync("/a/test/dir/file2.txt",function(err,data) {
// console.log(data);
// })
// fs.readdirSync("/from",function (err,paths) {
// console.log(paths);
// });
// console.log(fs.readdirSync("/from"));
// function cpdir(from,to) {
// if()
// }
/**
* is exists
*
* @param {String} file
* @return {Promise}
*/
// const fs = require("fs");
// const chalk = require("chalk");
// function isExist(path){
// return new Promise((resolve,reject)=>{
// try {
// fs.existsSync(path);
// }catch(err) {
// reject(`${path} does not exist`);
// };
// resolve(true);
// });
// }
function copyDir(from, to) {
if(!fs.existsSync(to)) {
fs.mkdirSync(to);
}
const paths = fs.readdirSync(from);
console.log(paths);
paths.forEach((path)=>{
var src = `${from}/${path}`;
var dist = `${to}/${path}`;
const res = fs.statSync(src);
if(res.isFile()) {
fs.writeFileSync(dist, fs.readFileSync(src));
// console.log(chalk.magenta(`🏇 copy ${src} `));
} else if(res.isDirectory()) {
copyDir(src, dist);
}
});
}
copyDir("/from","/to");
console.log(fs.readFileSync("/to/file2.txt",'utf-8'));// returns Buffer("Hello World"))
console.log(fs.readFileSync("/to/from2/file1.txt",'utf-8'));
- 自己實現node10版本的自動創建多級目錄,以前版本的創建必須先創建前面的目錄,才能創建后面的
var fs = require('fs')
var path = require('path')
/**
* 同步遞歸創建路徑
*
* @param {string} dir 處理的路徑
* @param {function} cb 回調函數
*/
//利用path.parse 返回的dir總是去除最后一個路徑的特性,dir會把目錄最后一個當做文件
var $$mkdir = function(dir, cb) {
var pathinfo = path.parse(dir);
// console.log(fs.existsSync(pathinfo.dir));
// console.log(pathinfo.dir);
if (!fs.existsSync(pathinfo.dir)) {
$$mkdir(pathinfo.dir,function() {
fs.mkdirSync(pathinfo.dir)
})
}
cb && cb()
}
$$mkdir(path.join(__dirname, 'demo/test/123/'));
相關源碼:
https://github.com/ZhangMingZhao1/Nodejs_pratice---noob_to_God
