Starting a React Project from Scratch with Parcel.js

Hey, y'all. I've been working on more React as I gear up for a new project at work, and I decided to try my hand at setting a React project from scratch. create-react-app is cool for getting started quickly, but usually leaves me with a project that's got too much going on for me. I usually put up with that because I don't want to deal with creating my own Webpack config. But, with the advent of Parcel.js, I don't need to worry about that. So, let's get started.

Small disclaimer: I'm going to use Yarn throughout this article, and I won't be showing the equivalent commands using npm. This is for the sake of brevity. In case, you'd like one, here's a quick conversion guide:

yarn add == npm install

yarn remove == npm uninstall

Step One: The Base

First, we need a project:

> mkdir my-awesome-manual-react-project && cd my-awesome-manual-react-project 

> yarn init -y      

Cool. Now that we have a project, let's install some stuff:

> yarn add @babel/core @babel/cli @babel/preset-env @babel/preset-react --dev

> yarn add parcel-bundler --dev 

> yarn add react react-dom  

These installs give us a lot of stuff. We get React and react-dom, which is good since that's the kind of project we're building. We get Parcel which is going to handle all of the build layer for us, which is really great. We will not be writing a single line of config for Parcel. It has sensible defaults for bundling the application, including code splitting in the production build, which means we don't need to do a thing to get going. Lastly, we get Babel along with some presets for ensuring browser compatibility and proper transformations for our React code. Babel, if you don't know, is a language transformer for JavaScript that makes it possible to use many future or unstable features of the language today, as well as rewriting your ES6+ code to work in older browsers.

For Babel, we will need a little bit of a config. But, it's a little baby config, really. Only three lines:

{
  "presets": ["@babel/preset-env, "@babel/preset-react"]
}

Next, we'll add a couple scripts to start and build the app to our package.json:

{
  // ...the rest of your package.json
  "scripts": {
    "start": "parcel index.html",
    "build": "parcel build index.html"
  }
}

That's cool, but we still don't have an app, yet, just a bunch of other people's code. Let's fix that by creating an index.html and an index.js:

// index.html at the root of the project
        
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <meta http-equiv="X-UA-Compatible" content="ie=edge" />
    <title>Parcel.js React project!</title>
  </head>
  <body>
    <div id="app"></div>

    <script src="./index.js"></script>
  </body>
</html>
// index.js file at the root of the project

import React from 'react'
import ReactDOM from 'react-dom'

import App from './src/App'

ReactDOM.render(<App />, document.querySelector('#app'))
// src/App.js

import React from 'react'
import GameboyImage from './img/gameboy.svg'

export default App = props => (
  <div>
    <h1>Hello React App</h1> 
    <img src={GameboyImage} alt='a cartoon gameboy' width='200' height='200'/>
  </div>
)

// image available from 'https://pixelbuddha.net/freebie/nerd-free-flat-icons'

That should give us a really simple app. Let's find out. Start it up with yarn start and load up http://localhost:1234 in your browswer. You should see our welcome message.

Note: The port 1234 is the default for Parcel's dev server.

And that's pretty cool. A (fledgling) app in a couple minutes with no configuration for building and no separate development server needed to get hot-module reloading. Fantastic.

But, there's still something missing... Tests!

Step Two: The Tests

Same as the step before, we'll start by installing some dependencies:

> yarn add jest babel-jest enzyme enzyme-adapter-react-16 --dev

When that's done installing, we need to configure Jest a little bit so our code gets transformed correctly before the tests run:

// in package.json
  "scripts": {
    // ...those scripts from above,
    "test": "jest",
    "watch": "jest --watchAll"
  }
  "jest": {
    "setupFiles": [
      "<rootDir>/jest.setup.js"
    ],
    "transform": {
      "^.+\.js$": "babel-jest"
    }
  }

This tells Jest to use Babel to transform all our React code before running the tests. Jest will throw some weird errors if you forget this step. Then, because we're using Enzyme, we need a setup file to initialize it at the beginning of all of the tests. But, we don't have one of those, so let's get that in there:

// <rootDir>/jest.setup.js

const Enzyme = require('enzyme')
const Adapter = require('enzyme-adapter-react-16')

Enzyme.configure({ adapter: new Adapter() })

Note: Normally, I would like to the import/export syntax to get enyzme and the adapter. However, this jest setup file is run with Node, which doesn't support that syntax, yet. It is possible to make this work, but requires we install a few other things, so I'm not going to include it in this guide.

With all of that done, we can write our first test:

// App.spec.js

import React from 'react'
import { shallow } from 'enzyme'
import App from './App'

describe('App', () => {
  it('should render without crashing', () => {
    const wrapper = shallow(<App />)

    expect(wrapper).toBeTruthy()
  })
})

Not a very valuable test, but it should validate our test setup is working. So, let's try it out. Run yarn test in your terminal, and...

black and white gif of plane crashing into hill

That didn't quite work. Why? Because Jest doesn't know what to do with the SVG. I tried a few different solutions for this:

  • Creating a file mock

    1. Create a folder called __mocks__ (I think that's just a convention, not something that's actually required) at the root of your project

    2. Add a file called fileMock.js and add module.exports = 'file-stub'

    3. Add this option to your jest config in your package.json:

      
        "jest": {
          "transform": {
            // ...the transform we added earlier
            "moduleNameMapper": {
              "\.(svg)$": "<rootDir>/__mocks__/fileMock.js"
            }
          }
        }
      

      This didn't work for me, and, I'm not 100% on this, I think it might be because I'm using Parcel.

  • 'identity-obj-mock'

    1. > yarn add identity-obj-mock --dev
    2. Use the same moduleNameMapper option from above but set the value to "identity-obj-mock"

    This also didn't work for me, but I have no idea why.

  • Create your own transformer:

    // assetTransformers/genericTransformer.js
    module.exports = {
      process() {
        return 'module.exports = {}'
      },
      getCacheKey() {
        return 'genericTransformer'
      }
    }
    

    Use the same "moduleNameMapper" option as the last two solutions and set the value to the path to this transfomer (mine was '<rootDir>/assetTransformers/genericTransformer.js' )

    This is what ultimately worked for me. You can add file extensions to the regex as needed.

With that, the tests, should run and pass!

Step Three: Profit!

You're all done!

baby raising its arms in triumph

You just created a React app from scratch.

Go forth and build amazing things!

Conclusion

I hope you found this guide helpful. It was fun to put together, and I certainly felt accomplished when I figured it out. Thanks for reading; see you in the next one.