Package detail

modify-source-webpack-plugin

artembatura270.6kMIT4.1.0

Webpack plugin for modifying modules source

webpack, plugin, hooks, module

readme

npm version npm version npm version npm version npm version npm version

modify-source-webpack-plugin

Webpack plugin for modifying modules source.

Compatibility

Webpack Version Plugin version Status
^5.0.0 ^4.0.0

^4.37.0 ^4.0.0

Migration guide from version 3

Installation

NPM

npm i -D modify-source-webpack-plugin

Yarn

yarn add -D modify-source-webpack-plugin

Import

ES6/TypeScript

import { ModifySourcePlugin } from 'modify-source-webpack-plugin';

CJS

const { ModifySourcePlugin } = require('modify-source-webpack-plugin');

Usage

webpack.config.js

module.exports = {
  plugins: [new ModifySourcePlugin(options)]
};

Options

rules[].test

Type: RegExp | ((module: webpack.NormalModule) => boolean)

Required

test is RegExp or function, which used to determinate which modules should be modified.

RegExp will be applied to full module path (based on userRequest).

function will be applied to NormalModule.

Example with RegExp

plugins: [
  new ModifySourcePlugin({
    rules: [
      {
        test: /index\.js$/
      }
    ]
  })
];

Example with Function

plugins: [
  new ModifySourcePlugin({
    rules: [
      {
        test: module =>
          module.source().source().includes('my-secret-module-marker')
      }
    ]
  })
];

rules[].operations

Type: AbstractOperation[] (supported ConcatOperation, ReplaceOperation)

Required

List of operations which describes how modules should be modified.

:warning: Operations should make syntax compatible changes. For example all unsupported syntax will break your build or create errors in runtime.

Example with concat operation

import {
  ModifySourcePlugin,
  ConcatOperation
} from 'modify-source-webpack-plugin';

module.exports = {
  plugins: [
    new ModifySourcePlugin({
      rules: [
        {
          test: /my-file\.js$/,
          operations: [
            new ConcatOperation(
              'start',
              '// Proprietary and confidential.\n\n'
            ),
            new ConcatOperation(
              'end',
              '\n\n// File is written by me, January 2022'
            )
          ]
        }
      ]
    })
  ]
};

Example with replace operation

import {
  ModifySourcePlugin,
  ReplaceOperation
} from 'modify-source-webpack-plugin';

module.exports = {
  plugins: [
    new ModifySourcePlugin({
      rules: [
        {
          test: /my-file\.js$/,
          operations: [
            new ReplaceOperation('once', 'searchValue', 'replaceValue'),
            new ReplaceOperation('all', 'searchValue', 'replaceValue')
          ]
        }
      ]
    })
  ]
};

Bad example

module.exports = {
  plugins: [
    new ModifySourcePlugin({
      rules: [
        {
          test: /my-file\.js$/,
          operations: [
            new ConcatOperation('start', 'Haha I break your build LOL')
          ]
        }
      ]
    })
  ]
};

debug

Type: boolean

For easier debugging. Print some logs in the console.

Advanced Usage

Compile-time constants

Constants related to information about files that we change.

Constant Description
$FILE_PATH Path to file
$FILE_NAME File name
plugins: [
  new ModifySourcePlugin({
    rules: [
      {
        test: /my-file\.js$/,
        operations: [
          new ConcatOperation(
            'end',
            '\n\n // This file is on the path - $FILE_PATH and filename - $FILE_NAME'
          )
        ]
      }
    ]
  })
];

Put content before and after file contents

<summary>my-file.js (clickable)</summary> js console.log('Hello world!');

webpack.config.js

plugins: [
  new ModifySourcePlugin({
    rules: [
      {
        test: /my-file\.js$/,
        operations: [
          new ConcatOperation('start', '// Something before file contents.\n'),
          new ConcatOperation('end', '\n// Something after file contents.')
        ]
      }
    ]
  })
];
<summary>Result my-file.js (clickable)</summary> js // Something before file contents. console.log('Hello world!'); // Something after file contents.

Replace plug with a content

<summary>my-component.jsx (clickable)</summary> jsx function HelloMessage(props) { return ( <div> Hello, $NAME <button onClick={() => { props.userLogout(); alert('Goodbye, $NAME!'); }} > $EXIT_LABEL </button> </div> ); }

webpack.config.js

plugins: [
  new ModifySourcePlugin({
    rules: [
      {
        test: /my-component\.jsx$/,
        operations: [
          new ReplaceOperation('all', '$NAME', 'Artem Batura'),
          new ReplaceOperation('once', '$EXIT_LABEL', 'Exit')
          // new ReplaceOperation('once', '$EXIT_LABEL', 'Leave')
        ]
      }
    ]
  })
];
<summary>Result my-component.jsx (clickable)</summary> jsx function HelloMessage(props) { return ( <div> Hello, Artem Batura <button onClick={() => { props.userLogout(); alert('Goodbye, Artem Batura!'); }} > Exit </button> </div> ); }

Place code/text fragment in required position

<summary>my-component.jsx (clickable)</summary> jsx function HelloMessage(props) { $MY_DEBUG_CODE; return ( <div> Hello, user! $MY_USER_COMPONENT <button onClick={() => props.userLogout()}>Exit</button> </div> ); }

webpack.config.js

