{
  "$type": "site.standard.document",
  "coverImage": {
    "$type": "blob",
    "ref": {
      "$link": "bafkreidsdrn7ughhnfpkv4szxkpk23j4rp7avem6qq5zh6lg2iatcxgmay"
    },
    "mimeType": "image/png",
    "size": 40746
  },
  "description": "If you’re worried about complexity in the JavaScript ecosystem, you might be sleeping on one of my favorite web development tools: esbuild!",
  "path": "/words/an-extremely-simple-react-starter-kit/",
  "publishedAt": "2023-07-21T00:00:00Z",
  "site": "at://did:plc:vrrdgcidwpvn4omvn7uuufoo/site.standard.publication/3mmyfl3pxzi2a",
  "tags": [
    "javascript",
    "react"
  ],
  "textContent": "Let’s face it: the modern JavaScript ecosystem has a reputation for complexity. People feel like best practices and popular tools change out from underneath them, there are too many layers of dependencies and everything is perpetually on the brink of collapse.\n\nFor the record, I think that take lacks nuance. We expect more out of web apps than ever before: real-time updates and collaboration, fancy animations, interactive widgets like popovers and nested dropdown menus — and accessible and performant versions of all of the above. The ecosystem is complex because building UIs is complex, and if we push down complexity in one area it’ll probably just pop up in another.\n\nStill, though — the point is well taken. When I want to quickly try out an idea, time spent on tooling is time wasted. I don’t want to have to deal with a bunch of configuration or read up on changes to a big framework or make sure different dependencies play well together. I want something boring that’s easy to spin up with as few moving parts as possible.\n\nThat’s why I feel compelled to write about one of my favorite web development tools: esbuild! It’s a batteries-included JS and CSS bundler made by Evan Wallace, co-founder and former CTO of Figma. Written in Go rather than JavaScript, it’s ridiculously fast, and it powers some other popular bundlers you may have heard of.\n\nGoals\n\nWhat features do we want when building a web app? Here’s my opinionated list:\n\nA development server with live reload: I work significantly faster when the browser is already up-to-date with my latest changes by the time I look over from my text editor.\n\nJavaScript and CSS bundling: This lets me structure files however I want, since the tooling will combine them to produce the final output that runs in the browser.\n\nSyntax lowering: There are a lot of cool new JavaScript and CSS features, and it’s nice to be able to use them before they’re widely supported.\n\nTypeScript support: This is a must for me, but if you don’t like TypeScript just forget I said anything!\n\nCSS modularity: It’s convenient if the styles in different components are isolated from each other. Some people use Tailwind for this, but I don’t like it. My preferred way to solve this problem is CSS modules.\n\nSpeed: I suppose this is a nice-to-have, but development is so much more efficient (and fun!) when there’s a quick feedback loop between making a change and seeing the results.\n\nUsing something like Next.js will get you all that and a lot more — but it’s much heavier. According to NPM, Next.js has 25 dependencies, and Packagephobia reports a 170MB install size. esbuild has one dependency, and adds a comparatively tiny 9MB to your node_modules folder.\n\nI should also mention that this list is for building a single-page app. There’s been a lot of debate around those lately, so let me reiterate: if you’re building a serious production app, you should probably use a framework that lets you render HTML on the server. This is about starting prototypes and fun projects quickly while keeping tooling to a bare minimum.\n\nSo why am I writing this now? As of a few days ago, esbuild added partial CSS module support. Before that, esbuild checked every one of those items except for CSS modularity; now, it’s got everything we need out of the box.\n\nProject Structure\n\nWe’ll go over a barebones example React app just to showcase the different features mentioned above. Here’s what the directory structure looks like:\n\nindex.html is pretty straightforward: it sets up a basic HTML document, links to the bundled CSS and JS files and has an empty <div> for the React app to render into. Note that even though the global stylesheet in our source code is named style.css, we’re linking to index.css — esbuild names the bundled CSS file based on the name of the corresponding JS entrypoint.\n\nindex.tsx is similarly spartan. It has two jobs: import any global styles, and render the root React component.\n\nGlobal styles go in style.css. The selectors in there won’t be changed, so that’s a good place to put things that apply to the whole project: fonts, CSS reset, custom properties, base styles, etc. You can also @import other stylesheets and they'll be bundled along with it:\n\nApp.tsx is the root component. The project layout at this point is kinda arbitrary; this is mostly a contrived example to show off CSS modules.\n\nSpeaking of CSS modules, did you notice types.d.ts? That’s there because TypeScript doesn’t know how to type check .module.css files (so you can skip this section if you’re not using TypeScript). You need to tell it the type of the import:\n\nFinally, there’s livereload.js, which will only be included in development. It (surprise!) reloads the page whenever the esbuild server rebuilds any files. This snippet is straight from the esbuild documentation on live reloading:\n\nImplementation\n\nAnd now the big reveal: we can get the whole development environment with one dependency, two shell commands and zero config files!\n\nHere’s the command for the dev server:\n\nThis builds atop my TIL on using esbuild to run a dev server with live reload. Let’s go through what each line does:\n\nesbuild src/index.html src/index.tsx runs esbuild with src/index.html and src/index.tsx as entrypoints. We don’t need to specify any CSS files because esbuild will gather them as they’re imported into JS files.\n\n--loader:.html=copy‌ tells esbuild to copy files ending in .html unchanged to the build folder. This also gets triggered when the HTML file changes.\n\n--outdir=build --bundle tells esbuild to bundle all the files and place them in the build folder.\n\nRemember those flags, because we’ll use them both when developing locally and building for production. The rest of the flags are specific to development:\n\n--watch tells esbuild to rebuild when any source files change.\n\n--servedir=build serves all static files from the build folder\n\n--serve-fallback=src/index.html serves src/index.html instead of a 404 page (useful for client-side routing)\n\n--inject:src/livereload.js appends src/livereload.js to the end of the JS bundle.\n\nHere’s how to build for production:\n\nIt’s mostly the same! There are only two differences: the last two lines with the development server flags are gone, and --watch has been replaced with --minify.\n\nIf these commands seem kinda gnarly, remember that we don’t have to type out them every single time. They can go in the scripts section of our package.json file. I’ve consolidated this one a bit by moving the common flags into an esbuild script, and having start and build scripts add the extra flags at the end.\n\nExtra Credit\n\nOkay, so we’ve covered our goals. But esbuild can do a lot more. Here are some other flags you might think of enabling:\n\n--sourcemap makes debugging easier by exporting .js.map and .css.map files that tell the browser how your bundled code relates to your source code.\n\n--loader lets you import different file types from JS and CSS. A particularly useful option is the file loader, which copies the file to the build directory and embeds the file name as a string. For example, to be able to use url() to load .woff2 files from a stylesheet, you’d add --loader:.woff2=file.\n\n--jsx-factory lets you change how JSX is compiled into function calls if you’d rather replace React with a different library like Preact.\n\nThis isn’t a flag, but in your tsconfig.json (or jsconfig.json if you don’t use TypeScript) you can set paths to { \"~/*\": [\"./src/*\"] } in order to use ~ to reference your files relative to the source root. For example, you’d import src/components/App.tsx with import App from \"~/components/App\".\n\nThat's it! Happy hacking! If you come up with any cool additions to this, let me know!\n\nUpdates\n\nOriginally, this post recommended --loader:.module.css=local-css in order to use the local-css loader for .module.css files. As of esbuild v0.19.0, that’s the default behavior, so that flag is no longer necessary!",
  "title": "An Extremely Simple React Starter Kit"
}