Context outdir and serve servedir should be able to point to different folders
Before 0.17.0
serve function was building in memory and then use servedir to find other files then build.
Now
You need to set outdir of context if you have multiple output files.
outdir of context and servedir can't be different.
const context = await esbuild.context({
...
entryPoints: [
'src/index.tsx',
'src/index-callback.tsx',
'src/index-refresh.tsx',
],
outdir: '../dist.serve',
...
})
await context.serve({
servedir: 'src',
})
Expected
The outdir of context would be used to check if file exists and will be returned by serve-server.
If file is not found in outdir it will searched in servedir of serve command and returned
servedir and outdir can be set to different folders
That would also match the documentation see https://esbuild.github.io/api/#serve-arguments -> servedir
Reason
Currently we use for development the serve server.
We let our JS build but all other files like static, html, css, etc. we serve directly unbuild from our source folder.
Within version 0.17.0 this behavior is impossible.
So we can't use esbuild with a minimal configurations
The way this is supposed to work is that you'd use esbuild to build your website into outdir, and then serve your web root with a web server. In production, the website would be a production-ready web server such as nginx. So for example your web root might be /www and your outdir might be /www/js.
As a convenience, esbuild offers a built-in development server where you can provide your web root as servedir and esbuild will automatically rebuild and serve up-to-date copies of all files in outdir that it would have generated normally had you not been using the development server. To do that, esbuild needs a mapping from the HTTP request path to the file system path, which is why it requires outdir to be contained within the servedir directory tree.
You are welcome to do custom stuff that's more complicated than what esbuild provides as built-in behavior, but you may end up needing to do more work to achieve that. There is documentation for how to do that here: https://esbuild.github.io/api/#serve-proxy. It's not that much more code and gives you complete control over how the web server behaves.
In case it helps: You can run esbuild's web server without any entry points. You can even do this for separate directories on different ports:
await esbuild.context({}).then(ctx => ctx.serve({
servedir: 'dir1',
port: 8001,
}))
await esbuild.context({}).then(ctx => ctx.serve({
servedir: 'dir2',
port: 8002,
}))
Perhaps I'm misunderstanding the explanation, but this seems like a very undesirable change. It's extremely common to have:
./
dist/
src/
static/
favicon.svg
index.html
…
Where static/ are files that don't need processing (aside from injecting some stuff into index.html) and dist/ is the (git-ignored) source compiled for deployment plus the contents of static (post-injection) copied over.
I feel like I'm mansplaining here, but when running a dev server, we don't want anything written to disk (it's slow and almost never useful). This is how esbuild behaved until recently.
I'm confused when you say it's necessary to now write to disk because it wasn't necessary until v0.17.0.
I was previously supplying outdir as just the servedir, which worked fine because nothing was actually written to it; but now esbuild dumps a bunch of junk in there. I tried putting a symlink within servedir to dist/, but esbuild seems to not follow the symlink, so then a bunch of files are "missing" at run time.
Is this maybe user-error and I just need to add some new config to prevent esbuild writing to disk for serve? (I looked but didn't see anything, and your message above seems to be saying writing to disk is indeed intended).
I tried putting a symlink within servedir to
dist/, but esbuild seems to not follow the symlink, so then a bunch of files are "missing" at run time.
Oh gosh, silly me 🤦♂️ I set up the symlink in reverse.
I was able to work around the write-to-disk issue with:
cd static
ln -s ../dist dist
const ctx = await context({ outdir: 'static/dist', ...otherBuildConfig });
await ctx.serve({ servedir: 'static/dist', ...otherServeConfig });
(Note in my actual config, my *dir values are computed with fancy things like fileURLToPath and import.meta.url)
The dev server is now noticeably slower than it was previously though (0.15.18 → 0.18.12).
We've just had the same issue and solved it using the separate context method mentioned in the comment above. Using a proxy server, we first forward to esbuild, and if that returns a 404, we fall back to the second esbuild context which serves from the source folder.
async function run() {
const ctx = await esbuild.context({
...config,
sourcemap: true,
});
await ctx.watch();
let listenHost = "localhost";
let listenPort = 3011;
let keyfile = undefined;
let certfile = undefined;
let serverType = "http";
const { hosts, port } = await ctx.serve({
servedir: "build/public",
host: "127.0.0.1",
fallback: "build/public/index.html",
});
// We need to emulate our production HTML processing here too
const indexPath = "lib/public/index.html";
let indexContent = fs.readFileSync(indexPath, "utf-8");
const processor = new HTMLProcessor({
process: {
env: {
NODE_ENV: "development",
},
},
})
indexContent = processor.processContent(indexContent, indexPath);
indexContent = processor.template(indexContent, processor.data);
// Start a second server to serve files from lib/public for assets not built by esbuild
const { hosts: srcHosts, port: srcPort } = await esbuild.context({}).then(ctx => ctx.serve({
servedir: "lib/public",
host: "127.0.0.1",
port: port + 1,
}));
// Then start a proxy server on port 3000
(serverType === "https" ? https : http)
.createServer(
{
key: keyfile,
cert: certfile,
},
(req, res) => {
const options = {
hostname: hosts[0],
port: port,
path: req.url,
method: req.method,
headers: {
...req.headers,
},
};
delete options.headers.host;
// Serve index.html from lib/public for "/"
if (req.url === "/") {
res.writeHead(200, {
'Content-Type': 'text/html',
});
res.end(indexContent);
return;
}
// Forward each incoming request to esbuild
const proxyReq = http.request(options, (proxyRes) => {
// If the file is not found, try to serve from lib/public
if (proxyRes.statusCode === 404 && req.url) {
const srcReq = http.request({
...options,
hostname: srcHosts[0],
port: srcPort,
}, (srcRes) => {
// If it's still not found, serve index.html
if (srcRes.statusCode === 404) {
res.writeHead(200, {
'Content-Type': 'text/html',
});
res.end(indexContent);
return;
}
// Pipe the response from lib/public
res.writeHead(srcRes.statusCode, srcRes.headers);
srcRes.pipe(res, { end: true });
});
req.pipe(srcReq, { end: true });
return;
}
// Pipe the response from esbuild
res.writeHead(proxyRes.statusCode, proxyRes.headers);
proxyRes.pipe(res, { end: true });
});
req.pipe(proxyReq, { end: true });
},
)
.listen(listenPort, listenHost);
// eslint-disable-next-line no-console
console.log(`Project is running at ${serverType}://${listenHost}:${listenPort}/`);
}
run();