char/avril

(wip) why'd you have to go and make things so complicaaaaated?

git clone https://git.t4t.associates/char/avril

Charlotte Somnpm: assume "module" package.json entries are es modules61e3521

main
4.2 KiB74 linesraw

CommonJS support

avril executes CommonJS via synchronous require at the core V8 layer, with ESM wrappers for import of CommonJS. There is no global registry: every isolate keeps its own require cache shared by require and ESM.

deps: ['npm:cjslib@1']
// main.cjs
const lib = require('npm:cjslib@1');
const local = require('./a.cjs');

What works

  • Local .cjs/.cts files: require('./a.cjs'), require.resolve, JSON require('./data.json'), directory lookup (package.json main then index.js/.json/.cjs), extension lookup (.js/.json/.node rejecting .node). Local .js stays ESM: requiring it fails with require() of ESM … use import() instead.
  • npm CommonJS/JSON via require: .cjs, .cts (transpiled preserving CJS), and .js outside "type": "module" (nearest type boundary, including nested package.json scopes), unless that file is advertised as the package's module entry. That entry is ESM, without changing sibling files; explicit .cjs/.cts extensions still take precedence. .json parses via require, never via import. .mjs/.mts stay ESM.
  • ESM import of CommonJS (local and npm): import def, { named } from './a.cjs' where default is the exact module.exports value (objects share identity with require callers) and named exports snapshot its properties. Primitives expose default only (named is undefined).
  • Wrapper semantics: function (exports, require, module, __filename, __dirname) called with this as the initial exports, non-strict script semantics (top-level return legal), BOM/shebang stripped, absolute __filename/__dirname, module.id/filename/loaded plus module.require. The module is cached BEFORE execution so cycles observe partial exports and replacements; failures are removed for retry.
  • require.resolve returns the canonical path without executing. require(ESM) fails explicitly (use import() instead); node: builtins and native .node addons fail explicitly. avril: builtins via require are explicitly unsupported (use import()); dynamic import() from CJS uses the existing pipeline with the CJS filename as referrer.
  • Conditional exports/imports choose require (avril,require,default) independently from import (avril,import,module,default). Legacy npm entries prefer main for require (never module). Export targets stay exact in both modes.
  • Lazy/conditional/try-catch and computed requires never become eager imports: only static ESM imports are prepared up front. Local config npm bindings are acquired during async preparation of the local CJS root, never on sync require; unprepared npm:/remote acquisition via sync require fails clearly without network I/O or leaking project import maps into npm internals.

Limits

  • Named export analysis is static: exports.foo, module.exports.foo (identifier or valid-identifier string keys), module.exports = { … } object literals, and single-property Object.defineProperty(exports, "foo", …). Top-level this.foo executes (this === exports) but is not statically listed (use default). String keys that are not valid identifiers (for example exports["foo-bar"]) stay available via default only. exports = { … } does not affect module.exports and is ignored. module.exports = require(…) re-exports are not followed (only default). Computed keys beyond string literals are skipped.
  • Synchronous remote (http:/https:) require is unavailable (use import()); require of a remote URL is rejected without fetching. require.cache/require.main are not provided. require(ESM) is rejected (use import()); JSON loads only via require.
  • Capabilities: every require (including cached returns and require.resolve) checks referrer and target; sources are rechecked around reads and after user code, with transitive CommonJS closures rechecked so cached exports cannot bypass a later pledge. npm data flows through verified manifests and virtual-registry js-exec authority; local project grants never authorize npm cache files. Code loads require only js-exec, never read.
