Comment configurer esbuild pour créer une application React avec Typescript
25/08/2022
Introduction
esbuild est un compilateur JavaScript rapide et simple qui prend en charge JSX et TypeScript. Dans cet article, nous allons configurer esbuild pour créer une application React avec Typescript.
Vous pouvez vérifier le code sur ce repos.
J'ai écrit cet article en 2022. C'est plus un POC qu'une véritable application prête pour la production. Je l'utilise pour tester esbuild et créer un modèle pour mes futurs projets. Aujourd'hui, je ne recommanderais pas styled-components, mais j'utiliserais plutôt tailwindcss.
Initialisation
Créez votre dossier de projet et initialisez-le.
yarn init
{
"name": "esbuild-static",
"version": "1.0.0"
}
Installer les dépendances
yarn add esbuild dotenv react react-dom styled-components
Ensuite, ajoutez les devdependencies.
yarn add --dev typescript @types/react @types/react-dom @types/styled-components @types/node serve-handler @types/serve-handler
Configuration Typescript
Ajoutez le fichier tsconfig.json
.
{
"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": ["**/node_modules", "**/.*/"]
}
Configuration Esbuild
Créez le dossier esbuild
puis ajoutez les fichiers dev.js
et prod.js
.
La configuration dev surveille les modifications des fichiers et démarre un serveur pour le rechargement à chaud et les fichiers statiques. Vous pouvez également ajouter des variables d'environnement.
const { spawn } = require("child_process");
const esbuild = require("esbuild");
const { createServer, request } = require("http");
require("dotenv").config();
const handler = require("serve-handler");
const clientEnv = { "process.env.NODE_ENV": `'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.NODE_ENV": `'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));
Ajouter des scripts
"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
Créez le fichier src/index.tsx
.
import React from "react";
import ReactDOM from "react-dom";
import App from "./App";
import GlobalStyle from "./globalStyle";
ReactDOM.render(
<>
<GlobalStyle />
<App />
</>,
document.getElementById("root")
);
Hot reload
Pour écouter le rechargement du serveur esbuild dev, nous devons ajouter un hook pour le développement.
import { useEffect } from "react";
const useHMR = () => {
useEffect(() => {
if (process.env.NODE_ENV !== "production") {
new EventSource("http://localhost:5010/esbuild").onmessage = () =>
window.location.reload();
}
}, []);
};
export default useHMR;
Global style
Ajoutez le fichier src/globalStyle.ts
.
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
Créez le fichier src/App.tsx
.
import React from "react";
import useHMR from "./useHMR";
const App: React.FC = () => {
useHMR();
return (
<div className="App">
<header className="App-header">
<img src="/logo.svg" className="App-logo" alt="logo" />
<p>
Edit <code>src/App.tsx</code> and save to reload.
</p>
<a
className="App-link"
href="https://reactjs.org"
target="_blank"
rel="noopener noreferrer"
>
Learn React
</a>
</header>
</div>
);
};
export default App;
Fichiers statiques
Pour ajouter des fichiers statiques, créez le dossier public
et ajoutez le fichier logo.svg
.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="icon" href="/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" />
<meta name="description" content="React App" />
<link rel="apple-touch-icon" href="/logo192.png" />
<link rel="manifest" href="/manifest.json" />
<title>React App</title>
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
</body>
</html>
<script src="/index.js"></script>
Puis ajoutez les autres fichiers statiques comme favicon.ico
, logo192.png
et manifest.json
.
Lancer le serveur
yarn start
Build
yarn build
Conclusion
Vous pouvez maintenant créer une application React avec Typescript et esbuild.
Je suis Simon Boisset, développeur fullstack freelance. Je travaille principalement avec React, React Native et Node.js. Je suis disponible pour des missions de développement ou de conseil. N'hésitez pas à me contacter sur mon site.