While the different build tools are similar in principle, the next article should be about Vite as well.

The difference between Loader and Plugins

Loaders primarily work with static resources, and plugins can be used throughout the webPack build cycle to do things that Loaders can’t. However, loader can use an independent runtime environment, and can use some libraries locally to perform local debugging, while plugins cannot, he must write this plugin after the Webpack build plugin in the plugins array execution.

Common development methods for loader development

Webpack document DEMO structure analysis

import { getOptions } from 'loader-utils';
import { validate } from 'schema-utils';
const schema = {
  type: 'object'.properties: {
    test: {
      type: 'string',}}};export default function (source) {
  const options = getOptions(this);
  validate(schema, options, {
    name: 'Example Loader'.baseDataPath: 'options'});// Apply some transformations to the source...
  return `export default The ${JSON.stringify(source)}`;
}
Copy the code

Loader is actually a JS module that provides a method to perform logical operations on the original file and return the process after processing. By the way, loader makes chained calls from back to front.

Obtain loader parameters

Arguments can be retrieved using a loader called loader-utils, which uses the getOptions method to retrieve the passed arguments. In the runLoaders configuration, loaders is changed to a configuration with options according to the documentation. For example, add an object:

runLoaders({
    loaders: [
        // path.join(__dirname, './loaders/raw-loader.js'),
        {
            loader: path.join(__dirname, './loaders/raw-loader.js'),
            options: {env: 'development'.version: '1.0.0'}}],... })Copy the code

Then use loader-utils to bring out the passed parameters. Module. exports cannot use arrow functions, otherwise this would point to the current scope and not get the properties in runLoaders.

// npm install loader-utils -save-dev
const loaderUtils = require('loader-utils');

module.exports = function (source) {
    console.log(this);
    const { env, version } = loaderUtils.getOptions(this);
    console.log(env);
    console.log(version); . };// npmjs -> https://www.npmjs.com/package/loader-utils 
Copy the code

Synchronization Loader exception handling

In the case of synchronous execution, there are two ways to handle abnormal error printing:

The first is to directly use Error output Error, Error number can be filled in the information


module.exports = function (source) {...return new Error('error:10001');
};
Copy the code

The second option is to use this.callback directly, and execute the next step or output an error message depending on the parameter.

module.exports = function (source) {...return this.callback(new Error('error:10001'), source);
};
Copy the code

If the first parameter of this.callback is Error, the function will return data. If the first parameter is null, the function will return data.

module.exports = function (source) {...const params = { key: 1 }
    return this.callback(null, source, params);
};
Copy the code

Asynchronous loader processing

To get results asynchronously, use this.async() as a callback, with the first argument checking for errors and the second passing argument directly.

module.exports = function (source) {...const callback = this.async();
    fs.readFile(path.join(__dirname, '.. /src/number.txt'), 'utf-8'.(err, data) = > {
        if(err){
            callback(err,'error')
        }
        callback(null, data);
    });
};
Copy the code

Loader output file

Use emitFile for output.

const loaderUtils = require('loader-utils');
module.exports = function (source) {
    const url = loaderUtils.interpolateName(this.'[name].[ext]', source);
    this.emitFile(url, source)
    return source;
}
Copy the code

Develop an active CSS Sprites loader

First, you need to use the spritesmith dependency to merge multiple images and images together.

Step 1: Read the image URL

The idea is to get the CSS file first, and then use the regular match to export all image addresses

const loaderUtils = require('loader-utils');
module.exports = function (source) {
   const images = source.match(/url\((\S*)/g);
   const matchedImages = [];
   if (images && images.length > 0) {
		for (let i = 0; i < images.length; i++) {
     const img = images[i].match(/url\(.. (\S*)\'/) [1]; matchedImages.push(path.join(__dirname, img)); }}}Copy the code

Then extract the directory of the file, convert it into an absolute address and push it into an array. The printed matchedImages are as follows:

[
  '/Users/.. /images/web-security-bg-1.jpeg'.'/Users/.. /images/webpack-images-2.png'
]
Copy the code

Step 2: Use Spritesmith to merge images and CSS address replacements

Spritesmith.run({ src: matchedImages}, (err, result) = > {
	fs.writeFileSync(path.join(process.cwd(), 'dist/sprite.png'), result.image);
	source = source.replace(/url\((\S*)/g.(match) = >{
		return `url("dist/sprite.jpg")`;
  });
  fs.writeFileSync(path.join(process.cwd(), 'dist/index.css'), source);
     callback(null,source); })

Copy the code

In the dist directory, there is a combined image and there is also a CSS file in dist that has replaced the Sprite image. Of course, this is just an idea. If you want to fully implement the image and style replacement, you need to consider the background size, positioning, or some boundary issues. I won’t go into details here.

Webpack Plugin common development methods

Webpack document DEMO structure analysis

// webpack writting a plugin -> https://webpack.js.org/contribute/writing-a-plugin/

// A JavaScript class.
class HelloWorldPlugin {
  apply(compiler) {
    compiler.hooks.done.tap('Hello World Plugin'.(
      stats /* stats is passed as an argument when done hook is tapped. */
    ) = > {
      console.log('Hello World! '); }); }}module.exports = HelloWorldPlugin;
Copy the code

Plugin is actually a class. When webpack uses plugin, there will be a new XXplugin() operation, which is to create a new plugin class. Apply is the plugin that runs every time webPack is built.

Hooks are hooks for compiler objects that listen for what to do at a particular stage. The last thing that will go is the logic of the plugin.

Develop a plugin that compresses build resources as ZIP packages

Creating a ZIP file

First, use a JsZip that compresses the file into a ZIP package, using the emit hooks of the Compiler object to generate a file.

const JSZip = require('jszip');
const zip = new JSZip();
compiler.hooks.emit.tapAsync('ZipPlugin'.(Compilation, callback) = > {
    const folder = zip.folder(this.options.filename);
    for (let filename in Compilation.assets) {
        const source = Compilation.assets[filename].source();
        folder.file(filename, source)
    }
})
Copy the code

Folder is used to set the name of the zip package, and when the emit tapAsunc asynchronous hook is fired, the compilation content is filled into the Folder. Compilation. assist is a directory name. After iterating through the file name, use Source to retrieve the source and put it back in Folder.

The second step prints the file as a ZIP

zip.generateAsync({
  type:'nodebuffer'
  	}).then((content) = >{
  	const outputPath = path.join(Compilation.options.output.path 	,this.options.filename + '.zip');
  const outputRelativePath = path.relative(
    Compilation.options.output.path,
    outputPath
  )
  Compilation.assets[outputRelativePath] = new RawSource(content);
    callback()
    console.log(Compilation.options)
  })
})
Copy the code

The content returned by zip.generateAsync is a buffer that needs to be converted into the Assist of the compilation using RawSource and then executed as a callback.