Frontend Development 9 min read

Developing Custom Webpack Loaders: del‑log‑loader and transform‑loader

This article explains step‑by‑step how to create, configure, and use custom synchronous and asynchronous Webpack loaders—including a console‑log remover and an ES6‑to‑ES5 transformer—covering project setup, loader implementation, options validation, source‑map generation, and loader resolution.

Beike Product & Technology
Beike Product & Technology
Beike Product & Technology
Developing Custom Webpack Loaders: del‑log‑loader and transform‑loader

Webpack loaders transform non‑JavaScript resources into JavaScript that Webpack can bundle; when built‑in loaders are insufficient, developers can write custom loaders.

1. Project setup – Create a sample project with the following structure:

├── dist               // output directory
├── loaders            // custom loader directory
├── del-log-loader.js // custom loader that removes console.log
├── transform-loader.js // custom loader that transpiles ES6 to ES5
├── src
├── index.js          // entry file
├── node_modules
├── package-lock.json
├── package.json
├── webpack.config.js // Webpack configuration

2. Basic Webpack configuration

module.exports = {
  mode: 'none',
  entry: './src/index.js',
  devtool: 'source-map',
  module: {
    rules: [] // loader rules will be added later
  }
};

3. Sample source code (src/index.js)

class Animal {
  constructor(name) {
    this.name = name;
  }
  getName() {
    console.log('this.name========', this.name); // del‑log‑loader will delete this line
    return this.name;
  }
}
const dog = new Animal('wangcai');
dog.getName();

4. del‑log‑loader (synchronous) – removes console.log statements.

module.exports = function (source) {
  const reg = /console\.log\([\s\S]*?\);/g;
  source = source.replace(reg, '');
  return source; // source must be a Buffer or String
};

After adding the loader to webpack.config.js :

rules: [{
  test: /\.js$/,
  use: [{
    loader: path.resolve(__dirname, 'loaders/del-log-loader.js')
  }]
}]

running npx webpack shows that console.log has been stripped.

5. Returning multiple values (source map) – use this.callback to return both transformed code and a source map.

const uglify = require('uglify-es');
module.exports = function (source) {
  const reg = /console\.log\([\s\S]*?\);/g;
  source = source.replace(reg, '');
  const filename = this.resourcePath.split('/').pop();
  const result = uglify.minify({ [filename]: source }, {
    sourceMap: { includeSources: true }
  });
  const { error, map } = result;
  this.callback(error, source, map);
};

6. Options handling – use loader-utils to read options and schema-utils to validate them.

const uglify = require('uglify-es');
const validate = require('schema-utils');
const loaderUtils = require('loader-utils');
const schema = {
  type: 'object',
  properties: {
    filename: { type: 'string' },
    isDelLog: { type: 'boolean' }
  },
  additionalProperties: false
};
module.exports = function (source) {
  const options = loaderUtils.getOptions(this);
  validate(schema, options, 'del-log-loader');
  const sourceName = this.resourcePath.split('/').pop();
  const { isDelLog, filename = sourceName } = options;
  if (isDelLog) {
    const reg = /console\.log\([\s\S]*?\);/g;
    source = source.replace(reg, '');
  }
  // further processing (e.g., source‑map generation) would follow here
};

Configure the loader with options in webpack.config.js :

rules: [{
  test: /\.js$/,
  use: [{
    loader: path.resolve(__dirname, 'loaders/del-log-loader'),
    options: {
      filename: 'source.js',
      isDelLog: process.env.NODE_ENV === 'production'
    }
  }]
}]

7. transform‑loader (asynchronous) – transpiles ES6 to ES5 using Babel and forwards the source map.

const babel = require('@babel/core');
module.exports = function (source, map) {
  const callback = this.async();
  babel.transform(source, {
    presets: ['@babel/preset-env']
  }, (err, result) => {
    const { code } = result;
    callback(err, code, map);
  });
};

Place transform-loader before del-log-loader in the rule array because loaders are applied from right to left.

rules: [{
  test: /\.js$/,
  use: [
    'transform-loader',
    { loader: 'del-log-loader', options: { /* same options as above */ } }
  ]
}]

8. Simplifying loader resolution – add resolveLoader so that custom loaders can be referenced by name without path.resolve .

resolveLoader: {
  modules: ['node_modules', path.resolve(__dirname, 'loaders')]
},
module: {
  rules: [{
    test: /\.js$/,
    use: ['transform-loader', {
      loader: 'del-log-loader',
      options: {
        filename: 'source.js',
        isDelLog: process.env.NODE_ENV === 'production'
      }
    }]
  }]
}

The article concludes that custom loaders give developers fine‑grained control over the build pipeline, and that Webpack’s loader API, along with helper libraries, makes creating both synchronous and asynchronous loaders straightforward.

JavaScriptBabelWebpackBuild Toolsloadercustom loaderSource Maps
Beike Product & Technology
Written by

Beike Product & Technology

As Beike's official product and technology account, we are committed to building a platform for sharing Beike's product and technology insights, targeting internet/O2O developers and product professionals. We share high-quality original articles, tech salon events, and recruitment information weekly. Welcome to follow us.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.