Slide 1

Slide 1 text

Cache busting using Laravel Mix and CDN Laracon EU 2019 in Amsterdam

Slide 2

Slide 2 text

Ryuta Hamasaki @avosalmon

Slide 3

Slide 3 text

No content

Slide 4

Slide 4 text

No content

Slide 5

Slide 5 text

No content

Slide 6

Slide 6 text

No content

Slide 7

Slide 7 text

Cache busting using Laravel Mix and CDN

Slide 8

Slide 8 text

webpack.mix.js const mix = require('laravel-mix'); mix.js('resources/js/app.js', 'public/js') .sass('resources/sass/app.scss', 'public/css');

Slide 9

Slide 9 text

Install webpack-s3-plugin $ yarn add -D webpack-s3-plugin

Slide 10

Slide 10 text

const mix = require('laravel-mix'); const s3Plugin = require('webpack-s3-plugin'); mix.js('resources/js/app.js', 'public/js') .sass('resources/sass/app.scss', 'public/css'); mix.webpackConfig({ plugins: [ new s3Plugin({ // }) ] });

Slide 11

Slide 11 text

const mix = require('laravel-mix'); const s3Plugin = require('webpack-s3-plugin'); mix.js('resources/js/app.js', 'public/js') .sass('resources/sass/app.scss', 'public/css'); mix.webpackConfig({ plugins: [ new s3Plugin({ // }) ] });

Slide 12

Slide 12 text

const mix = require('laravel-mix'); const s3Plugin = require('webpack-s3-plugin'); mix.js('resources/js/app.js', 'public/js') .sass('resources/sass/app.scss', 'public/css'); mix.webpackConfig({ plugins: [ new s3Plugin({ // }) ] });

Slide 13

Slide 13 text

const mix = require('laravel-mix'); const s3Plugin = require('webpack-s3-plugin'); mix.js('resources/js/app.js', 'public/js') .sass('resources/sass/app.scss', 'public/css'); if (mix.inProduction()) { mix.webpackConfig({ plugins: [ new s3Plugin({ // }) ] }); }

Slide 14

Slide 14 text

new s3Plugin({ include: /.*\.(css|js)$/, s3Options: { accessKeyId: process.env.AWS_ACCESS_KEY_ID, secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY, region: process.env.AWS_DEFAULT_REGION, }, s3UploadOptions: { Bucket: process.env.AWS_BUCKET, CacheControl: 'public, max-age=31536000' }, directory: 'public' })

Slide 15

Slide 15 text

new s3Plugin({ include: /.*\.(css|js)$/, s3Options: { accessKeyId: process.env.AWS_ACCESS_KEY_ID, secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY, region: process.env.AWS_DEFAULT_REGION, }, s3UploadOptions: { Bucket: process.env.AWS_BUCKET, CacheControl: 'public, max-age=31536000' }, directory: 'public' })

Slide 16

Slide 16 text

new s3Plugin({ include: /.*\.(css|js)$/, s3Options: { accessKeyId: process.env.AWS_ACCESS_KEY_ID, secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY, region: process.env.AWS_DEFAULT_REGION, }, s3UploadOptions: { Bucket: process.env.AWS_BUCKET, CacheControl: 'public, max-age=31536000' }, directory: 'public' })

Slide 17

Slide 17 text

new s3Plugin({ include: /.*\.(css|js)$/, s3Options: { accessKeyId: process.env.AWS_ACCESS_KEY_ID, secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY, region: process.env.AWS_DEFAULT_REGION, }, s3UploadOptions: { Bucket: process.env.AWS_BUCKET, CacheControl: 'public, max-age=31536000' }, directory: 'public' })

Slide 18

Slide 18 text

$ yarn dev

Slide 19

Slide 19 text

$ yarn prod

Slide 20

Slide 20 text

app.js

Slide 21

Slide 21 text

cache-control: public, max-age=31536000 Cache app.js app.js

Slide 22

Slide 22 text

app.js

Slide 23

Slide 23 text

app.js?id=xyz Cache Busting

Slide 24

Slide 24 text

const mix = require('laravel-mix'); const s3Plugin = require('webpack-s3-plugin'); mix.js('resources/js/app.js', 'public/js') .sass('resources/sass/app.scss', 'public/css'); if (mix.inProduction()) { mix.version(); mix.webpackConfig({ plugins: [ new s3Plugin({ // }) ] }); }

Slide 25

Slide 25 text

const mix = require('laravel-mix'); const s3Plugin = require('webpack-s3-plugin'); mix.js('resources/js/app.js', 'public/js') .sass('resources/sass/app.scss', 'public/css'); if (mix.inProduction()) { mix.version(); mix.webpackConfig({ plugins: [ new s3Plugin({ // }) ] }); }

Slide 26

Slide 26 text

mix-manifest.json { "/js/app.js": "/js/app.js?id=731163b8f50084f947b6", "/css/app.css": "/css/app.css?id=33e746eff21c53ed136b" }

Slide 27

Slide 27 text

mix-manifest.json { "/js/app.js": "/js/app.js?id=731163b8f50084f947b6", "/css/app.css": "/css/app.css?id=33e746eff21c53ed136b" }

Slide 28

Slide 28 text

// blade template => /js/app.js?id=731163b8f50084f947b6 Versioned file name mix('/js/app.js')

Slide 29

Slide 29 text

How do we reference assets URL on the CDN?

Slide 30

Slide 30 text

No content

Slide 31

Slide 31 text

// .env ASSET_URL=https://cdn.something.com

Slide 32

Slide 32 text

// .env ASSET_URL=https://cdn.something.com 'asset_url' => env('ASSET_URL', null) // app.php

Slide 33

Slide 33 text

// .env => https://cdn.something.com/js/app.js?id=731163b8f50084f947b6 ASSET_URL=https://cdn.something.com 'asset_url' => env('ASSET_URL', null) // app.php asset(mix('/js/app.js')) // blade template

Slide 34

Slide 34 text

No content

Slide 35

Slide 35 text

No content

Slide 36

Slide 36 text

// .env ASSET_URL= APP_URL=http://localhost

Slide 37

Slide 37 text

// .env => http://localhost/js/app.js?id=731163b8f50084f947b6 ASSET_URL= APP_URL=http://localhost asset(mix('/js/app.js')) // blade template

Slide 38

Slide 38 text

No content

Slide 39

Slide 39 text

No content

Slide 40

Slide 40 text

PITFALL OF QUERY STRING VERSIONING

Slide 41

Slide 41 text

Some CDNs ignore query string

Slide 42

Slide 42 text

app.js?id=1234 CloudFront app.js Origin app.js

Slide 43

Slide 43 text

CloudFront Origin app.js app.js app.js?id=1234 app.js?id=1234 app.js Cached! Cached!

Slide 44

Slide 44 text

CloudFront Origin app.js app.js app.js?id=1234

Slide 45

Slide 45 text

app.js?id=5678 CloudFront Origin Updated app.js?id=1234 app.js app.js

Slide 46

Slide 46 text

app.js?id=5678 CloudFront Origin Updated app.js?id=5678 app.js app.js stale content

Slide 47

Slide 47 text

No content

Slide 48

Slide 48 text

No content

Slide 49

Slide 49 text

No content

Slide 50

Slide 50 text

No content

Slide 51

Slide 51 text

app.js?id=1234 CloudFront app.js?id=1234 Origin app.js

Slide 52

Slide 52 text

CloudFront Origin app.js app.js?id=1234 app.js?id=1234 app.js?id=1234 Cached! Cached! app.js?id=1234

Slide 53

Slide 53 text

CloudFront Origin app.js app.js?id=1234 app.js?id=1234

Slide 54

Slide 54 text

app.js?id=5678 CloudFront Origin Updated app.js?id=5678 app.js?id=1234 app.js?id=1234 app.js

Slide 55

Slide 55 text

CloudFront fresh content Origin Updated app.js?id=5678 app.js?id=5678 app.js?id=5678 app.js?id=5678 app.js Cached! Cached!

Slide 56

Slide 56 text

No content

Slide 57

Slide 57 text

No content

Slide 58

Slide 58 text

In some cases, S3 might return previous version of the object

Slide 59

Slide 59 text

https://docs.aws.amazon.com/AmazonS3/latest/dev/Introduction.html#ConsistencyModel

Slide 60

Slide 60 text

app.js CloudFront Origin app.js?id=1234

Slide 61

Slide 61 text

app.js CloudFront Origin app.js?id=1234 app.js?id=1234 Cached!

Slide 62

Slide 62 text

app.js CloudFront Origin app.js?id=1234

Slide 63

Slide 63 text

app.js CloudFront Origin app.js?id=5678 Update has not been propagated yet app.js?id=1234

Slide 64

Slide 64 text

app.js Origin OLD DATA app.js?id=5678 app.js?id=5678 CloudFront

Slide 65

Slide 65 text

Filename Based Versioning

Slide 66

Slide 66 text

CloudFront Origin app.1234.js

Slide 67

Slide 67 text

CloudFront Origin app.5678.js New app.1234.js

Slide 68

Slide 68 text

No content

Slide 69

Slide 69 text

Install laravel-mix-versionhash $ yarn add -D laravel-mix-versionhash

Slide 70

Slide 70 text

const mix = require('laravel-mix'); const s3Plugin = require('webpack-s3-plugin'); require('laravel-mix-versionhash'); mix.js('resources/js/app.js', 'public/js') .sass('resources/sass/app.scss', 'public/css'); if (mix.inProduction()) { mix.version(); mix.webpackConfig({ plugins: [ new s3Plugin({ // }) ] }); }

Slide 71

Slide 71 text

const mix = require('laravel-mix'); const s3Plugin = require('webpack-s3-plugin'); require('laravel-mix-versionhash'); mix.js('resources/js/app.js', 'public/js') .sass('resources/sass/app.scss', 'public/css'); if (mix.inProduction()) { mix.version(); mix.webpackConfig({ plugins: [ new s3Plugin({ // }) ] }); }

Slide 72

Slide 72 text

const mix = require('laravel-mix'); const s3Plugin = require('webpack-s3-plugin'); require('laravel-mix-versionhash'); mix.js('resources/js/app.js', 'public/js') .sass('resources/sass/app.scss', 'public/css'); if (mix.inProduction()) { mix.versionHash(); mix.webpackConfig({ plugins: [ new s3Plugin({ // }) ] }); }

Slide 73

Slide 73 text

mix-manifest.json { "/js/app.js": "/js/app.104ee8.js", "/css/app.css": "/css/app.bcdfca.css" }

Slide 74

Slide 74 text

mix-manifest.json { "/js/app.js": "/js/app.104ee8.js", "/css/app.css": "/css/app.bcdfca.css" }

Slide 75

Slide 75 text

No content

Slide 76

Slide 76 text

Is this the only way to build assets for Laravel?

Slide 77

Slide 77 text

Webpack

Slide 78

Slide 78 text

Webpack - Full Customization

Slide 79

Slide 79 text

Webpack - Full Customization - Filename based versioning → Easy

Slide 80

Slide 80 text

Webpack - Full Customization - Filename based versioning → Easy - Steep learning curve

Slide 81

Slide 81 text

JS Framework CLIs

Slide 82

Slide 82 text

JS Framework CLIs - Standalone SPA

Slide 83

Slide 83 text

JS Framework CLIs - Standalone SPA - Testing, Linting, TypeScript, PWA, etc

Slide 84

Slide 84 text

JS Framework CLIs - Standalone SPA - Filename based versioning by default - Testing, Linting, TypeScript, PWA, etc

Slide 85

Slide 85 text

Laracon 2018 - Vue CLI Overview By Evan You

Slide 86

Slide 86 text

WRAP UP

Slide 87

Slide 87 text

WRAP UP - Query string versioning vs Filename based versioning

Slide 88

Slide 88 text

WRAP UP - If you use a CDN, stick to filename based versioning - Query string versioning vs Filename based versioning

Slide 89

Slide 89 text

WRAP UP - Other options to achieve filename based versioning → Webpack or JS Framework CLIs - If you use a CDN, stick to filename based versioning - Query string versioning vs Filename based versioning

Slide 90

Slide 90 text

THANK YOU https://github.com/avosalmon/laravel-mix-cdn @avosalmon