9

I am trying to create a simple SSR powered project using express + react. To do this, I need to simultaneously watch frontend and backend scripts during development.

The goal here is to use express routes to point to react page components. Now, I have this working, but I am having problems with DX.

Here are my package scripts:

    "build:client": "esbuild src/index.js --bundle --outfile=public/bundle.js --loader:.js=jsx",
    "build:server": "esbuild src/server.jsx --bundle --outfile=public/server.js --platform=node",
    "build": "npm run build:client && npm run build:server",
    "start": "node ./public/server.js"

Now this works if I do npm run build && npm run start, but the problem is that it doesn't watch for changes and rebuild the frontend bundle or restart the backend server.

Now, if I add --watch to the 2 build scripts, it only starts watching the index.js file and does not execute the other scripts.

So if I add nodemon to my start script, it doesn't matter because esbuild won't get past the first script due to the watcher.

Is there a simpler way of doing what I am trying to do here? I also want to add tailwind to this project once I figure this out, so any tips around that would be helpful as well.

jaxramus
  • 145
  • 2
  • 6
  • Hello did u manage to figure it out I'm having the same issue and actually the same structure as u. How did u add the server and client builds in the JavaScript version of building + watching for changes ? – Frozendawn Jul 22 '22 at 20:51
  • @Frozendawn i ended up using a package called `concurrently` to run the scripts in parallel. you can also use `npm-run-all` package with the `--parallel` flag – jaxramus Jul 23 '22 at 04:35

4 Answers4

6

I use this code snippet to watch my custom react and esbuild project

const esbuild = require("esbuild");
async function watch() {
  let ctx = await esbuild.context({
    entryPoints: ["./src/app.tsx"],
    minify: false,
    outfile: "./build/bundle.js",
    bundle: true,
    loader: { ".ts": "ts" },
  });
  await ctx.watch();
  console.log('Watching...');
}
watch();

For more details: https://esbuild.github.io/api/#watch

Zahin Afsar
  • 91
  • 2
  • 7
  • Excellent ! Note that one can set `logLevel` in BuildOptions to eg `info` to be told when a rebuild succeeds after an error occured. – la Fleur Mar 29 '23 at 12:25
5

@es-exec/esbuild-plugin-serve or @es-exec/esbuild-plugin-start are two esbuild plugins that can run your bundles or any command line script (similarly to nodemon) for you after building your project (supports watch mode for rebuilding and rerunning on file changes).

Doing this is as easy as:

import serve from '@es-exec/esbuild-plugin-serve';

/** @type import('@es-exec/esbuild-plugin-serve').ESServeOptions */
const options = {
  ... // Any options you want to provide.
};

export default {
    ..., // Other esbuild config options.
    plugins: [serve(options)],
};

The documentation can be found at the following:

Disclaimer: I am the author of these packages.

tim117
  • 244
  • 2
  • 14
3

I would suggest using the JS interface to esbuild, i.e., write a small JS script that requires esbuild and runs it, and then use the functional version of https://esbuild.github.io/api/#watch. Something like this:

require('esbuild').build({
  entryPoints: ['app.js'],
  outfile: 'out.js',
  bundle: true,
  watch: {
    onRebuild(error, result) {
      if (error) console.error('watch build failed:', error)
      else { 
        console.log('watch build succeeded:', result)
        // HERE: somehow restart the server from here, e.g., by sending a signal that you trap and react to inside the server.
      }
    },
  },
}).then(result => {
  console.log('watching...')
})
Christian Fritz
  • 20,641
  • 3
  • 42
  • 71
0

I had a very similar issue, and solved it with npm-run-all

In my case I was building a VS Code extension, so my package.json script line looks like this:

  "esbuild-watch": "npm-run-all --parallel esbuild-base esbuild-server -- --sourcemap --watch",