web-dev-qa-db-fra.com

Compiler Webpack en mémoire mais résoudre en node_modules sur le disque

J'essaie d'utiliser Web Pack pour compiler une chaîne en mémoire de code javascript valide. J'utilise la mémoire mémoire fs comme indiqué ici: https://webpack.github.io/docs/node.js-api.html#compile-to-memory

Donc, je prends une chaîne contenant du javascript brut, l'écris dans la mémoire, puis le pack Web se résout en ce point d'entrée. Mais la compilation échoue lors de la première instruction require, probablement parce qu’elle n’est pas en mesure de rechercher node_modules dans le fichier fs réel. 

Des idées sur comment puis-je accomplir cela?

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;
}

Usage

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

L'erreur est 

ModuleNotFoundError: Module introuvable. Erreur: Impossible de résoudre le module souligner dans /

Cette question indique que d'autres personnes ont essayé la même chose: https://github.com/webpack/webpack/issues/1562 . il y a un Gist référencé à https://Gist.github.com/DatenMetzgerX/2a96ebf287b4311f4c18 que je crois était destiné à faire ce que j'espère accomplir, mais sous sa forme actuelle, je ne vois pas comment. Il attribue une instance de MemoryFs à tous les résolveurs. J'ai essayé d'assigner le module fs du noeud, mais pas de dés. 

En bref, j'essaie de définir un point d'entrée sur une chaîne de javascript brut en mémoire, mais j'ai toujours les instructions require et import résolues en node_modules sur le disque.

METTRE À JOUR

J'ai pu obtenir le résultat que je cherche mais ce n'est pas joli. Je remplace essentiellement l'implémentation de #stat et #readFile dans MemoryFS pour vérifier le système de fichiers réel s'il reçoit une requête pour un fichier qui n'existe pas en mémoire. Je pourrais nettoyer cela un peu en sous-classant MemoryFS au lieu de permuter les implémentations de méthodes au moment de l'exécution, mais l'idée serait toujours la même.

Solution de travail

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;
}

Utilisation

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 _.

S'il n'y a pas de meilleur moyen, alors je vais certainement l'intégrer dans une sous-classe de MemoryFS, mais j'espère qu'il existe un moyen plus sain de le faire avec l'API de Webpack.

29
mike

J'ai créé cet extrait non testé. Je pense que vous voulez que inputFS soit le vrai et que le fs de sortie soit celui de la mémoire. D'autre part, vous voulez que toutes les dépendances de file.js soient construites séparément. Pour cela, j'ai pensé que le plugin webpack.optimize.CommonsChunkPlugin pourrait aider. Je m'attends à ce que webpack écrive tout dans la mémoire. J'espère que ca fonctionne.

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;
}
1
qballer

Au lieu de memory-fs, la combinaison de unionfs/memfs/linkfs devrait aider.

1
user2524758

Vous utilisez MemoryFS , qui est une réimplémentation JavaScript d'une fonctionnalité normalement gérée par le système d'exploitation. Je me demande si vous pouvez monter un répertoire en utilisant tmpfs au niveau du système d’exploitation, puis l’utiliser? webpack ne saurait alors ni ne s'inquiéterait du fait que le fichier d'entrée est réellement stocké en mémoire.

En supposant que vous ayez monté un système de fichiers basé sur une mémoire sous / media/memory, le code de configuration webpack pourrait être aussi simple que cela:

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

Cette approche présente également un avantage caché: si vous souhaitez déboguer le code d'entrée, montez simplement / media/memory avec un système de fichiers non basé sur la RAM et vous pouvez voir ce qui est généré.

0
John Anderson