Socket
Socket
Sign inDemoInstall

@ts-bridge/cli

Package Overview
Dependencies
Maintainers
1
Versions
14
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@ts-bridge/cli - npm Package Compare versions

Comparing version 0.3.0 to 0.4.0

dist/helpers.d.ts

24

CHANGELOG.md

@@ -10,2 +10,23 @@ # Changelog

## [0.4.0]
### Added
- Add transform for default CommonJS imports when targeting ESM ([#19](https://github.com/ts-bridge/ts-bridge/pull/19), [#37](https://github.com/ts-bridge/ts-bridge/pull/37))
- Default CommonJS imports are transformed to use a helper function which
checks if the module has a `__esModule` property, and returns the default
export if it does.
### Changed
- Inline shims instead of importing from `@ts-bridge/shims` ([#35](https://github.com/ts-bridge/ts-bridge/pull/35), [#36](https://github.com/ts-bridge/ts-bridge/pull/36), [#37](https://github.com/ts-bridge/ts-bridge/pull/37))
- `@ts-bridge/shims` is now deprecated and no longer used by the tool.
- This reduces the number of dependencies and makes the tool more
self-contained.
- Only transform undetected named CommonJS imports ([#34](https://github.com/ts-bridge/ts-bridge/pull/34))
- Named CommonJS imports are only transformed if they are not detected as
exports.
- This uses `cjs-module-lexer` to detect named exports, which is used by
Node.js and other tools to detect named exports as well.
## [0.3.0]

@@ -78,3 +99,4 @@

[Unreleased]: https://github.com/ts-bridge/ts-bridge/compare/@ts-bridge/cli@0.3.0...HEAD
[Unreleased]: https://github.com/ts-bridge/ts-bridge/compare/@ts-bridge/cli@0.4.0...HEAD
[0.4.0]: https://github.com/ts-bridge/ts-bridge/compare/@ts-bridge/cli@0.3.0...@ts-bridge/cli@0.4.0
[0.3.0]: https://github.com/ts-bridge/ts-bridge/compare/@ts-bridge/cli@0.2.0...@ts-bridge/cli@0.3.0

@@ -81,0 +103,0 @@ [0.2.0]: https://github.com/ts-bridge/ts-bridge/compare/@ts-bridge/cli@0.1.4...@ts-bridge/cli@0.2.0

3

dist/build-type.js
import typescript from 'typescript';
import { getRemoveImportAttributeTransformer, getImportAttributeTransformer, getRequireTransformer, getGlobalsTransformer, getImportMetaTransformer, getNamedImportTransformer, getTargetTransformer, } from './transformers.js';
import { getDefaultImportTransformer, getRemoveImportAttributeTransformer, getImportAttributeTransformer, getRequireTransformer, getGlobalsTransformer, getImportMetaTransformer, getNamedImportTransformer, getTargetTransformer, } from './transformers.js';
const { ModuleKind } = typescript;

@@ -12,2 +12,3 @@ export const BUILD_TYPES = {

getTransformers: (options) => [
getDefaultImportTransformer(options),
getNamedImportTransformer(options),

@@ -14,0 +15,0 @@ getImportAttributeTransformer({

@@ -53,2 +53,3 @@ import type { CompilerHost, CompilerOptions, ParsedCommandLine, Program, ProjectReference, System } from 'typescript';

references?: boolean;
shims?: boolean;
};

@@ -72,2 +73,3 @@ /**

verbose?: boolean;
shims: boolean;
};

@@ -89,4 +91,6 @@ /**

* @param options.verbose - Whether to enable verbose logging.
* @param options.shims - Whether to generate shims for environment-specific
* APIs.
*/
export declare function buildNode10({ program, projectReferences, compilerOptions, format, files, system, host, verbose, }: BuilderOptions): void;
export declare function buildNode10({ program, projectReferences, compilerOptions, format, files, system, host, verbose, shims, }: BuilderOptions): void;
/**

@@ -100,4 +104,6 @@ * Build the project using the Node.js 16 module resolution strategy.

* @param options.verbose - Whether to enable verbose logging.
* @param options.shims - Whether to generate shims for environment-specific
* APIs.
*/
export declare function buildNode16({ program, format, system, verbose, }: BuilderOptions): void;
export declare function buildNode16({ program, format, system, verbose, shims, }: BuilderOptions): void;
/**

@@ -113,4 +119,6 @@ * Build the project references. This function will build the project references

* @param options.verbose - Whether to enable verbose logging.
* @param options.shims - Whether to generate shims for environment-specific
* APIs.
*/
export declare function buildProjectReferences({ program, format, system, baseDirectory, verbose, }: BuilderOptions): void;
export declare function buildProjectReferences({ program, format, system, baseDirectory, verbose, shims, }: BuilderOptions): void;
/**

@@ -133,7 +141,6 @@ * Get the build function to use based on the TypeScript configuration. This

* @param options - The transformer options.
* @param useShims - Whether to use shims. By default, this is determined by
* whether the shims package is installed.
* @param useShims - Whether to generate shims for environment-specific APIs.
* @returns The transformers to use for the build.
*/
export declare function getTransformers(type: BuildType, options: TransformerOptions, useShims?: boolean): typescript.TransformerFactory<typescript.SourceFile>[];
export declare function getTransformers(type: BuildType, options: TransformerOptions, useShims: boolean): typescript.TransformerFactory<typescript.SourceFile>[];
type BuildOptions = {

@@ -144,2 +151,3 @@ program: Program;

verbose?: boolean;
shims: boolean;
};

@@ -155,6 +163,8 @@ /**

* @param options.verbose - Whether to enable verbose logging.
* @param options.shims - Whether to generate shims for environment-specific
* APIs.
* @returns A promise that resolves when the build is complete.
*/
export declare function build({ program, type, system, verbose, }: BuildOptions): Program;
export declare function build({ program, type, system, verbose, shims, }: BuildOptions): Program;
export {};
//# sourceMappingURL=build.d.ts.map
import chalk from 'chalk';
import { dirname, join, relative } from 'path';
import { dirname, relative } from 'path';
import typescript from 'typescript';
import { pathToFileURL } from 'url';
import { getBuildTypeOptions } from './build-type.js';

@@ -11,3 +10,2 @@ import { getBaseCompilerOptions, getCompilerOptions, getTypeScriptConfig, } from './config.js';

import { createProjectReferencesCompilerHost, getResolvedProjectReferences, } from './project-references.js';
import { isShimsPackageInstalled } from './shims.js';
import { executeSteps } from './steps.js';

@@ -66,3 +64,3 @@ import { getTypeImportExportTransformer, getExportExtensionTransformer, getImportExtensionTransformer, getRequireExtensionTransformer, } from './transformers.js';

export function buildHandler(options) {
const { format, project, files: customFiles, clean, system, host, verbose, references, } = options;
const { format, project, files: customFiles, clean, system, host, verbose, references, shims = true, } = options;
const tsConfig = getTypeScriptConfig(project, system);

@@ -93,2 +91,3 @@ const baseOptions = getBaseCompilerOptions(dirname(project), tsConfig.options);

verbose,
shims,
};

@@ -113,4 +112,6 @@ const buildFunction = getBuildFunction(tsConfig, references);

* @param options.verbose - Whether to enable verbose logging.
* @param options.shims - Whether to generate shims for environment-specific
* APIs.
*/
export function buildNode10({ program, projectReferences, compilerOptions, format, files, system, host, verbose, }) {
export function buildNode10({ program, projectReferences, compilerOptions, format, files, system, host, verbose, shims, }) {
const buildSteps = [

@@ -140,2 +141,3 @@ {

verbose,
shims,
});

@@ -167,2 +169,3 @@ },

verbose,
shims,
});

@@ -182,4 +185,6 @@ },

* @param options.verbose - Whether to enable verbose logging.
* @param options.shims - Whether to generate shims for environment-specific
* APIs.
*/
export function buildNode16({ program, format, system, verbose, }) {
export function buildNode16({ program, format, system, verbose, shims, }) {
const buildSteps = [

@@ -190,3 +195,3 @@ {

task: () => {
build({ program, type: 'module', system });
build({ program, type: 'module', system, shims });
},

@@ -198,3 +203,3 @@ },

task: () => {
build({ program, type: 'commonjs', system });
build({ program, type: 'commonjs', system, shims });
},

@@ -215,4 +220,6 @@ },

* @param options.verbose - Whether to enable verbose logging.
* @param options.shims - Whether to generate shims for environment-specific
* APIs.
*/
export function buildProjectReferences({ program, format, system, baseDirectory, verbose, }) {
export function buildProjectReferences({ program, format, system, baseDirectory, verbose, shims, }) {
const resolvedProjectReferences = getDefinedArray(program.getResolvedProjectReferences());

@@ -246,2 +253,3 @@ const sortedProjectReferences = getResolvedProjectReferences(baseDirectory, resolvedProjectReferences);

verbose,
shims,
});

@@ -276,7 +284,6 @@ }

* @param options - The transformer options.
* @param useShims - Whether to use shims. By default, this is determined by
* whether the shims package is installed.
* @param useShims - Whether to generate shims for environment-specific APIs.
* @returns The transformers to use for the build.
*/
export function getTransformers(type, options, useShims = isShimsPackageInstalled(pathToFileURL(join(process.cwd(), 'dummy.js')).href)) {
export function getTransformers(type, options, useShims) {
const { getTransformers: getBaseTransformers, getShimsTransformers } = getBuildTypeOptions(type);

@@ -298,5 +305,7 @@ const baseTransformers = getBaseTransformers(options);

* @param options.verbose - Whether to enable verbose logging.
* @param options.shims - Whether to generate shims for environment-specific
* APIs.
* @returns A promise that resolves when the build is complete.
*/
export function build({ program, type, system, verbose, }) {
export function build({ program, type, system, verbose, shims, }) {
const { name, extension } = getBuildTypeOptions(type);

@@ -314,3 +323,3 @@ const options = {

getTypeImportExportTransformer(options),
...getTransformers(type, options),
...getTransformers(type, options, shims),
],

@@ -317,0 +326,0 @@ afterDeclarations: [

@@ -8,7 +8,2 @@ import { getFixture, noOp, parseJson } from '@ts-bridge/test-utils';

const { sys } = typescript;
vi.mock('./shims.js', async (importOriginal) => ({
// eslint-disable-next-line @typescript-eslint/consistent-type-imports
...(await importOriginal()),
isShimsPackageInstalled: vi.fn(() => true),
}));
vi.mock('./file-system.js', async (importOriginal) => ({

@@ -425,3 +420,3 @@ // eslint-disable-next-line @typescript-eslint/consistent-type-imports

}, true);
expect(transformers).toHaveLength(5);
expect(transformers).toHaveLength(6);
});

@@ -442,3 +437,3 @@ it('returns the correct transformers for the `commonjs` format', () => {

}, false);
expect(transformers).toHaveLength(3);
expect(transformers).toHaveLength(4);
});

@@ -445,0 +440,0 @@ it('returns the correct transformers for the `commonjs` format without shims', () => {

@@ -51,2 +51,7 @@ import { resolve } from 'path';

default: true,
})
.option('shims', {
type: 'boolean',
description: 'Generate shims for environment-specific APIs.',
default: true,
}), ({ format, ...options }) => {

@@ -53,0 +58,0 @@ return buildHandler({

@@ -1,2 +0,2 @@

import type { TypeChecker, Node, SourceFile, ImportDeclaration, Statement, System, ExportDeclaration } from 'typescript';
import type { TypeChecker, Node, SourceFile, ImportDeclaration, Statement, System, ExportDeclaration, NodeArray, ImportSpecifier } from 'typescript';
import typescript from 'typescript';

@@ -33,2 +33,48 @@ /**

/**
* An import declaration, containing the name of the import and an optional
* property name.
*/
export type Import = {
/**
* The name of the import. This is the name that is available the scope of the
* file containing the import. If the `propertyName` is not set, this is the
* name that is exported by the module as well.
*/
name: string;
/**
* The property name of the import. If this is set, this is the name that is
* exported by the module.
*/
propertyName?: string;
};
/**
* An object with the detected and undetected imports.
*/
export type Imports = {
/**
* The detected imports, i.e., the named imports that are detected by
* `cjs-module-lexer` and can be used as named imports in ES modules.
*/
detected: Import[];
/**
* The other imports, i.e., the named imports that are not detected by
* `cjs-module-lexer` and need to be imported as a default import and
* destructured.
*/
undetected: Import[];
};
/**
* Get the detected and undetected imports for the given package specifier.
* This function uses `cjs-module-lexer` to parse the CommonJS module and
* extract the exports, and then compares the named imports to the exports to
* determine which imports are detected and which are not.
*
* @param packageSpecifier - The specifier for the package.
* @param system - The TypeScript system.
* @param parentUrl - The URL of the parent module.
* @param imports - The named imports from the import declaration.
* @returns The "exported" imports and other imports.
*/
export declare function getImports(packageSpecifier: string, system: System, parentUrl: string, imports: NodeArray<ImportSpecifier>): Imports;
/**
* Get the named import node(s) for the given import declaration. This function

@@ -35,0 +81,0 @@ * transforms named imports for CommonJS modules to a default import and a

import typescript from 'typescript';
import { isCommonJs } from './module-resolver.js';
import { getCommonJsExports, isCommonJs } from './module-resolver.js';
import { getIdentifierName } from './utils.js';

@@ -50,2 +50,35 @@ const { factory, isNamedExports, isNamedImports, isNamespaceImport, isStringLiteral, NodeFlags, SymbolFlags, SyntaxKind, } = typescript;

/**
* Get the detected and undetected imports for the given package specifier.
* This function uses `cjs-module-lexer` to parse the CommonJS module and
* extract the exports, and then compares the named imports to the exports to
* determine which imports are detected and which are not.
*
* @param packageSpecifier - The specifier for the package.
* @param system - The TypeScript system.
* @param parentUrl - The URL of the parent module.
* @param imports - The named imports from the import declaration.
* @returns The "exported" imports and other imports.
*/
export function getImports(packageSpecifier, system, parentUrl, imports) {
const exports = getCommonJsExports(packageSpecifier, system, parentUrl);
return imports.reduce((accumulator, element) => {
if (element.isTypeOnly) {
return accumulator;
}
const name = element.name.text;
const propertyName = element.propertyName?.text;
const exportName = propertyName ?? name;
if (exports.includes(exportName)) {
return {
...accumulator,
detected: [...accumulator.detected, { name, propertyName }],
};
}
return {
...accumulator,
undetected: [...accumulator.undetected, { name, propertyName }],
};
}, { detected: [], undetected: [] });
}
/**
* Get the named import node(s) for the given import declaration. This function

@@ -91,20 +124,26 @@ * transforms named imports for CommonJS modules to a default import and a

}
// If the named bindings are a named import, get the import names.
const importNames = namedBindings.elements
.filter((element) => !element.isTypeOnly)
.map((element) => ({
name: element.name.text,
propertyName: element.propertyName?.text,
}));
if (importNames.length === 0) {
const importNames = getImports(node.moduleSpecifier.text, system, sourceFile.fileName, namedBindings.elements);
// If there are no named imports, return the node as is.
if (importNames.detected.length === 0 &&
importNames.undetected.length === 0) {
return node;
}
// If there are no undetected imports, return the node as is.
if (importNames.undetected.length === 0) {
return node;
}
const moduleSpecifier = getIdentifierName(node.moduleSpecifier.text);
const importIdentifier = getUniqueIdentifier(typeChecker, sourceFile, moduleSpecifier);
const statements = [];
if (importNames.detected.length > 0) {
// Create a new named import node for the detected imports.
const namedImport = factory.createImportDeclaration(node.modifiers, factory.createImportClause(false, undefined, factory.createNamedImports(importNames.detected.map(({ propertyName, name }) => factory.createImportSpecifier(false, propertyName ? factory.createIdentifier(propertyName) : undefined, factory.createIdentifier(name))))), node.moduleSpecifier);
statements.push(namedImport);
}
// Create a new default import node.
const wildcardImport = factory.createImportDeclaration(node.modifiers, factory.createImportClause(false, factory.createIdentifier(importIdentifier), undefined), node.moduleSpecifier);
// Create a variable declaration for the import names.
const defaultImport = factory.createImportDeclaration(node.modifiers, factory.createImportClause(false, factory.createIdentifier(importIdentifier), undefined), node.moduleSpecifier);
// Create a variable declaration for the undetected import names.
const variableStatement = factory.createVariableStatement(undefined, factory.createVariableDeclarationList([
factory.createVariableDeclaration(factory.createObjectBindingPattern([
...importNames.map(({ propertyName, name }) => factory.createBindingElement(undefined, propertyName, name)),
...importNames.undetected.map(({ propertyName, name }) => factory.createBindingElement(undefined, propertyName, name)),
]), undefined, undefined, factory.createIdentifier(importIdentifier)),

@@ -114,3 +153,4 @@ ],

NodeFlags.Const));
return [wildcardImport, variableStatement];
statements.push(defaultImport, variableStatement);
return statements;
}

@@ -117,0 +157,0 @@ /**

@@ -1,5 +0,7 @@

import { getVirtualEnvironment } from '@ts-bridge/test-utils';
import { resolve } from '@ts-bridge/resolver';
import { getFixture, getVirtualEnvironment } from '@ts-bridge/test-utils';
import typescript from 'typescript';
import { describe, expect, it, vi } from 'vitest';
import { getImportAttribute, getImportMetaUrl, getNamedImportNodes, getNamespaceImport, getNonTypeExports, getNonTypeImports, getUniqueIdentifier, hasImportAttributes, } from './generator.js';
import { getImportAttribute, getImportMetaUrl, getImports, getNamedImportNodes, getNamespaceImport, getNonTypeExports, getNonTypeImports, getUniqueIdentifier, hasImportAttributes, } from './generator.js';
const { factory, isAssertClause, isImportAttributes, sys } = typescript;
// TODO: Change these tests to use the real file system, to avoid the need for

@@ -10,5 +12,5 @@ // mocking the resolver.

format: 'commonjs',
path: '/fake.js',
})),
}));
const { factory, isAssertClause, isImportAttributes } = typescript;
/**

@@ -65,2 +67,27 @@ * Compile a statement. This is used to test the output of the generator

});
describe('getImports', () => {
it('returns the imports from an import declaration', () => {
const resolveMock = vi.mocked(resolve);
resolveMock.mockReturnValueOnce({
format: 'commonjs',
path: getFixture('named-imports', 'packages', 'commonjs-module', 'index.js'),
});
const imports = getImports('commonjs-module', sys, getFixture('named-imports', 'src', 'index.ts'), factory.createNodeArray([
factory.createImportSpecifier(false, factory.createIdentifier('foo'), factory.createIdentifier('bar')),
factory.createImportSpecifier(false, undefined, factory.createIdentifier('baz')),
]));
expect(imports.detected).toStrictEqual([
{
name: 'bar',
propertyName: 'foo',
},
]);
expect(imports.undetected).toStrictEqual([
{
name: 'baz',
propertyName: undefined,
},
]);
});
});
describe('getNamedImportNodes', () => {

@@ -71,9 +98,10 @@ const { program, typeChecker, system } = getVirtualEnvironment({

'/foo.ts': 'export const $foo: number = 1;',
'/fake.js': 'module.exports.foo = 1;',
},
});
it('transforms named bindings into a default import', () => {
it('transforms undetected named bindings into a default import', () => {
const sourceFile = program.getSourceFile('/index.ts');
const importDeclaration = factory.createImportDeclaration(undefined, factory.createImportClause(false, undefined, factory.createNamedImports([
factory.createImportSpecifier(false, undefined, factory.createIdentifier('foo')),
factory.createImportSpecifier(false, undefined, factory.createIdentifier('bar')),
factory.createImportSpecifier(false, undefined, factory.createIdentifier('undetected')),
])), factory.createStringLiteral('foo'), undefined);

@@ -84,8 +112,9 @@ const result = getNamedImportNodes(typeChecker, sourceFile, importDeclaration, system);

""use strict";
import { foo } from "foo";
import $foo from "foo";
const { foo, bar } = $foo;
const { undetected } = $foo;
"
`);
});
it('transforms named bindings into a default import and removes type imports', () => {
it('removes type imports', () => {
const sourceFile = program.getSourceFile('/index.ts');

@@ -101,4 +130,5 @@ const importDeclaration = factory.createImportDeclaration(undefined, factory.createImportClause(false, undefined, factory.createNamedImports([

""use strict";
import { foo } from "foo";
import $foo from "foo";
const { foo, bar } = $foo;
const { bar } = $foo;
"

@@ -132,4 +162,5 @@ `);

""use strict";
import { foo } from "foo";
import $_foo from "foo";
const { foo, bar } = $_foo;
const { bar } = $_foo;
"

@@ -136,0 +167,0 @@ `);

@@ -16,2 +16,6 @@ import type { FileSystemInterface, FileFormat } from '@ts-bridge/resolver';

/**
* The path to the module.
*/
path: string;
/**
* The type of the module.

@@ -107,2 +111,14 @@ */

export declare function isCommonJs(packageSpecifier: string, system: System, parentUrl: string): boolean;
/**
* Get the exports for a CommonJS package. This uses `cjs-module-lexer` to parse
* the CommonJS module and extract the exports, which matches the behaviour of
* Node.js. This function will return an empty array if the package is not a
* CommonJS package, or if the package could not be resolved.
*
* @param packageSpecifier - The specifier for the package.
* @param system - The TypeScript system.
* @param parentUrl - The URL of the parent module.
* @returns The exports for the CommonJS package.
*/
export declare function getCommonJsExports(packageSpecifier: string, system: System, parentUrl: string): string[];
//# sourceMappingURL=module-resolver.d.ts.map
import { resolve } from '@ts-bridge/resolver';
import chalk from 'chalk';
import { init, parse } from 'cjs-module-lexer';
import { resolve as resolvePath, extname } from 'path';
import { pathToFileURL } from 'url';
import { warn } from './logging.js';
// This initialises `cjs-module-lexer` so that we can parse CommonJS modules
// quickly using the WASM binary.
await init();
// The first entry is an empty string, which is used for the base package name.

@@ -36,5 +40,6 @@ const DEFAULT_EXTENSIONS = ['', '.js', '.cjs', '.mjs', '.json'];

try {
const { format } = resolve(`${specifier}${extension}`, pathToFileURL(parentUrl), getFileSystemFromTypeScript(system));
const { format, path } = resolve(`${specifier}${extension}`, pathToFileURL(parentUrl), getFileSystemFromTypeScript(system));
return {
specifier: `${specifier}${extension}`,
path,
format,

@@ -180,1 +185,25 @@ };

}
/**
* Get the exports for a CommonJS package. This uses `cjs-module-lexer` to parse
* the CommonJS module and extract the exports, which matches the behaviour of
* Node.js. This function will return an empty array if the package is not a
* CommonJS package, or if the package could not be resolved.
*
* @param packageSpecifier - The specifier for the package.
* @param system - The TypeScript system.
* @param parentUrl - The URL of the parent module.
* @returns The exports for the CommonJS package.
*/
export function getCommonJsExports(packageSpecifier, system, parentUrl) {
const resolution = resolveModule(packageSpecifier, parentUrl, system);
if (!resolution || resolution.format !== 'commonjs') {
return [];
}
const { path } = resolution;
const code = system.readFile(path);
if (!code) {
return [];
}
const { exports, reexports } = parse(code);
return [...exports, ...reexports];
}

@@ -6,3 +6,3 @@ import { noOp } from '@ts-bridge/test-utils';

import { describe, expect, it, vi } from 'vitest';
import { getFileSystemFromTypeScript, getModulePath, getModuleType, resolvePackageSpecifier, resolveRelativePackageSpecifier, } from './module-resolver.js';
import { getCommonJsExports, getFileSystemFromTypeScript, getModulePath, getModuleType, resolvePackageSpecifier, resolveRelativePackageSpecifier, } from './module-resolver.js';
const { sys } = typescript;

@@ -16,2 +16,3 @@ const BASE_DIRECTORY = resolve(dirname(fileURLToPath(import.meta.url)), '..', '..', 'test-utils', 'test', 'fixtures', 'import-resolver');

specifier: 'typescript',
path: expect.stringContaining('node_modules/typescript/lib/typescript.js'),
format: 'commonjs',

@@ -24,2 +25,3 @@ });

specifier: 'is-stream/index.js',
path: expect.stringContaining('node_modules/is-stream/index.js'),
format: 'commonjs',

@@ -32,2 +34,3 @@ });

specifier: 'semver/preload.js',
path: expect.stringContaining('node_modules/semver/preload.js'),
format: 'commonjs',

@@ -42,2 +45,3 @@ });

specifier: './dummy.ts',
path: expect.stringContaining('test-utils/test/fixtures/import-resolver/src/dummy.ts'),
format: null,

@@ -50,2 +54,3 @@ });

specifier: './folder/index.ts',
path: expect.stringContaining('test-utils/test/fixtures/import-resolver/src/folder/index.ts'),
format: null,

@@ -217,1 +222,15 @@ });

});
describe('getCommonJsExports', () => {
it('returns the exports for a CommonJS module', () => {
expect(getCommonJsExports('semver', sys, PARENT_URL)).toStrictEqual(expect.arrayContaining(['parse', 'valid', 'clean']));
});
it('returns an empty array if the module is not CommonJS', () => {
expect(getCommonJsExports('chalk', sys, PARENT_URL)).toStrictEqual([]);
});
it('returns an empty array if the module does not resolve', () => {
expect(getCommonJsExports('foo', sys, PARENT_URL)).toStrictEqual([]);
});
it('returns an empty array if the code is empty', () => {
expect(getCommonJsExports('commonjs-module/empty', sys, PARENT_URL)).toStrictEqual([]);
});
});

@@ -156,2 +156,3 @@ import { getFixture, getRelativePath } from '@ts-bridge/test-utils';

system: sys,
shims: false,
});

