webpack loader原理以及自定义loader

loader主要是帮助webpac将不同类型的文件转换为webpack可识别的模块。

分类:enforce属性

pre 前置loader,normal 普通loader,inline:内联loader,post:后置loader

如果不写默认是 normal类型

执行顺序:pre > normal > inline > post

相同等级的,从后往前执行。

//普通loader,执行顺序:loader3,loader2,loader1
module:{
    {
        test:/\.js$/,
        loader:"loader1"
    },
    {
        test:/\.js$/,
        loader:"loader2"
    },
    {
        test:/\.js$/,
        loader:"loader3"
    },

}

//不同级别loader,执行顺序:loader1,loader2,loader3
module:{
    {
        enforce:"pre",
        test:/\.js$/,
        loader:"loader1"
    },
    {
        test:/\.js$/,
        loader:"loader2"
    },
    {
        enforce:"post",
        test:/\.js$/,
        loader:"loader3"
    },

}

使用loader的方式:

配置方式:如上述直接在webpack.config.js文件配置,加上enforce配置。(pre normal post)

内联方式:在每个import语句中显示指定loader(inline)

inline loader用法:

这里使用css-loader,style-loader来处理这个css资源,!是为了隔开各个loader

import Styles from 'style-loader!css-loader?modules!./styles.css'

inline loader可以通过添加不同的前缀,跳过其他类型的loader:

! :表示跳过normal loader,即使在module里配置了style-loader,css-loader,如是normal类型的,也不会执行的。

import Styles from '!style-loader!css-loader?modules!./styles.css'

- ! :表示跳过pre和normal loader,即使在module里配置了style-loader,css-loader,如是pre或normal类型的,也不会执行的。

import Styles from '-!style-loader!css-loader?modules!./styles.css'

 ! ! :表示跳过pre和normal和post loader,即使在module里配置了style-loader,css-loader,如是pre或normal或post类型的,也不会执行的。

import Styles from '!!style-loader!css-loader?modules!./styles.css'

但是一般不会选择使用inline,因为不好复用。


实际使用:

新创建一个项目:

npm init -y
//webpack.config.js
const path = require("path")
const HtmlWebpackPlugin=reuqire("html-webpack-plugin")
module.exports={
    entry:"./src/main.js",
    output:{
        path:path.resolve(__dirname,"./dist"),
        filename:"js/[name].js",
        clean:true
    },
    module:{
        rules:[]
    },
    plugins:[
        new HtmlWebpackPlugin({
            template:path.resolve(__dirname,"public/index.html")
        })
    ],
    mode:"development"
}
//index.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>myLoader</title>
</head>
<body>
    <div id="app"></div>
</body>
</html>
//main.js
console.log("hello main");

下载依赖:

npm i webpack webpack-cli html-webpack-plugin -D

尝试打包:npx webpack成功

新建loaders文件夹,建立test-loader.js

loader是一个函数,当webpack解析资源时,会调用响应的loader,传入内容loader会作为参数收到数据并且返回出去,

content 文件内容

map SourceMap相关

meta 别的loader传来的数据

module.exports=function(content,map,meta){
    console.log(content);
    return content
}

webpack.config.js配置:

rules:[
            {
                test:/\.js$/,
                loader:"./loaders/test-loader.js"
            }
        ]

loader的分类

同步loader

// 同步loader 
// 简单写法,只有一个loader
// module.exports=function(content){
//     return content
// }
//多个loader传递,倾向于这个
module.exports=function(context,map,meta){
    //第一个参数:表示是否有错误,有错就是具体内容,无错就是null
    //第二个参数:传递的内容
    //第三个参数:source-map,继续传递
    //第四个参数:meta给其他loader的参数,可以自己写,也可以传上一个下来的

    this.callback(null,context,map,meta)
}

异步loader:异步代码一定要放在异步loader中放在同步中会有问题。

// 异步loader,会等待异步做完后再做其他的事
module.exports=function(context,map,meta){
    const callback = this.async();
    setTimeout(()=>{
        console.log("test2");
        callback(null,context,map,meta)
    },1000)
}

在webpack配置:

rules:[
            // {
            //     test:/\.js$/,
            //     loader:"./loaders/test-loader.js"
            // },
            {
                test:/\.js$/,
                use:[
                    "./loaders/test/test1.js","./loaders/test/test2.js"
                ]
            },
        ]

raw loader:可以写异步或者同步,但是要在最后加一句:module.exports.raw = true

//接收的content数据是buffer类型的
module.exports=function(context,map,meta){
    const callback = this.async();
    setTimeout(()=>{
        console.log("test2");
        callback(null,context,map,meta)
    },1000)
}
module.exports.raw = true

pitch loader:在输出的对象里加上pitch方法,这个方法会在loader执行之前先执行,优先级大于loader,会先把每个loader的pitch方法执行后,在按照顺序执行loader

pitch的执行顺序取决于loader的顺序,同级的话,正向顺序执行,如果loader存在不同的优先级,那么pitch也根据优先级的反向顺序执行。

例如:

全部为normal,从左向右,从上到下执行;

存在pre,normal,post,则从优先级低的开始执行,post=>normal=>pre

pitch全部执行结束后,执行loader

