diff --git a/.editorconfig b/.editorconfig
new file mode 100644
index 0000000..aaac325
--- /dev/null
+++ b/.editorconfig
@@ -0,0 +1,8 @@
+root = true
+
+[*]
+indent_style = tab
+end_of_line = lf
+charset = utf-8
+trim_trailing_whitespace = true
+insert_final_newline = true
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 137c762..9e14bd7 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -11,13 +11,13 @@ on:
jobs:
test:
-
runs-on: ubuntu-latest
strategy:
matrix:
- node-version: [20.x, 22.x]
+ node-version: [23.x, 22.x, 20.x]
# See supported Node.js release schedule at https://nodejs.org/en/about/releases/
+ fail-fast: false # prevent a failure in other versions run cancelling others
steps:
- uses: actions/checkout@v4
diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml
index e75af05..1bba903 100644
--- a/.github/workflows/publish.yml
+++ b/.github/workflows/publish.yml
@@ -1,7 +1,9 @@
name: Publish to NPM
on:
- release:
- types: [created]
+ push:
+ tags:
+ - '**@*'
+
jobs:
build:
runs-on: ubuntu-latest
@@ -15,6 +17,7 @@ jobs:
node-version-file: '.nvmrc'
registry-url: 'https://registry.npmjs.org'
- run: npm ci
- - run: npm publish --provenance --access public
+ - name: Get the name of the package
+ run : npm publish --workspace=\"packages/${GITHUB_REF_NAME##*@}\"
env:
- NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
+ NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} --provenance --access public
diff --git a/.nvmrc b/.nvmrc
index 2edeafb..3c8ce91 100644
--- a/.nvmrc
+++ b/.nvmrc
@@ -1 +1 @@
-20
\ No newline at end of file
+23.2
diff --git a/README.md b/README.md
index 61a81ba..62bef8f 100644
--- a/README.md
+++ b/README.md
@@ -2,183 +2,39 @@
-[![npm version](https://img.shields.io/npm/v/nodejs-loaders.svg)](https://www.npmjs.com/package/nodejs-loaders)
-![size](https://img.shields.io/github/languages/code-size/JakobJingleheimer/nodejs-loaders)
![coverage](https://img.shields.io/coverallsCoverage/github/JakobJingleheimer/nodejs-loaders)
![tests](https://github.com/JakobJingleheimer/nodejs-loaders/actions/workflows/ci.yml/badge.svg)
-This package provides a variety of loaders to facilitate a quick and easy local development and CI testing environment.
-
-## Local dev
-
-```console
---loader=nodejs-loaders/dev/alias \
---loader=nodejs-loaders/dev/tsx \
---loader=nodejs-loaders/dev/svgx \
---loader=nodejs-loaders/dev/mismatched-format
-```
-
-Sequence here **is** important (you want to correct a mismatched package format before you try to use it, and SVGX is a form of JSX, so it needs TSX loader to finish the job).
-
-### Alias
-
-This loader supports 2 options, both of which follow TypeScript's [`paths`](https://www.typescriptlang.org/docs/handbook/modules/reference.html#paths); if you're using TypeScript, this loader handles the (important) half of work TypeScript ignores. It checks for `tsconfig.json` in the project root (the current working directory) and builds aliases from `compilerOptions.paths` if it exists.
-
-If you're not using TypeScript (or you're not using `compilerOptions.paths`¹), you can specify aliases in `package.json` in the project root (the current working directory) in the same way `compilerOptions.paths` are defined:
-
-```json
-{
- "aliases": {
- "…/*": "./src/*",
- "CONF": "./config.json"
- }
-}
-```
-
-¹ Note that if you are using aliases and do not set up `compilerOptions.paths`, TypeScript will make your life hell.
-
-#### A simple prefix
-
-This is commonly used to reference the project root; common prefixes are `@/` (or some variation like `@app/`) and `…/`: `import foo from '…/app/foo.mts;` → `${project_root}/src/app/foo.mts`.
-
-> [!TIP]
-> Due to package namespacing (aka ["scopes"](https://docs.npmjs.com/about-scopes)) it may be best to avoid using the "at" symbol (`@`) since that could lead to confusion over what is a package and what is an alias (especially if you eventually add a package named with the alias you're using).
-
-> [!IMPORTANT]
-> When configuring these aliases, ensure astrisks (`*`) are used correctly; configuring this for TypeScript can be extremely confusing. See [_Why are these tsconfig paths not working?_](https://stackoverflow.com/q/50679031) for some of the litany of ways configuration can fail.
-
-#### A pointer
-
-This is a static specifier similar to a bare module specifier: `foo` → `${project_root}/src/app/foo.mts`. This may be useful when you have a commonly referenced file like config (which may conditionally not even live on the same filesystem): `import CONF from 'conf';` → `${project_root}/config.json`.
-
-## Mismatched format
-
-Many packages are incorrectly configured, claiming to be ESM yet not actually surfacing ESM. This is most commonly due to a `package.json` using the non-standard `"module"` field:
-
-```json
-{
- "name": "mismatched-example",
- "type": "module",
- "main": "./dist/cjs.js",
- "module": "./dist/esm.js"
-}
-```
-
-`mismatched-example` has told node its main entry point is `./dist/cjs.js` and that it is ESM. Therefore, trying to import this package will (likely) explode in a `SyntaxError` as node loads `./dist/cjs.js` as ESM (instead of the CJS it actually is).
-
-This loader detects the explosion and re-instructs node to ignore the misconfiguration and instead load `./dist/cjs.js` as CJS (the loader doesn't try to find a potential ESM main entry point the package may have—there are too many options, several of which are non-standard).
-
-Note to package authors reading this: The simplest fix here is to distribute only CJS. See [_Configuring CommonJS & ES Modules for Node.js_](https://dev.to/jakobjingleheimer/configuring-commonjs-es-modules-for-nodejs-12ed) for a thorough explanation of options.
-
-> [!IMPORTANT]
-> Node.js now has experimental support that better handles this via [`--experimental-detect-module`](https://nodejs.org/api/cli.html#--experimental-detect-module). This loader may be re-purposed to address the root of the problem instead: that `mismatched-example`'s configuration is wrong.
-
-## JSX / TS(X)
-
-This loader checks for a `esbuild.config.mjs` in the project root (if you want to keep it elsewhere, consider a symlink in the project root pointing to its actual location); only options for [esbuild's "transform" API](https://esbuild.github.io/api/#transform) are valid (esbuild handles looking for a tsconfig). When none is found, it uses a few necessary default.
-
-This loader does _not_ handle TypeScript's file extension nonsense. Import specifiers must use the actual file extension of the file actually on disk:
-
-```
-./
- ├ …
- └ foo.ts
-```
-
-💥 `import foo from './foo.js';`
-✅ `import foo from './foo.ts';`
-
-
-Supported file extensions
-
-* `.jsx`
-* `.mts`
-* `.ts`
-* `.tsx`
-
-
-## CI testing
-
-```console
---loader=nodejs-loaders/testing/css-module \
---loader=nodejs-loaders/testing/media \
---loader=nodejs-loaders/testing/text
-```
-
-Sequence here is not important.
-
-### CSS Module
-
-This loads the module as a plain-object of simple key-value pairs of the css specifiers like:
-
-```css
-/* main.module.css */
-#Bar { font-weight: bold }
-
-.Foo {
- text-decoration: none
-
- .Baz { color: red }
-}
-
-.Qux .Zed { font-size: 1.1em }
-```
-
-```js
-import styles from 'main.module.css';
-
-styles.Bar; // 'Bar'
-styles.Baz; // 'Baz'
-styles.Foo; // 'Foo'
-styles.Zed; // 'Zed'
-```
-
-This ensures snapshots are unaffected by unrelated changes.
+This package provides a variety of loaders to facilitate a quick and easy CI testing environment.
> [!WARNING]
-> This loader does not differentiate classes vs ids; thus duplicate names can create a last-wins conflict. For example `#Foo` and `.Foo` will result in just `Foo: 'Foo'`. This is unlikely to cause any real-world problems (and you probably shouldn't be doing this anyway).
-
-### Media
-
-This loader returns the specifier (truncated from project root / current working directory) as the default export:
-
-```js
-import photo from './team.jpg'; // photo = '[…]/team.jpg'
-```
-
-This ensures snapshots are unaffected by the file system on which the test is run.
-
-
-Supported file extensions
+> These should NOT be used in production; they will likely not do what you need there anyway.
-Audio/Video:
-* `.av1`
-* `.mp3`
-* `.mp3`
-* `.mp4`
-* `.ogg`
-* `.webm`
-
-Images:
-
-* `.avif`
-* `.gif`
-* `.ico`
-* `.jpeg`
-* `.jpg`
-* `.png`
-* `.webp`
-
-
-### Text
-
-This loader handles files that are effectively plain text.
-
-
-Supported file extensions
-
-* `.graphql`
-* `.gql`
-* `.md`
-* `.txt`
-
+```console
+node
+# sequence here IS important:
+ --loader=@nodejs-loaders/alias \
+ --loader=@nodejs-loaders/tsx \
+ --loader=@nodejs-loaders/svgx \
+ --loader=@nodejs-loaders/mismatched-format \
+# sequence here is NOT important:
+ --loader=@nodejs-loaders/css-module \
+ --loader=@nodejs-loaders/media \
+ --loader=@nodejs-loaders/text \
+ ./main.js
+```
+
+* [Alias loader](./packages/alias/)
+* [JSX / TSX loader](./packages/tsx/)
+* [SVGX loader](./packages/svgx/)
+* [Mismatched format loader](./packages/mismatched-format/)
+* [CSS Modules loader](./packages/css-module/)
+* [Media loader](./packages/media/)
+* [Text loader](./packages/text/)
+
+## Project-official loaders
+
+These loaders are officially maintained by their respective projects and are recommended (they're the most up-to-date and have the best support).
+
+* [MDX loader](https://mdxjs.com/packages/node-loader/)
+* [SWC register](https://github.com/swc-project/swc-node/tree/master/packages/register#swc-noderegister)
diff --git a/coverage.lcov b/coverage.lcov
new file mode 100644
index 0000000..7bc6e1c
--- /dev/null
+++ b/coverage.lcov
@@ -0,0 +1,762 @@
+TN:
+SF:packages/alias/alias.mjs
+FN:25,resolveAlias
+FN:30,resolveAliases
+FN:39,readConfigFile
+FN:44,anonymous_3
+FN:46,anonymous_4
+FN:49,buildAliasMaps
+FNDA:24,resolveAlias
+FNDA:48,resolveAliases
+FNDA:1,readConfigFile
+FNDA:1,anonymous_3
+FNDA:0,anonymous_4
+FNDA:3,buildAliasMaps
+FNF:6
+FNH:5
+BRDA:1,0,0,1
+BRDA:17,1,0,0
+BRDA:25,2,0,24
+BRDA:26,3,0,0
+BRDA:30,4,0,24
+BRDA:31,5,0,48
+BRDA:32,6,0,4
+BRDA:32,7,0,44
+BRDA:33,8,0,20
+BRDA:34,9,0,0
+BRDA:39,10,0,1
+BRDA:44,11,0,1
+BRDA:49,12,0,1
+BRDA:50,13,0,0
+BRDA:54,14,0,3
+BRDA:58,15,0,1
+BRDA:58,16,0,2
+BRDA:59,17,0,1
+BRDA:59,18,0,2
+BRDA:60,19,0,2
+BRDA:61,20,0,2
+BRDA:62,21,0,1
+BRF:22
+BRH:18
+DA:1,1
+DA:2,1
+DA:3,1
+DA:4,1
+DA:5,1
+DA:6,1
+DA:7,1
+DA:8,1
+DA:9,1
+DA:10,1
+DA:11,1
+DA:12,1
+DA:13,1
+DA:14,1
+DA:15,1
+DA:16,1
+DA:17,1
+DA:18,0
+DA:19,0
+DA:20,0
+DA:21,0
+DA:22,0
+DA:23,0
+DA:24,1
+DA:25,24
+DA:26,24
+DA:27,24
+DA:28,1
+DA:29,1
+DA:30,1
+DA:31,24
+DA:32,48
+DA:33,48
+DA:34,48
+DA:35,0
+DA:36,0
+DA:37,24
+DA:38,1
+DA:39,1
+DA:40,1
+DA:41,1
+DA:42,1
+DA:43,1
+DA:44,1
+DA:45,1
+DA:46,1
+DA:47,1
+DA:48,1
+DA:49,1
+DA:50,1
+DA:51,1
+DA:52,1
+DA:53,1
+DA:54,1
+DA:55,3
+DA:56,3
+DA:57,3
+DA:58,3
+DA:59,3
+DA:60,3
+DA:61,3
+DA:62,3
+DA:63,3
+DA:64,3
+DA:65,3
+DA:66,1
+DA:67,1
+DA:68,1
+LH:60
+LF:68
+end_of_record
+SF:packages/css-module/css-module.mjs
+FN:9,resolveCSSModule
+FN:22,loadCSSModule
+FN:37,parseCssToObject
+FN:47,parseCssToObjectRecursive
+FNDA:6,resolveCSSModule
+FNDA:2,loadCSSModule
+FNDA:6,parseCssToObject
+FNDA:16,parseCssToObjectRecursive
+FNF:4
+FNH:4
+BRDA:1,0,0,1
+BRDA:9,1,0,6
+BRDA:12,2,0,2
+BRDA:12,3,0,4
+BRDA:22,4,0,2
+BRDA:25,5,0,1
+BRDA:37,6,0,1
+BRDA:42,7,0,6
+BRDA:47,8,0,16
+BRDA:48,9,0,7
+BRDA:49,10,0,1
+BRDA:51,11,0,8
+BRDA:54,12,0,7
+BRDA:54,13,0,10
+BRF:14
+BRH:14
+DA:1,1
+DA:2,1
+DA:3,1
+DA:4,1
+DA:5,1
+DA:6,1
+DA:7,1
+DA:8,1
+DA:9,6
+DA:10,6
+DA:11,6
+DA:12,6
+DA:13,4
+DA:14,4
+DA:15,4
+DA:16,4
+DA:17,4
+DA:18,4
+DA:19,6
+DA:20,1
+DA:21,1
+DA:22,2
+DA:23,2
+DA:24,2
+DA:25,2
+DA:26,1
+DA:27,1
+DA:28,1
+DA:29,1
+DA:30,1
+DA:31,1
+DA:32,1
+DA:33,1
+DA:34,2
+DA:35,1
+DA:36,1
+DA:37,1
+DA:38,1
+DA:39,1
+DA:40,1
+DA:41,1
+DA:42,1
+DA:43,1
+DA:44,1
+DA:45,1
+DA:46,1
+DA:47,16
+DA:48,16
+DA:49,7
+DA:50,7
+DA:51,7
+DA:52,7
+DA:53,16
+DA:54,16
+DA:55,16
+DA:56,1
+DA:57,1
+DA:58,1
+DA:59,1
+DA:60,1
+LH:60
+LF:60
+end_of_record
+SF:packages/media/media.mjs
+FN:6,resolveMedia
+FN:21,loadMedia
+FNDA:49,resolveMedia
+FNDA:13,loadMedia
+FNF:2
+FNH:2
+BRDA:1,0,0,1
+BRDA:6,1,0,49
+BRDA:11,2,0,1
+BRDA:11,3,0,48
+BRDA:21,4,0,13
+BRDA:22,5,0,1
+BRDA:22,6,0,12
+BRF:7
+BRH:7
+DA:1,1
+DA:2,1
+DA:3,1
+DA:4,1
+DA:5,1
+DA:6,49
+DA:7,49
+DA:8,49
+DA:9,49
+DA:10,49
+DA:11,49
+DA:12,48
+DA:13,48
+DA:14,48
+DA:15,48
+DA:16,48
+DA:17,48
+DA:18,49
+DA:19,1
+DA:20,1
+DA:21,13
+DA:22,13
+DA:23,12
+DA:24,12
+DA:25,12
+DA:26,12
+DA:27,12
+DA:28,12
+DA:29,12
+DA:30,12
+DA:31,13
+DA:32,1
+DA:33,1
+DA:34,1
+DA:35,1
+DA:36,1
+DA:37,1
+DA:38,1
+DA:39,1
+DA:40,1
+DA:41,1
+DA:42,1
+DA:43,1
+DA:44,1
+DA:45,1
+DA:46,1
+DA:47,1
+DA:48,1
+DA:49,1
+DA:50,1
+DA:51,1
+DA:52,1
+DA:53,1
+DA:54,1
+DA:55,1
+DA:56,1
+LH:56
+LF:56
+end_of_record
+SF:packages/mismatched-format/contains-cjs.mjs
+FN:1,containsCJS
+FNDA:41,containsCJS
+FNF:1
+FNH:1
+BRDA:1,0,0,2
+BRDA:1,1,0,41
+BRDA:4,2,0,5
+BRDA:4,3,0,36
+BRDA:6,4,0,3
+BRDA:8,5,0,2
+BRDA:8,6,0,6
+BRDA:8,7,0,28
+BRDA:6,8,0,2
+BRDA:6,9,0,31
+BRF:10
+BRH:10
+DA:1,2
+DA:2,41
+DA:3,41
+DA:4,41
+DA:5,36
+DA:6,41
+DA:7,31
+DA:8,41
+DA:9,28
+DA:10,28
+DA:11,41
+DA:12,2
+DA:13,2
+DA:14,2
+DA:15,2
+DA:16,2
+DA:17,2
+DA:18,2
+DA:19,2
+DA:20,2
+DA:21,2
+DA:22,2
+DA:23,2
+DA:24,2
+DA:25,2
+DA:26,2
+DA:27,2
+DA:28,2
+LH:28
+LF:28
+end_of_record
+SF:packages/mismatched-format/contains-cjs.mjs
+FNF:0
+FNH:0
+BRDA:1,0,0,1
+BRDA:8,1,0,0
+BRF:2
+BRH:1
+DA:1,1
+DA:2,1
+DA:3,1
+DA:4,1
+DA:5,1
+DA:6,1
+DA:7,1
+DA:8,1
+DA:9,0
+DA:10,0
+DA:11,0
+DA:12,0
+DA:13,0
+DA:14,0
+DA:15,0
+DA:16,1
+DA:17,1
+DA:18,1
+DA:19,1
+DA:20,1
+DA:21,1
+DA:22,1
+DA:23,1
+DA:24,1
+DA:25,1
+DA:26,1
+DA:27,1
+DA:28,0
+LH:20
+LF:28
+end_of_record
+SF:packages/mismatched-format/mismatched-format.mjs
+FN:25,loadMismatchedFormat
+FN:31,anonymous_1
+FN:36,anonymous_2
+FNDA:7,loadMismatchedFormat
+FNDA:5,anonymous_1
+FNDA:5,anonymous_2
+FNF:3
+FNH:3
+BRDA:1,0,0,2
+BRDA:25,1,0,7
+BRDA:26,2,0,0
+BRDA:31,3,0,5
+BRDA:32,4,0,2
+BRDA:36,5,0,5
+BRDA:38,6,0,1
+BRDA:39,7,0,2
+BRDA:42,8,0,0
+BRF:9
+BRH:7
+DA:1,2
+DA:2,2
+DA:3,2
+DA:4,2
+DA:5,2
+DA:6,2
+DA:7,2
+DA:8,2
+DA:9,2
+DA:10,2
+DA:11,2
+DA:12,2
+DA:13,2
+DA:14,2
+DA:15,2
+DA:16,2
+DA:17,2
+DA:18,2
+DA:19,2
+DA:20,2
+DA:21,2
+DA:22,2
+DA:23,2
+DA:24,2
+DA:25,7
+DA:26,7
+DA:27,7
+DA:28,7
+DA:29,7
+DA:30,7
+DA:31,7
+DA:32,5
+DA:33,2
+DA:34,2
+DA:35,7
+DA:36,7
+DA:37,5
+DA:38,5
+DA:39,5
+DA:40,5
+DA:41,5
+DA:42,5
+DA:43,0
+DA:44,0
+DA:45,7
+DA:46,7
+DA:47,2
+DA:48,2
+DA:49,2
+DA:50,2
+DA:51,2
+LH:49
+LF:51
+end_of_record
+SF:packages/parse-filename/parse-filename.mjs
+FN:10,getFilenameExt
+FN:14,stripExtras
+FN:21,getFilenameParts
+FNDA:93,getFilenameExt
+FNDA:99,stripExtras
+FNDA:3,getFilenameParts
+FNF:3
+FNH:3
+BRDA:1,0,0,7
+BRDA:10,1,0,93
+BRDA:14,2,0,99
+BRDA:21,3,0,3
+BRDA:24,4,0,0
+BRF:5
+BRH:4
+DA:1,7
+DA:2,7
+DA:3,7
+DA:4,7
+DA:5,7
+DA:6,7
+DA:7,7
+DA:8,7
+DA:9,7
+DA:10,7
+DA:11,93
+DA:12,93
+DA:13,7
+DA:14,7
+DA:15,99
+DA:16,99
+DA:17,7
+DA:18,7
+DA:19,7
+DA:20,7
+DA:21,7
+DA:22,3
+DA:23,3
+DA:24,3
+DA:25,3
+DA:26,3
+DA:27,3
+DA:28,3
+DA:29,3
+DA:30,3
+DA:31,3
+DA:32,3
+DA:33,3
+LH:33
+LF:33
+end_of_record
+SF:packages/svgx/svgx.mjs
+FN:12,loadSVGX
+FN:45,pascalCase
+FNDA:3,loadSVGX
+FNDA:1,pascalCase
+FNF:2
+FNH:2
+BRDA:1,0,0,1
+BRDA:12,1,0,3
+BRDA:15,2,0,1
+BRDA:15,3,0,2
+BRDA:17,4,0,1
+BRDA:45,5,0,1
+BRF:6
+BRH:6
+DA:1,1
+DA:2,1
+DA:3,1
+DA:4,1
+DA:5,1
+DA:6,1
+DA:7,1
+DA:8,1
+DA:9,1
+DA:10,1
+DA:11,1
+DA:12,3
+DA:13,3
+DA:14,3
+DA:15,3
+DA:16,2
+DA:17,3
+DA:18,1
+DA:19,1
+DA:20,1
+DA:21,1
+DA:22,1
+DA:23,1
+DA:24,1
+DA:25,1
+DA:26,1
+DA:27,1
+DA:28,1
+DA:29,1
+DA:30,1
+DA:31,1
+DA:32,1
+DA:33,3
+DA:34,1
+DA:35,1
+DA:36,1
+DA:37,1
+DA:38,1
+DA:39,1
+DA:40,1
+DA:41,1
+DA:42,1
+DA:43,1
+DA:44,1
+DA:45,1
+DA:46,1
+DA:47,1
+LH:47
+LF:47
+end_of_record
+SF:packages/text/text.mjs
+FN:4,resolveText
+FN:19,loadText
+FNDA:17,resolveText
+FNDA:5,loadText
+FNF:2
+FNH:2
+BRDA:1,0,0,1
+BRDA:4,1,0,17
+BRDA:9,2,0,1
+BRDA:9,3,0,16
+BRDA:19,4,0,5
+BRDA:22,5,0,1
+BRDA:22,6,0,4
+BRF:7
+BRH:7
+DA:1,1
+DA:2,1
+DA:3,1
+DA:4,17
+DA:5,17
+DA:6,17
+DA:7,17
+DA:8,17
+DA:9,17
+DA:10,16
+DA:11,16
+DA:12,16
+DA:13,16
+DA:14,16
+DA:15,16
+DA:16,17
+DA:17,1
+DA:18,1
+DA:19,5
+DA:20,5
+DA:21,5
+DA:22,5
+DA:23,4
+DA:24,4
+DA:25,4
+DA:26,4
+DA:27,4
+DA:28,4
+DA:29,4
+DA:30,5
+DA:31,1
+DA:32,1
+DA:33,1
+DA:34,1
+DA:35,1
+DA:36,1
+DA:37,1
+DA:38,1
+DA:39,1
+DA:40,1
+LH:40
+LF:40
+end_of_record
+SF:packages/tsx/find-esbuild-config.mjs
+FN:12,findEsbuildConfig
+FNDA:4,findEsbuildConfig
+FNF:1
+FNH:1
+BRDA:1,0,0,2
+BRDA:12,1,0,4
+BRDA:13,2,0,2
+BRDA:13,3,0,1
+BRDA:20,4,0,1
+BRDA:22,5,0,0
+BRDA:25,6,0,1
+BRF:7
+BRH:6
+DA:1,2
+DA:2,2
+DA:3,2
+DA:4,2
+DA:5,2
+DA:6,2
+DA:7,2
+DA:8,2
+DA:9,2
+DA:10,2
+DA:11,2
+DA:12,2
+DA:13,4
+DA:14,1
+DA:15,1
+DA:16,4
+DA:17,4
+DA:18,4
+DA:19,4
+DA:20,4
+DA:21,4
+DA:22,4
+DA:23,4
+DA:24,4
+DA:25,4
+DA:26,1
+DA:27,1
+DA:28,1
+DA:29,1
+DA:30,1
+DA:31,1
+DA:32,1
+DA:33,1
+DA:34,4
+LH:34
+LF:34
+end_of_record
+SF:packages/tsx/tsx.mjs
+FN:8,resolveTSX
+FN:28,loadTSX
+FN:40,anonymous_2
+FNDA:17,resolveTSX
+FNDA:4,loadTSX
+FNDA:1,anonymous_2
+FNF:3
+FNH:3
+BRDA:1,0,0,1
+BRDA:8,1,0,17
+BRDA:14,2,0,4
+BRDA:17,3,0,13
+BRDA:19,4,0,12
+BRDA:22,5,0,1
+BRDA:28,6,0,4
+BRDA:29,7,0,1
+BRDA:29,8,0,3
+BRDA:37,9,0,0
+BRDA:37,10,0,3
+BRDA:51,11,0,2
+BRDA:51,12,0,0
+BRDA:51,13,0,3
+BRDA:40,14,0,1
+BRF:15
+BRH:13
+DA:1,1
+DA:2,1
+DA:3,1
+DA:4,1
+DA:5,1
+DA:6,1
+DA:7,1
+DA:8,17
+DA:9,17
+DA:10,17
+DA:11,17
+DA:12,17
+DA:13,17
+DA:14,17
+DA:15,4
+DA:16,4
+DA:17,4
+DA:18,13
+DA:19,17
+DA:20,12
+DA:21,12
+DA:22,12
+DA:23,1
+DA:24,1
+DA:25,17
+DA:26,1
+DA:27,1
+DA:28,4
+DA:29,4
+DA:30,3
+DA:31,3
+DA:32,3
+DA:33,3
+DA:34,3
+DA:35,3
+DA:36,3
+DA:37,4
+DA:38,3
+DA:39,3
+DA:40,3
+DA:41,1
+DA:42,1
+DA:43,1
+DA:44,1
+DA:45,1
+DA:46,1
+DA:47,1
+DA:48,1
+DA:49,3
+DA:50,3
+DA:51,4
+DA:52,3
+DA:53,3
+DA:54,3
+DA:55,3
+DA:56,3
+DA:57,4
+DA:58,1
+DA:59,1
+DA:60,1
+DA:61,1
+DA:62,1
+DA:63,1
+DA:64,1
+DA:65,1
+DA:66,1
+DA:67,1
+DA:68,1
+DA:69,1
+DA:70,1
+LH:70
+LF:70
+end_of_record
diff --git a/assert-suffixed-specifiers.fixture.mjs b/fixtures/assert-suffixed-specifiers.fixture.mjs
similarity index 100%
rename from assert-suffixed-specifiers.fixture.mjs
rename to fixtures/assert-suffixed-specifiers.fixture.mjs
diff --git a/esbuild.config.mjs b/fixtures/esbuild.config.mjs
similarity index 100%
rename from esbuild.config.mjs
rename to fixtures/esbuild.config.mjs
diff --git a/fixture.ext b/fixtures/fixture.ext
similarity index 100%
rename from fixture.ext
rename to fixtures/fixture.ext
diff --git a/fixtures/nextLoad.fixture.mjs b/fixtures/nextLoad.fixture.mjs
new file mode 100644
index 0000000..5cb8556
--- /dev/null
+++ b/fixtures/nextLoad.fixture.mjs
@@ -0,0 +1,14 @@
+import fs from 'node:fs/promises';
+import { fileURLToPath } from 'node:url';
+
+
+export const nextLoad = async (url, { format = 'unknown' } = { format: 'unknown' }) => {
+ const fsPath = URL.canParse(url)
+ ? fileURLToPath(url)
+ : url;
+
+ return {
+ format,
+ source: await fs.readFile(fsPath, 'utf-8'),
+ };
+};
diff --git a/nextResolve.fixture.mjs b/fixtures/nextResolve.fixture.mjs
similarity index 100%
rename from nextResolve.fixture.mjs
rename to fixtures/nextResolve.fixture.mjs
diff --git a/lcov.coverage b/lcov.coverage
deleted file mode 100644
index bd0caa5..0000000
--- a/lcov.coverage
+++ /dev/null
@@ -1,1469 +0,0 @@
-TN:
-SF:alias.mjs
-FN:25,resolve
-FN:29,resolveAliases
-FN:38,readConfigFile
-FN:43,anonymous_3
-FN:45,anonymous_4
-FN:48,buildAliasMaps
-FNDA:24,resolve
-FNDA:48,resolveAliases
-FNDA:1,readConfigFile
-FNDA:1,anonymous_3
-FNDA:0,anonymous_4
-FNDA:3,buildAliasMaps
-FNF:6
-FNH:5
-BRDA:1,0,0,1
-BRDA:17,1,0,0
-BRDA:25,2,0,24
-BRDA:26,3,0,0
-BRDA:29,4,0,24
-BRDA:30,5,0,48
-BRDA:31,6,0,4
-BRDA:31,7,0,44
-BRDA:32,8,0,20
-BRDA:33,9,0,0
-BRDA:38,10,0,1
-BRDA:43,11,0,1
-BRDA:48,12,0,1
-BRDA:49,13,0,0
-BRDA:53,14,0,3
-BRDA:57,15,0,1
-BRDA:57,16,0,2
-BRDA:58,17,0,1
-BRDA:58,18,0,2
-BRDA:59,19,0,2
-BRDA:60,20,0,2
-BRDA:61,21,0,1
-BRF:22
-BRH:18
-DA:1,1
-DA:2,1
-DA:3,1
-DA:4,1
-DA:5,1
-DA:6,1
-DA:7,1
-DA:8,1
-DA:9,1
-DA:10,1
-DA:11,1
-DA:12,1
-DA:13,1
-DA:14,1
-DA:15,1
-DA:16,1
-DA:17,1
-DA:18,0
-DA:19,0
-DA:20,0
-DA:21,0
-DA:22,0
-DA:23,0
-DA:24,1
-DA:25,1
-DA:26,24
-DA:27,24
-DA:28,1
-DA:29,1
-DA:30,24
-DA:31,48
-DA:32,48
-DA:33,48
-DA:34,0
-DA:35,0
-DA:36,24
-DA:37,1
-DA:38,1
-DA:39,1
-DA:40,1
-DA:41,1
-DA:42,1
-DA:43,1
-DA:44,1
-DA:45,1
-DA:46,1
-DA:47,1
-DA:48,1
-DA:49,1
-DA:50,1
-DA:51,1
-DA:52,1
-DA:53,1
-DA:54,3
-DA:55,3
-DA:56,3
-DA:57,3
-DA:58,3
-DA:59,3
-DA:60,3
-DA:61,3
-DA:62,3
-DA:63,3
-DA:64,3
-DA:65,1
-DA:66,1
-DA:67,1
-DA:68,1
-LH:60
-LF:68
-end_of_record
-SF:alias.spec.mjs
-FN:13,anonymous_0
-FN:22,
-FN:26,anonymous_2
-FN:29,pathToFileURL
-FN:32,anonymous_4
-FN:35,anonymous_5
-FN:44,anonymous_6
-FN:47,anonymous_7
-FN:50,anonymous_8
-FN:59,anonymous_9
-FN:62,runCases
-FN:63,anonymous_11
-FN:70,anonymous_12
-FN:77,anonymous_13
-FN:84,anonymous_14
-FN:99,anonymous_15
-FN:114,anonymous_16
-FNDA:1,anonymous_0
-FNDA:2,
-FNDA:1,anonymous_2
-FNDA:1,pathToFileURL
-FNDA:1,anonymous_4
-FNDA:1,anonymous_5
-FNDA:1,anonymous_6
-FNDA:1,anonymous_7
-FNDA:1,anonymous_8
-FNDA:1,anonymous_9
-FNDA:2,runCases
-FNDA:2,anonymous_11
-FNDA:2,anonymous_12
-FNDA:2,anonymous_13
-FNDA:2,anonymous_14
-FNDA:2,anonymous_15
-FNDA:2,anonymous_16
-FNF:17
-FNH:17
-BRDA:1,0,0,1
-BRDA:13,1,0,1
-BRDA:22,2,0,2
-BRDA:26,3,0,1
-BRDA:29,4,0,1
-BRDA:32,5,0,1
-BRDA:35,6,0,1
-BRDA:44,7,0,1
-BRDA:47,8,0,1
-BRDA:50,9,0,1
-BRDA:59,10,0,1
-BRDA:62,11,0,2
-BRDA:63,12,0,2
-BRDA:70,13,0,2
-BRDA:77,14,0,2
-BRDA:84,15,0,2
-BRDA:99,16,0,2
-BRDA:114,17,0,2
-BRF:18
-BRH:18
-DA:1,1
-DA:2,1
-DA:3,1
-DA:4,1
-DA:5,1
-DA:6,1
-DA:7,1
-DA:8,1
-DA:9,1
-DA:10,1
-DA:11,1
-DA:12,1
-DA:13,1
-DA:14,1
-DA:15,1
-DA:16,1
-DA:17,1
-DA:18,1
-DA:19,1
-DA:20,1
-DA:21,1
-DA:22,1
-DA:23,2
-DA:24,2
-DA:25,1
-DA:26,1
-DA:27,1
-DA:28,1
-DA:29,1
-DA:30,1
-DA:31,1
-DA:32,1
-DA:33,1
-DA:34,1
-DA:35,1
-DA:36,1
-DA:37,1
-DA:38,1
-DA:39,1
-DA:40,1
-DA:41,1
-DA:42,1
-DA:43,1
-DA:44,1
-DA:45,1
-DA:46,1
-DA:47,1
-DA:48,1
-DA:49,1
-DA:50,1
-DA:51,1
-DA:52,1
-DA:53,1
-DA:54,1
-DA:55,1
-DA:56,1
-DA:57,1
-DA:58,1
-DA:59,1
-DA:60,1
-DA:61,1
-DA:62,1
-DA:63,2
-DA:64,2
-DA:65,2
-DA:66,2
-DA:67,2
-DA:68,2
-DA:69,2
-DA:70,2
-DA:71,2
-DA:72,2
-DA:73,2
-DA:74,2
-DA:75,2
-DA:76,2
-DA:77,2
-DA:78,2
-DA:79,2
-DA:80,2
-DA:81,2
-DA:82,2
-DA:83,2
-DA:84,2
-DA:85,2
-DA:86,2
-DA:87,2
-DA:88,2
-DA:89,2
-DA:90,2
-DA:91,2
-DA:92,2
-DA:93,2
-DA:94,2
-DA:95,2
-DA:96,2
-DA:97,2
-DA:98,2
-DA:99,2
-DA:100,2
-DA:101,2
-DA:102,2
-DA:103,2
-DA:104,2
-DA:105,2
-DA:106,2
-DA:107,2
-DA:108,2
-DA:109,2
-DA:110,2
-DA:111,2
-DA:112,2
-DA:113,2
-DA:114,2
-DA:115,2
-DA:116,2
-DA:117,2
-DA:118,2
-DA:119,2
-DA:120,2
-DA:121,2
-DA:122,2
-DA:123,2
-DA:124,2
-DA:125,2
-DA:126,2
-DA:127,2
-DA:128,2
-DA:129,1
-LH:129
-LF:129
-end_of_record
-SF:assert-suffixed-specifiers.fixture.mjs
-FN:6,assertSuffixedSpecifiers
-FNDA:63,assertSuffixedSpecifiers
-FNF:1
-FNH:1
-BRDA:1,0,0,4
-BRDA:6,1,0,21
-BRDA:7,2,0,63
-BRF:3
-BRH:3
-DA:1,4
-DA:2,4
-DA:3,4
-DA:4,4
-DA:5,4
-DA:6,4
-DA:7,21
-DA:8,63
-DA:9,63
-DA:10,63
-DA:11,63
-DA:12,63
-DA:13,63
-DA:14,63
-DA:15,21
-DA:16,4
-DA:17,4
-DA:18,4
-DA:19,4
-DA:20,4
-DA:21,4
-LH:21
-LF:21
-end_of_record
-SF:containsCJS.mjs
-FN:1,containsCJS
-FNDA:37,containsCJS
-FNF:1
-FNH:1
-BRDA:1,0,0,1
-BRDA:1,1,0,37
-BRDA:4,2,0,4
-BRDA:4,3,0,33
-BRDA:6,4,0,2
-BRDA:6,5,0,31
-BRDA:8,6,0,5
-BRDA:8,7,0,26
-BRF:8
-BRH:8
-DA:1,1
-DA:2,37
-DA:3,37
-DA:4,37
-DA:5,33
-DA:6,37
-DA:7,31
-DA:8,37
-DA:9,26
-DA:10,26
-DA:11,37
-DA:12,1
-DA:13,1
-DA:14,1
-DA:15,1
-DA:16,1
-DA:17,1
-DA:18,1
-DA:19,1
-DA:20,1
-DA:21,1
-DA:22,1
-DA:23,1
-DA:24,1
-DA:25,1
-DA:26,1
-DA:27,1
-DA:28,1
-LH:28
-LF:28
-end_of_record
-SF:containsCJS.spec.mjs
-FN:6,anonymous_0
-FN:7,anonymous_1
-FN:17,anonymous_2
-FN:23,anonymous_3
-FN:33,anonymous_4
-FN:45,anonymous_5
-FN:51,anonymous_6
-FN:60,anonymous_7
-FNDA:1,anonymous_0
-FNDA:1,anonymous_1
-FNDA:6,anonymous_2
-FNDA:6,anonymous_3
-FNDA:1,anonymous_4
-FNDA:5,anonymous_5
-FNDA:2,anonymous_6
-FNDA:5,anonymous_7
-FNF:8
-FNH:8
-BRDA:1,0,0,1
-BRDA:6,1,0,1
-BRDA:7,2,0,1
-BRDA:17,3,0,1
-BRDA:18,4,0,6
-BRDA:23,5,0,1
-BRDA:24,6,0,6
-BRDA:33,7,0,1
-BRDA:45,8,0,1
-BRDA:46,9,0,5
-BRDA:51,10,0,1
-BRDA:52,11,0,2
-BRDA:60,12,0,1
-BRDA:61,13,0,5
-BRF:14
-BRH:14
-DA:1,1
-DA:2,1
-DA:3,1
-DA:4,1
-DA:5,1
-DA:6,1
-DA:7,1
-DA:8,1
-DA:9,1
-DA:10,1
-DA:11,1
-DA:12,1
-DA:13,1
-DA:14,1
-DA:15,1
-DA:16,1
-DA:17,1
-DA:18,1
-DA:19,6
-DA:20,6
-DA:21,1
-DA:22,1
-DA:23,1
-DA:24,1
-DA:25,6
-DA:26,6
-DA:27,6
-DA:28,6
-DA:29,6
-DA:30,1
-DA:31,1
-DA:32,1
-DA:33,1
-DA:34,1
-DA:35,1
-DA:36,1
-DA:37,1
-DA:38,1
-DA:39,1
-DA:40,1
-DA:41,1
-DA:42,1
-DA:43,1
-DA:44,1
-DA:45,1
-DA:46,1
-DA:47,5
-DA:48,5
-DA:49,1
-DA:50,1
-DA:51,1
-DA:52,1
-DA:53,2
-DA:54,2
-DA:55,2
-DA:56,1
-DA:57,1
-DA:58,1
-DA:59,1
-DA:60,1
-DA:61,1
-DA:62,5
-DA:63,5
-DA:64,5
-DA:65,5
-DA:66,5
-DA:67,1
-DA:68,1
-DA:69,1
-LH:69
-LF:69
-end_of_record
-SF:css-module.mjs
-FN:9,resolve
-FN:21,load
-FN:35,parseCssToObject
-FN:45,parseCssToObjectRecursive
-FNDA:6,resolve
-FNDA:2,load
-FNDA:6,parseCssToObject
-FNDA:16,parseCssToObjectRecursive
-FNF:4
-FNH:4
-BRDA:1,0,0,1
-BRDA:9,1,0,6
-BRDA:12,2,0,2
-BRDA:12,3,0,4
-BRDA:21,4,0,2
-BRDA:24,5,0,1
-BRDA:35,6,0,1
-BRDA:40,7,0,6
-BRDA:45,8,0,16
-BRDA:46,9,0,7
-BRDA:47,10,0,1
-BRDA:49,11,0,8
-BRDA:52,12,0,7
-BRDA:52,13,0,10
-BRF:14
-BRH:14
-DA:1,1
-DA:2,1
-DA:3,1
-DA:4,1
-DA:5,1
-DA:6,1
-DA:7,1
-DA:8,1
-DA:9,1
-DA:10,6
-DA:11,6
-DA:12,6
-DA:13,4
-DA:14,4
-DA:15,4
-DA:16,4
-DA:17,4
-DA:18,4
-DA:19,6
-DA:20,1
-DA:21,1
-DA:22,2
-DA:23,2
-DA:24,2
-DA:25,1
-DA:26,1
-DA:27,1
-DA:28,1
-DA:29,1
-DA:30,1
-DA:31,1
-DA:32,1
-DA:33,2
-DA:34,1
-DA:35,1
-DA:36,1
-DA:37,1
-DA:38,1
-DA:39,1
-DA:40,1
-DA:41,1
-DA:42,1
-DA:43,1
-DA:44,1
-DA:45,16
-DA:46,16
-DA:47,7
-DA:48,7
-DA:49,7
-DA:50,7
-DA:51,16
-DA:52,16
-DA:53,16
-DA:54,1
-DA:55,1
-DA:56,1
-DA:57,1
-DA:58,1
-LH:58
-LF:58
-end_of_record
-SF:css-module.spec.mjs
-FN:11,anonymous_0
-FN:12,anonymous_1
-FN:13,anonymous_2
-FN:22,anonymous_3
-FN:31,anonymous_4
-FN:40,anonymous_5
-FN:45,anonymous_6
-FN:46,anonymous_7
-FN:53,anonymous_8
-FNDA:1,anonymous_0
-FNDA:1,anonymous_1
-FNDA:1,anonymous_2
-FNDA:1,anonymous_3
-FNDA:1,anonymous_4
-FNDA:1,anonymous_5
-FNDA:1,anonymous_6
-FNDA:1,anonymous_7
-FNDA:1,anonymous_8
-FNF:9
-FNH:9
-BRDA:1,0,0,1
-BRDA:11,1,0,1
-BRDA:12,2,0,1
-BRDA:13,3,0,1
-BRDA:22,4,0,1
-BRDA:31,5,0,1
-BRDA:40,6,0,1
-BRDA:45,7,0,1
-BRDA:46,8,0,1
-BRDA:53,9,0,1
-BRF:10
-BRH:10
-DA:1,1
-DA:2,1
-DA:3,1
-DA:4,1
-DA:5,1
-DA:6,1
-DA:7,1
-DA:8,1
-DA:9,1
-DA:10,1
-DA:11,1
-DA:12,1
-DA:13,1
-DA:14,1
-DA:15,1
-DA:16,1
-DA:17,1
-DA:18,1
-DA:19,1
-DA:20,1
-DA:21,1
-DA:22,1
-DA:23,1
-DA:24,1
-DA:25,1
-DA:26,1
-DA:27,1
-DA:28,1
-DA:29,1
-DA:30,1
-DA:31,1
-DA:32,1
-DA:33,1
-DA:34,1
-DA:35,1
-DA:36,1
-DA:37,1
-DA:38,1
-DA:39,1
-DA:40,1
-DA:41,1
-DA:42,1
-DA:43,1
-DA:44,1
-DA:45,1
-DA:46,1
-DA:47,1
-DA:48,1
-DA:49,1
-DA:50,1
-DA:51,1
-DA:52,1
-DA:53,1
-DA:54,1
-DA:55,1
-DA:56,1
-DA:57,1
-DA:58,1
-DA:59,1
-DA:60,1
-DA:61,1
-DA:62,1
-DA:63,1
-DA:64,1
-DA:65,1
-DA:66,1
-DA:67,1
-DA:68,1
-LH:68
-LF:68
-end_of_record
-SF:esbuild.config.mjs
-FNF:0
-FNH:0
-BRDA:1,0,0,1
-BRF:1
-BRH:1
-DA:1,1
-DA:2,1
-DA:3,1
-LH:3
-LF:3
-end_of_record
-SF:media.mjs
-FN:6,resolve
-FN:20,load
-FNDA:49,resolve
-FNDA:13,load
-FNF:2
-FNH:2
-BRDA:1,0,0,1
-BRDA:6,1,0,49
-BRDA:11,2,0,1
-BRDA:11,3,0,48
-BRDA:20,4,0,13
-BRDA:21,5,0,1
-BRDA:21,6,0,12
-BRF:7
-BRH:7
-DA:1,1
-DA:2,1
-DA:3,1
-DA:4,1
-DA:5,1
-DA:6,1
-DA:7,49
-DA:8,49
-DA:9,49
-DA:10,49
-DA:11,49
-DA:12,48
-DA:13,48
-DA:14,48
-DA:15,48
-DA:16,48
-DA:17,48
-DA:18,49
-DA:19,1
-DA:20,1
-DA:21,13
-DA:22,12
-DA:23,12
-DA:24,12
-DA:25,12
-DA:26,12
-DA:27,12
-DA:28,12
-DA:29,12
-DA:30,13
-DA:31,1
-DA:32,1
-DA:33,1
-DA:34,1
-DA:35,1
-DA:36,1
-DA:37,1
-DA:38,1
-DA:39,1
-DA:40,1
-DA:41,1
-DA:42,1
-DA:43,1
-DA:44,1
-DA:45,1
-DA:46,1
-DA:47,1
-DA:48,1
-DA:49,1
-DA:50,1
-DA:51,1
-DA:52,1
-DA:53,1
-DA:54,1
-LH:54
-LF:54
-end_of_record
-SF:media.spec.mjs
-FN:11,anonymous_0
-FN:12,anonymous_1
-FN:13,anonymous_2
-FN:22,anonymous_3
-FN:34,anonymous_4
-FN:39,anonymous_5
-FN:40,anonymous_6
-FN:49,anonymous_7
-FN:52,anonymous_8
-FNDA:1,anonymous_0
-FNDA:1,anonymous_1
-FNDA:1,anonymous_2
-FNDA:12,anonymous_3
-FNDA:12,anonymous_4
-FNDA:1,anonymous_5
-FNDA:1,anonymous_6
-FNDA:12,anonymous_7
-FNDA:0,anonymous_8
-FNF:9
-FNH:8
-BRDA:1,0,0,1
-BRDA:11,1,0,1
-BRDA:12,2,0,1
-BRDA:13,3,0,1
-BRDA:22,4,0,1
-BRDA:23,5,0,12
-BRDA:34,6,0,1
-BRDA:35,7,0,12
-BRDA:39,8,0,1
-BRDA:40,9,0,1
-BRDA:49,10,0,1
-BRDA:50,11,0,12
-BRF:12
-BRH:12
-DA:1,1
-DA:2,1
-DA:3,1
-DA:4,1
-DA:5,1
-DA:6,1
-DA:7,1
-DA:8,1
-DA:9,1
-DA:10,1
-DA:11,1
-DA:12,1
-DA:13,1
-DA:14,1
-DA:15,1
-DA:16,1
-DA:17,1
-DA:18,1
-DA:19,1
-DA:20,1
-DA:21,1
-DA:22,1
-DA:23,1
-DA:24,12
-DA:25,12
-DA:26,12
-DA:27,12
-DA:28,12
-DA:29,12
-DA:30,12
-DA:31,12
-DA:32,1
-DA:33,1
-DA:34,1
-DA:35,1
-DA:36,1
-DA:37,1
-DA:38,1
-DA:39,1
-DA:40,1
-DA:41,1
-DA:42,1
-DA:43,1
-DA:44,1
-DA:45,1
-DA:46,1
-DA:47,1
-DA:48,1
-DA:49,1
-DA:50,1
-DA:51,12
-DA:52,12
-DA:53,12
-DA:54,12
-DA:55,12
-DA:56,12
-DA:57,12
-DA:58,12
-DA:59,12
-DA:60,1
-DA:61,1
-DA:62,1
-LH:62
-LF:62
-end_of_record
-SF:nextLoad.fixture.mjs
-FN:3,nextLoad
-FNDA:18,nextLoad
-FNF:1
-FNH:1
-BRDA:1,0,0,5
-BRDA:3,1,0,18
-BRF:2
-BRH:2
-DA:1,5
-DA:2,5
-DA:3,5
-DA:4,18
-DA:5,18
-DA:6,18
-DA:7,18
-DA:8,5
-LH:8
-LF:8
-end_of_record
-SF:nextResolve.fixture.mjs
-FN:1,nextResolve
-FNDA:113,nextResolve
-FNF:1
-FNH:1
-BRDA:1,0,0,5
-BRDA:1,1,0,113
-BRF:2
-BRH:2
-DA:1,5
-DA:2,113
-DA:3,113
-DA:4,5
-LH:4
-LF:4
-end_of_record
-SF:parse-filename.mjs
-FN:10,getFilenameExt
-FN:14,stripExtras
-FN:18,getFilenameParts
-FNDA:85,getFilenameExt
-FNDA:91,stripExtras
-FNDA:2,getFilenameParts
-FNF:3
-FNH:3
-BRDA:1,0,0,5
-BRDA:10,1,0,85
-BRDA:14,2,0,91
-BRDA:18,3,0,2
-BRF:4
-BRH:4
-DA:1,5
-DA:2,5
-DA:3,5
-DA:4,5
-DA:5,5
-DA:6,5
-DA:7,5
-DA:8,5
-DA:9,5
-DA:10,5
-DA:11,85
-DA:12,85
-DA:13,5
-DA:14,5
-DA:15,91
-DA:16,91
-DA:17,5
-DA:18,5
-DA:19,2
-DA:20,2
-DA:21,2
-DA:22,2
-DA:23,2
-DA:24,2
-DA:25,2
-DA:26,2
-DA:27,2
-DA:28,2
-LH:28
-LF:28
-end_of_record
-SF:svgx.mjs
-FN:12,load
-FN:44,pascalCase
-FNDA:2,load
-FNDA:2,pascalCase
-FNF:2
-FNH:2
-BRDA:1,0,0,1
-BRDA:12,1,0,2
-BRDA:16,2,0,1
-BRDA:18,3,0,0
-BRDA:24,4,0,1
-BRDA:44,5,0,2
-BRF:6
-BRH:5
-DA:1,1
-DA:2,1
-DA:3,1
-DA:4,1
-DA:5,1
-DA:6,1
-DA:7,1
-DA:8,1
-DA:9,1
-DA:10,1
-DA:11,1
-DA:12,1
-DA:13,2
-DA:14,2
-DA:15,2
-DA:16,2
-DA:17,1
-DA:18,2
-DA:19,0
-DA:20,0
-DA:21,0
-DA:22,0
-DA:23,0
-DA:24,0
-DA:25,1
-DA:26,1
-DA:27,1
-DA:28,1
-DA:29,1
-DA:30,1
-DA:31,1
-DA:32,1
-DA:33,2
-DA:34,1
-DA:35,1
-DA:36,1
-DA:37,1
-DA:38,1
-DA:39,1
-DA:40,1
-DA:41,1
-DA:42,1
-DA:43,1
-DA:44,2
-DA:45,2
-DA:46,2
-LH:40
-LF:46
-end_of_record
-SF:svgx.spec.mjs
-FN:10,anonymous_0
-FN:11,anonymous_1
-FN:12,anonymous_2
-FN:21,anonymous_3
-FNDA:1,anonymous_0
-FNDA:1,anonymous_1
-FNDA:1,anonymous_2
-FNDA:1,anonymous_3
-FNF:4
-FNH:4
-BRDA:1,0,0,1
-BRDA:10,1,0,1
-BRDA:11,2,0,1
-BRDA:12,3,0,1
-BRDA:21,4,0,1
-BRF:5
-BRH:5
-DA:1,1
-DA:2,1
-DA:3,1
-DA:4,1
-DA:5,1
-DA:6,1
-DA:7,1
-DA:8,1
-DA:9,1
-DA:10,1
-DA:11,1
-DA:12,1
-DA:13,1
-DA:14,1
-DA:15,1
-DA:16,1
-DA:17,1
-DA:18,1
-DA:19,1
-DA:20,1
-DA:21,1
-DA:22,1
-DA:23,1
-DA:24,1
-DA:25,1
-DA:26,1
-DA:27,1
-DA:28,1
-DA:29,1
-DA:30,1
-DA:31,1
-LH:31
-LF:31
-end_of_record
-SF:text.mjs
-FN:4,resolve
-FN:18,load
-FNDA:17,resolve
-FNDA:5,load
-FNF:2
-FNH:2
-BRDA:1,0,0,1
-BRDA:4,1,0,17
-BRDA:9,2,0,1
-BRDA:9,3,0,16
-BRDA:18,4,0,5
-BRDA:21,5,0,1
-BRDA:21,6,0,4
-BRF:7
-BRH:7
-DA:1,1
-DA:2,1
-DA:3,1
-DA:4,1
-DA:5,17
-DA:6,17
-DA:7,17
-DA:8,17
-DA:9,17
-DA:10,16
-DA:11,16
-DA:12,16
-DA:13,16
-DA:14,16
-DA:15,16
-DA:16,17
-DA:17,1
-DA:18,1
-DA:19,5
-DA:20,5
-DA:21,5
-DA:22,4
-DA:23,4
-DA:24,4
-DA:25,4
-DA:26,4
-DA:27,4
-DA:28,4
-DA:29,5
-DA:30,1
-DA:31,1
-DA:32,1
-DA:33,1
-DA:34,1
-DA:35,1
-DA:36,1
-DA:37,1
-DA:38,1
-LH:38
-LF:38
-end_of_record
-SF:text.spec.mjs
-FN:11,anonymous_0
-FN:12,anonymous_1
-FN:13,anonymous_2
-FN:22,anonymous_3
-FN:34,anonymous_4
-FN:41,anonymous_5
-FN:42,anonymous_6
-FN:51,anonymous_7
-FNDA:1,anonymous_0
-FNDA:1,anonymous_1
-FNDA:1,anonymous_2
-FNDA:4,anonymous_3
-FNDA:4,anonymous_4
-FNDA:1,anonymous_5
-FNDA:1,anonymous_6
-FNDA:4,anonymous_7
-FNF:8
-FNH:8
-BRDA:1,0,0,1
-BRDA:11,1,0,1
-BRDA:12,2,0,1
-BRDA:13,3,0,1
-BRDA:22,4,0,1
-BRDA:23,5,0,4
-BRDA:34,6,0,1
-BRDA:35,7,0,4
-BRDA:41,8,0,1
-BRDA:42,9,0,1
-BRDA:51,10,0,1
-BRDA:52,11,0,4
-BRF:12
-BRH:12
-DA:1,1
-DA:2,1
-DA:3,1
-DA:4,1
-DA:5,1
-DA:6,1
-DA:7,1
-DA:8,1
-DA:9,1
-DA:10,1
-DA:11,1
-DA:12,1
-DA:13,1
-DA:14,1
-DA:15,1
-DA:16,1
-DA:17,1
-DA:18,1
-DA:19,1
-DA:20,1
-DA:21,1
-DA:22,1
-DA:23,1
-DA:24,4
-DA:25,4
-DA:26,4
-DA:27,4
-DA:28,4
-DA:29,4
-DA:30,4
-DA:31,4
-DA:32,1
-DA:33,1
-DA:34,1
-DA:35,1
-DA:36,4
-DA:37,4
-DA:38,1
-DA:39,1
-DA:40,1
-DA:41,1
-DA:42,1
-DA:43,1
-DA:44,1
-DA:45,1
-DA:46,1
-DA:47,1
-DA:48,1
-DA:49,1
-DA:50,1
-DA:51,1
-DA:52,1
-DA:53,4
-DA:54,4
-DA:55,4
-DA:56,4
-DA:57,4
-DA:58,4
-DA:59,4
-DA:60,4
-DA:61,1
-DA:62,1
-DA:63,1
-LH:63
-LF:63
-end_of_record
-SF:tsx.mjs
-FN:11,anonymous_0
-FN:12,anonymous_1
-FN:15,resolve
-FN:34,load
-FN:44,anonymous_4
-FNDA:1,anonymous_0
-FNDA:0,anonymous_1
-FNDA:17,resolve
-FNDA:3,load
-FNDA:0,anonymous_4
-FNF:5
-FNH:3
-BRDA:1,0,0,1
-BRDA:11,1,0,1
-BRDA:15,2,0,17
-BRDA:21,3,0,4
-BRDA:24,4,0,13
-BRDA:26,5,0,12
-BRDA:29,6,0,1
-BRDA:34,7,0,3
-BRDA:35,8,0,1
-BRDA:35,9,0,2
-BRDA:41,10,0,0
-BRDA:41,11,0,2
-BRDA:55,12,0,0
-BRDA:55,13,0,2
-BRF:14
-BRH:12
-DA:1,1
-DA:2,1
-DA:3,1
-DA:4,1
-DA:5,1
-DA:6,1
-DA:7,1
-DA:8,1
-DA:9,1
-DA:10,1
-DA:11,1
-DA:12,1
-DA:13,1
-DA:14,1
-DA:15,1
-DA:16,17
-DA:17,17
-DA:18,17
-DA:19,17
-DA:20,17
-DA:21,17
-DA:22,4
-DA:23,4
-DA:24,4
-DA:25,13
-DA:26,17
-DA:27,12
-DA:28,12
-DA:29,12
-DA:30,1
-DA:31,1
-DA:32,17
-DA:33,1
-DA:34,1
-DA:35,3
-DA:36,2
-DA:37,2
-DA:38,2
-DA:39,2
-DA:40,2
-DA:41,3
-DA:42,2
-DA:43,2
-DA:44,2
-DA:45,0
-DA:46,0
-DA:47,0
-DA:48,0
-DA:49,0
-DA:50,0
-DA:51,0
-DA:52,0
-DA:53,2
-DA:54,2
-DA:55,3
-DA:56,2
-DA:57,2
-DA:58,2
-DA:59,2
-DA:60,2
-DA:61,3
-DA:62,1
-DA:63,1
-DA:64,1
-DA:65,1
-DA:66,1
-DA:67,1
-DA:68,1
-DA:69,1
-DA:70,1
-DA:71,1
-DA:72,1
-DA:73,1
-DA:74,1
-DA:75,1
-DA:76,1
-DA:77,1
-DA:78,1
-DA:79,1
-DA:80,1
-DA:81,1
-DA:82,1
-LH:74
-LF:82
-end_of_record
-SF:tsx.spec.mjs
-FN:11,anonymous_0
-FN:12,anonymous_1
-FN:13,anonymous_2
-FN:22,anonymous_3
-FN:34,anonymous_4
-FN:46,anonymous_5
-FN:52,anonymous_6
-FN:53,anonymous_7
-FN:80,anonymous_8
-FN:88,anonymous_9
-FNDA:1,anonymous_0
-FNDA:1,anonymous_1
-FNDA:1,anonymous_2
-FNDA:1,anonymous_3
-FNDA:3,anonymous_4
-FNDA:3,anonymous_5
-FNDA:1,anonymous_6
-FNDA:1,anonymous_7
-FNDA:1,anonymous_8
-FNDA:1,anonymous_9
-FNF:10
-FNH:10
-BRDA:1,0,0,1
-BRDA:11,1,0,1
-BRDA:12,2,0,1
-BRDA:13,3,0,1
-BRDA:22,4,0,1
-BRDA:34,5,0,1
-BRDA:35,6,0,3
-BRDA:46,7,0,1
-BRDA:48,8,0,3
-BRDA:52,9,0,1
-BRDA:53,10,0,1
-BRDA:80,11,0,1
-BRDA:88,12,0,1
-BRF:13
-BRH:13
-DA:1,1
-DA:2,1
-DA:3,1
-DA:4,1
-DA:5,1
-DA:6,1
-DA:7,1
-DA:8,1
-DA:9,1
-DA:10,1
-DA:11,1
-DA:12,1
-DA:13,1
-DA:14,1
-DA:15,1
-DA:16,1
-DA:17,1
-DA:18,1
-DA:19,1
-DA:20,1
-DA:21,1
-DA:22,1
-DA:23,1
-DA:24,1
-DA:25,1
-DA:26,1
-DA:27,1
-DA:28,1
-DA:29,1
-DA:30,1
-DA:31,1
-DA:32,1
-DA:33,1
-DA:34,1
-DA:35,1
-DA:36,3
-DA:37,3
-DA:38,3
-DA:39,3
-DA:40,3
-DA:41,3
-DA:42,3
-DA:43,3
-DA:44,1
-DA:45,1
-DA:46,1
-DA:47,1
-DA:48,1
-DA:49,1
-DA:50,1
-DA:51,1
-DA:52,1
-DA:53,1
-DA:54,1
-DA:55,1
-DA:56,1
-DA:57,1
-DA:58,1
-DA:59,1
-DA:60,1
-DA:61,1
-DA:62,1
-DA:63,1
-DA:64,1
-DA:65,1
-DA:66,1
-DA:67,1
-DA:68,1
-DA:69,1
-DA:70,1
-DA:71,1
-DA:72,1
-DA:73,1
-DA:74,1
-DA:75,1
-DA:76,1
-DA:77,1
-DA:78,1
-DA:79,1
-DA:80,1
-DA:81,1
-DA:82,1
-DA:83,1
-DA:84,1
-DA:85,1
-DA:86,1
-DA:87,1
-DA:88,1
-DA:89,1
-DA:90,1
-DA:91,1
-DA:92,1
-DA:93,1
-DA:94,1
-DA:95,1
-DA:96,1
-LH:96
-LF:96
-end_of_record
diff --git a/nextLoad.fixture.mjs b/nextLoad.fixture.mjs
deleted file mode 100644
index ee7613f..0000000
--- a/nextLoad.fixture.mjs
+++ /dev/null
@@ -1,8 +0,0 @@
-import fs from 'node:fs/promises';
-
-export const nextLoad = async (url, { format = 'unknown' } = { format: 'unknown' }) => {
- return {
- format,
- source: await fs.readFile(url, 'utf-8'),
- };
-};
diff --git a/package-lock.json b/package-lock.json
index de3e8df..587a05f 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,71 +1,18 @@
{
"name": "nodejs-loaders",
- "version": "1.0.0",
+ "version": "2.0.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "nodejs-loaders",
- "version": "1.0.0",
+ "version": "2.0.0",
"license": "ISC",
- "dependencies": {
- "lodash-es": "^4.17.21"
- },
- "devDependencies": {
- "@types/node": "^20.11.28",
- "testdouble": "^3.20.1"
- },
- "peerDependencies": {
- "esbuild": "~0.19.5",
- "postcss": "~8.4.38"
- }
- },
- "node_modules/@esbuild/android-arm": {
- "version": "0.19.5",
- "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.19.5.tgz",
- "integrity": "sha512-bhvbzWFF3CwMs5tbjf3ObfGqbl/17ict2/uwOSfr3wmxDE6VdS2GqY/FuzIPe0q0bdhj65zQsvqfArI9MY6+AA==",
- "cpu": [
- "arm"
+ "workspaces": [
+ "./packages/*"
],
- "optional": true,
- "os": [
- "android"
- ],
- "peer": true,
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/@esbuild/android-arm64": {
- "version": "0.19.5",
- "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.19.5.tgz",
- "integrity": "sha512-5d1OkoJxnYQfmC+Zd8NBFjkhyCNYwM4n9ODrycTFY6Jk1IGiZ+tjVJDDSwDt77nK+tfpGP4T50iMtVi4dEGzhQ==",
- "cpu": [
- "arm64"
- ],
- "optional": true,
- "os": [
- "android"
- ],
- "peer": true,
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/@esbuild/android-x64": {
- "version": "0.19.5",
- "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.19.5.tgz",
- "integrity": "sha512-9t+28jHGL7uBdkBjL90QFxe7DVA+KGqWlHCF8ChTKyaKO//VLuoBricQCgwhOjA1/qOczsw843Fy4cbs4H3DVA==",
- "cpu": [
- "x64"
- ],
- "optional": true,
- "os": [
- "android"
- ],
- "peer": true,
- "engines": {
- "node": ">=12"
+ "devDependencies": {
+ "@types/node": "^22.9.0"
}
},
"node_modules/@esbuild/darwin-arm64": {
@@ -84,301 +31,46 @@
"node": ">=12"
}
},
- "node_modules/@esbuild/darwin-x64": {
- "version": "0.19.5",
- "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.19.5.tgz",
- "integrity": "sha512-Ly8cn6fGLNet19s0X4unjcniX24I0RqjPv+kurpXabZYSXGM4Pwpmf85WHJN3lAgB8GSth7s5A0r856S+4DyiA==",
- "cpu": [
- "x64"
- ],
- "optional": true,
- "os": [
- "darwin"
- ],
- "peer": true,
- "engines": {
- "node": ">=12"
- }
+ "node_modules/@nodejs-loaders/alias": {
+ "resolved": "packages/alias",
+ "link": true
},
- "node_modules/@esbuild/freebsd-arm64": {
- "version": "0.19.5",
- "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.19.5.tgz",
- "integrity": "sha512-GGDNnPWTmWE+DMchq1W8Sd0mUkL+APvJg3b11klSGUDvRXh70JqLAO56tubmq1s2cgpVCSKYywEiKBfju8JztQ==",
- "cpu": [
- "arm64"
- ],
- "optional": true,
- "os": [
- "freebsd"
- ],
- "peer": true,
- "engines": {
- "node": ">=12"
- }
+ "node_modules/@nodejs-loaders/css-module": {
+ "resolved": "packages/css-module",
+ "link": true
},
- "node_modules/@esbuild/freebsd-x64": {
- "version": "0.19.5",
- "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.19.5.tgz",
- "integrity": "sha512-1CCwDHnSSoA0HNwdfoNY0jLfJpd7ygaLAp5EHFos3VWJCRX9DMwWODf96s9TSse39Br7oOTLryRVmBoFwXbuuQ==",
- "cpu": [
- "x64"
- ],
- "optional": true,
- "os": [
- "freebsd"
- ],
- "peer": true,
- "engines": {
- "node": ">=12"
- }
+ "node_modules/@nodejs-loaders/media": {
+ "resolved": "packages/media",
+ "link": true
},
- "node_modules/@esbuild/linux-arm": {
- "version": "0.19.5",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.19.5.tgz",
- "integrity": "sha512-lrWXLY/vJBzCPC51QN0HM71uWgIEpGSjSZZADQhq7DKhPcI6NH1IdzjfHkDQws2oNpJKpR13kv7/pFHBbDQDwQ==",
- "cpu": [
- "arm"
- ],
- "optional": true,
- "os": [
- "linux"
- ],
- "peer": true,
- "engines": {
- "node": ">=12"
- }
+ "node_modules/@nodejs-loaders/mismatched-format": {
+ "resolved": "packages/mismatched-format",
+ "link": true
},
- "node_modules/@esbuild/linux-arm64": {
- "version": "0.19.5",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.19.5.tgz",
- "integrity": "sha512-o3vYippBmSrjjQUCEEiTZ2l+4yC0pVJD/Dl57WfPwwlvFkrxoSO7rmBZFii6kQB3Wrn/6GwJUPLU5t52eq2meA==",
- "cpu": [
- "arm64"
- ],
- "optional": true,
- "os": [
- "linux"
- ],
- "peer": true,
- "engines": {
- "node": ">=12"
- }
+ "node_modules/@nodejs-loaders/parse-filename": {
+ "resolved": "packages/parse-filename",
+ "link": true
},
- "node_modules/@esbuild/linux-ia32": {
- "version": "0.19.5",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.19.5.tgz",
- "integrity": "sha512-MkjHXS03AXAkNp1KKkhSKPOCYztRtK+KXDNkBa6P78F8Bw0ynknCSClO/ztGszILZtyO/lVKpa7MolbBZ6oJtQ==",
- "cpu": [
- "ia32"
- ],
- "optional": true,
- "os": [
- "linux"
- ],
- "peer": true,
- "engines": {
- "node": ">=12"
- }
+ "node_modules/@nodejs-loaders/svgx": {
+ "resolved": "packages/svgx",
+ "link": true
},
- "node_modules/@esbuild/linux-loong64": {
- "version": "0.19.5",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.19.5.tgz",
- "integrity": "sha512-42GwZMm5oYOD/JHqHska3Jg0r+XFb/fdZRX+WjADm3nLWLcIsN27YKtqxzQmGNJgu0AyXg4HtcSK9HuOk3v1Dw==",
- "cpu": [
- "loong64"
- ],
- "optional": true,
- "os": [
- "linux"
- ],
- "peer": true,
- "engines": {
- "node": ">=12"
- }
+ "node_modules/@nodejs-loaders/text": {
+ "resolved": "packages/text",
+ "link": true
},
- "node_modules/@esbuild/linux-mips64el": {
- "version": "0.19.5",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.19.5.tgz",
- "integrity": "sha512-kcjndCSMitUuPJobWCnwQ9lLjiLZUR3QLQmlgaBfMX23UEa7ZOrtufnRds+6WZtIS9HdTXqND4yH8NLoVVIkcg==",
- "cpu": [
- "mips64el"
- ],
- "optional": true,
- "os": [
- "linux"
- ],
- "peer": true,
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/@esbuild/linux-ppc64": {
- "version": "0.19.5",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.19.5.tgz",
- "integrity": "sha512-yJAxJfHVm0ZbsiljbtFFP1BQKLc8kUF6+17tjQ78QjqjAQDnhULWiTA6u0FCDmYT1oOKS9PzZ2z0aBI+Mcyj7Q==",
- "cpu": [
- "ppc64"
- ],
- "optional": true,
- "os": [
- "linux"
- ],
- "peer": true,
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/@esbuild/linux-riscv64": {
- "version": "0.19.5",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.19.5.tgz",
- "integrity": "sha512-5u8cIR/t3gaD6ad3wNt1MNRstAZO+aNyBxu2We8X31bA8XUNyamTVQwLDA1SLoPCUehNCymhBhK3Qim1433Zag==",
- "cpu": [
- "riscv64"
- ],
- "optional": true,
- "os": [
- "linux"
- ],
- "peer": true,
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/@esbuild/linux-s390x": {
- "version": "0.19.5",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.19.5.tgz",
- "integrity": "sha512-Z6JrMyEw/EmZBD/OFEFpb+gao9xJ59ATsoTNlj39jVBbXqoZm4Xntu6wVmGPB/OATi1uk/DB+yeDPv2E8PqZGw==",
- "cpu": [
- "s390x"
- ],
- "optional": true,
- "os": [
- "linux"
- ],
- "peer": true,
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/@esbuild/linux-x64": {
- "version": "0.19.5",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.19.5.tgz",
- "integrity": "sha512-psagl+2RlK1z8zWZOmVdImisMtrUxvwereIdyJTmtmHahJTKb64pAcqoPlx6CewPdvGvUKe2Jw+0Z/0qhSbG1A==",
- "cpu": [
- "x64"
- ],
- "optional": true,
- "os": [
- "linux"
- ],
- "peer": true,
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/@esbuild/netbsd-x64": {
- "version": "0.19.5",
- "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.19.5.tgz",
- "integrity": "sha512-kL2l+xScnAy/E/3119OggX8SrWyBEcqAh8aOY1gr4gPvw76la2GlD4Ymf832UCVbmuWeTf2adkZDK+h0Z/fB4g==",
- "cpu": [
- "x64"
- ],
- "optional": true,
- "os": [
- "netbsd"
- ],
- "peer": true,
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/@esbuild/openbsd-x64": {
- "version": "0.19.5",
- "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.19.5.tgz",
- "integrity": "sha512-sPOfhtzFufQfTBgRnE1DIJjzsXukKSvZxloZbkJDG383q0awVAq600pc1nfqBcl0ice/WN9p4qLc39WhBShRTA==",
- "cpu": [
- "x64"
- ],
- "optional": true,
- "os": [
- "openbsd"
- ],
- "peer": true,
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/@esbuild/sunos-x64": {
- "version": "0.19.5",
- "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.19.5.tgz",
- "integrity": "sha512-dGZkBXaafuKLpDSjKcB0ax0FL36YXCvJNnztjKV+6CO82tTYVDSH2lifitJ29jxRMoUhgkg9a+VA/B03WK5lcg==",
- "cpu": [
- "x64"
- ],
- "optional": true,
- "os": [
- "sunos"
- ],
- "peer": true,
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/@esbuild/win32-arm64": {
- "version": "0.19.5",
- "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.19.5.tgz",
- "integrity": "sha512-dWVjD9y03ilhdRQ6Xig1NWNgfLtf2o/STKTS+eZuF90fI2BhbwD6WlaiCGKptlqXlURVB5AUOxUj09LuwKGDTg==",
- "cpu": [
- "arm64"
- ],
- "optional": true,
- "os": [
- "win32"
- ],
- "peer": true,
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/@esbuild/win32-ia32": {
- "version": "0.19.5",
- "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.19.5.tgz",
- "integrity": "sha512-4liggWIA4oDgUxqpZwrDhmEfAH4d0iljanDOK7AnVU89T6CzHon/ony8C5LeOdfgx60x5cnQJFZwEydVlYx4iw==",
- "cpu": [
- "ia32"
- ],
- "optional": true,
- "os": [
- "win32"
- ],
- "peer": true,
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/@esbuild/win32-x64": {
- "version": "0.19.5",
- "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.19.5.tgz",
- "integrity": "sha512-czTrygUsB/jlM8qEW5MD8bgYU2Xg14lo6kBDXW6HdxKjh8M5PzETGiSHaz9MtbXBYDloHNUAUW2tMiKW4KM9Mw==",
- "cpu": [
- "x64"
- ],
- "optional": true,
- "os": [
- "win32"
- ],
- "peer": true,
- "engines": {
- "node": ">=12"
- }
+ "node_modules/@nodejs-loaders/tsx": {
+ "resolved": "packages/tsx",
+ "link": true
},
"node_modules/@types/node": {
- "version": "20.11.28",
- "resolved": "https://registry.npmjs.org/@types/node/-/node-20.11.28.tgz",
- "integrity": "sha512-M/GPWVS2wLkSkNHVeLkrF2fD5Lx5UC4PxA0uZcKc6QqbIQUJyW1jVjueJYi1z8n0I5PxYrtpnPnWglE+y9A0KA==",
+ "version": "22.9.0",
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-22.9.0.tgz",
+ "integrity": "sha512-vuyHg81vvWA1Z1ELfvLko2c8f34gyA0zaic0+Rllc5lbCnbSyuvb2Oxpm6TAUAC/2xZN3QGqxBNggD1nNR2AfQ==",
"dev": true,
+ "license": "MIT",
"dependencies": {
- "undici-types": "~5.26.4"
+ "undici-types": "~6.19.8"
}
},
"node_modules/esbuild": {
@@ -418,67 +110,23 @@
"@esbuild/win32-x64": "0.19.5"
}
},
- "node_modules/function-bind": {
- "version": "1.1.2",
- "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
- "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
- "dev": true,
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/hasown": {
- "version": "2.0.2",
- "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
- "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
- "dev": true,
- "dependencies": {
- "function-bind": "^1.1.2"
- },
- "engines": {
- "node": ">= 0.4"
- }
- },
- "node_modules/is-core-module": {
- "version": "2.13.1",
- "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.13.1.tgz",
- "integrity": "sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw==",
- "dev": true,
- "dependencies": {
- "hasown": "^2.0.0"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
+ "node_modules/lodash.camelcase": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz",
+ "integrity": "sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==",
+ "license": "MIT"
},
- "node_modules/is-plain-obj": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz",
- "integrity": "sha512-yvkRyxmFKEOQ4pNXCmJG5AEQNlXJS5LaONXo5/cLdTZdWvsZ1ioJEonLGAosKlMWE8lwUy/bJzMjcw8az73+Fg==",
- "dev": true,
- "engines": {
- "node": ">=0.10.0"
- }
+ "node_modules/lodash.get": {
+ "version": "4.4.2",
+ "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz",
+ "integrity": "sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ==",
+ "license": "MIT"
},
- "node_modules/is-regexp": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/is-regexp/-/is-regexp-1.0.0.tgz",
- "integrity": "sha512-7zjFAPO4/gwyQAAgRRmqeEeyIICSdmCqa3tsVHMdBzaXXRiqopZL4Cyghg/XulGWrtABTpbnYYzzIRffLkP4oA==",
- "dev": true,
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/lodash": {
- "version": "4.17.21",
- "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
- "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
- "dev": true
- },
- "node_modules/lodash-es": {
- "version": "4.17.21",
- "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz",
- "integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw=="
+ "node_modules/lodash.upperfirst": {
+ "version": "4.3.1",
+ "resolved": "https://registry.npmjs.org/lodash.upperfirst/-/lodash.upperfirst-4.3.1.tgz",
+ "integrity": "sha512-sReKOYJIJf74dhJONhU4e0/shzi1trVbSWDOhKYE5XV2O+H7Sb2Dihwuc7xWxVl+DgFPyTqIN3zMfT9cq5iWDg==",
+ "license": "MIT"
},
"node_modules/nanoid": {
"version": "3.3.7",
@@ -498,12 +146,6 @@
"node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
}
},
- "node_modules/path-parse": {
- "version": "1.0.7",
- "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz",
- "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==",
- "dev": true
- },
"node_modules/picocolors": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.1.tgz",
@@ -538,36 +180,6 @@
"node": "^10 || ^12 || >=14"
}
},
- "node_modules/quibble": {
- "version": "0.9.1",
- "resolved": "https://registry.npmjs.org/quibble/-/quibble-0.9.1.tgz",
- "integrity": "sha512-2EkLLm3CsBhbHfYEgBWHSJZZRpVHUZLeuJVEQoU/lsCqxcOvVkgVlF4nWv2ACWKkb0lgxgMh3m8vq9rhx9LTIg==",
- "dev": true,
- "dependencies": {
- "lodash": "^4.17.21",
- "resolve": "^1.22.8"
- },
- "engines": {
- "node": ">= 0.14.0"
- }
- },
- "node_modules/resolve": {
- "version": "1.22.8",
- "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz",
- "integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==",
- "dev": true,
- "dependencies": {
- "is-core-module": "^2.13.0",
- "path-parse": "^1.0.7",
- "supports-preserve-symlinks-flag": "^1.0.0"
- },
- "bin": {
- "resolve": "bin/resolve"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
"node_modules/source-map-js": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.0.tgz",
@@ -577,57 +189,99 @@
"node": ">=0.10.0"
}
},
- "node_modules/stringify-object-es5": {
- "version": "2.5.0",
- "resolved": "https://registry.npmjs.org/stringify-object-es5/-/stringify-object-es5-2.5.0.tgz",
- "integrity": "sha512-vE7Xdx9ylG4JI16zy7/ObKUB+MtxuMcWlj/WHHr3+yAlQoN6sst2stU9E+2Qs3OrlJw/Pf3loWxL1GauEHf6MA==",
+ "node_modules/undici-types": {
+ "version": "6.19.8",
+ "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz",
+ "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==",
"dev": true,
+ "license": "MIT"
+ },
+ "packages/alias": {
+ "name": "@nodejs-loaders/alias",
+ "version": "1.0.0",
+ "license": "ISC",
"dependencies": {
- "is-plain-obj": "^1.0.0",
- "is-regexp": "^1.0.0"
- },
- "engines": {
- "node": ">=0.10.0"
+ "lodash.get": "^4.4.2"
}
},
- "node_modules/supports-preserve-symlinks-flag": {
+ "packages/css-module": {
+ "name": "@nodejs-loaders/css-module",
"version": "1.0.0",
- "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz",
- "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==",
- "dev": true,
- "engines": {
- "node": ">= 0.4"
+ "license": "ISC",
+ "dependencies": {
+ "@nodejs-loaders/parse-filename": "1.0.0"
},
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
+ "peerDependencies": {
+ "postcss": "~8.4.38"
}
},
- "node_modules/testdouble": {
- "version": "3.20.1",
- "resolved": "https://registry.npmjs.org/testdouble/-/testdouble-3.20.1.tgz",
- "integrity": "sha512-D9Or6ayxr16dPPEkmXyGb8ow7VcQjUzuYFUxPTkx2FdSkn5Z6EC6cxQHwEGhedmE30FAJOYiAW+r7XXg6FmYOQ==",
- "dev": true,
+ "packages/css-modules": {
+ "name": "@nodejs-loaders/css-module",
+ "version": "1.0.0",
+ "extraneous": true,
+ "license": "ISC",
"dependencies": {
- "lodash": "^4.17.21",
- "quibble": "^0.9.1",
- "stringify-object-es5": "^2.5.0",
- "theredoc": "^1.0.0"
+ "@nodejs-loaders/parse-filename": "1.0.0"
},
- "engines": {
- "node": ">= 16"
+ "devDependencies": {
+ "testdouble": "^3.20.1"
+ },
+ "peerDependencies": {
+ "postcss": "~8.4.38"
}
},
- "node_modules/theredoc": {
+ "packages/media": {
+ "name": "@nodejs-loaders/media",
"version": "1.0.0",
- "resolved": "https://registry.npmjs.org/theredoc/-/theredoc-1.0.0.tgz",
- "integrity": "sha512-KU3SA3TjRRM932jpNfD3u4Ec3bSvedyo5ITPI7zgWYnKep7BwQQaxlhI9qbO+lKJoRnoAbEVfMcAHRuKVYikDA==",
- "dev": true
+ "license": "ISC",
+ "dependencies": {
+ "@nodejs-loaders/parse-filename": "1.0.0"
+ }
},
- "node_modules/undici-types": {
- "version": "5.26.5",
- "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz",
- "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==",
- "dev": true
+ "packages/mismatched-format": {
+ "name": "@nodejs-loaders/mismatched-format",
+ "version": "1.0.0",
+ "license": "ISC",
+ "dependencies": {
+ "@nodejs-loaders/parse-filename": "1.0.0"
+ }
+ },
+ "packages/parse-filename": {
+ "name": "@nodejs-loaders/parse-filename",
+ "version": "1.0.0",
+ "license": "ISC"
+ },
+ "packages/svgx": {
+ "name": "@nodejs-loaders/svgx",
+ "version": "1.0.0",
+ "license": "ISC",
+ "dependencies": {
+ "@nodejs-loaders/parse-filename": "1.0.0",
+ "lodash.camelcase": "~4.3.0",
+ "lodash.upperfirst": "~4.3.1"
+ }
+ },
+ "packages/text": {
+ "name": "@nodejs-loaders/text",
+ "version": "1.0.0",
+ "license": "ISC",
+ "dependencies": {
+ "@nodejs-loaders/parse-filename": "1.0.0"
+ }
+ },
+ "packages/tsx": {
+ "name": "@nodejs-loaders/tsx",
+ "version": "1.0.0",
+ "license": "ISC",
+ "dependencies": {
+ "@nodejs-loaders/parse-filename": "1.0.0"
+ },
+ "engines": {
+ "node": ">=23.2"
+ },
+ "peerDependencies": {
+ "esbuild": "~0.19.5"
+ }
}
}
}
diff --git a/package.json b/package.json
index d236107..542fbc4 100644
--- a/package.json
+++ b/package.json
@@ -1,38 +1,27 @@
{
- "version": "1.1.0",
+ "version": "2.0.0",
"name": "nodejs-loaders",
"author": "Jacob Smith",
+ "maintainers": [
+ "Augustin Mauroy"
+ ],
"license": "ISC",
"keywords": [
- "nodejs",
+ "customisation hooks",
"loaders",
- "plugin",
- "typescript"
+ "node.js",
+ "plugin"
],
"scripts": {
- "test": "node --experimental-test-coverage --test-reporter=lcov --test-reporter-destination=./coverage.lcov --test ./*.spec.*"
- },
- "exports": {
- "./dev/alias": "./alias.mjs",
- "./dev/mismatched-format": "./mismatched-format.mjs",
- "./dev/svgx": "./svgx.mjs",
- "./dev/tsx": "./tsx.mjs",
- "./testing/css-module": "./css-module.mjs",
- "./testing/media": "./media.mjs",
- "./testing/text": "./text.mjs"
- },
- "peerDependencies": {
- "postcss": "~8.4.38",
- "esbuild": "~0.19.5"
- },
- "dependencies": {
- "lodash-es": "^4.17.21"
- },
- "devDependencies": {
- "@types/node": "^20.11.28",
- "testdouble": "^3.20.1"
+ "test": "node --no-warnings --experimental-test-coverage --test-reporter=lcov --test-reporter-destination=./coverage.lcov --test-reporter=spec --test-reporter-destination=stdout --experimental-test-module-mocks --test --test-coverage-include='packages/**/*' --test-coverage-exclude='**/*.spec.mjs' --test-coverage-exclude='**/*.test.mjs' './packages/*/*.spec.mjs' './packages/*/*.test.mjs'"
},
"repository": {
"url": "https://github.com/JakobJingleheimer/nodejs-loaders"
+ },
+ "workspaces": [
+ "./packages/*"
+ ],
+ "devDependencies": {
+ "@types/node": "^22.9.0"
}
}
diff --git a/packages/alias/README.md b/packages/alias/README.md
new file mode 100644
index 0000000..a9ad8b0
--- /dev/null
+++ b/packages/alias/README.md
@@ -0,0 +1,27 @@
+# Nodejs Loaders: Alias
+
+
+
+[![npm version](https://img.shields.io/npm/v/nodejs-loaders/alias.svg)](https://www.npmjs.com/package/nodejs-loaders/alias)
+![size](https://img.shields.io/github/languages/code-size/JakobJingleheimer/nodejs-loaders/alias)
+
+**Environments**: dev, test
+
+This loader facilitates TypeScript's [`paths`](https://www.typescriptlang.org/docs/handbook/modules/reference.html#paths), handling the (important) half of work TypeScript ignores. It looks for a `tsconfig.json` in the project root (the current working directory) and builds aliases from `compilerOptions.paths` if it exists. If your tsconfig lives in a different location, create a symlink to it from your project root.
+
+> [!TIP]
+> **If you're not using TypeScript**, consider using Node.js's [subpath imports](https://nodejs.org/api/packages.html#subpath-imports).
+
+## A simple prefix
+
+This is commonly used to reference the project root; common prefixes are `@/` (or some variation like `@app/`) and `…/`: `import foo from '…/app/foo.mts;` → `${project_root}/src/app/foo.mts`.
+
+> [!TIP]
+> Due to package namespacing (aka ["scopes"](https://docs.npmjs.com/about-scopes)) it may be best to avoid using the "at" symbol (`@`) since that could lead to confusion over what is a package and what is an alias (especially if you eventually add a package named with the alias you're using). You should similarly avoid the octothorpe/hash symbol (`#`) because that is used by Node.js's sub-path imports.
+
+> [!IMPORTANT]
+> When configuring these aliases, ensure astrisks (`*`) are used correctly; configuring this for TypeScript can be extremely confusing. See [_Why are these tsconfig paths not working?_](https://stackoverflow.com/q/50679031) for some of the litany of ways configuration can fail.
+
+## A pointer
+
+This is a static specifier similar to a bare module specifier: `foo` → `${project_root}/src/app/foo.mts`. This may be useful when you have a commonly referenced file like config (which may conditionally not even live on the same filesystem): `import CONF from 'conf';` → `${project_root}/config.json`.
diff --git a/alias.mjs b/packages/alias/alias.mjs
similarity index 94%
rename from alias.mjs
rename to packages/alias/alias.mjs
index 9c7ff00..966ad11 100644
--- a/alias.mjs
+++ b/packages/alias/alias.mjs
@@ -2,7 +2,7 @@ import { readFile } from 'node:fs/promises';
import path from 'node:path';
import { pathToFileURL, URL } from 'node:url';
-import _get from 'lodash-es/get.js';
+import _get from 'lodash.get';
const projectRoot = pathToFileURL(`${process.cwd()}/`);
@@ -22,9 +22,10 @@ if (!aliases) console.warn(
'This loader will behave as a noop (but you should probably remove it if you aren’t using it).',
);
-export function resolve(specifier, ctx, next) {
+function resolveAlias(specifier, ctx, next) {
return (aliases ? resolveAliases : next)(specifier, ctx, next);
}
+export { resolveAlias as resolve }
export async function resolveAliases(specifier, ctx, next) {
for (const [key, dest] of aliases) {
@@ -65,4 +66,3 @@ function buildAliasMaps(config) {
return aliases;
}
-
diff --git a/alias.spec.mjs b/packages/alias/alias.spec.mjs
similarity index 73%
rename from alias.spec.mjs
rename to packages/alias/alias.spec.mjs
index 941c892..dae2217 100644
--- a/alias.spec.mjs
+++ b/packages/alias/alias.spec.mjs
@@ -2,16 +2,16 @@ import assert from 'node:assert/strict';
import {
before,
describe,
+ mock,
test,
} from 'node:test';
-import * as td from 'testdouble';
-
-import { nextResolve } from './nextResolve.fixture.mjs';
+import { nextResolve } from '../../fixtures/nextResolve.fixture.mjs';
describe('alias', () => {
- const readFile = td.func('mock_readFile');
+ /** @type {MockFunctionContext} */
+ let mock_readFile;
const base = 'file://';
const aliases = {
'…/*': ['./src/*'],
@@ -24,19 +24,25 @@ describe('alias', () => {
}
before(async () => {
- await td.replaceEsm('node:fs/promises', { readFile });
- const nodeUrl = await import('node:url');
- await td.replaceEsm('node:url', { ...nodeUrl, pathToFileURL() { return new URL(base) } });
+ const readFile = mock.fn(function mock_readFile() {});
+ mock_readFile = readFile.mock;
+ mock.module('node:fs/promises', { namedExports: { readFile } });
+ mock.module('node:url', {
+ namedExports: {
+ ...(await import('node:url')),
+ pathToFileURL() { return new URL(base) },
+ },
+ });
});
describe('that are in tsconfig.json', async () => {
let resolve;
before(async () => {
- td.when(readFile(td.matchers.contains('/package.json')))
- .thenReject(new ENOENT()); // shouldn't matter
- td.when(readFile(td.matchers.contains('/tsconfig.json')))
- .thenResolve(JSON.stringify({ compilerOptions: { paths: aliases } }));
+ mock_readFile.mockImplementation(async function mock_readFile(p) {
+ if (p.includes('/package.json')) throw new ENOENT(); // shouldn't matter
+ if (p.includes('/tsconfig.json')) return JSON.stringify({ compilerOptions: { paths: aliases } });
+ });
({ resolve } = await import('./alias.mjs'));
});
@@ -48,10 +54,10 @@ describe('alias', () => {
let resolve;
before(async () => {
- td.when(readFile(td.matchers.contains('/package.json')))
- .thenResolve(JSON.stringify({ aliases }));
- td.when(readFile(td.matchers.contains('/tsconfig.json')))
- .thenReject(new ENOENT()); // must be voided so package.json gets checked
+ mock_readFile.mockImplementation(async function mock_readFile(p) {
+ if (p.includes('/tsconfig.json')) throw new ENOENT(); // must be voided so package.json gets checked
+ if (p.includes('/package.json')) return JSON.stringify({ paths: aliases });
+ });
({ resolve } = await import('./alias.mjs'));
});
diff --git a/packages/alias/package.json b/packages/alias/package.json
new file mode 100644
index 0000000..6f4ca99
--- /dev/null
+++ b/packages/alias/package.json
@@ -0,0 +1,20 @@
+{
+ "version": "1.0.0",
+ "name": "@nodejs-loaders/alias",
+ "type": "module",
+ "author": "Jacob Smith",
+ "maintainers": [
+ "Augustin Mauroy"
+ ],
+ "license": "ISC",
+ "main": "./alias.mjs",
+ "keywords": [
+ "customisation hook",
+ "loader",
+ "node.js",
+ "path alias"
+ ],
+ "dependencies": {
+ "lodash.get": "^4.4.2"
+ }
+}
diff --git a/packages/css-module/README.md b/packages/css-module/README.md
new file mode 100644
index 0000000..c7a5dae
--- /dev/null
+++ b/packages/css-module/README.md
@@ -0,0 +1,41 @@
+# Nodejs Loaders: CSS Module
+
+
+
+[![npm version](https://img.shields.io/npm/v/nodejs-loaders/media.svg)](https://www.npmjs.com/package/nodejs-loaders/css-module)
+![size](https://img.shields.io/github/languages/code-size/JakobJingleheimer/nodejs-loaders/css-module)
+
+**Environment**: test
+
+This loads the module as a plain-object of simple key-value pairs of the css specifiers like:
+
+```css
+/* main.module.css */
+#Bar {
+ font-weight: bold;
+}
+
+.Foo {
+ text-decoration: none
+
+ .Baz { color: red }
+}
+
+.Qux .Zed {
+ font-size: 1.1em;
+}
+```
+
+```js
+import styles from 'main.module.css';
+
+styles.Bar; // 'Bar'
+styles.Baz; // 'Baz'
+styles.Foo; // 'Foo'
+styles.Zed; // 'Zed'
+```
+
+This ensures snapshots are unaffected by unrelated changes.
+
+> [!WARNING]
+> This loader does not differentiate classes vs ids; thus duplicate names can create a last-wins conflict. For example `#Foo` and `.Foo` will result in just `Foo: 'Foo'`. This is unlikely to cause any real-world problems (and you probably shouldn't be doing this anyway).
diff --git a/css-module.mjs b/packages/css-module/css-module.mjs
similarity index 80%
rename from css-module.mjs
rename to packages/css-module/css-module.mjs
index ee48fed..626d09f 100644
--- a/css-module.mjs
+++ b/packages/css-module/css-module.mjs
@@ -3,25 +3,26 @@
import postcss from 'postcss';
-import { stripExtras } from './parse-filename.mjs';
+import { stripExtras } from '@nodejs-loaders/parse-filename';
-export async function resolve(specifier, ctx, nextResolve) {
+async function resolveCSSModule(specifier, ctx, nextResolve) {
const nextResult = await nextResolve(specifier);
if (!stripExtras(specifier).endsWith('.module.css')) return nextResult;
return {
...ctx,
- format: 'cssmodule',
+ format: 'css-module',
url: nextResult.url,
};
}
+export { resolveCSSModule as resolve }
-export async function load(url, ctx, nextLoad) {
+async function loadCSSModule(url, ctx, nextLoad) {
const nextResult = await nextLoad(url, ctx);
- if (ctx.format !== 'cssmodule') return nextResult;
+ if (ctx.format !== 'css-module') return nextResult;
const rawSource = '' + nextResult.source;
const parsed = parseCssToObject(rawSource);
@@ -31,6 +32,7 @@ export async function load(url, ctx, nextLoad) {
source: JSON.stringify(parsed),
};
}
+export { loadCSSModule as load }
function parseCssToObject(rawSource) {
const output = new Map(); // Map is best for mutation
diff --git a/css-module.spec.mjs b/packages/css-module/css-module.spec.mjs
similarity index 70%
rename from css-module.spec.mjs
rename to packages/css-module/css-module.spec.mjs
index cac8f0c..28b0dfa 100644
--- a/css-module.spec.mjs
+++ b/packages/css-module/css-module.spec.mjs
@@ -1,9 +1,9 @@
import assert from 'node:assert/strict';
import { describe, it } from 'node:test';
-import { assertSuffixedSpecifiers } from './assert-suffixed-specifiers.fixture.mjs';
-import { nextResolve } from './nextResolve.fixture.mjs';
-import { nextLoad } from './nextLoad.fixture.mjs';
+import { assertSuffixedSpecifiers } from '../../fixtures/assert-suffixed-specifiers.fixture.mjs';
+import { nextResolve } from '../../fixtures/nextResolve.fixture.mjs';
+import { nextLoad } from '../../fixtures/nextLoad.fixture.mjs';
import { resolve, load } from './css-module.mjs';
@@ -14,7 +14,7 @@ describe('css-module loader', { concurrency: true }, () => {
const result = await resolve('./fixture.module.css', {}, nextResolve);
assert.deepEqual(result, {
- format: 'cssmodule',
+ format: 'css-module',
url: './fixture.module.css',
});
});
@@ -29,29 +29,29 @@ describe('css-module loader', { concurrency: true }, () => {
});
it('should ignore files that aren’t css at all', async () => {
- const result = await resolve('./fixture.ext', {}, nextResolve);
+ const result = await resolve('../../fixtures/fixture.ext', {}, nextResolve);
assert.deepEqual(result, {
format: 'unknown',
- url: './fixture.ext',
+ url: '../../fixtures/fixture.ext',
});
});
it('should handle specifiers with appending data', async () => {
- await assertSuffixedSpecifiers(resolve, './fixture.module.css', 'cssmodule');
+ await assertSuffixedSpecifiers(resolve, './fixture.module.css', 'css-module');
});
});
describe('load', () => {
it('should ignore files that aren’t css-modules', async () => {
- const result = await load('./fixture.js', { format: 'commonjs' }, nextLoad);
+ const result = await load(import.meta.resolve('./fixture.js'), { format: 'commonjs' }, nextLoad);
assert.equal(result.format, 'commonjs');
assert.equal(result.source, `export = 'foo';\n`);
});
it('should handle files with nested and non-nested comments', async () => {
- const result = await load('./fixture.module.css', { format: 'cssmodule' }, nextLoad);
+ const result = await load(import.meta.resolve('./fixture.module.css'), { format: 'css-module' }, nextLoad);
assert.equal(result.format, 'json');
assert.deepEqual(result.source, JSON.stringify({
diff --git a/fixture.js b/packages/css-module/fixture.js
similarity index 100%
rename from fixture.js
rename to packages/css-module/fixture.js
diff --git a/fixture.module.css b/packages/css-module/fixture.module.css
similarity index 100%
rename from fixture.module.css
rename to packages/css-module/fixture.module.css
diff --git a/packages/css-module/package.json b/packages/css-module/package.json
new file mode 100644
index 0000000..e8aa52b
--- /dev/null
+++ b/packages/css-module/package.json
@@ -0,0 +1,23 @@
+{
+ "version": "1.0.0",
+ "name": "@nodejs-loaders/css-module",
+ "type": "module",
+ "author": "Jacob Smith",
+ "maintainers": [
+ "Augustin Mauroy"
+ ],
+ "license": "ISC",
+ "main": "./css-module.mjs",
+ "keywords": [
+ "customisation hook",
+ "css modules",
+ "loader",
+ "node.js"
+ ],
+ "dependencies": {
+ "@nodejs-loaders/parse-filename": "1.0.0"
+ },
+ "peerDependencies": {
+ "postcss": "~8.4.38"
+ }
+}
diff --git a/packages/media/README.md b/packages/media/README.md
new file mode 100644
index 0000000..dcf1568
--- /dev/null
+++ b/packages/media/README.md
@@ -0,0 +1,38 @@
+# Nodejs Loaders: Media
+
+
+
+[![npm version](https://img.shields.io/npm/v/nodejs-loaders/media.svg)](https://www.npmjs.com/package/nodejs-loaders/media)
+![size](https://img.shields.io/github/languages/code-size/JakobJingleheimer/nodejs-loaders/media)
+
+**Environment**: test
+
+This loader returns the specifier (truncated from project root / current working directory) as the default export:
+
+```js
+import photo from './team.jpg'; // photo = '[…]/team.jpg'
+```
+
+This ensures snapshots are unaffected by the file system on which the test is run.
+
+
+Supported file extensions
+
+Audio/Video:
+* `.av1`
+* `.mp3`
+* `.mp3`
+* `.mp4`
+* `.ogg`
+* `.webm`
+
+Images:
+
+* `.avif`
+* `.gif`
+* `.ico`
+* `.jpeg`
+* `.jpg`
+* `.png`
+* `.webp`
+
diff --git a/media.mjs b/packages/media/media.mjs
similarity index 79%
rename from media.mjs
rename to packages/media/media.mjs
index f9f0329..feaabe9 100644
--- a/media.mjs
+++ b/packages/media/media.mjs
@@ -1,9 +1,9 @@
import process from 'node:process';
-import { getFilenameExt } from './parse-filename.mjs';
+import { getFilenameExt } from '@nodejs-loaders/parse-filename';
-export async function resolve(specifier, ctx, nextResolve) {
+async function resolveMedia(specifier, ctx, nextResolve) {
const nextResult = await nextResolve(specifier);
// Check against the fully resolved URL, not just the specifier, in case another loader has
@@ -16,8 +16,9 @@ export async function resolve(specifier, ctx, nextResolve) {
url: nextResult.url,
}
}
+export { resolveMedia as resolve }
-export async function load(url, ctx, nextLoad) {
+async function loadMedia(url, ctx, nextLoad) {
if (ctx.format !== 'media') return nextLoad(url);
const source = `export default '${url.replace(cwd, '[…]')}';`;
@@ -28,6 +29,7 @@ export async function load(url, ctx, nextLoad) {
source,
};
}
+export { loadMedia as load }
const cwd = process.cwd();
diff --git a/media.spec.mjs b/packages/media/media.spec.mjs
similarity index 65%
rename from media.spec.mjs
rename to packages/media/media.spec.mjs
index 5b7ac04..22bc011 100644
--- a/media.spec.mjs
+++ b/packages/media/media.spec.mjs
@@ -1,9 +1,9 @@
import assert from 'node:assert/strict';
import { describe, it } from 'node:test';
-import { assertSuffixedSpecifiers } from './assert-suffixed-specifiers.fixture.mjs';
-import { nextLoad } from './nextLoad.fixture.mjs';
-import { nextResolve } from './nextResolve.fixture.mjs';
+import { assertSuffixedSpecifiers } from '../../fixtures/assert-suffixed-specifiers.fixture.mjs';
+import { nextLoad } from '../../fixtures/nextLoad.fixture.mjs';
+import { nextResolve } from '../../fixtures/nextResolve.fixture.mjs';
import { exts, load, resolve } from './media.mjs';
@@ -11,11 +11,11 @@ import { exts, load, resolve } from './media.mjs';
describe('media loader', { concurrency: true }, () => {
describe('resolve', () => {
it('should ignore unrecognised files', async () => {
- const result = await resolve('./fixture.ext', {}, nextResolve);
+ const result = await resolve('../../fixtures/fixture.ext', {}, nextResolve);
assert.deepEqual(result, {
format: 'unknown',
- url: './fixture.ext',
+ url: '../../fixtures/fixture.ext',
});
});
@@ -38,7 +38,7 @@ describe('media loader', { concurrency: true }, () => {
describe('load', () => {
it('should ignore unrecognised files', async () => {
- const result = await load('./fixture.ext', {}, nextLoad);
+ const result = await load(import.meta.resolve('../../fixtures/fixture.ext'), {}, nextLoad);
assert.deepEqual(result, {
format: 'unknown',
@@ -48,13 +48,15 @@ describe('media loader', { concurrency: true }, () => {
it('should return the resolved URL for the media file', async () => {
for (const ext of exts) {
- const fileUrl = `./fixture.${ext}`;
- const result = await load(fileUrl, { format: 'media' }, () => { throw new Error('media file should not be read from disk'); });
+ const fileUrl = import.meta.resolve(`./fixture${ext}`);
+ const result = await load(fileUrl, { format: 'media' }, () => {
+ throw new Error('media file should not be read from disk');
+ });
assert.deepEqual(result, {
format: 'module',
shortCircuit: true,
- source: `export default '${fileUrl}';`,
+ source: `export default 'file://[…]/packages/media/fixture${ext}';`,
});
}
});
diff --git a/packages/media/package.json b/packages/media/package.json
new file mode 100644
index 0000000..7585ad0
--- /dev/null
+++ b/packages/media/package.json
@@ -0,0 +1,23 @@
+{
+ "version": "1.0.0",
+ "name": "@nodejs-loaders/media",
+ "type": "module",
+ "author": "Jacob Smith",
+ "maintainers": [
+ "Augustin Mauroy"
+ ],
+ "license": "ISC",
+ "main": "./media.mjs",
+ "keywords": [
+ "audio",
+ "customisation hook",
+ "images",
+ "video",
+ "loader",
+ "media",
+ "node.js"
+ ],
+ "dependencies": {
+ "@nodejs-loaders/parse-filename": "1.0.0"
+ }
+}
diff --git a/packages/mismatched-format/README.md b/packages/mismatched-format/README.md
new file mode 100644
index 0000000..e832523
--- /dev/null
+++ b/packages/mismatched-format/README.md
@@ -0,0 +1,31 @@
+# Nodejs Loaders: Mismatched format
+
+
+
+[![npm version](https://img.shields.io/npm/v/nodejs-loaders/mismatched-format.svg)](https://www.npmjs.com/package/nodejs-loaders/mismatched-format)
+![size](https://img.shields.io/github/languages/code-size/JakobJingleheimer/nodejs-loaders/mismatched-format)
+
+**Environments**: dev, test
+
+> [!important]
+> This loader works in most but not necessarily all cases. If you encounter a case where it fails to appropriately correct a mismatch, please open an issue and include the inappropriately handled module, what it claimed to be, and what the loader resolved the format to.
+
+Many packages are incorrectly configured, claiming to be ESM yet don't actually surface ESM. This is most commonly due to a `package.json` using the non-standard `"module"` field:
+
+```json
+{
+ "name": "mismatched-example",
+ "type": "module",
+ "main": "./dist/cjs.js",
+ "module": "./dist/esm.js"
+}
+```
+
+`mismatched-example` has told node its main entry point is `./dist/cjs.js` and that it is ESM. Therefore, trying to import this package will (likely) explode in a `SyntaxError` as node loads `./dist/cjs.js` as ESM (instead of the CJS it actually is).
+
+This loader detects the explosion and re-instructs node to ignore the misconfiguration and instead load `./dist/cjs.js` as CJS (the loader doesn't try to find a potential ESM main entry point the package may have—there are too many options, several of which are non-standard).
+
+Note to package authors reading this: The simplest fix here is to distribute only CJS. See [_Configuring CommonJS & ES Modules for Node.js_](https://dev.to/jakobjingleheimer/configuring-commonjs-es-modules-for-nodejs-12ed) for a thorough explanation of options.
+
+> [!IMPORTANT]
+> This is _not_ the same as [`--experimental-detect-module`](https://nodejs.org/api/cli.html#--experimental-detect-module): even with module detection enabled, node still believes when it is explicitly told a specific format (ex by `"type": "module"`), so it will not try to confirm it. Module detection also works only way: try CJS, then try ESM (_never_ try ESM, then try CJS).
diff --git a/containsCJS.mjs b/packages/mismatched-format/contains-cjs.mjs
similarity index 92%
rename from containsCJS.mjs
rename to packages/mismatched-format/contains-cjs.mjs
index 6e1b4b1..ba289be 100644
--- a/containsCJS.mjs
+++ b/packages/mismatched-format/contains-cjs.mjs
@@ -1,4 +1,4 @@
-export default function containsCJS(source) {
+export function containsCJS(source) {
const src = '' + source;
if (EXPORTS_PROPERTY.test(src)) return true;
diff --git a/containsCJS.spec.mjs b/packages/mismatched-format/contains-cjs.spec.mjs
similarity index 94%
rename from containsCJS.spec.mjs
rename to packages/mismatched-format/contains-cjs.spec.mjs
index 67962de..f371e16 100644
--- a/containsCJS.spec.mjs
+++ b/packages/mismatched-format/contains-cjs.spec.mjs
@@ -1,9 +1,10 @@
-import assert from 'node:assert';
+import assert from 'node:assert/strict';
import { describe, it } from 'node:test';
-import containsCJS from './containsCJS.mjs';
+import { containsCJS } from './contains-cjs.mjs';
-describe('containsCJS()', () => {
+
+describe('Contains CJS()', () => {
describe('exports', () => {
const matches = [
'exports.foo =',
diff --git a/packages/mismatched-format/fixtures/actually-cjs/module-exports.cjs.js b/packages/mismatched-format/fixtures/actually-cjs/module-exports.cjs.js
new file mode 100644
index 0000000..7606819
--- /dev/null
+++ b/packages/mismatched-format/fixtures/actually-cjs/module-exports.cjs.js
@@ -0,0 +1 @@
+module.exports = function foo() {}
diff --git a/packages/mismatched-format/fixtures/actually-cjs/package.json b/packages/mismatched-format/fixtures/actually-cjs/package.json
new file mode 100644
index 0000000..de61b5c
--- /dev/null
+++ b/packages/mismatched-format/fixtures/actually-cjs/package.json
@@ -0,0 +1,4 @@
+{
+ "name": "erroneously-esm",
+ "type": "module"
+}
diff --git a/packages/mismatched-format/fixtures/actually-cjs/uses-require.cjs.js b/packages/mismatched-format/fixtures/actually-cjs/uses-require.cjs.js
new file mode 100644
index 0000000..f6cf642
--- /dev/null
+++ b/packages/mismatched-format/fixtures/actually-cjs/uses-require.cjs.js
@@ -0,0 +1 @@
+const os = require('node:os');
diff --git a/packages/mismatched-format/fixtures/actually-esm/create-require.esm.js b/packages/mismatched-format/fixtures/actually-esm/create-require.esm.js
new file mode 100644
index 0000000..68d8bfa
--- /dev/null
+++ b/packages/mismatched-format/fixtures/actually-esm/create-require.esm.js
@@ -0,0 +1,6 @@
+import { createRequire } from 'node:module';
+
+
+const require = createRequire(import.meta.url);
+
+require('../../../fixtures/fixture.ext');
diff --git a/packages/mismatched-format/fixtures/actually-esm/package.json b/packages/mismatched-format/fixtures/actually-esm/package.json
new file mode 100644
index 0000000..f84e2f9
--- /dev/null
+++ b/packages/mismatched-format/fixtures/actually-esm/package.json
@@ -0,0 +1,4 @@
+{
+ "name": "erroneously-cjs",
+ "type": "commonjs"
+}
diff --git a/packages/mismatched-format/fixtures/actually-esm/require-in-comment.esm.js b/packages/mismatched-format/fixtures/actually-esm/require-in-comment.esm.js
new file mode 100644
index 0000000..09a8b97
--- /dev/null
+++ b/packages/mismatched-format/fixtures/actually-esm/require-in-comment.esm.js
@@ -0,0 +1,5 @@
+import os from 'node:os';
+
+// require(whatever)
+/* require(whatever) */
+/** require(whatever) */
diff --git a/mismatched-format.mjs b/packages/mismatched-format/mismatched-format.mjs
similarity index 53%
rename from mismatched-format.mjs
rename to packages/mismatched-format/mismatched-format.mjs
index 50ff711..cc9e219 100644
--- a/mismatched-format.mjs
+++ b/packages/mismatched-format/mismatched-format.mjs
@@ -1,17 +1,33 @@
-import containsCJS from './containsCJS.mjs';
-import { getFilenameExt } from './parse-filename.mjs';
+import { getFilenameExt } from '@nodejs-loaders/parse-filename';
+import { containsCJS } from './contains-cjs.mjs';
+
+
+/**
+ * @typedef {{
+ * format: string,
+ * parentURL: URL['href']
+ * }} LoadContext
+ */
+/**
+ * @callback LoadHook
+ * @param {URL['href']} url
+ * @param {LoadContext} ctx
+ * @param {LoadHook} next
+ * @returns {Promise<{ format: string, source: Buffer|string }>}
+ */
/**
* This loader attempts to detect and override misconfigured packages, such as those that declare
* themselves as ESM but are actually CJS, and vice versa.
+ * @type {LoadHook}
*/
-export async function load(url, ctx, next) {
+function loadMismatchedFormat(url, ctx, next) {
if (!exts.has(getFilenameExt(url))) return next(url);
// Ensure the ESMLoader is used to read the contents.
// It may throw, in which case we'll probably get a telling error we can use to know it was CJS.
- const nextResult = await next(url, { ...ctx, format: 'module' })
+ return next(url, { ...ctx, format: 'module' })
.then((result) => {
if (containsCJS(''+result.source)) { throw new Error('CommonJS'); }
@@ -19,7 +35,7 @@ export async function load(url, ctx, next) {
})
.catch(async (err) => {
if (
- (err?.message.includes('require') && err.includes('import'))
+ (err?.message.includes('require') && err?.message.includes('import'))
|| err?.message.includes('CommonJS')
) {
return { format: 'commonjs' };
@@ -27,9 +43,8 @@ export async function load(url, ctx, next) {
throw err;
});
-
- return nextResult;
}
+export { loadMismatchedFormat as load }
const exts = new Set([
'.js',
diff --git a/packages/mismatched-format/mismatched-format.spec.mjs b/packages/mismatched-format/mismatched-format.spec.mjs
new file mode 100644
index 0000000..68d1097
--- /dev/null
+++ b/packages/mismatched-format/mismatched-format.spec.mjs
@@ -0,0 +1,61 @@
+import assert from 'node:assert/strict';
+import {
+ before,
+ describe,
+ it,
+ mock,
+} from 'node:test';
+
+
+describe('Mismatched format loader (unit)', () => {
+ /** @type {import('./mismatched-format.mjs').load} */
+ let load;
+ /** @type {MockFunctionContext} */
+ let mock__containsCJS;
+
+ before(async () => {
+ const containsCJS = mock.fn();
+ mock__containsCJS = containsCJS.mock;
+ mock.module('./contains-cjs.mjs', { namedExports: { containsCJS } });
+
+ ({ load } = await import('./mismatched-format.mjs'));
+ });
+
+ describe('when "esm" is actually cjs', () => {
+ it('should detect and report the corrected format', async () => {
+ mock__containsCJS.mockImplementationOnce(function mock__containsCJS() { return true });
+ const result = await load(
+ import.meta.resolve('./unimportant.js'),
+ {},
+ async () => ({
+ format: 'module',
+ source: '"unimportant"',
+ }),
+ );
+
+ assert.equal(result.format, 'commonjs');
+ });
+
+ it('should detect and report the corrected format', async () => {
+ mock__containsCJS.mockImplementationOnce(function mock__containsCJS() { return false });
+ const result = await load(
+ import.meta.resolve('./unimportant.js'),
+ {},
+ async () => { throw new Error('require and import') },
+ );
+
+ assert.equal(result.format, 'commonjs');
+ });
+
+ it('should detect and report the corrected format', async () => {
+ mock__containsCJS.mockImplementationOnce(() => false)
+ const result = await load(
+ import.meta.resolve('./unimportant.js'),
+ {},
+ async () => { throw new Error('CommonJS') },
+ );
+
+ assert.equal(result.format, 'commonjs');
+ });
+ });
+});
diff --git a/packages/mismatched-format/mismatched-format.test.mjs b/packages/mismatched-format/mismatched-format.test.mjs
new file mode 100644
index 0000000..6e50bc5
--- /dev/null
+++ b/packages/mismatched-format/mismatched-format.test.mjs
@@ -0,0 +1,39 @@
+import assert from 'node:assert/strict';
+import {
+ describe,
+ it,
+} from 'node:test';
+
+import { load } from './mismatched-format.mjs';
+import { nextLoad } from '../../fixtures/nextLoad.fixture.mjs';
+
+
+describe('Mismatched format loader (e2e)', () => {
+ describe('correctly identify the containing CJS as CJS, despite "type": "module"', () => {
+ it('should handle `require()`', async () => {
+ const result = await load(import.meta.resolve('./fixtures/actually-cjs/uses-require.cjs.js'), {}, nextLoad);
+
+ assert.equal(result.format, 'commonjs');
+ });
+
+ it('should handle `module.exports`', async () => {
+ const result = await load(import.meta.resolve('./fixtures/actually-cjs/module-exports.cjs.js'), {}, nextLoad);
+
+ assert.equal(result.format, 'commonjs');
+ });
+ });
+
+ describe('correctly identify the containing ESM as ESM, despite "type": "commonjs"', () => {
+ it('should handle createRequire', async () => {
+ const result = await load(import.meta.resolve('./fixtures/actually-esm/create-require.esm.js'), {}, nextLoad);
+
+ assert.equal(result.format, 'module');
+ });
+
+ it('should handle `require()` within a comment', async () => {
+ const result = await load(import.meta.resolve('./fixtures/actually-esm/require-in-comment.esm.js'), {}, nextLoad);
+
+ assert.equal(result.format, 'module');
+ });
+ });
+});
diff --git a/packages/mismatched-format/package.json b/packages/mismatched-format/package.json
new file mode 100644
index 0000000..b8a9c40
--- /dev/null
+++ b/packages/mismatched-format/package.json
@@ -0,0 +1,20 @@
+{
+ "version": "1.0.0",
+ "name": "@nodejs-loaders/mismatched-format",
+ "type": "module",
+ "author": "Jacob Smith",
+ "maintainers": [
+ "Augustin Mauroy"
+ ],
+ "license": "ISC",
+ "main": "./mismatched-format.mjs",
+ "keywords": [
+ "customisation hook",
+ "loader",
+ "misconfiguration",
+ "node.js"
+ ],
+ "dependencies": {
+ "@nodejs-loaders/parse-filename": "1.0.0"
+ }
+}
diff --git a/packages/parse-filename/package.json b/packages/parse-filename/package.json
new file mode 100644
index 0000000..c2c8602
--- /dev/null
+++ b/packages/parse-filename/package.json
@@ -0,0 +1,11 @@
+{
+ "version": "1.0.0",
+ "name": "@nodejs-loaders/parse-filename",
+ "type": "module",
+ "author": "Jacob Smith",
+ "maintainers": [
+ "Augustin Mauroy"
+ ],
+ "license": "ISC",
+ "main": "./parse-filename.mjs"
+}
diff --git a/parse-filename.mjs b/packages/parse-filename/parse-filename.mjs
similarity index 64%
rename from parse-filename.mjs
rename to packages/parse-filename/parse-filename.mjs
index f04e28a..e44164d 100644
--- a/parse-filename.mjs
+++ b/packages/parse-filename/parse-filename.mjs
@@ -15,11 +15,16 @@ export function stripExtras(f) {
return f.split('?')[0].split('#')[0];
}
+/**
+ * @param {`/${string}` | URL['href']} resolvedUrl
+ */
export function getFilenameParts(resolvedUrl) {
- const url = new URL(resolvedUrl);
+ const pathname = URL.canParse(resolvedUrl)
+ ? (new URL(resolvedUrl)).pathname
+ : resolvedUrl;
- const ext = getFilenameExt(url.pathname);
- const base = path.basename(url.pathname, ext);
+ const ext = getFilenameExt(pathname);
+ const base = path.basename(pathname, ext);
return {
base,
diff --git a/packages/svgx/README.md b/packages/svgx/README.md
new file mode 100644
index 0000000..633cfc1
--- /dev/null
+++ b/packages/svgx/README.md
@@ -0,0 +1,32 @@
+# Nodejs Loaders: SVGX
+
+
+
+[![npm version](https://img.shields.io/npm/v/nodejs-loaders/svgx.svg)](https://www.npmjs.com/package/nodejs-loaders/svgx)
+![size](https://img.shields.io/github/languages/code-size/JakobJingleheimer/nodejs-loaders/svgx)
+
+**Environment**: test
+
+This loader facilitates running tests against JSX/TSX components that consume SVGs as JSX/TSX. It looks for a `esbuild.config.mjs` in the project root (the current working directory); if your config lives in a different location, create a symlink to it from your project root. Only options for [esbuild's "transform" API](https://esbuild.github.io/api/#transform) are valid (esbuild handles looking for a tsconfig). When none is found, it uses a few necessary default.
+
+This loader does _not_ handle TypeScript's file extension nonsense. Import specifiers must use the actual file extension of the file actually on disk:
+
+```
+./
+ ├ …
+ └ foo.ts
+```
+
+💥 `import foo from './foo.js';`
+✅ `import foo from './foo.ts';`
+
+If your project contains erroneous specifiers like above, use the [correct-ts-specifiers](https://github.com/JakobJingleheimer/correct-ts-specifiers) codemod to fix your source-code.
+
+
+Supported file extensions
+
+* `.jsx`
+* `.mts`
+* `.ts`
+* `.tsx`
+
diff --git a/packages/svgx/esbuild.config.mjs b/packages/svgx/esbuild.config.mjs
new file mode 120000
index 0000000..48dd409
--- /dev/null
+++ b/packages/svgx/esbuild.config.mjs
@@ -0,0 +1 @@
+../../fixtures/esbuild.config.mjs
\ No newline at end of file
diff --git a/fixture.svg b/packages/svgx/fixture.svg
similarity index 100%
rename from fixture.svg
rename to packages/svgx/fixture.svg
diff --git a/packages/svgx/package.json b/packages/svgx/package.json
new file mode 100644
index 0000000..e57ebaa
--- /dev/null
+++ b/packages/svgx/package.json
@@ -0,0 +1,22 @@
+{
+ "version": "1.0.0",
+ "name": "@nodejs-loaders/svgx",
+ "type": "module",
+ "author": "Jacob Smith",
+ "maintainers": [
+ "Augustin Mauroy"
+ ],
+ "license": "ISC",
+ "main": "./svgx.mjs",
+ "keywords": [
+ "customisation hook",
+ "loader",
+ "node.js",
+ "svg as jsx"
+ ],
+ "dependencies": {
+ "lodash.camelcase": "~4.3.0",
+ "lodash.upperfirst": "~4.3.1",
+ "@nodejs-loaders/parse-filename": "1.0.0"
+ }
+}
diff --git a/svgx.mjs b/packages/svgx/svgx.mjs
similarity index 76%
rename from svgx.mjs
rename to packages/svgx/svgx.mjs
index 42605af..518530c 100644
--- a/svgx.mjs
+++ b/packages/svgx/svgx.mjs
@@ -1,7 +1,7 @@
-import _camelCase from 'lodash-es/camelCase.js';
-import _upperFirst from 'lodash-es/upperFirst.js';
+import _camelCase from 'lodash.camelcase';
+import _upperFirst from 'lodash.upperfirst';
-import { getFilenameParts } from './parse-filename.mjs';
+import { getFilenameParts } from '@nodejs-loaders/parse-filename';
const nonWords = /[\W$]/;
@@ -9,20 +9,20 @@ const nonWords = /[\W$]/;
/**
* Read an SVG file (which is text) and build a react component that returns the SVG.
*/
-export async function load(url, ctx, next) {
+async function loadSVGX(url, ctx, next) {
const { ext, ...others } = getFilenameParts(url);
- const base = pascalCase(others.base);
if (ext !== '.svg') return next(url);
- if (nonWords.test(base)) {
+ if (nonWords.test(others.base)) {
throw new SyntaxError([
'Cannot generate a react component name from filename',
- `"${base}"`,
+ `"${others.base}"`,
'as it contains character(s) illegal for JavaScript identifiers',
].join(' '));
}
+ const base = pascalCase(others.base);
const source = `export default function ${base}() { return (\n${(await next(url, { format: 'jsx' })).source}); }`;
return {
@@ -31,6 +31,7 @@ export async function load(url, ctx, next) {
source,
};
}
+export { loadSVGX as load }
/**
* Convert a string to quasi-PascalCase.
diff --git a/packages/svgx/svgx.spec.mjs b/packages/svgx/svgx.spec.mjs
new file mode 100644
index 0000000..63237bc
--- /dev/null
+++ b/packages/svgx/svgx.spec.mjs
@@ -0,0 +1,40 @@
+import assert from 'node:assert/strict';
+import { describe, it } from 'node:test';
+
+import { nextLoad } from '../../fixtures/nextLoad.fixture.mjs';
+
+import { load } from './svgx.mjs';
+
+
+describe('SVGX loader', { concurrency: true }, () => {
+ describe('load', () => {
+ it('should ignore files that aren’t SVG', async () => {
+ const result = await load(import.meta.resolve('../../fixtures/fixture.ext'), {}, nextLoad);
+
+ assert.deepEqual(result, {
+ format: 'unknown',
+ source: '',
+ });
+ });
+
+ it('should transpile the SVG to a JSX module', async () => {
+ const fileUrl = import.meta.resolve('./fixture.svg');
+ const result = await load(fileUrl, { format: 'jsx' }, nextLoad);
+
+ const { source } = await nextLoad(fileUrl, { format: 'jsx' });
+
+ assert.equal(result.format, 'module');
+ assert.equal(result.source, `export default function Fixture() { return (\n${source}); }`);
+ });
+
+ it('should throw a helpful error when a valid component name cannot be derived', async () => {
+ const fileUrl = import.meta.resolve('./fixtur$e.svg');
+
+ const { message } = await load(fileUrl, { format: 'jsx' }, nextLoad).catch((err) => err);
+
+ assert.match(message, /component name/);
+ assert.match(message, /fixtur\$e/);
+ assert.match(message, /illegal/);
+ });
+ });
+});
diff --git a/packages/text/README.md b/packages/text/README.md
new file mode 100644
index 0000000..74addc0
--- /dev/null
+++ b/packages/text/README.md
@@ -0,0 +1,19 @@
+# Nodejs Loaders: Text
+
+
+
+[![npm version](https://img.shields.io/npm/v/nodejs-loaders/text.svg)](https://www.npmjs.com/package/nodejs-loaders/text)
+![size](https://img.shields.io/github/languages/code-size/JakobJingleheimer/nodejs-loaders/text)
+
+**Environment**: test
+
+This loader handles files that are effectively plain text.
+
+
+Supported file extensions
+
+* `.graphql`
+* `.gql`
+* `.md`
+* `.txt`
+
diff --git a/fixture.gql b/packages/text/fixture.gql
similarity index 100%
rename from fixture.gql
rename to packages/text/fixture.gql
diff --git a/fixture.graphql b/packages/text/fixture.graphql
similarity index 100%
rename from fixture.graphql
rename to packages/text/fixture.graphql
diff --git a/fixture.md b/packages/text/fixture.md
similarity index 100%
rename from fixture.md
rename to packages/text/fixture.md
diff --git a/fixture.txt b/packages/text/fixture.txt
similarity index 100%
rename from fixture.txt
rename to packages/text/fixture.txt
diff --git a/packages/text/package.json b/packages/text/package.json
new file mode 100644
index 0000000..c620b67
--- /dev/null
+++ b/packages/text/package.json
@@ -0,0 +1,20 @@
+{
+ "version": "1.0.0",
+ "name": "@nodejs-loaders/text",
+ "type": "module",
+ "author": "Jacob Smith",
+ "maintainers": [
+ "Augustin Mauroy"
+ ],
+ "license": "ISC",
+ "main": "./text.mjs",
+ "keywords": [
+ "customisation hook",
+ "loader",
+ "node.js",
+ "plain text"
+ ],
+ "dependencies": {
+ "@nodejs-loaders/parse-filename": "1.0.0"
+ }
+}
diff --git a/text.mjs b/packages/text/text.mjs
similarity index 71%
rename from text.mjs
rename to packages/text/text.mjs
index 707b332..2aa2030 100644
--- a/text.mjs
+++ b/packages/text/text.mjs
@@ -1,7 +1,7 @@
-import { getFilenameExt } from './parse-filename.mjs';
+import { getFilenameExt } from '@nodejs-loaders/parse-filename';
-export async function resolve(specifier, ctx, nextResolve) {
+async function resolveText(specifier, ctx, nextResolve) {
const nextResult = await nextResolve(specifier);
const format = exts[getFilenameExt(nextResult.url)];
@@ -14,8 +14,9 @@ export async function resolve(specifier, ctx, nextResolve) {
url: nextResult.url,
};
}
+export { resolveText as resolve }
-export async function load(url, ctx, nextLoad) {
+async function loadText(url, ctx, nextLoad) {
const nextResult = await nextLoad(url, ctx);
if (!formats.has(ctx.format)) return nextResult;
@@ -27,6 +28,7 @@ export async function load(url, ctx, nextLoad) {
source,
};
}
+export { loadText as load }
export const exts = {
'.graphql': 'graphql',
diff --git a/text.spec.mjs b/packages/text/text.spec.mjs
similarity index 79%
rename from text.spec.mjs
rename to packages/text/text.spec.mjs
index 67ced14..63532dd 100644
--- a/text.spec.mjs
+++ b/packages/text/text.spec.mjs
@@ -1,9 +1,9 @@
import assert from 'node:assert/strict';
import { describe, it } from 'node:test';
-import { assertSuffixedSpecifiers } from './assert-suffixed-specifiers.fixture.mjs';
-import { nextLoad } from './nextLoad.fixture.mjs';
-import { nextResolve } from './nextResolve.fixture.mjs';
+import { assertSuffixedSpecifiers } from '../../fixtures/assert-suffixed-specifiers.fixture.mjs';
+import { nextLoad } from '../../fixtures/nextLoad.fixture.mjs';
+import { nextResolve } from '../../fixtures/nextResolve.fixture.mjs';
import { exts, load, resolve } from './text.mjs';
@@ -40,7 +40,7 @@ describe('text loader', { concurrency: true }, () => {
describe('load', () => {
it('should ignore files that aren’t text', async () => {
- const result = await load('./fixture.ext', {}, nextLoad);
+ const result = await load(import.meta.resolve('../../fixtures/fixture.ext'), {}, nextLoad);
assert.deepEqual(result, {
format: 'unknown',
@@ -50,7 +50,7 @@ describe('text loader', { concurrency: true }, () => {
it('should generate a module from the text file', async () => {
for (const ext of Object.keys(exts)) {
- const fileUrl = `./fixture${ext}`;
+ const fileUrl = import.meta.resolve(`./fixture${ext}`);
const result = await load(fileUrl, { format: 'graphql' }, nextLoad);
const { source } = await nextLoad(fileUrl, { format: 'graphql' });
diff --git a/packages/tsx/README.md b/packages/tsx/README.md
new file mode 100644
index 0000000..eaaab38
--- /dev/null
+++ b/packages/tsx/README.md
@@ -0,0 +1,42 @@
+# Nodejs Loaders: JSX / TSX
+
+
+
+[![npm version](https://img.shields.io/npm/v/nodejs-loaders/tsx.svg)](https://www.npmjs.com/package/nodejs-loaders/tsx)
+![size](https://img.shields.io/github/languages/code-size/JakobJingleheimer/nodejs-loaders/tsx)
+
+**Environment**: test
+
+> [!TIP]
+> If you are using _only_ TypeScript (**not** tsx), consider using Node.js's builtin [type stripping](https://nodejs.org/api/typescript.html#type-stripping). This _can_ handle it, but the builtin may provide better/more consisent results.
+
+This loader facilitates running tests against JSX or TSX components. It looks for a `esbuild.config.mjs` in the project root (the current working directory); if your config lives in a different location, create a symlink to it from your project root. Only options for [esbuild's "transform" API](https://esbuild.github.io/api/#transform) are valid (esbuild handles looking for a tsconfig). When none is found, it uses a few necessary default.
+
+This loader does _not_ handle TypeScript's file extension nonsense. Import specifiers must use the actual file extension of the file actually on disk:
+
+```
+./
+ ├ …
+ └ foo.ts
+```
+
+💥 `import foo from './foo.js';`
+✅ `import foo from './foo.ts';`
+
+If your project contains erroneous specifiers like above, use the [correct-ts-specifiers](https://github.com/JakobJingleheimer/correct-ts-specifiers) codemod to fix your source-code.
+
+
+Supported file extensions
+
+* `.jsx`
+* `.mts`
+* `.ts`
+* `.tsx`
+
+
+## Alternatives
+
+* [Node's built-in support](https://nodejs.org/api/typescript.html) (via [amaro](https://github.com/nodejs/amaro)). `@nodejs-loader/tsx` currently supports more than Amaro.
+* [SWC register](https://github.com/swc-project/swc-node/tree/master/packages/register#swc-noderegister)
+* [ts-node](https://typestrong.org/ts-node/docs/), it's bigger because they support old node versions. It's also based on typescript.
+* [tsx](https://github.com/privatenumber/tsx), it's bigger because they inlude more features such as a cli. It's also based on esbuild.
diff --git a/packages/tsx/esbuild.config.mjs b/packages/tsx/esbuild.config.mjs
new file mode 120000
index 0000000..48dd409
--- /dev/null
+++ b/packages/tsx/esbuild.config.mjs
@@ -0,0 +1 @@
+../../fixtures/esbuild.config.mjs
\ No newline at end of file
diff --git a/packages/tsx/find-esbuild-config.mjs b/packages/tsx/find-esbuild-config.mjs
new file mode 100644
index 0000000..bc25081
--- /dev/null
+++ b/packages/tsx/find-esbuild-config.mjs
@@ -0,0 +1,34 @@
+import { createRequire, findPackageJSON } from 'node:module';
+import process from 'node:process';
+import { fileURLToPath } from 'node:url';
+
+
+// This config must contain options that are compatible with esbuild's `transform` API.
+let esbuildConfig;
+
+/**
+ * @param {URL['href']} parentURL
+ */
+export function findEsbuildConfig(parentURL) {
+ if (esbuildConfig != null) return esbuildConfig;
+
+ const esBuildConfigLocus = findPackageJSON(parentURL)
+ ?.replace('package.json', 'esbuild.config.mjs');
+
+ const req = createRequire(fileURLToPath(parentURL));
+ try {
+ esbuildConfig = req(esBuildConfigLocus)?.default;
+ } catch (err) {
+ if (err.code !== 'ENOENT') throw err;
+
+ process.emitWarning('No esbuild config found in project root. Using default config.')
+ }
+
+ return esbuildConfig = Object.assign({
+ jsx: 'automatic',
+ jsxDev: true,
+ jsxFactory: 'React.createElement',
+ loader: 'tsx',
+ minify: true,
+ }, esbuildConfig);
+}
diff --git a/packages/tsx/find-esbuild-config.spec.mjs b/packages/tsx/find-esbuild-config.spec.mjs
new file mode 100644
index 0000000..0508702
--- /dev/null
+++ b/packages/tsx/find-esbuild-config.spec.mjs
@@ -0,0 +1,48 @@
+import assert from 'node:assert/strict';
+import {
+ before,
+ describe,
+ it,
+ mock,
+} from 'node:test';
+
+
+describe('finding an ESbuild config', () => {
+ /** @type {MockFunctionContext} */
+ let mock_emitWarning;
+ /** @type {MockFunctionContext} */
+ let mock_createRequire;
+ /** @type {MockFunctionContext} */
+ let mock_findPackageJSON;
+ /** @type import('./find-esbuild-config.mjs').findEsbuildConfig */
+ let findEsbuildConfig;
+
+ before(async () => {
+ const emitWarning = mock.fn();
+ mock_emitWarning = emitWarning.mock;
+ const findPackageJSON = mock.fn();
+ mock_findPackageJSON = findPackageJSON.mock;
+ const createRequire = mock.fn();
+ mock_createRequire = createRequire.mock;
+
+ mock.module('node:process', { namedExports: { emitWarning }});
+ mock.module('node:module', { namedExports: {
+ createRequire,
+ findPackageJSON,
+ }});
+
+ ({ findEsbuildConfig } = await import('./find-esbuild-config.mjs'));
+ });
+
+ it('should warn when no config is found', () => {
+ mock_createRequire.mockImplementationOnce(() => function mock_require() { throw { code: 'ENOENT' } });
+
+ findEsbuildConfig(import.meta.url);
+
+ assert.equal(mock_emitWarning.callCount(), 1);
+ const msg = mock_emitWarning.calls[0].arguments[0];
+ assert.match(msg, /found/);
+ assert.match(msg, /root/);
+ assert.match(msg, /default config/);
+ });
+});
diff --git a/fixture.jsx b/packages/tsx/fixture.jsx
similarity index 100%
rename from fixture.jsx
rename to packages/tsx/fixture.jsx
diff --git a/fixture.tsx b/packages/tsx/fixture.tsx
similarity index 100%
rename from fixture.tsx
rename to packages/tsx/fixture.tsx
diff --git a/packages/tsx/package.json b/packages/tsx/package.json
new file mode 100644
index 0000000..b5c84f7
--- /dev/null
+++ b/packages/tsx/package.json
@@ -0,0 +1,26 @@
+{
+ "version": "1.0.0",
+ "name": "@nodejs-loaders/tsx",
+ "type": "module",
+ "author": "Jacob Smith",
+ "maintainers": [
+ "Augustin Mauroy"
+ ],
+ "license": "ISC",
+ "main": "./tsx.mjs",
+ "keywords": [
+ "customisation hook",
+ "loader",
+ "node.js",
+ "typescript"
+ ],
+ "engines": {
+ "node": "^20.19 || ^22.12 || ^23.2"
+ },
+ "dependencies": {
+ "@nodejs-loaders/parse-filename": "1.0.0"
+ },
+ "peerDependencies": {
+ "esbuild": "~0.19.5"
+ }
+}
diff --git a/tsx.mjs b/packages/tsx/tsx.mjs
similarity index 60%
rename from tsx.mjs
rename to packages/tsx/tsx.mjs
index 49baf62..4b9d9e4 100644
--- a/tsx.mjs
+++ b/packages/tsx/tsx.mjs
@@ -1,18 +1,11 @@
-import path from 'node:path';
-import { pathToFileURL } from 'node:url';
-
import { transform } from 'esbuild';
-import { getFilenameExt } from './parse-filename.mjs';
-
+import { getFilenameExt } from '@nodejs-loaders/parse-filename';
-// This config must contain options that are compatible with esbuild's `transform` API.
-const esbuildConfig = await import(pathToFileURL(path.resolve('esbuild.config.mjs')).href)
- .then(({ default: config }) => config)
- .catch(() => process.emitWarning('No esbuild config found in project root. Using default config.'));
+import { findEsbuildConfig } from './find-esbuild-config.mjs';
-export async function resolve(specifier, ctx, nextResolve) {
+async function resolveTSX(specifier, ctx, nextResolve) {
const nextResult = await nextResolve(specifier);
// Check against the fully resolved URL, not just the specifier, in case another loader has
// something to contribute to the resolution.
@@ -30,17 +23,20 @@ export async function resolve(specifier, ctx, nextResolve) {
return nextResult;
}
+export { resolveTSX as resolve }
-export async function load(url, ctx, nextLoad) {
+async function loadTSX(url, ctx, nextLoad) {
if (!formats.has(ctx.format)) return nextLoad(url); // not j|tsx
const format = 'module';
const nextResult = await nextLoad(url, { format });
let rawSource = ''+nextResult.source; // byte array → string
- if (config.jsx === 'transform') rawSource = `import * as React from 'react';\n${rawSource}`;
+ const esbuildConfig = findEsbuildConfig(ctx.parentURL);
- const { code: source, warnings } = await transform(rawSource, config)
+ if (esbuildConfig.jsx === 'transform') rawSource = `import * as React from 'react';\n${rawSource}`;
+
+ const { code: source, warnings } = await transform(rawSource, esbuildConfig)
.catch(({ errors }) => {
for (const {
location: { column, line, lineText },
@@ -59,15 +55,7 @@ export async function load(url, ctx, nextLoad) {
source,
};
}
-
-const config = {
- jsx: 'automatic',
- jsxDev: true,
- jsxFactory: 'React.createElement',
- loader: 'tsx',
- minify: true,
- ...esbuildConfig,
-};
+export { loadTSX as load }
export const jsxExts = new Set([
'.jsx',
diff --git a/tsx.spec.mjs b/packages/tsx/tsx.spec.mjs
similarity index 61%
rename from tsx.spec.mjs
rename to packages/tsx/tsx.spec.mjs
index bf485fa..e8e9b01 100644
--- a/tsx.spec.mjs
+++ b/packages/tsx/tsx.spec.mjs
@@ -1,9 +1,9 @@
import assert from 'node:assert/strict';
-import { describe, it } from 'node:test';
+import { describe, it, mock } from 'node:test';
-import { assertSuffixedSpecifiers } from './assert-suffixed-specifiers.fixture.mjs';
-import { nextLoad } from './nextLoad.fixture.mjs';
-import { nextResolve } from './nextResolve.fixture.mjs';
+import { assertSuffixedSpecifiers } from '../../fixtures/assert-suffixed-specifiers.fixture.mjs';
+import { nextLoad } from '../../fixtures/nextLoad.fixture.mjs';
+import { nextResolve } from '../../fixtures/nextResolve.fixture.mjs';
import { jsxExts, tsxExts, load, resolve } from './tsx.mjs';
@@ -21,7 +21,7 @@ describe('JSX & TypeScript loader', { concurrency: true }, () => {
it('should recognise JSX files', async () => {
for (const ext of jsxExts) {
- const fileUrl = `./fixture${ext}`;
+ const fileUrl = import.meta.resolve(`./fixture${ext}`);
const result = await resolve(fileUrl, {}, nextResolve);
assert.deepEqual(result, {
@@ -33,7 +33,7 @@ describe('JSX & TypeScript loader', { concurrency: true }, () => {
it('should recognise TypeScript files', async () => {
for (const ext of tsxExts) {
- const fileUrl = `./fixture${ext}`;
+ const fileUrl = import.meta.resolve(`./fixture${ext}`);
const result = await resolve(fileUrl, {}, nextResolve);
assert.deepEqual(result, {
@@ -50,8 +50,10 @@ describe('JSX & TypeScript loader', { concurrency: true }, () => {
});
describe('load', () => {
+ const parentURL = import.meta.url;
+
it('should ignore files that aren’t J|TSX', async () => {
- const result = await load('./fixture.ext', {}, nextLoad);
+ const result = await load(import.meta.resolve('../../fixtures/fixture.ext'), {}, nextLoad);
assert.deepEqual(result, {
format: 'unknown',
@@ -78,19 +80,42 @@ describe('JSX & TypeScript loader', { concurrency: true }, () => {
].join('\n');
it('should transpile JSX', async () => {
- const fileUrl = './fixture.jsx';
- const result = await load(fileUrl, { format: 'jsx' }, nextLoad);
+ const fileUrl = import.meta.resolve('./fixture.jsx');
+ const result = await load(fileUrl, { format: 'jsx', parentURL }, nextLoad);
assert.equal(result.format, 'module');
assert.equal(result.source, transpiled);
});
it('should transpile TSX', async () => {
- const fileUrl = './fixture.tsx';
- const result = await load(fileUrl, { format: 'tsx' }, nextLoad);
+ const fileUrl = import.meta.resolve('./fixture.tsx');
+ const result = await load(fileUrl, { format: 'tsx', parentURL }, nextLoad);
assert.equal(result.format, 'module');
assert.equal(result.source, transpiled);
});
+
+ it('should log transpile errors', async () => {
+ const badJSX = `const Foo (a) => ()`; // missing `=`
+ const orig_consoleError = console.error;
+
+ const consoleErr = globalThis.console.error = mock.fn();
+
+ await load(
+ 'whatever.tsx',
+ {
+ format: 'tsx',
+ parentURL: import.meta.url,
+ },
+ async () => ({ source: badJSX }),
+ );
+
+ const errLog = consoleErr.mock.calls[0].arguments[0];
+
+ assert.match(errLog, /TranspileError/);
+ assert.match(errLog, /found "\("/);
+
+ globalThis.console.error = orig_consoleError;
+ });
});
});
diff --git a/svgx.spec.mjs b/svgx.spec.mjs
deleted file mode 100644
index b847a24..0000000
--- a/svgx.spec.mjs
+++ /dev/null
@@ -1,31 +0,0 @@
-import assert from 'node:assert/strict';
-import { pathToFileURL } from 'node:url';
-import { describe, it } from 'node:test';
-
-import { nextLoad } from './nextLoad.fixture.mjs';
-
-import { load } from './svgx.mjs';
-
-
-describe('SVGX loader', { concurrency: true }, () => {
- describe('load', () => {
- it('should ignore files that aren’t SVG', async () => {
- const result = await load(pathToFileURL('./fixture.ext'), {}, nextLoad);
-
- assert.deepEqual(result, {
- format: 'unknown',
- source: '',
- });
- });
-
- it('should transpile the SVG to a JSX moduele', async () => {
- const fileUrl = pathToFileURL('./fixture.svg');
- const result = await load(fileUrl, { format: 'jsx' }, nextLoad);
-
- const { source } = await nextLoad(fileUrl, { format: 'jsx' });
-
- assert.equal(result.format, 'module');
- assert.equal(result.source, `export default function Fixture() { return (\n${source}); }`);
- });
- });
-});