@@ -158,0 +159,0 @@ });

@@ -1,12 +0,116 @@

export declare const CJS_SHIMS_PACKAGE = "@ts-bridge/shims";
export declare const ESM_SHIMS_PACKAGE = "@ts-bridge/shims/esm";
export declare const ESM_REQUIRE_SHIMS_PACKAGE = "@ts-bridge/shims/esm/require";
import type { Statement } from 'typescript';
import typescript from 'typescript';
/**
* Check if the `@ts-bridge/shims` package is installed.
* Get the AST for the `fileURLToPath` function, i.e.:
*
* @param basePath - The path to start resolving from. This should be a `file:`
* path and include the filename, e.g., `import.meta.url`.
* @returns `true` if the package is installed, `false` otherwise.
* ```ts
* function fileURLToPath(fileUrl: string) {
* const url = new URL(fileUrl);
* return url.pathname.replace(/^\/([a-zA-Z]:)/u, '$1');
* }
* ```
*
* This function is a simplified version of the `fileURLToPath` function in
* Node.js and does not handle edge cases like file URLs that contain invalid
* characters. It is assumed that the input is always a valid file URL.
*
* This is used to avoid the need for polyfills in browser environment.
*
* @param functionName - The name of the function to create.
* @returns The AST for the `fileURLToPath` function.
*/
export declare function isShimsPackageInstalled(basePath: string): boolean;
export declare function getFileUrlToPathHelperFunction(functionName: string): typescript.FunctionDeclaration;
/**
* Get the AST for the `dirname` function, i.e.:
*
* ```ts
* function dirname(path: string) {
* const sanitisedPath = path
* .toString()
* .replace(/\\/gu, '/')
* .replace(/\/$/u, '');
*
* const index = sanitisedPath.lastIndexOf('/');
* if (index === -1) {
* return path;
* }
*
* if (index === 0) {
* return '/';
* }
*
* return sanitisedPath.slice(0, index);
* }
* ```
*
* This function is a simplified version of the `dirname` function in Node.js.
* It does not handle edge cases like paths that end with multiple slashes or
* paths that contain invalid characters. It is assumed that the input is always
* a valid path.
*
* This is used to avoid the need for polyfills in browser environment.
*
* @param functionName - The name of the function to create.
* @returns The AST for the `dirname` function.
*/
export declare function getDirnameHelperFunction(functionName: string): typescript.FunctionDeclaration;
/**
* Get the AST for the `__dirname` global function, i.e.:
*
* ```ts
* function __dirname(url: string): string {
* return dirname(fileUrlToPath(url));
* }
* ```
*
* This function returns the directory name of the current module, i.e.,
* `__dirname`, but for ESM.
*
* @param functionName - The name of the function to create.
* @param fileUrlToPathFunctionName - The name of the function that converts a
* file URL to a path.
* @param dirnameFunctionName - The name of the function that gets the directory
* name of a path.
* @returns The AST for the `__dirname` global function.
*/
export declare function getDirnameGlobalFunction(functionName: string, fileUrlToPathFunctionName: string, dirnameFunctionName: string): typescript.FunctionDeclaration;
/**
* Get the AST for the `getImportMetaUrl` function, i.e.:
*
* ```ts
* function getImportMetaUrl(fileName: string): string {
* return typeof document === 'undefined'
* ? new URL(`file:${fileName}`).href
* : document.currentScript?.src ?? new URL('main.js', document.baseURI).href;
* }
* ```
*
* If the current environment is a browser, it will return the URL of the
* current script (if it's available). Otherwise, it will return the URL of the
* current file.
*
* @param functionName - The name of the function to create.
* @returns The AST for the `getImportMetaUrl` function.
*/
export declare function getImportMetaUrlFunction(functionName: string): typescript.FunctionDeclaration;
/**
* Get the AST for the `require` function, i.e.:
*
* ```ts
* import { createRequire } from 'module';
*
* function require(identifier: string, url: string): any {
* const fn = createRequire(url);
* return fn(identifier);
* }
* ```
*
* This is a shim for Node.js's `require` function, and is intended to be used
* in ESM modules. Note that this function cannot be used to import ESM modules,
* only CJS modules.
*
* @param functionName - The name of the function to create.
* @returns The AST for the `require` function.
*/
export declare function getRequireHelperFunction(functionName: string): [Statement, Statement];
//# sourceMappingURL=shims.d.ts.map

