編寫自己的Webpack Loader

相學長發表於2017-10-12

本文將簡單介紹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結構順序,我得每個檔案都再引一次headerfooter,沒法將他們作為一個單獨的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

明確程式碼可行後,我們得完善自己的功能點,我仔細想想,大概需要考慮如下功能:

  1. 針對每個載入的html,應該可以設定自己的layout檔案,而不是所有的html,都必須載入同一個layout。
  2. 替換的佔位符{{__content__}}也應該可以配置。

另外,還需要考慮一些異常的處理,如模板檔案找不到。

完善自身的需求後,我們又可以編寫程式碼了,這回我就不一行行闡述程式碼了,直接放連結:html-layout-loader

程式碼也比較簡單,算是實現了自己的基本需求,大家有興趣的話可以先看看readme的介紹。

寫在最後

當我們在遇到大問題時,首先想到的總是去搜搜看有沒有現成的解決方案,但現實卻難免是沒有解決方案。在這種情況下,我們也可以嘗試著去寫一些外掛、元件、或者一個通用化的解決方案,來解決自身的問題,同時對自己掌握一些知識也會有幫助。而且嘗試過後可能發現,它也沒那麼難嘛。

另外,如果這個loader,也對讀者們有幫助的話,請盡情使用,有什麼問題、想法可以提issue或PR。

--閱讀原文 --轉載請先經過本人授權-丁香園F2E @相學長

相關文章