『Webpack进阶系列』—— 手写一个loader

121 阅读2分钟

前言

什么是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

效果

image.png