Skip to content

Commit 09cc9b9

Browse files
committed
docs: update README with enhanced usage instructions and new features section
1 parent 8fc7530 commit 09cc9b9

7 files changed

Lines changed: 102 additions & 43 deletions

File tree

README.md

Lines changed: 57 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,30 @@
11
# module-tsx
22

3-
Run TypeScript (and React) module directly in the browser.
3+
Run TypeScript (and React) modules directly in the browser, without a build step.
44

5-
# Usage
5+
## Usage
66

77
```html
8-
<!-- Load This Library -->
8+
<!-- Load this library -->
99
<script type="module" src="https://esm.sh/module-tsx"></script>
1010

1111
<div id="root"></div>
1212

13-
<!-- Write Your TypeScript Module -->
13+
<!-- Write your TypeScript/TSX inline -->
1414
<script type="module-tsx">
15-
import React from "react"; // <- This will be converted to https://esm.sh/react
16-
import { createRoot } from "react-dom/client"; // <- This will also be converted automatically
17-
import App from "./src/App.tsx"; // <- Your TypeScript module will also be compiled on the fly
18-
import lib from "https://my.cdn.domain"; // <- Direct http import will NOT be compiled
15+
import React from "react"; // bare specifier → https://esm.sh/react
16+
import { createRoot } from "react-dom/client";
17+
import App from "./src/App.tsx"; // relative imports are fetched and compiled on the fly
1918
20-
const root = document.getElementById("root") as HTMLDivElement; // <- You can write TypeScript directly
19+
const root = document.getElementById("root") as HTMLDivElement;
2120
createRoot(root).render(
2221
<React.StrictMode>
2322
<App />
2423
</React.StrictMode>
25-
// ^ You can also write JSX/TSX directly
2624
);
2725
</script>
28-
<!-- OR -->
26+
27+
<!-- OR load an external file -->
2928
<script type="module-tsx" src="./main.tsx"></script>
3029
```
3130

@@ -37,3 +36,50 @@ export default function App() {
3736
return <div>Hello, module-tsx!</div>;
3837
}
3938
```
39+
40+
## Features
41+
42+
- **TypeScript & JSX/TSX** — transpiled on the fly using the TypeScript compiler
43+
- **Bare specifier resolution**`import "react"` is automatically rewritten to a CDN URL (defaults to `https://esm.sh/`)
44+
- **Relative imports**`.ts`/`.tsx` files are fetched and compiled recursively
45+
- **CSS imports**`import "./style.css"` injects a `<style>` tag; `import "./style.module.css"` returns a CSS Modules object
46+
- **Import map support** — respects `<script type="importmap">` on the page for specifier overrides
47+
- **Auto React import** — JSX is detected and `import React from "react"` is injected automatically if missing
48+
49+
## Programmatic API
50+
51+
```ts
52+
import { ModuleTSX } from "module-tsx";
53+
54+
const m = new ModuleTSX({
55+
baseUrl: location.href, // base for resolving relative specifiers
56+
importMap: { imports: { react: "https://esm.sh/react" } }, // merged with page importmaps
57+
resolveBareSpecifier: "https://cdn.jsdelivr.net/npm/", // string prefix or function
58+
});
59+
60+
// Import a module by specifier
61+
const react = await m.import("react");
62+
const app = await m.import("./app.tsx");
63+
64+
// Import from an in-memory string
65+
const { x } = await m.importCode(document.location.href, `export const x: number = 1`);
66+
67+
// Events
68+
m.addEventListener("import", (e) => console.log("loading", e.detail.id));
69+
m.addEventListener("import:error", (e) => console.error("failed", e.detail.id, e.detail.error));
70+
m.addEventListener("transform", (e) => console.log("compiling", e.detail.sourceUrl));
71+
m.addEventListener("transform:error", (e) => console.error("compile error", e.detail.sourceUrl, e.detail.error));
72+
```
73+
74+
## CDN
75+
76+
```html
77+
<!-- ESM -->
78+
<script type="module" src="https://esm.sh/module-tsx"></script>
79+
80+
<!-- ESM (self-contained, no external dependencies) -->
81+
<script type="module" src="https://raw.esm.sh/module-tsx/dist/index.mjs"></script>
82+
83+
<!-- UMD (exposes window.ModuleTSX) -->
84+
<script src="https://raw.esm.sh/module-tsx/dist/index.umd.js"></script>
85+
```

