@qinzisong/webpack-preprocessor-loader

1.5.0 • Public • Published

webpack-preprocessor-loader

Version Node Downloads License Build Status

Bring the awesome "Conditional Compilation" to the Webpack, and more.

Why

Make life easy with the help of webpack-preprocessor-loader !

Now leverage the full power of Conditional Compilation in Webpack to output specific codes based on conditional directives. By which you could:

  • Hide specific contents from the final result;
  • Import different packages by environment (eg: development/production);
  • Remove debugs in production;
  • Split codes in production, while bundle them in development;
  • Many other scenarios...

Simply write:

// #!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';

...which yields:

ENV === 'product', debug === false, secret === false

const anotherModule = import('another-module-name');

Also with build-in JSX/HTML comment syntax support. See Usage.

Pros:

  • It is "Conditional Compilation";
  • Say goodbye to those "process.env.NODE_ENV"s messing around the code;
  • Deals directly with raw text, so it just works on any text-based file;
  • Create custom directives if needed;

Cons:

  • Maybe a little verbose in some cases;

    If so, consider using webpack.DefinePlugin backwards.

Compatibility

  • webpack: 4.x+
  • node: 6.11.5 minimum (aligned with webpack 4)

Installation

yarn add webpack-preprocessor-loader -D

or

npm install webpack-preprocessor-loader -D

Configuration

Since it deals directly with the raw text, webpack-preprocessor-loader should be the last loader in use definition:

module.exports = {
  // ...
  module: {
    rules: [
      {
        test: /\.js$/,
        use: [
          'babel-loader',
          {
            loader: 'webpack-preprocessor-loader',
            options: {
              debug: process.env.NODE_ENV !== 'product',
              directives: {
                secret: false,
              },
              params: {
                ENV: process.env.NODE_ENV,
              },
              verbose: false,
            },
          },
        ],
      },
    ],
  },
};

Usage

Note that any text-based file can be compiled, not only codes, for example:

  • HTML/Pug/...
  • Sass/Less/...
  • Json5/Xml/Yaml/...

Basics

Conditional Compilation relies on a series of specified directives to decide code emitting strategy. The directive must be wrapped in a comment, followed with "#!".

Demo in Javascript:

// Nope. Wrap it in a comment.
'#!debug';

// Depends on the value provided in options.debug...
// #!debug
const a = 1; // ...this line may be omitted.

// What about a custom directive? 
// Add a property on options.directives, say "secret", and set it to `false`
// #!secret
const b = 1; // ...this line will be omitted.

// Works like real "if-else"!
// But first set a value on options.params, say "options.params.foo = 1"
// #!if foo === 1
const c = 1; //
/* #!else */ // <-- Also legal.
const c = 2;
// And always remember to close it by...
// #!endif

// Now try to find your own usage!

More detailed explanations see examples provided in Options and Build-in Directives.

JSX comment syntax (^1.0.4)

import React from 'react';

/* #!debug */
console.log('wow');

function Hello() {
  return <div>
    {/* #!debug */}
    <p>oops</p>

    {/* #!if stage === 'product' */}
    <p>Ready to go</p>
    {/* #!endif */}
  </div>;
}

HTML comment syntax (^1.1.0)

<body>
  <!-- #!debug -->
  <p>oops</p>

  <!-- #!if stage === 'product' -->
  <p>Ready to go</p>
  <!-- #!endif -->
</body>

Inline directive within comment (^1.1.0)

See below.

Multiline directive syntax (^1.1.0)

See below.

Multiline directive syntax with comment (^1.1.0)

The following syntax are equivalent and legal:

// #!if stage === 'product'
/* #!if stage === 'product' */

/*
  #!if stage === 'product'
*/

/*
 * Look mom I have a comment!
 * #!if stage === 'product'
 */

// I have a comment too. #!if stage === 'product'

Options

debug _

type: boolean

default: false

Provide constant value for build-in #!debug directive. See Directives - #!debug.

directives

type: {[key: string]: boolean}

default: {}

Define custom directives. For example, to create a directive called "secret":

// In webpack 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('Kept'); // This line will not be affected by "#!secret", hence it will be kept 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.

params

type: {[key: string]: any}

default: {}

Provide constant values for build-in #!if / #!elseif / #!else / #!endif directives. See Directives - #!if / #!else / #!elseif / #!endif

verbose

type: boolean

default: false

Whether to keep raw info or not. Basically for debugging purpose.

// options.params.ENV === 'product'

// #!if ENV === 'develop'
console.log('many doge');
// #!else
console.log('much wow');
// #!endif

If set to true, yields:

// #!if ENV === 'develop'
// console.log('many doge');
// #!else
console.log('much wow');
// #!endif

Build-in Directives

#!if / #!else / #!elseif / #!endif

Basic Usage

As name suggests, these directives work similarly like real if logic:

// In webpack config...

{
  loader: 'webpack-preprocessor-loader',
  options: {
    params: {
      foo: 2,
      bar: 1,
    },
  },
},

The following code...

// #!if foo === 1
const a = 1;
// #!elseif bar === 1
const a = 2;
// #!elseif bar === 2
const a = 3;
// #!else
const a = 4;
// #!endif

...yields

const a = 2;

Any valid #!if / #!else / #!elseif / #!endif combination is accepted, only remember always close selection statements by #!endif.

Advanced Condition

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. Ensure the expression returns a boolean value.

#!debug

A semantic and handy directive to mark specific line only to be kept 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('Kept'); // This line will not be affected by "#!debug", hence it will be kept anyway

Caveats

Inline directive with code

The following code may not work as expected:

// debug === false
const foo = 1; /* #!debug */    // <-- the directive will be ignored and this line will be kept
const bar = 2;                  // <-- this line will be kept anyway

// or

// debug === true
/* #!debug */ const foo = 1;    // <-- this line will be omitted anyway
const bar = 2;

So please make sure not mix directive and code on the same line.

Linting

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"

Typescript

To suppress the error, a tricky way is simply adding // @ts-ignore before all declarations:

// #!if ENV = 'develop'
// @ts-ignore
const foo = 1;
// #!else
// @ts-ignore
const foo = -1;
// #!endif

// Errors gone.

ESlint

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: {
              // ...
            },
          },
        ],
      },
    ],
  },
};

License

MIT License

Dependents (0)

Package Sidebar

Install

npm i @qinzisong/webpack-preprocessor-loader

Weekly Downloads

0

Version

1.5.0

License

MIT

Unpacked Size

24.4 kB

Total Files

10

Last publish

Collaborators

  • qinzisong