A modern JavaScript workflow in Webpack Encore

If you have read our previous article "Webpack Encore under the hood", you will now have a good understanding of what Encore is and what it does. Let's put that knowledge to good use and set up a modern JavaScript workflow.

 

 

 

Wishlist

What do we want from our workflow? In this article we will implement the following features: 

  • Module bundling
  • Transpilation and polyfills
  • Minification and sourcemaps
  • Versioned URLs
  • Code style checks
  • Code quality checks
  • Tests

Set up Symfony Flex and Webpack Encore

Before we can start implementing our JavaScript workflow, we first have to set up Symfony Flex and Webpack Encore.

You should follow the official documentation:

But here is a quick reminder

composer create-project symfony/website-skeleton my-project
cd my-project
composer require symfony/webpack-encore-pack
yarn install
git init

Set up a simple page

To test our Encore setup, we create a simple page.

Set up a route for the homepage

# config/routes.yaml
index:
    path: /
    controller: App\Controller\DefaultController::index
Read versioned asset URLs from manifest.json
# config/packages/framework.yaml
framework:
    assets:
        json_manifest_path: '%kernel.project_dir%/public/build/manifest.json'
Add a controller to render our template

Create a base template that loads our assets

And create the homepage template

Then, we create a simple javascript application.
Note that we use ES6 features like arrow functions and template literals.

Finally, some simple styling

The Webpack configuration

You will find that a webpack.config.js is already provided by Symfony.

We uncomment enableVersioning, addEntry and addStyleEntry.

Since this article is about JavaScript we'll only use plain CSS, so we can remove enableSassLoader. In the real world, you probably do want to use Sass. You can find the documentation at:

We don't use jQuery, so we can remove autoProvidejQuery.

Now that our Webpack config is set up, we can run development or production builds with yarn run encore dev and yarn run encore production respectively.

What do we have already?

The base of our Webpack Encore setup already provides a lot of features out of the box. We can check off half of the items on our wishlist!

  • ✅ Module bundling
  • ☑️️ Transpilation and polyfills
  • ✅️ Minification and sourcemaps
  • ✅️ Versioned URLs
  • ⬜️ Code style checks
  • ⬜️ Code quality checks
  • ⬜️ Tests

Module bundling
Our app.js depends on greet.js, requiring it with require('./greet'). After Webpack compilation, all JavaScript code ends up in one convenient file for the browser to download, public/build/js/app.js. This shouldn't be a big surprise since module bundling is the main purpose of Webpack.

Transpilation and polyfills
Our app contains some ES6 syntax, this will be transpiled to ES5 by Bower since ES6 isn't supported by all browsers. There are no polyfills yet, we will add those later.

Minification and sourcemaps
In production builds, our source code is minified so the browser receives a smaller file, saving us some valuable kilobytes. While in development builds, a sourcemap is included at the end of the file. This will help us understand error messages in the browser developer tools. 

Versioned URLs
The files in our production build include a version number as part of their filename (eg. app.72573e12.js). Browsers will interpret this as a new file and download it directly, this helps us avoid issues with browser cache.  We don't need to worry about changing our includes every time, Symfony's asset() includes the correct versioned files automatically.

Polyfills

Let's try using some fancy new features.
Array.from(new Set([1, 2, 3, 2, 1]));

You'll quickly realize this doesn't work in every browser (we all know the usual suspects).
This can easily be remedied by adding babel-polyfill.

Install the package

yarn add babel-polyfill
Change assets/js/app.js to require the newly added package, place it at the top of the file

And add an entry point to the Webpack config

Code style checks

We would like to verify that the code we write conforms to a given code style. For this, we will use Prettier.

First, we install prettier and prettier-loader

yarn add --dev prettier prettier-loader
Then, we add a custom loader to the Webpack config

Now, whenever we run a Webpack build, our JavaScript files will be automatically updated to conform to the code style. 

Code quality checks

