背景
前几篇的 clean-log-loader、insert-comment-loader 处理的都是 JS 文件。但 Loader 的能力远不止于此:任何能被 import 的文件类型,都可以交给自定义 loader 处理——Markdown、SVG、模板、配置……这篇就用 Markdown 转 HTML 讲清「处理文件的 loader」是怎么工作的。
关键认知:Loader 产出的必须是 JS 模块
Webpack 的模块系统只认识 JavaScript。当你写:
import post from './post.md';
Webpack 并不理解 .md,它会先找到匹配 .md 的 loader,把文件内容交给它,然后期望 loader 返回一段合法的 JS 代码。所以文件 loader 的核心任务是「翻译」:
Markdown 文本 ──loader──▶ module.exports = '<h1>...</h1>'
浏览器最终拿到的是字符串,而不是 Markdown 本身。
实现
安装一个 Markdown 解析器:
npm install -D marked
新建 loaders/markdown-loader.js:
const { marked } = require('marked');
const schema = {
type: 'object',
properties: {
wrapper: { type: 'string' },
},
additionalProperties: false,
};
module.exports = function markdownLoader(source) {
const { wrapper } = this.getOptions(schema) || {};
let html = marked.parse(source);
if (wrapper) {
html = `<${wrapper}>${html}</${wrapper}>`;
}
return `module.exports = ${JSON.stringify(html)};`;
};
逐段解读
-
source 是文件内容 和 JS loader 一样,
source就是.md的原始文本。区别只在于我们不再做语法分析,而是整体编译。 -
编译成 HTML
marked.parse(source)输出 HTML 字符串。 -
options 控制包裹元素
wrapper允许把结果再包一层,比如article。依然用this.getOptions(schema)做校验。 -
包装成 JS 模块 最关键的一步:
return \module.exports = ${JSON.stringify(html)};`。这里必须用JSON.stringify` 把 HTML 转义成合法的 JS 字符串字面量,否则引号、换行、反斜杠都会破坏语法。
配置与使用
{
test: /\.md$/i,
use: [
{
loader: path.resolve(__dirname, 'loaders/markdown-loader.js'),
options: { wrapper: 'article' },
},
],
}
然后在代码里像普通模块一样使用:
import post from './post.md';
const article = document.createElement('div');
article.className = 'article';
article.innerHTML = post;
document.body.appendChild(article);
构建后,post.md 会被编译成一段导出 HTML 字符串的模块,post 就是渲染好的内容。
与 JS Loader 的区别
| 维度 | JS Loader | 文件 Loader |
|---|---|---|
| 输入 | JS 源码 | 任意文件内容 |
| 处理 | 解析、增删改 AST | 编译、转换、序列化 |
| 输出 | JS 源码 | 一段导出数据的 JS 模块 |
| 常见搭配 | babel-loader | markdown-loader、svg-inline-loader |
注意事项
- 导出格式要与导入方式匹配:示例导出 CommonJS,
import会被 Webpack 正确互操作。若目标环境严格区分 ESM,可改成export default。 - 注意 XSS:
innerHTML渲染用户可控的 Markdown 有风险,生产环境应对 HTML 做净化(如 DOMPurify)。 - 解析器配置:
marked支持 GFM、代码高亮等配置,可作为 loader 的 options 继续扩展。 - 缓存:Loader 默认是可缓存的,只要输入和 options 不变,Webpack 会复用结果。
- sourcemap:这里做了「内容替换」,行号意义不大,通常无需额外处理;若做行级转换,记得透传
this.sourceMap。
小结
处理文件的 loader,本质上是一个「格式翻译器」:把非 JS 文件编译成一段可 import 的 JS 模块。理解这一点后,你可以用它接入 Markdown、模板引擎、配置文件,甚至让 import 一份 .sql 或 .graphql 变成可能。
套路只有三步:读取 source → 转换内容 → 包装成 JS 模块导出。