在内存中编译Webpack,但解析为磁盘上的node_modules



我正在尝试使用web包编译内存中的有效javascript代码字符串。我使用的内存fs如下所示:https://webpack.github.io/docs/node.js-api.html#compile-存储。

所以我取一个包含原始javascript的字符串,将其写入内存fs,然后web包解析到该入口点。但是编译第一个require语句失败,可能是因为它无法在实际的fs中查找node_modules。

关于我该如何做到这一点,有什么想法吗?

import webpack from 'webpack';
import MemoryFS from 'memory-fs';
import thenify from 'thenify';
function* compile(code) {
    const fs = new MemoryFS();
    fs.writeFileSync('/file.js', code);
    const compiler = webpack({
        entry: { file: '/file.js' },
        output: {
            path: '/build',
            filename: '[name].js'
        },
        module: {
            loaders: [
                { test: /.json$/, loader: 'json' }
            ],  
        }
    });
    compiler.run = thenify(compiler.run);
    compiler.inputFileSystem = fs;
    compiler.resolvers.normal.fileSystem = fs; //this is needed for memfs
    compiler.outputFileSystem = fs;
    const stats = yield compiler.run();
    //retrieve the output of the compilation
    const res = stats.compilation.assets['file.js'].source();
    return res;
}

使用

var code = "var _ = require('underscore'); console.log(_);";
var bundle = yield compile(code); //should be a bundle containing the underscore source.

错误为

ModuleNotFoundError:找不到模块:错误:无法解析模块/中的下划线

这个问题表明其他人也尝试过同样的事情:https://github.com/webpack/webpack/issues/1562.有一个要点引用在https://gist.github.com/DatenMetzgerX/2a96ebf287b4311f4c18我相信这是为了做我希望完成的事情,但以目前的形式,我不知道该怎么做。它将MemoryFs的一个实例分配给所有解析器。我已经尝试分配节点的fs模块,但没有骰子。

简言之,我试图为内存中的原始javascript字符串设置一个入口点,但仍然需要将require和import语句解析为磁盘上的node_modules。

更新

我已经能够得到我想要的结果,但并不漂亮。我基本上覆盖了MemoryFS中#stat和#readFile的实现以检查实际的文件系统是否收到对内存中不存在的文件的任何请求。我可以通过子类化MemoryFS而不是在运行时交换方法实现来稍微清理一下,但想法仍然是一样的。

工作解决方案

import webpack from 'webpack';
import JsonLoader from 'json-loader';
import MemoryFS from 'memory-fs';
import UglifyJS from "uglify-js";
import thenify from 'thenify';
import path from 'path';
import fs from 'fs';
import root from 'app-root-path';
/*
* Provide webpack with an instance of MemoryFS for
* in-memory compilation. We're currently overriding
* #stat and #readFile. Webpack will ask MemoryFS for the 
* entry file, which it will find successfully. However, 
* all dependencies are on the real filesystem, so any require 
* or import statements will fail. When that happens, our wrapper 
* functions will then check fs for the requested file. 
*/
const memFs = new MemoryFS();
const statOrig = memFs.stat.bind(memFs);
const readFileOrig = memFs.readFile.bind(memFs);
memFs.stat = function (_path, cb) {
    statOrig(_path, function(err, result) {
        if (err) {
            return fs.stat(_path, cb);
        } else {
            return cb(err, result);
        }
    });
};
memFs.readFile = function (path, cb) {
    readFileOrig(path, function (err, result) {
        if (err) {
            return fs.readFile(path, cb);
        } else {
            return cb(err, result);
        }
    });
};

export default function* compile(code) {
    // Setup webpack 
    //create a directory structure in MemoryFS that matches
    //the real filesystem
    const rootDir = root.toString();
    //write code snippet to memoryfs
    const outputName = `file.js`;
    const entry = path.join(rootDir, outputName);
    const rootExists = memFs.existsSync(rootDir);
    if (!rootExists) {
        memFs.mkdirpSync(rootDir);
    }
    memFs.writeFileSync(entry, code);
    //point webpack to memoryfs for the entry file
    const compiler = webpack({
        entry: entry,
        output: {
            filename: outputName
        },
        module: {
            loaders: [
                { test: /.json$/, loader: 'json' }
            ]
        }
    });
    compiler.run = thenify(compiler.run);
    //direct webpack to use memoryfs for file input
    compiler.inputFileSystem = memFs;
    compiler.resolvers.normal.fileSystem = memFs;
    //direct webpack to output to memoryfs rather than to disk
    compiler.outputFileSystem = memFs;
    const stats = yield compiler.run();
    //remove entry from memory. we're done with it
    memFs.unlinkSync(entry);
    const errors = stats.compilation.errors;
    if (errors && errors.length > 0) {
        //if there are errors, throw the first one
        throw errors[0];
    }
    //retrieve the output of the compilation
    const res = stats.compilation.assets[outputName].source(); 
    return res;
}

