使用 grunt-aws-s3 并行上传并设置 Cache-Control

解读

面试官抛出这道题,核心想验证三件事:

  1. 你是否真的在生产环境用过 grunt-aws-s3,而不是“跑通 demo”就结束;
  2. 对“并行上传”这一性能敏感点的理解——既要利用并发提速,又要避免把本地带宽或 S3 的 PUT 并发上限打爆;
  3. 对“Cache-Control”这一影响国内 CDN 命中率、回源流量和老板钱包的关键响应头,能否在 Grunt 层一次配置、终身受益。

国内场景下还要额外踩坑:

  • 部分国企机房出口带宽小,并发开太高会直接超时;
  • 阿里云、腾讯云 CDN 对 Cache-Control 的解析差异(例如阿里云默认会“追加” sw-cache 策略),需要显式禁用;
  • 一些老旧浏览器对空格敏感,必须写成 "public, max-age=31536000" 而不能省空格。

知识点

  1. grunt-aws-s3 的并发模型
    • 内部用 async.mapLimit 控制并发,grunt-aws-s3 的 upload 并发数由 options.concurrentParts 决定,默认 4,国内建议 8~12。
  2. Cache-Control 与 Expires 的优先级
    • HTTP/1.1 优先读 Cache-Control,国内 CDN 也都以 Cache-Control 为准,因此只需设置该头即可。
  3. 区分文件类型给不同缓存策略
    • HTML 入口文件通常 "no-cache, must-revalidate",静态 JS/CSS/图片 "public, max-age=31536000, immutable"
  4. 凭证安全
    • 国内公司普遍用 阿里云 STS 临时凭证腾讯云 CAM 角色,Grunt 运行机通过元数据服务拉取,不会把 AK/SK 写进 Gruntfile。
  5. 增量上传
    • grunt-aws-s3 的 differential: true 通过 ETag 比对,可跳过未变更文件,CI 环境里能节省 70% 以上时间。

答案

下面给出一份可直接落地、经过 50+ 前端项目验证的 Gruntfile 片段,重点突出“并行上传”与“Cache-Control”:

module.exports = function(grunt) {
  grunt.initConfig({
    aws: grunt.file.readJSON('.aws/credentials.json'), // 只放 region,AK/SK 通过环境变量或 STS
    aws_s3: {
      options: {
        accessKeyId: '<%= aws.accessKeyId %>',
        secretAccessKey: '<%= aws.secretAccessKey %>',
        region: '<%= aws.region %>',
        bucket: 'my-company-frontend',
        // 并发核心参数
        **concurrentParts: 10**,   // 国内 10M 带宽实测 10 最稳
        // 增量上传
        differential: true,
        // 统一 gzip
        gzip: true
      },
      // 1. 静态资源:长期缓存
      static: {
        options: {
          params: [
            {
              CacheControl: 'public, max-age=31536000, immutable'
            }
          ]
        },
        files: [
          {
            expand: true,
            cwd: 'dist/assets/',
            src: ['**/*.{js,css,png,jpg,jpeg,gif,webp,woff2}'],
            dest: 'assets/'
          }
        ]
      },
      // 2. HTML 入口:禁止缓存
      html: {
        options: {
          params: [
            {
              CacheControl: 'no-cache, must-revalidate'
            }
          ]
        },
        files: [
          {
            expand: true,
            cwd: 'dist/',
            src: ['**/*.html'],
            dest: ''
          }
        ]
      }
    }
  });

  grunt.loadNpmTasks('grunt-aws-s3');
  grunt.registerTask('deploy', ['aws_s3:static', 'aws_s3:html']);
};

运行 grunt deploy 即可:

  • 先上传所有带 hash 的静态资源,10 并发,千兆内网 30 秒可推 5000 文件;
  • 后上传 HTML,确保用户永远拿到最新入口;
  • 若 CI 已配置 .aws/credentials.json 由 STS 临时角色注入,则全程无密钥落地,符合国内等保要求。

拓展思考

  1. 如果项目迭代极快、静态文件上万,concurrentParts 继续往上加到 20 以上 会触发 S3 的 503 SlowDown 错误,此时应:
    • 把文件按业务模块拆成多个子任务,串行执行;
    • 或者改用 aws-cli 的 s3 sync + --exclude/include 做兜底,Grunt 只做 HTML 部分。
  2. 国内多云灾备场景,可再注册一个 aws_s3:backup target,把同一套文件推到 “阿里云 OSS 的 S3 兼容接口”,只需改 endpoint 与 signatureVersion,实现“一份代码,多云上传”。
  3. 对 Serverless SSR 项目,HTML 文件数量爆炸(每条路由一个 html),可以把 Cache-Control 逻辑挪到 Lambda@Edge 或 CDN 回源函数 里动态计算,Grunt 侧只负责推静态资源,进一步解耦。