What is a webpack – chain?

Webpack-chain attempts to create and modify webPack configurations by providing chained or downstream apis. The Key part of the API can be referenced by a user-specified name, which helps standardize how configurations are modified across projects. Apply a chained API to generate and simplify configuration changes to WebPack versions 2-4.

Why webpack-chain?

The core configuration of WebPack is created and modified based on a potentially difficult JavaScript object. While this is fine for configuring a single project, it can get messy when you try to share these objects across projects and make subsequent changes.

What can we do with Webpack-chain?

Build pluggable development environment and production environment, flexibly configure projects, perfect customized solutions.

Webpack – chain started

Once you have webpack-chain installed, you can start creating a webPack configuration. The following code sample is from the official example. Basic configuration webpack.config.js

// Imports the Webpack-chain module, which exports a single constructor for creating a WebPack configuration API. const Config = require('webpack-chain'); // Create a new configuration instance of the single constructor const config = new config (); // Each call to the API tracks changes to the storage configuration. Config // Modify entry config.entry ('index')
    .add('src/index.js'.output. Path ().output.'dist')
    .filename('[name].bundle.js'); // Create a named rule that can be used later to modify the rule config.module. rule('lint')
    .test(/\.js$/)
    .pre()
    .include
      .add('src').end() // You can also create named use(loaders).use('eslint')
      .loader('eslint-loader')
      .options({
        rules: {
          semi: 'off'}}); config.module .rule('compile')
    .test(/\.js$/)
    .include
      .add('src')
      .add('test')
      .end()
    .use('babel')
      .loader('babel-loader')
      .options({
        presets: [
          ['@babel/preset-env', { modules: false}}]]); // You can also create a named plug-in! config .plugin('clean')
    .use(CleanPlugin, [['dist'], { root: '/dir'}]); Module.exports = config.toconfig (); // Export the configuration object module.exports = config.toconfig () to be used by Webpack;Copy the code

The shared configuration is also simple. Just export the configuration and call the.toconfig () method to export the configuration to WebPack for use before passing it to webpack.

If you’ve learned how to use jquery, you’ll be able to get used to the above chain calls, but we need to take a look at the core API in Webpack-Chain.

  1. ChainedMap
  2. ChainedSet

** ChainedMap and ChainedSet operate like JavaScript Maps, providing some convenience for chained and generated configurations **

For a detailed example, please check out the official document, Webpack-chain. Next, let’s see how to set up our production environment configuration step by step.

directory

│─ build │ ├ ─ config.js// Public configuration│ │ ─ ─ build. Js │ └ ─ ─ dev. Js │ ─ ─ the config │ │ ─ ─ base. Js// Basic configuration│ │ ─ ─ CSS, js/ / CSS configuration│ │ ─ ─ HtmlWebpackPlugin. Js/ / HTML configuration│ └ ─ ─ MiniCssExtractPlugin. Js// Extract CSS styles│ ─ ─ the public// Public resources│ └ ─ ─ index. HTML/ / HTML template└ ─ ─ the SRC// Develop the directory│ │ ─ ─ index. The CSS// Test the style└ ─ ─ the main js/ / the main entry
Copy the code

build/base.js

const { findSync } = require('.. /lib');
const Config = require('webpack-chain');
const config = new Config();
const files = findSync('config');
const path = require('path');
const resolve = p= > {
  return path.join(process.cwd(), p);
};

module.exports = (a)= > {
  const map = new Map(a); files.map(_= > {
    const name = _.split('/')
      .pop()
      .replace('.js'.' ');
    return map.set(name, require(_)(config, resolve));
  });

  map.forEach(v= > v());

  return config;
};
Copy the code

Build the production environment

build/build.js

const rimraf = require('rimraf');
const ora = require('ora');
const chalk = require('chalk');
const path = require('path');
rimraf.sync(path.join(process.cwd(), 'dist'));// Delete the dist directory

const config = require('./config') ();const webpack = require('webpack');
const spinner = ora('Start building the project... ');
spinner.start();

webpack(config.toConfig(), function(err, stats) {
  spinner.stop();
  if (err) throw err;
  process.stdout.write(
    stats.toString({
      colors: true.modules: false.children: false.chunks: false.chunkModules: false
    }) + '\n\n'
  );

  if (stats.hasErrors()) {
    console.log(chalk.red('Build failed \n'));
    process.exit(1);
  }

  console.log(chalk.cyan('build complete \ n'));
});
Copy the code

Building a development environment

build/dev.js

const config = require('./config') ();const webpack = require('webpack');
const chalk = require('chalk');
const WebpackDevServer = require('webpack-dev-server');
const port = 8080;
const publicPath = '/common/';

config.devServer
  .quiet(true)
  .hot(true)
  .https(false)
  .disableHostCheck(true)
  .publicPath(publicPath)
  .clientLogLevel('none');

