開發一個可以加載 markdown 文件的加載器,以便可以在代碼中直接導入 md 文件。
markdown 一般是需要轉換爲 html 之後再呈現到頁面上的,所以我們希望導入 md 文件後,直接得到 markdown 轉換後的 html 字符串,如下圖所示:
└─ 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
<!-- ./src/about.md -->
# About
this is a markdown file.
// ./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
參數接收輸入,通過返回值輸出。
先嚐試打印一下 source,然後在函數的內部直接返回一個字符串 hello loader ~,具體代碼如下所示:
// ./markdown-loader.js
module.exports = source => {
// 加載到的模塊內容 => '# About\n\nthis is a markdown file.'
console.log(source)
// 返回值就是最終被打包的內容
return 'hello loader ~'
}
完成以後,我們回到 Webpack 配置文件中添加一個加載器規則,這裏匹配到的擴展名是 .md
,使用的加載器就是我們剛剛編寫的這個 markdown-loader.js 模塊,具體代碼如下所示:
// ./webpack.config.js
module.exports = {
entry: './src/main.js',
output: {
filename: 'bundle.js'
},
module: {
rules: [
{
test: /\.md$/,
// 直接使用相對路徑
use: './markdown-loader'
}
]
}
}
這裏的 use 中不僅可以使用模塊名稱,還可以使用模塊文件路徑。
配置完成後,打開命令行終端運行打包命令 npx webpack
(👈 運行這條命令之前要確保已經運行 npm i webpack webpack-cli --save-dev
命令),如下圖所示:
打印出來了 Markdown 文件內容(說明 Loader 函數的參數確實是文件的內容),同時也報錯了(可能還需要一個額外的加載器來處理當前加載器的結果),原因:
Webpack 加載資源文件的過程類似於一個工作管道,你可以在這個過程中依次使用多個 Loader,但是最終這個管道結束過後的結果必須是一段標準的 JS 代碼字符串。
解決的辦法:
- 直接在這個 Loader 的最後返回一段 JS 代碼字符串;
- 再找一個合適的加載器,在後面接着處理我們這裏得到的結果。
方法一
回到 markdown-loader
中,將返回的字符串內容修改爲 console.log('hello loader~')
,然後再次運行打包,此時 Webpack 就不再會報錯了,代碼如下所示:
// ./markdown-loader.js
module.exports = source => {
// 加載到的模塊內容 => '# About\n\nthis is a markdown file.'
console.log(source)
// 返回值就是最終被打包的內容
// return 'hello loader ~'
return 'console.log("hello loader ~")'
}
那此時打包的結果是怎樣的呢?
打開輸出的 bundle.js
,找到最後一個模塊(因爲這個 md 文件是後引入的),如下圖所示:
這個模塊裏面非常簡單,就是把我們剛剛返回的字符串直接拼接到了該模塊中。這也解釋了剛剛 Loader 管道最後必須返回 JS 代碼的原因,因爲如果隨便返回一個內容,放到這裏語法就不通過了。
實現 Loader 的邏輯
瞭解了 Loader 大致的工作機制過後,我們再回到 markdown-loader.js
中,接着完成需求。這裏需要 安裝
一個能夠將 Markdown 解析爲 HTML 的模塊,叫作 marked
。
安裝完成後,在 markdown-loader.js
中導入這個模塊,然後使用這個模塊去解析 source
。這裏解析完的結果就是一段 HTML 字符串,如果我們直接返回的話同樣會面臨 Webpack 無法解析模塊的問題,正確的做法是把這段 HTML 字符串拼接爲一段 JS 代碼。
此時我們希望返回的代碼是通過 module.exports
導出這段 HTML 字符串,這樣外界導入模塊時就可以接收到這個 HTML 字符串了。具體操作如下所示:
// ./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>"'
}
先通過 JSON.stringify()
將字段字符串轉換爲標準的 JSON 字符串,然後再參與拼接,這樣就不會有問題了。
我們回到命令行再次運行打包,打包後的結果就是我們所需要的了。
除了 module.exports
這種方式,Webpack 還允許我們在返回的代碼中使用 ES Modules 的方式導出,例如,我們這裏將 module.exports 修改爲 export default
,然後運行打包,結果同樣是可以的,Webpack 內部會自動轉換 ES Modules 代碼。
// ./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
}
方法二
剛剛說的第二種思路,在 markdown-loader
中直接返回 HTML 字符串,然後交給下一個 Loader 處理。這就涉及多個 Loader 相互配合工作的情況了。
回到代碼中,直接返回 marked 解析後的 HTML,代碼如下所示:
// ./markdown-loader.js
const marked = require('marked')
module.exports = source => {
// 1. 將 markdown 轉換爲 html 字符串
const html = marked(source)
return html
}
然後安裝一個處理 HTML 的 Loader,叫作 html-loader
,代碼如下所示:
// ./webpack.config.js
module.exports = {
entry: './src/main.js',
output: {
filename: 'bundle.js',
},
module: {
rules: [
{
test: /\.md$/,
use: [
'html-loader',
'./markdown-loader'
]
}
]
}
}
安裝完成過後回到配置文件,這裏同樣把 use
屬性修改爲一個數組,以便依次使用多個 Loader。不過同樣需要注意,這裏的執行順序是從後往前,也就是說我們應該把先執行的 markdown-loader 放在後面,html-loader 放在前面。
完成以後我們回到命令行終端再次打包,這裏的打包結果仍然是可以的。
至此,完成。