Create a react app with esbuild Type: blog post Language: en-US Canonical URL: https://simonboisset.com/en/blog/create-react-app-with-esbuild Published: August 25, 2022 Summary: Introduction Esbuild: https://esbuild.github.io/ is a new javascript bundler. It's written with Go and is extremely fast. Let's go to use it to create react... Introduction Esbuild: https://esbuild.github.io/ is a new javascript bundler. It's written with Go and is extremely fast. Let's go to use it to create react with hot reload app from scratch without webpack You can check the code on this repos: https://github.com/simonboisset/templates/tree/main/templates/react-spa. I wrote this article in 2022. It's more a POC than a real production ready app. I use it to test esbuild and create a template for my future projects. Today I would not recommend styled-components instead I would use tailwindcss. Initialization Create your folder project and initialize it. yarn init { "name": "esbuild-static", "version": "1.0.0" } Install dependencies yarn add esbuild dotenv react react-dom styled-components Then add devdependencies. yarn add --dev typescript @types/react @types/react-dom @types/styled-components @types/node serve-handler @types/serve-handler Typescript config Add tsconfig.json file. { "compilerOptions": { "outDir": "dist", "rootDir": "src", "module": "commonjs", "target": "ESNext", "lib": ["dom", "dom.iterable", "esnext"], "moduleResolution": "node", "strict": true, "forceConsistentCasingInFileNames": true, "noImplicitReturns": true, "skipLibCheck": true, "esModuleInterop": true, "jsx": "react" }, "include": ["src"], "exclude": ["/nodemodules", "/./"] } Esbuild config Create esbuild folder then add dev.js and prod.js files. The dev config watch files changes and start a server for hot reload and static files. You can add environment variables too. const { spawn } = require("childprocess"); const esbuild = require("esbuild"); const { createServer, request } = require("http"); require("dotenv").config(); const handler = require("serve-handler"); const clientEnv = { "process.env.NODEENV": 'dev' }; const clients = []; Object.keys(process.env).forEach((key) => { if (key.indexOf("CLIENT") === 0) { clientEnv[process.env.${key}] = '${process.env[key]}'; } }); const openBrowser = () => { setTimeout(() => { const op = { darwin: ["open"], linux: ["xdg-open"], win32: ["cmd", "/c", "start"], }; if (clients.length === 0) spawn(op[process.platform][0], ["http://localhost:3000"]); }, 1000); }; esbuild .build({ entryPoints: ["src/index.tsx"], bundle: true, minify: true, define: clientEnv, outfile: "dist/index.js", sourcemap: "inline", watch: { onRebuild(error) { setTimeout(() => { clients.forEach((res) => res.write("data: update\n\n")); }, 1000); console.log(error || "client rebuilt"); }, }, }) .catch((err) => { console.log(err); process.exit(1); }); esbuild.serve({ servedir: "./" }, {}).then((result) => { createServer((req, res) => { const { url, method, headers } = req; if (req.url === "/esbuild") { return clients.push( res.writeHead(200, { "Content-Type": "text/event-stream", "Cache-Control": "no-cache", "Access-Control-Allow-Origin": "", Connection: "keep-alive", }) ); } const path = url.split("/").pop().indexOf(".") ? url : /index.html; const proxyReq = request( { hostname: "0.0.0.0", port: 8000, path, method, headers }, (prxRes) => { res.writeHead(prxRes.statusCode, prxRes.headers); prxRes.pipe(res, { end: true }); } ); req.pipe(proxyReq, { end: true }); return null; }).listen(5010); createServer((req, res) => { return handler(req, res, { public: "dist" }); }).listen(3000); openBrowser(); }); const esbuild = require("esbuild"); require("dotenv").config(); const clientEnv = { "process.env.NODEENV": 'production' }; for (const key in process.env) { if (key.indexOf("CLIENT") === 0) { clientEnv[process.env.${key}] = '${process.env[key]}'; } } esbuild .build({ entryPoints: ["src/index.tsx"], bundle: true, minify: true, define: clientEnv, outfile: "dist/index.js", }) .catch(() => process.exit(1)); Eslint config Install eslint. yarn add --dev eslint eslint-config-react-app @typescript-eslint/eslint-plugin @typescript-eslint/parser Add .eslintrc.js file. module.exports = { env: { browser: true, es2021: true, }, extends: ["react-app"], parser: "@typescript-eslint/parser", parserOptions: { ecmaFeatures: { jsx: true, }, ecmaVersion: 13, sourceType: "module", }, plugins: ["react", "@typescript-eslint"], settings: { "import/resolver": { node: { extensions: [".js", ".jsx", ".ts", ".tsx"], }, }, }, }; Scripts Add scripts to package.json "scripts": { "build": "node esbuild/prod", "type-check": "tsc --noEmit", "lint": "eslint src//.ts src//.tsx", "start": "nodemon --watch dist --exec 'yarn type-check & yarn lint' & node esbuild/dev" }, React app In src folder add index.tsx file. import React from "react"; import ReactDOM from "react-dom"; import App from "./App"; import GlobalStyle from "./globalStyle"; ReactDOM.render( , document.getElementById("root") ); Hot reload tools For listening esbuild dev server reload we must add a hook for development. import { useEffect } from "react"; const useHMR = () => { useEffect(() => { if (process.env.NODEENV !== "production") { new EventSource("http://localhost:5010/esbuild").onmessage = () => window.location.reload(); } }, []); }; export default useHMR; CSS with styled-components Add global style with styled-components import { createGlobalStyle } from 'styled-components'; const GlobalStyle = createGlobalStyle body { margin: 0; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; } code { font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', monospace; } .App { text-align: center; } .App-logo { height: 40vmin; pointer-events: none; } @media (prefers-reduced-motion: no-preference) { .App-logo { animation: App-logo-spin infinite 20s linear; } } .App-header { background-color: #282c34; min-height: 100vh; display: flex; flex-direction: column; align-items: center; justify-content: center; font-size: calc(10px + 2vmin); color: white; } .App-link { color: #61dafb; } @keyframes App-logo-spin { from { transform: rotate(0deg); } to { transform: rotate(360deg); } } ; export default GlobalStyle App Fanaly create the App component. import React, { FC } from "react"; import useHMR from "./useHMR"; import Logo from "./Logo"; const App: FC = () => { useHMR(); return ( Edit src/App.tsx and save to reload. Learn React ); }; export default App; Static files Add static files in dist folder. React App You need to enable JavaScript to run this app. Then create other files : favicon.ico, manifest.json, logo192.png Run Start dev server. yarn start Build for production yarn build Now let's go to code --- I'm Simon Boisset, freelance fullstack developer. I mainly work with React, React Native and Node.js. I'm available for development or consulting missions. Feel free to contact me on my website: https://simonboisset.com/. Related links Blog index: https://simonboisset.com/en/blog Website: https://simonboisset.com/en