官⽅⽂档:https://webpack.js.org/contribute/writing-a-loader/
接⼝⽂档:https://webpack.js.org/api/loaders/

⾃⼰编写⼀个 Loader 的过程是⽐较简单的,Loader 就是⼀个函数,声明式函数,不能⽤箭头函数拿到源代码,作进⼀步的修饰处理,再返回处理后的源码就可以了

Webpack 加载资源文件的过程类似于一个工作管道,你可以在这个过程中依次使用多个 Loader,但是最终这个管道结束过后的结果必须是一段标准的 JS 代码字符串
image.png

一个最简单的 loader

定义:loader 只是一个导出为函数的 JavaScript 模块

  1. module.exports = function(source) {
  2. return source;
  3. };

多个 Loader 串行执行:顺序从后到前

  1. module.exports = {
  2. entry: './src/index.js',
  3. output: {
  4. filename: 'bundle.js',
  5. path: path.resolve(__dirname, 'dist')
  6. },
  7. module: {
  8. rules: [{
  9. test: /\.less$/,
  10. use: ['style-loader','css-loader',' less-loader']
  11. }]
  12. }
  13. };

loader-runner

定义:loader-runner 允许你在不安装 webpack 的情况下运行 loaders
作用:

  • 作为 webpack 的依赖,webpack 中使用它执行 loader
  • 进行 loader 的开发和调试
    1. mport { runLoaders } from "loader-runner";
    2. runLoaders({
    3. resource: “/abs/path/to/file.txt?query”, // String: 资源的绝对路径(可以增加查询字符串)
    4. loaders: [“/abs/path/to/loader.js?query”], // String[]: loader 的绝对路径(可以增加查询字符串)
    5. context: { minimize: true }, // 基础上下文之外的额外 loader 上下文
    6. readResource: fs.readFile.bind(fs) // 读取资源的函数
    7. }, function(err, result) {
    8. // err: Error?
    9. // result.result: Buffer | String
    10. })
    ```javascript const { runLoaders } = require(‘loader-runner’); const fs = require(‘fs’); const path = require(‘path’);

runLoaders({ resource: path.join(dirname, ‘./src/demo.txt’), loaders: [ { loader: path.join(dirname, ‘./src/raw-loader.js’), options: { name: ‘test’ } } ], context: { emitFile: () => {} }, readResource: fs.readFile.bind(fs) }, (err, result) => { err ? console.log(err) : console.log(result); });

  1. ![image.png](https://cdn.nlark.com/yuque/0/2022/png/338495/1644070733620-4bb581bf-4733-47c4-8875-d41b66803aa0.png#clientId=u1c058b93-16d4-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=165&id=u05a4af42&margin=%5Bobject%20Object%5D&name=image.png&originHeight=206&originWidth=810&originalType=binary&ratio=1&rotation=0&showTitle=false&size=125092&status=done&style=none&taskId=u56927165-76b8-40c9-93c7-3b3d7768d46&title=&width=648)
  2. <a name="E4uFT"></a>
  3. ## loader 的参数获取
  4. 通过 loader-utils getOptions 方法获取
  5. ```javascript
  6. const loaderUtils = require("loader-utils");
  7. module.exports = function(content) {
  8. const { name } = loaderUtils.getOptions(this); //获取options参数
  9. };

this.callback

loader 内直接通过 throw 抛出,或通过 this.callback 传递错误

  1. this.callback(
  2. err: Error | null,
  3. content: string | Buffer,
  4. sourceMap?: SourceMap,
  5. meta?: any
  6. );
  1. this.callback(new Error('Error'), ''); //传递错误
  2. //也可以直接 throw new Error('Error');
  1. // 可通过callback 代替return
  2. this.callback(null, data);
  3. // 如何返回多个信息,不⽌是处理好的源码,可以使⽤this.callback来处理
  4. this.callback(null, data, 'test');

异步处理

