Skip to content

Commit a7cf267

Browse files
chore: polish docs, test web app fixture. (#31)
1 parent 55e3135 commit a7cf267

13 files changed

Lines changed: 123 additions & 13 deletions

File tree

README.md

Lines changed: 21 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -11,14 +11,16 @@ Node.js utility for transforming a JavaScript or TypeScript file from an ES modu
1111

1212
Highlights
1313

14+
- ESM ➡️ CJS and CJS ➡️ ESM with one function call.
1415
- Defaults to safe CommonJS output: strict live bindings, import.meta shims, and specifier preservation.
15-
- Opt into stricter/looser behaviors: live binding enforcement, import.meta.main gating, and top-level await strategies.
16-
- Can optionally rewrite relative specifiers and write transformed output to disk.
16+
- Configurable lowering modes: full syntax transforms or globals-only.
17+
- Specifier tools: add extensions, add directory indexes, or map with a custom callback.
18+
- Output control: write to disk (`out`/`inPlace`) or return the transformed string.
1719

1820
> [!IMPORTANT]
1921
> All parsing logic is applied under the assumption the code is in [strict mode](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Strict_mode) which [modules run under by default](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Modules#other_differences_between_modules_and_classic_scripts).
2022
21-
By default `@knighted/module` transforms the one-to-one [differences between ES modules and CommonJS](https://nodejs.org/api/esm.html#differences-between-es-modules-and-commonjs). Options let you control syntax rewriting, specifier updates, and output.
23+
By default `@knighted/module` transforms the one-to-one [differences between ES modules and CommonJS](https://nodejs.org/api/esm.html#differences-between-es-modules-and-commonjs). Options let you control syntax rewriting (full vs globals-only), specifier updates, and output.
2224

2325
## Requirements
2426

@@ -30,9 +32,9 @@ By default `@knighted/module` transforms the one-to-one [differences between ES
3032
npm install @knighted/module
3133
```
3234

33-
## Example
35+
## Quick examples
3436

35-
Given an ES module:
37+
ESM ➡️ CJS:
3638

3739
**file.js**
3840

@@ -93,6 +95,17 @@ use@computer: $ node file.cjs
9395
invoked directly by node
9496
```
9597
98+
CJS ➡️ ESM:
99+
100+
```js
101+
import { transform } from '@knighted/module'
102+
103+
await transform('./file.cjs', {
104+
target: 'module',
105+
out: './file.mjs',
106+
})
107+
```
108+
96109
## Options
97110
98111
```ts
@@ -124,20 +137,21 @@ type ModuleOptions = {
124137
}
125138
```
126139
127-
Behavior notes (defaults in parentheses)
140+
### Behavior notes (defaults in parentheses)
128141
129142
- `target` (`commonjs`): output module system.
130143
- `transformSyntax` (true): enable/disable the ESM↔CJS lowering pass; set to `'globals-only'` to rewrite module globals (`import.meta.*`, `__dirname`, `__filename`, `require.main` shims) while leaving import/export syntax untouched. In `'globals-only'`, no helpers are injected (e.g., `__requireResolve`), `require.resolve` rewrites to `import.meta.resolve`, and `idiomaticExports` is skipped. See [globals-only](#globals-only-scope).
131144
- `liveBindings` (`strict`): getter-based live bindings, or snapshot (`loose`/`off`).
132145
- `appendJsExtension` (`relative-only` when targeting ESM): append `.js` to relative specifiers; never touches bare specifiers.
133146
- `appendDirectoryIndex` (`index.js`): when a relative specifier ends with a slash, append this index filename (set `false` to disable).
147+
- `appenders` precedence: `rewriteSpecifier` runs first; if it returns a string, that result is used. If it returns `undefined` or `null`, `appendJsExtension` and `appendDirectoryIndex` still run. Bare specifiers are never modified by appenders.
134148
- `dirFilename` (`inject`): inject `__dirname`/`__filename`, preserve existing, or throw.
135149
- `importMeta` (`shim`): rewrite `import.meta.*` to CommonJS equivalents.
136150
- `importMetaMain` (`shim`): gate `import.meta.main` with shimming/warning/error when Node support is too old.
137151
- `requireMainStrategy` (`import-meta-main`): use `import.meta.main` or the realpath-based `pathToFileURL(realpathSync(process.argv[1])).href` check.
138152
- `detectCircularRequires` (`off`): optionally detect relative static require cycles and warn/throw.
139153
- `topLevelAwait` (`error`): throw, wrap, or preserve when TLA appears in CommonJS output.
140-
- `rewriteSpecifier` (off): rewrite relative specifiers to a chosen extension or via a callback.
154+
- `rewriteSpecifier` (off): rewrite relative specifiers to a chosen extension or via a callback. Precedence: the callback (if provided) runs first; if it returns a string, that wins. If it returns `undefined` or `null`, the appenders still apply.
141155
- `requireSource` (`builtin`): whether `require` comes from Node or `createRequire`.
142156
- `cjsDefault` (`auto`): bundler-style default interop vs direct `module.exports`.
143157
- `out`/`inPlace`: write the transformed code to a file; otherwise the function returns the transformed string only.

package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@knighted/module",
3-
"version": "1.0.0-rc.6",
3+
"version": "1.0.0",
44
"description": "Bidirectional transform for ES modules and CommonJS.",
55
"type": "module",
66
"main": "dist/module.js",

src/module.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ const rewriteSpecifierValue = (
7777
const relative = /^(?:\.\.?)\//
7878

7979
if (relative.test(collapsed)) {
80-
return value.replace(/(.+)\.(?:m|c)?(?:j|t)s([)'"]*)?$/, `$1${rewriteSpecifier}$2`)
80+
return value.replace(/(.+)\.(?:m|c)?(?:j|t)sx?([)'"]*)?$/, `$1${rewriteSpecifier}$2`)
8181
}
8282
}
8383

src/specifier.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,10 @@ const formatSpecifiers = async (src: string, ast: ParseResult, cb: Callback) =>
147147

148148
await walk(ast.program, {
149149
enter(node) {
150+
if (node.type === 'ImportExpression') {
151+
formatExpression(node)
152+
}
153+
150154
if (node.type === 'ExpressionStatement') {
151155
const { expression } = node
152156

src/types.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,8 @@ export type ModuleOptions = {
3838
appendJsExtension?: 'off' | 'relative-only' | 'all'
3939
/** Add directory index (e.g. /index.js) or disable. */
4040
appendDirectoryIndex?: string | false
41-
/** Control __dirname and __filename handling. */
41+
/** Precedence: rewriteSpecifier runs first; if it returns a string that wins. If it returns undefined or null, appenders apply. Bare specifiers are never modified by appenders. */
42+
/** Control __dirname/__filename handling (inject shims, preserve existing, or throw on use). */
4243
dirFilename?: 'inject' | 'preserve' | 'error'
4344
/** How to treat import.meta. */
4445
importMeta?: 'preserve' | 'shim' | 'error'
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import type { Config } from './utils/config.js'
2+
import { renderApp } from './ui/app.js'
3+
4+
export const boot = async (url: string) => {
5+
const { loadConfig } = await import('./utils/config.js')
6+
const loaded = await loadConfig(url)
7+
const rendered = renderApp(loaded)
8+
return { rendered, url }
9+
}
10+
11+
export const lazyApp = async () => {
12+
const { renderApp: render } = await import('./ui/app.js')
13+
return render({ title: 'lazy' })
14+
}
15+
16+
export const hydrate = (target: HTMLElement, config: Config) =>
17+
renderApp({ ...config, targetId: target.id })
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
declare namespace JSX {
2+
type Element = any
3+
interface IntrinsicElements {
4+
[elemName: string]: any
5+
}
6+
}
7+
8+
declare module 'react/jsx-runtime' {
9+
export const jsx: any
10+
export const jsxs: any
11+
export const Fragment: any
12+
}
13+
14+
declare module 'react/jsx-dev-runtime' {
15+
export const jsxDEV: any
16+
export const Fragment: any
17+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import type { Config } from '../utils/config.js'
2+
import { View } from './view.js'
3+
4+
export type Rendered = { node: JSX.Element; props: Config }
5+
6+
export const renderApp = (config: Config): Rendered => ({
7+
node: <View title={config.title} target={config.targetId ?? 'root'} />,
8+
props: config,
9+
})
10+
11+
export const mount = (config: Config) => renderApp(config)
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
export const View = ({ title, target }: { title: string; target: string }) => (
2+
<section data-target={target}>
3+
<h1>{title}</h1>
4+
<p>ready</p>
5+
</section>
6+
)

0 commit comments

Comments
 (0)