webpack loader 生成虚拟文件的方案
此文已由作者张磊授权网易云社区发布。
欢迎访问网易云社区,了解更多网易技术产品运营经验。
前言
使用 webpack 的时候,难免需要写一些 loader,接着就会遇到一个很纠结的问题。该 loader 会生成一个文件,一般这个文件的生成时机都是在 loader 处理所有的文件后。一般有两种处理方案。一种是写一个 plugin,监听对应的事件;一种是生成一个临时文件,将每次读到的内容都写在 临时文件 中。第一种在使用的时候也很麻烦,需要同时在 loader 和 plugin 加一下对应的逻辑。第二种,写入临时文件的这个过程很是让人纠结。很明显,两种方案对于有一定洁癖的人来说,都不优雅,那么就来寻找一种方案,既不需要写 plugin,又不需要写入 临时文件 中。
解决方案
在 github 上找到一个可用解决方案的 loader,这个 loader 看起来是关于虚拟文件生成的,使用很简单,指定名字,指定内容,生成一个虚拟文件,研究了一下,对解决问题很有帮助。关键代码如下:
// index.jsimport * as fsPatch from './fs-patch';// 省略...fsPatch.add( this.fs, { path: file, content: src });// 省略...
这个文件传入 loader.fs,看起来是对 fs 打补丁,接着再来看 fs-patch.js
// fs-patch.jsimport path from 'path';const NS = __filename; export function patch( fs ) { if ( fs[ NS ] ) return; const virtualFS = { files: {}, add( options ) { const file = path.resolve( options.path ); virtualFS.files[ file ] = { path: file, content: options.content }; } }; fs[ NS ] = virtualFS; createPatchFn( fs, 'readFile', function( orig, args, file, encoding, cb ) { var rfile = path.resolve( file ); var vfile = virtualFS.files[ rfile ]; if ( vfile ) { if ( typeof(encoding) === 'function' ) { cb = encoding; encoding = null; } var content = vfile.content; if ( encoding != null ) content = content.toString( encoding ); cb( null, content ) return; } return orig.apply( this, args ); }); createPatchFn( fs, 'readFileSync', function( orig, args, file, encoding ) { var rfile = path.resolve( file ); var vfile = virtualFS.files[ rfile ]; if ( vfile ) { var content = vfile.content; if ( encoding != null ) content = content.toString( encoding ); return content; } return orig.apply( this, args ); }); createPatchFn( fs, 'stat', function( orig, args, p, cb ) { var rp = path.resolve( p ); var vfile = virtualFS.files[ rp ]; if ( vfile ) { var vstat = { dev: 8675309, nlink: 1, uid: 501, gid: 20, rdev: 0, blksize: 4096, ino: 44700000, mode: 33188, size: vfile.content.length, isFile() { return true; }, isDirectory() { return false; }, isBlockDevice() { return false; }, isCharacterDevice() { return false; }, isSymbolicLink() { return false; }, isFIFO() { return false; }, isSocket() { return false; }, }; cb( null, vstat ); return; } return orig.apply( this, args ); }); createPatchFn( fs, 'statSync', function( orig, args, p ) { var rp = path.resolve( p ); var vfile = virtualFS.files[ rp ]; if ( vfile ) { var vstat = { dev: 8675309, nlink: 1, uid: 501, gid: 20, rdev: 0, blksize: 4096, ino: 44700000, mode: 33188, size: vfile.content.length, isFile() { return true; }, isDirectory() { return false; }, isBlockDevice() { return false; }, isCharacterDevice() { return false; }, isSymbolicLink() { return false; }, isFIFO() { return false; }, isSocket() { return false; }, }; return vstat; } return orig.apply( this, args ); }); }; export function add( fs, options ) { patch( fs ); fs[ NS ].add( options ); }function createPatchFn( obj, name, fn ) { const origin = obj[ name ]; obj[ name ] = function() { const args = Array.prototype.slice.call( arguments ); return fn.apply( this, [origin, args].concat( args ) ); }; }
代码分析
可以看到 fs-patch.js 直接劫持了 loader.fs,重写了 fs 的一些方法,而重写的这些方法就是生成虚拟文件的关键。劫持后的 fs 在访问这些方法的时候,首先去从缓存中获取路径对应的内容,不存在则再从硬盘中读取。
优缺点
优点是不需要生成临时文件或者另写一个 plugin,缺点在文件比较大或者计算比较频繁,对机器的要求会比较高。
更多网易技术、产品、运营经验分享请点击。
相关文章:
【推荐】 云架构师进阶攻略(1)
【推荐】 BRVAH(让RecyclerView变得更高效)(1)