@@ -1,21 +0,186 @@

import { createRequire } from 'module';
export const CJS_SHIMS_PACKAGE = '@ts-bridge/shims';
export const ESM_SHIMS_PACKAGE = '@ts-bridge/shims/esm';
export const ESM_REQUIRE_SHIMS_PACKAGE = '@ts-bridge/shims/esm/require';
import typescript from 'typescript';
const { factory, SyntaxKind, NodeFlags } = typescript;
/**
* Check if the `@ts-bridge/shims` package is installed.
* Get the AST for the `fileURLToPath` function, i.e.:
*
* @param basePath - The path to start resolving from. This should be a `file:`
* path and include the filename, e.g., `import.meta.url`.
* @returns `true` if the package is installed, `false` otherwise.
* ```ts
* function fileURLToPath(fileUrl: string) {
* const url = new URL(fileUrl);
* return url.pathname.replace(/^\/([a-zA-Z]:)/u, '$1');
* }
* ```
*
* This function is a simplified version of the `fileURLToPath` function in
* Node.js and does not handle edge cases like file URLs that contain invalid
* characters. It is assumed that the input is always a valid file URL.
*
* This is used to avoid the need for polyfills in browser environment.
*
* @param functionName - The name of the function to create.
* @returns The AST for the `fileURLToPath` function.
*/
export function isShimsPackageInstalled(basePath) {
const require = createRequire(basePath);
try {
require.resolve(CJS_SHIMS_PACKAGE);
return true;
}
catch {
return false;
}
export function getFileUrlToPathHelperFunction(functionName) {
return factory.createFunctionDeclaration(undefined, undefined, factory.createIdentifier(functionName), undefined, [
factory.createParameterDeclaration(undefined, undefined, factory.createIdentifier('fileUrl'), undefined, factory.createKeywordTypeNode(SyntaxKind.StringKeyword), undefined),
], undefined, factory.createBlock([
factory.createVariableStatement(undefined, factory.createVariableDeclarationList([
factory.createVariableDeclaration(factory.createIdentifier('url'), undefined, undefined, factory.createNewExpression(factory.createIdentifier('URL'), undefined, [factory.createIdentifier('fileUrl')])),
], NodeFlags.Const)),
factory.createReturnStatement(factory.createCallExpression(factory.createPropertyAccessExpression(factory.createPropertyAccessExpression(factory.createIdentifier('url'), factory.createIdentifier('pathname')), factory.createIdentifier('replace')), undefined, [
factory.createRegularExpressionLiteral('/^\\/([a-zA-Z]:)/u'),
factory.createStringLiteral('$1'),
])),
], true));
}
/**
* Get the AST for the `dirname` function, i.e.:
*
* ```ts
* function dirname(path: string) {
* const sanitisedPath = path
* .toString()
* .replace(/\\/gu, '/')
* .replace(/\/$/u, '');
*
* const index = sanitisedPath.lastIndexOf('/');
* if (index === -1) {
* return path;
* }
*
* if (index === 0) {
* return '/';
* }
*
* return sanitisedPath.slice(0, index);
* }
* ```
*
* This function is a simplified version of the `dirname` function in Node.js.
* It does not handle edge cases like paths that end with multiple slashes or
* paths that contain invalid characters. It is assumed that the input is always
* a valid path.
*
* This is used to avoid the need for polyfills in browser environment.
*
* @param functionName - The name of the function to create.
* @returns The AST for the `dirname` function.
*/
export function getDirnameHelperFunction(functionName) {
return factory.createFunctionDeclaration(undefined, undefined, factory.createIdentifier(functionName), undefined, [
factory.createParameterDeclaration(undefined, undefined, factory.createIdentifier('path'), undefined, factory.createKeywordTypeNode(SyntaxKind.StringKeyword), undefined),
], undefined, factory.createBlock([
factory.createVariableStatement(undefined, factory.createVariableDeclarationList([
factory.createVariableDeclaration(factory.createIdentifier('sanitisedPath'), undefined, undefined, factory.createCallExpression(factory.createPropertyAccessExpression(factory.createCallExpression(factory.createPropertyAccessExpression(factory.createCallExpression(factory.createPropertyAccessExpression(factory.createIdentifier('path'), factory.createIdentifier('toString')), undefined, []), factory.createIdentifier('replace')), undefined, [
factory.createRegularExpressionLiteral('/\\\\/gu'),
factory.createStringLiteral('/'),
]), factory.createIdentifier('replace')), undefined, [
factory.createRegularExpressionLiteral('/\\/$/u'),
factory.createStringLiteral(''),
])),
], NodeFlags.Const)),
factory.createVariableStatement(undefined, factory.createVariableDeclarationList([
factory.createVariableDeclaration(factory.createIdentifier('index'), undefined, undefined, factory.createCallExpression(factory.createPropertyAccessExpression(factory.createIdentifier('sanitisedPath'), factory.createIdentifier('lastIndexOf')), undefined, [factory.createStringLiteral('/')])),
], NodeFlags.Const)),
factory.createIfStatement(factory.createBinaryExpression(factory.createIdentifier('index'), factory.createToken(SyntaxKind.EqualsEqualsEqualsToken), factory.createPrefixUnaryExpression(SyntaxKind.MinusToken, factory.createNumericLiteral('1'))), factory.createBlock([factory.createReturnStatement(factory.createIdentifier('path'))], true), undefined),
factory.createIfStatement(factory.createBinaryExpression(factory.createIdentifier('index'), factory.createToken(SyntaxKind.EqualsEqualsEqualsToken), factory.createNumericLiteral('0')), factory.createBlock([factory.createReturnStatement(factory.createStringLiteral('/'))], true), undefined),
factory.createReturnStatement(factory.createCallExpression(factory.createPropertyAccessExpression(factory.createIdentifier('sanitisedPath'), factory.createIdentifier('slice')), undefined, [
factory.createNumericLiteral('0'),
factory.createIdentifier('index'),
])),
], true));
}
/**
* Get the AST for the `__dirname` global function, i.e.:
*
* ```ts
* function __dirname(url: string): string {
* return dirname(fileUrlToPath(url));
* }
* ```
*
* This function returns the directory name of the current module, i.e.,
* `__dirname`, but for ESM.
*
* @param functionName - The name of the function to create.
* @param fileUrlToPathFunctionName - The name of the function that converts a
* file URL to a path.
* @param dirnameFunctionName - The name of the function that gets the directory
* name of a path.
* @returns The AST for the `__dirname` global function.
*/
export function getDirnameGlobalFunction(functionName, fileUrlToPathFunctionName, dirnameFunctionName) {
return factory.createFunctionDeclaration(undefined, undefined, factory.createIdentifier(functionName), undefined, [
factory.createParameterDeclaration(undefined, undefined, factory.createIdentifier('url'), undefined, factory.createKeywordTypeNode(SyntaxKind.StringKeyword), undefined),
], factory.createKeywordTypeNode(SyntaxKind.StringKeyword), factory.createBlock([
factory.createReturnStatement(factory.createCallExpression(factory.createIdentifier(dirnameFunctionName), undefined, [
factory.createCallExpression(factory.createIdentifier(fileUrlToPathFunctionName), undefined, [factory.createIdentifier('url')]),
])),
], true));
}
/**
* Get the AST for the `getImportMetaUrl` function, i.e.:
*
* ```ts
* function getImportMetaUrl(fileName: string): string {
* return typeof document === 'undefined'
* ? new URL(`file:${fileName}`).href
* : document.currentScript?.src ?? new URL('main.js', document.baseURI).href;
* }
* ```
*
* If the current environment is a browser, it will return the URL of the
* current script (if it's available). Otherwise, it will return the URL of the
* current file.
*
* @param functionName - The name of the function to create.
* @returns The AST for the `getImportMetaUrl` function.
*/
export function getImportMetaUrlFunction(functionName) {
return factory.createFunctionDeclaration(undefined, undefined, factory.createIdentifier(functionName), undefined, [
factory.createParameterDeclaration(undefined, undefined, factory.createIdentifier('fileName'), undefined, factory.createKeywordTypeNode(SyntaxKind.StringKeyword), undefined),
], factory.createKeywordTypeNode(SyntaxKind.StringKeyword), factory.createBlock([
factory.createReturnStatement(factory.createConditionalExpression(factory.createBinaryExpression(factory.createTypeOfExpression(factory.createIdentifier('document')), factory.createToken(SyntaxKind.EqualsEqualsEqualsToken), factory.createStringLiteral('undefined')), factory.createToken(SyntaxKind.QuestionToken), factory.createPropertyAccessExpression(factory.createNewExpression(factory.createIdentifier('URL'), undefined, [
factory.createTemplateExpression(factory.createTemplateHead('file:', 'file:'), [
factory.createTemplateSpan(factory.createIdentifier('fileName'), factory.createTemplateTail('', '')),
]),
]), factory.createIdentifier('href')), factory.createToken(SyntaxKind.ColonToken), factory.createBinaryExpression(factory.createPropertyAccessChain(factory.createPropertyAccessExpression(factory.createIdentifier('document'), factory.createIdentifier('currentScript')), factory.createToken(SyntaxKind.QuestionDotToken), factory.createIdentifier('src')), factory.createToken(SyntaxKind.QuestionQuestionToken), factory.createPropertyAccessExpression(factory.createNewExpression(factory.createIdentifier('URL'), undefined, [
factory.createStringLiteral('main.js'),
factory.createPropertyAccessExpression(factory.createIdentifier('document'), factory.createIdentifier('baseURI')),
]), factory.createIdentifier('href'))))),
], true));
}
/**
* Get the AST for the `require` function, i.e.:
*
* ```ts
* import { createRequire } from 'module';
*
* function require(identifier: string, url: string): any {
* const fn = createRequire(url);
* return fn(identifier);
* }
* ```
*
* This is a shim for Node.js's `require` function, and is intended to be used
* in ESM modules. Note that this function cannot be used to import ESM modules,
* only CJS modules.
*
* @param functionName - The name of the function to create.
* @returns The AST for the `require` function.
*/
export function getRequireHelperFunction(functionName) {
return [
factory.createImportDeclaration(undefined, factory.createImportClause(false, undefined, factory.createNamedImports([
factory.createImportSpecifier(false, factory.createIdentifier('createRequire'), factory.createIdentifier(functionName)),
])), factory.createStringLiteral('module'), undefined),
factory.createFunctionDeclaration(undefined, undefined, factory.createIdentifier('require'), undefined, [
factory.createParameterDeclaration(undefined, undefined, factory.createIdentifier('identifier'), undefined, factory.createKeywordTypeNode(SyntaxKind.StringKeyword), undefined),
factory.createParameterDeclaration(undefined, undefined, factory.createIdentifier('url'), undefined, factory.createKeywordTypeNode(SyntaxKind.StringKeyword), undefined),
], factory.createKeywordTypeNode(SyntaxKind.AnyKeyword), factory.createBlock([
factory.createVariableStatement(undefined, factory.createVariableDeclarationList([
factory.createVariableDeclaration(factory.createIdentifier('fn'), undefined, undefined, factory.createCallExpression(factory.createIdentifier(functionName), undefined, [factory.createIdentifier('url')])),
], NodeFlags.Const)),
factory.createReturnStatement(factory.createCallExpression(factory.createIdentifier('fn'), undefined, [factory.createIdentifier('identifier')])),
], true)),
];
}

@@ -1,26 +0,213 @@

import { beforeEach, describe, expect, it, vi } from 'vitest';
describe('isShimsPackageInstalled', () => {
beforeEach(() => {
vi.resetModules();
import { evaluateModule, getVirtualEnvironment } from '@ts-bridge/test-utils';
import { factory } from 'typescript';
import { beforeAll, describe, expect, it } from 'vitest';
import { getDirnameGlobalFunction, getDirnameHelperFunction, getFileUrlToPathHelperFunction, getImportMetaUrlFunction, getRequireHelperFunction, } from './shims.js';
/**
* Compile a statement. This is used to test the output of the shim functions.
*
* @param node - The statement to compile.
* @returns The compiled code.
*/
function compile(node) {
const { program, system } = getVirtualEnvironment({
files: {
'/index.ts': '// no-op',
},
});
it('returns true if the package is installed', async () => {
vi.doMock('module', () => ({
createRequire: vi.fn(() => ({
resolve: vi.fn(),
})),
}));
const { isShimsPackageInstalled } = await import('./shims.js');
expect(isShimsPackageInstalled(import.meta.url)).toBe(true);
const statements = Array.isArray(node) ? node : [node];
program.emit(undefined, undefined, undefined, false, {
before: [
() => {
return (sourceFile) => {
// TypeScript doesn't provide a nice way to add a new source file, so
// instead we update the dummy source file with the new node.
return factory.updateSourceFile(sourceFile, statements);
};
},
],
});
it('returns false if the package is not installed', async () => {
vi.doMock('module', () => ({
createRequire: vi.fn(() => ({
resolve: vi.fn(() => {
throw new Error();
}),
})),
}));
const { isShimsPackageInstalled } = await import('./shims.js');
expect(isShimsPackageInstalled(import.meta.url)).toBe(false);
const code = system.readFile('/index.js');
if (!code) {
throw new Error('Compilation failed.');
}
return code;
}
describe('getFileUrlToPathHelperFunction', () => {
it('returns the `fileUrlToPath` function', () => {
const ast = getFileUrlToPathHelperFunction('fileUrlToPath');
expect(compile(ast)).toMatchInlineSnapshot(`
""use strict";
function fileUrlToPath(fileUrl) {
const url = new URL(fileUrl);
return url.pathname.replace(/^\\/([a-zA-Z]:)/u, "$1");
}
"
`);
});
describe('fileUrlToPath', () => {
let fileUrlToPath;
beforeAll(async () => {
const code = `
${compile(getFileUrlToPathHelperFunction('fileUrlToPath'))}
export { fileUrlToPath };
`;
const module = await evaluateModule(code);
fileUrlToPath = module.fileUrlToPath;
});
it('converts a file URL to a path', () => {
const fileUrl = 'file:///Users/test/file.js';
expect(fileUrlToPath(fileUrl)).toBe('/Users/test/file.js');
});
it('converts a file URL to a path with a drive letter', () => {
const fileUrl = 'file:///C:/Users/test/file.js';
expect(fileUrlToPath(fileUrl)).toBe('C:/Users/test/file.js');
});
});
});
describe('getDirnameHelperFunction', () => {
it('returns the `dirname` function', () => {
const ast = getDirnameHelperFunction('dirname');
expect(compile(ast)).toMatchInlineSnapshot(`
""use strict";
function dirname(path) {
const sanitisedPath = path.toString().replace(/\\\\/gu, "/").replace(/\\/$/u, "");
const index = sanitisedPath.lastIndexOf("/");
if (index === -1) {
return path;
}
if (index === 0) {
return "/";
}
return sanitisedPath.slice(0, index);
}
"
`);
});
describe('dirname', () => {
let dirname;
beforeAll(async () => {
const code = `
${compile(getDirnameHelperFunction('dirname'))}
export { dirname };
`;
const module = await evaluateModule(code);
dirname = module.dirname;
});
it('returns the directory name of a path', () => {
expect(dirname('/path/to/file')).toBe('/path/to');
});
it('returns the directory name of a path with a trailing slash', () => {
expect(dirname('/path/to/file/')).toBe('/path/to');
});
it('returns the directory name of a path with a drive letter', () => {
expect(dirname('C:/path/to/file')).toBe('C:/path/to');
});
it('returns the directory name of a path with a drive letter and trailing slash', () => {
expect(dirname('C:/path/to/file/')).toBe('C:/path/to');
});
it('returns the directory name of a path with a root directory', () => {
expect(dirname('/')).toBe('/');
});
it('returns the directory name of a path with a root directory and trailing slash', () => {
expect(dirname('//')).toBe('/');
});
it('returns the directory name of a path with a root directory and a trailing slash', () => {
expect(dirname('/path/to/')).toBe('/path');
});
it('returns the directory name of a path with a root directory and a drive letter', () => {
expect(dirname('C:/')).toBe('C:/');
});
it('returns the directory name of a path with a root directory and a drive letter and a trailing slash', () => {
expect(dirname('C:/path/to/')).toBe('C:/path');
});
});
});
describe('getDirnameGlobalFunction', () => {
it('returns the `dirname` global function', () => {
const ast = getDirnameGlobalFunction('dirname', 'fileUrlToPath', 'getDirname');
expect(compile(ast)).toMatchInlineSnapshot(`
""use strict";
function dirname(url) {
return getDirname(fileUrlToPath(url));
}
"
`);
});
describe('dirname', () => {
let dirname;
beforeAll(async () => {
const code = `
${compile(getFileUrlToPathHelperFunction('fileUrlToPath'))}
${compile(getDirnameHelperFunction('getDirname'))}
${compile(getDirnameGlobalFunction('dirname', 'fileUrlToPath', 'getDirname'))}
export { dirname };
`;
const module = await evaluateModule(code);
dirname = module.dirname;
});
it('returns the directory name of a URL', () => {
const url = 'file:///path/to/file.js';
expect(dirname(url)).toBe('/path/to');
});
it('returns the directory name of a URL with a drive letter', () => {
const url = 'file:///C:/path/to/file.js';
expect(dirname(url)).toBe('C:/path/to');
});
});
});
describe('getImportMetaUrlFunction', () => {
it('returns the `importMetaUrl` function', () => {
const ast = getImportMetaUrlFunction('importMetaUrl');
expect(compile(ast)).toMatchInlineSnapshot(`
""use strict";
function importMetaUrl(fileName) {
return typeof document === "undefined" ? new URL(\`file:\${fileName}\`).href : document.currentScript?.src ?? new URL("main.js", document.baseURI).href;
}
"
`);
});
describe('importMetaUrl', () => {
let importMetaUrl;
beforeAll(async () => {
const code = `
${compile(getImportMetaUrlFunction('importMetaUrl'))}
export { importMetaUrl };
`;
const module = await evaluateModule(code);
importMetaUrl = module.importMetaUrl;
});
it('returns the import meta URL', () => {
const fileName = '/path/to/file.js';
expect(importMetaUrl(fileName)).toBe(`file://${fileName}`);
});
});
});
describe('getRequireHelperFunction', () => {
it('returns the `require` helper function', () => {
const ast = getRequireHelperFunction('createRequire');
expect(compile(ast)).toMatchInlineSnapshot(`
""use strict";
import { createRequire as createRequire } from "module";
function require(identifier, url) {
const fn = createRequire(url);
return fn(identifier);
}
"
`);
});
describe('require', () => {
let require;
beforeAll(async () => {
const code = `
${compile(getRequireHelperFunction('createRequire'))}
export { require };
`;
const module = await evaluateModule(code);
require = module.require;
});
it('requires a module', () => {
const url = 'file:///path/to/file.js';
expect(require('fs', url)).toBeDefined();
});
});
});

@@ -89,6 +89,8 @@ import type { FileFormat } from '@ts-bridge/resolver';

* will be transformed to:
* ```
* import * as shims from '@ts-bridge/shims/esm';
* ```ts
* function $__filename(url) {
* // ...;
* }
*
* const foo = shims.__filename(import.meta.url);
* const foo = $__filename(import.meta.url);
* ```

@@ -113,6 +115,8 @@ *

* will be transformed to:
* ```
* import * as nodeShims from '@ts-bridge/shims/esm/require';
* ```ts
* function require(path, url) {
* // ...;
* }
*
* const foo = nodeShims.require('bar', import.meta.url);
* const foo = require('bar', import.meta.url);
* ```

@@ -138,5 +142,7 @@ *

* ```ts
* import * as shims from '@ts-bridge/shims';
* function getImportMetaUrl(filename) {
* // ...;
* }
*
* const foo = shims.getImportMetaUrl(__filename);
* const foo = getImportMetaUrl(__filename);
* ```

@@ -152,2 +158,29 @@ *

/**
* Get a transformer that updates the default imports to use the `importDefault`
* helper function for CommonJS modules.
*
* For example, the following default import:
*
* ```ts
* import foo from 'module';
* ```
*
* will be transformed to:
*
* ```ts
* function $importDefault(module) {
* // ...;
* }
*
* import $foo from 'module';
* const foo = $importDefault($foo);
* ```
*
* @param options - The transformer options.
* @param options.typeChecker - The type checker to use.
* @param options.system - The compiler system to use.
* @returns The transformer function.
*/
export declare function getDefaultImportTransformer({ typeChecker, system, }: TransformerOptions): (context: TransformationContext) => Transformer<SourceFile>;
/**
* Get a transformer that updates the named imports. This updates the imports to

@@ -154,0 +187,0 @@ * use a default import, and destructures the imports from the default import.

import typescript from 'typescript';
import { getImportAttribute, getImportMetaUrl, getNamedImportNodes, getNamespaceImport, getNonTypeExports, getNonTypeImports, getUniqueIdentifier, isGlobal, } from './generator.js';
import { getModulePath, getModuleType } from './module-resolver.js';
import { CJS_SHIMS_PACKAGE, ESM_REQUIRE_SHIMS_PACKAGE, ESM_SHIMS_PACKAGE, } from './shims.js';
const { factory, isBindingElement, isCallExpression, isExportDeclaration, isFunctionDeclaration, isIdentifier, isImportDeclaration, isMetaProperty, isPropertyAccessExpression, isStringLiteral, isVariableDeclaration, visitEachChild, visitNode, SyntaxKind, } = typescript;
import { getImportAttribute, getImportMetaUrl, getNamedImportNodes, getNonTypeExports, getNonTypeImports, getUniqueIdentifier, isGlobal, } from './generator.js';
import { getImportDefaultHelper } from './helpers.js';
import { getModulePath, getModuleType, isCommonJs } from './module-resolver.js';
import { getDirnameGlobalFunction, getDirnameHelperFunction, getFileUrlToPathHelperFunction, getImportMetaUrlFunction, getRequireHelperFunction, } from './shims.js';
const { factory, isBindingElement, isCallExpression, isExportDeclaration, isFunctionDeclaration, isIdentifier, isImportDeclaration, isMetaProperty, isPropertyAccessExpression, isStringLiteral, isVariableDeclaration, NodeFlags, visitEachChild, visitNode, SyntaxKind, } = typescript;
/**

@@ -164,6 +165,8 @@ * Get a transformer that updates the import extensions to append the given

* will be transformed to:
* ```
* import * as shims from '@ts-bridge/shims/esm';
* ```ts
* function $__filename(url) {
* // ...;
* }
*
* const foo = shims.__filename(import.meta.url);
* const foo = $__filename(import.meta.url);
* ```

@@ -180,4 +183,7 @@ *

return (sourceFile) => {
let insertShim = false;
const shimsIdentifier = getUniqueIdentifier(typeChecker, sourceFile, 'shims');
let insertFilenameShim = false;
let insertDirnameShim = false;
const dirnameHelperFunctionName = getUniqueIdentifier(typeChecker, sourceFile, 'getDirname');
const fileUrlToPathFunctionName = getUniqueIdentifier(typeChecker, sourceFile, '__filename');
const dirnameFunctionName = getUniqueIdentifier(typeChecker, sourceFile, '__dirname');
const visitor = (node) => {

@@ -190,4 +196,4 @@ if (isIdentifier(node) &&

isGlobal(typeChecker, node, '__filename')) {
insertShim = true;
return factory.createCallExpression(factory.createPropertyAccessExpression(factory.createIdentifier(shimsIdentifier), factory.createIdentifier('__filename')), undefined, [getImportMetaUrl()]);
insertFilenameShim = true;
return factory.createCallExpression(factory.createIdentifier(fileUrlToPathFunctionName), undefined, [getImportMetaUrl()]);
}

@@ -200,4 +206,4 @@ if (isIdentifier(node) &&

isGlobal(typeChecker, node.parent, '__dirname')) {
insertShim = true;
return factory.createCallExpression(factory.createPropertyAccessExpression(factory.createIdentifier(shimsIdentifier), factory.createIdentifier('__dirname')), undefined, [getImportMetaUrl()]);
insertDirnameShim = true;
return factory.createCallExpression(factory.createIdentifier(dirnameFunctionName), undefined, [getImportMetaUrl()]);
}

@@ -207,5 +213,11 @@ return visitEachChild(node, visitor, context);

const modifiedSourceFile = visitNode(sourceFile, visitor);
if (insertShim) {
const statements = [];
if (insertDirnameShim) {
statements.push(getDirnameHelperFunction(dirnameHelperFunctionName));
statements.push(getDirnameGlobalFunction(dirnameFunctionName, fileUrlToPathFunctionName, dirnameHelperFunctionName));
}
if (insertFilenameShim || insertDirnameShim) {
statements.unshift(getFileUrlToPathHelperFunction(fileUrlToPathFunctionName));
return factory.updateSourceFile(modifiedSourceFile, [
getNamespaceImport(shimsIdentifier, ESM_SHIMS_PACKAGE),
...statements,
...modifiedSourceFile.statements,

@@ -228,6 +240,8 @@ ]);

* will be transformed to:
* ```
* import * as nodeShims from '@ts-bridge/shims/esm/require';
* ```ts
* function require(path, url) {
* // ...;
* }
*
* const foo = nodeShims.require('bar', import.meta.url);
* const foo = require('bar', import.meta.url);
* ```

@@ -245,3 +259,3 @@ *

let insertShim = false;
const shimsIdentifier = getUniqueIdentifier(typeChecker, sourceFile, 'nodeShims');
const createRequireFunctionName = getUniqueIdentifier(typeChecker, sourceFile, 'createRequire');
const visitor = (node) => {

@@ -254,3 +268,3 @@ if (isCallExpression(node) &&

insertShim = true;
return factory.createCallExpression(factory.createPropertyAccessExpression(factory.createIdentifier(shimsIdentifier), factory.createIdentifier('require')), undefined, [node.arguments[0], getImportMetaUrl()]);
return factory.createCallExpression(factory.createIdentifier('require'), undefined, [node.arguments[0], getImportMetaUrl()]);
}

@@ -262,3 +276,3 @@ return visitEachChild(node, visitor, context);

return factory.updateSourceFile(modifiedSourceFile, [
getNamespaceImport(shimsIdentifier, ESM_REQUIRE_SHIMS_PACKAGE),
...getRequireHelperFunction(createRequireFunctionName),
...modifiedSourceFile.statements,

@@ -282,5 +296,7 @@ ]);

* ```ts
* import * as shims from '@ts-bridge/shims';
* function getImportMetaUrl(filename) {
* // ...;
* }
*
* const foo = shims.getImportMetaUrl(__filename);
* const foo = getImportMetaUrl(__filename);
* ```

@@ -298,3 +314,3 @@ *

let insertShim = false;
const shimsIdentifier = getUniqueIdentifier(typeChecker, sourceFile, 'shims');
const functionName = getUniqueIdentifier(typeChecker, sourceFile, 'getImportMetaUrl');
const visitor = (node) => {

@@ -306,3 +322,3 @@ if (isPropertyAccessExpression(node) &&

insertShim = true;
return factory.createCallExpression(factory.createPropertyAccessExpression(factory.createIdentifier(shimsIdentifier), factory.createIdentifier('getImportMetaUrl')), undefined, [factory.createIdentifier('__filename')]);
return factory.createCallExpression(factory.createIdentifier(functionName), undefined, [factory.createIdentifier('__filename')]);
}

@@ -314,3 +330,3 @@ return visitEachChild(node, visitor, context);

return factory.updateSourceFile(modifiedSourceFile, [
getNamespaceImport(shimsIdentifier, CJS_SHIMS_PACKAGE),
getImportMetaUrlFunction(functionName),
...modifiedSourceFile.statements,

@@ -324,2 +340,66 @@ ]);

/**
* Get a transformer that updates the default imports to use the `importDefault`
* helper function for CommonJS modules.
*
* For example, the following default import:
*
* ```ts
* import foo from 'module';
* ```
*
* will be transformed to:
*
* ```ts
* function $importDefault(module) {
* // ...;
* }
*
* import $foo from 'module';
* const foo = $importDefault($foo);
* ```
*
* @param options - The transformer options.
* @param options.typeChecker - The type checker to use.
* @param options.system - The compiler system to use.
* @returns The transformer function.
*/
export function getDefaultImportTransformer({ typeChecker, system, }) {
return (context) => {
return (sourceFile) => {
let insertShim = false;
const importDefaultFunctionName = getUniqueIdentifier(typeChecker, sourceFile, 'importDefault');
const visitor = (node) => {
if (isImportDeclaration(node) && node.importClause?.name) {
// If the module specifier is not a string literal, return the node as is.
if (!isStringLiteral(node.moduleSpecifier)) {
return node;
}
// If the module specifier is not a CommonJS module, return the node as is.
if (!isCommonJs(node.moduleSpecifier.text, system, sourceFile.fileName)) {
return node;
}
insertShim = true;
const name = getUniqueIdentifier(typeChecker, sourceFile, node.importClause.name.text);
const importDeclaration = factory.updateImportDeclaration(node, node.modifiers, factory.updateImportClause(node.importClause, node.importClause.isTypeOnly, factory.createIdentifier(name), node.importClause.namedBindings), node.moduleSpecifier, node.attributes);
const variableDeclaration = factory.createVariableStatement(undefined, factory.createVariableDeclarationList([
factory.createVariableDeclaration(factory.createIdentifier(node.importClause.name.text), undefined, undefined, factory.createCallExpression(factory.createIdentifier(importDefaultFunctionName), undefined, [factory.createIdentifier(name)])),
],
// eslint-disable-next-line no-bitwise
NodeFlags.Const));
return [importDeclaration, variableDeclaration];
}
return visitEachChild(node, visitor, context);
};
const modifiedSourceFile = visitNode(sourceFile, visitor);
if (insertShim) {
return factory.updateSourceFile(modifiedSourceFile, [
getImportDefaultHelper(importDefaultFunctionName),
...modifiedSourceFile.statements,
]);
}
return modifiedSourceFile;
};
};
}
/**
* Get a transformer that updates the named imports. This updates the imports to

@@ -326,0 +406,0 @@ * use a default import, and destructures the imports from the default import.

@@ -7,3 +7,3 @@ import { getFixture } from '@ts-bridge/test-utils';

import { getTypeScriptConfig } from './config.js';
import { getExportExtensionTransformer, getGlobalsTransformer, getImportAttributeTransformer, getImportExtensionTransformer, getImportMetaTransformer, getNamedImportTransformer, getRemoveImportAttributeTransformer, getRequireExtensionTransformer, getRequireTransformer, getTargetTransformer, getTypeImportExportTransformer, } from './transformers.js';
import { getDefaultImportTransformer, getExportExtensionTransformer, getGlobalsTransformer, getImportAttributeTransformer, getImportExtensionTransformer, getImportMetaTransformer, getNamedImportTransformer, getRemoveImportAttributeTransformer, getRequireExtensionTransformer, getRequireTransformer, getTargetTransformer, getTypeImportExportTransformer, } from './transformers.js';
/**

@@ -415,4 +415,8 @@ * Compile a string of (TypeScript) code to JavaScript, and return the compiled

expect(files['filename.js']).toMatchInlineSnapshot(`
"import * as $shims from "@ts-bridge/shims/esm";
console.log($shims.__filename(import.meta.url));
"function $__filename(fileUrl) {
const url = new URL(fileUrl);
return url.pathname.replace(/^\\/([a-zA-Z]:)/u, "$1");
}
console.log($__filename(import.meta.url));
export {};
"

@@ -423,4 +427,22 @@ `);

expect(files['dirname.js']).toMatchInlineSnapshot(`
"import * as $shims from "@ts-bridge/shims/esm";
console.log($shims.__dirname(import.meta.url));
"function $__filename(fileUrl) {
const url = new URL(fileUrl);
return url.pathname.replace(/^\\/([a-zA-Z]:)/u, "$1");
}
function $getDirname(path) {
const sanitisedPath = path.toString().replace(/\\\\/gu, "/").replace(/\\/$/u, "");
const index = sanitisedPath.lastIndexOf("/");
if (index === -1) {
return path;
}
if (index === 0) {
return "/";
}
return sanitisedPath.slice(0, index);
}
function $__dirname(url) {
return $getDirname($__filename(url));
}
console.log($__dirname(import.meta.url));
export {};
"

@@ -431,4 +453,22 @@ `);

expect(files['multiple.js']).toMatchInlineSnapshot(`
"import * as $shims from "@ts-bridge/shims/esm";
console.log($shims.__dirname(import.meta.url), $shims.__filename(import.meta.url));
"function $__filename(fileUrl) {
const url = new URL(fileUrl);
return url.pathname.replace(/^\\/([a-zA-Z]:)/u, "$1");
}
function $getDirname(path) {
const sanitisedPath = path.toString().replace(/\\\\/gu, "/").replace(/\\/$/u, "");
const index = sanitisedPath.lastIndexOf("/");
if (index === -1) {
return path;
}
if (index === 0) {
return "/";
}
return sanitisedPath.slice(0, index);
}
function $__dirname(url) {
return $getDirname($__filename(url));
}
console.log($__dirname(import.meta.url), $__filename(import.meta.url));
export {};
"

@@ -439,5 +479,9 @@ `);

expect(files['rename.js']).toMatchInlineSnapshot(`
"import * as $_shims from "@ts-bridge/shims/esm";
const $shims = 'foo';
console.log($shims, $_shims.__filename(import.meta.url));
"function $__filename(fileUrl) {
const url = new URL(fileUrl);
return url.pathname.replace(/^\\/([a-zA-Z]:)/u, "$1");
}
const $dirname = 'foo';
console.log($dirname, $__filename(import.meta.url));
export {};
"

@@ -468,4 +512,8 @@ `);

expect(files['require.js']).toMatchInlineSnapshot(`
"import * as $nodeShims from "@ts-bridge/shims/esm/require";
const { builtinModules } = $nodeShims.require('module', import.meta.url);
"import { createRequire as $createRequire } from "module";
function require(identifier, url) {
const fn = $createRequire(url);
return fn(identifier);
}
const { builtinModules } = require('module', import.meta.url);
console.log(builtinModules);

@@ -499,29 +547,8 @@ "

""use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
Object.defineProperty(exports, "__esModule", { value: true });
const $shims = __importStar(require("@ts-bridge/shims"));
function $getImportMetaUrl(fileName) {
return typeof document === "undefined" ? new URL(\`file:\${fileName}\`).href : document.currentScript?.src ?? new URL("main.js", document.baseURI).href;
}
// @ts-expect-error - \`import.meta.url\` isn't allowed here.
console.log($shims.getImportMetaUrl(__filename));
console.log($getImportMetaUrl(__filename));
"

@@ -532,2 +559,59 @@ `);

});
describe('getDefaultImportTransformer', () => {
describe('when targeting `module`', () => {
let files;
beforeAll(() => {
const compiler = createCompiler(getFixture('default-imports'));
files = compiler('module', getDefaultImportTransformer({
typeChecker: compiler.typeChecker,
system: sys,
}));
});
it('rewrites a default import for a CommonJS module import', async () => {
expect(files['default-import.js']).toMatchInlineSnapshot(`
"function $importDefault(module) {
if (module?.__esModule) {
return module.default;
}
return module;
}
import $foo from 'commonjs-module';
const foo = $importDefault($foo);
console.log(foo);
"
`);
});
it('only rewrites default imports', async () => {
expect(files['multiple-imports.js']).toMatchInlineSnapshot(`
"function $importDefault(module) {
if (module?.__esModule) {
return module.default;
}
return module;
}
import $foo from 'commonjs-module';
const foo = $importDefault($foo);
import { foo as bar } from 'commonjs-module';
import baz from 'es-module';
console.log(foo, bar, baz);
"
`);
});
it('does not rewrite an invalid import', async () => {
expect(files['invalid.js']).toMatchInlineSnapshot(`
"// @ts-expect-error - Invalid module specifier.
import foo from bar;
foo;
"
`);
});
it('does not rewrite an ESM import', async () => {
expect(files['es-module-import.js']).toMatchInlineSnapshot(`
"import foo from 'es-module';
console.log(foo);
"
`);
});
});
});
describe('getNamedImportTransformer', () => {

@@ -543,7 +627,7 @@ describe('when targeting `module`', () => {

});
it('rewrites a named import for a CommonJS module', async () => {
expect(files['rewrite.js']).toMatchInlineSnapshot(`
it('rewrites a named import for an undetected CommonJS module import', async () => {
expect(files['undetected.js']).toMatchInlineSnapshot(`
"import $commonjsmodule from 'commonjs-module';
const { foo } = $commonjsmodule;
console.log(foo);
const { bar } = $commonjsmodule;
console.log(bar);
"

@@ -555,8 +639,33 @@ `);

"import $_commonjsmodule from 'commonjs-module';
const { foo } = $_commonjsmodule;
const { bar } = $_commonjsmodule;
const $commonjsmodule = 'foo';
console.log($commonjsmodule, foo);
console.log($commonjsmodule, bar);
"
`);
});
it('only rewrites undetected imports if there are multiple imports', async () => {
expect(files['both.js']).toMatchInlineSnapshot(`
"import { foo } from 'commonjs-module';
import $commonjsmodule from 'commonjs-module';
const { bar } = $commonjsmodule;
console.log(foo, bar);
"
`);
});
it('supports imports with property names', async () => {
expect(files['property-names.js']).toMatchInlineSnapshot(`
"import { foo as fooImport } from 'commonjs-module';
import $commonjsmodule from 'commonjs-module';
const { bar: barImport } = $commonjsmodule;
console.log(fooImport, barImport);
"
`);
});
it('does not rewrite a named import for a detected CommonJS module import', async () => {
expect(files['detected.js']).toMatchInlineSnapshot(`
"import { foo } from 'commonjs-module';
console.log(foo);
"
`);
});
it('does not rewrite the import when using a default import', () => {

@@ -563,0 +672,0 @@ expect(files['default-import.js']).toMatchInlineSnapshot(`

{
"name": "@ts-bridge/cli",
"version": "0.3.0",
"version": "0.4.0",
"description": "Bridge the gap between ES modules and CommonJS modules with an easy-to-use alternative to `tsc`.",

@@ -50,2 +50,3 @@ "keywords": [

"chalk": "^5.3.0",
"cjs-module-lexer": "^1.3.1",
"yargs": "^17.7.2"

@@ -64,10 +65,4 @@ },

"peerDependencies": {
"@ts-bridge/shims": "^0.1.1",
"typescript": ">=4.8.0"
},
"peerDependenciesMeta": {
"@ts-bridge/shims": {
"optional": true
}
},
"packageManager": "yarn@4.1.1",

@@ -74,0 +69,0 @@ "engines": {

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc