最近參與到一個項目,須要在線上快速打包和快速讀取,爲了提升速率,當時咱們想到了webpack dev模式下打包文件是臨時貯存在內存中的,想學習一下webpack的這種技術是怎麼實現的,好應用到項目中。javascript
https://juejin.im/entry/5b0e3eba5188251534379615java
看這篇文章就夠了,很細緻,這篇博客主要講下webpack的memory-fs系統,總體原理就很少說了。node
這個一點是最有意思的,Hot Module Replacement ,相似熱更新,就是由於這個咱們保存代碼後瀏覽器沒有刷新前提下自動改變對應的變化,且狀態不丟失。webpack
總流程就是下面這樣,使用了sockjs(websocket)雙工通訊,稍有點複雜,參考這篇文章:https://juejin.im/entry/5a0278fe6fb9a045076f15b9
講的很詳細了。git
這裏我最關心的是中間過程當中wenpack打包的文件跑去哪裏了,你在磁盤裏是找不到output.path 目錄的,這裏也就是我須要調研的東西:memory-fs。github
memory-fs:NodeJS原生fs模塊內存版(in-memory)的完整功能實現。相比於從磁盤讀寫數據,memory-fs是內存緩存和快速數據處理(fast data processing)的完美替代方案。web
webpack 經過本身實現的memory-fs將 bundle.js 文件打包到了內存中,訪問內存中的代碼文件也就更快,也減小了代碼寫入文件的開銷。npm
memory-fs 是 webpack-dev-middleware 的一個依賴庫,webpack-dev-middleware 將 webpack 本來的 outputFileSystem (node的fs系統)替換成了MemoryFileSystem 實例,這樣代碼就將輸出到內存中。webpack-dev-middleware 中該部分源碼以下:promise
// 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 對象返回給瀏覽器端。
這個模塊我在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可從內存中讀取。
雖然實現大部門原生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,調用本身方法便可。
在作件事的時候,本身也用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'));
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