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

相关推荐
beelan2 分钟前
v-on的思考
前端
山河木马5 分钟前
前端学习C++之:.h(.hpp)与.cpp文件
前端·javascript·c++
用户9272472502195 分钟前
PHP + CSS + JS + JSON 数据采集与展示系统,支持伪静态
前端
努力只为躺平9 分钟前
一文搞懂 Promise 并发控制:批量执行 vs 最大并发数,实用场景全解析!
前端·javascript
李大玄11 分钟前
Google浏览器拓展工具 "GU"->google Utils
前端·javascript·github
爱编程的喵12 分钟前
从DOM0到事件委托:揭秘JavaScript事件机制的性能密码
前端·javascript·dom
蓝倾17 分钟前
京东批量获取商品SKU操作指南
前端·后端·api
JSLove24 分钟前
常见 npm 报错问题
前端·npm
sunbyte24 分钟前
50天50个小项目 (Vue3 + Tailwindcss V4) ✨ | ContentPlaceholder(背景占位)
前端·javascript·css·vue.js·tailwindcss
爱学习的茄子25 分钟前
React Hooks进阶:从0到1打造高性能Todo应用
前端·react.js·面试