Skip to content

Commit

Permalink
fix(tags): use appropriate import/request for ESM/CommonJS (#1172)
Browse files Browse the repository at this point in the history
  • Loading branch information
Anber committed Jan 6, 2023
1 parent 2ed729e commit 3ce985e
Show file tree
Hide file tree
Showing 5 changed files with 122 additions and 11 deletions.
7 changes: 7 additions & 0 deletions .changeset/calm-keys-jog.md
@@ -0,0 +1,7 @@
---
'@linaria/babel-preset': patch
'@linaria/tags': patch
'@linaria/utils': patch
---

Update tags processor to insert appropriate import/request for ESM/CommonJS.
87 changes: 84 additions & 3 deletions packages/babel/src/helper-module-imports.d.ts
@@ -1,11 +1,92 @@
declare module '@babel/helper-module-imports' {
import type { NodePath } from '@babel/traverse';
import type { Identifier } from '@babel/types';
import type * as t from '@babel/types';

type ImportOptions = {
/**
* The module being referenced.
*/
importedSource: string | null;
/**
* The type of module being imported:
*
* * 'es6' - An ES6 module.
* * 'commonjs' - A CommonJS module. (Default)
*/
importedType: 'es6' | 'commonjs';
/**
* The type of interop behavior for namespace/default/named when loading
* CommonJS modules.
*
* ## 'babel' (Default)
*
* Load using Babel's interop.
*
* If '.__esModule' is true, treat as 'compiled', else:
*
* * Namespace: A copy of the module.exports with .default
* populated by the module.exports object.
* * Default: The module.exports value.
* * Named: The .named property of module.exports.
*
* The 'ensureLiveReference' has no effect on the liveness of these.
*
* ## 'compiled'
*
* Assume the module is ES6 compiled to CommonJS. Useful to avoid injecting
* interop logic if you are confident that the module is a certain format.
*
* * Namespace: The root module.exports object.
* * Default: The .default property of the namespace.
* * Named: The .named property of the namespace.
*
* Will return erroneous results if the imported module is _not_ compiled
* from ES6 with Babel.
*
* ## 'uncompiled'
*
* Assume the module is _not_ ES6 compiled to CommonJS. Used a simplified
* access pattern that doesn't require additional function calls.
*
* Will return erroneous results if the imported module _is_ compiled
* from ES6 with Babel.
*
* * Namespace: The module.exports object.
* * Default: The module.exports object.
* * Named: The .named property of module.exports.
*/
importedInterop: 'babel' | 'node' | 'compiled' | 'uncompiled';
/**
* The type of CommonJS interop included in the environment that will be
* loading the output code.
*
* * 'babel' - CommonJS modules load with Babel's interop. (Default)
* * 'node' - CommonJS modules load with Node's interop.
*
* See descriptions in 'importedInterop' for more details.
*/
importingInterop: 'babel' | 'node';
/**
* Define whether the import should be loaded before or after the existing imports.
* "after" is only allowed inside ECMAScript modules, since it's not possible to
* reliably pick the location _after_ require() calls but _before_ other code in CJS.
*/
importPosition: 'before' | 'after';

nameHint?: string;
blockHoist?: number;
};

function addDefault(
path: NodePath,
importedSource: string,
opts?: Partial<ImportOptions>
): t.Identifier;

function addNamed(
path: NodePath,
name: string,
importedSource: string,
opts?: { nameHint: string }
): Identifier;
opts?: Partial<ImportOptions>
): t.Identifier;
}
12 changes: 9 additions & 3 deletions packages/babel/src/utils/getTagProcessor.ts
Expand Up @@ -2,7 +2,7 @@ import { readFileSync } from 'fs';
import { basename, dirname, join } from 'path';

import { types as t } from '@babel/core';
import { addNamed } from '@babel/helper-module-imports';
import { addDefault, addNamed } from '@babel/helper-module-imports';
import type { NodePath } from '@babel/traverse';
import type {
Expression,
Expand Down Expand Up @@ -327,10 +327,16 @@ function getBuilderForIdentifier(
});
};

