CSS & Spritesheet generators

I like tools that let me drag and drop a bunch of images into my browser, and generate CSS & a single image for me to integrate into my project.

Stitches is my goto tool for generating spritesheets, and I’ll keep using it for complicated sheets made of many small images.

Today, I found SpritePad which lets me manually place the sprites within the spritesheet. Sometimes I think I can do a better job at making a compact spritesheet than Stitches does, so that’s a useful feature for me. If there are enough images in the spritesheet that manually placing them is too much work, than I can go back to Stitches.

Deploying apps from circleci to Firebase hosting

The command line page of the Firebase documentation doesn’t list all the commands available to you from the firebase-tools npm package. To see them all, install the tools with npm install -g firebase-tools and execute the command firebase in your terminal.

Usage: firebase [options] [command]


  Commands:

    data:get [options]               fetch and print JSON data at the specified path
    data:push [options]  [infile]    add a new JSON object to a list of data in your Firebase
    data:set [options]  [infile]     store JSON data at the specified path via STDIN, arg, or file
    data:remove [options]            remove data from your Firebase at the specified path
    data:update [options]  [infile]  update some of the keys for the defined path in your Firebase
    deploy [options]                       deploy hosting assets and rules for the current app
    deploy:hosting [options]               deploy hosting assets for the current app
    deploy:rules [options]                 deploy security rules for the current app
    disable:hosting [options]              stop serving web traffic to your Firebase Hosting site
    help [command]                         display help information
    init [options]                         set up a Firebase app in the current directory
    list                                   list the Firebases to which you have access
    login                                  log the CLI into Firebase
    login:ci                               generate an access token for use in non-interactive environments
    logout                                 log the CLI out of Firebase
    open [options] [panel]                 open Firebase Hosting URL in browser or jump to a dashboard panel
    prefs:token                            print the currently logged in user's access token
    serve [options]                        start a local server for your static assets

  Options:

    -h, --help         output usage information
    -V, --version      output the version number
    -j, --json         output JSON instead of text, also triggers non-interactive mode
    --token     supply an auth token for this command
    --non-interactive  error out of the command instead of waiting for prompts
    --interactive      force interactive shell treatment even when not detected
    --debug            print verbose debug output and keep a debug log file

The interesting command here is:

    login:ci                               generate an access token for use in non-interactive environments

Run firebase login:ci and follow the instructions to receive an access token like awelkfjw8efwpafh8phaua9 that you can use within circleci to deploy your app.

Inside circleci

Save the access token in circleci’s project settings as an environment variable with a name like FIREBASE_DEPLOY_TOKEN. Then configure circle.yml to use the environment variable inside the deploy step like this:

deployment:
  master:
    branch: master
    commands:
      - firebase deploy --token "$FIREBASE_DEPLOY_TOKEN" --non-interactive

When a build on the master branch succeeds, circleci will use that command to log into Firebase hosting on your behalf (via the access token) and upload the files configured in your firebase.json file.

Links

When Bower install doesn’t work

If you have a bower.json file, and running bower install from the same directory appears to download packages bot not actually put them in your project directory or even create a bower_components folder, check the version number of your package inside bower.json. Sometimes a version number of 0.0.0 prevents packages from installing, but setting the version number to 0.0.1 will make it work.

Reusing an ES6 iterator? Not quite

One you’ve called iterator.next() on an array iterator enough times to make it return the last item with the property done set to true, you can’t make it go back to the first item. But you can grab a new iterator for the same array. I was surprised to find out that:

node
> var users = ['steve', 'bob', 'jill'];
> users[Symbol.iterator]() === users[Symbol.iterator]()
false

The two iterators retrieved are actually 2 different iterators. No need to shallow copy the array to get another iterator. I have a scenario where I want to loop through an array of users of a blogging service and retrieve their RSS feeds every one in a while. This will work:

var users = ['steve', 'bob', 'jill'];
var userIterator = users[Symbol.iterator]();

function oneIteration() {

  var nextItem = userIterator.next();

  if (nextItem.done) {
    userIterator = users[Symbol.iterator]();
    nextItem = userIterator.next();
  }

  // get the rss feed, or just print out the user's name for this example
  console.log(nextItem.value);

  // retrieve the next feed after some amount of time
  setTimeout(oneIteration, 5000);

}

oneIteration();

The iterator can take the place of a userIndex variable that is incremented with each RSS fetch, and reset to 0 at the end of the array. Whether this is better or worse is up to you.

Recording & compressing short screencasts on Windows

Tools I use

  • CamStudio with the lossless codec for screen recording
  • ffmpeg
  • a scripting environment with shell access (I chose node.js) for batch converting

