webpack原理篇(六十一):更复杂的 loader 的开发场

简介: webpack原理篇(六十一):更复杂的 loader 的开发场

说明

玩转 webpack 学习笔记



loader 的参数获取

通过 loader-utils 的 getOptions 方法获取

const loaderUtils = require("loader-utils");
module.exports = function(content) {
  const { name } = loaderUtils.getOptions(this);
};


安装依赖,这里使用 1.2.3 版本的

npm i loader-utils@1.2.3 -S

0470f226aa064fb98cb67aba546c8c5e.png


run-loader.js 传递参数 name:

const fs = require("fs");
const path = require("path");
const { runLoaders } = require("loader-runner");
runLoaders(
    {
        resource: "./src/kaimo.txt",
        loaders: [
            {
                loader: path.resolve(__dirname, "./src/raw-loader.js"),
                options: {
                    name: "kaimo313"
                }
            }
        ],
        context: {
            minimize: true
        },
        readResource: fs.readFile.bind(fs),
    },
    (err, result) => {
        err ? console.error(err) : console.log(result)
    }
);


raw-loader.js 接收参数

const loaderUtils = require("loader-utils");
module.exports = function(source) {
  const { name } = loaderUtils.getOptions(this);
    console.log("raw-loader-getOptions-name->", name);
    const json = JSON.stringify(source)
    .replace('666', '313')
    .replace(/\u2028/g, '\\u2028' ) // 为了安全起见, ES6模板字符串的问题
    .replace(/\u2029/g, '\\u2029');
    return `export default ${json}`;
};


然后运行 node run-loader.js

a2750b3d4b1c413f8dc325e29aec171c.png



loader 异常处理


loader 内直接通过 throw 抛出

通过 this.callback 传递错误

this.callback(
    err: Error | null,
    content: string | Buffer,
    sourceMap?: SourceMap,
    meta?: any
);
const loaderUtils = require("loader-utils");
module.exports = function(source) {
  const { name } = loaderUtils.getOptions(this);
    console.log("raw-loader-getOptions-name->", name);
    const json = JSON.stringify(source)
    .replace('666', '313')
    .replace(/\u2028/g, '\\u2028' ) // 为了安全起见, ES6模板字符串的问题
    .replace(/\u2029/g, '\\u2029');
    // throw new Error("Error kaimo313");
    this.callback(new Error("Error kaimo313"), "");
    // return `export default ${json}`;
    // 可以回传多个值
    // this.callback(null, `export default ${json}`, 1, 2, 3, 4);
};


a2e0131a773a4eb38304699ce59c5051.png

this.callback(null, `export default ${json}`, 1, 2, 3, 4);


60be8b81ca284006b21dee641899080e.png


loader 的异步处理


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


  • 第一个参数是 Error,第二个参数是处理的结果

示意代码:

module.exports = function (input) {
    const callback = this.async();
    // No callback -> return synchronous results
    // if (callback) { ... }
    callback(null, input + input);
};


新建一个 async.txt 的文件,添加 async kaimo313 的内容。

043319db579440dc867c133f410a0e62.png


添加异步读取文件

const loaderUtils = require("loader-utils");
const fs = require('fs');
const path = require('path');
const { callbackify } = require("util");
module.exports = function(source) {
  const { name } = loaderUtils.getOptions(this);
    console.log("raw-loader-getOptions-name->", name);
    const json = JSON.stringify(source)
    .replace('666', '313')
    .replace(/\u2028/g, '\\u2028' ) // 为了安全起见, ES6模板字符串的问题
    .replace(/\u2029/g, '\\u2029');
    // throw new Error("Error kaimo313");
    // this.callback(new Error("Error kaimo313"), "");
    // return `export default ${json}`;
    // 可以回传多个值
    // this.callback(null, `export default ${json}`, 1, 2, 3, 4);
    // 上下文方法 async
    const callback = this.async();
    fs.readFile(path.join(__dirname, './async.txt'), 'utf-8', (err, data) => {
        if(err) {
            callback(err, '');
        }
        callback(null, data)
    });
};


然后运行 node run-loader.js


115c9fa913c946fb87e23a62fce13390.png



在 loader 中使用缓存


webpack 中默认开启 loader 缓存


  • 可以使用 this.cacheable(false) 关掉缓存

缓存条件: loader 的结果在相同的输入下有确定的输出


  • 有依赖的 loader 无法使用缓存


