I've been using ember-cli
to develop
watch.canary.io and I'm deploying it to S3. I
started by just manually copying the files into my bucket, but I finally
decided to set up a real automated deployment process. Here's how I
set up grunt
to allow for easy command line based deployment.
First I looked for a grunt extension for deploying to S3. I found
several, but grunt-s3
seems to be the most active, so I went with
that. So then I installed grunt
and grunt-s3
into my project.
npm install --save-dev grunt
npm install --save-dev grunt-s3
Next I created a file called aws-keys.json
in the root of my project
to hold my S3 credentials and the name of the bucket I want to deploy to.
{
"AWSAccessKeyId": "AK.....",
"AWSSecretKey": "sooooper-secret-stuff",
"bucket":"watch.canary.io"
}
Since I don't want this info to be puclicly
available in the GitHub repo, I added this file to .gitignore
.
echo "aws-keys.json" >> .gitignore
Finally I created Gruntfile.js
to tell grunt how what to do.
module.exports = function(grunt) {
grunt.initConfig({
// Load the S3 credentials from aws-keys.json
aws: grunt.file.readJSON('aws-keys.json'),
// Pass config options to 'grunt-s3'
s3: {
options: {
key: '<%= aws.AWSAccessKeyId %>',
secret: '<%= aws.AWSSecretKey %>',
bucket: '<%= aws.bucket %>',
access: 'public-read',
headers: {
// Two Year cache policy (1000 * 60 * 60 * 24 * 730)
"Cache-Control": "max-age=630720000, public",
"Expires": new Date(Date.now() + 63072000000).toUTCString()
}
},
prod: {
upload: [
{
src: 'dist/index.html',
dest: 'index.html'
},
{
src: 'dist/assets/*',
dest: 'assets/'
}
]
}
} // end s3
}); // end grunt.initConfig
// tell grunt to load and use the grunt-s3 extension
grunt.loadNpmTasks('grunt-s3');
// Default task(s).
grunt.registerTask('default', ['s3']);
};
Now I can run grunt s3
(or just grunt
since I set the default task)
and all the compiled project files in dist
will be uploaded to S3. At
this point the files are allowed to be cached for 2 years, which means
that people will need to do a hard refresh to see any updates. This is
less than ideal.
To fix the hard reload issue I'm going to add asset fingerprinting, so
that the paths to asset files will be updated any time that the
underlying assets change. This will allow me to still use a long cache
timeout on the assets, and then just reduce the timeout for index.html
so that visitors almost always get a fresh copy of index.html
, which
will always be pointing to the latest version of the assets.
grunt-hashres
is a grunt
extension that can handle renaming asset files with a fingerprint, and
can also rewrite any references to those files in index.html
.
So, next I installed grunt-hashres
.
npm install --save-dev grunt-hashres
Then I updated Gruntfile.js
to use and configure that extension.
module.exports = function(grunt) {
grunt.initConfig({
aws: grunt.file.readJSON('aws-keys.json'),
s3: {
// previously discussed S3 config goes here
}, // end s3
hashres: {
prod: {
// files to be fingerprinted
src: [
'dist/assets/app.js',
'dist/assets/vendor.css',
'dist/assets/app.css'
],
// file that needs references rewritten
dest: 'dist/index.html',
}
} // end hashres
});
grunt.loadNpmTasks('grunt-s3');
grunt.loadNpmTasks('grunt-hashres');
// Default task(s).
grunt.registerTask('default', ['hashres','s3']);
};
Now the only thing left to do is to update the grunt-s3
config to set
a much lower cache timeout policy for index.html
. This can be done
easily by passing an options
block to the portion of the upload
config for index.html
. The block for dist/assets/*
will just use
the default timeout that I set at the top of the s3 configuration
section (2 years).
// ...
upload: [
{
src: 'dist/index.html',
dest: 'index.html',
options: {
headers: {
// 1 minute cache policy (1000 * 60)
"Cache-Control": "max-age=60000, public",
"Expires": new Date(Date.now() + 60000).toUTCString()
}
}
},
{
src: 'dist/assets/*',
dest: 'assets/'
}
]
// ...
Now whenever I'm ready to deploy a new release it's as easy as:
ember build
grunt
ember build
works just like normal, aggregating/compling the JS, CSS
and SASS files into production ready files. Then grunt
will
fingerprint the assets files, rewrite the references to them in
index.html
and upload all of the production files to S3.
You can see my full Gruntfile.js
here