|
| 1 | +There's a lot to getting this set up by hand, but bear with me, and I promsie at the end of this you're going to understand RSCs at a depth that allows you to make trade-offs of when to use them and when to avoid them. |
| 2 | + |
| 3 | +So, first, we're going to use Webpack and Babel directly (despite normally I'd suggest Vite.) Why? Because this allows us to use the React team's code directly without a layer indirection between Vite and Webpack. In general I suggest Vite for React devs. |
| 4 | + |
| 5 | +So let's get our project started. In a new directory run |
| 6 | + |
| 7 | +```bash |
| 8 | +npm init -y |
| 9 | + |
| 10 | +``` |
| 11 | + |
| 12 | +You can either run that, or just copy this package.json into your project and run `npm i`. |
| 13 | + |
| 14 | +```json |
| 15 | +{ |
| 16 | + "name": "no-framework", |
| 17 | + "version": "1.0.0", |
| 18 | + "main": "index.js", |
| 19 | + "scripts": {}, |
| 20 | + "keywords": [], |
| 21 | + "author": "Brian Holt", |
| 22 | + "license": "Apache-2.0", |
| 23 | + "description": "React Server Components without a framework!", |
| 24 | + "dependencies": { |
| 25 | + "@babel/core": "^7.26.8", |
| 26 | + "@babel/plugin-transform-modules-commonjs": "^7.26.3", |
| 27 | + "@babel/preset-react": "^7.26.3", |
| 28 | + "@babel/register": "^7.25.9", |
| 29 | + "@fastify/static": "^8.1.0", |
| 30 | + "babel-loader": "^9.2.1", |
| 31 | + "css-loader": "^7.1.2", |
| 32 | + "doodle.css": "^0.0.2", |
| 33 | + "fastify": "^5.2.1", |
| 34 | + "html-webpack-plugin": "^5.6.3", |
| 35 | + "nodemon": "^3.1.9", |
| 36 | + "pino-pretty": "^13.0.0", |
| 37 | + "promised-sqlite3": "^2.1.0", |
| 38 | + "react": "^19.0.0", |
| 39 | + "react-dom": "^19.0.0", |
| 40 | + "react-server-dom-webpack": "^19.0.0", |
| 41 | + "sqlite3": "^5.1.7", |
| 42 | + "style-loader": "^4.0.0", |
| 43 | + "webpack": "^5.97.1", |
| 44 | + "webpack-cli": "^6.0.1" |
| 45 | + } |
| 46 | +} |
| 47 | +``` |
| 48 | + |
| 49 | +Either works! We need a lot of machinery to get this to work but the high level is we're going to be building a Fastify server that is going to be serving RSCs via the React Flight format. |
| 50 | + |
| 51 | +Let's set up Webpack. Create a webpack.config.js |
| 52 | + |
| 53 | +```javascript |
| 54 | +const path = require("node:path"); |
| 55 | +const HtmlWebpackPlugin = require("html-webpack-plugin"); |
| 56 | +const ReactServerWebpackPlugin = require("react-server-dom-webpack/plugin"); |
| 57 | + |
| 58 | +const mode = process.env.NODE_ENV || "development"; |
| 59 | +const development = mode === "development"; |
| 60 | + |
| 61 | +const config = { |
| 62 | + mode, |
| 63 | + entry: "./src/Client.jsx", |
| 64 | + module: { |
| 65 | + rules: [ |
| 66 | + { |
| 67 | + test: /\.jsx?$/, |
| 68 | + use: "babel-loader", |
| 69 | + exclude: /node_modules/, |
| 70 | + }, |
| 71 | + { |
| 72 | + test: /\.css$/i, |
| 73 | + use: ["style-loader", "css-loader"], |
| 74 | + }, |
| 75 | + ], |
| 76 | + }, |
| 77 | + resolve: { |
| 78 | + extensions: [".js", ".jsx"], |
| 79 | + }, |
| 80 | + plugins: [ |
| 81 | + new HtmlWebpackPlugin({ |
| 82 | + inject: true, |
| 83 | + publicPath: "/assets/", |
| 84 | + template: "./index.html", |
| 85 | + }), |
| 86 | + new ReactServerWebpackPlugin({ isServer: false }), |
| 87 | + ], |
| 88 | + output: { |
| 89 | + chunkFilename: development |
| 90 | + ? "[id].chunk.js" |
| 91 | + : "[id].[contenthash].chunk.js", |
| 92 | + path: path.resolve(__dirname, "dist"), |
| 93 | + filename: "[name].js", |
| 94 | + clean: true, |
| 95 | + }, |
| 96 | + optimization: { |
| 97 | + runtimeChunk: "single", |
| 98 | + }, |
| 99 | +}; |
| 100 | + |
| 101 | +module.exports = config; |
| 102 | +``` |
| 103 | + |
| 104 | +This isn't a Webpack class so let's not dive too deep here - we're just making a Webpack config that's going to compile our React from JSX to usable JS code, use style-loader to load CSS, use the HTML plugin to generate a valid HTML for us, and make sure it's all living in one bundle so our React Flight protocol can find client side components in the bundle. |
| 105 | + |
| 106 | +Let's set up the Babel config. Make a babel.config.js file. |
| 107 | + |
| 108 | +```javascript |
| 109 | +const development = (process.env.NODE_ENV || "development") === "development"; |
| 110 | + |
| 111 | +module.exports = { |
| 112 | + presets: [ |
| 113 | + [ |
| 114 | + "@babel/preset-react", |
| 115 | + { |
| 116 | + runtime: "automatic", |
| 117 | + useSpread: true, |
| 118 | + development: true, |
| 119 | + }, |
| 120 | + ], |
| 121 | + ], |
| 122 | +}; |
| 123 | +``` |
| 124 | + |
| 125 | +Now everything will work with Babel. |
| 126 | + |
| 127 | +Let's make an index.html |
| 128 | + |
| 129 | +```html |
| 130 | +<!DOCTYPE html> |
| 131 | +<html lang="en"> |
| 132 | + <head> |
| 133 | + <meta charset="utf-8" /> |
| 134 | + <meta name="viewport" content="width=device-width, initial-scale=1" /> |
| 135 | + <title>React Server Components without a Framework!</title> |
| 136 | + <link rel="stylesheet" href="/index.css" /> |
| 137 | + </head> |
| 138 | + <body class="doodle"> |
| 139 | + <div id="root"><!--ROOT--></div> |
| 140 | + </body> |
| 141 | +</html> |
| 142 | +``` |
| 143 | + |
| 144 | +Looks quite similar to our previous ones. Let's make a /public/index.css. [Copy it from here][css] |
| 145 | + |
| 146 | +Copy [this SQLite file][sqlite] to your root directory as well. |
| 147 | + |
| 148 | +Lastly add these scripts to your package.json |
| 149 | + |
| 150 | +```json |
| 151 | +"scripts": { |
| 152 | + "dev:client": "webpack --watch", |
| 153 | + "dev:server": "node --watch --conditions react-server server/main.js" |
| 154 | +}, |
| 155 | +``` |
| 156 | + |
| 157 | +Okay, this should give us everything that's needed for our app to function before we actually write the server and the React app. One bit to highlight is `const ReactServerWebpackPlugin = require("react-server-dom-webpack/plugin");` in our Webpack file - this is the magic plugin that will allow Webpack to _not_ render server components and only include client components. Other than that, this is a very standard React + Webpack set up. |
| 158 | + |
| 159 | +Another new thing you might be the `--conditions react-server` part of running our server app. This lets Node.js know how to resolve its modules - we're in a react-server condition so only import those and know not to import client modules. I had never used this feature of Node.js before but it's pretty cool, even if it's a bit niche. |
| 160 | + |
| 161 | +[css]: |
| 162 | +[sqlite]: |
0 commit comments