编写一个 Webpack 的 loader

2022-08-15 08:29:16 浏览数 (1)

使用Webpack往往离不开loader的安装配置,手写一个loader其实非常简单,类似手写一个功能函数,下面我们来实现一个替换字符串的loader

初始化项目

创建一个根目录mack-loader,此目录下 npm init -y生成默认的package.json文件 ,在文件中配置打包命令

代码语言:javascript复制
"scripts": {
  "build": "webpack"
}

之后npm i -D webpack webpack-cli,安装完webpack,在根目录 创建配置文件webpack.config.js

代码语言:javascript复制
const path = require('path')

module.exports = {
  mode: 'development', // 先设置为development,不压缩代码,方便调试
  entry: {
    main: './src/index.js'
  },
  output: {
    filename: '[name].js',
    path: path.resolve(__dirname, 'dist')
  }
}

在根目录创建一个src目录,里面创建index.js,输入console.log('hello, world')

之后运行npm run build即可打包项目,初始化项目完成

编写 replace-loader

根目录创建loaders文件夹,里面创建replaceLoader.js

代码语言:javascript复制
const loaderUtils = require('loader-utils');

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

这里我们采用官方推荐的loader-utils读取options配置,也可用this.query获取配置对象,name是我们在loader配置项输入的字段名,source是源文件内容,最后需要返回,注意这里不能使用箭头函数,否则this指向会有错误,之后便可在webpack.config.js配置文件使用这个loader

代码语言:javascript复制
  module: {
    rules: [{
      test: /.js$/,
      use: {
        loader: path.resolve(__dirname, './loaders/replaceLoader.js'),
        options: {
          name: 'echo'
        }
      }
    }]
  }

效果是会把world替换为name中得字符串,npm run build 后在main.js里面则可以看到此效果

loader 返回更多内容

官方文档配置的 API 中有loader的许多API ,除了this.query,常用的还有this.callback

代码语言:javascript复制
this.callback(
  err: Error | null,
  content: string | Buffer,
  sourceMap?: SourceMap, // 可选参数,返回source-map
  meta?: any // 可选参数,返回meta
);

可使用此API替代return

代码语言:javascript复制
const loaderUtils = require('loader-utils');

module.exports = function (source) {
  const options = loaderUtils.getOptions(this);
  const result = source.replace('world', options.name);
  // return result;
  this.callback(null, result);
}

loader 中编写异步代码

loader中编写异步代码需要用this.async,我们可以再实现一个异步loader,创建replaceLoderAsync.js

代码语言:javascript复制
const loaderUtils = require('loader-utils');

module.exports = function (source) {
  const options = loaderUtils.getOptions(this);
  const callback = this.async()

  setTimeout(() => {
    const result = source.replace('world', options.name);
    callback(null, result)
  }, 1000)
}

其中this.async返回的是this.callback,因此可以当做return来使用,将replaceLoder.js中的代码改为

代码语言:javascript复制
module.exports = function (source) {
  const result = source.replace('echo', 'world');
  this.callback(null, result)
}

我们实现先调用异步loader,将world改为echo,之后再调用同步loaderecho改为world,在配置文件的相应配置为

代码语言:javascript复制
const path = require('path')

module.exports = {
  mode: 'development', // 先设置为development,不压缩代码,方便调试
  entry: {
    main: './src/index.js'
  },
  output: {
    filename: '[name].js',
    path: path.resolve(__dirname, 'dist')
  },
  resolveLoader: {
    // 会依次在node_modules、loaders文件夹中查找是否存在对应loader
    modules: ['node_modules', './loaders'] 
  },
  module: {
    rules: [{
      test: /.js$/,
      use: [{
        loader: 'replaceLoader.js'
      },{
        loader: 'replaceLoaderAsync.js',
        options: {
          name: 'echo'
        }
      }]
    }]
  }
}

之后运行npm run build即可在distmain.js验证效果

编写loader的应用场景

  1. 监控前端方法错误:可以自己编写loader检测业务代码中含有function关键字时自动用try...catch...包含代码块捕获错误,可以避免自己手写try...catch...导致的业务代码的臃肿
  2. 实现网站的中英文替换:可以将文字用占位符包裹,检测到占位符则根据环境变量替换为中英文,伪代码如下
代码语言:javascript复制
module.exports = function (source) {
  if(Node全局变量 === '中文') {
    source.replace('{{title}}', '中文标题')
  } else {
    source.replace('{{title}}', 'english title')
  }

  const result = source.replace('echo', 'world');
  this.callback(null, result)
}

总结

完整的代码可以参考我的github项目,欢迎star:https://github.com/zyqq/make-loader

0 人点赞