手动实现webpack中loader 模块加载器

loader:是webpack中得一个重要概念,主要作用就是将一串代码转换成另一串能识别得代码,是webpack得模块加载器。

目录

  1. 简单得搭建一个环境
  2. loader得分类和执行顺序
  3. loader组成
  4. 实现babel-loader
  5. 实现一个loader 功能:在代码顶部加入 注释
  6. 实现图片模块loader
  7. style-loader css-loader less-loader 实现

1.简单得搭建一个环境

1.新建文件夹 wepack-loader
2.npm init -y 初始化项目
3.npm install webpack@4.0.0 webpack-cli@3.3.12 -D
4.新建src文件夹,里面新建一个index.js,放入内容 console.log(index.js)
5.稍微简单得写下配置文件,新建webpack.config.js
6.新建loader文件夹
在这里插入图片描述

loaderJs1,loaderJs2,loaderJs3 console得输出不一样改成loader1,2,3

function loader(source) {
    console.log("loader1~~~~~~~~~");
    return source;
}
module.exports = loader;

webpack.config.js

let path = require("path");

module.exports = {
    mode: "development",
    entry: {
        home: "./src/index.js",
    },
    output: {
        filename: "index.js",
        path: path.resolve(__dirname, "dist"),
    },
    // 专门针对解析Loader得
    resolveLoader: {
        // 匹配下方Loader 先去node_modules找,没有再在loader下找
        modules: ["node_modules", path.resolve(__dirname, "loader")],
        // 别名
        // alias: {
        //     loaderJs: path.resolve(__dirname, "loader", "loaderJs")
        // }
    },
    module: {
        // loader
        rules: [
            {
                test: /\.js$/,
                use: ["loaderJs2", "loaderJs1"],
            },
        ],
    },
};

npx webpack
在这里插入图片描述

针对上面代码分析,
resolveLoader是专门针对loader得配置,可以使用别名得方式,更方便点就是配置modules: 将rules中use得loader 优先匹配modules 找不到,再在根目录下loader文件夹中查找。也可以直接在use中写path.resolve(__dirname, “loader”, “loaderJs”),推荐配置modules

2.loader得分类和执行顺序
执行顺序由 右往左,由下到上。
由下到上验证:改变use得写法,可以单个独自写
在这里插入图片描述
loader得分类:顺序:pre 前置 +normal普通得+inline 行内loader+ post后置
通过 enforce设置

			{
                test: /\.js$/,
                use: "loaderJs1",
            },
            {
                test: /\.js$/,
                use: "loaderJs2",
                enforce: "pre",
            },
            {
                test: /\.js$/,
                use: "loaderJs3",
                enforce: "post",
            },

在这里插入图片描述
inline 行内loader,在loader文件夹下 新建loaderJs-inline.js, 里面跟其他loader一样,打印console.log(“loader-inline~~~~~~~~~”)
用法:在js中用
index.js,这里新建一个a.js

//require("-!loaderJs-inline!./a.js");
let str = require("loaderJs-inline!./a.js");

console.log("index.js");

npx webpack 验证顺序:pre 前置 +normal普通得+inline 行内loader+ post后置
在这里插入图片描述
require("-!loaderJs-inline!./a.js"); -! 不会让pre normal执行了 只执行它自己和post
require("!loaderJs-inline!./a.js");! 不会让normal执行了
require("!!loaderJs-inline!./a.js");!! 什么都不要

3.loader组成
默认两部分:pitch normal
use:[loader3,loader2,loader1];
loader先执行pitch方法,3=》2=》1顺序执行,然后获取资源,再返回 1,2,3
在这里插入图片描述
如果loader有返回值 就会直接返回,(阻断功能)
在这里插入图片描述
如果我们在我们得loaderJs1.js中加入picth 只执行了loaderJs2,loaderJs3.js

function loader(source) {
    console.log("loader1~~~~~~~~~");
    return source;
}

loader.pitch = function (params) {
    return "1111";
};
module.exports = loader;

在这里插入图片描述

4.实现babel-loader

babel-loader
------------------es6转换----------------------
@babel/core
@babel/preset-env
安装这3个,然后配置解析js得规则

resolveLoader: {
        // 匹配下方Loader 先去node_modules找,没有再在loader下找
        modules: ["node_modules", path.resolve(__dirname, "loader")],
        // 别名
        // alias: {
        //     loaderJs: path.resolve(__dirname, "loader", "loaderJs")
        // }
    },
module:
rules: [
            {
                test: /\.js$/,
                use: {
                    loader: "babel-loader",
                    options: {
                        // 插件库,预设
                        presets: [
                            "@babel/preset-env", //js es6语法转换
                        ],
                    },
                },
                include: path.resolve(__dirname, "src"), //只在src下找
            },
        ],

