Skip to content

Commit

Permalink
fix(babel): ignore babelrc for preeval and shaker transforms (#1313)
Browse files Browse the repository at this point in the history
  • Loading branch information
Anber committed Aug 2, 2023
1 parent 9bb782d commit ae162f4
Show file tree
Hide file tree
Showing 10 changed files with 111 additions and 44 deletions.
8 changes: 8 additions & 0 deletions .changeset/empty-lemons-admire.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
'@linaria/babel-preset': minor
'@linaria/shaker': minor
'@linaria/testkit': minor
'@linaria/utils': minor
---

babelrc should not be used for preeval transformations (fixes #1308)
2 changes: 1 addition & 1 deletion packages/babel/src/module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -383,7 +383,7 @@ class Module {

// Resolve module id (and filename) relatively to parent module
const resolved = this.resolve(id);
const [filename, onlyAsString] = resolved.split('\0');
const [filename, onlyAsString = '*'] = resolved.split('\0');
if (filename === id && !path.isAbsolute(id)) {
// The module is a builtin node modules, but not in the allowed list
throw new Error(
Expand Down
63 changes: 49 additions & 14 deletions packages/babel/src/transform-stages/1-prepare-for-eval.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import {
buildOptions,
EventEmitter,
getFileIdx,
getPluginKey,
loadBabelOptions,
} from '@linaria/utils';

Expand Down Expand Up @@ -59,26 +60,25 @@ function runPreevalStage(
babel: Core,
item: IEntrypoint,
originalAst: File,
pluginOptions: StrictOptions,
eventEmitter: EventEmitter
): BabelFileResult {
const babelOptions = item.parseConfig;
const { pluginOptions, evalConfig } = item;

const preShakePlugins =
babelOptions.plugins?.filter((i) =>
evalConfig.plugins?.filter((i) =>
hasKeyInList(i, pluginOptions.highPriorityPlugins)
) ?? [];

const plugins = [
...preShakePlugins,
[require.resolve('../plugins/preeval'), { ...pluginOptions, eventEmitter }],
...(babelOptions.plugins ?? []).filter(
...(evalConfig.plugins ?? []).filter(
(i) => !hasKeyInList(i, pluginOptions.highPriorityPlugins)
),
];

const transformConfig = buildOptions({
...babelOptions,
...evalConfig,
envName: 'linaria',
plugins,
});
Expand All @@ -100,17 +100,15 @@ export function prepareCode(
babel: Core,
item: IEntrypoint,
originalAst: File,
pluginOptions: StrictOptions,
eventEmitter: EventEmitter
): [code: string, imports: Module['imports'], metadata?: BabelFileMetadata] {
const { evaluator, log, parseConfig, only } = item;
const { evaluator, log, evalConfig, pluginOptions, only } = item;

const onPreevalFinished = eventEmitter.pair({ method: 'preeval' });
const preevalStageResult = runPreevalStage(
babel,
item,
originalAst,
pluginOptions,
eventEmitter
);
onPreevalFinished();
Expand All @@ -135,7 +133,7 @@ export function prepareCode(

const onEvaluatorFinished = eventEmitter.pair({ method: 'evaluator' });
const [, code, imports] = evaluator(
parseConfig,
evalConfig,
preevalStageResult.ast!,
preevalStageResult.code!,
evaluatorConfig,
Expand Down Expand Up @@ -182,7 +180,6 @@ function processQueueItem(
babel,
item,
ast,
pluginOptions,
eventEmitter
);

Expand Down Expand Up @@ -268,27 +265,65 @@ export function createEntrypoint(
paths: [dirname(name)],
})).default;

const parseConfig = buildOptions(pluginOptions?.babelOptions, babelOptions, {
// FIXME: All those configs should be memoized

const commonOptions = {
ast: true,
filename: name,
inputSourceMap: options.inputSourceMap,
root: options.root,
sourceFileName: name,
sourceMaps: true,
};

const rawConfig = buildOptions(
pluginOptions?.babelOptions,
babelOptions,
commonOptions
);

const parseConfig = loadBabelOptions(babel, name, {
babelrc: true,
...rawConfig,
});

const fullParserOptions = loadBabelOptions(babel, name, parseConfig);
const isModuleResolver = (plugin: PluginItem) =>
getPluginKey(plugin) === 'module-resolver';
const parseHasModuleResolver = parseConfig.plugins?.some(isModuleResolver);
const rawHasModuleResolver = rawConfig.plugins?.some(isModuleResolver);

if (parseHasModuleResolver && !rawHasModuleResolver) {
// eslint-disable-next-line no-console
console.warn(
`[linaria] ${name} has a module-resolver plugin in its babelrc, but it is not present` +
`in the babelOptions for the linaria plugin. This works for now but will be an error in the future.` +
`Please add the module-resolver plugin to the babelOptions for the linaria plugin.`
);

rawConfig.plugins = [
...(parseConfig.plugins?.filter((plugin) => isModuleResolver(plugin)) ??
[]),
...(rawConfig.plugins ?? []),
];
}

const evalConfig = loadBabelOptions(babel, name, {
babelrc: false,
...rawConfig,
});

log('[createEntrypoint] %s (%o)\n%s', name, only, code || EMPTY_FILE);

finishEvent();
return {
code,
evalConfig,
evaluator,
log,
name,
only,
parseConfig: fullParserOptions,
log,
parseConfig,
pluginOptions,
};
}

Expand Down
6 changes: 4 additions & 2 deletions packages/babel/src/transform-stages/helpers/ModuleQueue.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,18 @@ import type { TransformOptions } from '@babel/core';

import type { CustomDebug, Debugger } from '@linaria/logger';
import { createCustomDebug } from '@linaria/logger';
import type { Evaluator } from '@linaria/utils';
import type { Evaluator, StrictOptions } from '@linaria/utils';
import { getFileIdx } from '@linaria/utils';

export interface IEntrypoint {
code: string;
evalConfig: TransformOptions;
evaluator: Evaluator;
log: Debugger;
name: string;
only: string[];
parseConfig: TransformOptions;
log: Debugger;
pluginOptions: StrictOptions;
}

type Node = [entrypoint: IEntrypoint, stack: string[], refCount?: number];
Expand Down
47 changes: 22 additions & 25 deletions packages/shaker/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,60 +1,57 @@
import type { TransformOptions, PluginItem } from '@babel/core';

import type { Evaluator } from '@linaria/utils';
import { hasEvaluatorMetadata } from '@linaria/utils';
import { getPluginKey, hasEvaluatorMetadata } from '@linaria/utils';

export { default as shakerPlugin } from './plugins/shaker-plugin';

const getKey = (plugin: PluginItem): string | null => {
if (typeof plugin === 'string') {
return plugin;
}

if (Array.isArray(plugin)) {
return getKey(plugin[0]);
}

if (typeof plugin === 'object' && plugin !== null && 'key' in plugin) {
return (plugin as { key?: string | null }).key ?? null;
}

return null;
};

const hasKeyInList = (plugin: PluginItem, list: string[]): boolean => {
const pluginKey = getKey(plugin);
const pluginKey = getPluginKey(plugin);
return pluginKey ? list.some((i) => pluginKey.includes(i)) : false;
};

const shaker: Evaluator = (
babelOptions,
evalConfig,
ast,
code,
{ highPriorityPlugins, ...config },
babel
) => {
const preShakePlugins =
babelOptions.plugins?.filter((i) => hasKeyInList(i, highPriorityPlugins)) ??
evalConfig.plugins?.filter((i) => hasKeyInList(i, highPriorityPlugins)) ??
[];

const plugins = [
...preShakePlugins,
[require.resolve('./plugins/shaker-plugin'), config],
...(babelOptions.plugins ?? []).filter(
...(evalConfig.plugins ?? []).filter(
(i) => !hasKeyInList(i, highPriorityPlugins)
),
];

const hasCommonjsPlugin = babelOptions.plugins?.some(
(i) => getKey(i) === 'transform-modules-commonjs'
const hasCommonjsPlugin = evalConfig.plugins?.some(
(i) => getPluginKey(i) === 'transform-modules-commonjs'
);

if (!hasCommonjsPlugin) {
plugins.push(require.resolve('@babel/plugin-transform-modules-commonjs'));
}

if (
evalConfig.filename?.endsWith('.ts') ||
evalConfig.filename?.endsWith('.tsx')
) {
const hasTypescriptPlugin = evalConfig.plugins?.some(
(i) => getPluginKey(i) === '@babel/plugin-transform-typescript'
);

if (!hasTypescriptPlugin) {
plugins.push(require.resolve('@babel/plugin-transform-typescript'));
}
}

const transformOptions: TransformOptions = {
...babelOptions,
...evalConfig,
caller: {
name: 'linaria',
},
Expand All @@ -64,7 +61,7 @@ const shaker: Evaluator = (
const transformed = babel.transformFromAstSync(ast, code, transformOptions);

if (!transformed || !hasEvaluatorMetadata(transformed.metadata)) {
throw new Error(`${babelOptions.filename} has no shaker metadata`);
throw new Error(`${evalConfig.filename} has no shaker metadata`);
}

return [
Expand Down
8 changes: 8 additions & 0 deletions packages/testkit/src/babel.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2722,11 +2722,19 @@ describe('strategy shaker', () => {
});

it('respects module-resolver plugin', async () => {
const warn = jest.spyOn(console, 'warn').mockImplementation(() => {});

const { code, metadata } = await transformFile(
resolve(__dirname, './__fixtures__/with-babelrc/index.js'),
[evaluator]
);

expect(warn).toHaveBeenCalledWith(
expect.stringContaining(
'This works for now but will be an error in the future'
)
);
warn.mockRestore();
expect(code).toMatchSnapshot();
expect(metadata).toMatchSnapshot();
});
Expand Down
1 change: 0 additions & 1 deletion packages/testkit/src/prepareCode.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,6 @@ describe('prepareCode', () => {
babel,
entrypoint,
ast,
pluginOptions,
EventEmitter.dummy
);

Expand Down
17 changes: 17 additions & 0 deletions packages/utils/src/getPluginKey.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import type { PluginItem } from '@babel/core';

export const getPluginKey = (plugin: PluginItem): string | null => {
if (typeof plugin === 'string') {
return plugin;
}

if (Array.isArray(plugin)) {
return getPluginKey(plugin[0]);
}

if (typeof plugin === 'object' && plugin !== null && 'key' in plugin) {
return (plugin as { key?: string | null }).key ?? null;
}

return null;
};
1 change: 1 addition & 0 deletions packages/utils/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ export { default as findIdentifiers, nonType } from './findIdentifiers';
export { findPackageJSON } from './findPackageJSON';
export { hasEvaluatorMetadata } from './hasEvaluatorMetadata';
export { default as getFileIdx } from './getFileIdx';
export { getPluginKey } from './getPluginKey';
export { hasMeta } from './hasMeta';
export { getSource } from './getSource';
export { isBoxedPrimitive } from './isBoxedPrimitive';
Expand Down
2 changes: 1 addition & 1 deletion packages/utils/src/options/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ export type EvaluatorConfig = {
};

export type Evaluator = (
babelOptions: TransformOptions,
evalConfig: TransformOptions,
ast: File,
code: string,
config: EvaluatorConfig,
Expand Down

0 comments on commit ae162f4

Please sign in to comment.