loading...

Dockerize The Small Things

drk profile image Derek Reynolds Updated on ・7 min read

tl;dr

  • Some familiarity with react-native, yarn, Docker and the command line will be helpful.
  • Workflows that run in development environments could become problematic when devs use different machines, OSes, versions.
  • Using Docker for common development tasks (like generating code using native dependencies) is dope even though there's extra overhead in using Docker.
  • Check out the full config/setup at the bottom.

Setup

Docker has many applications around automating development environments and production environments. It's often not intuitive or easy to work with, but the trade-offs are typically worth it. With such a complex and powerful tool that's somewhat difficult to learn: is it worthwhile for automating smaller tasks? I think so.

I recently hit an issue generating icon fonts for use in a React Native application. For mostly performance and simplicity it's recommended to use fonts over SVG for icons. That's what I've seen and been told anyway 🤷‍♂️. React Native (at least iOS as a platform) can work with True Type Font (.ttf) files. The setup I'm working with is a directory of separate SVG files per icon. The sister www project already has some tooling around converting those SVG files in to all necessary font files, CSS and preview HTML using the FontCustom ruby library. Pretty nifty.

Let's Go

With everything installed and ready to go I run the fontcustom compile command and everything generates accordingly. I check the web preview file generated and the icons look dope. As part of my config I also generate a React component file that uses react-native-vector-icons under the hood for rendering the icons from the generated .ttf font file. Run the app to check out some of the icons and they're great. Feels like I cheated. On closer inspection though the icons were slightly off in a few cases… Funky.


The times icon looks a bit thinner when rendered in iOS vs the web.

Debugging

Where is this happening? Does react-native-vector-icons have a hard time generating glyphs from the .ttf file? Are all fonts rendering thinner? Can I hack this at the react-native layer? Is the .ttf file any different than the other files like .woff or .woff2?

After attempting to address in code I wanted to verify the .ttf file used was producing the same glyphs as the other file formats. I edited the generated preview html file by commenting out the other file formats in CSS and sure enough the glyph was thinner in a browser as well when using the .ttf file.

/* Icon Font: hc-awesome */

@font-face {
  font-family: "hc-awesome";
  src: url("./hc-awesome.eot");
  src: url("./hc-awesome.eot?#iefix") format("embedded-opentype"),
       /* url("./hc-awesome.woff2") format("woff2"),
       url("./hc-awesome.woff") format("woff"), */
       url("./hc-awesome.ttf") format("truetype");
       /* url("./hc-awesome.svg#hc-awesome") format("svg"); */
  font-weight: normal;
  font-style: normal;
}

Up to this point I had been running the same fontcustom compile command for the web project on my host OSX machine. The FontCustom lib hasn't been updated in a while so I was wondering if it was buggy on newer versions of OSX. This prompted me to experiment with running the command in a Linux environment. Specifically in Docker.

Dockerizing

The web project is already using Docker for more "Docker-ish" problems like running a local database so it wasn't a huge leap to leverage that infrastructure for a much smaller task like generating fonts. I added a Dockerfile, co-locating it next to the directory with my SVG source files.

# Used the Docker community ruby image
FROM ruby:2.6

# Installed all the same dependencies from our host machine, but using Linux flavors.
RUN apt-get -y update && apt-get -y upgrade
RUN apt-get -y install zlib1g-dev fontforge
RUN git clone https://github.com/bramstein/sfnt2woff-zopfli.git sfnt2woff-zopfli && cd sfnt2woff-zopfli && make && mv sfnt2woff-zopfli /usr/local/bin/sfnt2woff
RUN git clone --recursive https://github.com/google/woff2.git && cd woff2 && make clean all && mv woff2_compress /usr/local/bin/ && mv woff2_decompress /usr/local/bin/
RUN gem install fontcustom

# Copies the directory containing the SVG files in to the built container
COPY . .

Now we need to build a container to run the fontcustom compile command in.

docker build -t hc/hc-awesome . 

And finally running the command.

docker run
    \ -v $(realpath ./generated):/generated 
    \ -t hc/hc-awesome 
    \ fontcustom compile

A couple things to note about that docker run command.

  • -v is the option for configuring a Volume which we can think of as mirroring a directory from your host machine to a directory in the container.
  • realpath is necessary because the -v option expects an absolute path.
  • The -t option is specifying the container we previously built.
  • Finally we're providing the command to be run in the container fontcustom compile.

So here we're saying "whatever data (files) show up in the /generated directory in the container should also show up in the ./generated directory that's co-located with the Dockerfile.

If everything worked out ok we should see the same font files, CSS and HTML preview in our host machine's ./generated directory.

The React Component

Earlier I skipped how this is done, but once we have our generated files from fontcustom we can run the the generate-icon command from react-native-vector-icons on our host machine, not Docker, to create our React component.

yarn generate-icon 
  \ Icon/generated/hc-awesome.css 
  \ --componentName=hc-awesome 
  \ --fontFamily=hc-awesome 
  \ -p .hc-awesome- 
  \ -o Icon/generated/HcAwesomeIcon.tsx 
  \ -t Icon/lib/iconSet.tpl

Now when we run our app we should see a nice accurate glyph…

Much better!

Taking It Further

