手把手用代碼教你實現一個 webpack loader

什麼是 Loader

webpack 打包時只能處理 js 文件,對於其餘類型的文件如 jsx, css, scss, vue, png 等文件,須要專門的東西處理一下再傳入 webpack,這個東西就是 loadercss

loader 用於對模塊的源代碼進行轉換。loader 可使你在 import 或"加載"模塊時預處理文件。所以,loader 相似於其餘構建工具中「任務(task)」,並提供了處理前端構建步驟的強大方法。loader 能夠將文件從不一樣的語言(如 TypeScript)轉換爲 JavaScript,或將內聯圖像轉換爲 data URL。loader 甚至容許你直接在 JavaScript 模塊中 import CSS文件!

Loader 的開發

在開發本身 loader 以前,咱們得知道 loader 是啥html

loader 是導出爲一個函數的 node 模塊。該函數在 loader 轉換資源的時候調用。給定的函數將調用 loader API,並經過 this 上下文訪問。

說白了,loader 就是一個函數,接收源模塊,而後處理一番,再導出去,給下一個 loader 或者 webpack前端

module.exports = function(source) {
    // handle source
    ...
    return handled source
}

關於開發一個 loader 遵循的一些原則,你們能夠去看文檔,本文以一個處理 txt 文件的小例子來講明如何開發一個 loader。目錄結構以下vue

image.png

// webpack.config.js
const path = require('path')
module.exports = {
  mode: 'development',
  entry:  __dirname + "/src/app.js",
  output: {
    path: __dirname + "/dist",
    filename: "bundle.js"
  },
  module: {
    rules: [
      {
        test: /\.txt$/,
        use: [
          'text-loader'
        ]
      }
    ]
  }
}

loaders 文件中存放咱們的 txt-loader.jsnode

// txt-loader.js
module.exports = function(source) {
    console.log(source)
}

源文件 name.txtwebpack

// name.txt
hello [name]!

入口文件 app.jsweb

// app.js
const name = require('./name.txt')
console.log(name)

先執行走一波,終端執行數組

./node_modules/.bin/webpack

確定會報錯,由於咱們的 loader 尚未寫完,可是源文件內容已經打印出來了瀏覽器

image.png

報錯信息也說,這個 loader沒有返回 Buffer 或者 Stringsass

txt-loader 要作的事情就是將任何 .txt 文件中的 [name] 直接替換爲咱們想要的名字,而後返回包含默認導出文本的 JavaScript 模塊。

須要注意的是,咱們不能再 loader 裏面將這個名字寫死,而應該在使用 loader 的時候以配置的形式傳進去,咱們平時看到的 loader 通常都有個 options 選項,就是爲了傳些配置進去

{
    test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
    loader: 'url-loader',
    options: {
       limit: 10000,
       name: utils.assetsPath('img/[name].[hash:7].[ext]')
    }
},

咱們給 txt-loader 加個配置選項

// webpack.config.js
...
{
    test: /\.txt$/,
    use: {
        loader: path.resolve(__dirname, './src/loaders/txt-loader.js'),
        options: {
            name: 'Jay'
        }
    }
}
...

那咱們在 txt-loader.js 怎麼接收配置呢?webpack 提供了一個 loader 工具庫

// txt-loader.js
const loaderUtils = require('loader-utils')

module.exports = function(source) {
    this.cacheable && this.cacheable()
    const options = loaderUtils.getOptions(this) || {}
    console.log(options)
    
    source = source.replace(/\[name\]/g, options.name)
    console.log(source)
    
    return source
}

執行一下

image.png

發現咱們期待的結果打印出來了,可是仍是報錯了,報錯信息說還須要額外的 loader 去處理當前 loader 的結果。

有時咱們處理某種類型的文件須要多個 loader,這些 loader 的執行順序和 use 數組中 loader 書寫順序是相反的,如解析 scss 文件時

{//處理.scss文件
    test: /\.scss$/,
    use: [{
        loader: "style-loader" // creates style nodes from JS strings
    }, {
        loader: "css-loader" // translates CSS into CommonJS
    }, {
        loader: "sass-loader" // compiles Sass to CSS
    }]
},

上面例子 webpack 是先通過 sass-loader,而後將結果傳入 css-loader,最後再進入 style-loader

鏈路中間的 loader 返回什麼樣的結果都行,只要下一個接收的 loader 可以正常處理就行,可是最後一個調用 loader 的結果是須要傳入至 webpack 中,webpack 指望它返回 JS 代碼,以及可選的source map

注意:若是是處理順序排在最後一個的 loader,那麼它的返回值將最終交給 webpack 的 require,換句話說,它必定是一段可執行的 JS 腳本 (用字符串來存儲),更準確來講,是一個 node 模塊的 JS 腳本。
// 處理順序排在最後的 loader
module.exports = function (source) {
    // 這個 loader 的功能是把源模塊轉化爲字符串交給 require 的調用方
    return `module.exports = ${JSON.stringify(source)}`
}

本例處理 txt 文件只有一個 txt-loader,最終傳入至 webpack 中的是 hello Jay!,不是個可執行的 JS 腳本。最終代碼以下

module.exports = function(source) {
    this.cacheable && this.cacheable()
    const options = loaderUtils.getOptions(this) || {}
    source = source.replace(/\[name\]/g, options.name)
    return `module.exports = ${JSON.stringify(source)}`
}

dist 中生成的 bundle.js 文件放入瀏覽器控制檯中運行一下,能夠看到輸出 hello Jay!

image.png

咱們再看一個使用多個 loader 的例子,處理 html 文件並壓縮,解析 html 並使之成爲 JS 可執行的腳本的任務就交給現有的 html-loader,壓縮的任務就我們本身來實現,就叫 html-optimize-loader 吧。

修改一下 webpack.config.js

// webpack.config.js
...
module: {
    rules: [
        {
            test: /\.txt$/,
            use: {
                loader: 'name-loader',
                options: {
                    name: 'Jay'
                }
            }
        },
        {
            test: /\.html$/,
            use: ['html-loader',
            {
                loader: 'html-optimize-loader',
                options: {
                    comments: false
                }
            }]
        }
    ]
},
resolveLoader: {
// html-loader 在 'node_modules'
modules: ['node_modules', path.resolve(__dirname, './src/loaders')]
},
...

這裏咱們改爲多個 loader 配置的模式,也在咱們新加的 html-optimize-loader 中加入了配置,壓縮時是否保留註釋。

src 中新建 test.html 文件

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
</head>
<body>
    <!-- 這裏是 main 內容 -->
</body>
</html>

入口文件 app.js 改爲 test.html

const html = require('./test.html')
console.log(html)

loaders 文件中新建 html-optimize-loader.js

// hmtl-optimize-loader.js
const Minimize = require('minimize')
const loaderUtils = require('loader-utils')

module.exports = function (source) {
    var callback = this.async()
    this.cacheable && this.cacheable()

    var options = loaderUtils.getOptions(this) || {} 
    var minimize = new Minimize(options)
    console.log(source)
    console.log(minimize.parse(source))
    return minimize.parse(source, callback)
}

這裏 loader 咱們採用異步的方式,執行一下

image.png

發現 source 和壓縮後的 source 都打印出來了,這裏咱們直接將壓縮後 source 直接傳入 html-loader 中去處理了。你們能夠將 options 中的 comment 設成 true,發現註釋就會保留了,最終生成的 bundle 文件也能夠丟進瀏覽器的控制檯跑一下。

就這樣咯,下一篇寫實現一個 webpack plugin

相關文章
相關標籤/搜索