- Clone this gist:
git clone https://gist.github.com/7f5a3821e46adc0c33908f68fa05803c.git my-app
- Create a new local git repository:
cd my-app && rm -rf .git && git init
- Install dependencies:
yarn install
- Happy react hackin'
-
-
Save danigb/7f5a3821e46adc0c33908f68fa05803c to your computer and use it in GitHub Desktop.
{ | |
"presets": ["env", "react"], | |
"plugins": [ | |
"transform-class-properties", | |
"transform-object-rest-spread", | |
"react-hot-loader/babel" | |
] | |
} |
{ | |
"parser": "babel-eslint", | |
"extends": ["eslint:recommended", "plugin:react/recommended"], | |
"env": { | |
"browser": true, | |
"jest": true, | |
"es6": true | |
} | |
} |
node_modules/ | |
.cache/ | |
dist/ | |
*.log | |
.idea/ | |
report.html | |
*.orig | |
coverage/ |
This setup includes:
- Healthy codebase: transpilation, linting and formatting
- Awesome css support via PostCSS (including sass)
- Development server with hot reloading
- Production build and deployment
mkdir my-react-app
cd my-react-app
mkdir src/
git init
echo '# My Awesome React App' > README.md
Then, create the project's npm package:
yarn init -y
echo 'node_modules/' > .gitignore
It's probably that we want to set the module private to prevent accidental publishing. Also, we're going to store all the files inside a src folder, so inside package.json write the following:
{
...
"private": true,
"main": "src/index.js"
}
We'll need to transpile the code for two reasons: convert JSX to javascript, and convert javascript features not supported by our target browsers into more standard JS. This two processes are covered by this two babel-preset packages:
yarn add --dev babel-preset-env babel-preset-react
The configuration is written inside a .babelrc.json file:
{
"presets": ["env", "react"]
}
The babel-preset-env
plugin can be configured to target browsers we want to support, so it will transpile the code according to the javascript features available on that browsers. But the defaults are ok by now.
A linter is a program that analyzes source code to detect possible errors and maintain coherence in the codebase. The babel-eslint package will allow our linter to parse JSX code, and the eslint-plugin-react
includes the react related rules:
yarn add --dev eslint babel-eslint eslint-plugin-react
The configuration goes inside .eslintrc.json:
{
"parser": "babel-eslint",
"extends": ["eslint:recommended", "plugin:react/recommended"],
"env": {
"browser": true,
"jest": true,
"es6": true
}
}
I like to have a script to detect lint errors (in package.json):
{
"scripts": {
...
"test:eslint": "eslint src/ --ext .js --ext .jsx"
}
}
One handy feature of npm scripts is the ability to launch a script before another just by prepending "pre" to the name of the target script. You can run the linter before the "test" with this "pretest" script:
{
"scripts": {
...
"pretest": "yarn run test:eslint"
}
}
We want to ensure before every commit that our source files have the same format. This can be done with a combination of prettier
, husky
and lint-staged
:
yarn add --dev lint-staged husky prettier
And then, in package.json:
{
...
"scripts": {
"precommit": "lint-staged"
},
"lint-staged": {
"*.{js,jsx,json,scss,css,md}": ["prettier --write", "git add"]
}
}
We're ready to add parcel bundler to the mix:
yarn add --dev parcel-bundler
We need a script to start a development server and another to build our app:
{
...
"scripts": {
"start": "NODE_ENV=development parcel src/index.html --open",
"build": "NODE_ENV=production parcel build src/index.html --public-url ./",
...
}
}
Note: this doesn't work on Windows (because the environment variables). If you need windows support take a look to cross-env
.
If you are planning to deploy the compiled bundle you need to change the public-url parameter to point the root url of your app.
Finally, we don't want our compiled app to be on the git repository:
cat 'build/' >> .gitignore
Parcel comes with PostCSS support out of the box. PostCSS can be extended with other languages and preprocessors. Sass is our css language of choice, implemented via node-sass
. autoprefixer
it's a popular PostCSS plugin that add vendor css prefixes when required:
yarn add --dev node-sass autoprefixer
Configure it inside package.json:
{
...
"postcss": {
"modules": false,
"plugins": {
"autoprefixer": {
"browsers": [">1%", "last 4 versions", "Firefox ESR", "not ie < 9"],
"flexbox": "no-2009"
}
}
},
...
}
Hot code reloading means that the browser will reload automatically the app when the code changes. It's a handy development feature (specially if you have two or more screens):
yarn add --dev react-hot-loader
The react-hot-loader
is implemented as a babel plugin, so we need to change .babelrc.json configuration:
{
"presets": ["env", "react"],
"plugins": ["react-hot-loader/babel"]
}
And we need to change a little bit our source code. Let's suppose our index.js file is like this one:
import React from "react";
import { render } from "react-dom";
import App from "./App";
const root = document.getElementById("root");
render(<App />, el);
To enable hot code reloading we should change the above code into this:
/* global module */
import React from "react";
import { render } from "react-dom";
const root = document.getElementById("root");
function renderApp() {
const App = require("./App").default;
render(<App />, root);
}
renderApp();
if (module.hot) {
module.hot.accept(renderApp);
}
The first comment tells eslint to treat module as a global object instead of an undefined variable. With the module.hot.accept
function we tell react-hot-loader
to invoke the renderApp
function each time the code changes.
There are many ways to deploy a serverless app. The quick-and-dirty solution is use github pages (not recommended for production):
yarn add --dev gh-pages
And a couple of scripts:
{
...
"scripts": {
...
"predeploy": "yarn run build",
"deploy": "gh-pages -d dist",
}
}
Remember to change the url of the build script to match the root url of the app. For example:
{
...
"scripts": {
...
"build": "NODE_ENV=production parcel build src/index.html --public-url https://marsbased.github.io/my-awesome-app/",
}
}
Now the hardest thing is missing: code your app.
{ | |
"name": "my-awesome-app", | |
"private": true, | |
"version": "1.0.0", | |
"description": "My awesome app", | |
"main": "src/index.js", | |
"repository": "git@github.com:MarsBased/my-awesome-app.git", | |
"authors": [], | |
"scripts": { | |
"start": "NODE_ENV=development parcel src/index.html --open", | |
"build": "NODE_ENV=production parcel build src/index.html --public-url https://marsbased.github.io/my-awesome-app/", | |
"precommit": "lint-staged", | |
"predeploy": "yarn run build", | |
"deploy": "gh-pages -d dist", | |
"test": "jest", | |
"test:eslint": "eslint src/ --ext .js --ext .jsx" | |
}, | |
"lint-staged": { | |
"*.{js,jsx,scss,json,css,md}": [ | |
"prettier --write", | |
"git add" | |
] | |
}, | |
"jest": { | |
"moduleNameMapper": { | |
"\\.(css|less|scss|sass)$": "identity-obj-proxy" | |
}, | |
"collectCoverageFrom": [ | |
"src/**" | |
] | |
}, | |
"postcss": { | |
"modules": false, | |
"plugins": { | |
"autoprefixer": { | |
"browsers": [ | |
">1%", | |
"last 4 versions", | |
"Firefox ESR", | |
"not ie < 9" | |
], | |
"flexbox": "no-2009" | |
} | |
} | |
}, | |
"license": "MIT", | |
"dependencies": { | |
"prop-types": "^15.6.1", | |
"react": "^16.4.0", | |
"react-dom": "^16.4.0", | |
"react-router": "^4.2.0", | |
"react-router-dom": "^4.2.2" | |
}, | |
"devDependencies": { | |
"autoprefixer": "^8.5.0", | |
"babel-eslint": "^8.2.3", | |
"babel-plugin-transform-class-properties": "^6.24.1", | |
"babel-plugin-transform-object-rest-spread": "^6.26.0", | |
"babel-preset-env": "^1.7.0", | |
"babel-preset-react": "^6.24.1", | |
"enzyme": "^3.3.0", | |
"enzyme-adapter-react-16": "^1.1.1", | |
"eslint": "^4.19.1", | |
"eslint-plugin-react": "^7.8.2", | |
"gh-pages": "^1.1.0", | |
"husky": "^0.14.3", | |
"identity-obj-proxy": "^3.0.0", | |
"jest": "^23.0.0", | |
"lint-staged": "^7.1.0", | |
"node-sass": "^4.9.0", | |
"parcel-bundler": "^1.8.1", | |
"parcel-plugin-bundle-visualiser": "^1.1.2", | |
"prettier": "^1.12.1", | |
"react-hot-loader": "^4.2.0" | |
} | |
} |