How do I build a JSON file with webpack?

Viewed 22132

I'd like to assemble a manifest.json file, as used by Chrome extensions, in a "smarter," programmatic sort of way. I'm using npm for my dependency resolution, and its package.json includes some shared fields with the manifest.json file, including "name," "description," and "version."

Is there a way to define something like a partial manifest.json file which includes all the Chrome-specific stuff, but fill in shared values where appropriate? I've found that this is pretty straightforward in Gulp:

var gulp = require('gulp');
var fs = require('fs');
var jeditor = require('gulp-json-editor');

gulp.task('manifest', function() {
    var pkg = JSON.parse(fs.readFileSync('./package.json'));
    gulp.src('./manifest.json')
      .pipe(jeditor({
        'name': pkg.name,
        'description': pkg.description,
        'version': pkg.version,
        'author': pkg.author,
        'homepage_url': pkg.homepage,
      }))
      .pipe(gulp.dest("./dist"));
});

Even if there's some npm package out there designed for this purpose, can someone explain to me how something like this might be done generally? I know Webpack 2 has a built-in json loader, but I'm not clear how it would be used in a case like this.

6 Answers

There is actually a more elegant solution than the one by @user108471 (although it is inspired by it), and that is to use the copy-webpack-plugin. With its transform ability, you can add the desired values to the manifest.json on the fly before copying it to its destination.

It has two advantages:

  • it doesn't generate an extra unnecessary manifest.js-bundle (@bronson's solution also fixes this)
  • you don't need to require the manifest.json in some other .js-file (which would seem semantically backwards to me)

A minimal setup could be this:

webpack.config.js

// you can just require .json, saves the 'fs'-hassle
let package = require('./package.json');

function modify(buffer) {
   // copy-webpack-plugin passes a buffer
   var manifest = JSON.parse(buffer.toString());

   // make any modifications you like, such as
   manifest.version = package.version;

   // pretty print to JSON with two spaces
   manifest_JSON = JSON.stringify(manifest, null, 2);
   return manifest_JSON;
}


module.exports = {

   // ...

   plugins: [
      new CopyWebpackPlugin([
         {
            from: "./src/manifest.json",
            to:   "./dist/manifest.json",
            transform (content, path) {
                return modify(content)
            }
         }])
   ]

}

My solution in Webpack 4 below. It's a generic solution for generating json files using Webpack loaders, but it works well for manifest.json files as well.

webpack.config.js

const ExtractTextPlugin = require("extract-text-webpack-plugin");
const resolve = require("path").resolve;

module.exports = {
    entry: {
        entry: resolve(__dirname, "app/main.js"),
    },
    module: {
        rules: [
            {
                test: /manifest\.js$/,
                use: ExtractTextPlugin.extract({
                    use: []  // Empty array on purpose.
                })
            }
        ],
        {
            test: /\.png$/,
            use: [{
                loader: "file-loader",
                options: {
                    context: resolve(__dirname, "app"),
                    name: "[path][name].[ext]",
                    publicPath: "/",
                }
            }]
        }
    },
    output: {
        filename: "[name].js",
        path: resolve(__dirname, 'dist'),
    },
    plugins: [
        new webpack.EnvironmentPlugin(["npm_package_version"]),  // automagically populated by webpack, available as process.env.npm_package_version in loaded files.
        new ExtractTextPlugin("manifest.json"),
    ]
};

app/main.js

const manifest = require('./manifest.js');

// Other parts of app …

app/manifest.js

const icon = require('./icon.png');  

const manifestData = {  
    icon: {"128": icon},  // icon.png will be in the emitted files, yay!
    version: process.env.npm_package_version,  // See webpack.config.js plugins
    // other manifest data …
};

// Whatever string you output here will be emitted as manifest.json:
module.exports = JSON.stringify(manifestData, null, 2);

package.json dependencies

{
    "extract-text-webpack-plugin": "4.0.0-beta.0",
    "file-loader": "1.1.11",
    "webpack": "4.12.0",
}
const manifest = {
  entry: './src/chrome_extension/dummy_index.js',
  output: {
    path: path.resolve(__dirname, 'dist'),
    filename: 'DELETED.js',
  },
  plugins: [
    new CleanWebpackPlugin(),
    new CopyWebpackPlugin({
      patterns: [
        {from: 'LICENSE.md'},
        {from: 'assets/img/icon.png', to: `${ASSETS_PATH}/icon.png`},
        {from: 'src/chrome_extension/manifest.json',
          transform: function(manifestBuffer, path) {
            const manifestString = manifestBuffer.toString()
                .replace(/\$\{OPTIONS_PAGE_PATH\}/g, OPTIONS_PAGE_PATH)
                .replace(/\$\{CONTENT_SCRIPT_PATH\}/g, CONTENT_SCRIPT_PATH)
                .replace(/\$\{ASSETS_PATH\}/g, ASSETS_PATH);
            return Buffer.from(manifestString);
          },
        },
      ],
    }),
    new RemoveFilesWebpackPlugin({
      after: {
        log: false,
        include: [
          'dist/DELETED.js',
        ],
      },
    }),
  ],
  stats: 'none',
  mode: 'none',
};

If you are using webpack 4, its pretty simple. We dont need to specify any explicit json loaders

Note: I am just bundling everything into a single html file here, but you get the idea that there is no json loader in webpack.config.js file

webpack.config.js

const HtmlWebPackPlugin = require("html-webpack-plugin");
 const HtmlWebpackInlineSourcePlugin = require('html-webpack-inline-source-plugin');

module.exports = {
 module: {
rules: [
  {
    test: /\.js$/,
    exclude: [/node_modules/],
    use: [{
      loader: 'babel-loader'
    }],
  },
  {
    test: /\.css$/,
    use: ["style-loader", "css-loader"]
  },
  {
      test: /\.ico$/,
      use: ["file-loader"]
  },
  {
    test: /\.html$/,
    use: [
      {
        loader: "html-loader",
        options: { minimize: true }
      }
    ]
  }
],
},
 plugins: [
new HtmlWebPackPlugin({
  template: "./src/index.html",
  filename: "./index.html",
  inlineSource: '.(js|css)$'
}),
new HtmlWebpackInlineSourcePlugin(),
],
devServer: {
  compress: true,
  disableHostCheck: true,
  }
}

In my app.js I just use

import data from './data/data.json'
Related