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

Posted

技术标签:

【中文标题】在内存中编译 Webpack 但解析为磁盘上的 node_modules【英文标题】:Compiling Webpack in memory but resolving to node_modules on disk 【发布时间】:2016-12-11 07:40:20 【问题描述】:

我正在尝试使用 web pack 来编译内存中的有效 javascript 代码字符串。我正在使用此处概述的内存 fs:https://webpack.github.io/docs/node.js-api.html#compile-to-memory。

所以我正在获取一个包含原始 javascript 的字符串,将其写入内存 fs,然后 web pack 解析到该入口点。但是在第一个 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 来完成。

【问题讨论】:

您能否展示更完整的代码,包括您的require 语句? @jonaz 我已经更新了原始的 sn-p 并且还添加了一个我已经能够开始工作的新版本。希望能有更好的办法。 你能说明更多的原产地要求信息吗?听起来您想要一个动态入口点? htmlwebpackplugin 也会带来更多错误,因此当我在memory-fs 中的根是/src 时,插件尝试访问C:\src 失败。所以我将此添加到 readfile 覆盖:if(_path.includes('C:\\src')) _path = _path.substring(2) _path = _path.replace(/\\/g, '/') 【参考方案1】:

unionfs/memfs/linkfs 的组合应该有帮助,而不是 memory-fs。

https://npmjs.com/unionfs

https://npmjs.com/memfs

https://npmjs.com/linkfs

【讨论】:

这应该是公认的答案。只是需要更多细节。 我正在尝试这个,但unionfs 有基本错误,现在它无法正确读取目录:github.com/streamich/unionfs/issues/240 另外 unionfs 不支持 webpack 使用的 mkdirp 确实需要更多细节,我尝试了这个解决方案大约2小时没有成功。【参考方案2】:

我创建了这个未经测试的 sn-p。我认为您希望 inputFS 是真实的,而输出 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;

【讨论】:

【参考方案3】:

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

假设您已经在 /media/memory 上挂载了一个基于内存的文件系统,那么 webpack 配置代码可以像这样简单:

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

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

【讨论】:

【参考方案4】:

我知道已经很晚了,但为了记录,这里有一个代码 sn-p。

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

【讨论】:

以上是关于在内存中编译 Webpack 但解析为磁盘上的 node_modules的主要内容,如果未能解决你的问题,请参考以下文章

webpack常见面试题

webpack初识一

Vue-cli webpack 编译内存溢出

如何在为服务器开发但不重新编译 webpack 客户端配置时使用 nodemon?

db.create_all() 在磁盘上但不在内存中创建模式

是否可以在 Webpack 的编译时解析导入的模块路径?