How can I replace files at compile time using webpack?
Asked Answered
P

6

25

I have a project with miltiple configuration. The first config is config.dev.js file that contains some development configiration. I using it in development mode. The second config is config.js file. I using it in production mode.

In the code I using imports:

import * as config from './config.js';

I want to use the first config file in development and the second to production whithout rewriting all of the imports. How can I replace this config depending on the build mode?

Pazit answered 14/7, 2018 at 12:38 Comment(0)
S
22

This is an old question but I've recently stumbled across the same issue and webpack.NormalModuleReplacementPlugin doesn't seem to work anymore (or at least not in my case, where I used JSON files as config). Instead I found another solution using alias:

const path = require("path");

modules.export = {
    ...
    resolve: {
        ...
        alias: {
            [path.resolve(__dirname, "src/conf/config.json")]:
                path.resolve(__dirname, "src/conf/config.prod.json")
        }
    }
    ...
}
Songful answered 13/4, 2020 at 10:12 Comment(2)
NormalModuleReplacementPlugin didn't work during my usage as well while it errors 'NormalModuleFactory.beforeResolve is no longer a waterfall hook, but a bailing hook instead.' It seems related to webpack version change: github.com/webpack/webpack/issues/11647Cashbox
Could you elavorate on what this is doing?Bumbling
I
17

I realize this is an old post, but this is one of the first results on Google, so I thought a better answer would be good.

Webpack has a built in "Normal Module Replacement Plugin".

plugins: [
  new webpack.NormalModuleReplacementPlugin(
    /some\/path\/config\.development\.js/,
    './config.production.js'
  )
]

For my use, I put the env file in a variable Here is an example:

let envFilePath = './environment.dev.js';
if (env === 'production') {
  envFilePath = './environment.prod.js';
} else if (env === 'staging') {
  envFilePath = './environment.stg.js';
}

module.exports = {
    // other webpack stuff
    ....
    plugins:[ 
        new webpack.NormalModuleReplacementPlugin(
            /src\/environment\/environment\.js/,
            envFilePath
        ),
        ...
    ]
}
Isabelleisac answered 14/3, 2019 at 5:55 Comment(1)
Maybe it's worth to mention that the second path is relative to the first, means: If my old one is in ./src/config/dev.js the ./prod.js will refer to ./src/config/prod.js, not to root/prod.jsSongful
T
10

You can use webpack file-replace-loader

https://www.npmjs.com/package/file-replace-loader

Example:

//webpack.config.js

const resolve = require('path').resolve;

module.exports = {
  //...
  module: {
    rules: [{
      test: /\.config\.js$/,
      loader: 'file-replace-loader',
      options: {
        condition: process.env.NODE_ENV === 'development',
        replacement: resolve('./config.dev.js'),
        async: true,
      }
    }]
  }
}
Trave answered 15/7, 2018 at 2:54 Comment(0)
S
5

I wanted to imitate the Angular fileReplacements syntax so I used a config.json like Angular's and if the configuration key matches the env I pass to webpack, loop through the replacements and create several Webpack module rules.

It's not the most elegant thing ever but this is what I ended up with:

// config.json

{
    "title": "Some Title",
    "configurations": {
        "production": {
            "fileReplacements": [
                {
                    "replace": "src/environments/environment.ts",
                    "with": "src/environments/environment.prod.ts"
                }
            ]
        },
        "lan": {
            "fileReplacements": [
                {
                    "replace": "src/environments/environment.ts",
                    "with": "src/environments/environment.lan.ts"
                }
            ]
        }
    }
}

// webpack.config.js

const appConfig = require('./config.json');



module.exports = (env, argv) => {

    // env.build is like `ng build --prod` as `webpack --env.build=production` 
    const configuration = appConfig.configurations[env.build];
    const fileReplacements = [];

    // Safety Check
    if(configuration && configuration.fileReplacements) {

        // Iterate through replacements
        for(const replacement of configuration.fileReplacements) {
            // create Webpack module rule
            const replace = {
                test: path.resolve(replacement.replace),
                loader: 'file-replace-loader',
                options: {
                    replacement: path.resolve(replacement.with),
                    async: true
                }
            }
            fileReplacements.push(replace);
        }
    }

    return {
        mode: //...
        entry: //...
        module: {
            rules: [
                {
                    //...
                },
                // Unpack anywhere here
                ...fileReplacements
            ]
       }
    }
}

This way you don't have to keep messing with webpack and regex tests, just add to the array in config.json

Subrogation answered 22/10, 2019 at 15:22 Comment(1)
This is basically what I wanted to do as well. Love your solution. Can't believe every framework doesn't include something like this. Would you ever want to build an app -- or debug a production bug locally -- without it?Neodarwinism
F
2

You can also use babel-loader like this:

//webpack.config.js

const resolve = require('path').resolve;

module.exports = {
  //...
  module: {
    strictExportPresence: true,
    rules: [{
      test: /\.config\.js$/,
      include: paths.appSrc,
      loader: require.resolve('babel-loader'),
      options: {
        plugins: [
            [
              "module-resolver",
              {
                resolvePath(sourcePath, currentFile, opts) {
                  if(process.env.NODE_ENV === 'development') {
                    return sourcePath.substr(0, sourcePath.lastIndexOf('/')) + '/config.dev.js';
                  } else {
                    return sourcePath;
                  }
                }
              }
            ]
          ]
        }
    }]
  }
}

This way you can even define complex algorithms to determine which file you wanna use.

Feverroot answered 27/3, 2019 at 9:6 Comment(0)
T
0

Another way is to use Webpack.DefinePlugin. Especially useful if you have a tiny code block that you want to be included conditionally.

Example follows:

// webpack.conf.js
// Code block that should be inserted or left blank
const ServiceWorkerReg = `window.addEventListener('load', () => {
    navigator.serviceWorker.register('service-worker.js').then(registration => {
        console.log('SW registered: ', registration);
    }).catch(registrationError => {
        console.log('SW registration failed: ', registrationError);
    });
});`


appConfig = {
    plugins: [
        new webpack.DefinePlugin({
            __SERVICE_WORKER_REG: isDev ? '' : ServiceWorkerReg,
        }),
    ]
    ...
}



 // Some module index.js

 __SERVICE_WORKER_REG

 ... // other non conditional code
Thomasson answered 11/5, 2020 at 12:21 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.