前言
什么是loader
loader本质上就是一个node模块,exports了一个函数,这个函数可以把接受进来的东西进行转换,返回在返回出去
为什么要使用loader
webpack只能识别js和json文件,无法识别其他类型的文件,所以需要loader来加载其他类型的文件
手写一个markdown-loader
webpack配置
最主要的是要在webpack中的loader中配置能够加载markdown文件的loader
// webpack.config.js 入口文件
...
{
test: /\.md$/,
use: path.resolve("./src/myLoader/index.js"),
options: { flag: true, },
},
loader的逻辑
// myloader/index.js
const { getOptions } = require('loader-utils')
const MarkdownIt = require('markdown-it')
module.exports = function (source) {
const options = getOptions(this) || {}
// 这里可以拿到传入的选项 flag: true
const md = new MarkdownIt({
html: true,
...options,
})
let html = md.render(source)
html = `module.exports = ${JSON.stringify(html)}`
this.callback(null, html) // 等同于直接 return(html);
}
option
Webpack 提供了loader-utils工具, 可以拿到传入的选项
结果返回callback
this.callback(
// 当无法转换原内容时,给 Webpack 返回一个 Error
err: Error | null,
// 原内容转换后的内容
content: string | Buffer,
// 用于把转换后的内容得出原内容的 Source Map,方便调试
sourceMap?: SourceMap,
// 如果本次转换为原内容生成了 AST 语法树,可以把这个 AST 返回,以方便之后需要 AST 的 Loader 复用该 AST,以避免重复生成 AST,提升性能
abstractSyntaxTree?: AST
);
缓存
如果为每个构建重新执行重复的转换操作,这样Webpack
构建可能会变得非常慢。
Webpack
默认会缓存所有loader
的处理结果,也就是说,当待处理的文件或者依赖的文件没有变化时,不会再次调用对应的loader
进行转换操作
一般默认开启缓存,如果不想Webpack
这个loader
进行缓存,也可以关闭缓存
const { getOptions } = require('loader-utils')
const MarkdownIt = require('markdown-it')
module.exports = function (source) {
// this.cacheable && this.cacheable();
// 一般会自动开启缓存,这里手动关闭
this.cacheable(false);
const options = getOptions(this) || {}
// 这里可以拿到传入的选项 flag: true
const md = new MarkdownIt({
html: true,
...options,
})
let html = md.render(source)
html = `module.exports = ${JSON.stringify(html)}`
this.callback(null, html) // 等同于直接 return(html);
}
同步与异步
有时候转换需要请求,如果不用异步需要等待请求结果返回,请求会堵塞构建,导致构建缓慢
const { getOptions } = require('loader-utils')
const MarkdownIt = require('markdown-it')
module.exports = function (source) {
var callback = this.async() // someAsyncOperation 代表一些异步的方法
someAsyncOperation(source, function (err, result, sourceMaps, ast) {
// 通过 callback 返回异步执行后的结果
callback(err, result, sourceMaps, ast)
})
}
入口的逻辑
// index.tsx 入口文件
import mdHtml from './test.md'
const content = document.createElement('div')
content.className = 'content'
content.innerHTML = mdHtml
document.body.appendChild(content)
md文件
// test.md