preface

As the front-end is dealing with webpack every day, it is very necessary to learn, especially responsible for file parsing Webpack-loader (hereinafter referred to as loader), as the loader of Webpack has become an essential part of packaging. This article will be from the implementation level insight loader implementation principle, I believe that after reading this article, you can also write a loader of your own, no more nonsense, let’s start!

The preparatory work

1. We need someone to debugloaderwebpackEnvironment, the console executes the following commands:

npm init
npm install webpack webpack-cli webpack-dev-server babel-loader @babel/core -D
Copy the code

2. To createwebpack.config.jsPackaging entrancemain.jsAnd the CSS file we need to loadcolor.css

touch webpack.config.js
touch main.js
touch color.css
Copy the code

3. Write the basic packaging configuration

const path = require('path');
module.exports = {
    entry: './main.js'.output: {
        path: path.resolve(__dirname, 'dist'),
        filename: 'output.bundle.js'
    },
    devServer: {
        contentBase: path.join(__dirname, 'dist'),
        port: 9000}};Copy the code

4. Create oursloaderThe folder andloaderfile

mkdir my-loader
cd my-loader
touch css-loader.js
touch style-loader.js
Copy the code

5. Becausestyle-loaderIs to operate on phiThe browser, we need to see the effect through the page, createhtmlFile, againWebpack configurationmodified

npm install html-webpack-plugin -D
touch index.html
Copy the code
const path = require('path');
+ const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
    entry: './main.js',
    output: {
        path: path.resolve(__dirname, 'dist'),
        filename: 'output.bundle.js'
    },
    module: {
        rules: [
            {
                test: /\.js$/,
                use: 'babel-loader'
            }
        ]
    },
    devServer: {
        contentBase: path.join(__dirname, 'dist'),
        port: 9000
    },
+ plugins: [new HtmlWebpackPlugin({ template: './index.html' })]
};
Copy the code

6. Createloaderconfiguration

const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
    entry: './main.js',
    output: {
        path: path.resolve(__dirname, 'dist'),
        filename: 'output.bundle.js'
    },
    module: {
        rules: [
            {
                test: /\.js$/,
                use: 'babel-loader'
            },
+ {
+ test: /\.css$/,
+ use: [
+ {
+ loader: path.resolve('./my-loader/style-loader')
+},
+ {
+ loader: path.resolve('./my-loader/css-loader')
+}
+]
+}
        ]
    },
    devServer: {
        contentBase: path.join(__dirname, 'dist'),
        port: 9000
    },
    plugins: [new HtmlWebpackPlugin({ template: './index.html' })]
};
Copy the code

Now that our environment setup is complete, the directory should look like this if nothing else

├ ─ ─ color. CSS ├ ─ ─ index. The HTML ├ ─ ─ the main, js ├ ─ ─ my - loader | ├ ─ ─ CSS - loader. Js | └ ─ ─ style - loader. Js ├ ─ ─ package - lock. Json ├ ─ ─ package. Json └ ─ ─ webpack. Config. JsCopy the code

Implementation of CSS – loader

Css-loader is the main loader used to parse CSS files. It is mainly used to parse CSS style files imported through import/requrie. According to webPack official website, all loaders are node modules exported as a function.

// The simplest loader returns resources without processing any files
module.exports = function (source) {
   return source;
};
Copy the code

Now let’s turn it into a loader that can handle CSS files!

The preparatory work

  • First of all incolor.cssWrite our style
body {
    background-color: #20232a;
}
span {
    font-size: 40px;
    font-weight: bold;
    margin: 0 16px;
    text-transform: capitalize;
}
.react {
    color: #61dafb;
}
.vue {
    color: #4fc08d;
}
.angular {
    color: #f4597b;
}
Copy the code
  • main.jsIntroducing ourcssfile
import style from './color.css'; // If CSS-loader works, style will be the keyMap of the class name
window.onload = () = > {
    const body = document.body;
    const frameworks = ['react'.'vue'.'angular'];
    frameworks.forEach((item) = > {
        const span = document.createElement('span');
        span.innerText = item;
        span.setAttribute('class', style[item]);
        body.appendChild(span);
    });
};
Copy the code

Write the loader

  • To obtainThe CSS textIn this step,webpackIt’s already taken care of for us, by matching.cssFile suffix, automatically obtainedThe CSS text, that is,sourceparameter
"body {\n background-color: #20232a; \n}\nspan {\n font-size: 40px; \n font-weight: bold; \n margin: 0 16px; \n text-transform: capitalize; \n}\n.react {\n color: #61dafb; \n}\n.vue {\n color: #4fc08d; \n}\n.angular {\n color: #f4597b; \n}\n"Copy the code
  • parsingThe CSS textIs extracted by reCSS class selector(Note: Since this is a simple implementation, only single class names are considered.)
