編寫自己的Webpack Loader

本文將簡單介紹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 -->n<!DOCTYPE html>n<html>n<head>n <meta charset="utf-8">n <title>Pure Web</title>n <meta name=viewport content="width_=device-width,initial-scale=1,maximum-scale=1,minimum-scale=1,user-scalable=no">n</head>n<body>n <header>This is Header</header>nn <!-- 我希望我webpack載入的html,都會被嵌套在這個{{__content__}}部分 -->n {{__content__}}nn <footer>This is footer</header>n</body>n</html>n

這樣如果是編寫多頁應用,我就只需要編寫唯一不一樣的中心內容,而把網站公共的部分作為layout抽離開來。

可惜html-loader它只能幫我在一個html文件中去載入另外一個html文件,像這樣:

<body>n ${require(@/htmls/header.html)}n ${require(@/htmls/index.html)}n ${require(@/htmls/footer.html)}n</body>n

這樣雖然能抽離公共部分,但我依舊需要在每個html文件中去引用,而且為了保證html結構順序,我得每個文件都再引一次header,footer,沒法將他們作為一個單獨的layout來引入。所以它並不完全符合我的需求。

那該怎麼辦,我們沒有辦法,只能自己動手寫啊。

動手寫一個webpack loader

首先,我們要先閱讀一遍webpack官網的介紹:如何編寫一個loader?

看完後,我們能知道,loader本質就是接收字元串(或者buffer),再返回處理完的字元串(或者buffer)的過程。webpack會將載入的資源作為參數傳入loader方法,交於loader處理,再返回。

在我這個需求中,就是將我載入的html,套在我設定的layout中,再將這個處理完的html返回。大致的代碼就是這樣:

// {string} source: 載入的html的字元串值nmodule.exports = function (source) {n return getLayoutHtml().replace({{__content__}}, source)n}n

簡單思考後,發現可行,那麼開始編寫。

開始嘗試

所以,我們第一步,只要實現一個getLayoutHtml方法,能得到設定的layout.html文件就好。仔細想想,layout文件應該是通過配置聲明的,然後在loader里去根據配置,調用node的api去載入文件就好。

查閱node與webpack文檔,我們可以通過loader-utils來獲取loader的配置項。通過node的fs.readFileSync去載入文件,那我們的代碼大概可以這樣寫:

module.exports = function (source) {n const options = loaderUtils.getOptions(this)n const layoutHtml = fs.readFileSync(options.layout, utf-8)n return layoutHtml.replace({{__content__}}, source)n}n

webpack的config增加如下loader配置:

{n test: /.(html)$/,n loader: html-layout-loader,n include: htmlPath, // the htmls you want inject to layoutn options: {n layout: layoutHtmlPath // the path of default layout htmln }n}n

難以置信,這樣就完成一個基礎的html-layout-loader了。當然,這才是真正的開始,真正讓loader變得可用,好用。我們還需要考慮很多情況。

完善自己的loader

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

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

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

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

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

寫在最後

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

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

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


推薦閱讀:

webpack之loader和plugin簡介
webpack打包之 緩存
webpack增量打包

TAG:前端开发 | webpack |