通过 this.async来返回一个异步函数

  • 第一个参数是 Error,第二个参数是处理的结果
    1. module.exports = function(input) {
    2. const callback = this.async();
    3. // No callback -> return synchronous results
    4. // if (callback) { ... }
    5. callback(null, input + input);
    6. };
    ```javascript const callback = this.async();

fs.readFile(path.join(__dirname, ‘./async.txt’), ‘utf-8’, (err, data) => { if (err) { callback(err, ‘’); } callback(null, data); });

  1. <a name="b6SqT"></a>
  2. ##
  3. <a name="GVWSG"></a>
  4. ## 在 loader 中使用缓存
  5. webpack 中默认开启 loader 缓存
  6. - 可以使用 this.cacheable(false) 关掉缓存
  7. 缓存条件: loader 的结果在相同的输入下有确定的输出
  8. - 有依赖的 loader 无法使用缓存
  9. <a name="lFj5c"></a>
  10. ## loader 进行文件输出
  11. 通过 this.emitFile 进行文件写入
  12. ```javascript
  13. const loaderUtils = require("loader-utils");
  14. module.exports = function(content) {
  15. const url = loaderUtils.interpolateName(this, "[hash].[ext]", {
  16. content,
  17. });
  18. this.emitFile(url, content);
  19. const path = `__webpack_public_path__ + ${JSON.stringify(url)};`;
  20. return `export default ${path}`;
  21. };

创建一个替换源码中字符串的loader

  1. //index.js
  2. console.log("hello kkk");
  1. //replaceLoader.js
  2. module.exports = function (source) {
  3. console.log(source, this, this.query);
  4. return source.replace('kkk', '哈哈哈');
  5. };
  6. //需要⽤声明式函数,因为要用到上下⽂的this,⽤到this的数据,该函数接受⼀个参数,是源码
  • 在配置⽂件中使⽤loader ```javascript //需要使⽤node核⼼模块path来处理路径 const path = require(‘path’); module: { rules: [ {
    1. test: /\.js$/,
    2. use: path.resolve(__dirname, './loader/replaceLoader.js')
    } ]; }
  1. - **配置参数**
  2. ```javascript
  3. //webpack.config.js
  4. module: {
  5. rules: [
  6. {
  7. test: /\.js$/,
  8. use: [
  9. {
  10. loader: path.resolve(__dirname, './loader/replaceLoader.js'),
  11. options: {
  12. name: '哈哈哈'
  13. }
  14. }
  15. ]
  16. }
  17. ];
  18. }
  19. // replaceLoader.js
  20. module.exports = function (source) {
  21. //this.query 通过this.query来接受配置⽂件传递进来的参数
  22. return source.replace('kkk', this.query.name);
  23. };
  • 如何返回多个信息,不⽌是处理好的源码呢,可以使⽤this.callback来处理
  • 官⽅推荐处理loader,query的⼯具 ```javascript //replaceLoader.js const loaderUtils = require(‘loader-utils’); //官⽅推荐处理loader,query的⼯具 module.exports = function (source) { const options = loaderUtils.getOptions(this); const result = source.replace(‘kkk’, options.name); this.callback(null, result); };
  1. - 如果loader⾥⾯有异步的事情要怎么处理呢
  2. ```javascript
  3. const loaderUtils = require('loader-utils');
  4. module.exports = function (source) {
  5. const options = loaderUtils.getOptions(this);
  6. // 定义⼀个异步处理,告诉webpack,这个loader⾥有异步事件,在⾥⾯调⽤下这个异步
  7. // callback 就是 this.callback 注意参数的使⽤
  8. const callback = this.async();
  9. setTimeout(() => {
  10. const result = source.replace('kkk', options.name);
  11. callback(null, result);
  12. }, 3000);
  13. };
  • 处理loader的路径问题
    1. resolveLoader: {
    2. modules: ['node_modules', './loader']; //增加一个loader目录
    3. }