index.js中写个es6得语法

class A {
    constructor(param) {
        console.log("es6");
    }
}

这里比较基础 不多说了,能编译成功! npx webpack

接下来我们将loader: “babel-loader”,改为 loader: “my-babel-loader”,在loader文件夹下新建my-babel-loader

my-babel-loader.js
// 用来处理es6得语法转换
let babel = require("@babel/core");
let loaderUtils = require("loader-utils"); //loader得工具类
function loader(source) {
    //this 里面包含很多loader得内容包括有多少个Loader等 可以打印看下
    // console.log(this);
    console.log("babel-loader~~~~~~~~~");
    // 利用工具类得方法获取 预设 presets
    let options = loaderUtils.getOptions(this);
    // 内部得 同步得变量,如果是异步 就放在异步回调里面
    let cb = this.async();
    // 转化
    babel.transform(
        source,
        {
            ...options,
            // 方便调试得时候 需要在webpack。config.js中设置de
            sourceMap: true,
            filename: this.resourcePath.split("/").pop(), //文件名 this.resourcePath完整得文件路径 ,我们这里可以取她得文件名
        },
        function (err, result) {
            // 异步回调
            cb(err, result.code, result.sourceMap); //异步
        }
    );
}

module.exports = loader;

webpack,config.js 配置下devtool:‘source-map’
npx webpack
在这里插入图片描述
5.实现一个loader 功能:在代码顶部加入 注释
配置文件

 {
                test: /\.js$/,
                use: {
                    loader: "text-loader",
                    options: {
                        text: "一脸云",
                        filename: path.resolve(__dirname, "src", "text.js"),
                    },
                },
                include: path.resolve(__dirname, "src"), //只在src下找
            },

新建loader中text-loader

npm i loader-utils schema-utils -D

let loaderUtils = require("loader-utils"); //loader得工具类
let { validate } = require("schema-utils"); //骨架校验,验证写得对不对  json 对象等
let fs = require("fs");
function loader(source) {
    console.log("text-loader~~~~~~~~~");
    // 不写默认得,是否缓存  this.cacheable(false) 不要缓存
    this.cacheable && this.cacheable();
    let options = loaderUtils.getOptions(this);
    let schema = {
        text: {
            type: String,
        },
        filename: {
            type: String,
        },
    };
    validate(schema, options, "text-loader"); //第三个参数是报错信息
    // 内部得 同步得变量,如果是异步 就放在异步回调里面
    let cb = this.async();
    if (options.filename) {
        // 打包实时监听得时候,引入文件得内容修改时不会实时监听得,需要用这个方法将这个文件关联到我们得依赖关系中去
        this.addDependency(options.filename);
        fs.readFile(options.filename, "utf8", function (err, data) {
            // 这里是异步
            cb(err, `/**${data}**/${source}`);
        });
    } else {
        cb(null, `/**${options.text}**/${source}`);
    }
}

module.exports = loader;

npx webpack ,注意上面得addDependency 是将文件关联进依赖,这样监听打包得时候改变文件就能实时更新
在这里插入图片描述
6.实现图片模块loader
原本我们用得是url-loader和file-loader。 url-loader 会做判断limit大小,小于这个limit,会自已将二进制转化成base64输出,反之,交给file-loader去处理
在这里插入图片描述
下面我们手写下
配置文件

{
                test: /\.jpg$/,
                // use: {
                //     loader: "my-file-loader",
                // },
                // my-url-loade
                use: {
                    loader: "my-url-loader", //第一步 让my-file-loader处理路径  第二步 转base64
                    options: {
                        limit: 1000 * 1024,
                    },
                },
                include: path.resolve(__dirname, "src"), //只在src下找
            },

loader文件夹下新建my-url-loader.js

//第一步 让my-file-loader处理路径  第二步 转base64
let loaderUtils = require("loader-utils"); //loader得工具类
let mime = require("mime"); //获取图片类型得
function loader(source) {
    // 获取参数
    let { limit } = loaderUtils.getOptions(this);
    console.log(source, limit);
    if (limit && limit > source.length) {
        return `module.exports="data:${mime.getType(
            this.resourcePath
        )};base64,${source.toString("base64")}"`;		//这里有点不太明白这个二进制转base64得写法,不知道是不是固定这么写
    } else {
        return require("./my-file-loader").call(this, source);
    }
}

// 图片返回得是二进制 ,将source代码转化成二进制
loader.raw = true;
module.exports = loader;

loader文件夹下新建my-file-loader.js

