目的

  1. 使用AWS Lambba通知CloudFront自动进行更新.
  2. 对生成后的文件进行瘦身, 加快加载的时间.

CloudFront自动更新

CloudFront本质上就是一个K/V缓存. 对CloudFront发的请求将会在CloudFront本地检索,

  • 如果找到了就直接返回,
  • 没找的话就根据行为(Behaviors)的路径转发到不同的源(Origin), 将结果在本地缓存, 然后返回给用户
    AWS cloudfront workflow

所以除了刚启动的时候请求可能会经过1-6步骤, 大部分情况下请求在步骤2时就会返回结果. 这也导致了当对已经在缓存里的文章进行更新, 新的文章往往不会被返回(新的文章在S3里, 而用户获取的是CloudFront缓存里的).

这时就应该将CloudFront里的缓存进行无效化(Invalidation), 使请求重新经过步骤1-6. 在当前CloudFront的节点下的”Invalidations”标签下选中Create Invalidation, 在输入框处填 /* 使所有内容无效.
AWS cloudfront create invalidation

但是这样的话每次内容更新都需要无效化当前的CloudFront, 也太麻烦了. 为了自动化这个步骤, 这里将创建一个新的Lambda函数, 将S3的更新作为触发器, 对CloudFront发送一个无效化内容的请求.

  • 创建一个新的Lambda函数, 就叫”my-blog-lambda-invalidate-cloudfront”, 直接默认配置点击创建.
  • 进入新的函数, 在”配置”标签的左边找到”触发器”并添加一个新的触发器. 源的话使用S3放置网站的Bucket.
  • 模式(Event Type)的话就选在写入(Put)的时触发.
    • Prefix填写 index.html
      aws lambda craete function

Lambda函数的代码使用nodejs来完成, javascript官方文档没有例子和python官方文档有例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import { CloudFrontClient, CreateInvalidationCommand} 
from "@aws-sdk/client-cloudfront";

export const handler = async (event) => {
const client = new CloudFrontClient(); // 创建一个新的CloudFront客户端

const request = new CreateInvalidationCommand({
DistributionId: "<<distribution_id>>",
InvalidationBatch: {
CallerReference: new Date().toISOString(), // 每一次都使用当前时间作为uniqu值
Paths: {
Quantity: 1, // 下面数字的长度
Items: ["/*"]
}
},
});
const reuslt = await client.send(request);

const response = {
statusCode: 200,
body: JSON.stringify('Complete'),
};
return response;
};

需要注意的是

  1. DistributionId不是ARN, 是ARN最后斜杆的标识. 如, ARN为arn:aws:cloudfront::<>:distribution/AAAAABBBBB1123. 那么DistributionId就是“AAAAABBBBB1123”。
  2. 所有请求的变量都要写, 否则函数回报变量为null的错误.

最后发布(Deploy), 这样的话每当CodeBuild将编译完成的博客保存到S3时, 上面的Lambda函数就会被调用, 无效化CloudFront中旧的博客.

Gulp减少文件体积

Gulp是一款自动化工具. 这里将使用Gulp对Hexo生成的博客代码/素材进行瘦身, 是其可以更快得被加载. 这里的配置都是依照Butterfly主题的官方源码来配的.

  1. 增加包依赖
    1
    2
    yarn add --dev gulp
    yarn add gulp-html-minifier-terser gulp-htmlclean gulp-imagemin
    更新CodeBuild的buildspec.yml
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    version: 0.2

    phases:
    # No need for install because of taskbjorn/hexo container
    install:
    runtime-versions:
    nodejs: 18
    commands:
    - npm install
    - npm install -g hexo-cli
    - npm install -g gulp-cli
    build:
    on-failure: ABORT
    commands:
    - echo Start building website
    - hexo generate
    - gulp

    artifacts:
    files:
    - '**/*'
    base-directory: public
  2. 在根目录下添加一个文件在gulpfile.js, 引用这里
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    const gulp = require('gulp')
    const cleanCSS = require('gulp-clean-css')
    const htmlmin = require('gulp-html-minifier-terser')
    const htmlclean = require('gulp-htmlclean')
    const imagemin = require('gulp-imagemin')
    const terser = require('gulp-terser');

    // minify js
    gulp.task('compress', () =>
    gulp.src(['./public/**/*.js', '!./public/**/*.min.js'])
    .pipe(terser())
    .pipe(gulp.dest('./public'))
    )

    // css
    gulp.task('minify-css', () => {
    return gulp.src('./public/**/*.css')
    .pipe(cleanCSS())
    .pipe(gulp.dest('./public'))
    })

    // 壓縮 public 目錄內 html
    gulp.task('minify-html', () => {
    return gulp.src('./public/**/*.html')
    .pipe(htmlclean())
    .pipe(htmlmin({
    removeComments: true, // 清除 HTML 註釋
    collapseWhitespace: true, // 壓縮 HTML
    collapseBooleanAttributes: true, // 省略布爾屬性的值 <input checked="true"/> ==> <input />
    removeEmptyAttributes: true, // 刪除所有空格作屬性值 <input id="" /> ==> <input />
    removeScriptTypeAttributes: true, // 刪除 <script> 的 type="text/javascript"
    removeStyleLinkTypeAttributes: true, // 刪除 <style> 和 <link> 的 type="text/css"
    minifyJS: true, // 壓縮頁面 JS
    minifyCSS: true, // 壓縮頁面 CSS
    minifyURLs: true
    }))
    .pipe(gulp.dest('./public'))
    })

    // 壓縮 public/uploads 目錄內圖片
    gulp.task('minify-images', async () => {
    gulp.src('./public/assets/**/*.png')
    .pipe(imagemin({
    optimizationLevel: 5, // 類型:Number 預設:3 取值範圍:0-7(優化等級)
    progressive: true, // 類型:Boolean 預設:false 無失真壓縮jpg圖片
    interlaced: false, // 類型:Boolean 預設:false 隔行掃描gif進行渲染
    multipass: false // 類型:Boolean 預設:false 多次優化svg直到完全優化
    }))
    .pipe(gulp.dest('./public/assets'))
    })

    // 執行 gulp 命令時執行的任務
    gulp.task('default', gulp.parallel(
    'compress', 'minify-css', 'minify-html', 'minify-images'
    ))