手撕一个 webpack cdn-loader

最近在系统的整理 webpack 相关的内容,在 loader 这块想起之前公司的一个 cdn-loader 库,可以在运行的时候自动将 png、jpg 等图片文件上传到自己的 CDN 服务上,优化项目体验,提升构建效率。

话都说到这里了,就开始手撕一个简单的 cdn-loader ,具体需要实现的功能如下:

  • 在项目运行、构建的时候自动匹配图片相关的静态资源,实现上传到 cdn 上,并替换源代码中的 资源路径
  • 增加缓存的能力,避免重复上传消耗 cdn 容量。

1. 实现 cdn-loader

我这里以 NextJs 项目为例。初始化完项目,我们在根目录新建一个 loaders 文件夹,在该文件夹下新建一个 cdn-loader.js 文件,然后开始编辑 loader 的具体实现。

1.1 实现 loader 的整体框架

const fs = require('fs');
const path = require('path');

module.exports = function (source) {
  const callback = this.async();
  const resourcePath = this.resourcePath;

  // 读取图片文件
  fs.readFile(resourcePath, async (err, fileBuffer) => {
    if (err) {
      return callback(err);
    }

    try {
      // ... do Something
    } catch (uploadError) {
      callback(uploadError);
    }
  });
  return callback(null, source);
};

这就是这个 loader 的整体框架,需要注意的一点是 webpackloader 是在 Nodejs 环境运行的,所以需要使用 CommonJS 规范。

:::tip[参考]

  • callback: 这里是对后续上传 cdn 服务的异步回调
  • resourcePath: 是图片文件的相对路径
  • source: 图片文件的二进制信息

:::

1.2 实现具体的上传逻辑

// 上传到 CDN (你需要根据你的 CDN API 调整这个逻辑)

try {
  const response = await axios.post('https://your-cdn.com/upload', fileBuffer, {
    headers: {
      'Content-Type': 'image/jpeg', // 根据图片类型调整
      Authorization: `Bearer ${token}`
    }
  });
  const cdnUrl = response.data.url;
  console.log('上传成功,CDN URL:', cdnUrl);

  // 将代码中的绝对地址替换成 cdn url
  callback(null, `module.exports = ${JSON.stringify(cdnUrl)};`);
} catch (error) {}

到这里基础基础版的 cdn-loader 就基本完成了

2. cdn-loader 优化

通过第一步的实现,我们已经完成了一个基础版的 cdn-loader 了,但是有个潜在的问题,这样在每次运行、或者构建的情况下都会执行,会产生无用的重复数据,接下来我们着重来优化下重复数据的问题。

2.1 优先增加缓存能力

通过创建 .cdn-cache.json 来缓存已经上传的文件内容,每次动态的去判断是否已经上传过,已经上传就跳过就好,如果没有上传的话就走正常的上传逻辑。

const fs = require('fs');
const path = require('path');
const crypto = require('crypto');
const axios = require('axios');

// 缓存文件路径
const cacheFilePath = path.resolve(__dirname, '.cdn-cache.json');

// 读取缓存
function readCache() {
  if (fs.existsSync(cacheFilePath)) {
    return JSON.parse(fs.readFileSync(cacheFilePath, 'utf-8'));
  }
  return {};
}

// 写入缓存
function writeCache(cache) {
  fs.writeFileSync(cacheFilePath, JSON.stringify(cache, null, 2));
}

// 生成文件哈希
function generateHash(buffer) {
  return crypto.createHash('md5').update(buffer).digest('hex');
}

module.exports = function (source) {
  const callback = this.async();
  const resourcePath = this.resourcePath;
  const cache = readCache();

  fs.readFile(resourcePath, async (err, fileBuffer) => {
    if (err) {
      return callback(err);
    }

    // 生成文件哈希值
    const fileHash = generateHash(fileBuffer);

    // 检查缓存
    if (cache[fileHash]) {
      console.log(`图片 ${resourcePath} 已缓存,使用 CDN URL: ${cache[fileHash]}`);
      return callback(null, `module.exports = ${JSON.stringify(cache[fileHash])};`);
    }

    // 如果没有缓存,则上传图片
    try {
      const response = await axios.post('https://your-cdn.com/upload', fileBuffer, {
        headers: {
          'Content-Type': 'image/jpeg',
          Authorization: `Bearer ${token}`
        }
      });

      const cdnUrl = response.data.url;
      console.log('上传成功,CDN URL:', cdnUrl);

      // 保存哈希和 URL 的映射
      cache[fileHash] = cdnUrl;
      writeCache(cache);

      return callback(null, `module.exports = ${JSON.stringify(cdnUrl)};`);
    } catch (uploadError) {
      console.error('上传失败:', uploadError);
      callback(uploadError);
    }
  });
  return callback(null, source);
};

这里需要注意的是这里是根据文件内容生成的 hash 值,如果文件内容没变,图片名称变了的话,这个会被跳过。
到这里这个就具有一个缓存的效果了,只会上传缓存文件中没有的文件。

3. 配置 wenpack 的 loader

nextjs 在 next.config.mjs 在这里配置

const nextConfig = {
  webpack(config) {
    // ...
    config.module.rules.push({
      test: /\.(png|jpe?g|gif)$/,
      exclude: /node_modules/,
      use: [
        {
          loader: path.resolve(process.cwd(), 'loaders/cdn-loader.js')
        }
      ]
    });
    return config;
  },

  images: {
    disableStaticImages: true // 禁用静态图片优化
  }
};

:::tip[注意]
这里需要注意,如果使用 Nextjs 提供的的 Image 组件会导致冲突格式冲突,所以这里我们把这个关掉,然后可以手动封装一个 Image 组件来优化和兼容图片的显示。
:::

这里通过 test: /\.(png|jpe?g|gif)$/ 来匹配相关的图片资源,其实换成其他的静态资源 css 也一样,这里其实实现了一个静态资源的 cdn-loader,可以根据自己的场景来配置,比如只在构建向上环境的时候运行等。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

LaughingZhu

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值