My previous solutions don’t work well at most organizations, because tools that rearrange imports like prettier
or ESLint sort-imports
will break the carefully handcrafted import order. Instead of continuously fighting the never-ending stream of new tooling, as of 2023, I suggest making these imports imperative using the import
function.
// bootstrap.ts <- this is your entrypoint
import dotenv from 'dotenv';
dotenv.config({ silent: process.env.NODE_ENV === 'production' });
await import('./server');
// server.ts
import express from 'express';
import SparkPost from 'sparkpost';
const sparky = new SparkPost();
...
In case you don’t have access to top-level awaits for some reason, you can try these alternatives:
import('./server').catch(console.error);
// or
require('./server');
Original answers
Javascript import
s are hoisted (but not Typescript!), so imported modules will initialize before any of the current modules initialization code gets to run. Fortunately imported modules are initialized in order, so a possible workaround is putting the config code in its own module:
// main.js <- make this your entry point
import "./config";
import "./server";
// config.js
import dotenv from "dotenv";
dotenv.config({ silent: process.env.NODE_ENV === 'production' });
// server.js
import express from 'express';
import SparkPost from 'sparkpost';
const sparky = new SparkPost();
...
Or even simpler:
// config.js
import dotenv from "dotenv";
dotenv.config({ silent: process.env.NODE_ENV === 'production' });
// server.js <- make this your entry point
import './config';
import express from 'express';
import SparkPost from 'sparkpost';
const sparky = new SparkPost();
...
Typescript does not hoist imports
The Typescript compiler currently does not hoist imports, so you could initialize dotenv
right in the entry point .ts
file, so your example in the question would just work. Linter tools tend to reject it and auto-formatting tools tend to break it tough, it is a fragile approach.