TypeScript+Node API无重启热重载方案咨询
Great question—dealing with slow full restarts in a large TypeScript project (especially with TypeORM dragging down boot time) is such a pain. Let’s dive into your options, including the tsc --watch + require.cache approach you’re curious about, plus some more polished tools to make your dev workflow faster.
1. Can You Use tsc --watch + require.cache? Short Answer: Yes, But With Caveats
This approach absolutely works for basic cases, but you’ll need to handle some edge cases around stateful modules (like your TypeORM connections or global singletons). Here’s how it works:
- First, run
tsc --watchin one terminal to compile your TypeScript to JavaScript continuously. - Then, write a custom Node script that:
- Starts your server.
- Listens for changes to the compiled
.jsfiles (use a tool likechokidarfor reliable file watching). - When a file changes, deletes the corresponding entry from
require.cacheso Node will reimport the updated module next time it’s referenced. - Restarts only the parts of your server that depend on the changed module (e.g., routes, service logic) instead of the entire process.
Key Limitations to Watch For
- Stateful modules: You can’t just clear the cache for modules that manage long-lived resources (like TypeORM database connections, Redis clients, or connection pools). Doing this will leave orphaned connections in memory, leading to leaks or database errors. Instead:
- Keep these stateful modules in a separate file that you never clear from the cache.
- Add cleanup logic (e.g., closing old connections) before reloading dependent modules.
- Deep dependencies: If a changed module is imported by many other files, you’ll need to track those dependencies to clear their cache too—this can get messy for large projects.
2. More Robust Tools to Avoid Reinventing the Wheel
For most large projects, using a pre-built tool will save you time and avoid the pitfalls of manual cache management. Here are the best options:
tsx watch: This is the easiest solution to start with.tsxis a fast TypeScript execution tool that includes built-in hot reloading. It intelligently tracks module dependencies and only reloads the parts of your code that changed, not the entire server. Just run:tsx watch src/server.tsIt handles TypeScript compilation under the hood, so you don’t need to run
tsc --watchseparately. It’s also way faster thants-node-devfor large projects.esbuild with HMR: esbuild is an extremely fast bundler that supports Hot Module Replacement (HMR) for Node.js. You can set up a watch script that compiles your TypeScript and triggers HMR updates when files change. The configuration is minimal compared to webpack, and it’s perfect for projects where speed is critical.
Webpack HMR: If you’re already using webpack for frontend code, you can extend it to bundle your Node API and enable HMR. While the setup is more complex, it gives you fine-grained control over which modules are reloaded. You’ll need to use plugins like
webpack-node-externalsto exclude Node core modules andwebpack-hot-middlewareto handle HMR in your server.Optimize TypeORM Startup: Since your slow restarts are tied to TypeORM, fixing this directly can make even full restarts bearable:
- Disable automatic entity scanning and manually import your entities (this avoids the time-consuming filesystem scan on boot).
- Enable TypeORM’s metadata cache: Add
cache: { type: "file" }to your TypeORM config to cache entity metadata between restarts.
3. Example: Manual tsc --watch + Cache Clearing Script
If you want to try the manual approach, here’s a basic script to get you started (you’ll need to install chokidar first with npm install chokidar --save-dev):
// dev-server.js const chokidar = require('chokidar'); const path = require('path'); const http = require('http'); let server = null; // Function to reload the server logic function reloadServer() { // Clear cache for non-stateful modules (adjust paths to match your project) Object.keys(require.cache).forEach(cacheKey => { if (cacheKey.includes(path.join(__dirname, 'dist', 'routes')) || cacheKey.includes(path.join(__dirname, 'dist', 'services'))) { delete require.cache[cacheKey]; } }); // Close existing server if running if (server) { server.close(() => { console.log('Old server closed. Reloading...'); startServer(); }); return; } startServer(); } // Load and start the server from compiled code function startServer() { const { app } = require('./dist/server'); // Your compiled server entry file server = http.createServer(app); server.listen(3000, () => { console.log('Server running on http://localhost:3000'); }); } // Watch for changes in compiled JS files chokidar.watch(path.join(__dirname, 'dist'), { ignoreInitial: true }) .on('change', reloadServer) .on('add', reloadServer); // Initial server start reloadServer();
How to Use This
- Run
tsc --watchin one terminal to compile your TypeScript. - Run
node dev-server.jsin another terminal to start the hot-reloading server.
Remember to adjust the paths in the cache-clearing logic to exclude any stateful modules (like your TypeORM connection setup) to avoid connection leaks.
内容的提问来源于stack exchange,提问作者osdiab