const importedType = imports.some((i) => i.type === 'esm')
? 'es6'
: 'commonjs';

const astService = {
...t,
addNamedImport: (name: string, importedSource: string) =>
addNamed(path, name, importedSource),
addDefaultImport: (importedSource: string, nameHint?: string) =>
addDefault(path, importedSource, { importedType, nameHint }),
addNamedImport: (name: string, importedSource: string, nameHint?: string) =>
addNamed(path, name, importedSource, { importedType, nameHint }),
};

return (...args: BuilderArgs) =>
Expand Down
7 changes: 6 additions & 1 deletion packages/tags/src/BaseProcessor.ts
Expand Up @@ -51,7 +51,12 @@ export default abstract class BaseProcessor {
public constructor(
params: Params,
protected readonly astService: typeof t & {
addNamedImport: (name: string, source: string) => Identifier;
addDefaultImport: (source: string, nameHint?: string) => Identifier;
addNamedImport: (
name: string,
source: string,
nameHint?: string
) => Identifier;
},
public readonly location: SourceLocation | null,
protected readonly replacer: (
Expand Down
20 changes: 16 additions & 4 deletions packages/utils/src/collectExportsAndImports.ts
Expand Up @@ -45,6 +45,7 @@ export interface IImport {
imported: string | 'default' | '*';
local: NodePath<Identifier | MemberExpression>;
source: string;
type: 'cjs' | 'dynamic' | 'esm';
}

export interface IExport {
Expand Down Expand Up @@ -97,23 +98,23 @@ const collectors: {
if (isType(path)) return [];
const imported = getValue(path.get('imported'));
const local = path.get('local');
return [{ imported, local, source }];
return [{ imported, local, source, type: 'esm' }];
},

ImportDefaultSpecifier(
path: NodePath<ImportDefaultSpecifier>,
source
): IImport[] {
const local = path.get('local');
return [{ imported: 'default', local, source }];
return [{ imported: 'default', local, source, type: 'esm' }];
},

ImportNamespaceSpecifier(
path: NodePath<ImportNamespaceSpecifier>,
source
): IImport[] {
const local = path.get('local');
return unfoldNamespaceImport({ imported: '*', local, source });
return unfoldNamespaceImport({ imported: '*', local, source, type: 'esm' });
},
};

Expand Down Expand Up @@ -305,7 +306,12 @@ function collectFromDynamicImport(path: NodePath<Import>, state: IState): void {
// Is it `const something = await import("something")`?
if (key === 'init' && container.isVariableDeclarator()) {
importFromVariableDeclarator(container, isAwaited).map((prop) =>
state.imports.push({ imported: prop.what, local: prop.as, source })
state.imports.push({
imported: prop.what,
local: prop.as,
source,
type: 'dynamic',
})
);
}
}
Expand Down Expand Up @@ -411,13 +417,15 @@ function collectFromRequire(path: NodePath<Identifier>, state: IState): void {
imported,
local: id,
source,
type: 'cjs',
});
state.imports.push(...unfolded);
} else {
state.imports.push({
imported,
local: id,
source,
type: 'cjs',
});
}
}
Expand Down Expand Up @@ -445,6 +453,7 @@ function collectFromRequire(path: NodePath<Identifier>, state: IState): void {
imported: getValue(property),
local: id,
source,
type: 'cjs',
});
} else {
warn(
Expand All @@ -460,6 +469,7 @@ function collectFromRequire(path: NodePath<Identifier>, state: IState): void {
imported: getValue(property),
local: container,
source,
type: 'cjs',
});
}

Expand All @@ -474,6 +484,7 @@ function collectFromRequire(path: NodePath<Identifier>, state: IState): void {
imported: '*',
local: prop.as,
source,
type: 'cjs',
});

state.imports.push(...unfolded);
Expand All @@ -482,6 +493,7 @@ function collectFromRequire(path: NodePath<Identifier>, state: IState): void {
imported: prop.what,
local: prop.as,
source,
type: 'cjs',
});
}
});
Expand Down

0 comments on commit 3ce985e

Please sign in to comment.