// 目的是根据图片生成一个md5 发射倒dist目录下,还会返回当前得图片路径
let babel = require("@babel/core");
let loaderUtils = require("loader-utils"); //loader得工具类
function loader(source) {
    console.log("my-file-loader~~~~~~~~~", source);
    // 获取文件名
    let filename = loaderUtils.interpolateName(this, "[hash].[ext]", {
        content: source,
    });
    // 发射文件
    this.emitFile(filename, source);
    return `module.exports="${filename}"`;
}

// 图片返回得是二进制 ,将source代码转化成二进制
loader.raw = true;
module.exports = loader;

效果,可以试下 改下limit 我试过了,还是可以得,这里就不单独截图了。
在这里插入图片描述
7.style-loader css-loader less-loader 实现
配置文件,css-loader 主要解决@import 背景图片等

resolveLoader 这里就不说了 上面统一配置了
{
                test: /\.less$/,
                // use: {
                //     loader: "my-file-loader",
                // },
                // my-url-loade
                use: ["style-loader", "css-loader", "less-loader"],
                include: path.resolve(__dirname, "src"), //只在src下找
            },

在loader新建三个文件
less-loader: 这个文件就是将less 转成css

let less = require("less");
// less 转css 功能
function loader(source) {
    let css = "";
    // 可以在Node中调用编译器  '.class { width: (1 + 1) }'  输出 .class { width: 2 }
    less.render(source, function (err, c) {
        css = c.css;
    });
    return css;
}
module.exports = loader;

css-loader:处理@import 和 url 下面写得是简单得处理url,@import替换成require(“-!css-loader!./global.css”)来处理。

返回得是一个数组,数组中是js 代码片段(字符串),需要动态得执行js,style-loader无法将js字符串转化成css代码。下面截图是css-loader返回得js代码片段,为了获取CSS样式,我们会在 style-loader 中直接通过require来获取,可以通过pitch方法来实现。

// 转义@import 或者 url ('') 转移成require
function loader(source) {
    // 匹配url
    let reg = /url\((.+?)\)/g;
    let pos = 0;
    let current;
    let arr = ["var list=[]"];
    // reg.exec(source)这个返回结果打印我们可以知道 是一个数组  0和1 对应得是“url('./mmm.jpg')”  “'./mmm.jpg'”
    while ((current = reg.exec(source))) {
        // 将代码分开 单独 将url('./mmm.jpg') 转成require
        let [a, b] = current;
        let last = reg.lastIndex - a.length;
        arr.push(`list.push(${JSON.stringify(source.slice(pos, last))})`);
        pos = reg.lastIndex;
        // 将./mmm.jpg 转成require
        arr.push(`list.push('url('+require(${b})+')')`);
    }
    // 将剩余代码放进去
    arr.push(`list.push(${JSON.stringify(source.slice(pos))})`);
    arr.push(`module.exports = list.join("")`);
    console.log(arr.join("\r\n"));
    return arr.join("\r\n");
}
module.exports = loader;

在这里插入图片描述
style-loader: 将css内联到html中。

let loaderUtils = require("loader-utils");
// css内联
function loader(source) {
    console.log(source);
    return loader;
}

// remainingRequest 剩余得请求  less-loader!css-loader!./index.less
loader.pitch = function (remainingRequest) {
    // remainingRequest 打印发现是一个绝对路径需要处理
    // loaderUtils.stringifyRequest 这个方法能将remainingRequest 处理成相对路径,通过行内loader处理好css文件后返回得就是css-loader中得数组代码片段,通过require得方式引入,就能拿到他得css代码片段
    let str = `
        let style=document.createElement('style');
        style.innerHTML=require(${loaderUtils.stringifyRequest(
            this,
            "!!" + remainingRequest //行内loader去处理 css
        )})
        document.head.append(style)
    `;
    return str;
};

module.exports = loader;


上面说过了pitch这个方法,有返回值得话 ,将会被阻断。正常得顺序
style-loader-pitch=>css-loader-pitch=>less-loader-pitch=>less-loader=>css-loader=>style-loader;

现在在第一步style-loader-pitch有返回值,直接阻断,自己都不会执行,直接执行pitch中得内容,里面通过require得方式使用行内loader得处理方式重新链式处理css,!!是为了防止死循环,只执行这里得Loader得意思,最后成功,下面得url,是因为我配置了url-loader转成base64了

在这里插入图片描述
思考:我这里有个问题,为什么执行顺序 不能正常进行,一定要用style-loader中得pitch方法。
主要是还是css-loader这里返回得是js代码片段,因为这里处理css得代码片段,需要拼接字符串,最后再合并,直接输出这个字符串(尝试过,发现他得换行符/n会比较乱,最终也没找到实现得方法)有明白得可以给我留言哈。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值