編寫本身的Webpack Loader

本文將簡單介紹webpack loader,以及如何去編寫一個loader來知足自身的需求,從而也能提升對webpack的認識與使用,努力進階爲webpack配置工程師。javascript

Webpack Loader

webpack想必前端圈的人都知道了,大多數人也都或多或少的用過。簡單的說就是它可以加載資源文件,並對這些文件進行一些處理,諸如編譯、壓縮等,最終一塊兒打包到指定的文件中。能夠說,它做爲一個打包工具,在前端工程化浪潮中,起到了中流砥柱的做用。css

那webpack其中很是重要的一環就是,可以對加載的資源文件,進行一些處理。好比把less、sass文件編譯成css文件,負責這個處理過程的,就是webpack的loader。html

loader 用於對模塊的源代碼進行轉換。loader 可使你在 import 或"加載"模塊時預處理文件。所以,loader 相似於其餘構建工具中「任務(task)」,並提供了處理前端構建步驟的強大方法。前端

舉個稍微複雜的例子,vue-loader,它官網介紹以下:vue

vue-loader 是一個 Webpack 的 loader,能夠將指定格式編寫的 Vue 組件轉換爲 JavaScript 模塊。java

Vue組件默認分紅三部分,<template><script><style>,咱們能夠把一個組件要有的html,js,css寫在一個組件文件中,而vue-loader,會幫助咱們去處理這個vue組件,把其中的html,js,css分別編譯處理,最終打包成一個模塊。node

明確本身須要什麼Loader

咱們知道了webpack的強大依託於一個個強大的 loader(固然還有plugin,本文就不介紹了)。若是想真的玩溜webpack,咱們就必須掌握loader的使用。在咱們使用它們前,咱們得知道本身須要什麼loader。若是想編譯less,能夠用less-loader;想加載html文件並打包它內鏈的靜態文件,可使用html-loader。只要咱們想對文件進行處理時,咱們均可以去找想對應的loader。webpack

那麼問題來了,萬一找不到想要的loader該怎麼辦?git

好比我前幾天遇到了一個需求,我但願我加載的html文件,都嵌套在一個 layout.html 文件中。以下所示:github

<!-- 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 @相學長