1

原文地址

什么是 url-loader

url-loader 会将引入的文件进行编码,生成 DataURL,相当于把文件翻译成了一串字符串,再把这个字符串打包到 JavaScript

什么时候使用

一般来说,我们会发请求来获取图片或者字体文件。如果图片文件较多时(比如一些 icon),会频繁发送请求来回请求多次,这是没有必要的。此时,我们可以考虑将这些较小的图片放在本地,然后使用 url-loader 将这些图片通过 base64 的方式引入代码中。这样就节省了请求次数,从而提高页面性能。

如何使用

1. 安装 url-loader

npm install url-loader --save-dev

2. 配置 webapck

module.exports = {
  module: {
    rules: [
      {
        test: /\.(png|jpg|gif)$/,
        use: [
          {
            loader: 'url-loader',
            options: {},
          },
        ],
      },
    ],
  },
};

3. 引入一个文件,可以是 import(或 require

import logo from '../assets/image/logo.png';
console.log('logo的值: ', logo); // 打印一下看看 logo 是什么

简单三步就搞定了。

4. 见证奇迹的时刻

webpack

执行 webpack 之后,dist 目录只生成了一个 bundle.js。和 file-loader 不同的是,没有生成我们引入的那个图片。上文说过,url-loader 是将图片转换成一个 DataURL,然后打包到 JavaScript 代码中。

那我们就看看 bundle.js 是否有我们需要的 DataURL

// bundle.js
(function(module, exports) {
module.exports = "data:image/jpeg;base64.........."; // 省略无数行
})

我们可以看到这个模块导出的是一个标准的 DataURL

一个标准的DataURL: data:[<mediatype>][;base64],<data>

通过这个 DataURL,我们就可以从本地加载这张图片了,也就不用将图片文件打包到 dist 目录下。

使用 base64 来加载图片也是有两面性的:

  • 优点:节省请求,提高页面性能
  • 缺点:增大本地文件大小,降低加载性能

所以我们得有取舍,只对部分小 size 的图片进行 base64 编码,其它的大图片还是发请求吧。

url-loader 自然是已经做了这个事情,我们只要通过简单配置即可实现上述需求。

options

  • limit: 文件阈值,当文件大小大于 limit 的时候使用 fallbackloader 来处理文件
  • fallback: 指定一个 loader 来处理大于 limit 的文件,默认值是 file-loader

我们来试试设一个 limit

{
  test: /\.(png|jpg|gif)$/,
  use: [
    {
      loader: 'url-loader',
      options: {
        limit: 1000, // 大于 1000 bytes 的文件都走 fallback
      },
    },
  ],
},

重新执行 webpack,由于我们引入的 logo.png 大于 1000,所以使用的是 file-loader 来处理这个文件。图片被打包到 dist 目录下,并且返回的值是它的地址:

(function(module, exports, __webpack_require__) {
module.exports = __webpack_require__.p + "dab1fd6b179f2dd87254d6e0f9f8efab.png";
}),

更多关于 file-loader

源码解析

file-loader 的代码也不多,就直接复制过来通过注释讲解了:

import { getOptions } from 'loader-utils'; // loader 工具包
import validateOptions from 'schema-utils'; // schema 工具包
import mime from 'mime';

import normalizeFallback from './utils/normalizeFallback'; // fallback loader
import schema from './options.json'; // options schema

// 定义一个是否转换的函数
/*
 *@method shouldTransform
 *@param {Number|Boolean|String} limit 文件大小阈值
 *@param {Number} size 文件实际大小
 *@return {Boolean} 是否需要转换
*/
function shouldTransform(limit, size) {
  if (typeof limit === 'boolean') {
    return limit;
  }

  if (typeof limit === 'number' || typeof limit === 'string') {
    return size <= parseInt(limit, 10);
  }

  return true;
}

export default function loader(src) {
  // 获取 webpack 配置里的 options
  const options = getOptions(this) || {};

  // 校验 options
  validateOptions(schema, options, {
    name: 'URL Loader',
    baseDataPath: 'options',
  });

  // 判断是否要转换,如果要就进入,不要就往下走
  // src 是一个 Buffer,所以可以通过 src.length 获取大小
  if (shouldTransform(options.limit, src.length)) {
    const file = this.resourcePath;
    // 获取文件MIME类型,默认值是从文件取,比如 "image/jpeg"
    const mimetype = options.mimetype || mime.getType(file);

    // 如果 src 不是 Buffer,就变成 Buffer
    if (typeof src === 'string') {
      src = Buffer.from(src);
    }
    
    // 构造 DataURL 并导出
    return `module.exports = ${JSON.stringify(
      `data:${mimetype || ''};base64,${src.toString('base64')}`
    )}`;
  }

  // 判断结果是不需要通过 url-loader 转换成 DataURL,则使用 fallback 的 loader
  const {
    loader: fallbackLoader,
    options: fallbackOptions,
  } = normalizeFallback(options.fallback, options);

  // 引入 fallback loader
  const fallback = require(fallbackLoader);

  // fallback loader 执行环境
  const fallbackLoaderContext = Object.assign({}, this, {
    query: fallbackOptions,
  });

  // 执行 fallback loader 来处理 src
  return fallback.call(fallbackLoaderContext, src);
}

// 默认情况下 webpack 对文件进行 UTF8 编码,当 loader 需要处理二进制数据的时候,需要设置 raw 为 true
export const raw = true;

参考


axuebin
4k 声望1.2k 粉丝