本文將簡單介紹webpack loader,以及如何去編寫一個loader來滿足自身的需求,從而也能提高對webpack的認識與使用,努力進階為webpack配置工程師。
Webpack Loader
webpack想必前端圈的人都知道了,大多數人也都或多或少的用過。簡單的說就是它能夠載入資原始檔,並對這些檔案進行一些處理,諸如編譯、壓縮等,最終一起打包到指定的檔案中。可以說,它作為一個打包工具,在前端工程化浪潮中,起到了中流砥柱的作用。
那webpack其中非常重要的一環就是,能夠對載入的資原始檔,進行一些處理。比如把less、sass檔案編譯成css檔案,負責這個處理過程的,就是webpack的loader。
loader 用於對模組的原始碼進行轉換。loader 可以使你在 import 或"載入"模組時預處理檔案。因此,loader 類似於其他構建工具中“任務(task)”,並提供了處理前端構建步驟的強大方法。
舉個稍微複雜的例子,vue-loader,它官網介紹如下:
vue-loader 是一個 Webpack 的 loader,可以將指定格式編寫的 Vue 元件轉換為 JavaScript 模組。
Vue元件預設分成三部分,<template>
、<script>
和 <style>
,我們可以把一個元件要有的html,js,css寫在一個元件檔案中,而vue-loader
,會幫助我們去處理這個vue元件,把其中的html,js,css分別編譯處理,最終打包成一個模組。
明確自己需要什麼Loader
我們知道了webpack的強大依託於一個個強大的 loader(當然還有plugin,本文就不介紹了)。如果想真的玩溜webpack,我們就必須掌握loader的使用。在我們使用它們前,我們得知道自己需要什麼loader。如果想編譯less,可以用less-loader
;想載入html檔案並打包它內鏈的靜態檔案,可以使用html-loader
。只要我們想對檔案進行處理時,我們都可以去找想對應的loader。
那麼問題來了,萬一找不到想要的loader該怎麼辦?
比如我前幾天遇到了一個需求,我希望我載入的html檔案,都巢狀在一個 layout.html 檔案中。如下所示:
<!-- layout.html -->
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Pure Web</title>
<meta name=viewport content="width=device-width,initial-scale=1,maximum-scale=1,minimum-scale=1,user-scalable=no">
</head>
<body>
<header>This is Header</header>
<!-- 我希望我webpack載入的html,都會被巢狀在這個{{__content__}}部分 -->
{{__content__}}
<footer>This is footer</header>
</body>
</html>複製程式碼
這樣如果是編寫多頁應用,我就只需要編寫唯一不一樣的中心內容,而把網站公共的部分作為layout抽離開來。
可惜html-loader
它只能幫我在一個html檔案中去載入另外一個html檔案,像這樣:
<body>
${require('@/htmls/header.html')}
${require('@/htmls/index.html')}
${require('@/htmls/footer.html')}
</body>複製程式碼
這樣雖然能抽離公共部分,但我依舊需要在每個html檔案中去引用,而且為了保證html結構順序,我得每個檔案都再引一次header
,footer
,沒法將他們作為一個單獨的layout
來引入。所以它並不完全符合我的需求。
那該怎麼辦,我們沒有辦法,只能自己動手寫啊。
動手寫一個webpack loader
首先,我們要先閱讀一遍webpack官網的介紹:如何編寫一個loader?
看完後,我們能知道,loader本質就是接收字串(或者buffer),再返回處理完的字串(或者buffer)的過程。webpack會將載入的資源作為引數傳入loader方法,交於loader處理,再返回。
在我這個需求中,就是將我載入的html,套在我設定的layout中,再將這個處理完的html返回。大致的程式碼就是這樣:
// {string} source: 載入的html的字串值
module.exports = function (source) {
return getLayoutHtml().replace('{{__content__}}', source)
}複製程式碼
簡單思考後,發現可行,那麼開始編寫。
開始嘗試
所以,我們第一步,只要實現一個getLayoutHtml
方法,能得到設定的layout.html檔案就好。仔細想想,layout檔案應該是通過配置宣告的,然後在loader裡去根據配置,呼叫node的api去載入檔案就好。
查閱node與webpack文件,我們可以通過loader-utils來獲取loader的配置項。通過node的fs.readFileSync去載入檔案,那我們的程式碼大概可以這樣寫:
module.exports = function (source) {
const options = loaderUtils.getOptions(this)
const layoutHtml = fs.readFileSync(options.layout, 'utf-8')
return layoutHtml.replace('{{__content__}}', source)
}複製程式碼
webpack的config增加如下loader配置:
{
test: /\.(html)$/,
loader: 'html-layout-loader',
include: htmlPath, // the htmls you want inject to layout
options: {
layout: layoutHtmlPath // the path of default layout html
}
}複製程式碼
難以置信,這樣就完成一個基礎的html-layout-loader
了。當然,這才是真正的開始,真正讓loader變得可用,好用。我們還需要考慮很多情況。
完善自己的loader
明確程式碼可行後,我們得完善自己的功能點,我仔細想想,大概需要考慮如下功能:
- 針對每個載入的html,應該可以設定自己的layout檔案,而不是所有的html,都必須載入同一個layout。
- 替換的佔位符
{{__content__}}
也應該可以配置。
另外,還需要考慮一些異常的處理,如模板檔案找不到。
完善自身的需求後,我們又可以編寫程式碼了,這回我就不一行行闡述程式碼了,直接放連結:html-layout-loader。
程式碼也比較簡單,算是實現了自己的基本需求,大家有興趣的話可以先看看readme的介紹。
寫在最後
當我們在遇到大問題時,首先想到的總是去搜搜看有沒有現成的解決方案,但現實卻難免是沒有解決方案。在這種情況下,我們也可以嘗試著去寫一些外掛、元件、或者一個通用化的解決方案,來解決自身的問題,同時對自己掌握一些知識也會有幫助。而且嘗試過後可能發現,它也沒那麼難嘛。
另外,如果這個loader
,也對讀者們有幫助的話,請盡情使用,有什麼問題、想法可以提issue或PR。