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
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
Read versioned asset URLs from manifest.json
# config/routes.yaml index: path: / controller: App\Controller\DefaultController::index
Add a controller to render our template
# config/packages/framework.yaml framework: assets: json_manifest_path: '%kernel.project_dir%/public/build/manifest.json'
Create a base template that loads our assets
And create the homepage template
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.
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
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.
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.
PolyfillsLet'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
Change assets/js/app.js to require the newly added package, place it at the top of the file
yarn add babel-polyfill
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
Then, we add a custom loader to the Webpack config
yarn add --dev prettier prettier-loader
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
And add the loader
yarn add --dev eslint eslint-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.
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.
- 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.
Then, add some launchers to run the tests in Chrome and Firefox
yarn add --dev karma karma-webpack
And finally, we install Mocha, Chai, and their Karma plugins
yarn add --dev karma-chrome-launcher karma-firefox-launcher
Now we just need the karma-cli tool
yarn add --dev mocha karma-mocha chai karma-chai
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
yarn global add karma-cli
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.
Let's install JSVerify
Rewrite our tests to make use of it
yarn add --dev jsverify
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