編寫一個Loader

在前面webpack 的配置中,我們使用了許多loader, css-loader, style-loader, vue-loader, file-loader, ...

下面我們來自己寫一個Loader.

我們先創建一個文件夾,make-loader ,接着使用 npm init -y 命令對這個文件夾進行node 項目初始化。

然後我們再在項目中安裝webpack 和 webpack-cli

npm install webpack webpack-cli --save-dev

然後我們在項目跟目錄下創建目錄src,在 src 下創建 index.js 文件。

先隨意寫一個console 語句。

console.log('hello')

然後一般我們的配置打包流程是,在項目跟目錄下新建文件webpack.config.js 文件。然後,大概寫一下基本的配置項如下。(真實項目的配置項是很多的,這兒將要講的是loader 故儘量把其他的細節略掉)

const path = require('path')

module.exports = {
    entry: {
        main: './src/index.js'
    },
    output: {
        path: path.resolve(__dirname, 'dist'),
        filename: '[name].js'
    }
}

然後在 package.json 中加入script 命令:

  "scripts": {
    "build": "webpack"
  },

然後,執行npm run build 命令就可以打包index.js 文件了。

好,接下來,我們可以編寫一些loader,讓打包過程進行一些變更。

比如,我們希望在打包過程中,一旦引用js 文件的時候,遇到 ‘hello’ 字符串 就變成 ‘hello user’ 。這個時候,我們就可以通過Loader 進行修改。

那麼,我們開始寫一下 Loader 代碼。在項目根目錄下,創建目錄 loaders ,在 loaders 目錄下創建文件 replaceLoader.js。

Loader 實際上是一個函數,因此我們可以使用module.export 導出一個函數(不要使用箭頭函數,因爲在函數內部要使用this,webpack 在調用Loader 時,this 會指向調用函數的那個上下文,那個this包含了一些方法,函數內部可能需要使用。而箭頭函數的this 是與調用時無關的,取決於定義的位置)

下面就是這個Loader 的代碼實現,其中loader 接收一個參數,是引入文件的源代碼或者內容。

module.exports = function (source) {
    return source.replace('hello', 'hello user')
}

那我們在打包的時候,如何使用自己的Loader 呢。

我們打開項目的webpack.config.js 新增module.rules 如下。

const path = require('path')

module.exports = {
    mode: 'development',
    entry: {
        main: './src/index.js'
    },
    module: {
        rules: [{
            test: /\.js$/,
            use: [path.resolve(__dirname, './loaders/replaceLoader.js')]
        }]
    },
    output: {
        path: path.resolve(__dirname, 'dist'),
        filename: '[name].js'
    }
}

然後運行打包命令。就會發現,打包代碼中 ‘hello’ 變爲了 ‘hello user’ 。

以上就完成了一個最簡單的Loader 的例子。

之前,我們在使用某些loader 時,就配置了參數, 比如 url-loader 。那麼這兒,我們也可以試試在Loader 中配置參數。下面是webpack.config.js 中,向Loader 中配置一些參數,比如一個名字。

const path = require('path')

module.exports = {
    mode: 'development',
    entry: {
        main: './src/index.js'
    },
    module: {
        rules: [{
            test: /\.js$/,
            use: [
                {
                    loader: path.resolve(__dirname, './loaders/replaceLoader.js'),
                    options: {
                        name: 'Administrator'
                    }
                }
            ]
        }]
    },
    output: {
        path: path.resolve(__dirname, 'dist'),
        filename: '[name].js'
    }
}

那麼,在使用這個Loader 打包的時候,就可以把 options 中的配置項傳遞給 Loader。注意,這個並不是通過函數參數傳遞的,而是在函數的上下文中的屬性中保存,如下在this.query 裏。

module.exports = function (source) {
    console.log(this.query)
    return source.replace('hello', 'hello user')
}

好的,下面我們將Loader 代碼改一下。

module.exports = function (source) {
    return source.replace('hello', 'hello ' + this.query.name)
}

