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文件。同时也提高了编译速度,减小了打包体积。

相关推荐
酉鬼女又兒1 分钟前
零基础入门前端JavaScript 核心语法:var/let/const、箭头函数与 setTimeout 循环陷阱全解析(可用于备赛蓝桥杯Web应用开发)
开发语言·前端·javascript·蓝桥杯
Bling_Bling_15 分钟前
【无标题】
前端·网络协议
We་ct5 分钟前
React Diff & Key 核心解析
开发语言·前端·javascript·react.js·前端框架·reactjs·diff
哥本哈士奇6 分钟前
Vue 3 快速入门:从零搭建前后端 CRUD 应用
前端·javascript·vue.js
biubiubiu07067 分钟前
Agent 是如何拥有“手脚”的(ReAct 运行流程)
开发语言·前端·javascript
摸鱼的春哥11 分钟前
Agent教程21:知识图谱🕸,让AI🤖学会联想
前端·javascript·后端
SuperEugene11 分钟前
Vue3 组件拆分实战规范:页面 / 业务 / 基础组件边界清晰化,高内聚低耦合落地指南|Vue 组件与模板规范篇
前端·javascript·vue.js·前端框架
泯泷11 分钟前
阶段二:为什么先设计指令集,编译器和运行时才能稳定对齐?
前端·javascript·架构
Dxy123931021614 分钟前
HTML常用布局详解:从基础到进阶的网页结构指南
前端·html
ywf12152 小时前
前端的dist包放到后端springboot项目下一起打包
前端·spring boot·后端