首页 > 建站教程 > 前端框架 >  webpack5学习笔记:自定义loader正文

webpack5学习笔记:自定义loader

# 自定义loader

## loader解释

1. Loader是用于对模块的源代码进行转换(处理),之前我们已经使用过很多Loader,如css-loader、style-loader、babel-loader、vue-loader、ts-loader等

2. Loader本质上是一个导出为函数的JavaScript模块

3. Loader runner库会调用这个函数,然后将上一个loader产生的结果或者资源文件传入进去


## 自定义Loader用法:

1. 在根目录新建文件夹 loaders,在里面新建 custom-loader01.js

```

// 参数:content 为上一个loader利用fs读取的文件内容,在这个函数里面,就可以对匹配到的内容进行处理

module.exports = function(content) {

    return content

}

```


2. webapack中使用

```

const path = require('path')

module.exports = {

    // context: path.resolve(__dirname, '.'),

    entry: './src/main.js',  // 这里的相对路径,是相对于上面配置的context,而不是当前文件的位置,而context默认指向根目录

    output: {

        filename: 'bundle.js',

        path: path.resolve(__dirname, './build')

    },

    module: {

        rules: [

            {

                test: /\.js$/i,

                // 使用自定义loader

                use: './loaders/custom-loader01.js'  // 这里的相对路径,是相对于上面配置的context,而不是当前文件的位置,而context默认指向根目录

            }

        ]

    }

}

```



## resolveLoader 简化路径

1. 修改上面的代码

```

const path = require('path')

module.exports = {

    entry: './src/main.js',

    output: {

        filename: 'bundle.js',

        path: path.resolve(__dirname, './build')

    },

    module: {

        rules: [

            {

                test: /\.js$/i,

                // 使用自定义loader

                use: 'custom-loader01'  // 这里路径简化,仅仅只要写 loader 的名称,会自动到下面的 module.resolveLoader.modules 指定的路径里面去寻找相关的loaders

            }

        ]

    },

    resolveLoader: {

        modules: ['node_modules', './loaders']       // 这里默认是 node_modules, 可以配置其他路径

    }

}

```


## loader 的执行顺序

1. 假设下面配置了多个loader

```

const path = require('path')

module.exports = {

    entry: './src/main.js',

    output: {

        filename: 'bundle.js',

        path: path.resolve(__dirname, './build')

    },

    module: {

        rules: [

            {

                test: /\.js$/i,

                use: [

                    // 这里配置了多个loader

                    'custom-loader01',

                    'custom-loader02',

                    'custom-loader03'

                ]

            }

        ]

    },

    resolveLoader: {

        modules: ['node_modules', './loaders']

    }

}

```

    上面的执行顺序是,`custom-loader03->custom-loader02->custom-loader01`,即从下往上,从右往左,严格来说,每个loader里面可以写两个函数:normal和pitch,pitch的执行顺序是从上往下,从左往右。


## 修改loader的执行顺序

```

rules: [

    {

        test: /\.js$/i,

        use: [

            'custom-loader01',

        ],

        enforce: 'pre' // 最先执行

    },

    {

        test: /\.js$/i,

        use: [

            'custom-loader02'

        ],

        enforce: 'post' // 最后执行

    },

    {

        test: /\.js$/i,

        use: [

            'custom-loader03'

        ]

    },

],

```


## 同步 Loader

1. 同步 Loader 有两种方式返回数据:

```

// 直接return

module.exports = function(content) {

    return content

}

// callback 里面return

module.exports = function(content) {

    this.callback(null, content)

}

```

    如果不返回,会报错


## 异步 Loader

1. 在loader里执行请求等耗时操作,可以用异步callback

```

module.exports = function(content) {

    const callback = this.async()

    // 模拟耗时操作

    setTimeout(() => {

        callback(null, content)

    }, 2000)

}

```


## 自定义loader 接收 webpack 配置的参数

1. 假设有下面的传参

```

const path = require('path')

module.exports = {

    entry: './src/main.js',

    output: {

        filename: 'bundle.js',

        path: path.resolve(__dirname, './build')

    },

    module: {

        rules: [

            {

                test: /\.js$/i,

                use: [

                    // loader 传参

                    {

                        loader: 'custom-loader01',

                        options: {

                            name: '张三',

                            age: '35'

                        }

                    },

                    'custom-loader02'

                ]

            }

        ]

    },

    resolveLoader: {

        modules: ['node_modules', './loaders']

    }

}

```

2. 安装 loader-utils 库

`npm install loader-utils -D`

