Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix/collection output transpile imports #3523

Merged
merged 11 commits into from
Sep 8, 2022
Merged
92 changes: 45 additions & 47 deletions src/compiler/transformers/map-imports-to-path-aliases.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,65 +21,63 @@ export const mapImportsToPathAliases = (
destinationDirectory: string
): ts.TransformerFactory<ts.SourceFile> => {
return (transformCtx) => {
let sourceFile: string;

const compilerHost = ts.createCompilerHost(config.tsCompilerOptions);

const visit = (node: ts.Node): ts.VisitResult<ts.Node> => {
// We should only attempt to transpile standard module imports:
// - import * as ts from 'typescript';
// - import { Foo, Bar } from 'baz';
// - import { Foo as Bar } from 'baz';
// - import Foo from 'bar';
// We should NOT transpile other import declaration types:
// - import a = Foo.Bar
if (ts.isImportDeclaration(node) && ts.isStringLiteral(node.moduleSpecifier)) {
let importPath = node.moduleSpecifier.text;
const visit =
(sourceFile: string) =>
(node: ts.Node): ts.VisitResult<ts.Node> => {
// We should only attempt to transpile standard module imports:
// - import * as ts from 'typescript';
// - import { Foo, Bar } from 'baz';
// - import { Foo as Bar } from 'baz';
// - import Foo from 'bar';
// We should NOT transpile other import declaration types:
// - import a = Foo.Bar
if (ts.isImportDeclaration(node) && ts.isStringLiteral(node.moduleSpecifier)) {
let importPath = node.moduleSpecifier.text;

// We will ignore transforming any paths that are already relative paths or
// imports from external modules/packages
if (!importPath.startsWith('.')) {
const module = ts.resolveModuleName(importPath, sourceFile, config.tsCompilerOptions, compilerHost);
// We will ignore transforming any paths that are already relative paths or
// imports from external modules/packages
if (!importPath.startsWith('.')) {
const module = ts.resolveModuleName(importPath, sourceFile, config.tsCompilerOptions, compilerHost);

const hasResolvedFileName = module.resolvedModule?.resolvedFileName != null;
const isModuleFromNodeModules = module.resolvedModule?.isExternalLibraryImport === true;
const shouldTranspileImportPath = hasResolvedFileName && !isModuleFromNodeModules;
const hasResolvedFileName = module.resolvedModule?.resolvedFileName != null;
const isModuleFromNodeModules = module.resolvedModule?.isExternalLibraryImport === true;
const shouldTranspileImportPath = hasResolvedFileName && !isModuleFromNodeModules;

if (shouldTranspileImportPath) {
// Create a global extension matching regular expression
// This can be used to strip all file extensions off the generated import path string
const extensionRegex = new RegExp(Object.values(ts.Extension).join('|'), 'g');
if (shouldTranspileImportPath) {
// Create a regular expression that will be used to remove the last file extension
// from the import path
const extensionRegex = new RegExp(Object.values(ts.Extension).join('$|'));
tanner-reits marked this conversation as resolved.
Show resolved Hide resolved

// In order to make sure the relative path works when the destination depth is different than the source
// file structure depth, we need to determine where the resolved file exists relative to the destination directory
const resolvePathInDestination = module.resolvedModule.resolvedFileName.replace(
config.srcDir,
destinationDirectory
);
// In order to make sure the relative path works when the destination depth is different than the source
// file structure depth, we need to determine where the resolved file exists relative to the destination directory
const resolvePathInDestination = module.resolvedModule.resolvedFileName.replace(
config.srcDir,
destinationDirectory
);

importPath = normalize(
relative(dirname(destinationFilePath), resolvePathInDestination).replace(extensionRegex, '')
);
importPath = normalize(
relative(dirname(destinationFilePath), resolvePathInDestination).replace(extensionRegex, '')
);
}
}

return transformCtx.factory.updateImportDeclaration(
node,
node.decorators,
node.modifiers,
node.importClause,
transformCtx.factory.createStringLiteral(importPath),
node.assertClause
);
}

return transformCtx.factory.updateImportDeclaration(
node,
node.decorators,
node.modifiers,
node.importClause,
transformCtx.factory.createStringLiteral(importPath),
node.assertClause
);
}

return ts.visitEachChild(node, visit, transformCtx);
};
return ts.visitEachChild(node, visit(sourceFile), transformCtx);
};

return (tsSourceFile) => {
sourceFile = tsSourceFile.fileName;

return ts.visitEachChild(tsSourceFile, visit, transformCtx);
return ts.visitEachChild(tsSourceFile, visit(tsSourceFile.fileName), transformCtx);
};
};
};
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ describe('mapImportsToPathAliases', () => {
>;

beforeEach(() => {
config = mockValidatedConfig({ tsconfig: './tsconfig.json', tsCompilerOptions: {} });
config = mockValidatedConfig({ tsCompilerOptions: {} });

resolveModuleNameSpy = jest.spyOn(ts, 'resolveModuleName');
});
Expand Down Expand Up @@ -94,4 +94,58 @@ describe('mapImportsToPathAliases', () => {

expect(module.outputText).toContain('import { utils } from "utils";');
});

// The resolved module is not part of the output directory
it('generates the correct relative path when the resolved module is outside the transpiled project', () => {
config.srcDir = '/test-dir';
resolveModuleNameSpy.mockReturnValue({
resolvedModule: {
isExternalLibraryImport: false,
extension: Extension.Ts,
resolvedFileName: '/some-compiled-dir/utils/utils.ts',
},
});
const inputText = `
import { utils } from "@utils";

utils.test();
`;

module = transpileModule(
inputText,
config,
null,
[],
[mapImportsToPathAliases(config, '/dist/collection/test.js', '/dist/collection')]
);

expect(module.outputText).toContain('import { utils } from "../../some-compiled-dir/utils/utils";');
});

// Source module and resolved module are in the same output directory
it('generates the correct relative path when the resolved module is within the transpiled project', () => {
config.srcDir = '/test-dir';
resolveModuleNameSpy.mockReturnValue({
resolvedModule: {
isExternalLibraryImport: false,
extension: Extension.Ts,
resolvedFileName: '/test-dir/utils/utils.ts',
},
});
const inputText = `
import { utils } from "@utils";

utils.test();
`;

module = transpileModule(
inputText,
config,
null,
[],
[mapImportsToPathAliases(config, 'dist/collection/test.js', 'dist/collection')]
);

expect(module.outputText).toContain('import { utils } from "utils/utils";');
});
});