1# CommonJS support
2
3avril executes CommonJS via synchronous `require` at the core V8 layer, with
4ESM wrappers for `import` of CommonJS. There is no global registry: every
5isolate keeps its own `require` cache shared by `require` and ESM.
6
7```text
8deps: ['npm:cjslib@1']
9```
10```js
11// main.cjs
12const lib = require('npm:cjslib@1');
13const local = require('./a.cjs');
14```
15
16## What works
17
18- Local `.cjs`/`.cts` files: `require('./a.cjs')`, `require.resolve`, JSON
19  `require('./data.json')`, directory lookup (`package.json` `main` then
20  `index.js`/`.json`/`.cjs`), extension lookup (`.js`/`.json`/`.node`
21  rejecting `.node`). Local `.js` stays ESM: requiring it fails with
22  `require() of ESM … use import() instead`.
23- npm CommonJS/JSON via `require`: `.cjs`, `.cts` (transpiled preserving CJS),
24  and `.js` outside `"type": "module"` (nearest `type` boundary, including
25  nested `package.json` scopes), unless that file is advertised as the
26  package's `module` entry. That entry is ESM, without changing sibling files;
27  explicit `.cjs`/`.cts` extensions still take precedence. `.json` parses via
28  `require`, never via `import`. `.mjs`/`.mts` stay ESM.
29- ESM `import` of CommonJS (local and npm): `import def, { named } from
30  './a.cjs'` where `default` is the exact `module.exports` value (objects
31  share identity with `require` callers) and named exports snapshot its
32  properties. Primitives expose `default` only (`named` is `undefined`).
33- Wrapper semantics: `function (exports, require, module, __filename,
34  __dirname)` called with `this` as the initial `exports`, non-strict script
35  semantics (top-level `return` legal), BOM/shebang stripped, absolute
36  `__filename`/`__dirname`, `module.id`/`filename`/`loaded` plus
37  `module.require`. The module is cached BEFORE execution so cycles observe
38  partial exports and replacements; failures are removed for retry.
39- `require.resolve` returns the canonical path without executing.
40  `require(ESM)` fails explicitly (`use import() instead`); `node:` builtins
41  and native `.node` addons fail explicitly. `avril:` builtins via `require`
42  are explicitly unsupported (use `import()`); dynamic `import()` from CJS
43  uses the existing pipeline with the CJS filename as referrer.
44- Conditional `exports`/`imports` choose `require` (`avril,require,default`)
45  independently from `import` (`avril,import,module,default`). Legacy npm
46  entries prefer `main` for `require` (never `module`). Export targets stay
47  exact in both modes.
48- Lazy/conditional/`try-catch` and computed `require`s never become eager
49  imports: only static ESM `import`s are prepared up front. Local config npm
50  bindings are acquired during async preparation of the local CJS root, never
51  on sync `require`; unprepared `npm:`/remote acquisition via sync `require`
52  fails clearly without network I/O or leaking project import maps into npm
53  internals.
54
55## Limits
56
57- Named export analysis is static:
58  `exports.foo`, `module.exports.foo` (identifier or valid-identifier string
59  keys), `module.exports = { … }` object literals, and single-property
60  `Object.defineProperty(exports, "foo", …)`. Top-level `this.foo` executes
61  (`this === exports`) but is not statically listed (use `default`). String keys
62  that are not valid identifiers (for example
63  `exports["foo-bar"]`) stay available via `default` only. `exports = { … }`
64  does not affect `module.exports` and is ignored.
65  `module.exports = require(…)` re-exports are not followed (only `default`).
66  Computed keys beyond string literals are skipped.
67- Synchronous remote (`http:`/`https:`) `require` is unavailable (use `import()`); `require` of a remote URL is rejected without fetching. `require.cache`/`require.main` are not provided. `require(ESM)` is rejected (`use import()`); JSON loads only via `require`.
68- Capabilities: every `require` (including cached returns and
69  `require.resolve`) checks referrer and target; sources are rechecked around
70  reads and after user code, with transitive CommonJS closures rechecked so
71  cached exports cannot bypass a later pledge. npm data flows through verified
72  manifests and virtual-registry `js-exec` authority; local project grants
73  never authorize npm cache files. Code loads require only `js-exec`, never
74  `read`.