Skip to content

Commit 50f3556

Browse files
committed
add isESM helper
1 parent 80a6d7e commit 50f3556

File tree

2 files changed

+86
-0
lines changed

2 files changed

+86
-0
lines changed

packages/nextjs/src/utils/isESM.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
/**
2+
* Determine if the given source code represents a file written using ES6 modules.
3+
*
4+
* The regexes used are from https://github.com/component/is-module, which got them from
5+
* https://github.com/formatjs/js-module-formats, which says it got them from
6+
* https://github.com/ModuleLoader/es-module-loader, though the originals are now nowhere to be found.
7+
*
8+
* @param moduleSource The source code of the module
9+
* @returns True if the module contains ESM-patterned code, false otherwise.
10+
*/
11+
export function isESM(moduleSource: string): boolean {
12+
const importExportRegex =
13+
/(?:^\s*|[}{();,\n]\s*)(import\s+['"]|(import|module)\s+[^"'()\n;]+\s+from\s+['"]|export\s+(\*|\{|default|function|var|const|let|[_$a-zA-Z\xA0-\uFFFF][_$a-zA-Z0-9\xA0-\uFFFF]*))/;
14+
const exportStarRegex = /(?:^\s*|[}{();,\n]\s*)(export\s*\*\s*from\s*(?:'([^']+)'|"([^"]+)"))/;
15+
16+
return importExportRegex.test(moduleSource) || exportStarRegex.test(moduleSource);
17+
}
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
import { isESM } from '../../src/utils/isESM';
2+
3+
// Based on https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/import
4+
describe('import syntax', function () {
5+
it('recognizes import syntax', function () {
6+
expect(isESM("import dogs from 'dogs';")).toBe(true);
7+
expect(isESM("import * as dogs from 'dogs';")).toBe(true);
8+
expect(isESM("import { maisey } from 'dogs';")).toBe(true);
9+
expect(isESM("import { charlie as goofball } from 'dogs';")).toBe(true);
10+
expect(isESM("import { default as maisey } from 'dogs';")).toBe(true);
11+
expect(isESM("import { charlie, masiey } from 'dogs';")).toBe(true);
12+
expect(isESM("import { masiey, charlie as pickle } from 'dogs';")).toBe(true);
13+
expect(isESM("import charlie, { maisey } from 'dogs';")).toBe(true);
14+
expect(isESM("import maisey, * as dogs from 'dogs';")).toBe(true);
15+
expect(isESM("import 'dogs';")).toBe(true);
16+
});
17+
});
18+
19+
// Based on https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Statements/export
20+
describe('export syntax', function () {
21+
it('recognizes exported declarations', () => {
22+
expect(isESM('export var maisey, charlie;')).toBe(true);
23+
expect(isESM('export let charlie, maisey;')).toBe(true);
24+
expect(isESM("export var maisey = 'silly', charlie = 'goofy';")).toBe(true);
25+
expect(isESM("export let charlie = 'goofy', maisey = 'silly';")).toBe(true);
26+
expect(isESM("export const maisey = 'silly', charlie = 'goofy';")).toBe(true);
27+
expect(isESM('export function doDogStuff() { /* ... */ }')).toBe(true);
28+
expect(isESM('export class Dog { /* ... */ }')).toBe(true);
29+
expect(isESM('export function* generateWayTooManyPhotosOnMyPhone() { /* ... */ }')).toBe(true);
30+
expect(isESM('export const { maisey, charlie } = dogObject;')).toBe(true);
31+
expect(isESM('export const { charlie, masiey: maiseyTheDog } = dogObject;')).toBe(true);
32+
expect(isESM('export const [ maisey, charlie ] = dogArray;')).toBe(true);
33+
});
34+
35+
it('recognizes lists of exports', () => {
36+
expect(isESM('export { maisey, charlie };')).toBe(true);
37+
expect(isESM('export { charlie as charlieMcCharlerson, masiey as theMaiseyMaiseyDog };')).toBe(true);
38+
expect(isESM('export { charlie as default };')).toBe(true);
39+
});
40+
41+
it('recognizes default exports', () => {
42+
expect(isESM("export default 'dogs are great';")).toBe(true);
43+
expect(isESM('export default function doDogStuff() { /* ... */ }')).toBe(true);
44+
expect(isESM('export default class Dog { /* ... */ }')).toBe(true);
45+
expect(isESM('export default function* generateWayTooManyPhotosOnMyPhone() { /* ... */ }')).toBe(true);
46+
expect(isESM('export default function () { /* ... */ }')).toBe(true);
47+
expect(isESM('export default class { /* ... */ }')).toBe(true);
48+
expect(isESM('export default function* () { /* ... */ }')).toBe(true);
49+
});
50+
51+
it('recognizes exports directly from another module', () => {
52+
expect(isESM("export * from 'dogs';")).toBe(true);
53+
expect(isESM("export * as dogs from 'dogs';")).toBe(true);
54+
expect(isESM("export { maisey, charlie } from 'dogs';")).toBe(true);
55+
expect(
56+
isESM("export { maisey as goodGirl, charlie as omgWouldYouJustPeeAlreadyIWantToGoToBed } from 'dogs';"),
57+
).toBe(true);
58+
expect(isESM("export { default } from 'dogs';")).toBe(true);
59+
expect(isESM("export { default, maisey } from 'dogs';")).toBe(true);
60+
});
61+
});
62+
63+
describe('potential false positives', () => {
64+
it("doesn't get fooled by look-alikes", () => {
65+
expect(isESM("'this is an import statement'")).toBe(false);
66+
expect(isESM("'this is an export statement'")).toBe(false);
67+
expect(isESM('import(dogs)')).toBe(false);
68+
});
69+
});

0 commit comments

Comments
 (0)