const loaderUtils = require("loader-utils");
const fs = require('fs');
const path = require('path');
const { callbackify } = require("util");
module.exports = function(source) {
  const { name } = loaderUtils.getOptions(this);
    console.log("raw-loader-getOptions-name->", name);
    // 不开启缓存
    this.cacheable(false);
    const json = JSON.stringify(source)
    .replace('666', '313')
    .replace(/\u2028/g, '\\u2028' ) // 为了安全起见, ES6模板字符串的问题
    .replace(/\u2029/g, '\\u2029');
    // throw new Error("Error kaimo313");
    // this.callback(new Error("Error kaimo313"), "");
    // return `export default ${json}`;
    // 可以回传多个值
    // this.callback(null, `export default ${json}`, 1, 2, 3, 4);
    // 上下文方法 async
    const callback = this.async();
    fs.readFile(path.join(__dirname, './async.txt'), 'utf-8', (err, data) => {
        if(err) {
            callback(err, '');
        }
        callback(null, data)
    });
};



ac4d347be2534003b2a6a87cc2ce2e1e.png



loader 如何进行文件输出?

通过 this.emitFile 进行文件写入

const loaderUtils = require("loader-utils");
module.exports = function (content) {
    const url = loaderUtils.interpolateName(this, "[hash].[ext]", {
        content,
    });
    this.emitFile(url, content);
    const path = `__webpack_public_path__ + ${JSON.stringify(url)};`;
    return `export default ${path}`;
};


可以看一下 file-loader 的实现:https://github.com/webpack-contrib/file-loader/blob/master/src/index.js

import path from 'path';
import { getOptions, interpolateName } from 'loader-utils';
import { validate } from 'schema-utils';
import schema from './options.json';
import { normalizePath } from './utils';
export default function loader(content) {
  const options = getOptions(this);
  validate(schema, options, {
    name: 'File Loader',
    baseDataPath: 'options',
  });
  const context = options.context || this.rootContext;
  const name = options.name || '[contenthash].[ext]';
  const url = interpolateName(this, name, {
    context,
    content,
    regExp: options.regExp,
  });
  let outputPath = url;
  if (options.outputPath) {
    if (typeof options.outputPath === 'function') {
      outputPath = options.outputPath(url, this.resourcePath, context);
    } else {
      outputPath = path.posix.join(options.outputPath, url);
    }
  }
  let publicPath = `__webpack_public_path__ + ${JSON.stringify(outputPath)}`;
  if (options.publicPath) {
    if (typeof options.publicPath === 'function') {
      publicPath = options.publicPath(url, this.resourcePath, context);
    } else {
      publicPath = `${
        options.publicPath.endsWith('/')
          ? options.publicPath
          : `${options.publicPath}/`
      }${url}`;
    }
    publicPath = JSON.stringify(publicPath);
  }
  if (options.postTransformPublicPath) {
    publicPath = options.postTransformPublicPath(publicPath);
  }
  if (typeof options.emitFile === 'undefined' || options.emitFile) {
    const assetInfo = {};
    if (typeof name === 'string') {
      let normalizedName = name;
      const idx = normalizedName.indexOf('?');
      if (idx >= 0) {
        normalizedName = normalizedName.substr(0, idx);
      }
      const isImmutable = /\[([^:\]]+:)?(hash|contenthash)(:[^\]]+)?]/gi.test(
        normalizedName
      );
      if (isImmutable === true) {
        assetInfo.immutable = true;
      }
    }
    assetInfo.sourceFilename = normalizePath(
      path.relative(this.rootContext, this.resourcePath)
    );
    this.emitFile(outputPath, content, null, assetInfo);
  }
  const esModule =
    typeof options.esModule !== 'undefined' ? options.esModule : true;
  return `${esModule ? 'export default' : 'module.exports ='} ${publicPath};`;
}
export const raw = true;


可以看到使用了 interpolateName

29e15022ff0844e4b8d157c295544d1e.png


还有 emitFile


06949eef7f0f429fa52120b342a40100.png

https://github.com/webpack/loader-utils/tree/v1.2.3

使用多个占位符和/或正则表达式插入文件名模板。 模板和正则表达式在当前加载器的上下文中设置为名为 name 和 regExp 的查询参数。


const interpolatedName = loaderUtils.interpolateName(loaderContext, name, options);


8dc33171e5494199834fb24f69b9645e.png



我们在 loader-order 项目里安装依赖

7da49c1f321a4daeb3035e783003d653.png


然后在 a-loader.js 添加文件输出的代码

const loaderUtils = require("loader-utils");
module.exports = function(source) {
  console.log ('loader a is executed');
    const url = loaderUtils.interpolateName(this, '[name]_[hash].[ext]', source);
    console.log("url---->", url);
    this.emitFile(url, source);
  return source;
};


运行 npm run build,可以看到生成出来了 index 文件

726c127ed58c429e9110cbe466c18a76.png

我们可以看一下 dist 文件夹

5ac6a78f1bef471d858a575bc9647dd3.png





