2020年5月の個人的SPA CircleCI設定のベストプラクティス

割とテンプレ化されてきたのでそろそろまとめておく。


SPA+S3+CloudFrontの場合はこんな感じ。

version: 2.1

orbs:
  slack: circleci/slack@3.3.0

executors:
  default:
    working_directory: ~/workspace
    docker:
      - image: circleci/node:latest

commands:
  yarn-install:
    steps:
      - restore_cache:
          key: yarn-cache-{{ checksum "package.json" }}
      - run:
          name: install yarn
          command: yarn install
      - save_cache:
          key: yarn-cache-{{ checksum "package.json" }}
          paths:
              - node_modules
  lint:
    steps:
      - run:
          name: eslint
          command: yarn run lint:js
      - run:
          name: stylelint
          command: yarn run lint:css
      - run:
          name: secretlint
          command: yarn run lint:secret
  build:
    steps:
      - run:
          name: yarn run build
          command: yarn run build
  deploy:
    steps:
      - run:
          name: deploy staging
          command: ./node_modules/.bin/gulp deploy

jobs:
  test:
    executor:
      name: default
    steps:
      - checkout
      - yarn-install
      - lint
  deploy-stg:
    executor:
      name: default
    steps:
      - checkout
      - yarn-install
      - lint
      - build
      - deploy
      - slack/notify:
          message: projectのstagingをリリースしたよ
    environment:
      AWS_BUCKET_NAME: <aws bucket url>
      AWS_CLOUDFRONT: <cloudfront>
  deploy-prod:
    executor:
      name: default
    steps:
      - checkout
      - yarn-install
      - lint
      - build
      - deploy
      - slack/notify:
          message: productのproductionをリリースしたよ
    environment:
      AWS_BUCKET_NAME: <aws bucket url>
      AWS_CLOUDFRONT: <cloudfront>

workflows:
  version: 2
  build:
    jobs:
      - test
      - deploy-stg:
          requires:
            - test
          filters:
            branches:
              only: develop
      - deploy-prod:
          requires:
            - test
          filters:
            branches:
              only: master
const gulp = require("gulp");
const awspublish = require("gulp-awspublish");
const parallelize = require("concurrent-transform");
const cloudfront = require("gulp-cloudfront-invalidate-aws-publish");

const config = {
  params: {
    Bucket: process.env.AWS_BUCKET_NAME
  },
  credentials: {
    accessKeyId: process.env.AWS_ACCESS_KEY_ID,
    secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY,
    signatureVersion: "v3"
  },
  deleteOldVersions: false, // PRODUCTION で使用しない
  distribution: process.env.AWS_CLOUDFRONT, // CloudFront distribution ID
  region: process.env.AWS_DEFAULT_REGION,
  headers: {
    /*'Cache-Control': 'max-age=315360000, no-transform, public',*/
  },
  distDir: "dist",
  indexRootPath: true,
  cacheFileName: ".awspublish",
  concurrentUploads: 10,
  wait: true // CloudFront のキャッシュ削除が完了するまでの時間(約30〜60秒)
};

gulp.task("deploy", function() {
  const publisher = awspublish.create(config);
  let g = gulp.src(`./${config.distDir}/**`);
  g = g.pipe(parallelize(publisher.publish(config.headers), config.concurrentUploads));

  if (config.distribution) {
    g = g.pipe(cloudfront(config));
  }
  if (config.deleteOldVersions) g = g.pipe(publisher.sync());
  g = g.pipe(publisher.cache());
  g = g.pipe(awspublish.reporter());
  return g;
});