3. 修改 custom-loader01:

```

const { getOptions } = require('loader-utils')

module.exports = function(content) {

    // 获取传入参数

    const options = getOptions(this)

    console.log(options) //  { name: '张三', age: '35' }


    return content

}

```


## 自定义loader 参数的校验

1. 新建 custom-loader01-schema.json 文件

```

{

    "type": "object",

    "properties": {

        "name": {

            "type": "string",

            "description": "请输入名字"

        },

        "age": {

            "type": "number",

            "description": "请输入年龄"

        },

    },

    "additionalProperties": true // 为true表示除了上传的参数,还可以传入附加参数

}

```

2. 安装schema校验工具:

`npm install schema-utils -D`

3. 修改 custom-loader01:

```

const { getOptions } = require('loader-utils')

// 引入校验工具

const { validate } = require('schema-utils')

// 引入schema校验json

const schema = require('../custom-loader01-schema.json')

module.exports = function(content) {

    // 获取传入参数

    const options = getOptions(this)

    console.log(options) //  { name: '张三', age: '35' }


    // 用schema来校验options

    validate(schema, options, {

        name: 'custom-loader01'  // 这个参数主要是为了提醒哪里校验不通过

    })


    return content

}

```

    当webpack.config.js中custom-loader01传参错误,就会打包失败,致命错误


## 自定义loader实现 babel-loader 转换ES6语法的案例

1. 安装babel核心

`npm install @babel/core -D`


2. 安装babel预设,处理es6等语法

`npm install @babel/preset-env -D`


3. webpack中传入预设

```

const path = require('path')

module.exports = {

    // ...省略

    module: {

        rules: [

            {

                test: /\.js$/i,

                use: [

                    // loader 传参

                    {

                        loader: 'custom-loader01',

                        options: {

                            // 传入babel预设

                            presets: [

                                '@babel/preset-env'

                            ]

                        }

                    },

                    'custom-loader02'

                ]

            }

        ],

        resolveLoader: {

            modules: ['node_modules', './loaders']

        }

    }

}

```

4. 安装 loader-utils 库用来接收参数

`npm install loader-utils -D`


5. 修改上面的 custom-loader01

```

const babel = require('@babel/core')

// 获取刚才传入options

const { getOptions } = require('loader-utils')


module.exports = function(content) {

    // 设置为异步loader

    const callback = this.async()


    // 获取设置的 presets参数

    const options = getOptions(this)


    // 利用babel进行转码, content为获取的文件内容,options为预设参数

    babel.transform(content, options, (err, res) => {

        if(err){

            callback(err)

        }else{

            callback(null, res.code)

        }

    })

}

```


## 自定义loader实现 markdown 转 html 并显示到页面中

1. 安装html解析库

`npm install html-webpack-plugin -D`


2. 新建doc.md,在里面编写相关的md格式内容


3. 在入口文件 main.js 或其他文件中引入这个md文件,形成依赖

```

import code from './doc.md'

import 'highlight.js/styles/default.css'

document.body.innderHTML = code

```


4. 安装md转html库,用来解析md代码

`npm install marked -D`


5. 在loaders文件夹中,新建 custom-md-loader.js,并转换md文件的内容,同时安装`npm install highlight.js -D`来高亮代码

```

const marked = require('marked')

const hljs = require('highlight.js')

module.exports = function(content) {

    marked.setOptions({

        highlight: function(code, lang) {

            return hljs.highlight(lang, code).value

        }

    })

    const htmlContent = marked(content)

    // 注意:这里不能直接返回字符串,要么返回buffer、要么返回JavaScript的字符串

    const innerContent = '`'+htmlContent+'`'

    const moduleCode = `var code=${innerContent}; export default code;`

    return moduleCode

}

```


6. 在webpack中配置 html-webpack-plugin 插件,以及使用自定义loader

```

const path = require('path')

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

module.exports = {

    entry: './src/main.js',

    output: {

        filename: 'bundle.js',

        path: path.resolve(__dirname, './build')

    },

    module: {

        rules: [

            {

                test: /\.js$/i,

                use: [

                    {

                        loader: 'custom-loader01',

                        options: {

                            name: '张三',

                            age: '35'

                        }

                    },

                ]

            },

            // 自定义的可以解析md的loader

            {

                test: /\.md$/i,

                use: [

                    {

                        loader: 'custom-md-loader'

                    },

                ]

            }

        ]

    },

    resolveLoader: {

        modules: ['node_modules', './loaders']

    },

    plugins: [

        new HtmlWebpackPlugin()

    ]

}

```