Tools: Common Conventions
There's a variety of behaviors in authored JavaScript that (web) developers may depend on that aren't described in any officially published spec. This is an attempt to collect them and link to the best possible source for the expected behavior.
Module System
- Spec deviations
- Lack of import hoisting (running code before imports in the same file)
- Different import graph traversal order
- Conflating default exports and module namespace objects
- Named exports from JSON imports
- Implicitly injected imports, e.g. for JSX element and fragment
- JS-style module resolution inside of CSS, e.g. for
url()or@import
Additional module types
- CommonJS modules
- CommonJS code inside of ES modules (e.g.
require) - CommonJS / ES module interop
- Detection of ES module-generated CommonJS code (
__esModule)
- CommonJS code inside of ES modules (e.g.
- File content imports (e.g.
./x.txt?raw) - File content as
data:URL imports (e.g.data-url:./x.txt) - Bundle content imports (
bundle-text:./foo.css) - Side-effect CSS imports, implicitly added to the page
.module.css(import CSS classes)- Macro imports (https://bun.sh/docs/bundler/macros; https://parceljs.org/features/macros/)
Specifier resolution
- Symlink vs real path for file system resolution
- Extension searching & deduping of modules; target-environment-specific suffixes.
- Node-style resolution of bare specifiers
exportsfield inpackage.jsonimportsfield inpackage.json<name>/*for package-relative imports ("self-referential import")
- Exports conditions
browserdevelopment/production- Runtime keys (e.g.
node) modulerequire/importmodule-syncnode-addons
~for package-relative importssourcefield inpackage.jsonbrowserfield inpackage.jsonmodulefield inpackage.jsonaliasfield inpackage.json- Deno-style resolution (
deno.json#imports) pathsoption intsconfig.jsonnpm:scheme in import specifiersnode:scheme in import specifiers
Glob imports
import.meta.glob(Vite),import.meta.webpackContext/require.context(webpack)import('/files/' + x + '.json')
HMR (Hot Module Replacement)
import.meta.hot/import.meta.webpackHot/module.hot, typically tool-specific APIs
Syntax Features
- TypeScript-style type annotations
- TypeScript syntax features like
enum - JSX expressions
Runtime APIs
globalalias forglobalThisprocess.envfs.readFileSync(replaced with file contents)new WebWorker(new URL('./x.js', import.meta.url), { type: 'module' })(replaced with bundle withx.jsas entry point)- Similar for worklets, service worker.
Compilation
sideEffectsfield inpackage.json- compiler-notations-spec
/*#__PURE__*//*#__NO_SIDE_EFFECTS__*/
- Constant replacement & defines
- Special case:
process.envreplacement - Special case:
process.env.NODE_ENVreplacement - Special case:
import.meta.envreplacement - Special case:
import.meta.env.{DEV,PROD,MODE,SSR}replacement
- Special case:
Configuration
.envfiles for constant definitionsbrowserliststo configure target configuration(s)
Notable omissions
These are things that developers may depend on and that aren't part of official specs - but they aren't provided/polyfilled by tools, generally speaking.
Error.prepareStackTraceError.stack
Sequence of fields inspected in package.json in order to locate a suitable bundle for use, and also what forms of JS artifacts are acceptable for each field.
Specific example:
- Webpack 4 apparently looks for
["browser", "main", "module"]fields, in that order ( https://github.com/webpack/webpack/blob/v4.44.2/lib/WebpackOptionsDefaulter.js#L66-L74C18- Webpack 4 also apparently does not accept
.mjsfor"browser"or"main"from what I've seen - Webpack 5's logic is more complex, and does seem to accept
.mjsfiles ( https://github.com/webpack/webpack/blob/v5.97.1/lib/config/defaults.js#L1531-L1570 )
- Webpack 4 also apparently does not accept
- Metro has its own sequence of fields that it looks at, including
"react-native". It also looks for files with a.native.jsextension as part of its resolution
I ran into a lot of these issues while updating the Redux packages:
also tagging @aryaemami59 , as this is likely up your alley
Here are some additional fields I’ve encountered:
Top-Level/Main Fields in package.json
unpkgumdjsnext:main(Legacy)jsnext(Legacy)umd:mainesnextreact-nativetypingstypesVersions
exports conditions
sourcewebpackreact-nativebundenoreact-serverstylesassassetscriptesmoduleselectronworkerworkletsveltesolid
I will add explanations for each of these hopefully soon enough.
Potential source confusion
publishConfig: This field allows for overriding certain fields inpackage.jsonat during publication. Each package manager can target or leave out certain fields inpackage.jsonand it's not always clear which package managers target which fields.directories: Does it actually provide a use-case or is it just meta-data?
For more exports conditions, also see the WinterCG runtime keys proposal.
(Via @nicolo-ribaudo) ESTree is another internal convention across tools.