ES6 imports are hoisted to the top

From Exploring ES6:

Module imports are hoisted (internally moved to the beginning of the current scope). Therefore, it doesn’t matter where you mention them in a module and the following code works without any problems:

foo();
import { foo } from 'my_module';

Footgun example

When you're not aware of import hoisting you may be surprised that your code runs in a different order than you see in the source file.

The example below is taken from the [...

Ensure passing Jasmine specs from your Ruby E2E tests

Jasmine is a great way to unit test your JavaScript components without writing an expensive end-to-end test for every small requirement.

After we integrated Jasmine into a Rails app we often add an E2E test that opens that Jasmine runner and expects all specs to pass. This way we see Jasmine failures in our regular test runs.

RSpec

In a [feature spec](https://relishapp.com/rspec/rspec-rails/docs/feature-spec...

Using feature flags to stabilize flaky E2E tests

A flaky test is a test that is often green, but sometimes red. It may only fail on some PCs, or only when the entire test suite is run.

There are many causes for flaky tests. This card focuses on a specific class of feature with heavy side effects, mostly on on the UI. Features like the following can amplify your flakiness issues by unexpectedly changing elements, causing excessive requests or other timing issues:

  • Lazy loading images
  • Autocomplete in search f...

Better numeric inputs in desktop browsers

You want to use <input type="number"> fields in your applications.
However, your desktop users may encounter some weird quirks:

  1. Aside from allowing only digits and decimal separators, an "e" is also allowed (to allow scientific notation like "1e3").
    • Non-technical users will be confused by this.
    • Your server needs to understand that syntax. If it converts only digits (e.g. to_i in Ruby) you'll end up with wrong values (like 1 instead o...

Event delegation (without jQuery)

Event delegation is a pattern where a container element has a single event listener that handles events for all descendants that match a CSS selector.

This pattern was popularized by jQuery that lets you do this:

$('.container').on('click', '.message', function(event) {
  console.log("A message element was clicked!")
})

This technique has some advantages:

  1. When you have many descendants, you save time by only registering a single listener.
  2. When the descendants are changed dynamic...

Disable built-in dragging of text and images

Most browsers have built-in drag and drop support for different page elements like text and images. While this may be useful in most situations, it may become annoying in others. If you e.g. want to allow the user to scroll/move horizontally within a container by grabbing an item and moving the mouse, you will notice that nothing will move and you'll instead start dragging that element.

To disable this, add the following CSS to your content:

-webkit-user-drag: none
user-drag: none

-webkit-user-drag is only fully supported in ...

Unpoly 2: Don't try to download files through AJAX requests

Rails has the handy controller method send_file which lets us download files easily. We can decide whether the file should be downloaded (disposition: 'attachment') or shown in the browser (disposition: 'inline'). The default is disposition: 'attachment'.

Downloading files will not work when you are calling the controller action from an AJAX request. The browser will try to render the file and insert it in the DOM, which is never what you want.

Unpoly 2

Unpoly (sin...

You should probably load your JavaScript with <script defer>

It is generally discouraged to load your JavaScript by a <script src> tag in the <head>:

<head>
  <script src="app.js"></script>
</head>

The reason is that a <script src> tag will pause the DOM parser until the script has loaded and executed. This will delay the browser's first contentful paint.

A much better default is to load your scripts with a <script src defer> tag:

<head>
  <script src="app.js" defer></script>
</head>

A deferred script has many useful properties:

  • I...

Unobtrusive JavaScript helper to progressively enhance HTML

The attached compiler() function below applies JavaScript behavior to matching HTML elements as they enter the DOM.

This works like an Unpoly compiler for apps that don't use Unpoly, Custom Elements or any other mechanism that pairs JavaScript with HTML elements.

The compiler() function is also a lightweight replacement for our legacy [$.unobtrusive()](https://makandracards.com/makandra/4-unobtrusiv...

Using attribute event handlers with a strict Content Security Policy (CSP)

Given you have a strict CSP that only allows <script src> elements from your own domain:

Content-Security-Policy: script-src 'self'

This will block JavaScript handlers inlined as attribute into your HTML elements. Clicking on the following link will only log an error with a strict CSP:

<a href="javascript:alert('hello')">click me</a>
<a href="#" onclick="alert('hello')">click me</a>

Solution 1: Move the handler into your JavaScript

The recommended solution is to move the handler from the HTML to the allowed ...

Triggering JavaScript when an element is clicked

Often people need links which are not linked directly, but should trigger execution of javascript.

You can find a lot of workarounds for that:

  • <a href="#">Do something with js!</a>
    This defines an empty anchor. This may lead the browser to let the page jump to the top when the link is clicked, unless you call preventDefault on the event. This is probably not what you want.

  • <a href="#!">Do something with js!</a>
    This tells the browser to jump to an anchor !. It depends on the browser implementation what happens when an anc...

CSS: How to force background images to scale to the container, ignoring aspect ratio

You can scale background images in CSS to the container size using background-size (Demo).

Commonly, we use contain or cover because we want to preserve the image's aspect ratio.
If you do not want to do that, simply provide scaling values for X and Y:

background-size: 100% 100%

(a simple 100% would mean 100% auto and respect the image's aspect ratio)

SVGs with a viewBox will force their aspect ratio

The above may not work for you when ...

How to: Ensure proper iconfont rendering with Webpack

Background

After switching a project from Sprockets to Webpack, I started observing a bug that was hard to debug: Our custom icon font could sometimes not be displayed until a full page reload.

Digging deeper the only difference before and after the page load was the encoding interpretation of the iconfont stylesheet:

Correct representation (UTF-8):

.icon:before {
    content: ""
}

Broken representation (other charset):
`...

Accessing JavaScript objects from Capybara/Selenium

When testing JavaScript functionality in Selenium (E2E), you may need to access a class or function inside of a evaluate_script block in one of your steps. Capybara may only access definitions that are attached to the browser (over the window object that acts as the base). That means that once you are exporting your definition(s) in Webpacker, these won't be available in your tests (and neither in the dev console). The following principles/concepts also apply to Sprockets.

Say we have a StreetMap class:

// street_map.js
class S...

Pre-releasing a Ruby gem

When a Ruby version gem has a letter in its version number, it is considered a pre-release:

  • 1.0.0.rc1
  • 2.3.0.alpha2
  • 3.0.0.beta3
  • 4.0.0.pre.rc2

Even if a pre-release gem has the highest version number, it is never installed unless the user explictily requested the version:

gem install foobar --version="=2.3.0.alpha2"

Also bundle update will never update a stable version to a pre-release version unless the user explicitly requests it in the Gemfile:

gem 'foobar', '=2.3.0.alpha2'

A note on Semanti...

Debugging your Webpack build time with Speed Measure Plugin

If your Webpack build is slow, you can use the Speed Measure Plugin for Webpack to figure out where time is being spent.
Note that at time of writing, Webpack 5 seems unsupported. It works on Webpack 4, though.

Wire it into your application as described in the library's documentation:

  1. Hook into your environment file, e.g. config/webpack/development.js and instead of exporting your Webpack config,...

Webpacker: Loading code on demand

Sometimes you want to load code on demand. For instance, when a a large library is only used on a single screen, this library should only be loaded for that screen. It should not blow up the bundle size for all screens.

You can load code on demand by using import() as a function (with parentheses) instead of using it as a keyword import (without parentheses). The import() function returns a promise with the exported variables:

let exports = await import('large-library')

console.log("A named export is ", exports.exportedName)
c...

When does Webpacker compile?

Webpack builds can take a long time, so we only want to compile when needed.

This card shows what will cause Webpacker (the Rails/Webpack integration) to compile your assets.

When you run a dev server

While development it is recommended to boot a webpack dev server using bin/webpack-dev-server.

The dev server compiles once when booted. When you access your page on localhost before the initial compilation, the page may load without assets.

The ...

Converting ES6 to ES5 on the command line

I use the TypeScript compiler for this, since its output is more minimal than Babel's.

The following will transpile all src/*.js files into a file build.js:

npm install typescript
npx tsc src/*.js --target ES5 --allowJs --outFile build.js

The output will only transpile ES6 syntax. It will not include any polyfills for missing APIs.

Capybara: Preventing server errors from failing your test

When your Rails application server raises error, Capybara will fail your test when it clears the session after the last step. The effect is a test that passes all steps, but fails anyway.

Capybara's behavior will help you to detect and fix errors in your application code. However, sometimes your application will explode with an error outside your control. Two examples:

  • A JavaScript library references a source map in its build, but forgets to package the source map
  • CarrierWave cleans up an upload or cache file after the record was delet...

Capybara: Pretending to interact with the document

Browsers blocks abusable JavaScript API calls until the user has interacted with the document. Examples would be opening new tab or start playing video or audio.

E.g. if you attempt to call video.play() in a test, the call will reject with a message like this:

NotAllowedError: play() failed because the user didn't interact with the document first. https://goo.gl/xX8pDD

Workaround

To pretend document interaction in a test you can create an element, click on it, and remove the element again. This unblocks the entire JavaSc...