最近参与到一个项目,须要在线上快速打包和快速读取,为了提升速率,当时咱们想到了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