目录
相关文章
|
21天前
|
监控 前端开发 JavaScript
Webpack 中 HMR 插件的工作原理
【10月更文挑战第23天】可以进一步深入探讨 HMR 工作原理的具体细节、不同场景下的应用案例,以及与其他相关技术的结合应用等方面的内容。通过全面、系统地了解 HMR 插件的工作原理,能够更好地利用这一功能,为项目的成功开发提供有力保障。同时,要不断关注技术的发展动态,以便及时掌握最新的 HMR 技术和最佳实践。
|
21天前
|
缓存 前端开发 JavaScript
Webpack 动态加载的原理
【10月更文挑战第23天】Webpack 动态加载通过巧妙的机制和策略,实现了模块的按需加载和高效运行,提升了应用程序的性能和用户体验。同时,它也为前端开发提供了更大的灵活性和可扩展性,适应了不断变化的业务需求和技术发展。
|
21天前
|
缓存 前端开发 JavaScript
webpack 原理
【10月更文挑战第23天】Webpack 原理是一个复杂但又非常重要的体系。它通过模块解析、依赖管理、加载器和插件的协作,实现了对各种模块的高效打包和处理,为现代前端项目的开发和部署提供了强大的支持。同时,通过代码分割、按需加载、热模块替换等功能,提升了应用程序的性能和用户体验。随着前端技术的不断发展,Webpack 也在不断演进和完善,以适应不断变化的需求和挑战。
|
1月前
|
缓存 前端开发 JavaScript
Webpack 打包的基本原理
【10月更文挑战第5天】
|
1月前
|
前端开发 UED
Webpack 中处理 CSS 和图片资源的多 Loader 配置
【10月更文挑战第12天】 处理 CSS 和图片资源是 Webpack 配置中的重要部分。通过合理选择和配置多个 Loader,可以实现对这些资源的精细处理和优化,提升项目的性能和用户体验。在实际应用中,需要不断探索和实践,根据项目的具体情况进行灵活调整和优化,以达到最佳的处理效果。通过对 Webpack 中多 Loader 处理 CSS 和图片资源的深入了解和掌握,你将能够更好地应对各种复杂的资源处理需求,为项目的成功构建和运行提供坚实的基础。
55 1
|
1月前
|
前端开发 JavaScript
Webpack 中多个 Loader 的配置
【10月更文挑战第12天】使用多个 Loader 进行配置是 Webpack 中常见的操作,可以实现对各种资源的精细处理和优化。在配置时,需要根据具体需求合理选择和排列 Loader,并注意它们之间的顺序和交互关系。同时,不断了解和掌握新的 Loader 以及它们的特性,有助于更好地发挥 Webpack 的强大功能,提升项目的开发效率和质量。通过深入理解和熟练运用多个 Loader 的配置方法,你将能够更加灵活地处理各种资源,满足项目的多样化需求。
45 2
|
1月前
|
前端开发 JavaScript
Webpack 常用 Loader 和 Plugin
【10月更文挑战第12天】Webpack 是一个强大的模块打包工具,能够将各种资源模块进行打包和处理。Loader 用于转换模块的源代码,如 `babel-loader` 将 ES6+ 代码转换为 ES5,`css-loader` 处理 CSS 文件等。Plugin 扩展 Webpack 功能,如 `HtmlWebpackPlugin` 自动生成 HTML 文件,`UglifyJsPlugin` 压缩 JavaScript 代码。通过合理配置和使用 Loader 和 Plugin,可以构建高效、优化的项目。
21 2
|
1月前
|
移动开发 JavaScript 前端开发
webpack学习四:使用webpack配置plugin,来使用HtmlWebpackPlugin、uglifyjs-webpack-plugin、webpack-dev-server等插件简化开发
这篇文章主要介绍了如何通过配置Webpack的插件,如HtmlWebpackPlugin、uglifyjs-webpack-plugin和webpack-dev-server,来简化前端开发流程。
40 0
webpack学习四:使用webpack配置plugin,来使用HtmlWebpackPlugin、uglifyjs-webpack-plugin、webpack-dev-server等插件简化开发
|
2月前
|
设计模式 前端开发 JavaScript
webpack实战之手写一个loader和plugin
该文章详细讲解了如何从零开始编写一个自定义的Webpack Loader和Plugin,包括它们的工作原理、开发步骤以及如何将自定义的Loader和Plugin集成到Webpack配置中。
webpack实战之手写一个loader和plugin
|
2月前
|
JavaScript 前端开发
手写一个简易bundler打包工具带你了解Webpack原理
该文章通过手写一个简易的打包工具bundler,帮助读者理解Webpack的工作原理,包括模块解析、依赖关系构建、转换源代码以及生成最终输出文件的整个流程。