@knighted/module 1.0.0-alpha.1 → 1.0.0-alpha.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -55,7 +55,7 @@ await transform('./file.js', {
55
55
 
56
56
  Which produces
57
57
 
58
- **file.js**
58
+ **file.cjs**
59
59
 
60
60
  ```js
61
61
  const { argv } = require('node:process')
@@ -87,10 +87,15 @@ type ModuleOptions = {
87
87
  /* What module system to convert to. */
88
88
  type?: 'module' | 'commonjs'
89
89
  /* Whether import/export and require/exports should be transformed. */
90
- moduleLoading?: boolean
90
+ modules?: boolean
91
91
  /* Whether to change specifier extensions to the assigned value. If omitted they are left alone. */
92
- specifiers?: '.js' | '.mjs' | '.cjs' | '.ts' | '.mts' | '.cts'
93
- /* What filepath to write the transformed source to. If omitted the transformed source is returned. */
92
+ specifier?: '.js' | '.mjs' | '.cjs' | '.ts' | '.mts' | '.cts'
93
+ /* What filepath to write the transformed source to. */
94
94
  out?: string
95
95
  }
96
96
  ```
97
+
98
+ ## Roadmap
99
+
100
+ - Support option `modules`.
101
+ - Remove `@knighted/specifier` and avoid double parsing.
@@ -0,0 +1,5 @@
1
+ import MagicString from 'magic-string';
2
+ import type { NodePath } from '@babel/traverse';
3
+ import type { ExpressionStatement } from '@babel/types';
4
+ import type { FormatterOptions } from '../types.cjs';
5
+ export declare const expressionStatement: (nodePath: NodePath<ExpressionStatement>, src: MagicString, options: FormatterOptions) => void;
@@ -6,82 +6,34 @@ Object.defineProperty(exports, "__esModule", {
6
6
  exports.format = void 0;
7
7
  var _magicString = _interopRequireDefault(require("magic-string"));
8
8
  var _traverse2 = _interopRequireDefault(require("@babel/traverse"));
9
+ var _nodeModuleType = require("node-module-type");
10
+ var _identifier = require("./formatters/identifier.cjs");
11
+ var _metaProperty = require("./formatters/metaProperty.cjs");
12
+ var _memberExpression = require("./formatters/memberExpression.cjs");
9
13
  function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
10
- const traverse = _traverse2.default.default;
14
+ /**
15
+ * Runtime hack to prevent issues with babel's default interop while dual building with tsc.
16
+ * @see https://github.com/babel/babel/discussions/13093#discussioncomment-12705927
17
+ * Temporary fix until I switch to oxc-parser.
18
+ */
19
+ const type = (0, _nodeModuleType.moduleType)();
20
+ const traverse = typeof _traverse2.default === 'function' || type === 'commonjs' ? _traverse2.default : _traverse2.default.default;
21
+
22
+ /**
23
+ * Note, there is no specific conversion for `import.meta.main` as it does not exist.
24
+ * @see https://github.com/nodejs/node/issues/49440
25
+ */
11
26
  const format = (code, ast, options) => {
12
27
  const src = new _magicString.default(code);
13
- const {
14
- type = 'commonjs'
15
- } = options;
16
28
  traverse(ast, {
17
- MetaProperty(metaPropertyPath) {
18
- if (type === 'commonjs') {
19
- const path = metaPropertyPath.findParent(path => path.isMemberExpression());
20
- if (path) {
21
- const {
22
- node
23
- } = path;
24
- const {
25
- start,
26
- end
27
- } = node;
28
- if (node.type === 'MemberExpression' && node.property.type === 'Identifier' && typeof start == 'number' && typeof end === 'number') {
29
- const name = node.property.name;
30
- switch (name) {
31
- case 'url':
32
- src.update(start, end, 'require("node:url").pathToFileURL(__filename).toString()');
33
- break;
34
- case 'filename':
35
- src.update(start, end, '__filename');
36
- break;
37
- case 'dirname':
38
- src.update(start, end, '__dirname');
39
- break;
40
- case 'resolve':
41
- src.update(start, end, 'require.resolve');
42
- break;
43
- }
44
- }
45
- }
46
- }
29
+ Identifier(path) {
30
+ (0, _identifier.identifier)(path, src, options);
47
31
  },
48
- ExpressionStatement(expressionStatementPath) {
49
- if (type === 'module') {
50
- const {
51
- node
52
- } = expressionStatementPath;
53
- const {
54
- start,
55
- end
56
- } = node;
57
- if (node.expression.type === 'Identifier' && typeof start === 'number' && typeof end === 'number') {
58
- const name = node.expression.name;
59
- switch (name) {
60
- case '__filename':
61
- src.update(start, end, 'import.meta.filename');
62
- break;
63
- case '__dirname':
64
- src.update(start, end, 'import.meta.dirname');
65
- break;
66
- }
67
- }
68
- }
32
+ MetaProperty(path) {
33
+ (0, _metaProperty.metaProperty)(path, src, options);
69
34
  },
70
- MemberExpression(memberExpressionPath) {
71
- if (type === 'module') {
72
- const {
73
- node
74
- } = memberExpressionPath;
75
- const {
76
- start,
77
- end
78
- } = node;
79
-
80
- // Update require.resolve to import.meta.resolve
81
- if (node.object.type === 'Identifier' && node.object.name === 'require' && node.property.type === 'Identifier' && node.property.name == 'resolve' && typeof start === 'number' && typeof end === 'number') {
82
- src.update(start, end, 'import.meta.resolve');
83
- }
84
- }
35
+ MemberExpression(path) {
36
+ (0, _memberExpression.memberExpression)(path, src, options);
85
37
  }
86
38
  });
87
39
  return src;
@@ -1,7 +1,9 @@
1
1
  import MagicString from 'magic-string';
2
2
  import type { ParseResult } from '@babel/parser';
3
3
  import type { File } from '@babel/types';
4
- import type { ModuleOptions } from './types.cjs';
5
- type FormatOptions = Omit<ModuleOptions, 'out'>;
6
- export declare const format: (code: string, ast: ParseResult<File>, options: FormatOptions) => MagicString;
7
- export {};
4
+ import type { FormatterOptions } from './types.cjs';
5
+ /**
6
+ * Note, there is no specific conversion for `import.meta.main` as it does not exist.
7
+ * @see https://github.com/nodejs/node/issues/49440
8
+ */
9
+ export declare const format: (code: string, ast: ParseResult<File>, options: FormatterOptions) => MagicString;
@@ -0,0 +1,68 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.expressionStatement = void 0;
7
+ const expressionStatement = (nodePath, src, options) => {
8
+ if (options.type === 'module') {
9
+ const {
10
+ node
11
+ } = nodePath;
12
+ const {
13
+ start,
14
+ end
15
+ } = node;
16
+ if (typeof start === 'number' && typeof end === 'number') {
17
+ const isMemberExpressionModuleExports = expression => {
18
+ return expression.object.type === 'Identifier' && expression.object.name === 'module' && expression.property.type === 'Identifier' && expression.property.name === 'exports';
19
+ };
20
+ if (node.expression.type === 'Identifier') {
21
+ const name = node.expression.name;
22
+
23
+ // CommonJS globals
24
+ switch (name) {
25
+ case 'module':
26
+ src.update(start, end, 'import.meta');
27
+ break;
28
+ case 'exports':
29
+ src.update(start, end, '{}');
30
+ break;
31
+ case '__filename':
32
+ src.update(start, end, 'import.meta.filename');
33
+ break;
34
+ case '__dirname':
35
+ src.update(start, end, 'import.meta.dirname');
36
+ break;
37
+ }
38
+ }
39
+ if (node.expression.type === 'MemberExpression') {
40
+ const {
41
+ expression
42
+ } = node;
43
+
44
+ // Check for `module.exports` without an assignment
45
+ if (isMemberExpressionModuleExports(expression)) {
46
+ /**
47
+ * @TODO: Should this depend on `options.modules` being enabled?
48
+ * Probably not for the same reason `exports` is converted to an empty object (ReferenceError in ESM).
49
+ * This is a standalone reference to `module.exports` without being part of an AssignmentExpression.
50
+ */
51
+ src.update(start, end, '{}');
52
+ }
53
+ }
54
+
55
+ /*
56
+ if (
57
+ options.modules &&
58
+ node.expression.type === 'AssignmentExpression' &&
59
+ node.expression.left.type === 'MemberExpression' &&
60
+ isMemberExpressionModuleExports(node.expression.left)
61
+ ) {
62
+ // @TODO support `modules` option.
63
+ }
64
+ */
65
+ }
66
+ }
67
+ };
68
+ exports.expressionStatement = expressionStatement;
@@ -0,0 +1,48 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.identifier = void 0;
7
+ const identifier = (nodePath, src, options) => {
8
+ if (options.type === 'module') {
9
+ const {
10
+ node
11
+ } = nodePath;
12
+ const {
13
+ start,
14
+ end
15
+ } = node;
16
+ if (typeof start === 'number' && typeof end === 'number' && node.type === 'Identifier') {
17
+ const {
18
+ name
19
+ } = node;
20
+ const isMemberExpression = Boolean(nodePath.findParent(path => path.isMemberExpression()));
21
+
22
+ // CommonJS globals in expression/statement
23
+ switch (name) {
24
+ case 'module':
25
+ {
26
+ if (!isMemberExpression) {
27
+ src.update(start, end, 'import.meta');
28
+ }
29
+ break;
30
+ }
31
+ case 'exports':
32
+ {
33
+ if (!isMemberExpression) {
34
+ src.update(start, end, '{}');
35
+ }
36
+ break;
37
+ }
38
+ case '__filename':
39
+ src.update(start, end, 'import.meta.filename');
40
+ break;
41
+ case '__dirname':
42
+ src.update(start, end, 'import.meta.dirname');
43
+ break;
44
+ }
45
+ }
46
+ }
47
+ };
48
+ exports.identifier = identifier;
@@ -0,0 +1,40 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.memberExpression = void 0;
7
+ const memberExpression = (nodePath, src, options) => {
8
+ if (options.type === 'module') {
9
+ const {
10
+ node
11
+ } = nodePath;
12
+ const {
13
+ start,
14
+ end
15
+ } = node;
16
+ if (typeof start === 'number' && typeof end === 'number' && node.object.type === 'Identifier' && node.object.name === 'require' && node.property.type === 'Identifier') {
17
+ const {
18
+ name
19
+ } = node.property;
20
+
21
+ // CommonJS properties of `require`
22
+ switch (name) {
23
+ case 'main':
24
+ src.update(start, end, 'import.meta');
25
+ break;
26
+ case 'resolve':
27
+ src.update(start, end, 'import.meta.resolve');
28
+ break;
29
+ case 'cache':
30
+ /**
31
+ * Can of worms here. ¯\_(ツ)_/¯
32
+ * @see https://github.com/nodejs/help/issues/2806
33
+ */
34
+ src.update(start, end, '{}');
35
+ break;
36
+ }
37
+ }
38
+ }
39
+ };
40
+ exports.memberExpression = memberExpression;
@@ -0,0 +1,50 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.metaProperty = void 0;
7
+ const metaProperty = (nodePath, src, options) => {
8
+ if (options.type === 'commonjs') {
9
+ const path = nodePath.findParent(path => path.isMemberExpression());
10
+ if (path) {
11
+ const {
12
+ node
13
+ } = path;
14
+ const {
15
+ start,
16
+ end
17
+ } = node;
18
+ if (node.type === 'MemberExpression' && node.property.type === 'Identifier' && typeof start == 'number' && typeof end === 'number') {
19
+ const name = node.property.name;
20
+ switch (name) {
21
+ case 'url':
22
+ src.update(start, end, 'require("node:url").pathToFileURL(__filename).toString()');
23
+ break;
24
+ case 'filename':
25
+ src.update(start, end, '__filename');
26
+ break;
27
+ case 'dirname':
28
+ src.update(start, end, '__dirname');
29
+ break;
30
+ case 'resolve':
31
+ src.update(start, end, 'require.resolve');
32
+ break;
33
+ }
34
+ }
35
+ } else {
36
+ const {
37
+ node
38
+ } = nodePath;
39
+ const {
40
+ start,
41
+ end
42
+ } = node;
43
+ if (node.property.type === 'Identifier' && node.property.name === 'meta' && typeof start === 'number' && typeof end === 'number') {
44
+ // This is an `import.meta` expression
45
+ src.update(start, end, 'require.main');
46
+ }
47
+ }
48
+ }
49
+ };
50
+ exports.metaProperty = metaProperty;
@@ -0,0 +1,5 @@
1
+ import MagicString from 'magic-string';
2
+ import type { NodePath } from '@babel/traverse';
3
+ import type { Identifier } from '@babel/types';
4
+ import type { FormatterOptions } from '../types.cjs';
5
+ export declare const identifier: (nodePath: NodePath<Identifier>, src: MagicString, options: FormatterOptions) => void;
@@ -0,0 +1,5 @@
1
+ import MagicString from 'magic-string';
2
+ import type { NodePath } from '@babel/traverse';
3
+ import type { MemberExpression } from '@babel/types';
4
+ import type { FormatterOptions } from '../types.cjs';
5
+ export declare const memberExpression: (nodePath: NodePath<MemberExpression>, src: MagicString, options: FormatterOptions) => void;
@@ -0,0 +1,5 @@
1
+ import MagicString from 'magic-string';
2
+ import type { NodePath } from '@babel/traverse';
3
+ import type { MetaProperty } from '@babel/types';
4
+ import type { FormatterOptions } from '../types.cjs';
5
+ export declare const metaProperty: (nodePath: NodePath<MetaProperty>, src: MagicString, options: FormatterOptions) => void;
@@ -6,21 +6,30 @@ Object.defineProperty(exports, "__esModule", {
6
6
  exports.transform = void 0;
7
7
  var _nodePath = require("node:path");
8
8
  var _promises = require("node:fs/promises");
9
+ var _specifier = require("@knighted/specifier");
9
10
  var _parse = require("./parse.cjs");
10
11
  var _format = require("./format.cjs");
11
12
  const defaultOptions = {
12
13
  type: 'commonjs',
13
14
  out: undefined,
14
- moduleLoading: false,
15
- specifiers: undefined
15
+ modules: false,
16
+ specifier: undefined
17
+ };
18
+ const getLangFromExt = filename => {
19
+ const ext = (0, _nodePath.extname)(filename);
20
+ if (/\.js$/.test(ext)) {
21
+ return 'js';
22
+ }
23
+ if (/\.ts$/.test(ext)) {
24
+ return 'ts';
25
+ }
26
+ if (ext === '.tsx') {
27
+ return 'tsx';
28
+ }
29
+ if (ext === '.jsx') {
30
+ return 'jsx';
31
+ }
16
32
  };
17
-
18
- /**
19
- * Transforms a file from one Node.js module system to another based on options.
20
- * Module globals, for example import.meta, __dirname, __filename, etc. are always transformed.
21
- * However, the CommonJS `module` object is not transformed, for instance `module.path`, `module.children`, etc.,
22
- * with the exception of `module.exports` which is converted when `loading` is set to `true`.
23
- */
24
33
  const transform = async (filename, options = defaultOptions) => {
25
34
  const opts = {
26
35
  ...defaultOptions,
@@ -29,6 +38,24 @@ const transform = async (filename, options = defaultOptions) => {
29
38
  const file = (0, _nodePath.resolve)(filename);
30
39
  const code = (await (0, _promises.readFile)(file)).toString();
31
40
  const ast = (0, _parse.parse)(code);
32
- return (0, _format.format)(code, ast, opts).toString();
41
+ let source = (0, _format.format)(code, ast, opts).toString();
42
+ if (options.specifier) {
43
+ const code = await _specifier.specifier.updateSrc(source, getLangFromExt(filename), ({
44
+ value
45
+ }) => {
46
+ // Collapse any BinaryExpression or NewExpression to test for a relative specifier
47
+ const collapsed = value.replace(/['"`+)\s]|new String\(/g, '');
48
+ const relative = /^(?:\.|\.\.)\//;
49
+ if (relative.test(collapsed)) {
50
+ // $2 is for any closing quotation/parens around BE or NE
51
+ return value.replace(/(.+)\.(?:m|c)?(?:j|t)s([)'"`]*)?$/, `$1${options.specifier}$2`);
52
+ }
53
+ });
54
+ source = code;
55
+ }
56
+ if (opts.out) {
57
+ await (0, _promises.writeFile)((0, _nodePath.resolve)(opts.out), source);
58
+ }
59
+ return source;
33
60
  };
34
61
  exports.transform = transform;
@@ -1,9 +1,3 @@
1
1
  import type { ModuleOptions } from './types.cjs';
2
- /**
3
- * Transforms a file from one Node.js module system to another based on options.
4
- * Module globals, for example import.meta, __dirname, __filename, etc. are always transformed.
5
- * However, the CommonJS `module` object is not transformed, for instance `module.path`, `module.children`, etc.,
6
- * with the exception of `module.exports` which is converted when `loading` is set to `true`.
7
- */
8
2
  declare const transform: (filename: string, options?: ModuleOptions) => Promise<string>;
9
3
  export { transform };
@@ -1,6 +1,7 @@
1
1
  export type ModuleOptions = {
2
2
  type?: 'module' | 'commonjs';
3
- moduleLoading?: boolean;
4
- specifiers?: '.js' | '.mjs' | '.cjs' | '.ts' | '.mts' | '.cts';
3
+ modules?: boolean;
4
+ specifier?: '.js' | '.mjs' | '.cjs' | '.ts' | '.mts' | '.cts';
5
5
  out?: string;
6
6
  };
7
+ export type FormatterOptions = Omit<ModuleOptions, 'out'> & Required<Pick<ModuleOptions, 'type'>>;
@@ -0,0 +1,5 @@
1
+ import MagicString from 'magic-string';
2
+ import type { NodePath } from '@babel/traverse';
3
+ import type { ExpressionStatement } from '@babel/types';
4
+ import type { FormatterOptions } from '../types.js';
5
+ export declare const expressionStatement: (nodePath: NodePath<ExpressionStatement>, src: MagicString, options: FormatterOptions) => void;
package/dist/format.d.ts CHANGED
@@ -1,7 +1,9 @@
1
1
  import MagicString from 'magic-string';
2
2
  import type { ParseResult } from '@babel/parser';
3
3
  import type { File } from '@babel/types';
4
- import type { ModuleOptions } from './types.js';
5
- type FormatOptions = Omit<ModuleOptions, 'out'>;
6
- export declare const format: (code: string, ast: ParseResult<File>, options: FormatOptions) => MagicString;
7
- export {};
4
+ import type { FormatterOptions } from './types.js';
5
+ /**
6
+ * Note, there is no specific conversion for `import.meta.main` as it does not exist.
7
+ * @see https://github.com/nodejs/node/issues/49440
8
+ */
9
+ export declare const format: (code: string, ast: ParseResult<File>, options: FormatterOptions) => MagicString;
package/dist/format.js CHANGED
@@ -1,80 +1,33 @@
1
1
  import MagicString from 'magic-string';
2
2
  import _traverse from '@babel/traverse';
3
- const traverse = _traverse.default;
3
+ import { moduleType } from 'node-module-type';
4
+ import { identifier } from './formatters/identifier.js';
5
+ import { metaProperty } from './formatters/metaProperty.js';
6
+ import { memberExpression } from './formatters/memberExpression.js';
7
+
8
+ /**
9
+ * Runtime hack to prevent issues with babel's default interop while dual building with tsc.
10
+ * @see https://github.com/babel/babel/discussions/13093#discussioncomment-12705927
11
+ * Temporary fix until I switch to oxc-parser.
12
+ */
13
+ const type = moduleType();
14
+ const traverse = typeof _traverse === 'function' || type === 'commonjs' ? _traverse : _traverse.default;
15
+
16
+ /**
17
+ * Note, there is no specific conversion for `import.meta.main` as it does not exist.
18
+ * @see https://github.com/nodejs/node/issues/49440
19
+ */
4
20
  export const format = (code, ast, options) => {
5
21
  const src = new MagicString(code);
6
- const {
7
- type = 'commonjs'
8
- } = options;
9
22
  traverse(ast, {
10
- MetaProperty(metaPropertyPath) {
11
- if (type === 'commonjs') {
12
- const path = metaPropertyPath.findParent(path => path.isMemberExpression());
13
- if (path) {
14
- const {
15
- node
16
- } = path;
17
- const {
18
- start,
19
- end
20
- } = node;
21
- if (node.type === 'MemberExpression' && node.property.type === 'Identifier' && typeof start == 'number' && typeof end === 'number') {
22
- const name = node.property.name;
23
- switch (name) {
24
- case 'url':
25
- src.update(start, end, 'require("node:url").pathToFileURL(__filename).toString()');
26
- break;
27
- case 'filename':
28
- src.update(start, end, '__filename');
29
- break;
30
- case 'dirname':
31
- src.update(start, end, '__dirname');
32
- break;
33
- case 'resolve':
34
- src.update(start, end, 'require.resolve');
35
- break;
36
- }
37
- }
38
- }
39
- }
23
+ Identifier(path) {
24
+ identifier(path, src, options);
40
25
  },
41
- ExpressionStatement(expressionStatementPath) {
42
- if (type === 'module') {
43
- const {
44
- node
45
- } = expressionStatementPath;
46
- const {
47
- start,
48
- end
49
- } = node;
50
- if (node.expression.type === 'Identifier' && typeof start === 'number' && typeof end === 'number') {
51
- const name = node.expression.name;
52
- switch (name) {
53
- case '__filename':
54
- src.update(start, end, 'import.meta.filename');
55
- break;
56
- case '__dirname':
57
- src.update(start, end, 'import.meta.dirname');
58
- break;
59
- }
60
- }
61
- }
26
+ MetaProperty(path) {
27
+ metaProperty(path, src, options);
62
28
  },
63
- MemberExpression(memberExpressionPath) {
64
- if (type === 'module') {
65
- const {
66
- node
67
- } = memberExpressionPath;
68
- const {
69
- start,
70
- end
71
- } = node;
72
-
73
- // Update require.resolve to import.meta.resolve
74
- if (node.object.type === 'Identifier' && node.object.name === 'require' && node.property.type === 'Identifier' && node.property.name == 'resolve' && typeof start === 'number' && typeof end === 'number') {
75
- src.update(start, end, 'import.meta.resolve');
76
- }
77
- }
29
+ MemberExpression(path) {
30
+ memberExpression(path, src, options);
78
31
  }
79
32
  });
80
33
  return src;
@@ -0,0 +1,5 @@
1
+ import MagicString from 'magic-string';
2
+ import type { NodePath } from '@babel/traverse';
3
+ import type { ExpressionStatement } from '@babel/types';
4
+ import type { FormatterOptions } from '../types.js';
5
+ export declare const expressionStatement: (nodePath: NodePath<ExpressionStatement>, src: MagicString, options: FormatterOptions) => void;
@@ -0,0 +1,61 @@
1
+ export const expressionStatement = (nodePath, src, options) => {
2
+ if (options.type === 'module') {
3
+ const {
4
+ node
5
+ } = nodePath;
6
+ const {
7
+ start,
8
+ end
9
+ } = node;
10
+ if (typeof start === 'number' && typeof end === 'number') {
11
+ const isMemberExpressionModuleExports = expression => {
12
+ return expression.object.type === 'Identifier' && expression.object.name === 'module' && expression.property.type === 'Identifier' && expression.property.name === 'exports';
13
+ };
14
+ if (node.expression.type === 'Identifier') {
15
+ const name = node.expression.name;
16
+
17
+ // CommonJS globals
18
+ switch (name) {
19
+ case 'module':
20
+ src.update(start, end, 'import.meta');
21
+ break;
22
+ case 'exports':
23
+ src.update(start, end, '{}');
24
+ break;
25
+ case '__filename':
26
+ src.update(start, end, 'import.meta.filename');
27
+ break;
28
+ case '__dirname':
29
+ src.update(start, end, 'import.meta.dirname');
30
+ break;
31
+ }
32
+ }
33
+ if (node.expression.type === 'MemberExpression') {
34
+ const {
35
+ expression
36
+ } = node;
37
+
38
+ // Check for `module.exports` without an assignment
39
+ if (isMemberExpressionModuleExports(expression)) {
40
+ /**
41
+ * @TODO: Should this depend on `options.modules` being enabled?
42
+ * Probably not for the same reason `exports` is converted to an empty object (ReferenceError in ESM).
43
+ * This is a standalone reference to `module.exports` without being part of an AssignmentExpression.
44
+ */
45
+ src.update(start, end, '{}');
46
+ }
47
+ }
48
+
49
+ /*
50
+ if (
51
+ options.modules &&
52
+ node.expression.type === 'AssignmentExpression' &&
53
+ node.expression.left.type === 'MemberExpression' &&
54
+ isMemberExpressionModuleExports(node.expression.left)
55
+ ) {
56
+ // @TODO support `modules` option.
57
+ }
58
+ */
59
+ }
60
+ }
61
+ };
@@ -0,0 +1,5 @@
1
+ import MagicString from 'magic-string';
2
+ import type { NodePath } from '@babel/traverse';
3
+ import type { Identifier } from '@babel/types';
4
+ import type { FormatterOptions } from '../types.js';
5
+ export declare const identifier: (nodePath: NodePath<Identifier>, src: MagicString, options: FormatterOptions) => void;
@@ -0,0 +1,41 @@
1
+ export const identifier = (nodePath, src, options) => {
2
+ if (options.type === 'module') {
3
+ const {
4
+ node
5
+ } = nodePath;
6
+ const {
7
+ start,
8
+ end
9
+ } = node;
10
+ if (typeof start === 'number' && typeof end === 'number' && node.type === 'Identifier') {
11
+ const {
12
+ name
13
+ } = node;
14
+ const isMemberExpression = Boolean(nodePath.findParent(path => path.isMemberExpression()));
15
+
16
+ // CommonJS globals in expression/statement
17
+ switch (name) {
18
+ case 'module':
19
+ {
20
+ if (!isMemberExpression) {
21
+ src.update(start, end, 'import.meta');
22
+ }
23
+ break;
24
+ }
25
+ case 'exports':
26
+ {
27
+ if (!isMemberExpression) {
28
+ src.update(start, end, '{}');
29
+ }
30
+ break;
31
+ }
32
+ case '__filename':
33
+ src.update(start, end, 'import.meta.filename');
34
+ break;
35
+ case '__dirname':
36
+ src.update(start, end, 'import.meta.dirname');
37
+ break;
38
+ }
39
+ }
40
+ }
41
+ };
@@ -0,0 +1,5 @@
1
+ import MagicString from 'magic-string';
2
+ import type { NodePath } from '@babel/traverse';
3
+ import type { MemberExpression } from '@babel/types';
4
+ import type { FormatterOptions } from '../types.js';
5
+ export declare const memberExpression: (nodePath: NodePath<MemberExpression>, src: MagicString, options: FormatterOptions) => void;
@@ -0,0 +1,33 @@
1
+ export const memberExpression = (nodePath, src, options) => {
2
+ if (options.type === 'module') {
3
+ const {
4
+ node
5
+ } = nodePath;
6
+ const {
7
+ start,
8
+ end
9
+ } = node;
10
+ if (typeof start === 'number' && typeof end === 'number' && node.object.type === 'Identifier' && node.object.name === 'require' && node.property.type === 'Identifier') {
11
+ const {
12
+ name
13
+ } = node.property;
14
+
15
+ // CommonJS properties of `require`
16
+ switch (name) {
17
+ case 'main':
18
+ src.update(start, end, 'import.meta');
19
+ break;
20
+ case 'resolve':
21
+ src.update(start, end, 'import.meta.resolve');
22
+ break;
23
+ case 'cache':
24
+ /**
25
+ * Can of worms here. ¯\_(ツ)_/¯
26
+ * @see https://github.com/nodejs/help/issues/2806
27
+ */
28
+ src.update(start, end, '{}');
29
+ break;
30
+ }
31
+ }
32
+ }
33
+ };
@@ -0,0 +1,5 @@
1
+ import MagicString from 'magic-string';
2
+ import type { NodePath } from '@babel/traverse';
3
+ import type { MetaProperty } from '@babel/types';
4
+ import type { FormatterOptions } from '../types.js';
5
+ export declare const metaProperty: (nodePath: NodePath<MetaProperty>, src: MagicString, options: FormatterOptions) => void;
@@ -0,0 +1,43 @@
1
+ export const metaProperty = (nodePath, src, options) => {
2
+ if (options.type === 'commonjs') {
3
+ const path = nodePath.findParent(path => path.isMemberExpression());
4
+ if (path) {
5
+ const {
6
+ node
7
+ } = path;
8
+ const {
9
+ start,
10
+ end
11
+ } = node;
12
+ if (node.type === 'MemberExpression' && node.property.type === 'Identifier' && typeof start == 'number' && typeof end === 'number') {
13
+ const name = node.property.name;
14
+ switch (name) {
15
+ case 'url':
16
+ src.update(start, end, 'require("node:url").pathToFileURL(__filename).toString()');
17
+ break;
18
+ case 'filename':
19
+ src.update(start, end, '__filename');
20
+ break;
21
+ case 'dirname':
22
+ src.update(start, end, '__dirname');
23
+ break;
24
+ case 'resolve':
25
+ src.update(start, end, 'require.resolve');
26
+ break;
27
+ }
28
+ }
29
+ } else {
30
+ const {
31
+ node
32
+ } = nodePath;
33
+ const {
34
+ start,
35
+ end
36
+ } = node;
37
+ if (node.property.type === 'Identifier' && node.property.name === 'meta' && typeof start === 'number' && typeof end === 'number') {
38
+ // This is an `import.meta` expression
39
+ src.update(start, end, 'require.main');
40
+ }
41
+ }
42
+ }
43
+ };
@@ -0,0 +1,5 @@
1
+ import MagicString from 'magic-string';
2
+ import type { NodePath } from '@babel/traverse';
3
+ import type { Identifier } from '@babel/types';
4
+ import type { FormatterOptions } from '../types.js';
5
+ export declare const identifier: (nodePath: NodePath<Identifier>, src: MagicString, options: FormatterOptions) => void;
@@ -0,0 +1,5 @@
1
+ import MagicString from 'magic-string';
2
+ import type { NodePath } from '@babel/traverse';
3
+ import type { MemberExpression } from '@babel/types';
4
+ import type { FormatterOptions } from '../types.js';
5
+ export declare const memberExpression: (nodePath: NodePath<MemberExpression>, src: MagicString, options: FormatterOptions) => void;
@@ -0,0 +1,5 @@
1
+ import MagicString from 'magic-string';
2
+ import type { NodePath } from '@babel/traverse';
3
+ import type { MetaProperty } from '@babel/types';
4
+ import type { FormatterOptions } from '../types.js';
5
+ export declare const metaProperty: (nodePath: NodePath<MetaProperty>, src: MagicString, options: FormatterOptions) => void;
package/dist/module.d.ts CHANGED
@@ -1,9 +1,3 @@
1
1
  import type { ModuleOptions } from './types.js';
2
- /**
3
- * Transforms a file from one Node.js module system to another based on options.
4
- * Module globals, for example import.meta, __dirname, __filename, etc. are always transformed.
5
- * However, the CommonJS `module` object is not transformed, for instance `module.path`, `module.children`, etc.,
6
- * with the exception of `module.exports` which is converted when `loading` is set to `true`.
7
- */
8
2
  declare const transform: (filename: string, options?: ModuleOptions) => Promise<string>;
9
3
  export { transform };
package/dist/module.js CHANGED
@@ -1,20 +1,29 @@
1
- import { resolve } from 'node:path';
2
- import { readFile } from 'node:fs/promises';
1
+ import { resolve, extname } from 'node:path';
2
+ import { readFile, writeFile } from 'node:fs/promises';
3
+ import { specifier } from '@knighted/specifier';
3
4
  import { parse } from './parse.js';
4
5
  import { format } from './format.js';
5
6
  const defaultOptions = {
6
7
  type: 'commonjs',
7
8
  out: undefined,
8
- moduleLoading: false,
9
- specifiers: undefined
9
+ modules: false,
10
+ specifier: undefined
11
+ };
12
+ const getLangFromExt = filename => {
13
+ const ext = extname(filename);
14
+ if (/\.js$/.test(ext)) {
15
+ return 'js';
16
+ }
17
+ if (/\.ts$/.test(ext)) {
18
+ return 'ts';
19
+ }
20
+ if (ext === '.tsx') {
21
+ return 'tsx';
22
+ }
23
+ if (ext === '.jsx') {
24
+ return 'jsx';
25
+ }
10
26
  };
11
-
12
- /**
13
- * Transforms a file from one Node.js module system to another based on options.
14
- * Module globals, for example import.meta, __dirname, __filename, etc. are always transformed.
15
- * However, the CommonJS `module` object is not transformed, for instance `module.path`, `module.children`, etc.,
16
- * with the exception of `module.exports` which is converted when `loading` is set to `true`.
17
- */
18
27
  const transform = async (filename, options = defaultOptions) => {
19
28
  const opts = {
20
29
  ...defaultOptions,
@@ -23,6 +32,24 @@ const transform = async (filename, options = defaultOptions) => {
23
32
  const file = resolve(filename);
24
33
  const code = (await readFile(file)).toString();
25
34
  const ast = parse(code);
26
- return format(code, ast, opts).toString();
35
+ let source = format(code, ast, opts).toString();
36
+ if (options.specifier) {
37
+ const code = await specifier.updateSrc(source, getLangFromExt(filename), ({
38
+ value
39
+ }) => {
40
+ // Collapse any BinaryExpression or NewExpression to test for a relative specifier
41
+ const collapsed = value.replace(/['"`+)\s]|new String\(/g, '');
42
+ const relative = /^(?:\.|\.\.)\//;
43
+ if (relative.test(collapsed)) {
44
+ // $2 is for any closing quotation/parens around BE or NE
45
+ return value.replace(/(.+)\.(?:m|c)?(?:j|t)s([)'"`]*)?$/, `$1${options.specifier}$2`);
46
+ }
47
+ });
48
+ source = code;
49
+ }
50
+ if (opts.out) {
51
+ await writeFile(resolve(opts.out), source);
52
+ }
53
+ return source;
27
54
  };
28
55
  export { transform };
package/dist/types.d.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  export type ModuleOptions = {
2
2
  type?: 'module' | 'commonjs';
3
- moduleLoading?: boolean;
4
- specifiers?: '.js' | '.mjs' | '.cjs' | '.ts' | '.mts' | '.cts';
3
+ modules?: boolean;
4
+ specifier?: '.js' | '.mjs' | '.cjs' | '.ts' | '.mts' | '.cts';
5
5
  out?: string;
6
6
  };
7
+ export type FormatterOptions = Omit<ModuleOptions, 'out'> & Required<Pick<ModuleOptions, 'type'>>;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@knighted/module",
3
- "version": "1.0.0-alpha.1",
4
- "description": "Converts module differences in source files between ES and CommonJS.",
3
+ "version": "1.0.0-alpha.10",
4
+ "description": "Transforms module differences between ES and CommonJS.",
5
5
  "type": "module",
6
6
  "main": "dist/module.js",
7
7
  "exports": {
@@ -33,16 +33,8 @@
33
33
  },
34
34
  "keywords": [
35
35
  "transform",
36
- "es module",
37
- "commonjs",
38
- "module",
39
- "require",
40
- "import.meta",
41
- "__dirname",
42
- "__filename",
43
- "conversion",
44
- "tool",
45
- "build"
36
+ "esm",
37
+ "commonjs"
46
38
  ],
47
39
  "files": [
48
40
  "dist"
@@ -51,31 +43,35 @@
51
43
  "license": "MIT",
52
44
  "repository": {
53
45
  "type": "git",
54
- "url": "https://github.com/knightedcodemonkey/module.git"
46
+ "url": "git+https://github.com/knightedcodemonkey/module.git"
55
47
  },
56
48
  "bugs": {
57
49
  "url": "https://github.com/knightedcodemonkey/module/issues"
58
50
  },
59
51
  "devDependencies": {
60
- "@babel/preset-env": "^7.24.6",
61
- "@babel/preset-typescript": "^7.24.6",
62
- "@babel/types": "^7.24.6",
63
- "@eslint/js": "^9.3.0",
64
- "@types/babel__traverse": "^7.20.6",
65
- "@types/node": "^20.12.12",
66
- "babel-dual-package": "^1.1.3",
67
- "c8": "^9.1.0",
68
- "eslint": "^9.3.0",
69
- "eslint-plugin-n": "^17.7.0",
52
+ "@babel/preset-env": "^7.28.0",
53
+ "@babel/preset-typescript": "^7.27.1",
54
+ "@babel/types": "^7.28.2",
55
+ "@eslint/js": "^9.39.1",
56
+ "@types/babel__traverse": "^7.20.7",
57
+ "@types/node": "^22.13.17",
58
+ "babel-dual-package": "^1.2.3",
59
+ "c8": "^10.1.3",
60
+ "eslint": "^9.39.1",
61
+ "eslint-plugin-n": "^17.23.1",
70
62
  "prettier": "^3.2.5",
71
- "tsx": "^4.11.0",
72
- "typescript": "^5.4.5",
73
- "typescript-eslint": "^8.0.0-alpha.16"
63
+ "tsx": "^4.20.3",
64
+ "typescript": "^5.9.3",
65
+ "typescript-eslint": "^8.48.0"
74
66
  },
75
67
  "dependencies": {
76
- "@babel/parser": "^7.24.6",
77
- "@babel/traverse": "^7.24.6",
78
- "magic-string": "^0.30.10"
68
+ "@babel/parser": "^7.28.0",
69
+ "@babel/traverse": "^7.28.0",
70
+ "@knighted/specifier": "^2.0.7",
71
+ "@knighted/walk": "^1.0.0",
72
+ "magic-string": "^0.30.10",
73
+ "node-module-type": "^1.0.2",
74
+ "oxc-parser": "^0.78.0"
79
75
  },
80
76
  "prettier": {
81
77
  "arrowParens": "avoid",