Skip to main content
Concepts

Entry Point Detection

How Odyn identifies entry points, excludes barrel files, and handles orphan cycles.

In per-file and folder bundle modes, Odyn must decide which files are entry points (files that produce their own output) and which are internal modules (files that only execute when imported by an entry point). This page describes the detection algorithm, barrel file exclusion, and orphan cycle handling.

Algorithm Overview

Entry point detection runs before esbuild and operates on the project's virtual file map. The algorithm:

  1. Parse every file with es-module-lexer to extract import and export statements.
  2. Build a dependency graph: forward edges (file A imports file B) and reverse edges (file B is imported by file A).
  3. Identify files with zero importers (no other project file imports them).
  4. Exclude barrel files from the entry point set.
  5. Promote one file per orphaned cycle to ensure all code is reachable.

The result is a sorted list of entry points passed to esbuild as input files.

What Qualifies as an Entry Point

A file is an entry point if all of the following are true:

  • No other project file imports it (zero reverse edges in the dependency graph).
  • It is not a barrel file (see below).
  • It is a JS/TS file, or a CSS file not imported by any JS file.

JSON files are never standalone entry points. CSS files are entry points only when no JS file imports them (if a JS file imports a CSS file, esbuild handles the CSS as part of that JS entry's output).

Barrel File Exclusion

A barrel file is a module that contains only re-export statements and no executable code. Common examples:

barrel-examples.js
// barrel: yes
export * from './utils';
export { animate } from './animation';

// barrel: no (has executable code)
export * from './utils';
console.log('initializing');

// barrel: no (has side-effect import)
export * from './utils';
import './polyfill';

Odyn classifies a file as a barrel when all three conditions hold:

  1. Facade: the file contains only import/export statements (no executable code).
  2. Has exports: the file re-exports at least one binding.
  3. No side-effect imports: the file does not contain import './something' statements that execute code.

The third condition prevents misclassification. A file like export * from './a'; import './init' is facade and has exports, but the side-effect import (import './init') needs to execute. If this file were excluded as a barrel, the side effect would be silently lost.

Barrel files are excluded from the entry point set because they serve only as aggregation points. Their re-exported bindings are already reachable through the files they re-export from.

Orphan Cycle Handling

Circular dependencies can create situations where every file in a cycle has at least one importer, so none qualifies as an entry point through the standard rule.

Example: A.js imports B.js, and B.js imports A.js. Both files have importers, so neither is detected as an entry point. Without intervention, both files would be excluded from the build output.

Odyn resolves this through orphan cycle promotion:

  1. After the initial entry point pass, compute which files are reachable from the current entry points by following dependency edges.
  2. For each JS file that is unreachable and is not a barrel file, promote it to an entry point.
  3. Recompute reachability after each promotion.

This guarantees that exactly one file per disconnected cycle becomes an entry point, and all cycle members become reachable. Promotion order is alphabetical for deterministic results.

Performance

Worst case is O(n^2) if every file is in its own disconnected cycle (n promotions, each requiring an n-node reachability pass). This is acceptable for Odyn projects, which typically contain dozens of files, not thousands.

File Type Behavior

File typeEntry point rule
.js, .ts, .jsx, .tsx, .mjs, .cjsEntry point if not imported and not a barrel
.cssEntry point if not imported by any JS file
.scss, .sassCompiled to CSS first, then follows CSS rules
.jsonNever a standalone entry point

How Detection Affects Output

In per-file mode, each entry point produces its own output file. Shared code referenced by multiple entry points is split into chunks automatically by esbuild.

In folder bundle mode, entry point detection runs within each folder's scope. Only entry points inside the folder become inputs to that folder's IIFE bundle.

In bundled mode, the output is always a single IIFE, but entry point detection still determines its contents and order: the build generates an internal entry that includes every detected entry point in explorer (file tree) order — importing module files and inlining plain script files — and each entry point pulls in its own imports from there. Detection also powers bare import warnings. See File Types and Bundling for the practical inclusion and ordering rules.

Debugging Entry Points

If a file you expect in the output is missing, check:

  1. Is another file importing it? If so, it is an internal module, not an entry point.
  2. Is it a barrel file? Barrel files are excluded by design.
  3. Is the file empty? Empty files produce no output.
  4. Is the file excluded via the include_in_per_file flag?

See All JavaScript Empty for cases where all output files are empty.