用法

var code = "var _ = require('underscore'); console.log(_);";
var bundle = yield compile(code); //is a valid js bundle containing the underscore source and a log statement logging _.

如果没有更好的方法,那么我肯定会把它封装到MemoryFS的一个子类中,但我希望有一种更合理的方法可以用Webpack的api来实现这一点。

unionfs/memfs/linkfs的组合应该会有所帮助,而不是内存fs。

  • https://npmjs.com/unionfs

  • https://npmjs.com/memfs

  • https://npmjs.com/linkfs

我创建了这个未经测试的片段。我认为您希望输入fs是真实的,而输出fs是内存中的。另一方面,您希望单独构建file.js的所有依赖项。为此,我认为webpack.optimize.CommonsChunkPlugin插件可能会有所帮助。我希望webpack能把所有东西都写进内存。我希望它能起作用。

import webpack from 'webpack';
import MemoryFS from 'memory-fs';
import thenify from 'thenify';
import realFS from 'fs';
function* compile(code) {
    const fs = new MemoryFS();
    const compiler = webpack({
        entry: {
            file: '/file.js',
            vendor: [
                'underscore',
                'other-package-name'
            ]
        },
        output: {
            path: '/build',
            filename: '[name].js'
        },
        module: {
            loaders: [
                { test: /.json$/, loader: 'json' }
            ],
        },
        plugins: [
            new webpack.optimize.CommonsChunkPlugin('vendor', 'vendor.js')
        ]
    });
    compiler.run = thenify(compiler.run);
    compiler.inputFileSystem = realFS;
    compiler.resolvers.normal.fileSystem = fs; //this is needed for memfs
    compiler.outputFileSystem = fs;
    const stats = yield compiler.run();
    //retrieve the output of the compilation
    const res = stats.compilation.assets['file.js'].source();
    return res;
}

我知道已经晚了,但这里有一段代码。

import * as fs from 'fs';
import { resolve } from 'path';
import  { Volume } from 'memfs';
import { ufs } from 'unionfs';
const volume = Volume.fromJSON({
 [resolve(process.cwd(), 'test.js')]: 'this file is on memory not on disk'
});
ufs.use(fs).use(volume);
// Reads from memory
console.log(ufs.readFileSync(resolve(process.cwd(), 'test.js'), 'utf8'));
// Reads from disk
console.log(ufs.readFileSync(resolve(process.cwd(), 'package.json'), 'utf8'));
// Writing into memory
volume.writeFileSync(resolve(process.cwd(), 'test.memory'), 'This should be 
on memory');
console.log(ufs.readFileSync(resolve(process.cwd(), 'test.memory'), 'utf8'));
// Writing into disk
ufs.writeFileSync(resolve(process.cwd(), 'test.disk'), 'This should be on disk');
console.log(ufs.readFileSync(resolve(process.cwd(), 'test.disk'), 'utf8'));

她的控制台输出:

user1@pc playground % node inMem.mjs
this file is on memory not on disk
{
  "name": "playground",
  "version": "1.0.0",
  "main": "index.js",
  "license": "MIT",
  "dependencies": {
    "memfs": "^3.3.0",
    "unionfs": "^4.4.0"
  }
}
This should be on memory
This should be on disk
user1@pc playground % ls .
inMem.mjs       node_modules    package.json            yarn.lock

您使用的是MemoryFS,它是对操作系统通常处理的功能的JavaScript重新实现。我想知道,您是否可以在操作系统级别使用tmpfs装载一个目录,然后使用它webpack将不知道或不关心输入文件是否实际存储在内存中。

假设您在/media/memory上安装了一个基于内存的文件系统,webpack配置代码可能如下所示:

resolve: {
  root: ['/media/memory', ...other paths...],
  },
  output: {
    path: '/wherever/you/want/the/output/files'
  }
}

这种方法还有一个隐藏的好处:如果你想调试输入代码,你只需用一个非基于RAM的文件系统挂载/media/memory,就可以看到生成了什么。

最新更新