# 自定义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()
]
}
```