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