Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Webpack: can I provide a "virtual" file from a string?

Tags:

webpack

I have some JSON that is created at build time in my Webpack app - is there any way I can "inject" it with a path during the build? I know I could just write it out to a file and include it that way, but I'm hoping I can do something cleaner than that.

like image 493
Alastair Avatar asked Nov 05 '15 10:11

Alastair


1 Answers

Edit 2020/10/27: webpack-virtual-modules is a project that can do this and has compatibility with webpack 5

Edit 2018/04/09: val-loader is another way to achieve injecting code and values at build-time, but it requires loading that code from a separate file which may not be able to access the JSON data in the OP's setup if that only exists in memory.

I was looking for a way to do this as well. I ended up digging into the internals of webpack and wrote a plugin that seems to work.

It's clean in that you don't have to write a file to disk, but the internals of it are a little messy because I had to get into how webpack's CachedInputFileSystem works.

It didn't seem to be possible to do this using a loader. Webpack needs to resolve a file location on disk and read the contents of that before it will move on to the loader phase.

By placing a plugin function on the 'resolve' phase of compiler.resolvers.normal, it is possible to access the filesystem in use by webpack and then add the virtual filename and contents into the caches of that filesystem.

After doing that, everything else works as normal in webpack and your virtual file/module will pass through the other loaders and plugins you have configured.

See https://github.com/rmarscher/virtual-module-webpack-plugin for the code that I wrote. It has been published to npm: https://www.npmjs.com/package/virtual-module-webpack-plugin

Here is the code for the resolve part of the plugin. Note that this example was for webpack 1 and 2, but the plugin has been updated to work with more recent versions:

compiler.resolvers.normal.plugin('resolve', function resolverPlugin(request, cb) {
  // populate the file system cache with the virtual module
  const fs = this.fileSystem;

  // webpack 1.x compatibility
  if (typeof request === 'string') {
    request = cb;
    cb = null;
  }

  if (!modulePath) {
    modulePath = this.join(compiler.context, moduleName);
  }
  VirtualModulePlugin.populateFilesystem({ fs, modulePath, contents, ctime });
  if (cb) {
    cb();
  }
});

The populateFilesystem static method adds the contents to the fs._readFileStorage.data and creates a mock result to fs.stat() in the fs._statStorage.data cache. For creating the mock fs.Stats object, I borrowed some code from the mock-fs package.

So far I have tested it with the latest webpack 1.x and 2.x as well as webpack-dev-server. I've used the extract-text-webpack-plugin, json-loader, raw-loader and css-loader. All seem to work as expected.

Here is a webpack config that uses the plugin:

'use strict';

const path = require('path');
const ExtractTextPlugin = require('extract-text-webpack-plugin');
const VirtualModulePlugin = require('virtual-module-webpack-plugin');

module.exports = function webpackConfig() {
  const runtimeJsonContents = JSON.stringify({
    greeting: 'Hello!',
  });
  const runtimeStyleContents = `
    body { background: #000; color: #ccc; }
    .greeting { font: 600 40px/50px fantasy; text-align: center; }
  `;

  const config = {
    context: __dirname,
    devtool: 'source-map',
    entry: {
      index: './src/index',
    },
    output: {
      filename: '[name].js',
      path: 'dist',
      publicPath: '/',
      devtoolModuleFilenameTemplate: '../[resource-path]',
    },
    module: {
      loaders: [
        {
          test: /\.json$/,
          loaders: ['json-loader'],
        },
        {
          test: /\.css$/,
          loader: ExtractTextPlugin.extract({
            fallbackLoader: 'style-loader',
            loader: 'css-loader?sourceMap',
          }),
        },
      ],
    },
    plugins: [
      new VirtualModulePlugin({
        moduleName: 'src/mysettings.json',
        contents: runtimeJsonContents,
      }),
      new VirtualModulePlugin({
        moduleName: 'src/css/generated.css',
        contents: runtimeStyleContents,
      }),
      new ExtractTextPlugin({
        filename: '[name].css',
        allChunks: true,
      }),
    ],
    resolve: {
      modules: [
        path.join(__dirname, 'src'),
        'node_modules',
      ],
    },
  };
  return config;
};

See https://github.com/rmarscher/virtual-module-webpack-plugin/tree/master/examples for complete examples with different versions of webpack.

I should also note that this code requires NodeJS 4.x or higher.

like image 135
rmarscher Avatar answered Oct 07 '22 13:10

rmarscher