can you find in-depth documents on Obsidian-typesc...
Erstellt am: 28. August 2026
Erstellt am: 28. August 2026
can you find in-depth documents on Obsidian-typescript EMS, Module, babel?
Yes. I’m assuming “EMS” means ESM (ECMAScript Modules).
I checked both the files available in this chat and the web. There aren’t any conversation/internal documents currently available to search, but I found a strong set of primary technical references:
module: "ESNext", but esbuild bundles it into CommonJS (format: "cjs") for main.js. That source→bundle distinction is important for understanding Obsidian plugins. (GitHub)default, __esModule, esModuleInterop, Babel-style interop, and why the same import can behave differently under Node/bundlers/transpilers. (TypeScript)module, moduleResolution, nodenext, preserve, commonjs, bundler, .mts/.cts, package "type", etc. (TypeScript)tsc: Babel can strip/transform TypeScript syntax but does not type-check it. (Babel)__esModule, default-import interop, and Babel's importInterop modes. (Babel)The key Obsidian pipeline is roughly:
TypeScript source using ESM syntax
import { Plugin } from "obsidian"
↓
TypeScript type-checking (module: ESNext)
↓
esbuild bundling + transpilation
↓
CommonJS output (format: cjs)
↓
main.js loaded by Obsidian
So Babel is not part of the standard Obsidian sample-plugin toolchain; esbuild fills much of the transpilation/bundling role. Babel becomes relevant when comparing transformation semantics or when a project explicitly introduces Babel. (GitHub)
If you want, I can go one level deeper and assemble these into a technical reading pack specifically covering Obsidian + TypeScript + ESM/CJS + esbuild + Babel, ordered from fundamentals → advanced interoperability → Obsidian-specific implementation.
yes , i'd like a lot
Absolutely. I’d build the reading pack around Obsidian → TypeScript → ESM/CJS → esbuild → Babel → interoperability/debugging, rather than studying each technology separately.
The single most useful artifact is Obsidian’s official sample plugin. (GitHub)
Obsidian sample plugin repository
Obsidian esbuild.config.mjs
Obsidian tsconfig.json
Study those two configuration files side-by-side.
The interesting thing is that tsconfig.json says:
json"module": "ESNext", "target": "ES2021", "moduleResolution": "node", "isolatedModules": true, "allowSyntheticDefaultImports": true
while esbuild says, conceptually:
jsentryPoints: ["src/main.ts"], bundle: true, format: "cjs", target: "es2021", outfile: "main.js"
It also marks things such as obsidian, electron, CodeMirror, Lezer and Node built-ins as external dependencies. (GitHub)
That apparent contradiction—
textTypeScript: ESNext modules BUT esbuild: CommonJS
—is actually the first big concept to understand.
The TypeScript configuration describes how TypeScript should understand your source, while esbuild controls what ultimately gets packaged into the plugin's runtime artifact.
A useful mental model is:
textsrc/main.ts │ │ TypeScript syntax + types ▼ ┌───────────────────────────┐ │ TypeScript compiler model │ │ │ │ module = ESNext │ │ target = ES2021 │ │ isolatedModules = true │ └─────────────┬─────────────┘ │ │ source still uses │ import / export ▼ ┌───────────────────────────┐ │ esbuild │ │ │ │ bundle = true │ │ format = cjs │ │ treeShaking = true │ │ target = es2021 │ └─────────────┬─────────────┘ │ ▼ main.js │ │ CommonJS bundle ▼ Obsidian
And some dependencies deliberately escape the bundle:
textimport { Plugin } from "obsidian" │ ▼ ESBUILD │ "obsidian" is external │ ▼ runtime dependency │ ▼ Obsidian
That distinction between source module syntax, TypeScript's model, bundler resolution, bundle format, and runtime module system explains an enormous percentage of confusing JavaScript tooling behavior.
This is probably the single best deep document I found.
TypeScript — Modules Theory (TypeScript)
Don't treat this as API documentation. It's closer to a small textbook chapter.
Its central idea is:
The module system is ultimately determined by the host.
TypeScript has to model what that host will eventually do. (TypeScript)
That gives you several separate questions:
text1. What syntax am I writing? ↓ import/export 2. How does TypeScript resolve it? ↓ moduleResolution 3. Does TypeScript transform it? ↓ module 4. Does a bundler process it? ↓ esbuild / Rollup / etc. 5. What does the final JS contain? ↓ ESM or CJS 6. Who executes that JS? ↓ Node / browser / Electron / Obsidian host
Do not move on until those six questions feel distinct.
TypeScript's current documentation explicitly emphasizes that ESM/CJS interoperability differs among Node, bundlers, and transpilers, so there isn't one universal set of interop rules. (TypeScript)
Learn native ECMAScript modules independently of TypeScript.
The conceptual primitives are:
js// named export export const x = 10; // named import import { x } from "./a.js"; // default export export default foo; // default import import foo from "./a.js"; // namespace import * as foo from "./a.js"; // re-export export { foo } from "./a.js"; // dynamic import const module = await import("./a.js");
The important thing isn't memorizing syntax.
Understand that ESM has language-level module semantics.
Conceptually:
textES module ──────────────────────────── imports │ ├──── binding ────► export │ ├──── binding ────► export │ └──── binding ────► export
Imports aren't simply equivalent to:
jsconst foo = someObject.foo;
ESM exports/imports have module semantics including live bindings.
This becomes important once Babel or a bundler tries to reproduce those semantics using CommonJS.
Now contrast ESM with CommonJS.
Classic CommonJS:
jsconst obsidian = require("obsidian"); module.exports = something;
or:
jsexports.foo = foo;
Conceptually:
textCommonJS ───────────────────── require("foo") │ ▼ execute module │ ▼ module.exports │ ▼ JavaScript object/value
Whereas ESM is conceptually closer to:
textimport │ ▼ module loader │ ▼ module graph │ ▼ linked bindings │ ▼ evaluation
That difference is why converting between them isn't quite as trivial as replacing:
jsimport x from "x";
with:
jsconst x = require("x");
After Modules Theory, read this carefully:
TypeScript — ESM/CJS Interoperability (TypeScript)
This is advanced, but extremely relevant.
It explains why:
jsimport foo from "foo";
can mean subtly different things depending on whether foo is:
textNative ESM │ ├── default export │ ▼ CommonJS │ ├── module.exports │ ▼ Babel-generated CommonJS │ ├── __esModule ├── exports.default │ ▼ Bundled module
TypeScript's documentation specifically discusses the infamous “double default” problem, __esModule, default imports, named exports, Node's behavior, and bundler/transpiler behavior. (TypeScript)
This is essential background for understanding Babel.
esModuleInteropSpend time on this one option:
json{ "compilerOptions": { "esModuleInterop": true } }
It's much more consequential than its innocent-looking name suggests.
Suppose a CJS library contains:
jsmodule.exports = function hello() {};
Developers naturally want:
tsimport hello from "library";
But CJS doesn't actually contain:
jsexport default hello;
So somebody has to define what that import means.
That "somebody" might be:
textTypeScript Babel Node esbuild Webpack Rollup another runtime
And historically they haven't all agreed.
The TypeScript documentation recommends esModuleInterop for applications containing CommonJS code, particularly when a third-party transpiler or bundler is producing the actual JavaScript. (TypeScript)
allowSyntheticDefaultImportsNow revisit Obsidian's configuration:
json"allowSyntheticDefaultImports": true
That option affects TypeScript's checking, not the underlying module's actual exports.
Conceptually:
textReality: CommonJS package module.exports = foo TypeScript allows: import foo from "package" │ ▼ "Synthetic" default-import interpretation
That's another major lesson:
What TypeScript permits isn't necessarily a description of the JavaScript module's physical structure.
moduleNow read the full module reference.
TypeScript Modules Reference (TypeScript)
Pay particular attention to:
textmodule ├── commonjs ├── es2015 / es2020 / esnext ├── node16 ├── node18 ├── nodenext └── preserve
One modern option worth understanding is:
json"module": "preserve"
With preserve, TypeScript can preserve individual ESM imports/exports while also retaining CommonJS-style constructs where they appear. TypeScript describes this as closely reflecting the capabilities of modern bundlers. (TypeScript)
Don't immediately change an Obsidian project's configuration to it, though. Learn what it means first.
moduleResolutionThis is different from module.
It's probably the most common conceptual mistake.
textmodule │ └── What module format/semantics should TypeScript model or emit? moduleResolution │ └── Given: import x from "foo" how does TypeScript determine what "foo" refers to?
Current TypeScript has resolution modes including:
textnode-style modes nodenext node16 etc. versus bundler
bundler models behaviors common to modern JavaScript bundlers, including package "exports"/"imports" while allowing conveniences such as extensionless relative imports. (TypeScript)
So:
tsimport { foo } from "./utils";
raises the question:
textWhat is "./utils"? ./utils.ts? ./utils.tsx? ./utils.js? ./utils/index.ts? package? exports mapping? something else?
Module resolution answers that question.
Then:
TypeScript — Choosing Compiler Options (TypeScript)
This becomes especially interesting when bundling.
There's a fundamental complication:
textSome imports │ ▼ bundled by esbuild Other imports │ ▼ marked external │ ▼ resolved by runtime
That's exactly relevant to Obsidian because its sample esbuild configuration externalizes obsidian, Electron, CodeMirror/Lezer packages and Node built-ins. (GitHub)
TypeScript's guide points out that a single compilation can't simultaneously use one resolution model for bundled imports and another for externalized imports. (TypeScript)
That is a genuinely deep limitation worth understanding.
Babel historically played a huge role in establishing the ESM→CJS conventions developers became accustomed to.
Read:
Babel — transform modules CommonJS (Babel)
For example, Babel can take something conceptually like:
jsexport default 42;
and represent the result in CommonJS using an exports.default property plus an __esModule marker. (Babel)
That marker:
js__esModule
is worth researching deeply.
It isn't native ESM syntax.
It's part of the interoperability conventions created by transpiler ecosystems.
importInteropOnce you understand __esModule, investigate Babel's:
textimportInterop
in that same CommonJS transform documentation. (Babel)
This gets directly into:
text"Babel-style module" vs "Node-style CommonJS" vs plain CommonJS
This is where many mysterious errors originate:
textfoo.default is undefined
or:
textfoo is not a function
or:
textCannot use import statement outside a module
or:
textrequire is not defined
or:
textNamed export not found
These errors often aren't really "TypeScript problems."
They're module-boundary problems.
preset-envThen read:
Concentrate on:
texttargets modules
The modules option can be:
text"commonjs" "amd" "umd" "systemjs" "auto" false
and false preserves ESM. (Babel)
This teaches an important lesson:
textBabel does not necessarily convert ESM.
It depends on configuration.
You might have:
textTypeScript │ ▼ Babel │ │ modules = false ▼ ESM preserved │ ▼ Bundler │ ▼ CJS bundle
or:
textTypeScript │ ▼ Babel │ │ modules = commonjs ▼ CommonJS
or simply, as in the standard Obsidian setup:
textTypeScript source │ ▼ esbuild │ ▼ CJS
No Babel required.
This distinction is absolutely fundamental.
textTYPE CHECKING ──────────────────────────── Does this program make sense according to TypeScript types? TypeScript TRANSPILATION ──────────────────────────── Convert syntax into different JS. TypeScript / Babel / esbuild MODULE RESOLUTION ──────────────────────────── What file/package does "foo" mean? TypeScript / Node / bundler BUNDLING ──────────────────────────── Combine dependency graph into one/few output artifacts. esbuild / Rollup / Webpack RUNTIME ──────────────────────────── Actually execute resulting JS. browser / Node / Electron / Obsidian
One tool can occupy multiple boxes.
That's why discussions saying "TypeScript does X" or "Babel does X" can become misleading without specifying which responsibility we're talking about.
With that knowledge, the official Obsidian setup becomes much easier to reason about. (GitHub)
You write:
tsimport { Plugin, Notice, WorkspaceLeaf } from "obsidian";
Your source is written using ESM syntax.
TypeScript sees:
json"module": "ESNext"
esbuild sees:
jsbundle: true
but also:
jsexternal: ["obsidian", ...]
and finally:
jsformat: "cjs"
Therefore a better architecture diagram is:
textSOURCE │ ▼ ┌─────────────┐ │ TypeScript │ │ .ts │ └──────┬──────┘ │ │ ESM syntax ▼ ┌─────────────┐ │ esbuild │ └──────┬──────┘ │ ┌───────────┴───────────┐ │ │ bundled code externals │ │ │ obsidian │ electron │ Node APIs │ CodeMirror ▼ │ tree shake │ │ │ ▼ │ transform │ │ │ └───────────┬───────────┘ ▼ main.js │ │ format = CJS │ ▼ Obsidian runtime
That is the architecture I'd keep in your head.
"type"After that, learn:
json{ "type": "module" }
versus:
json{ "type": "commonjs" }
and:
text.js .mjs .cjs .ts .mts .cts
In Node-style environments, file extension and the nearest package.json "type" can participate in deciding whether JavaScript is treated as ESM or CJS. TypeScript's Node-oriented module modes model those rules. (TypeScript)
This is where a filename stops being "just a filename."
Study the difference between:
tsimport x from "./foo";
tsimport x from "./foo.js";
tsimport x from "foo";
tsimport x from "#foo";
These are different categories of module specifiers.
And importantly, TypeScript generally doesn't simply rewrite your module-specifier strings to fix whatever runtime-resolution model you've chosen. (TypeScript)
That's why:
ts"./foo"
can type-check in one configuration but produce code unsuitable for another host.
exportsThen investigate modern:
json{ "exports": { ".": "./dist/index.js", "./utils": "./dist/utils.js" } }
and conditional exports such as:
json{ "exports": { ".": { "import": "...", "require": "..." } } }
Now this:
tsimport foo from "package";
may resolve differently depending on whether the consumer is using:
textimport require Node bundler specific conditions
TypeScript's bundler resolution mode supports modern package "exports" and "imports" concepts while modeling bundler-style resolution. (TypeScript)
This is especially useful for Obsidian because its esbuild configuration explicitly enables:
jstreeShaking: true
(GitHub)
Start thinking in graphs:
textmain.ts │ ├──── import A ───► a.ts │ │ │ └── function used() │ ├──── import B ───► b.ts │ │ │ └── function unused() │ └──── import C ───► c.ts
The bundler builds a dependency graph and attempts to remove unreachable/unnecessary code.
This is another reason preserving structured ESM imports long enough for the bundler to analyze them can be useful.
isolatedModulesNotice Obsidian's sample has:
json"isolatedModules": true
(GitHub)
That is not random.
When a transpiler such as esbuild processes individual TypeScript files without performing TypeScript's whole-program type analysis, some TypeScript constructs can become problematic.
isolatedModules makes TypeScript warn about constructs that aren't safe under this kind of per-file transpilation model.
So think:
texttsc ──────────────────── understands whole TS program and type graph esbuild TS transpilation ──────────────────── primarily transforms files without TypeScript's complete type-checking model
That is why a common development workflow separates:
texttype checking
from:
textbuilding
If you want to get genuinely strong at this rather than merely fix one configuration, do it in this order:
Phase A — Obsidian
tsconfig.jsonesbuild.config.mjssrc/main.ts → main.jsPhase B — Module fundamentals
Phase C — TypeScript
modulemoduleResolutionesModuleInteropallowSyntheticDefaultImportsisolatedModulesnodenextbundlerpreservePhase D — interoperability
default semantics__esModuleimport()Phase E — Babel
@babel/plugin-transform-modules-commonjs (Babel)importInteroppreset-envmodules: falsemodules: "commonjs"modules: "auto" (Babel)Phase F — bundling
bundleexternalformattargetAnd finally return to Obsidian's build configuration. You'll understand considerably more of what every line is doing.
If you only keep five tabs/bookmarks, make them:
TypeScript Modules Theory — best conceptual foundation. (TypeScript)
Read Modules Theory
TypeScript ESM/CJS Interoperability — best explanation of why modules become weird. (TypeScript)
Read ESM/CJS Interoperability
TypeScript Modules Reference — detailed reference once you understand the theory. (TypeScript)
Read Modules Reference
Obsidian esbuild configuration — concrete implementation you're actually dealing with. (GitHub)
Read Obsidian esbuild config
Babel CommonJS transform — explains the historical/transpiler side of interoperability. (Babel)
Read Babel CommonJS transform
If you're learning this specifically to develop/debug Obsidian plugins, the next useful step would be to take a real Obsidian TypeScript file and trace one import all the way through TypeScript resolution → esbuild → generated main.js → Obsidian runtime, including exactly what happens to import { Plugin } from "obsidian". That exercise makes the whole module system click much faster than reading another ten docs.