module.exports = function (source) {
   // source
   const reg = / (? < = \.) (. *?) (? ={)/g; // Gets the re for all class names of strings
   const classKeyMap = Object.fromEntries(source.match(reg).map((str) = > [str.trim(), str.trim()])); // Retrieve the original CSS class name from the string
   return source;
};
Copy the code

You get the following classKeyMap

{
  react: "react".vue: "vue".angular: "angular",}Copy the code
  • According to theloaderReturn definition of,loaderThe returned result should beStringorBuffer(is converted to a string), so our output should be converted tostringThere are two things that need to be output, one processedcssThe other is the source file of the class nameMapping the MapTo identify these two variables, use a specialkeyTo mark, as shown below
module.exports = function (source) {
   // source
   const reg = / (? < = \.) (. *?) (? ={)/g; // Gets the re for all class names of strings
   const classKeyMap = Object.fromEntries(source.match(reg).map((str) = > [str.trim(), str.trim()])); // Retrieve the original CSS class name from the string
   return `/**__CSS_SOURCE__${source}*//**__CSS_CLASSKEYMAP__The ${JSON.stringify(classKeyMap)}* / `;
Copy the code

Now a simple CSS-Loader is complete!

Add CSS – module

  • Let’s try to add it to him nowcss-moduleFunction, addedwebpackconfiguration
  {
      test: /\.css$/,
      use: [
          {
              loader: path.resolve('./my-loader/style-loader')
          },
          {
              loader: path.resolve('./my-loader/css-loader'),
+ options: {
+ module: true
+}}}]Copy the code

Loader-utils for reading configuration and schema-utils for verifying configuration are provided to parse the loader configuration

npm install loader-utils schema-utils -D
Copy the code

Modify our previous loader

const getOptions = require('loader-utils').getOptions;
const validateOptions = require('schema-utils').validate;
const schema = {
    type: 'object'.properties: {
        module: {
            type: 'boolean'}}};module.exports = function (source) {
    const options = getOptions(this); // Get loader options
    validateOptions(schema, options, 'css-loader'); // Verify that the options parameter type is correct according to the schema
    const reg = / (? < = \.) (. *?) (? ={)/g; // Gets the re for all class names of strings
    const classKeyMap = Object.fromEntries(source.match(reg).map((str) = > [str.trim(), str.trim()])); // Retrieve the original CSS class name from the string
    return `/**__CSS_SOURCE__${source}*//**__CSS_CLASSKEYMAP__The ${JSON.stringify(classKeyMap)}* / `;
};
Copy the code

Schema-utils ensures the reliability of our arguments, and webpack throws exceptions if they do not match the schema’s type expectations

Module build failed (from ./my-loader/css-loader.js):
ValidationError: Invalid configuration object. Objecthas been initialized using a configuration object that does not match the API schema. - configuration.module should be a  boolean. at validate (/Users/redjue/Desktop/Webpack Loader/node_modules/schema-utils/dist/validate.js:104:11)
    at Object.module.exports (/Users/redjue/Desktop/Webpack Loader/my-loader/css-loader.js:19:5)
 @ ./main.js 1:0-32 9:31-36
Copy the code
  • forcssThe name of the class andscope
const getOptions = require('loader-utils').getOptions;
const validateOptions = require('schema-utils').validate;
const schema = {
    type: 'object'.properties: {
        module: {
            type: 'boolean'}}};// Hash generates functions
function hash() {
    const s4 = () = > (((1 + Math.random()) * 0x10000) | 0).toString(16).substring(1);
    return s4() + s4();
}

module.exports = function (source) {
    const options = getOptions(this);
    validateOptions(schema, options, 'css-loader'); // Verify that the options parameter type is correct according to the schema
    const reg = / (? < = \.) (. *?) (? ={)/g; // Gets the re for all class names of strings
    const classKeyMap = Object.fromEntries(source.match(reg).map((str) = > [str.trim(), str.trim()])); // Retrieve the original CSS class name from the string
    if (options.module) {
        //css-module
        const cssHashMap = new Map(a); source = source.replace(reg,(result) = > {
            const key = result.trim();
            const cssHash = hash();
            cssHashMap.set(key, cssHash);
            return `${key}-${cssHash}`;
        });
        Object.entries(classKeyMap).forEach((item) = > {
            classKeyMap[item[0]] = `${item[1]}-${cssHashMap.get(item[0])}`;
        });
    }
    return `/**__CSS_SOURCE__${source}*//**__CSS_classKeyMap__The ${JSON.stringify(classKeyMap)}* / `;
};
Copy the code

Css-loader supports CSS-Module. Next, let’s write a style-loader to render the style on the page

Implementation style – loader

Style-loader is responsible for putting CSS styles into the DOM and is relatively easier to implement than CSS-loader

module.exports = function (source) {
    const cssSource = source.match(/ (? <=__CSS_SOURCE__)((.|\s)*?) (? =\*\/)/g); // Get the CSS resource string
    const classKeyMap = source.match(/ (? <=__CSS_classKeyMap__)((.|\s)*?) (? =\*\/)/g); // Get the CSS class name Map
    let script = `var style = document.createElement('style');   
    style.innerHTML = The ${JSON.stringify(cssSource)};
    document.head.appendChild(style);
  `;
    if(classKeyMap ! = =null) {
        script += `module.exports = ${classKeyMap}`;
    }
    return script;
};
Copy the code

With csS-loader parsing the data, style-loader simply takes care of putting the styles on the page and exporting the classkeyMap.

Using the loader

With csS-loader and style-loader written, let’s see how it works in practice!

Since webPack 5.x is installed by default, the dev-server directive has been replaced by the Webpack Serve directive, so we run the following command to start the service

redjue@fengji:Webpack Loader ⍉ ➜ Webpack Serve Debugger Attached. ℹ "WDS" : Project is running at http://localhost:9000/ ℹ "WDS" : Webpack output is served from undefined "WDS" : Content not from webpack is served from /Users/redjue/Desktop/Webpack Loader/distCopy the code

Open a browserhttp://localhost:9000/If you see the following effect, ourstyle-loader To take effect!

Let’s seecss-moduleIs it working? Open the console

Good and effective, so far we have successfully run their loader

Write in the last

Of course, the official CSS-loader and style-loader are more complex, this article is mainly to let you know how to write a loader, code word is not easy, click a like and then go

Welcome to pay attention to the author’s public reed number [front-end can be really cool], do not regularly share the original good article, let us play together the most cool front-end!