diff --git a/README.md b/README.md index 4466038..c0452b5 100644 --- a/README.md +++ b/README.md @@ -39,6 +39,10 @@ Output files are expected to have the `.js` extension. AVA searches your entire project for `*.js`, `*.cjs`, `*.mjs` and `*.ts` files (or other extensions you've configured). It will ignore such files found in the `rewritePaths` targets (e.g. `build/`). If you use more specific paths, for instance `build/main/`, you may need to change AVA's `files` configuration to ignore other directories. +## ES Modules + +When used with AVA 4, if your `package.json` has configured `"type": "module"`, or you've configured AVA to treat the `js` extension as `module`, then `@ava/typescript` will import the output file as an ES module. Note that this is based on the *output file*, not the `ts` extension. + ## Add additional extensions You can configure AVA to recognize additional file extensions. To add (partial†) JSX support: diff --git a/index.js b/index.js index c3c66c0..d27d1b0 100644 --- a/index.js +++ b/index.js @@ -1,5 +1,6 @@ import fs from 'node:fs'; import path from 'node:path'; +import {pathToFileURL} from 'node:url'; import escapeStringRegexp from 'escape-string-regexp'; import execa from 'execa'; @@ -149,6 +150,7 @@ export default function typescriptProvider({negotiateProtocol}) { }, worker({extensionsToLoadAsModules, state: {extensions, rewritePaths}}) { + const useImport = extensionsToLoadAsModules.includes('js'); const testFileExtension = new RegExp(`\\.(${extensions.map(ext => escapeStringRegexp(ext)).join('|')})$`); return { @@ -157,16 +159,10 @@ export default function typescriptProvider({negotiateProtocol}) { }, async load(ref, {requireFn}) { - for (const extension of extensionsToLoadAsModules) { - if (ref.endsWith(`.${extension}`)) { - throw new Error('@ava/typescript cannot yet load ESM files'); - } - } - const [from, to] = rewritePaths.find(([from]) => ref.startsWith(from)); // TODO: Support JSX preserve mode — https://www.typescriptlang.org/docs/handbook/jsx.html const rewritten = `${to}${ref.slice(from.length)}`.replace(testFileExtension, '.js'); - return requireFn(rewritten); + return useImport ? import(pathToFileURL(rewritten)) : requireFn(rewritten); // eslint-disable-line node/no-unsupported-features/es-syntax }, }; }, diff --git a/package.json b/package.json index c064fcb..35fb797 100644 --- a/package.json +++ b/package.json @@ -44,6 +44,10 @@ "files": [ "!test/broken-fixtures/**" ], + "ignoredByWatcher": [ + "test/fixtures/**", + "test/broken-fixtures/**" + ], "timeout": "60s" }, "xo": { diff --git a/test/compilation.js b/test/compilation.js index dddb2da..6cf9257 100644 --- a/test/compilation.js +++ b/test/compilation.js @@ -30,7 +30,7 @@ test('worker(): load rewritten paths files', withProvider, async (t, provider) = const {state} = await compile(provider); const {stdout, stderr} = await execa.node( path.join(__dirname, 'fixtures/install-and-load'), - [JSON.stringify(state), path.join(__dirname, 'fixtures/ts', 'file.ts')], + [JSON.stringify({state}), path.join(__dirname, 'fixtures/ts', 'file.ts')], {cwd: path.join(__dirname, 'fixtures')}, ); if (stderr.length > 0) { @@ -44,7 +44,7 @@ test('worker(): runs compiled files', withProvider, async (t, provider) => { const {state} = await compile(provider); const {stdout, stderr} = await execa.node( path.join(__dirname, 'fixtures/install-and-load'), - [JSON.stringify(state), path.join(__dirname, 'fixtures/compiled', 'index.ts')], + [JSON.stringify({state}), path.join(__dirname, 'fixtures/compiled', 'index.ts')], {cwd: path.join(__dirname, 'fixtures')}, ); if (stderr.length > 0) { diff --git a/test/esm.js b/test/esm.js new file mode 100644 index 0000000..4d9d5e3 --- /dev/null +++ b/test/esm.js @@ -0,0 +1,33 @@ +import path from 'node:path'; +import {fileURLToPath} from 'node:url'; +import test from 'ava'; +import execa from 'execa'; +import createProviderMacro from './_with-provider.js'; + +const __dirname = path.dirname(fileURLToPath(import.meta.url)); +const withProvider = createProviderMacro('ava-3.2', '3.2.0', path.join(__dirname, 'fixtures')); + +const setup = async provider => ({ + state: await provider.main({ + config: { + rewritePaths: { + 'esm/': 'esm/', + }, + compile: false, + }, + }).compile(), +}); + +test('worker(): import ESM', withProvider, async (t, provider) => { + const {state} = await setup(provider); + const {stdout, stderr} = await execa.node( + path.join(__dirname, 'fixtures/install-and-load'), + [JSON.stringify({extensionsToLoadAsModules: ['js'], state}), path.join(__dirname, 'fixtures/esm', 'index.ts')], + {cwd: path.join(__dirname, 'fixtures')}, + ); + if (stderr.length > 0) { + t.log(stderr); + } + + t.snapshot(stdout); +}); diff --git a/test/fixtures/esm/index.js b/test/fixtures/esm/index.js new file mode 100644 index 0000000..b8a2e5c --- /dev/null +++ b/test/fixtures/esm/index.js @@ -0,0 +1 @@ +console.log('logged in fixtures/esm/index.js'); diff --git a/test/fixtures/esm/index.ts b/test/fixtures/esm/index.ts new file mode 100644 index 0000000..ed203aa --- /dev/null +++ b/test/fixtures/esm/index.ts @@ -0,0 +1 @@ +console.log('logged in fixtures/esm/index.ts'); diff --git a/test/fixtures/esm/tsconfig.json b/test/fixtures/esm/tsconfig.json new file mode 100644 index 0000000..41da438 --- /dev/null +++ b/test/fixtures/esm/tsconfig.json @@ -0,0 +1,8 @@ +{ + "compilerOptions": { + "outDir": "compiled" + }, + "include": [ + "." + ] +} diff --git a/test/fixtures/install-and-load.js b/test/fixtures/install-and-load.js index 08217bb..c92a633 100644 --- a/test/fixtures/install-and-load.js +++ b/test/fixtures/install-and-load.js @@ -14,7 +14,8 @@ const provider = makeProvider({ const worker = provider.worker({ extensionsToLoadAsModules: [], - state: JSON.parse(process.argv[2]), + state: {}, + ...JSON.parse(process.argv[2]), }); const ref = path.resolve(process.argv[3]); diff --git a/test/snapshots/esm.js.md b/test/snapshots/esm.js.md new file mode 100644 index 0000000..1642bed --- /dev/null +++ b/test/snapshots/esm.js.md @@ -0,0 +1,11 @@ +# Snapshot report for `test/esm.js` + +The actual snapshot is saved in `esm.js.snap`. + +Generated by [AVA](https://avajs.dev). + +## worker(): import ESM + +> Snapshot 1 + + 'logged in fixtures/esm/index.js' diff --git a/test/snapshots/esm.js.snap b/test/snapshots/esm.js.snap new file mode 100644 index 0000000..d31fee5 Binary files /dev/null and b/test/snapshots/esm.js.snap differ