Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/quiet-imports-resolve.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@compiled/babel-plugin': patch
---

Resolve module imports relative to the transformed file when resolving bindings.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this issue specific to compiled plugin? Or does it need to be ported over atlaspack SWC plugins?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

asked AI saying not needed :)
Screenshot 2026-07-15 at 10 39 32 am

4 changes: 3 additions & 1 deletion packages/babel-plugin/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,9 @@
"@compiled/css": "^0.22.0",
"@compiled/utils": "^0.13.1",
"@emotion/is-prop-valid": "^1.3.1",
"resolve": "^1.22.12"
"enhanced-resolve": "^5.18.1",
"resolve": "^1.22.12",
"resolve-from": "^5.0.0"
},
"devDependencies": {
"@compiled-private/module-a": "^0.1.0",
Expand Down
117 changes: 117 additions & 0 deletions packages/babel-plugin/src/resolver/base-resolver.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
/**
* Base resolver, used by tool-specific resolvers.
* This is used to make sure that packages resolve using the same algorithm as our webpack config
* (checking for "browser", "main", etc) meaning that we dont need the old root index.js hack anymore
*/
const fs = require('fs');
const path = require('path');

const enhancedResolve = require('enhanced-resolve');
const resolveFrom = require('resolve-from');

const BASE_DIR = path.resolve(__dirname, '..', '..', '..');

const requireCache = new Map();

const cached = (key, fn) => {
if (requireCache.has(key)) {
return requireCache.get(key);
}
const result = fn();
requireCache.set(key, result);
return result;
};

// This is the resolver used by webpack, which we configure similarly
// to AK website (see ./website/webpack.config.js - "resolve" field)
const wpResolver = enhancedResolve.ResolverFactory.createResolver({
fileSystem: new enhancedResolve.CachedInputFileSystem(fs, 4000),
useSyncFileSystemCalls: true,
mainFields: ['browser', 'main'],
extensions: ['.js', '.ts', '.tsx', '.json', '.d.ts'],
conditionNames: ['import', 'require', 'types', 'default'],
});

const nodeResolver = enhancedResolve.ResolverFactory.createResolver({
fileSystem: new enhancedResolve.CachedInputFileSystem(fs, 4000),
useSyncFileSystemCalls: true,
mainFields: ['main'],
extensions: ['.js', '.ts', '.tsx', '.json', '.d.ts', '.node'],
conditionNames: ['node', 'import', 'require', 'types', 'default'],
});

const exceptionList = Object.freeze([
/@atlassian\/parcel/,
/@parcel\//,
'parcel',
'self-published',
]);

const resolveNode = (modulePath, opts) => {
for (const exception of exceptionList) {
if (
(typeof exception === 'string' && exception === modulePath) ||
(exception instanceof RegExp && exception.test(modulePath))
) {
return nodeResolver.resolveSync({}, opts.basedir, modulePath);
}
}
return undefined;
};

const resolveModule = (basePath, module) =>
cached(`m:${basePath}/${module}`, () => {
try {
return wpResolver.resolveSync({}, basePath, module);
} catch {
return undefined;
}
});

const followSymLink = (file) =>
// Dereference symlinks to ensure we don't create a separate
// module instance depending on how it was referenced.
// @link https://github.com/facebook/jest/pull/4761
fs.realpathSync(file);

const resolveRelative = (modulePath /*: string */, opts /*: Object */) => {
// If resolving relative paths, make sure we use resolveFrom and not resolve
if (modulePath.startsWith('.') || modulePath.startsWith(path.sep)) {
return cached(`r:${opts.basedir}/${modulePath}`, () => {
try {
// resolveFrom could not "see" .ts/.tsx files
// the only registered extensions are `.js`, `.json` and `.node`
return resolveFrom(opts.basedir, modulePath);
} catch {
return undefined;
}
});
}
return undefined;
};

const resolveNamedEntry = (modulePath /*: string */) => {
return resolveModule(BASE_DIR, modulePath);
};

const resolveAbsolute = (modulePath /*: string */, opts /*: Object */) =>
resolveModule(opts.basedir, modulePath);

/**
* @typedef {Object} Opts - resolver options
* @property {string} basedir - The base directory of the file importing modulePath
*/
/**
* Base resolver
*
* @param {string} modulePath - The module path to be resolved
* @param {Opts} opts - Resolver options
*/
module.exports = function resolver(modulePath /*: string */, opts /*: Object */) {
return (
resolveRelative(modulePath, opts) ||
resolveNamedEntry(modulePath, opts) ||
resolveNode(modulePath, opts) ||
followSymLink(resolveAbsolute(modulePath, opts))
);
};
15 changes: 15 additions & 0 deletions packages/babel-plugin/src/resolver/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
/**
* Default resolver used by @compiled/babel-plugin when a custom resolver option
* is not provided.
*/
const path = require('path');

const baseResolver = require('./base-resolver');

module.exports = {
resolveSync: (context, request) => {
return baseResolver(request, {
basedir: path.dirname(context),
});
},
};
15 changes: 6 additions & 9 deletions packages/babel-plugin/src/utils/resolve-binding.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,20 @@
import fs from 'fs';
import { dirname, join } from 'path';

import { parse } from '@babel/parser';
import type { NodePath, Binding } from '@babel/traverse';
import traverse from '@babel/traverse';
import * as t from '@babel/types';
import { DEFAULT_PARSER_BABEL_PLUGINS } from '@compiled/utils';
import resolve from 'resolve';

import { DEFAULT_CODE_EXTENSIONS } from '../constants';
import type { Metadata } from '../types';
import type { Metadata, Resolver } from '../types';

import { getDefaultExport, getNamedExport, setImportedCompiledImports } from './traversers';
import type { PartialBindingWithMeta, EvaluateExpression } from './types';

// eslint-disable-next-line @typescript-eslint/no-var-requires
const defaultResolver = require('../resolver') as Resolver;

/**
* Will recursively checks if identifier name is coming from destructuring. If yes,
* then will return the resolved identifier. We can look for identifier name
Expand Down Expand Up @@ -176,18 +177,14 @@ const getDestructuredObjectPatternKey = (node: t.ObjectPattern, referenceName: s
return result;
};

const resolveRequest = (request: string, extensions: string[], meta: Metadata) => {
const resolveRequest = (request: string, _extensions: string[], meta: Metadata) => {
const { filename, resolver } = meta.state;
if (!filename) {
throw new Error('Unable to resolve request due to a missing filename, this is probably a bug!');
}

if (!resolver) {
const id = request.charAt(0) === '.' ? join(dirname(filename), request) : request;

return resolve.sync(id, {
extensions,
});
return defaultResolver.resolveSync(filename, request);
}

return resolver.resolveSync(filename, request);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
!node_modules/
!node_modules/@compiled-private/
!node_modules/@compiled-private/source-relative-responsive/
!node_modules/@compiled-private/source-relative-responsive/index.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { join } from 'path';

import { transform } from '../../test-utils';

describe('xcss prop transformation', () => {
Expand Down Expand Up @@ -81,6 +83,61 @@ describe('xcss prop transformation', () => {
`);
});

it('should resolve primitive xcss imports from package exports relative to the transformed file', () => {
// Use a fixture package instead of the real Atlaskit package so this test
// only verifies source-file-relative package resolution, not dependency setup.
const result = transform(
`
import { Box as AkBox, xcss as akXcss } from '@atlaskit/primitives';
import { media as rootMedia } from '@compiled-private/source-relative-responsive';
import { media as subpathMedia } from '@compiled-private/source-relative-responsive/media';

export const CheckboxList = ({ testId = 'checkbox-list' }) => (
<AkBox
xcss={akXcss({
display: 'grid',
gridTemplateColumns: '1fr 1fr',
flexWrap: 'wrap',
flexDirection: 'column',
[rootMedia.above.sm]: {
flexWrap: 'nowrap',
},
[subpathMedia.above.sm]: {
display: 'flex',
},
})}
testId={testId}
/>
);
`,
{ filename: join(__dirname, '__fixtures__', 'checkbox-list.jsx') }
);

expect(result).toMatchInlineSnapshot(`
"import { Box as AkBox, xcss as akXcss } from "@atlaskit/primitives";
import { media as rootMedia } from "@compiled-private/source-relative-responsive";
import { media as subpathMedia } from "@compiled-private/source-relative-responsive/media";
export const CheckboxList = ({ testId = "checkbox-list" }) => (
<AkBox
xcss={akXcss({

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am a bit confused. After the transformation, should this snapshot look like

<AkBox className="..." />

?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

other existing test cases are the same, I think it's because the test util it used which made it run in runtime mode.

display: "grid",
gridTemplateColumns: "1fr 1fr",
flexWrap: "wrap",
flexDirection: "column",
[rootMedia.above.sm]: {
flexWrap: "nowrap",
},
[subpathMedia.above.sm]: {
display: "flex",
},
})}
testId={testId}
/>
);
"
`);
});

it('should allow ternaries', () => {
const result = transform(`
import { cssMap } from '@compiled/react';
Expand Down
Loading