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
Top comments (0)