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}); }`); - }); - }); -});