With Prettier we have our code looking nice and consistent, but that only solves part of our potential problems. We would also like to verify we don't get caught in some common pitfalls. Enter ESLint.

The setup is very similar to that of Prettier. Start by installing eslint and eslint-loader

yarn add --dev eslint eslint-loader
And add the loader 

We use fix: true because some rules include a fixer that will automatically patch the problems it detects.

We also have to generate a config file. You can generate one with eslint --init, but we will use the following

Adding eslint-config-prettier removes all styling related rules since they are already covered by Prettier. This ruleset needs to be installed with yarn add --dev eslint-config-prettier. If you would like it to be even stricter, you can replace eslint:recommended with eslint:all to enable even more rules.

Tests

Earlier, we wrote a module to greet people, but how do we know it works correctly? Time to write some tests!
This gets a bit more complicated than what we have done so far.

Testing in JavaScript is separated into various components that all handle a specific part of the testing process. We will use the following:

  • Karma, a test runner. This will actually execute our tests, in various browsers or a headless environment.
  • Mocha, a test framework. This is the overarching structure our tests will be written in.
  • Chai, an assertion library. This lets us assert the properties that we want to check in our tests.

We have a lot of stuff to install, so let's get started. First, install Karma and its Webpack integration.

yarn add --dev karma karma-webpack
Then, add some launchers to run the tests in Chrome and Firefox
yarn add --dev karma-chrome-launcher karma-firefox-launcher
And finally, we install Mocha, Chai, and their Karma plugins
yarn add --dev mocha karma-mocha chai karma-chai
Now we just need the karma-cli tool
yarn global add karma-cli
But wait, we're not done yet! We still need to add a configuration file for Karma. We can generate one with karma init. The default setup is a nice starting point, but it won't work in our scenario as-is.  We need to integrate our tests with the Webpack Encore build process, so the tests
run on the output of the build (what will actually be sent to the browser) rather than our original source.

We also need to add "mocha": true to the env section in .eslintrc.json, so ESLint knows which globals Mocha defines.

After setting all that up, we can finally write some tests.

We'll add the following to the scripts section in our package.json to easily start the test

yarn run test will run your test in a headless Chrome browser.
yarn run test:full will run your test in all browsers defined in the Karma config (Chrome and Firefox).
And yarn run test:watch will watch for changes, and rerun your tests.

We're ready to run our tests now! Run yarn run test!
All tests green? Job well done!

Bonus: QuickCheck / JSVerify

QuickCheck is a tool that lets you do 'property based testing'.
In property-based testing, rather than testing the output of our functions for a carefully chosen set of inputs, we let QuickCheck generate a long list of inputs and check that our function satisfies some property for all of those inputs. 

In pseudo code, that looks like this:

// instead of doing this
assert.equals(add(1, 1), 2)
assert.equals(add(1, 2), 3)
assert.equals(add(2, -5), -3)

// we do this
a, b, c = generate.number()
// commutativity
assert.equals(
  add(a, b),
  add(b, a)
)
// associativity
assert.equals(
  add(add(a, b), c),
  add(a, add(b, c))
)

Note that a, b and c aren't just one number. Our properties get checked for a lot of different numbers.

Also, note that using property-based testing doesn't mean we can't check specific cases anymore. It is entirely possible to combine both approaches.

QuickCheck originated in the Haskell community, but luckily for us, there is a JavaScript alternative: JSVerify. (For those interested: there is also a PHP version, as well as versions for many other languages.)

Let's install JSVerify

yarn add --dev jsverify
Rewrite our tests to make use of it

Notice how we no longer hardcode the name 'Yappa'. The names are now being generated by JSVerify!

JSVerify can generate a lot more than just basic types likes strings. It can generate dates, arrays, functions, json, and a lot more. Check the documentation to learn about all the possibilities.

Taking it further

The app.js in this article is rather basic. In the real world, you probably use a framework like React.js or Vue.js, or a typed JavaScript variant like TypeScript. Luckily, Symfony has good documentation on how to set these up: