webpack打包自动上传静态资源

最近在做一个使用Taro框架的业务,最初因为只用到了H5,图片/svg等静态资源都是硬编码到代码中的,现在要编译成小程序,就需要将这些静态资源上传到oss。如果手动替换就是费时费力,所以需要使用webpack来做自动化上传,在此记录下。

解决思路

解决思路还是比较明确的,首先是loader部分,就是要webpack在解析图片和svg时执行上传任务,然后返回上传后的地址。

然后为了每次编译时不要重复上传已经上传过的内容,就需要记录下哪些资源已经上传过了,使用plugin来生成一个map文件,将该文件作为参数传到loader中,loader解析时判断如果已经存在在map文件中就直接返回地址,否则才执行上传任务。

loader部分

ini 复制代码
function webpackUploadLoader(source, map, meta) {
  const callback = this.async();
  const options = getOptions(this) || {};
  validate(schema as any, options, {
    name: 'Upload Loader',
    baseDataPath: 'options',
  });
  const { upload, mapJson } = options;
  const { resourcePath, rootContext } = this;
  const defaultCallback = url => {
    callback(null, url, map, meta);
  };
  let data;
  const relativePath = resourcePath.replace(rootContext, '.').replace(/\\/g, '/');
  try {
    data = this.fs.readFileSync(mapJson, 'utf8');
    data = JSON.parse(data);
  } catch (err) {}
  if (data && data[relativePath]) {
    defaultCallback(data[relativePath]);
    return;
  }
  const mimetype = getMimetype(options.mimetype, resourcePath);
  const fileName = interpolateName(this, '[contenthash].[ext][query]', {
    context: resourcePath,
    content: source,
  });
  const spinner = ora(
    `uploading file: ${`${fileName}`} local: ${resourcePath.replace(rootContext, '')}`,
  ).start();
  upload
    .call(this, {
      fileName,
      resourcePath,
      source,
      mimetype,
    })
    .then(url => {
      spinner.succeed(`uploaded to: ${url}------${resourcePath}`);
      callback(null, url, map, meta);
    })
    .catch(err => {
      spinner.clear();
      this.emitError(
        `${resourcePath} upload error: ${err === null || err === void 0 ? void 0 : err.toString()}`,
      );
    });
}

使用

javascript 复制代码
{
type:'javascript/auto',
    test: /.(png|jpe?g|gif|bpm|svg|webp)(?.*)?$/,
    include: path.resolve(__dirname, './src'),
    use:[
      {
        loader: 'url-loader',
        options:{
          esModule: false,
          generator: (content) => content.toString('utf8'),
        }
      },
      {
        loader: 'webpack-assets-upload',
        options:{
          mapJson: path.resolve(__dirname, 'ossMap.json'),
          // eslint-disable-next-line consistent-return
          upload: async ({ fileName, resourcePath }) => {
            //  Promise.resolve('https://www.baidu.com')
            try {
              const { url, res } = await put(fileName, resourcePath);
              if (res.status === 200) {
                return url;
              } 
                throw Error('上传失败');
              
            } catch (e) {
              console.log(e);
            }
          }
          ,
        }
      }
    ]
}

接受两个配置项,一个就是上传方法upload,该方法应该返回一个图片地址string。然后进入下一个loader(url-loader)将内容插入到结果中。其中url-loader需要注意将esModule设置为false,主要是为了处理css中的图片/svg资源,需要用commonjs的格式。

另一个就是通过plugin生成的json格式的映射文件。以此来判断资源是否上传过。

plugin部分

typescript 复制代码
const fs = require("fs");
const path = require("path");
class AssetsUploadPlugin {
  static defaultOptions = {
    outputFile: "ossMap.json",
  };

  options: any;

  // 需要传入自定义插件构造函数的任意选项
  //(这是自定义插件的公开API)
  constructor(options = {}) {
    // 在应用默认选项前,先应用用户指定选项
    // 合并后的选项暴露给插件方法
    // 记得在这里校验所有选项
    this.options = { ...AssetsUploadPlugin.defaultOptions, ...options };
  }

  apply(compiler) {
    const pluginName = AssetsUploadPlugin.name;

    compiler.hooks.emit.tapAsync(pluginName, (compilation, callback) => {
      const imageUrls = compilation.modules.filter((m) =>
        /\.(png|jpe?g|gif|bpm|svg|webp)(\?.*)?$/.test(m.resource)
      ); // 这里可以获取到所有的模块源(包括图片文件和其它模块)
      const imageUrlMap = {}; // 用于存储图片地址和原始文件地址的映射
      imageUrls.forEach((source) => {
        let moduleName = source.resourceResolveData.relativePath; // 文件相对路径
        moduleName = moduleName.startsWith("./")
          ? moduleName
          : `./${moduleName}`;
        try {
          const sourcePath = JSON.parse(
            source._source?._value.split("module.exports =")[1]
          ); // 处理后的文件路径(URL)
          imageUrlMap[moduleName] = sourcePath; // 将图片地址和原始文件地址存储到映射对象中
        } catch {
          console.log(source._source?._value.split("module.exports =")[1]);
        }
      });
      const content = JSON.stringify(imageUrlMap); // 将映射对象转换为 JSON 字符串
      fs.writeFileSync(this.options.outputFile, content); //在这里生成一个新的文件,内容为图片地址和原始文件地址的映射
      callback(); // 表示成功生成输出文件,继续下一轮构建过程
    });
  }
}

module.exports = { AssetsUploadPlugin };

使用相对路径来作为key表示资源唯一性,通过source._source?._value来获取到每个静态资源的最终地址。生成key-value的map文件。

最后

现在每次运行webpack编译都会自动上传没有在map文件中记录过的新的资源,重新生成map文件。同时也提高了编译速度,减小了打包体积。

相关推荐
摸鱼的春哥几秒前
春哥的Agent通关秘籍07:5分钟实现文件归类助手【实战】
前端·javascript·后端
念念不忘 必有回响4 分钟前
viepress:vue组件展示和源码功能
前端·javascript·vue.js
C澒9 分钟前
多场景多角色前端架构方案:基于页面协议化与模块标准化的通用能力沉淀
前端·架构·系统架构·前端框架
崔庆才丨静觅11 分钟前
稳定好用的 ADSL 拨号代理,就这家了!
前端
江湖有缘12 分钟前
Docker部署music-tag-web音乐标签编辑器
前端·docker·编辑器
恋猫de小郭1 小时前
Flutter Zero 是什么?它的出现有什么意义?为什么你需要了解下?
android·前端·flutter
崔庆才丨静觅8 小时前
hCaptcha 验证码图像识别 API 对接教程
前端
passerby60619 小时前
完成前端时间处理的另一块版图
前端·github·web components
掘了9 小时前
「2025 年终总结」在所有失去的人中,我最怀念我自己
前端·后端·年终总结
崔庆才丨静觅9 小时前
实用免费的 Short URL 短链接 API 对接说明
前端