关于webpack:webpack篇手写常见loader

loader 是导出为一个函数的 node 模块。该函数在 loader 转换资源的时候调用。给定的函数将调用 loader API,并通过 this 上下文拜访。

Webpack的配置离不来 loader,官网也有对于如何编写一个loader的文档介绍,这篇文章会通过手写一些常见的loader,加深对loader的意识,进步工作中的开发效率。

导出loader

loader是一个函数,承受匹配到的文件资源字符串和SourceMap,咱们能够通过批改文件内容的字符串返回给下个一loader解决:

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

筹备工作

为了不便咱们编写loader,咱们先筹备好webpack环境:

生成一份 package.json:

npm init -y

装置webpack:

npm install webpack webpack -D

创立webpack.config.js文件,并输出以下内容:

// 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命令:

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

配置别名

咱们晓得,webpack 默认会到 node_modules 外面找对应的loader,这样不不便咱们调试,咱们能够通过给webpack.config.js增加resolveLoader属性,将loader指向咱们创立的loaders文件夹

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

创立loaders文件夹,外面将寄存咱们本人编写的loader

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;

评论

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

这个站点使用 Akismet 来减少垃圾评论。了解你的评论数据如何被处理