Preact on Rails with Webpack

by on

I needed to brush up my front-end skills, since I’ve been in Gopherland for a couple years now, so I decided to write a few tutorials about using Rails with modern front-end frameworks. I’m learning this as I go, so please feel free to comment or message me on Twitter if you have some tips about the setup.

In this article, we’re going to walk through how to set up Rails 5.1 with Webpack to use Preact as a front-end framework. Rails 5.1 is the first version of Rails to support Webpack as part of the application scaffolding process, which makes it easy (well … easier) to get started with.

Preact is an alternate implementation of React using native dom features and with a focus on being small and light and lower in dependencies. I like the idea of Preact because it seems more approachable because of its smaller source code. I know I can just dig through it if I encounter something unexpected. I mean, look at its source code (seriously, open that link, that’s Preact without ES6 and JSX).

Prerequisites

In order to run through this tutorial, you’ll need:

Creating a new Rails App with Webpack

The first thing we need to do is make a new Rails app with Webpack. We’re also going to remove all the things we won’t need, since we’ll be using Preact. Here’s how we set up our application:

rails new hello \
  --skip-action-cable \
  --skip-sprockets \
  --skip-coffee \
  --skip-javascript \
  --skip-turbolinks \
  --webpack

We’re going to skip action cable, sprockets, coffeescript, rails javascript helpers, and turbolinks. And we’re going to turn on Webpack.

Using Webpacker @ master

Heads up! There’s currently a bug with automatic compilation so we’re going to use Webpack @ master. Once a release > 2.0.0 of rails/webpacker is released, you should be able to use that instead of this step.

Edit our Gemfile, and modify the webpacker line to use master from GitHub:

gem 'webpacker', github: 'rails/webpacker'

Then update it:

bundle update webpacker

As of the writing of this post, I’m on master@691389f.

Finally, reinstall webpacker:

rails webpacker:install

(you can answer a to allow all updates)

Trimming the Fat

Webpacker’s install is great because it brings in everything you might need. But, we can trim down a bunch of its dependencies since we will just be using Preact. First off, lets remove some vestigal asset pipeline folders:

rm -rf app/assets/ lib/assets/

Also, edit app/views/layouts/application.html.erb and remove the stylesheet tag and replace it with a webpack tag:

<!DOCTYPE html>
<html>
  <head>
    <title>Hello</title>
    <%= csrf_meta_tags %>

    <%= javascript_pack_tag 'application' %>
  </head>

  <body>
    <%= yield %>
  </body>
</html>

Next, let’s check out our package.json:

{
  "name": "hello",
  "private": true,
  "dependencies": {
    "autoprefixer": "^7.1.3",
    "babel-core": "^6.26.0",
    "babel-loader": "7.x",
    "babel-plugin-syntax-dynamic-import": "^6.18.0",
    "babel-plugin-transform-class-properties": "^6.24.1",
    "babel-plugin-transform-object-rest-spread": "^6.26.0",
    "babel-polyfill": "^6.26.0",
    "babel-preset-env": "^1.6.0",
    "coffee-loader": "^0.8.0",
    "coffee-script": "^1.12.7",
    "compression-webpack-plugin": "^1.0.0",
    "css-loader": "^0.28.5",
    "extract-text-webpack-plugin": "^3.0.0",
    "file-loader": "^0.11.2",
    "glob": "^7.1.2",
    "js-yaml": "^3.9.1",
    "node-sass": "^4.5.3",
    "path-complete-extname": "^0.1.0",
    "postcss-cssnext": "^3.0.2",
    "postcss-loader": "^2.0.6",
    "postcss-smart-import": "^0.7.5",
    "precss": "^2.0.0",
    "rails-erb-loader": "^5.2.1",
    "resolve-url-loader": "^2.1.0",
    "sass-loader": "^6.0.6",
    "style-loader": "^0.18.2",
    "webpack": "^3.5.5",
    "webpack-manifest-plugin": "^1.3.1",
    "webpack-merge": "^4.1.0"
  },
  "devDependencies": {
    "webpack-dev-server": "^2.7.1"
  }
}

Wow, that’s a lot of stuff! We can remove the coffeescript packages since we’ll be using ES6:

yarn remove coffee-loader coffee-script

We can also remove the rails-erb-loader since we won’t be using any erb templates in our javascript:

yarn remove rails-erb-loader

Now we can remove those loaders from webpack:

rm config/webpack/loaders/coffee.js config/webpack/loaders/erb.js

And in config/webpacker.yml we can remove the following extensions:

Just to make sure you didn’t break anything, you can run:

./bin/webpack

to bundle up your assets. Mine looks like this:

Hash: 77d3b57fb7806bb9009c
Version: webpack 3.5.5
Time: 262ms
                              Asset      Size  Chunks             Chunk Names
application-8f8bdd9f4ff51391ec46.js   4.47 kB       0  [emitted]  application
                      manifest.json  68 bytes          [emitted]
   [0] ./app/javascript/packs/application.js 515 bytes {0} [built]

Installing Preact

Installing Preact is as easy as:

$ yarn add preact
yarn add v0.27.5
[1/4] Resolving packages...
[2/4] Fetching packages...
[3/4] Linking dependencies...
[4/4] Building fresh packages...
success Saved lockfile.
success Saved 1 new dependency.
└─ preact@8.2.5
Done in 3.96s.

Of course, that doesn’t do anything to our app, because we’re not using Preact anywhere yet. In order to try it out, we’re going to need a page to work with in our app. I’m going to be lazy and just add a view to the application controller.

In config/routes.rb:

Rails.application.routes.draw do
  root to: 'application#index'
end
$ mkdir app/views/application
$ touch app/views/application/index.html.erb

Now we can run rails server and it should just be a blank screen, but in the console, we should see:

Hello World from Webpacker

Yay! Just for a sanity check, edit app/javascript/packs/application.js and edit the message and refresh your browser and you should see a new message (because Rails should recompile your assets for you with webpacker).

Additionally, we’re going to use JSX, so we need to install the babel JSX transformer:

yarn add babel-plugin-transform-react-jsx

And in order to map this transformer to Preact’s h method, add this line inside plugins in your .babelrc:

["transform-react-jsx", { "pragma":"h" }]

Preact Hello World

Now it’s time to actually use Preact (finally, whew). Edit app/javascript/packs/application.js and add this line:

import('hello.js')

That’s going to import app/javascript/hello.js (which we are about to write) into your app.

Here’s what we put into app/javascript/hello.js (which is straight from Preact’s tutorial):

import { h, render, Component } from 'preact';

render((
    <div id="foo">
        <span>Hello, world!</span>
        <button onClick={ e => alert("hi!") }>Click Me</button>
    </div>
), document.body);

Now, we should be able to refresh our site, and see the Preact demo. Click the button and it says “Hi!”. We did it.

Wrapping up

One of the things I was interested in is page weight. When running our app in dev mode, I saw two JS files, one is application.js, which contains a bunch of webpack code for dynamically loading all our JS files separately. This is great in development because we only have to rebuild what we change. So that’s why there’s a second “chunk” file also being loaded.

Doing a production asset build is as easy as:

RAILS_ENV=production ./bin/webpack

Then run our dev server again:

rails server

As long as you don’t touch any JS files our production built JS files are used. From here I was able to see that our application.js was just shy of 1kB, and our chunk with hello.js and preact in it are 3.7KB. Great! So we’re under 5KB, which is awesome.

I’m not sure if it’s possible to have just one application.js file (no chunks) so if you know how, let me know!

blog comments powered by Disqus