esbuild’s platform option doesn’t just tell it which environment to target; it fundamentally changes how it interprets and transforms your code.
Let’s see it in action. Imagine you have a simple script index.js with this content:
// index.js
import process from 'process';
console.log('Running on platform:', process.platform);
If we build this for node first:
esbuild index.js --bundle --outfile=bundle.node.js --platform=node --format=cjs
And then run it:
node bundle.node.js
The output will be something like:
Running on platform: linux
Now, if we try to build and run the exact same source file for browser:
esbuild index.js --bundle --outfile=bundle.browser.js --platform=browser --format=esm
And then try to run bundle.browser.js using Node.js (which is a common mistake, trying to run browser code in Node):
node bundle.browser.js
You’ll get an error:
/path/to/your/project/bundle.browser.js:3
import process from 'process';
^^^^^^^
SyntaxError: Cannot use import statement outside a module
This isn’t because process isn’t available in Node.js. It’s because when platform: 'browser' is set, esbuild assumes you’re writing for a web browser. In browsers, there’s no global process object like in Node.js. Also, the import syntax for Node.js modules is treated differently. esbuild, for browser platform, will try to resolve process as if it were a browser-compatible module, which it isn’t. It also assumes you want modern JavaScript features that might not be available in older Node.js versions without specific flags.
The platform option has three main values: node, browser, and neutral.
node: This is for building code that will run in a Node.js environment. esbuild will assume Node.js built-in modules (likefs,path,process) are available and can be imported directly. It also allows you to userequire()syntax freely if you chooseformat: 'cjs', and it optimizes for Node.js’s module resolution.browser: This is for building code that will run in a web browser. esbuild will not assume Node.js built-ins are available. If you try to importprocessorfs, esbuild will throw an error unless you provide an alternative (e.g., a polyfill or a plugin that transforms it). It also assumes you’re targeting modern browser APIs and JavaScript features.neutral: This is for code that can run in either environment, or a custom environment. esbuild will make fewer assumptions. It won’t automatically bundle Node.js built-ins, nor will it assume browser-specific globals. You’ll typically need to be more explicit about dependencies and may need to use plugins for environment-specific code.
The format option (cjs, esm, iife) works in conjunction with platform. For platform: 'node', format: 'cjs' is common, allowing require(). format: 'esm' is also supported for modern Node.js. For platform: 'browser', format: 'esm' or format: 'iife' (Immediately Invoked Function Expression) are typical.
When you set platform: 'browser', esbuild performs several key transformations:
- Node.js Built-in Modules: It will not resolve imports for Node.js built-in modules like
process,fs,path,http, etc. If your code imports them, esbuild will error out. This is a feature, not a bug, forcing you to use browser-compatible alternatives or polyfills. process.env: It automatically replacesprocess.env.NODE_ENV(and otherprocess.envvariables if specified via--define) with their literal values during the build. In a browser context, there’s noprocessobject at runtime.__dirnameand__filename: These Node.js globals are not available in the browser and will cause errors if used. esbuild will not polyfill them for the browser platform.- Module Syntax: While esbuild supports both
requireandimportsyntax, theplatformsetting influences how it handles them, especially when interacting with environment-specific modules. Forbrowser, it leans towardsimportand assumes an ES Module environment.
The most surprising thing about esbuild’s platform setting is how aggressively it enforces the boundaries of the target environment, especially for browser. It doesn’t just try to make your code run in the browser; it assumes you’re writing for the browser, stripping out or rejecting Node.js-specific constructs as if they were syntax errors. This is why trying to run a bundle.browser.js file that imports process in Node.js fails with a SyntaxError related to import, even though Node.js does have process and import statements. esbuild has already transformed the code based on the browser platform assumption, making it incompatible with the Node.js runtime before Node.js even gets a chance to interpret the process import.
If you need to use Node.js modules in a browser build, you typically need to use a plugin, such as @esbuild-plugins/node-modules-polyfill or manually provide your own polyfills and use esbuild’s --external flag or banner option to inject them.
The next thing you’ll likely encounter is needing to manage different environment variables for different builds, often controlled via the --define flag.