Skip to content

Commit

Permalink
fix(babel): support code transpiled with esbuild (#1143)
Browse files Browse the repository at this point in the history
* fix(babel): support code transpiled with esbuild
  • Loading branch information
Anber committed Dec 3, 2022
1 parent b49f3b8 commit 315f036
Show file tree
Hide file tree
Showing 8 changed files with 366 additions and 38 deletions.
7 changes: 7 additions & 0 deletions .changeset/shaggy-onions-deny.md
@@ -0,0 +1,7 @@
---
'@linaria/testkit': patch
'@linaria/utils': patch
'@linaria/babel-preset': patch
---

Support for code transpiled with esbuild.
1 change: 1 addition & 0 deletions packages/testkit/package.json
Expand Up @@ -14,6 +14,7 @@
"@linaria/tags": "workspace:^",
"@swc/core": "^1.3.20",
"dedent": "^0.7.0",
"esbuild": "^0.15.16",
"strip-ansi": "^5.2.0",
"typescript": "^4.7.4"
},
Expand Down
20 changes: 19 additions & 1 deletion packages/testkit/src/collectExportsAndImports.test.ts
Expand Up @@ -6,6 +6,7 @@ import type { NodePath } from '@babel/core';
import generator from '@babel/generator';
import { transformSync as swcTransformSync } from '@swc/core';
import dedent from 'dedent';
import { transformSync as esbuildTransformSync } from 'esbuild';
import * as ts from 'typescript';

import type { MissedBabelCoreTypes } from '@linaria/babel-preset';
Expand Down Expand Up @@ -42,6 +43,17 @@ const swcCommonJS =
return result.code;
};

const esbuildCommonJS = (source: string): string => {
const result = esbuildTransformSync(source, {
format: 'cjs',
loader: 'ts',
sourcefile: join(__dirname, 'source.ts'),
target: 'es2015',
});

return result.code;
};

