webpack篇-手寫常見loader

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

Webpack的配置離不來 loader,官方也有關於如何編寫一個loader的文檔介紹,這篇文章會經過手寫一些常見的loader,加深對loader的認識,提升工做中的開發效率。css

導出loader

loader是一個函數,接受匹配到的文件資源字符串和SourceMap,咱們能夠經過修改文件內容的字符串返回給下個一loader處理:node

module.exports = function(source,map){
    return source;
}

準備工做

爲了方便咱們編寫loader,咱們先準備好webpack環境:webpack

生成一份 package.json:web

npm init -y

安裝webpack:npm

npm install webpack webpack -D

建立webpack.config.js文件,並輸入如下內容:json

// webpack.config.js
const path = require('path')

module.exports = {
  mode: 'development',
  entry: './src/index.js',
  output: {
    filename: 'bundle.js',
    path: path.resolve(__dirname, 'dist')
  }
}

package.json加入scripts命令:babel

"scripts": {
    "build": "webpack",
    "dev": "webpack --watch"
  },

配置別名

咱們知道,webpack 默認會到 node_modules 裏面找對應的loader,這樣不方便咱們調試,咱們能夠經過給webpack.config.js添加resolveLoader屬性,將loader指向咱們建立的loaders文件夾app

...
  resolveLoader: {
    modules: [
      path.resolve(__dirname, "node_modules"),
      path.resolve(__dirname, "./loaders"),
    ],
  }
...

建立loaders文件夾,裏面將存放咱們本身編寫的loaderless

mkdir loaders

另外經過resolveLoader.alias也能配置loader別名:異步

resolveLoader: {
    alias: {
      loader1: resolve(__dirname, "./loaders/loader1.js"),
      loader2: resolve(__dirname, "./loaders/loader2.js"),
      loader3: resolve(__dirname, "./loaders/loader3.js"),
    },
  },
  module:{
      rules:[
          {
              test:/\.(js)$/,
              use:["loader1.js","loader2.js","loader3.js]
          }
      ]
  }

第一個 loader

先寫一個簡單的loader練練手,前面說到loader能夠替換目標資源文件的內容,這裏我要把項目裏面的console.log都幹掉,畢竟項目上線的時候控制檯出現調試內容不合適。

loaders文件夾下新建cleanlog-loader.js,輸入下面內容:

module.exports = function(source){
    return source.replace(/console\.log\(.*\);?\n/g, '');
}

webpack.config.js添加配置:

module:{
    rules:[{
        test: /\.(js)$/,
        use:"cleanlog-loader"
    }]
}

src文件夾下新建index.js寫點console內容,而後控制檯執行npm run build,能夠發現編譯後的文件dist/bundle.js已經沒有console信息了。

banner-loader

banner-loader能夠在腳本文件添加註釋信息,配置方式以下:

module:{
    rules:[{
        test: /\.(js)$/,
        use:{
            loader:"banner-loader",
            options:{
                text:"/**** build from chenwl ****/",
            }
        }
    }]
}

這裏有兩個地方須要考慮:

  • 獲取配置信息 loader-utils
  • 校驗配置參數是否正確 schema-utils

loaders 文件夾下建立banner-loader.js,輸入下面內容:

const fs = require("fs");
const {resolve} = require("path");
const loaderUtils = require("loader-utils");
const { validate } = require("schema-utils");

module.exports = function (source) {
    // 獲取配置參數
    let options= loaderUtils.getOptions(this);
    let schema = {
      type: "object",
      properties: {
        text: {type: "string"}
     } 
    }
    // 校驗參數是否正確
    validate(schema, options,"banner-loader")

    return `${options.text} ${source}`;
}

固然也能夠經過讀取文件內容寫入,新增配置參數 filename,對模板文件進行讀取:

if(options.filename){
        // 依賴某個文件變化,作到實時更新;
        this.addDependency(path.resolve(__dirname, `../${options.filename}`))
        return fs.readFileSync(options.filename, "utf-8") + source;
    }

利用addDependency,若是目標文件發生變化,能夠在觀察模式(watch mode)下重編譯,用npm run dev 啓動並修改filename對應的文件試試

babel-loader

babel-loader依賴@babel-core@babel/preset-env,經過npm先安裝:

npm install @babel-core @babel/preset-env -D

webpack-config.js添加rules:

{
    test: /\.(js)$/,
    use: [
        {
            loader: "babel-loader",
            options: {
                presets: ["@babel/preset-env"],
            },
        },
    ]
}

能夠經過this.async方法在loader中編寫異步代碼:

// babel-loader.js
const loaderUtils = require("loader-utils");
const babel = require("@babel/core");

module.exports = function(source){
    const options = loaderUtils.getOptions(this);
    const cb = this.async(); // 異步函數
    babel.transform(source,{
        ...options,
        sourceMaps:true
    },function(err,result){
        cb(err, result.code)
    })
}

style-loader 和 less-loader

// less-loader
let less = require("less");
module.exports = function (source) {
    let cssStr = "";
    // 用less轉成css
    less.render(source,function(error,result) {
        if(!error){
            cssStr = result.css
        }
    });
    return cssStr
}
// style-loader
module.exports = styleLoader(source) {
    // js 字符串,生成style標籤插入到模板文件中
    let code = `
        let styleEl = document.createElement("style");
        styleEl.innerHTML = ${JSON.stringify(source)};
        document.head.appendChild(styleEl);
    `;
    return code.replace(/\/n/,"");
}

file-loader

咱們知道 webpack 是識別不了js之外的其它文件的,因此file-loader須要設置loader.raw = true,讓 loader 知道如今處理的是二進制的內容:

const loaderUtils = require("loader-utils");

function fileLoader(source) {
  // interpolate 插值
  let fileUrl = loaderUtils.interpolateName(this, "[hash].[ext]", {
    content: source,
  });

  this.emitFile(fileUrl,source);
  // 轉換後的Buffer最終是要被插入到頁面中,返回類型只能是 buffer 或 string
  // fileUrl 記得加引號,否則會報錯哦
  return `module.exports = '${fileUrl}'`
}

// loader 處理的是二進制的內容
fileLoader.raw = true;

module.exports = fileLoader;

url-loader

url-loader目的是將小圖轉成base64編碼,不然就用file-loader處理:

// url-loader.js
const loaderUtils = require("loader-utils");
const mime = require("mime");

function urlLoader(source) {
    let {limit} = loaderUtils.getOptions(this);
    if(limit > source.length){
        let code = `data:${mime.getType(this.resourcePath)};base64,${source.toString('base64')}`
        return `module.exports = "${code}"`
    }else{
        return require("./file-loader").call(this, source)
    }
}
urlLoader.raw = true;
module.exports = urlLoader;
相關文章
相關標籤/搜索