开发一个可以加载 markdown 文件的加载器,以便可以在代码中直接导入 md 文件。
markdown 一般是需要转换为 html 之后再呈现到页面上的,所以我们希望导入 md 文件后,直接得到 markdown 转换后的 html 字符串,如下图所示:

1 2 3 4 5 6 7 | └─ webpack-loader ······················· sample root dir ├── src ································· source dir │ ├── about.md ························ markdown module │ └── main.js ························· entry module ├── package.json ························ package file + ├── markdown-loader.js ·················· markdown loader └── webpack.config.js ··················· webpack config file |
1 2 3 4 | <!-- ./src/about.md --> # About this is a markdown file. |
1 2 3 4 | // ./src/main.js import about from './about.md' console.log(about); // 希望 about => '<h1>About</h1><p>this is a markdown file.</p>' |
每个 Webpack 的 Loader 都需要导出一个函数,这个函数就是我们这个 Loader 对资源的处理过程,它的输入就是加载到的资源文件内容,输出就是我们加工后的结果。我们通过
先尝试打印一下 source,然后在函数的内部直接返回一个字符串 hello loader ~,具体代码如下所示:
1 2 3 4 5 6 7 | // ./markdown-loader.js module.exports = source => { // 加载到的模块内容 => '# About\n\nthis is a markdown file.' console.log(source) // 返回值就是最终被打包的内容 return 'hello loader ~' } |
完成以后,我们回到 Webpack 配置文件中添加一个加载器规则,这里匹配到的扩展名是
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | // ./webpack.config.js module.exports = { entry: './src/main.js', output: { filename: 'bundle.js' }, module: { rules: [ { test: /\.md$/, // 直接使用相对路径 use: './markdown-loader' } ] } } |
这里的 use 中不仅可以使用模块名称,还可以使用模块文件路径。
配置完成后,打开命令行终端运行打包命令

打印出来了 Markdown 文件内容(说明 Loader 函数的参数确实是文件的内容),同时也报错了(可能还需要一个额外的加载器来处理当前加载器的结果),原因:
Webpack 加载资源文件的过程类似于一个工作管道,你可以在这个过程中依次使用多个 Loader,但是最终这个管道结束过后的结果必须是一段标准的 JS 代码字符串。

解决的办法:
- 直接在这个 Loader 的最后返回一段 JS 代码字符串;
- 再找一个合适的加载器,在后面接着处理我们这里得到的结果。
方法一
回到
1 2 3 4 5 6 7 8 | // ./markdown-loader.js module.exports = source => { // 加载到的模块内容 => '# About\n\nthis is a markdown file.' console.log(source) // 返回值就是最终被打包的内容 // return 'hello loader ~' return 'console.log("hello loader ~")' } |
那此时打包的结果是怎样的呢?
打开输出的

这个模块里面非常简单,就是把我们刚刚返回的字符串直接拼接到了该模块中。这也解释了刚刚 Loader 管道最后必须返回 JS 代码的原因,因为如果随便返回一个内容,放到这里语法就不通过了。
实现 Loader 的逻辑
了解了 Loader 大致的工作机制过后,我们再回到
安装完成后,在
此时我们希望返回的代码是通过
1 2 3 4 5 6 7 8 9 10 11 12 | // ./markdown-loader.js const marked = require('marked') module.exports = source => { // 1. 将 markdown 转换为 html 字符串 const html = marked(source) // html => '<h1>About</h1><p>this is a markdown file.</p>' // 2. 将 html 字符串拼接为一段导出字符串的 JS 代码 const code = `module.exports = ${JSON.stringify(html)}` return code // code => 'export default "<h1>About</h1><p>this is a markdown file.</p>"' } |
先通过
我们回到命令行再次运行打包,打包后的结果就是我们所需要的了。
除了
1 2 3 4 5 6 7 8 9 | // ./markdown-loader.js const marked = require('marked') module.exports = source => { const html = marked(source) // const code = `module.exports = ${JSON.stringify(html)}` const code = `export default ${JSON.stringify(html)}` return code } |
方法二
刚刚说的第二种思路,在
回到代码中,直接返回 marked 解析后的 HTML,代码如下所示:
1 2 3 4 5 6 7 8 | // ./markdown-loader.js const marked = require('marked') module.exports = source => { // 1. 将 markdown 转换为 html 字符串 const html = marked(source) return html } |
然后安装一个处理 HTML 的 Loader,叫作
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | // ./webpack.config.js module.exports = { entry: './src/main.js', output: { filename: 'bundle.js', }, module: { rules: [ { test: /\.md$/, use: [ 'html-loader', './markdown-loader' ] } ] } } |
安装完成过后回到配置文件,这里同样把
完成以后我们回到命令行终端再次打包,这里的打包结果仍然是可以的。
至此,完成。