Home

Awesome

webextension-manifest-loader <!-- omit in toc -->

npm version node version code style: prettier

Webpack loader that loads browser tailored manifest.json. It also imports all importable properties, allowing you to have 'manifest.json' as your only webpack entry point.

Use it together with inert-entry-webpack-plugin, spawn-loader, and maybe html-loader, more info below.

Table of Contents <!-- omit in toc -->

Getting Started

To begin, you'll need to install webextension-manifest-loader:

npm install --save-dev webextension-manifest-loader

Then add the loader to your webpack config. For example:

webpack.config.js

module.exports = {
  module: {
    rules: [
      {
        // Prevent json-loader from loading the file
        type: 'javascript/auto',
        test: /manifest\.json$/,
        use: [
          // webextension-manifest-loader returns a function for importing in a .js file,
          // convert it to a plain string and resolve imports using extract-loader
          'extract-loader',
          {
            loader: 'webextension-manifest-loader',
            options: {
              // Use the environment variable TARGET_VENDOR as the target vendor
              targetVendor: process.env.TARGET_VENDOR,
              // Merge some properties into the finished manifest.json
              merge: {
                // This could easily come from an import of your package.json
                version: '1.3.0',
              },
            },
          },
        ],
      },
    ],
  },
};

Options

NameTypeDefaultDescription
targetVendorstringundefinedSpecify the target vendor, should be one of firefox,chrome,edge,opera
mergeObject{}Allows merging properties into the finished manifest.json

Example

See jsmnbom/ao3-enhancements <sup>(manifest.json, webpack.config.ts)</sup> for a fully featured example of using this loader together with typescript, vue (+vuetify) and pug.

Configuration

Make sure you're using inert-entry-webpack-plugin, otherwise webpack will try to output manifest.json as a javascript file, and that will obviously fail!

webpack.config.js (configuration)

module.exports = {
  entry: {
    manifest: './manifest.json',
  },
  module: {
    rules: [
      {
        // Prevent json-loader from loading the file
        type: 'javascript/auto',
        test: /manifest\.json$/,
        use: [
          // webextension-manifest-loader returns a function for importing in a .js file,
          // convert it to a plain string and resolve imports using extract-loader
          'extract-loader',
          {
            loader: 'webextension-manifest-loader',
            options: {
              // Use the environment variable TARGET_VENDOR as the target vendor
              targetVendor: process.env.TARGET_VENDOR,
              // Merge some properties into the finished manifest.json
              merge: {
                // This could easily come from an import of your package.json
                version: '1.3.0',
              },
            },
          },
        ],
      },
      {
        test: /\.html$/,
        use: [
          'file-loader?name=[path][name].html',
          'extract-loader',
          'html-loader',
        ],
      },
    ],
  },
  plugins: [new InertEntryPlugin()],
};

manifest.json (configuration)

Now you'll be able to specify vendors in your manifest.json keys and to import files.

{
  "manifest_version": 2,
  "version": "",
  "__firefox_icons__": {
    "48": "./icon.svg",
    "96": "./icon.svg"
  },
  "__chrome_icons__": {
    "48": "./icon-48.png",
    "96": "./icon-96.png"
  },
  "options_ui": {
    "page": "~spawn-loader?name=options_ui/index.html!./options_ui/index.html",
    "__firefox_browser_style__": false,
    "__chrome_chrome_style__": false
  },
  "content_scripts": [
    {
      "matches": [...],
      "__firefox_js__": [
        "~spawn-loader?name=content_script/index.js!./content_script/index.js"
      ],
      "__chrome_js__": [
        "~file-loader?name=[name].[ext]!webextension-polyfill/dist/browser-polyfill.min.js",
        "~spawn-loader?name=content_script/index.js!./content_script/index.js"
      ],
      "css": ["./content_script/style.css"]
    }
  ]
}

Here we import .svg if compiling for firefox, but .pngs if compiling for chrome since chrome doesn't support svgs in the icons property. We also add the webextension-polyfill on chrome but not on firefox as it's not needed there.

Use a loader like spawn-loader to add a new entry point. Remember to add a tilde in front of the loader or it will be resolved as a relative path.

Output if targetVendor == 'chrome'

Here's the output of the above configuration when run through webpack. And because we are using html-loader which can also resolve imports in <script> tags, again using spawn-loader we could include javascript files in our options_ui/index.html.

manifest.json (output)

{
  "manifest_version": 2,
  "version": "0.3.0",
  "icons": {
    "48": "/icon-48.png",
    "96": "/icon-96.png"
  },
  "options_ui": {
    "page": "/options_ui/index.html",
    "chrome_style": false,
    "open_in_tab": true
  },
  "content_scripts": [
    {
      "matches": [...],
      "js": [
        "/browser-polyfill.min.js",
        "/content_script/index.js"
      ],
      "css": [
        "/content_script/style.css"
      ],
      "run_at": "document_start"
    }
  ],
}

FAQ

Which properties does it currently support?

All properties can use the target vendor feature, but only some properties will be resolved by webpack. These are currently: (if you need more please open an issue or PR)

Please see the src/interfaces.ts -> Manifest interface for these currently supported properties.

Are there any known limitation or problems?

Related and thanks

I wrote this loader mostly as a challange to myself to see if it could be done. It is heavily inspired by wext-manifest-loader, and uses parts from html-loader for the import mechanism.