My process

  1. Capture video using Camstudio

    I choose to record 1 window as my region, and compress with the Camstudio lossless codec.

  2. Use ffmpeg via a node.js script to batch convert videos

    This turns them into something that can be played in a web browser

    var fs = require('fs'),
        util = require('util'),
        child_process = require('child_process');
     
    var shellCommand = 'c:\\ffmpeg\\bin\\ffmpeg.exe -i %s -codec:v libx264 -profile:v high -preset slow -b:v 500k -maxrate 500k -bufsize 1000k -threads 0 -y %s';
     
    fs.readdir('./', function (err, paths) {
     
        paths.forEach(function (path) {
     
            // TODO - only convert *.avi files
     
            var command = util.format(shellCommand, path, path.replace('.avi', '.mp4'))
     
            var child = child_process.exec(command, function (error, stdout, stderr) {
                console.log(path, err, stdout, stderr);
            });
     
     
        });
     
    });

    I base my settings on Jemej’s ffmpeg tutorial. The quality and framerate are low since I usually record things that don’t move much like terminal windows.

Ideas for improving

  • this is dumb – it runs every .avi file in the folder through ffmpeg whether it needs or not.
  • are there smarter ways to use all the cores of my CPU?
  • what about watching for .avi file changes/additions with gaze and encoding new .avi files as soon as CamStudio saves them?

#towebperf – performance @ Shopify

Web Performance Toronto @ Shopify

Bryson Gilbert presented techniques his team uses on Shopify’s main marketing site, shopify.com

Techniques

  • 10ms goal for generating pages on the server with Ruby on Rails
    • no database
    • no CMS
    • All content is in fast loading, versionable flat files.
  • marketing assets (icons, images, css, style guides) are stored in a Ruby gem and shared across different Shopify web properties (admin areas tools, internal tools, blogs etc).
    • optimize once, use anywhere
  • HTTP requests – in general, each shopify.com page loads 1 common js and css file (shared across all pages), and sometimes 1 page specific js and css file. 3rd party scripts are loaded if needed
    • the team is investigating inlining the page specific code – it is usually small, and could save 2 HTTP requests
    • some ab testing tools require blocking js to manipulate copy and images before gathering results. This sucks, but there is no choice
    • load as much 3rd part JS asynchronously as possible
    • lazy load images with lazysizes
  • responsive images – using a Ruby gem to make implementing img srcset attribute and lazy image loading easier across all web properties
  • icons – using inline .svg, no icon fonts
  • workflow – set up a Git precommit hook to run imageOptim on images so that unoptimized images never even get into the Git repo
  • fonts – just using @font-face, no inlining
    • just serving woff and woff2, best file size and adequate browser support for their audience (people on slow connections also tend to have browsers that don’t support woff, so they don’t get help up downloading fonts)
    • not optimized yet.
    • this is the hackiest part of optimization
  • going HTTPS only did slow down the site slightly

Measurement

  • SpeedCurve for monitoring performance, relating deploys to changes in performance
  • NewRelic RUM for checking on performance around the world.
  • Shopify is getting into Performance Budgeting by setting relative goals.

Future

  • HTTP2 will change how pages are optimized
  • resource hints will be helpful, but not 100% controllable
  • Better font loading is coming

Building a culture of performance

  • Sharing knowledge with documentation
  • Weekly frontend dev meetings to discuss failures & successes
  • Working performance into discussions early on in design process (can help with photo/image selection)