docs/src/pages/ApiReference.tsx

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import React from "react";
22
import { Table } from "soda-material";
33
import CodeBlock from "../components/CodeBlock.tsx";
44

5-
const CONSTRUCTOR_EXAMPLE = `import { ModuleTSX } from "https://yieldray.github.io/module-tsx/dist/index.mjs";
5+
const CONSTRUCTOR_EXAMPLE = `import { ModuleTSX } from "https://esm.sh/module-tsx";
66
77
const instance = new ModuleTSX({
88
baseUrl: "https://my-site.com/app/",
@@ -24,7 +24,7 @@ const code = \`
2424
const mod = await instance.importCode("https://my-site.com/", code);
2525
mod.greet("World"); // "Hello, World!"`;
2626

27-
const EVENTS_EXAMPLE = `import { instance } from "https://yieldray.github.io/module-tsx/dist/index.mjs";
27+
const EVENTS_EXAMPLE = `import { instance } from "https://esm.sh/module-tsx";
2828
2929
// Listen to all events
3030
instance.addEventListener("*", (e) => {
@@ -49,18 +49,18 @@ instance.addEventListener("transform:error", (e) => {
4949
});`;
5050

5151
const SINGLETON_EXAMPLE = `<!-- The singleton instance auto-processes <script type="module-tsx"> tags -->
52-
<script type="module" src="https://yieldray.github.io/module-tsx/dist/index.mjs"></script>
52+
<script type="module" src="https://esm.sh/module-tsx"></script>
5353
<script type="module-tsx" src="./src/main.tsx"></script>
5454
5555
<!-- Access the singleton from another module script -->
5656
<script type="module">
57-
import { instance } from "https://yieldray.github.io/module-tsx/dist/index.mjs";
57+
import { instance } from "https://esm.sh/module-tsx";
5858
instance.addEventListener("transform", (e) => {
5959
console.log("Transformed:", e.detail.sourceUrl);
6060
});
6161
</script>`;
6262

63-
const CUSTOM_CDN_EXAMPLE = `import { ModuleTSX } from "https://yieldray.github.io/module-tsx/dist/index.mjs";
63+
const CUSTOM_CDN_EXAMPLE = `import { ModuleTSX } from "https://esm.sh/module-tsx";
6464
6565
// Use a string prefix (appended to bare specifier)
6666
const instance = new ModuleTSX({

docs/src/pages/BasicUsage.tsx

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import React from "react";
22
import CodeBlock from "../components/CodeBlock.tsx";
33

4-
const INLINE_SCRIPT = `<script type="module" src="https://yieldray.github.io/module-tsx/dist/index.mjs"></script>
4+
const INLINE_SCRIPT = `<script type="module" src="https://esm.sh/module-tsx"></script>
55
66
<!-- Inline TypeScript/JSX directly in your HTML -->
77
<script type="module-tsx">
@@ -12,7 +12,7 @@ const INLINE_SCRIPT = `<script type="module" src="https://yieldray.github.io/mod
1212
createRoot(root).render(<h1>Hello from inline TSX!</h1>);
1313
</script>`;
1414

15-
const EXTERNAL_SCRIPT = `<script type="module" src="https://yieldray.github.io/module-tsx/dist/index.mjs"></script>
15+
const EXTERNAL_SCRIPT = `<script type="module" src="https://esm.sh/module-tsx"></script>
1616
1717
<!-- Reference an external .tsx file -->
1818
<script type="module-tsx" src="./src/main.tsx"></script>`;
@@ -42,15 +42,14 @@ export default function BasicUsage() {
4242

4343
<h2>Inline Scripts</h2>
4444
<p>
45-
Write TypeScript and JSX directly inside a{" "}
46-
<code>{"<script type=\"module-tsx\">"}</code> tag:
45+
Write TypeScript and JSX directly inside a <code>{'<script type="module-tsx">'}</code> tag:
4746
</p>
4847
<CodeBlock code={INLINE_SCRIPT} language="html" />
4948

5049
<h2>External Files</h2>
5150
<p>
52-
Reference an external <code>.tsx</code> file using the <code>src</code> attribute.
53-
module-tsx will fetch, compile, and execute it:
51+
Reference an external <code>.tsx</code> file using the <code>src</code> attribute. module-tsx will fetch,
52+
compile, and execute it:
5453
</p>
5554
<CodeBlock code={EXTERNAL_SCRIPT} language="html" />
5655
<p>The entry file can import other local files using relative paths:</p>
@@ -59,9 +58,8 @@ export default function BasicUsage() {
5958

6059
<h2>The async Attribute</h2>
6160
<p>
62-
By default, multiple <code>{"<script type=\"module-tsx\">"}</code> tags execute
63-
sequentially — each one waits for the previous to finish. Add the <code>async</code>{" "}
64-
attribute to execute them concurrently:
61+
By default, multiple <code>{'<script type="module-tsx">'}</code> tags execute sequentially — each one waits for
62+
the previous to finish. Add the <code>async</code> attribute to execute them concurrently:
6563
</p>
6664
<CodeBlock code={ASYNC_SCRIPT} language="html" />
6765
</article>

docs/src/pages/GettingStarted.tsx

Lines changed: 10 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ const QUICK_START = `<!doctype html>
1010
<div id="root"></div>
1111
1212
<!-- 1. Load module-tsx from CDN -->
13-
<script type="module" src="https://yieldray.github.io/module-tsx/dist/index.mjs"></script>
13+
<script type="module" src="https://esm.sh/module-tsx"></script>
1414
1515
<!-- 2. Write TypeScript/React directly -->
1616
<script type="module-tsx">
@@ -36,9 +36,8 @@ export default function GettingStarted() {
3636
<article>
3737
<h1 className="text-4xl font-bold mt-0">Getting Started</h1>
3838
<p className="text-lg leading-relaxed text-[var(--md-sys-color-on-surface-variant)]">
39-
<strong>module-tsx</strong> lets you run TypeScript and React directly in the browser —
40-
no build step, no npm install, no bundler required. You publish source code and users run
41-
it instantly.
39+
<strong>module-tsx</strong> lets you run TypeScript and React directly in the browser — no build step, no npm
40+
install, no bundler required. You publish source code and users run it instantly.
4241
</p>
4342

4443
<h2>Quick Start</h2>
@@ -47,22 +46,20 @@ export default function GettingStarted() {
4746

4847
<h2>Serve Locally</h2>
4948
<p>
50-
Because module-tsx fetches your source files via HTTP, you need to serve your project
51-
from a local server (not <code>file://</code>):
49+
Because module-tsx fetches your source files via HTTP, you need to serve your project from a local server (not{" "}
50+
<code>file://</code>):
5251
</p>
5352
<CodeBlock code={SERVE_CMD} language="bash" />
5453

5554
<h2>How It Works</h2>
5655
<p>
57-
When the page loads, module-tsx finds every{" "}
58-
<code>{"<script type=\"module-tsx\">"}</code> tag, fetches and transforms the
59-
TypeScript/JSX source using the TypeScript compiler running in the browser, then executes
60-
the result as an ES module via a Blob URL.
56+
When the page loads, module-tsx finds every <code>{`<script type="module-tsx">`}</code> tag, fetches and
57+
transforms the TypeScript/JSX source using the TypeScript compiler running in the browser, then executes the
58+
result as an ES module via a Blob URL.
6159
</p>
6260
<p>
63-
Bare specifiers like <code>import React from "react"</code> are automatically resolved
64-
to <code>https://esm.sh/react</code>. You can use any npm package without installing
65-
anything.
61+
Bare specifiers like <code>import React from "react"</code> are automatically resolved to{" "}
62+
<code>https://esm.sh/react</code>. You can use any npm package without installing anything.
6663
</p>
6764
</article>
6865
);

docs/src/pages/TypeScriptSupport.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ export default function TypeScriptSupport() {
3131
<p>
3232
module-tsx uses the TypeScript compiler (running in the browser) to transpile your code
3333
before execution. You can write full TypeScript syntax — interfaces, generics, enums,
34-
type assertions — directly inside <code>{"<script type=\"module-tsx\">"}</code> tags or
34+
type assertions — directly inside <code>{`<script type="module-tsx">`}</code> tags or
3535
in <code>.ts</code> / <code>.tsx</code> files.
3636
</p>
3737

src/index.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ async function sideEffect() {
2929
);
3030
}
3131
for (const key in ["integrity", "crossorigin"] as (keyof HTMLScriptElement)[]) {
32-
if (script[key]) {
32+
if (script[key as keyof HTMLScriptElement]) {
3333
warn(`script with type="${TYPE_ATTRIBUTE_VALUE}" does not support ${key} attribute.`);
3434
}
3535
}
@@ -42,4 +42,9 @@ async function sideEffect() {
4242
}
4343
}
4444

45+
/**
46+
* Since this module can be loaded as both ESM and UMD, we listen for DOMContentLoaded to ensure all type="module-tsx" tags are present.
47+
* ESM scripts are always deferred, so the document is already fully parsed when this module executes and the listener fires immediately.
48+
* Classic scripts run inline as the parser encounters them, so they must wait for DOMContentLoaded.
49+
*/
4550
document.addEventListener("DOMContentLoaded", sideEffect);

src/module-tsx.ts

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -32,9 +32,13 @@ interface ModuleTSXConfig {
3232
}
3333

3434
interface ModuleTSXEventMap {
35+
/** Fired when an import starts. `id` is the original specifier as passed by the caller. */
3536
import: CustomEvent<{ id: string }>;
37+
/** Fired when an import fails. `id` is the specifier at the point of failure. */
3638
"import:error": CustomEvent<{ id: string; error: any }>;
39+
/** Fired when a source file starts being transpiled. */
3740
transform: CustomEvent<{ sourceUrl: string }>;
41+
/** Fired when transpilation fails. */
3842
"transform:error": CustomEvent<{ sourceUrl: string; error: any }>;
3943
}
4044

@@ -44,6 +48,11 @@ interface IModuleTSX extends EventTarget {
4448
listener: (this: ModuleTSX, ev: ModuleTSXEventMap[T]) => any,
4549
options?: boolean | AddEventListenerOptions,
4650
): void;
51+
addEventListener(
52+
type: string,
53+
listener: EventListenerOrEventListenerObject,
54+
options?: boolean | AddEventListenerOptions,
55+
): void;
4756
}
4857

4958
export class ModuleTSX extends EventTarget implements IModuleTSX {
@@ -100,9 +109,13 @@ export class ModuleTSX extends EventTarget implements IModuleTSX {
100109
}
101110

102111
public async importCode(sourceUrl: string, code: string, options?: any): Promise<any> {
103-
const transformedUrl = await this.transformSourceModule("esm", sourceUrl, code);
104-
const module = await import(transformedUrl, options);
105-
return module;
112+
try {
113+
const transformedUrl = await this.transformSourceModule("esm", sourceUrl, code);
114+
return await import(transformedUrl, options);
115+
} catch (error) {
116+
this.emit("import:error", { id: sourceUrl, error });
117+
throw error;
118+
}
106119
}
107120

108121
/** Transform module source code and return a blob URL with the transformed content */

0 commit comments

Comments
 (0)