Plummer
Plummer

Reputation: 6688

webpack - DefinePlugin method

In the defintions for a webpack plugin, I'm trying to supply an override function that my module will pickup if the method exists.

export const listFetchAPI = () => {
  return ( LIST_FETCH_API ? LIST_FETCH_API : '/list/');
};
export const listFetchTX = (data) => {
  return ( LIST_FETCH_TX === 'function' ? LIST_FETCH_TX(data) : data );
};

In my webpack config, based on the environment or implementation of the project, I may or may not want to supply overrides for these functions.

webpackConfig.plugins.push(
  new webpack.DefinePlugin({
    LIST_FETCH_API: JSON.stringify('https://testapi.com/listFetch/')
    LIST_FETCH_TX(data) { return { ...data, test: 'HELLO!' }; }
  })
);  

I've tried with both ES5 and ES6 notation. When I build, I get an error SyntaxError: Unexpected identifier

I don't see in the docs where you can pass a method through the DefinePlugin. https://webpack.js.org/plugins/define-plugin/

Google search comes up nill. My next step would be to pass a string value and then use react-json-schema to pick up the component. That seems too involved, so I'm hoping there's a way to define methods in DefinePlugin.

EDIT For clarification, what I'm doing is build a generic redux module which can registered to a private npm registry. When invoked, this module can be supplied overrides for the API urls and response translators. This prevents me from branching code or creating modules that are 99% similar every time I do an install for a different vendor.

If supplying functions through an environment variable is not appropriate, what would be an alternative method to allow for such overrides? I'm not sure the config object plays out when things are being dispatched through the store. The other option I'm playing around with is overriding entire files in a module. I'm familiar methods in Ruby using import_path, but in research, I haven't seen any equivalent JS/npm methods.

Upvotes: 2

Views: 8907

Answers (2)

Michael Jungo
Michael Jungo

Reputation: 32972

The DefinePlugin is a direct text replacement, similar to macros in C. Webpack will look for the identifier and replace it with the given string. The following example illustrates how that works.

With the following plugin configuration

new webpack.DefinePlugin({
  VAR: 'myVar',
  STRING: '"a string (note the quotes inside quotes)"',
  FUNCTION: 'function myFun(arg) { console.log("myFun was called with", arg); }'
})

and the JavaScript as input:

const myVar = 'hello';

console.log(VAR);
console.log(STRING);
console.log(FUNCTION);
// IIFE requires parens to execute
(FUNCTION)('iife');

// Better, only defines the function once
const functionToCall = FUNCTION;
functionToCall('another arg');

The output JavaScript will be:

const myVar = 'hello';

console.log(myVar);
console.log("a string (note the quotes inside quotes)");
console.log(function myFun(arg) { console.log("myFun was called with", arg); });
// IIFE requires parens to execute
(function myFun(arg) { console.log("myFun was called with", arg); })('iife');

// Better, only defines the function once
const functionToCall = function myFun(arg) { console.log("myFun was called with", arg); };
functionToCall('another arg');

As you can see they have simply been replaced with the values defined in the DefinePlugin. If you run it you'll get the following console log:

hello
a string (note the quotes inside quotes)
[Function: myFun]
myFun was called with iife
myFun was called with another arg

For STRING you would usually use JSON.stringify(), which just gives you a string representation of the string (quotes inside quotes). If you don't do that, it will simply be an identifier, and if the identifier wasn't defined, it will throw an error. The FUNCTION also shows that it will be replaced everywhere, it's not referencing the same function, because it's a direct text replacement.


If you want to optionally define something you also need to check whether the variable exists, because if you don't, it will throw an error.

const varOrDefault = typeof VAR !== 'undefined' ?  VAR : 'default'; 

You can't do VAR === undefined because that assumes the variable exists and will only check whether it's undefined, but when it's not defined at all, it will throw an error that VAR is not defined. After that you can freely use the variable and use it like you wanted and check whether it's a function or not (when checking for a function you could skip the test whether it has been defined, because that would use a typeof anyway).

To be honest, that's not a very good solution, especially since a function will be included twice, because of the needed typeof check. And to be fair, such text replacement is not appropriate for any conditionally defined dynamic structure. It would be a much better idea to move this to a configuration object. It's very straightforward to accept a configuration object and provide default values. There are several ways to accomplish that.

For example with Object.assign:

function mainFunction(options) {
  const defaults = { /* default values */ };
  options = Object.assign({}, defaults, options);

  // Use the options later
}

Upvotes: 7

Sachin Thapa
Sachin Thapa

Reputation: 3709

As per documentation of DefinePlugin

The DefinePlugin allows you to create global constants which can be configured at compile time. This can be useful for allowing different behavior between development builds and release builds.

These definitions are key value pairs, in the following line of code syntax Key : Value should be followed.

Key : Value

 LIST_FETCH_TX(data) { return { ...data, test: 'HELLO!' }; }

Still I am not still sure it will work for function as the intention is to create global constants. It might change in future though, new stuff keeps coming :-)

Not sure this answers but might help you to figure out why it is throwing error with that : missing.

Cheers !

Upvotes: 0

Related Questions