Other fun stuff (some from Barbara’s new book

  • Etsy publishes quarterly website performance reviews
  • Performance is respect for users. Optimized for their perspective, not your s. Real User Monitoring.
  • think about the 14kb rule
  • use inline CSS for above the fold content
  • loads js as late as possible
  • Latency on mobile – remember cell phones have to initiate a connection to a cell phone tower that is rarely a consistent distance away.
    • JS and AJAX eat mobile batteries fast

Fixing SSL errors on Android Chrome with RapidSSL

Your ‘Order: xxxxxxx Complete’ email from RapidSSL includes links to a bunch of intermediate SSL certificates. Will you install the right one? I have seen installing the incorrect intermediate SSL certificate into the certificate chain cause Chrome on Android to declare a site insecure and block users from accessing it, while every other browser accepts it.

This tool helped me fix the issue: https://ssltools.geotrust.com/checker/views/certCheck.jsp. It pointed out that the wrong intermediate certificate was in the chain, and directed me to download a RapidSSL SHA256 CA - G3 certificate. I chained that with my server’s SSL certificate, and my Android issue went away.

Links

Generating directory structure ASCII art on Windows

Don’t feel left out Windows users, you have a tree command too! Tree documentation is here. I found I couldn’t use it from Git bash, but I could use from the usual Windows command prompt.

Usually you will have to edit the output a bit to make it appropriate for a blog post. It may give too much or too little information. You can see how I used the output in my migrating imakewebsites.ca to Hexo post.

Here’s a raw sample of tree /f from an old Laravel project folder:

C:\WWW\WEEKEND-WARRIORS
│   .gitignore
│   .htaccess
│   changelog.md
│   index.php
│   license.txt
│   php.ini
│   Readme.md
│
├───application
│   │   composers.php
│   │   filters.php
│   │   routes.php
│   │
│   ├───config
│   │   │   .gitignore
│   │   │   application.php
│   │   │   auth.php
│   │   │   cache.php
│   │   │   container.php
│   │   │   database.php
│   │   │   error.php
│   │   │   session.php
│   │   │
│   │   └───adam
│   │           application.php
│   │           error.php
│   │           session.php
│   │
│   ├───controllers
│   │       home.php
│   │
│   ├───language
│   │   └───en
│   │           common.php
│   │           enter.php
│   │           entries.php
│   │           pagination.php
│   │           validation.php
│   │
│   ├───libraries
│   │   │   .gitignore
│   │   │   base_facebook.php
│   │   │   facebook.php
│   │   │   fb_ca_chain_bundle.crt
│   │   │   jsredirect.php
│   │   │   mail.php
│   │   │   maxuploadsize.php
│   │   │   qqfileuploader.php
│   │   │   votebutton.php
│   │   │
│   │   └───phpmailer
│   │           class.phpmailer.php
│   │           class.smtp.php
│   │
│   ├───models
│   │       .gitignore
│   │       contestperiod.php
│   │       contestperioddetail.php
│   │       entry.php
│   │       error.php
│   │       fb.php
│   │       report.php
│   │       user.php
│   │       vote.php
│   │
│   ├───routes
│   │       error.php
│   │       info.php
│   │       submit.php
│   │
│   ├───storage
│   │   └───database
│   │           .gitignore
│   │
│   └───views
│       │   contest-over-page.php
│       │   default.php
│       │   entries.php
│       │   entry.php
│       │   info-page.php
│       │   rules.php
│       │   tab-liked.php
│       │   tab-unliked.php
│       │   thanks.php
│       │   upload-form.php
│       │
│       ├───common
│       │       footer.php
│       │       footerjs.php
│       │       header.php
│       │       nav.php
│       │       share-video.php
│       │       sponsor-likes.php
│       │       sportselect.php
│       │       vimeo-player.php
│       │
│       ├───entries
│       │       entry-details.php
│       │       register-vote.php
│       │       share-button.php
│       │       vote-button.php
│       │       vote-js.php
│       │       voted-already.php
│       │
│       ├───error
│       │       404.php
│       │       500.php
│       │
│       ├───home
│       │       index.php
│       │
│       └───info
│               vote-report-row.php
│               vote-report.php
│
├───laravel
│   │   arr.php
│   │   asset.php
│   │   auth.php
│   │   autoloader.php
│   │   benchmark.php
│   │   blade.php
│   │   config.php
│   │   cookie.php
│   │   core.php
│   │   crypter.php
│   │   facades.php
│   │   file.php
│   │   form.php
│   │   hash.php
│   │   helpers.php
│   │   html.php
│   │   inflector.php
│   │   input.php
│   │   ioc.php
│   │   lang.php
│   │   laravel.php
│   │   memcached.php
│   │   messages.php
│   │   paginator.php
│   │   redirect.php
│   │   redis.php
│   │   request.php
│   │   response.php
│   │   section.php
│   │   str.php
│   │   uri.php
│   │   url.php
│   │   validator.php
│   │   view.php
│   │
│   ├───cache
│   │   │   manager.php
│   │   │
│   │   └───drivers
│   │           apc.php
│   │           driver.php
│   │           file.php
│   │           memcached.php
│   │           redis.php
│   │
│   ├───config
│   │       ascii.php
│   │       mimes.php
│   │
│   ├───database
│   │   │   connection.php
│   │   │   expression.php
│   │   │   manager.php
│   │   │   query.php
│   │   │
│   │   ├───connectors
│   │   │       connector.php
│   │   │       mysql.php
│   │   │       postgres.php
│   │   │       sqlite.php
│   │   │
│   │   ├───eloquent
│   │   │       hydrator.php
│   │   │       model.php
│   │   │
│   │   └───grammars
│   │           grammar.php
│   │           mysql.php
│   │
│   ├───routing
│   │       controller.php
│   │       filter.php
│   │       loader.php
│   │       route.php
│   │       router.php
│   │
│   └───session
│       │   payload.php
│       │
│       └───drivers
│               apc.php
│               cookie.php
│               database.php
│               driver.php
│               factory.php
│               file.php
│               memcached.php
│               redis.php
│               sweeper.php
│
└───public
    │   .htaccess
    │   index.php
    │
    ├───css
    │       .gitignore
    │       fileuploader.css
    │       global.css
    │       tab.css
    │       thanks.css
    │
    ├───img
    │       .gitignore
    │       header.png
    │       loader.gif
    │       prizes-collage.jpg
    │       sponsors-signs.jpg
    │       square-logo.png
    │
    └───js
            .gitignore
            fileuploader.js
            global.js
            submit-entry.js
            vote.js

Migrating imakewebsites.ca to Hexo

Today I migrated http://imakewebsites.ca from a home-made PHP and MySQL CMS to the Hexo static blogging engine. Moving content over was easy, but it took some time to rebuild my theme and figure out a few quirks when using Hexo for a site without a blog component.

Why migrate?

I rarely update imakewebsites.ca, so moving it to static hosting makes life easier and takes a microscopic amount of load off the web server. I also want to learn what working with Hexo is like.

Pages

Like WordPress, Hexo supports pages, which are like blog posts but without chronological order and their own template. Hiding the blog related functionality by removing links the template engine was easy, but I discovered you must have at least 1 blog post in a site, otherwise the site’s homepage (index.html) will not render. I had to keep a dummy blog post in the site which is an orphaned page (no links to it)

Preserving URLs from my custom site was easy. I ended up with a directory structure like this:


public │ .htaccess │ favicon.ico │ index.html │ ├───2015 | (hidden dummy blog posts) │ ├───404 │ index.html │ ├───archives │ (hidden dummy blog posts) │ ├───contact │ (contact specific images) │ index.html │ ├───css │ style.css | ├───free-zen-cart-templates │ (template specific images and files) │ index.html │ ├───img │ (images used all over the site) │ ├───making-downloadable-products | (tutorial specific images and files) │ index.html │ ├───portfolio │ (portfolio specific images used on the homepage) │ └───zen-cart-layouts (tutorial specific images) index.html

I moved my page specific images out of a general /images folder and into the folder associated with each page. The content of each page is in each index.html, but browsers won’t need to show that in the URL. The folder names match the names of my old pages from the CMS.

It would be nice to disable the archive and blog page generation entirely, but their overhead is tiny so it isn’t a big problem.

Rendering HTML

Hexo encourages you to write content in Markdown. However, I already wrote content for the site in HTML. If you supply blog posts and page content with a .html extension, Hexo will use it and not run it through any sort of generator. Your raw HTML gets integrated into the site smoothly.

Moving the theme

I took the included Landscape theme and overwrote its HTML with content from my own site. I removed code for some blogging features. EJS Package for Sublime Text came in very handy for editing the theme’s files.

Deployment Process

hexo-deploy-ftpsync looks like is being smart and only uploading files that have changed, but I haven’t confirmed that yet. I wish I didn’t have to save my FTP name and password inside _config.yml to use it.

The fun part is when I make changes to the site, I can generate the new /public directory of the project and deploy with one terminal command:

hexo generate && hexo deploy

I like that!

Compile nginx with RTMP support on Raspbian

Here’s a bash script. Put this into a new file, ~/install-nginx-rtmp.sh:

 #!/bin/bash
sudo apt-get update
sudo apt-get install nginx -y
sudo apt-get remove nginx -y
sudo apt-get clean nginx -y
sudo apt-get install -y curl build-essential libpcre3-dev libpcre++-dev zlib1g-dev libcurl4-openssl-dev libssl-dev
mkdir nginx-build
cd nginx-build
wget http://nginx.org/download/nginx-1.7.5.tar.gz
wget https://github.com/arut/nginx-rtmp-module/archive/master.zip
tar -zxvf nginx-1.7.5.tar.gz
unzip master.zip
cd nginx-1.7.5
./configure --prefix=/var/www --sbin-path=/usr/sbin/nginx  --conf-path=/etc/nginx/nginx.conf --pid-path=/var/run/nginx.pid --error-log-path=/var/log/nginx/error.log --http-log-path=/var/log/nginx/access.log --with-http_ssl_module --without-http_proxy_module --add-module=/home/pi/nginx-build/nginx-rtmp-module-master

make
sudo make install
echo "made it to the end"

execute it like this:

sh install.sh

Notice it install nginx from the package manager, and then removes it. This will leave some useful files behind for running nginx as a service.

This script is made of commands I learned from these posts, which explain what’s going on line-by-line.