plugins: [
  new ModifySourcePlugin({
    rules: [
      {
        test: /my-component\.js$/,
        operations: [
          new ReplaceOperation(
            'once',
            '$MY_DEBUG_CODE',
            'console.log("props", props)'
          ),
          new ReplaceOperation(
            'once',
            '$MY_USER_COMPONENT',
            '<div>compilation-time markup</div>'
          )
        ]
      }
    ]
  })
];
<summary>Result my-component.jsx (clickable)</summary> jsx function HelloMessage(props) { console.log('props', props); return ( <div> Hello, user! <div>compilation-time markup</div> <button onClick={() => props.userLogout()}>Exit</button> </div> ); }

changelog

4.1.0 (2023-06-01)

Issue #87

Fixed bug when plugin doesn't attach loader to module and file is not being modified again in watch mode after triggered re-compilation.

4.0.0 (2023-02-11)

The main step forward in this release was rejecting a bad approach with global variable which was used to access modify functions from webpack loader.

It was used since passing functions directly to webpack loader options is not possible because webpack sometimes rehydrate options object before they came in the loader (as I understood).

Illustration of the problem with previous solution

Passing options to loader.

ModifySourcePlugin.js

normalModule.loaders.push({
  loader: require.resolve('./loader.js'),
  options: {
    path: moduleRequest,
    modifyFunctions: [src => src + '// my added code'] // <= Passing function here.
  }
});

Receiving options in loader.

loader.js

module.exports = function loader(source) {
  const options = getOptions(this); // = { modifyFunctions: ["Function"] } <= Problem here: function received as string after rehydration.

  // ...
};

In previous versions we tried to bypass this restriction and used this "bad crutch":

ModifySourcePlugin.js

global.modifyFunctions = rules.map(rule => rule.modify);

And accessing from loader:

loader.js

module.exports = function loader(source) {
  const options = getOptions(this); // { "path": "path/to/file.js", ruleIndex: 0 }

  const modify = global.modifyFunctions[options.ruleIndex];
};

This approach cause critical bugs what led to the rejection from this approach (thanks to @dreamerblue for reporting this).

Operations it's a new static way to describe how modules should be modified with the capability of proper caching by webpack.

Migration guide (3.x to 4.x)

  • options.rules[].modify property was removed.

  • options.rules[].operations[] property has added.

<summary>Before: (clickable)</summary> ts import { ModifySourcePlugin } from 'modify-source-webpack-plugin'; module.exports = { plugins: [ new ModifySourcePlugin({ rules: [ { test: /my-file\.js$/, modify: (src, path) => { let newSrc = src; // writing at start of file newSrc = 'value' + newSrc; // writing at end of file newSrc += 'value'; // replacing text once newSrc = newSrc.replace('searchValue', 'replaceValue'); // replacing all text in file newSrc = newSrc.replaceAll('searchValue', 'replaceValue'); // ... return newSrc; } } ] }) ] };

Now:

import {
  ModifySourcePlugin,
  ConcatOperation,
  ReplaceOperation
} from 'modify-source-webpack-plugin';

module.exports = {
  plugins: [
    new ModifySourcePlugin({
      rules: [
        {
          test: /my-file\.js$/,
          operations: [
            // writing at start of file
            new ConcatOperation('start', 'value'),
            // writing at end of file
            new ConcatOperation('end', 'value'),
            // replacing text once
            new ReplaceOperation('once', 'searchValue', 'replaceValue'),
            // replacing all text in file
            new ReplaceOperation('all', 'searchValue', 'replaceValue')
          ]
        }
      ]
    })
  ]
};

See a more specific examples with comparison below.

Write data in start/end of file

new ModifySourcePlugin({
  rules: [
    {
      test: /my-file\.js$/,
-     modify: (src) => 'start of file' + src
+     operations: [
+       new ConcatOperation('start', 'start of file')
+     ]
    }
  ]
})
new ModifySourcePlugin({
  rules: [
    {
      test: /my-file\.js$/,
-     modify: (src) => src + 'end of file'
+     operations: [
+       new ConcatOperation('end', 'end of file')
+     ]
    }
  ]
})

Replace text

Replace first found text:

new ModifySourcePlugin({
  rules: [
    {
      test: /my-file\.js$/,
-     modify: (src) => src.replace('searchValue', 'replaceValue')
+     operations: [
+       new ReplaceOperation('once', 'searchValue', 'replaceValue')
+     ]
    }
  ]
})

Replace all found text:

new ModifySourcePlugin({
  rules: [
    {
      test: /my-file\.js$/,
-     modify: (src) => src.replaceAll('searchValue', 'replaceValue')
+     operations: [
+       new ReplaceOperation('all', 'searchValue', 'replaceValue')
+     ]
    }
  ]
})

Using Compile-time constants

$FILE_PATH is a constant as a replacement for "path" argument of modify function.

new ModifySourcePlugin({
  rules: [
    {
      test: /my-file\.js$/,
-     modify: (src, path) => src + 'path: ' + path
+     operations: [
+       new ConcatOperation('end', 'path: $FILE_PATH')
+     ]
    }
  ]
})

And the new $FILE_NAME constant.

const path = require('path');

new ModifySourcePlugin({
  rules: [
    {
      test: /my-file\.js$/,
-     modify: (src, path) => src + 'filename: ' + path.basename(path)
+     operations: [
+       new ConcatOperation('end', 'filename: $FILE_NAME')
+     ]
    }
  ]
})

3.0.0 (2021-05-22)

:boom: Breaking Change

  • #55 Pass full path instead of filename to modify function. Issue

:nail_care: Enhancement

  • #55 Support for Webpack 5 and Webpack 4 in one package

  • #55 Validate incoming options to plugin