const compiler = webpack(config.toConfig());
const chainDevServer = compiler.options.devServer;
const server = new WebpackDevServer(
  compiler,
  Object.assign(chainDevServer, {})
);

['SIGINT'.'SIGTERM'].forEach(signal= > {
  process.on(signal, () => {
    server.close((a)= > {
      process.exit(0);
    });
  });
});
server.listen(port);// Listen on the port

new Promise((a)= > {
  compiler.hooks.done.tap('dev', stats => {
    const empty = ' ';
    const common = 'App running at: - Local: http://127.0.0.1:${port}${publicPath}\n`;
    console.log(chalk.cyan('\n' + empty + common));
  });
});
Copy the code

The CSS extracts loader configuration

config/css.js

module.exports = (config, resolve) = > {
  return (lang, test) = > {
    const baseRule = config.module.rule(lang).test(test);
    const normalRule = baseRule.oneOf('normal');
    applyLoaders(normalRule);
    function applyLoaders(rule) {
      rule
        .use('extract-css-loader')
        .loader(require('mini-css-extract-plugin').loader)
        .options({
          publicPath: '/'
        });
      rule
        .use('css-loader')
        .loader('css-loader') .options({}); }}; };Copy the code

CSS extraction plugin MiniCssExtractPlugin

config/MiniCssExtractPlugin.js

const MiniCssExtractPlugin = require('mini-css-extract-plugin');

module.exports = (config, resolve) = > {
  return (a)= > {
    config
      .oneOf('normal')
      .plugin('mini-css-extract')
      .use(MiniCssExtractPlugin);
  };
};
Copy the code

Automatic HTML generation

config/HtmlWebpackPlugin.js

const HtmlWebpackPlugin = require('html-webpack-plugin');

module.exports = (config, resolve) = > {
  return (a)= > {
    config.plugin('html').use(HtmlWebpackPlugin, [
      {
        template: 'public/index.html'}]); }; };Copy the code

This is the basic configuration based on webpack-chain. Let’s try to write a basic loader and plugin by ourselves. Before doing so, let’s review the basic concepts of loader and Plugin.

  • Loader Webpack natively supports only JS and JSON file types, Loaders supports other file types and converts them into valid modules. The execution sequence of the Loader is from right to left. The execution result on the right is sent to the left as a parameter.
  • Plugins are used to optimize bundle files, manage resources, and inject environment variables. Anything that loaders cannot handle can be done through plugins, which can be used throughout the build process.

Custom loader

options-chain-loader.js

module.exports = function(content) {
    // Re matches. Content is the file content loaded by the loader
  return content.replace(new RegExp(/([\$_\w\.]+\? \.) /.'g'),function(res) {
    let str  = res.replace(/ \? / \..' ');
    let arrs = str.split('. ');
    let strArr = [];
    for(let i = 1; i <= arrs.length; i++) {
      strArr.push(arrs.slice(0,i).join('. ')); 
    }
    let compile = strArr.join('&');
    const done = compile + '&' + str + '. '
    return  done;
  });
};
Copy the code

Used in the wepakc-chain configuration

module.exports = (config, resolve) = > {
  const baseRule = config.module.rule('js').test(/.js|.tsx? $/);
  const normalRule = baseRule.oneOf('normal');
  return (a)= > {
    normalRule
      .use('options-chain')
      .loader(resolve('options-chain-loader'))}}Copy the code

Custom plugins

fileListPlugins.js

class FileListPlugin { 
  apply(compiler) {
    // console.log(compiler)
    // Emit is an asynchronous hook, you can touch it using tapAsync, and you can also use tapPromise/tap(synchronous)
     compiler.hooks.emit.tapAsync('FileListPlugin', (compilation, callback) => { 
       // In the makefile, create a header string:
       var filelist = 'In this build:\n\n'; 
       // Go through all compiled resource files,
       // For each file name, add a line of content.
       for (var filename in compilation.assets) { 
        // console.log(filename)
         filelist += The '-' + filename + '\n'; 
        }
         // Insert this list as a new file resource into the WebPack build:
         compilation.assets['filelist.md'] = { 
           source: function() {
              return filelist; 
            }, 
            size: function() { 
              returnfilelist.length; }}; callback(null.11); 
        }); 
      } 
    // compiler.plugin('done', function() {
    // console.log('Hello World! ');
    // });
    // }
    // compiler.plugin("compilation", function(compilation) {

    // // Now, set up callbacks to access steps in Compilation:
    // compilation.plugin("optimize", function() {
    // console.log("Assets are being optimized.");
    / /});
    // });
} 
module.exports = FileListPlugin;

Copy the code

Used in the wepakc-chain configuration

const FileListPlugin = require(".. /fileListPlugins");

module.exports = (config, resolve) = > {
  return (a)= > {
    config
      .plugin('file-list-plugin')
      .use(FileListPlugin)
  }
}
Copy the code