Even as I was writing this I was thinking of alternative approaches to Dockerizing this task. There's awesome tools/services like IcoMoon.io or Fontello which provide a more manual method of generating icons as well as an API (Fontello). I was optimizing for not introducing too much new tech (I'd argue I introduced no new tech) and solving quickly which meant not going down a rabbit hole of a different flow (I'm using the same commands just in Docker). Now both projects can benefit from a consistent icon compilation process no matter who generates them. Going forward I think it'd be rad to automate this a bit more and have a service that can fetch the latest icons from GitHub and output all the necessary files for any project that needs them.

Config

package.json
Icon/
  generated/
  svgs/
  lib/
    iconSet.tpl
  Dockerfile
  fontcustom.yml

package.json

This is a truncated version showing how we can encapsulate the commands as a single yarn script.

{
  "scripts": {
    "hcawesome": "hcawesome:build && yarn hcawesome:compile && yarn hcawesome:clean && yarn hcawesome:generate",
    "hcawesome:build": "docker build -t hc/hc-awesome ./Icon",
    "hcawesome:compile": "docker run -v $(realpath ./Icon/generated):/generated -t hc/hc-awesome fontcustom compile",
    "hcawesome:clean": "cd ./Icon/generated && rm *.eot *.svg *.woff *.woff2",
    "hcawesome:generate": "yarn generate-icon Icon/generated/hc-awesome.css --componentName=hc-awesome --fontFamily=hc-awesome -p .hc-awesome- -o Icon/generated/HcAwesomeIcon.tsx -t Icon/lib/iconSet.tpl"
  }
}

Dockerfile

# Used the Docker community ruby image
FROM ruby:2.6

# Installed all the same dependencies from our host machine, but using Linux flavors.
RUN apt-get -y update && apt-get -y upgrade
RUN apt-get -y install zlib1g-dev fontforge
RUN git clone https://github.com/bramstein/sfnt2woff-zopfli.git sfnt2woff-zopfli && cd sfnt2woff-zopfli && make && mv sfnt2woff-zopfli /usr/local/bin/sfnt2woff
RUN git clone --recursive https://github.com/google/woff2.git && cd woff2 && make clean all && mv woff2_compress /usr/local/bin/ && mv woff2_decompress /usr/local/bin/
RUN gem install fontcustom

# Copies the directory containing the SVG files in to the built container
COPY . .

fontcustom.yml

# =============================================================================
# Font Custom Configuration
#   This file should live in the directory where you run `fontcustom compile`.
#   For more info, visit <https://github.com/FontCustom/fontcustom>.
# =============================================================================

# -----------------------------------------------------------------------------
# Project Info
# -----------------------------------------------------------------------------

# The font's name. Also determines the file names of generated templates.
font_name: hc-awesome

# Format of CSS selectors. {{glyph}} is substituted for the glyph name.
css_selector: .hc-awesome-{{glyph}}

# Generate fonts without asset-busting hashes.
no_hash: true

# Encode WOFF fonts into the generated CSS.
base64: false

# Forces compilation, even if inputs have not changed
#force: true

# Display (possibly useful) debugging messages.
#debug: true

# Hide status messages.
#quiet: true

# Copyright information.
#copyright:

# -----------------------------------------------------------------------------
# Input / Output Locations
#   You can save generated fonts, CSS, and other files to different locations
#   here. Font Custom can also read input vectors and templates from different
#   places.
#
#   NOTE:
#   - Be sure to preserve the whitespace in these YAML hashes.
#   - INPUT[:vectors] and OUTPUT[:fonts] are required. Everything else is
#     optional.
#   - Specify output locations for custom templates by including their file
#     names as the key.
# -----------------------------------------------------------------------------

input:
  vectors: ./svgs
#  templates: my/templates

output:
  fonts: ./generated
  css: ./generated
#  preview: app/views/styleguide
#  my-custom-template.yml: path/to/template/output

# -----------------------------------------------------------------------------
# Templates
#   A YAML array of templates and files to generate alongside fonts. Custom
#   templates should be saved in the INPUT[:templates] directory and referenced
#   by their base file name.
#
#   For Rails and Compass templates, set `preprocessor_path` as the relative
#   path from OUTPUT[:css] to OUTPUT[:fonts]. By default, these are the same
#   directory.
#
#   Included in Font Custom: preview, css, scss, scss-rails
#   Default: css, preview
# -----------------------------------------------------------------------------

#templates:
#- scss-rails
#- preview
#- my-custom-template.yml

#preprocessor_path: ../fonts/

# -----------------------------------------------------------------------------
# Font Settings (defaults shown)
# -----------------------------------------------------------------------------

# Size (in pica points) for which your font is designed.
#font_design_size: 16

# The em size. Setting this will scale the entire font to the given size.
#font_em: 512

# The font's ascent and descent. Used to calculate the baseline.
#font_ascent: 448
#font_descent: 64

# Horizontally fit glyphs to their individual vector widths.
autowidth: true

lib/iconSet.tpl

/**
 * ${componentName} icon set component.
 * Usage: <${componentName} name="icon-name" size={20} color="#4F8EF7" />
 */

import createIconSet from 'react-native-vector-icons/lib/create-icon-set'
export const glyphMap = ${glyphMap}

const iconSet = createIconSet(glyphMap, '${fontFamily}', '${componentName}.ttf')

export default iconSet

Discussion

pic
Editor guide