Nice!

我們翻翻官網 webpack documentation > API > Loader api

https://webpack.docschina.org/api/loaders/#this-query

我們翻到this.query 部分,官網提示,可以使用 loader-utils 中的getOptions 來提前配置中的option。當沒配置options 時,使用query 字符串作爲參數調用時,需要。

那麼,我們可以試一下,先下載loader-utils 

npm install loader-utils --save-dev

然後在loader 代碼中,引入,並使用如下。

const loaderUtils = require('loader-utils')

module.exports = function (source) {
    const options = loaderUtils.getOptions(this)
    return source.replace('hello', 'hello ' + options.name)
}

好。還有一個比較常用的 Loader 中的功能,this.callback()

當我們在一個Loader 中將source 進行處理並返回的同時,也對其source map 進行了處理,並希望也返回出去source map的話。一個return 語句就不夠了,就會需要this.callback() 。如下。(this.callback 第三個參數應該是source map,這兒沒有這個因此就填了source...)

const loaderUtils = require('loader-utils')

module.exports = function (source) {

    const options = loaderUtils.getOptions(this)
    const result = source.replace('hello', 'hello ' + options.name)
    this.callback(null, result, source)
}

有時,在Loader 中要進行一些異步操作。如下。這時我們需要使用this.async

const loaderUtils = require('loader-utils')

module.exports = function (source) {
    const options = loaderUtils.getOptions(this)
    const callback = this.async()
    setTimeout(() => {
        const result = source.replace('hello', 'hello ' + options.name)
        callback(null, result)
    }, 1000)
}

翻閱官網:

下面,我們在項目的loaders 目錄下,再創建一個replaceLoaderAsync.js ,讓它異步處理js 代碼(將hello 改爲hello Administrator),如下

const loaderUtils = require('loader-utils')

module.exports = function (source) {
    const options = loaderUtils.getOptions(this)
    const callback = this.async()
    setTimeout(() => {
        const result = source.replace('hello', 'hello ' + options.name)
        callback(null, result)
    }, 1000)
}

然後,再將replaceLoader.js 同步處理js 代碼,如下

module.exports = function (source) {
    const result = source.replace('Administrator', 'world')
    this.callback(null, result)
}

我們想先讓異步的loader 將字符串改掉,然後同步的loader 又將字符串再改一下。

那我們使用這兩個Loader 的時候要注意一下使用順序。先後順序:從右到左。因此在webpack.config.js 中我們這樣配置。

const path = require('path')

module.exports = {
    mode: 'development',
    entry: {
        main: './src/index.js'
    },
    module: {
        rules: [{
            test: /\.js$/,
            use: [
                path.resolve(__dirname, './loaders/replaceLoader.js'),
                {
                    loader: path.resolve(__dirname, './loaders/replaceLoaderAsync.js'),
                    options: {
                        name: 'Administrator'
                    }
                }
            ]
        }]
    },
    output: {
        path: path.resolve(__dirname, 'dist'),
        filename: '[name].js'
    }
}

上面,尋找自己寫的loader 每次都用path 查找,有些麻煩,可以配置webpack的resolveLoader , 讓webpack 來依據配置去尋找。如下。(resolveLoader 的配置表示,遇到loader 先去node_modules 中查找,沒有就會去./loaders 中查找)

const path = require('path')

module.exports = {
    mode: 'development',
    entry: {
        main: './src/index.js'
    },
    resolveLoader: {
        modules: ['node_modules', './loaders']
    },
    module: {
        rules: [{
            test: /\.js$/,
            use: [
                'replaceLoader',
                {
                    loader: 'replaceLoaderAsync',
                    options: {
                        name: 'Administrator'
                    }
                }
            ]
        }]
    },
    output: {
        path: path.resolve(__dirname, 'dist'),
        filename: '[name].js'
    }
}

Loader 是很有用的,能夠在打包的時候對整個項目進行配置修改或者是包裝。總之,以後繼續實踐!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章