
Company News
Socket Named Top Sales Organization by RepVue
Socket won two 2026 Reppy Awards from RepVue, ranking in the top 5% of all sales orgs. AE Alexandra Lister shares what it's like to grow a sales career here.
webpack-preprocessor-loader
Advanced tools
Bring the awesome "Conditional Compilation" to the Webpack, and more.
webpack-preprocessor-loader leverages the concept of Conditional Compilation to output specific code based on conditional directives. By which you can:
For a quick review, given:
ENV === "product", debug === false, secret === false
In code:
// #!if ENV === 'develop'
import someModule from "module-name";
// #!else
const anotherModule = import("another-module-name");
// #!endif
// #!debug
console.log(someModule);
/*
* My precious code!
* #!secret
*/
const the_answer_to_everything = "42";
Yields:
const anotherModule = import("another-module-name");
Pros:
Cons:
If so, consider using webpack.DefinePlugin backwards.
yarn add webpack-preprocessor-loader -D
or
npm install webpack-preprocessor-loader -D
Since it deals directly with the raw text, webpack-preprocessor-loader should be the last loader in use definition. A full example config is as follows:
module.exports = {
// ...
module: {
rules: [
{
test: /\.js$/,
use: [
"babel-loader",
// ... other loaders
{
loader: "webpack-preprocessor-loader",
options: {
debug: process.env.NODE_ENV !== "product",
directives: {
secret: false,
},
params: {
ENV: process.env.NODE_ENV,
},
verbose: false,
},
},
],
},
],
},
};
More details see Options.
Note that any text-based file can be compiled, not only codes, for example:
- HTML/Pug/...
- Sass/Less/...
- Json5/Xml/Yaml/...
Conditional Compilation relies on special comments, aka directive, which start with #!, followed by the directive name, e.g.,
// #!directive
Directives can be used to tag a certain line, or wrap a whole block of code.
// #!debug
const foo = 1;
// #!if env === "development"
const bar = 2;
const baz = 3;
// #!endif
Unlike normal comments, the directive must not appear in-line. For example:
// Won't work
const bar = 2; // #!debug // <-- Directive will be ignored.
/* #!debug */ const baz = 3; // <-- The code will always be omitted with the directive.
Multiple variants of comment are supported. Details see Comment Syntax.
One common case is that we want to omit one specified line of code under certain condition(s).
For example, we want to log some messages to the console, but only during development. To drop the code based on environment, we can define a custom directive.
First, declare a property in options.directives, e.g. dev.
In config:
{
loader: 'webpack-preprocessor-loader',
options: {
directives: {
dev: process.env.NODE_ENV === "development",
},
},
},
In code:
// #!dev
console.log("DEBUG ONLY");
During the compilation, if the value of dev is false, the exact line of code under the directive will be omitted, and vice versa.
For the development/production scenario, the loader provides a handy built-in directive called #!debug. Details see Options - debug and Built-in Directives.
The other common case is that we want to omit multiple lines of code at once depends on certain condition(s). Since custom directives can only tag one line of code, we need another group of built-in directives: #!if/#!endif.
Like real-world ifs, it needs a condition. We can also provide variables to form an expression.
Declare a property in options.params, e.g. env.
In config:
{
loader: 'webpack-preprocessor-loader',
options: {
params: {
env: process.env.NODE_ENV,
},
},
},
In code:
// #!if env === "development"
console.log("DEBUG ONLY");
doSomethingForTheDev();
// #!endif
Once compiled, the codes between #!if and #!endif will be omitted, if the condition expression provided evaluates to falsy value in javascript.
More details see Built-in Directives.
Sometimes, we may need a little bit more complex control flow. For example, the project runs different code between multiple stages. Like conditional branching in standard language, the loader provides #!else/#!elseif directives to stimulate the behavior of if statement.
Suppose there is a parameter called "env" defined in options.params, statement branching can easily be expressed like:
// #!if env === "development"
doSomethingA();
doSomethingA2();
// #!elseif env === "canary"
doSomethingB();
doSomethingB2();
// #!else
doSomethingC();
doSomethingC2();
// #!endif
doSomethingCommon();
In addition, nested #!if is also supported. More details see Built-in Directives.
The loader supports the following comment variants:
Line comment
// #!if foo === 1
Block comment:
/* #!if foo === 1 */
/*
* #!if stage === 'product'
*/
HTML comment:
<!-- #!if foo === 1 -->
JSX comment:
<div>{/* #!if foo === 1 */}</div>
And for better maintenance, embedded comments in directive are also supported. For example:
/*
* Look mom I have a comment!
* #!if stage === 'product'
*/
// I have a comment too. #!if stage === 'product'
debugtype:
booleandefault:
false
Provides constant value for built-in #!debug directive. See Directives - #!debug.
directivestype:
{[key: string]: boolean}default:
{}
Define custom directives. For example, to create a directive called "secret":
In config:
{
loader: 'webpack-preprocessor-loader',
options: {
directives: {
secret: false,
},
},
},
In code:
// #!secret
console.log("wow"); // This line will be omitted
Note that the custom directive only affects its next line, which means:
// #!secret
console.log("Removed"); // This line will be omitted
console.log("Preserved"); // This line will not be affected by "#!secret", hence it will be preserved anyway
If an undefined directive is referenced, say "foo", the next line marked by #!foo will always be omitted, because the value of foo is undefined, identical as false.
paramstype:
{[key: string]: any}default:
{}
Provide constant values for built-in #!if / #!elseif / #!else / #!endif directives. See Directives - #!if / #!else / #!elseif / #!endif
verbosetype:
boolean|{ escapeComments?: boolean; }default:
false
Preserve all directive comments and omitted lines as comments. Basically for debugging purpose. Note that the normal comments remain as-is(except padding).
Given:
// options.params.ENV === 'product'
// #!if ENV === 'develop'
/** some comment */
console.log("many doge");
// #!else
console.log("much wow");
// #!endif
If set to true, yields:
// #!if ENV === 'develop'
/** some comment */
// console.log('many doge');
// #!else
console.log("much wow");
// #!endif
escapeCommentsdefault:
false
There are rare cases where multiple kinds of comment notations live within the same control block. For example:
<body>
<!-- #!if foo === 1-->
<style>
.div {
/* comment because of reasons */
color: tomato;
}
</style>
<script>
/**
* another multiline comment
*/
const bar = 1;
</script>
<!-- #!endif -->
</body>
If foo === 2, the comments in style and script tag will stay as-is and "leak" into outside code. To prevent unwanted results, set escapeComments to true. All non-directive comment notations will be replaced by @@, and re-wrapped by those used in the previous directive:
<body>
<!-- #!if foo === 1-->
<!-- <style>-->
<!-- .div {-->
<!-- @@ comment because of reasons @@-->
<!-- color: tomato;-->
<!-- }-->
<!-- </style>-->
<!-- <script>-->
<!-- @@*-->
<!-- * another multiline comment-->
<!-- -->
<!-- @@-->
<!-- const bar = 1;-->
<!-- </script>-->
<!-- #!endif -->
</body>
#!if / #!else / #!elseif / #!endifAs name suggests, these directives work similarly like real if logic.
In config:
{
loader: 'webpack-preprocessor-loader',
options: {
params: {
foo: 1,
bar: 1,
},
},
},
Demo in Javascript:
// #!if foo === 1
const foo = 1;
// Even nested...
// #!if bar === 1
const bar = 1;
// Or even nested custom directive!
// Suppose "options.directives.test === true"
// #!test
const baz = 0;
// #!else
const bar = 2;
// #!test
const baz = 1; // <-- omitted, because bar !== 1, even though test === true
// #!endif
// #!else
const foo = 2;
// #!endif
Yields
const foo = 1;
const bar = 1;
const baz = 0;
Any valid #!if / #!else / #!elseif / #!endif combination is accepted, only remember always close branching statements by #!endif.
The condition can also be some more complex expressions. For example:
// #!if foo === 1 && bar === 2
// #!if foo + bar === 3
// Seriously?
// #!if (function(a){ return a === 1; })(foo)
Behind the scenes, the expression is wrapped in a return clause, and dynamically evaluated during compilation, thus its context is irrelevant to the code. So all variables in the expression should be pre-defined in the params and treated as constants. Finally ensure the expression returns a boolean value.
#!debugA semantic and handy directive to mark specific line only to be preserved when needed. For example:
// options.debug === false
// #!debug
console.log("test"); // This line will be omitted
Note that the #!debug directive only affects its next line, which means:
// options.debug === false
// #!debug
console.log("Removed"); // This line will be omitted
console.log("Preserved"); // This line will not be affected by "#!debug", hence it will be preserved anyway
The following code yields errors during linting:
// #!if env === 'develop'
const foo = 1;
// #!else
const foo = -1;
// #!endif
// "[ts] Cannot redeclare block-scoped variable 'foo'."
// "[eslint] Parsing error: Identifier 'foo' has already been declared"
To suppress the error, a tricky way is simply adding // @ts-ignore before all declarations:
// @ts-ignore #!if env === 'develop'
const foo = 1;
// @ts-ignore #!else
const foo = -1;
// #!endif
// Errors gone.
It is hard to get around this problem while linting through editor plugin, because ESLint parses the file into AST first, which caused a parsing error. So the only solution is to temporarily comment one or more declarations out during code editing.
Otherwise, if eslint-loader is used, simply put it before webpack-preprocessor-loader:
module.exports = {
// ...
module: {
rules: [
{
test: /\.js$/,
use: [
"babel-loader",
"eslint-loader",
{
loader: "webpack-preprocessor-loader",
options: {
// ...
},
},
],
},
],
},
};
See Github Release Page.
MIT License
FAQs
A code preprocessor for Webpack
The npm package webpack-preprocessor-loader receives a total of 4,547 weekly downloads. As such, webpack-preprocessor-loader popularity was classified as popular.
We found that webpack-preprocessor-loader demonstrated a not healthy version release cadence and project activity because the last version was released a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Company News
Socket won two 2026 Reppy Awards from RepVue, ranking in the top 5% of all sales orgs. AE Alexandra Lister shares what it's like to grow a sales career here.

Security News
NIST will stop enriching most CVEs under a new risk-based model, narrowing the NVD's scope as vulnerability submissions continue to surge.

Company News
/Security News
Socket is an initial recipient of OpenAI's Cybersecurity Grant Program, which commits $10M in API credits to defenders securing open source software.