如果在pitch方法中加入了返回值,那么之后的所有pitch以及loader全部都不执行,直接跳到前一个pitch方法对应的loader中继续加载loader。

module.exports=function(context,map,meta){
    console.log("loader1");
    this.callback(null,context,map,meta)
}
module.exports.pitch=function(){
    console.log("pitch1");
}

loader API

说几个常用的,具体的参考:Loader Interface | webpack 中文文档 | webpack 中文文档 | webpack 中文网

 自定义clean-log-loader

module.exports=function(content,map,meta){
    return content.replace(/console\.log\(.*\);?/g,"")
}
rules:[
            // {
            //     test:/\.js$/,
            //     loader:"./loaders/test-loader.js"
            // },
            // {
            //     test:/\.js$/,
            //     use:[
            //         // "./loaders/test/test1.js","./loaders/test/test2.js"
            //         // "./loaders/test/test3.js"
            //         "./loaders/test/test4.js","./loaders/test/test5.js","./loaders/test/test6.js"
            //     ]
            // },
            // {
            //     enforce:"pre",
            //     test:/\.js$/,
            //     loader:"./loaders/test/test4.js"
            // },
            
            // {
            //     enforce:"post",
            //     test:/\.js$/,
            //     loader:"./loaders/test/test6.js"
            // },
            // {
            //     test:/\.js$/,
            //     loader:"./loaders/test/test5.js"
            // },
            {
                test:/\.js$/,
                loader:"./loaders/clean-log-loader.js"
            },
        ]

自定义banner-loader,添加作者:

module.exports=function(context,map,meta){
    //获取传入的options选项
    //schema要符合JSONschema的验证规则
    let schema = {
        "type":"object",
        "properties":{
            "author":{ //author属性
                "type":"string"
            }
        },
        "additionalProperties":false //是否允许添加其他的属性
    }
    let options = this.getOptions(schema)
    const prefix =`
    /*
    *Author:${options.author}
    */
    `
    return prefix+context
}
rules:[
            {
                test:/\.js$/,
                loader:"./loaders/clean-log-loader.js"
            },
            {
                test:/\.js$/,
                loader:"./loaders/banner-loader.js",
                options:{
                    author:"大熊",
                    age:18
                }
            },
        ]

 自定义babel-loader:

下载依赖:

npm i @babel/core @babel/preset-env -D
//babel-loader
const babel = require("@babel/core")
module.exports=function(context,map,meta){
    let schema = {
        "type":"object",
        "properties":{
            "presets":{
                "type":"array"
            }
        },
        additionalProperties:true
    }
    const callback = this.async()
    let options = this.getOptions(schema)
    babel.transform(context,options,function(err,result){
        if(err) callback(err)
        else callback(null,result.code,)
    })
    return context
}
{
                test:/\.js$/,
                loader:"./loaders/babel-loader.js",
                options:{
                    presets:["@babel/preset-env"],
                }
            },

然后打包发现箭头函数转为普通函数。

自定义file-loader:主要针对图片,字体等文件,转为二进制的流。

下载工具:

npm i loader-utils -D
const loaderUtils = require("loader-utils")
module.exports=function(content){
    //1.根据文件内容生成带hash值的文件名
    const interpolateName = loaderUtils.interpolateName(
        this,//上下文this
        "[hash].[ext][query]",//生成文件名称格式
        content// 处理内容
    )
    //interpolateName = `images/${interpolateName}`可以设置输出的路径
    //2.将文件输出
    this.emitFile(interpolateName,content)
    // 3.返回:module.exports = "文件路径(文件名)"
    return `module.exports = "${interpolateName}"`
}
module.exports.raw=true
{
                test:/\.(png|jpe?g|gif)$/,
                loader:"./loaders/file-loader.js",
                type:"javascript/auto" //阻止webpack默认处理图片资源,只用file-loader处理
            },

自定义style loader

module.exports=function(context,map,meta){
    // 不能直接会用style-loader,只能处理样式,不能处理引入的其他资源
    //借助css-loader解决样式中引用其他资源的问题,但是css暴露的是js代码,style-loader需要执行js代码得到返回值,再创建style标签,插入到页面上,不好操作,于是styleloader使用pitch的方式
}
module.exports.pitch = function(remainningRequest){ // remainningRequest剩余要处理的数据
    // 1.将remainningRequest改为相对路径(后面处理只能使用相对路径)
    const relativePath = remainningRequest.split("!").map(absolutePath=>{
        // 返回一个相对路径
        return this.utils.contextify(this.context,absolutePath) //this.context指当前loader所在的目录
    }).join("!")
    // 2.在这里会使用css-loader处理资源,引入css-loader处理后的资源,创建tyle并引入。
    // 添加!!:加载完pitch方法以后不再执行其他的loader,包括pre,normal,post
    // 引入 styke使用内联的用法
    const script = `
        import style from "!!${relativePath}"
        const styleEl = document.createElement('style')
        styleEl.innerHTML= style
        document.head.appendChild(styleEl)
    `
    // 终止后面的loader执行
    return script
}
{
                test:/\.css$/,
                use:["./loaders/style-loader.js","css-loader"]
            },

猜你喜欢

转载自blog.csdn.net/m0_59962790/article/details/130450438