Create a Build pipeline for React app
It is mid-summer of 18th of July 2016, Christopher Chedeau made first commit into GitHub repository create-react-app. At the same time, Dan Abramov makes one more commit with the commit message “Explain what it is”. Commit contains README and Goal in it.
Make it easy to get started with React.
And that overall explains the situation with React a few years ago. Cool and shiny but to start hacking — it will take some time.
What is command create-react-app?
Create React App doesn’t handle backend logic or databases; it just creates a frontend build pipeline, so you can use it with any backend you want. Under the hood, it uses Babel and webpack, but you don’t need to know anything about them.
And here we are, almost three years later, where React is one of the most popular frontend JavaScript libraries. Can we blame partly command create-react-app for that? Most certainly yes, it takes one command to bootstrap React app. Versus spending the time to build a pipeline of your own, just to test your idea.
As cool it is, it hides a lot. What does it hide?
Ladies and Gentlemen, I bring to you my own experience of building your own build pipeline.
# The beginning
Create an empty directory, have you noticed that every new amazing project starts with an empty directory?
mkdir react-tutorial; cd react-tutorial
Initialize the npm project
npm init -y
React
npm i -S react react-dom
Webpack
npm i -D webpack webpack-cli
Create folder src which will contain app source
mkdir src
Add ./src/index.html
And ./src/index.js
Now the question is. How to combine these two things together? Usually, in the build process.
Add build
command to thepackage.json
Navigate to project root directory and run the command build
npm run build
WebPack will create a directory dist
and put all compiled result inside. Look insidedist
and you will find minified main.js
but no index.html
. The desired result would be: main.js
and index.html
insidedist
directory and index.html
should contain a reference to main.js
.
For that, we have to create webpack.config.js
in the project root and give instruction to WebPack to do so. Before that add package html-webpack-plugin
npm i -D html-webpack-plugin
This will allow manipulating with index.html
. Now create webpack.config.js
Run build
once more
npm run build
Dist
directory should contain index.html
and main.js
. Open index.html
with Google Chrome and press F12
It works!
Of course, this isn’t React app. This is plain javascript with console output. But take a few seconds and think about it. What possibilities are there for you as a software engineer to manipulate with HTML files in build time. Basically, html-webpack-plugin
is a lightweight templating engine.
# The Babel and Typescript
Babel is a toolchain that is mainly used to convert ECMAScript 2015+ code into a backwards compatible version of JavaScript in current and older browsers or environments.
npm i -D @babel/cli @babel/core @babel/preset-env babel-loader @babel/plugin-proposal-class-properties @babel/plugin-transform-arrow-functions
TypeScript and Babel TypeScript preset
npm i -D typescript @babel/preset-typescript
Create a Babel configuration .babelrc
in the project root directory
Rename index.js
to index.ts
Modify webpack.config.js
rename index.js
to index.ts
. Give WebPack instructions to use babel-loader when encountered files with extension ts or tsx.
Let us adjust index.ts
file, just to test. Does our TypeScript Babel configuration work
Now run the build
command again
npm run build
And if you open index.html
in the browser, the console will print “Hello Mom!” Isn’t it marvelous!? This proves that our build pipeline works.
# The React app
It time to add some reaction to our app! Let us create a Dashboard React component.
For that, we will need to teach Babel to understand reactish :)
npm i -D @babel/preset-react
Add preset to .babelrc
so that Babel can talk reactish
Create tsconfig.json
in the project root, it is a configuration file for TypeScript.
Changes in webpack.config.js
. For debugging purposed add source maps. As other npm packages import javascript files add to resolve extension “.js”.
Before we start hacking, add React typings. It will enhance your experience with modern IDEs, hints, strict type checks, etc.
npm i -D @types/react @types/react-dom
Now we are ready to build out component. Create a new directory in the source directory.
mkdir src/components
Create component Dashboard.tsx
Replace the content of index.tsx
And run once again build command
npm run build
Open index.html
with browser and
# The WebPack dev server
As you noticed, manually checking the changes in index.html
is annoying. To ease our pain, let us install WebPack dev server.
Use webpack with a development server that provides live reloading. This should be used for development only.
npm i -D webpack-dev-server
Add start
script entry in the package.json
to start WebPack dev server
Add dev server configuration in webpack.config.js
Start
will start the web server and open default browser. Now whenever you make changes in your code, the page will be refreshed automatically. Cool, isn’t it? Try it out yourself!
npm run start
# The Polyfill
Not all browsers understand the new and fancy javascript constructs. For that, Babel can be used to fill the gaps with polyfills. For example, Array.prototype.map().
Install babels polyfill package
npm i -D @babel/polyfill
Configure Babel in .babelrc
what gaps to fill. This is my preference.
# The Unit tests
Testing your code is crucial. Let us write a unit test for the Dashboard component.
Install all the required packages
npm i -D jest @types/jest @types/enzyme @types/enzyme-adapter-react-16 enzyme enzyme-adapter-react-16 enzyme-to-json ts-jest
Create jest.config.js
in the project root directory
This gives instructions to jest to look for test files in directory __test__ and file should end with .test.jsx
or test.tsx
.
Additionally, we need Enzyme to be friends with Jest.
Enzyme is a JavaScript Testing utility for React that makes it easier to assert, manipulate, and traverse your React Components’ output.
Create setupTests.ts
file in the root directory
Add new script in the package.json
named “test”
Make changes in the Dashboard.tsx
, so that we can actually test it. Add an id
to div
, we will use the id
to query by it.
Create a directory for test cases
mkdir src/components/__tests__
Create Dashboard.test.tsx
and place it inside __test__
directory
Execute the test script
npm run test
Perfect! 100% code coverage a lot of development teams can envy us now.
# The Styling
So far we dealt only with JavaScrip. But we shouldn't forget about styling. For styling, I prefer to use Sass.
Sass is the most mature, stable, and powerful professional grade CSS extension language in the world.
Install packages for styling
npm i -D sass-loader node-sass mini-css-extract-plugin css-loader style-loader
Modify webpack.config.js
This is a lot to process. But what I’m doing here is:
- Importing new plugin
mini-css-extrat-plugin
. This allows Web Pack to extract all CSS in one file. - Determinate the mode, development or production
- More rules, what to do when encountered files with extensions sass, scss and css. Loaders will be used in order sass-loader, css-loader and then it depends on mode either style-loader or extract plugin.
- Add an extension to resolve “.scss”, I would add “.css” if needed.
- And register new plugin for WebPack to use
Cool, we are ready to make out “Hello Mom!” message bigger and stronger again!
Create Dashboard.scss
file right beside Dashboard.tsx
Import Dashboard.scss
into Dashboard.tsx
and reference style class on a div
element.
npm run start
Now that is glorious I say!
But wait — what is this when I run
npm run test
It explodes in my face… :(
That's OK. Jest doesn’t understand styling imports and honestly, unit testing should care about styling. So let's mock them out.
Adjust jest.config.js
This basically says, when you see files with extensions like xyz use mock files.
Create a directory __mocks__ in the project root
mkdir __mocks__
Create fileMock.js
Create styleMock.js
Run tests again
npm run test
And it works again!
#The PostCSS
It turns out that not only JavaScript requires polyfills but CSS as well. To fill these gaps and enhance the output CSS with browser prefixes, PostCSS is the tool we will use.
Let us install required packages
npm i -D postcss-loader postcss-flexbugs-fixes postcss-preset-env postcss-safe-parser
Adjust webconfig.config.js
by adding postcss-loader
For testing purposes, let's put inside Dashboard.scss
placeholder definition.
::placeholder {
color: blue;
}
And run the build script
npm run build
Now look inside generated a CSS file
Now, now, now! There is more than we placed in SCSS file. PostCSS loader is here to blame. It will make sure that placeholder will look the same all across the different browsers.
# The Linting and Formatting
We all write perfect code, but just sometimes we forget that semicolumn or comma. And then at the build time, code explodes or even worse production. Linting your code before committing it to the repository is a smart move. We will you TSLint for that.
TSLint is an extensible static analysis tool that checks TypeScript code for readability, maintainability, and functionality errors. It is widely supported across modern editors & build systems and can be customized with your own lint rules, configurations, and formatters.
Counting the spaces and tabs, just to make your code prettier is mind-numbing. We can use a tool for that, called prettier.
Prettier is an opinionated code formatter. It enforces a consistent style by parsing your code and re-printing it with its own rules that take the maximum line length into account, wrapping code when necessary.
Install tslint and prettier packages
npm i -D prettier tslint tslint-config-prettier
Create TSLint configuration tslint.json
and place it in the project root
Create a prettier configuration .prettierrc
and place it in the project root
Add two new scripts in package.json
Run both commands
npm run format && npm run lint
Your code is ready for committing to the repository a good job!
#The E2E testing
As I understand E2E means. Does the flow happen when I press the button? Because one button click may trigger hundreds of component interactions. That kind of test should be done by a unit test.
e2e or end-to-end or UI testing is a methodology used to test whether the flow of an application is performing as designed from start to finish. In simple words, it is testing of your application from the user endpoint where the whole system is a blackbox with only the UI exposed to the user.
Now how we can put this in a pipeline of ours? Puppeteer will be a tool of ours!
Puppeteer is a Node library which provides a high-level API to control Chrome or Chromium over the DevTools Protocol. Puppeteer runs headless by default, but can be configured to run full (non-headless) Chrome or Chromium.
Add packages
npm i -D puppeteer @types/puppeteer
Create Dashboard.e2e.test.ts
right beside Dashboard.test.tsx
Now IDE will light up red on async keyword, to fix it, let’s modify tsconfig.json. Add lib configuration.
Run test script
npm run test
Congratulation! Your first end to end test is working! NB: Don’t forget to npm run start
before running the test.
# The End
End result of package.json
Worth notice that build
command executes linting, formation, and testing and only when that is done, WebPack builds the project.
And the end result of WebPack configuration webpack.config.js
This is the time when to remind our self what was the Goal of create-react-app
Make it easy to get started with React.
There is a lot of things which are hidden behind one command. And there is a good reason to do so. Imagen every time to do all those things we just did, just to get started. Madness — I know.
I personally encourage to use create-react-app. But I’m pretty sure that if the project is big or complex, you will end up with a pipeline of your own. Which most likely will look like one I created. To have full control over what happens when and why.
Overall by having this experience. I did see this amazing synergy between Titans, like Facebook (React), Puppeteer (Google), Enzyme (Airbnb), TypeScript (Microsoft), Webpack (OpenSource) and other technologies and tools.
Ladies and Gentlemen this was my experience of hacking a build pipeline for React app.
GitHub repo https://github.com/alzuma/react-tutorial
Next up. “Why web components matter”
— Kristaps