function babelCommonJS(source: string): string {
const result = babel.transformSync(source, {
babelrc: false,
Expand Down Expand Up @@ -82,6 +94,7 @@ function babelNode16(source: string): string {
const compilers: [name: string, compiler: (code: string) => string][] = [
['as is', babelNode16],
['babelCommonJS', babelCommonJS],
['esbuildCommonJS', esbuildCommonJS],
['swcCommonJSes5', swcCommonJS('es5')],
['swcCommonJSes2015', swcCommonJS('es2015')],
['typescriptCommonJS', typescriptCommonJS],
Expand Down Expand Up @@ -548,7 +561,12 @@ describe.each(compilers)('collectExportsAndImports (%s)', (name, compiler) => {
export const { a, ...rest } = obj;
`;

expect(exports).toMatchObject([
expect(
exports.filter((i) => {
// Esbuild, why?
return i.exported !== '_a';
})
).toMatchObject([
{
exported: 'a',
},
Expand Down
70 changes: 62 additions & 8 deletions packages/utils/src/collectExportsAndImports.ts
Expand Up @@ -328,13 +328,15 @@ function getImportTypeByInteropFunction(

if (
name.startsWith('_interopRequireWildcard') ||
name.startsWith('__importStar')
name.startsWith('__importStar') ||
name.startsWith('__toESM')
) {
return '*';
}

if (
name.startsWith('__rest') ||
name.startsWith('__objRest') ||
name.startsWith('_objectDestructuringEmpty')
) {
return '*';
Expand Down Expand Up @@ -921,11 +923,7 @@ function collectFromExportStarCall(
path.skip();
}

function collectFromExportCall(path: NodePath<CallExpression>, state: IState) {
const [exports, map] = path.get('arguments');
if (!isExports(exports)) return;
if (!map.isObjectExpression()) return;

function collectFromMap(map: NodePath<ObjectExpression>, state: IState) {
const properties = map.get('properties');
properties.forEach((property) => {
if (!property.isObjectProperty()) return;
Expand All @@ -945,6 +943,54 @@ function collectFromExportCall(path: NodePath<CallExpression>, state: IState) {
local: returnValue,
});
});
}

function collectFromEsbuildExportCall(
path: NodePath<CallExpression>,
state: IState
) {
const [sourceExports, map] = path.get('arguments');
if (!sourceExports.isIdentifier({ name: 'source_exports' })) return;
if (!map.isObjectExpression()) return;

collectFromMap(map, state);

path.skip();
}

function collectFromEsbuildReExportCall(
path: NodePath<CallExpression>,
state: IState
) {
const [sourceExports, requireCall, exports] = path.get('arguments');
if (!sourceExports.isIdentifier({ name: 'source_exports' })) return;
if (!requireCall.isCallExpression()) return;
if (!isExports(exports)) return;

const callee = requireCall.get('callee');
if (!isRequire(callee)) return;
const sourcePath = requireCall.get('arguments')?.[0];
if (!sourcePath.isStringLiteral()) return;

state.reexports.push({
exported: '*',
imported: '*',
local: path,
source: sourcePath.node.value,
});

path.skip();
}

function collectFromSwcExportCall(
path: NodePath<CallExpression>,
state: IState
) {
const [exports, map] = path.get('arguments');
if (!isExports(exports)) return;
if (!map.isObjectExpression()) return;

collectFromMap(map, state);

path.skip();
}
Expand All @@ -971,9 +1017,17 @@ function collectFromCallExpression(
collectFromExportStarCall(path, state);
}

// swc
if (name === '_export') {
collectFromExportCall(path, state);
collectFromSwcExportCall(path, state);
}

// esbuild
if (name === '__export') {
collectFromEsbuildExportCall(path, state);
}

if (name === '__reExport') {
collectFromEsbuildReExportCall(path, state);
}
}

Expand Down
24 changes: 14 additions & 10 deletions packages/utils/src/isExports.ts
@@ -1,19 +1,23 @@
import type { NodePath } from '@babel/traverse';

import { getScope } from './getScope';
import { isGlobal } from './isGlobal';

/**
* Checks that specified Identifier is a global `exports`
* @param id
* Checks that specified Identifier is a global `exports` or `module.exports`
* @param node
*/
export default function isExports(id: NodePath | null | undefined) {
if (!id?.isIdentifier() || id.node.name !== 'exports') {
return false;
export default function isExports(node: NodePath | null | undefined) {
if (node?.isIdentifier({ name: 'exports' })) {
return isGlobal(node, 'exports');
}

const scope = getScope(id);
if (
node?.isMemberExpression() &&
node.get('object').isIdentifier({ name: 'module' }) &&
node.get('property').isIdentifier({ name: 'exports' })
) {
return isGlobal(node, 'module');
}

return (
scope.getBinding('exports') === undefined && scope.hasGlobal('exports')
);
return false;
}
9 changes: 9 additions & 0 deletions packages/utils/src/isGlobal.ts
@@ -0,0 +1,9 @@
import type { NodePath } from '@babel/traverse';

import { getScope } from './getScope';

export const isGlobal = (node: NodePath, name: string) => {
const scope = getScope(node);

return scope.getBinding(name) === undefined && scope.hasGlobal(name);
};
8 changes: 2 additions & 6 deletions packages/utils/src/isRequire.ts
@@ -1,6 +1,6 @@
import type { NodePath } from '@babel/traverse';

import { getScope } from './getScope';
import { isGlobal } from './isGlobal';

/**
* Checks that specified Identifier is a global `require`
Expand All @@ -11,9 +11,5 @@ export default function isRequire(id: NodePath | null | undefined) {
return false;
}

const scope = getScope(id);

return (
scope.getBinding('require') === undefined && scope.hasGlobal('require')
);
return isGlobal(id, 'require');
}

0 comments on commit 315f036

Please sign in to comment.