From 13f56ef3b50d7b3d0457f66171933c73569ffd56 Mon Sep 17 00:00:00 2001 From: Naman Goel Date: Sat, 11 Jan 2025 14:22:51 -0800 Subject: [PATCH 1/3] fix: cli should rewrite the aliases --- .flowconfig | 2 +- .../babel-plugin/src/utils/state-manager.js | 11 +- packages/cli/src/config.js | 4 +- packages/cli/src/index.js | 5 +- packages/cli/src/plugins.js | 147 +++++++++++++++++- packages/cli/src/transform.js | 10 +- 6 files changed, 166 insertions(+), 13 deletions(-) diff --git a/.flowconfig b/.flowconfig index 456aa3529..cd5ec42c7 100644 --- a/.flowconfig +++ b/.flowconfig @@ -30,7 +30,7 @@ module.name_mapper='^@stylexjs\/shared\/lib\/\([a-zA-Z0-9_\-]+\)$' -> ' '/packages/stylex/src/stylex.js' module.name_mapper='^@stylexjs/stylex\/lib\/\([a-zA-Z0-9_\-]+\)$' -> '/packages/stylex/src/\1' module.name_mapper='^@stylexjs/babel-plugin$' -> '/packages/babel-plugin/src/index.js' -module.name_mapper='^@stylexjs/babel-plugin\/lib\/\([a-zA-Z0-9_\-]+\)$' -> '/packages/babel-plugin/src/\1' +module.name_mapper='^@stylexjs/babel-plugin\/lib\/\(.+\)$' -> '/packages/babel-plugin/src/\1' ; type-stubs module.system.node.resolve_dirname=flow_modules module.system.node.resolve_dirname=node_modules diff --git a/packages/babel-plugin/src/utils/state-manager.js b/packages/babel-plugin/src/utils/state-manager.js index 70a80d6d1..b842fab01 100644 --- a/packages/babel-plugin/src/utils/state-manager.js +++ b/packages/babel-plugin/src/utils/state-manager.js @@ -671,7 +671,7 @@ const getPossibleFilePaths = (filePath: string) => { // a function that resolves the absolute path of a file when given the // relative path of the file from the source file -const filePathResolver = ( +export const filePathResolver = ( relativeFilePath: string, sourceFilePath: string, aliases: StyleXStateOptions['aliases'], @@ -721,10 +721,11 @@ const addFileExtension = ( return importedFilePath + fileExtension; }; -const matchesFileSuffix = (allowedSuffix: string) => (filename: string) => - ['', ...EXTENSIONS].some((extension) => - filename.endsWith(`${allowedSuffix}${extension}`), - ); +export const matchesFileSuffix = + (allowedSuffix: string) => (filename: string) => + ['', ...EXTENSIONS].some((extension) => + filename.endsWith(`${allowedSuffix}${extension}`), + ); const getProgramPath = (path: NodePath<>): null | NodePath => { let programPath = path; diff --git a/packages/cli/src/config.js b/packages/cli/src/config.js index 15ad9bd61..091551bf5 100644 --- a/packages/cli/src/config.js +++ b/packages/cli/src/config.js @@ -8,7 +8,7 @@ * @flow strict */ -import type { Rule } from '@stylexjs/babel-plugin'; +import type { Rule, Options as StyleXOptions } from '@stylexjs/babel-plugin'; export type ModuleType = | string @@ -24,7 +24,7 @@ export type CliConfig = { babelPluginsPost?: $ReadOnlyArray, modules_EXPERIMENTAL: $ReadOnlyArray, useCSSLayers?: boolean, - styleXConfig?: { +[string]: mixed }, + styleXConfig?: StyleXOptions, }; export type TransformConfig = { diff --git a/packages/cli/src/index.js b/packages/cli/src/index.js index 35f4a868e..aa8fbf15e 100755 --- a/packages/cli/src/index.js +++ b/packages/cli/src/index.js @@ -8,7 +8,7 @@ * @flow strict */ -import type { Rule } from '@stylexjs/babel-plugin'; +import type { Rule, Options as StyleXOptions } from '@stylexjs/babel-plugin'; import yargs from 'yargs'; import path from 'path'; import ansis from 'ansis'; @@ -71,8 +71,7 @@ const babelPresets: $ReadOnlyArray = args.babelPresets; const babelPluginsPre: $ReadOnlyArray = args.babelPluginsPre; const babelPluginsPost: $ReadOnlyArray = args.babelPluginsPost; const useCSSLayers: boolean = args.useCSSLayers; -const styleXConfig: { +[string]: mixed } = - (config.styleXConfig as $FlowFixMe) ?? {}; +const styleXConfig: StyleXOptions = (config.styleXConfig as $FlowFixMe) ?? {}; const cliArgsConfig: CliConfig = { input, diff --git a/packages/cli/src/plugins.js b/packages/cli/src/plugins.js index cbd69e691..52827fe8b 100644 --- a/packages/cli/src/plugins.js +++ b/packages/cli/src/plugins.js @@ -12,12 +12,17 @@ import type { NodePath } from '@babel/traverse'; import { getRelativePath } from './files'; import { findModuleDir } from './modules'; import * as t from '@babel/types'; +import { moduleResolve } from '@dual-bundle/import-meta-resolve'; +import url from 'url'; import * as nodePath from 'path'; type ImportModifierPlugin = $ReadOnly<{ visitor: { - Program: { enter(path: NodePath): void }, + Program: { + enter?: (path: NodePath) => void, + exit?: (path: NodePath) => void, + }, }, }>; @@ -116,3 +121,143 @@ export const createModuleImportModifierPlugin = ( }, }; }; + +export const createAliasRewritePlugin = ( + sourceFilePath: string, + aliasConfig: $ReadOnly<{ [string]: string | $ReadOnlyArray }>, +): ImportModifierPlugin => { + return { + visitor: { + Program: { + exit(path: NodePath) { + path.traverse({ + ImportDeclaration: { + enter(path: NodePath) { + const source = path.node.source.value; + + const aliases = + aliasConfig == null + ? aliasConfig + : Object.fromEntries( + Object.entries(aliasConfig).map(([key, value]) => { + if (typeof value === 'string') { + return [key, [value]]; + } + return [key, value]; + }), + ); + + const themeFileExtension = '.stylex'; + if (!matchesFileSuffix(themeFileExtension)(source)) { + return; + } + const resolvedFilePath = filePathResolver( + source, + sourceFilePath, + aliases, + ); + + if (resolvedFilePath == null) { + return; + } + + let relativeFilePath = getRelativePath( + sourceFilePath, + resolvedFilePath, + ); + + const extension = EXTENSIONS.find((ext) => + relativeFilePath.endsWith(ext), + ); + if (extension != null) { + relativeFilePath = relativeFilePath.slice( + 0, + -extension.length, + ); + } + + path.node.source.value = relativeFilePath; + }, + }, + }); + }, + }, + }, + }; +}; + +const matchesFileSuffix = (allowedSuffix: string) => (filename: string) => + filename.endsWith(`${allowedSuffix}.js`) || + filename.endsWith(`${allowedSuffix}.ts`) || + filename.endsWith(`${allowedSuffix}.tsx`) || + filename.endsWith(`${allowedSuffix}.jsx`) || + filename.endsWith(`${allowedSuffix}.mjs`) || + filename.endsWith(`${allowedSuffix}.cjs`) || + filename.endsWith(allowedSuffix); + +const EXTENSIONS = ['.js', '.ts', '.tsx', '.jsx', '.mjs', '.cjs']; +const filePathResolver = ( + relativeFilePath: string, + sourceFilePath: string, + aliases: $ReadOnly<{ [string]: $ReadOnlyArray }>, +): ?string => { + // Try importing without adding any extension + // and then every supported extension + for (const ext of ['', ...EXTENSIONS]) { + const importPathStr = relativeFilePath + ext; + + // Try to resolve relative paths as is + if (importPathStr.startsWith('.')) { + try { + return moduleResolve(importPathStr, url.pathToFileURL(sourceFilePath)) + .pathname; + } catch { + continue; + } + } + + // Otherwise, try to resolve the path with aliases + const allAliases = possibleAliasedPaths(importPathStr, aliases); + for (const possiblePath of allAliases) { + try { + return moduleResolve(possiblePath, url.pathToFileURL(sourceFilePath)) + .pathname; + } catch { + continue; + } + } + } + // Failed to resolve the file path + return null; +}; + +function possibleAliasedPaths( + importPath: string, + aliases: $ReadOnly<{ [string]: $ReadOnlyArray }>, +): $ReadOnlyArray { + const result = [importPath]; + if (aliases == null || Object.keys(aliases).length === 0) { + return result; + } + + for (const [alias, value] of Object.entries(aliases)) { + if (alias.includes('*')) { + const [before, after] = alias.split('*'); + if (importPath.startsWith(before) && importPath.endsWith(after)) { + const replacementString = importPath.slice( + before.length, + after.length > 0 ? -after.length : undefined, + ); + value.forEach((v) => { + result.push(v.split('*').join(replacementString)); + }); + } + } else if (alias === importPath) { + value.forEach((v) => { + result.push(v); + }); + } + } + + return result; +} diff --git a/packages/cli/src/transform.js b/packages/cli/src/transform.js index 75ee8d7c3..fa6c6335f 100644 --- a/packages/cli/src/transform.js +++ b/packages/cli/src/transform.js @@ -32,6 +32,7 @@ import { getDefaultCachePath, } from './cache'; import { + createAliasRewritePlugin, createImportPlugin, createModuleImportModifierPlugin, } from './plugins'; @@ -160,6 +161,8 @@ export async function transformFile( : path.join(config.output, config.styleXBundleName), ); + const aliases = config.styleXConfig?.aliases; + const result = await babel.transformFileAsync(inputFilePath, { babelrc: false, presets: config.babelPresets, @@ -178,7 +181,12 @@ export async function transformFile( }, ], createImportPlugin(relativeImport), - ...(config.babelPluginsPost ?? []), + ...[ + aliases != null + ? createAliasRewritePlugin(inputFilePath, aliases) + : null, + ...(config.babelPluginsPost ?? []), + ].filter(Boolean), ], }); if (result == null) { From 3057f7528bf67f06ffe9e6e1213d832cd7724aac Mon Sep 17 00:00:00 2001 From: Naman Goel Date: Sun, 12 Jan 2025 19:27:12 -0800 Subject: [PATCH 2/3] fix: move alias rewriting to babel plugin --- packages/babel-plugin/src/index.js | 44 ++++++ .../babel-plugin/src/utils/state-manager.js | 21 ++- packages/cli/src/plugins.js | 142 ------------------ packages/cli/src/transform.js | 11 +- 4 files changed, 66 insertions(+), 152 deletions(-) diff --git a/packages/babel-plugin/src/index.js b/packages/babel-plugin/src/index.js index 106ffc1a8..5361066b6 100644 --- a/packages/babel-plugin/src/index.js +++ b/packages/babel-plugin/src/index.js @@ -12,6 +12,12 @@ import type { NodePath } from '@babel/traverse'; import type { PluginObj } from '@babel/core'; import type { StyleXOptions } from './utils/state-manager'; import StateManager from './utils/state-manager'; +import { + EXTENSIONS, + filePathResolver, + matchesFileSuffix, + getRelativePath, +} from './utils/state-manager'; import { readImportDeclarations, readRequires } from './visitors/imports'; import transformStyleXCreate from './visitors/stylex-create'; import transformStyleXDefineVars from './visitors/stylex-define-vars'; @@ -74,6 +80,44 @@ function styleXTransform(): PluginObj<> { // variables entirely if they're not needed. exit: (path: NodePath) => { path.traverse({ + ImportDeclaration(path: NodePath) { + const filename = state.filename; + if (filename == null || !state.options.rewriteAliases) { + return; + } + + const source = path.node.source.value; + + const aliases = state.options.aliases; + + const themeFileExtension = '.stylex'; + if (!matchesFileSuffix(themeFileExtension)(source)) { + return; + } + const resolvedFilePath = filePathResolver( + source, + filename, + aliases, + ); + + if (resolvedFilePath == null) { + return; + } + + let relativeFilePath = getRelativePath( + filename, + resolvedFilePath, + ); + + const extension = EXTENSIONS.find((ext) => + relativeFilePath.endsWith(ext), + ); + if (extension != null) { + relativeFilePath = relativeFilePath.slice(0, -extension.length); + } + + path.node.source.value = relativeFilePath; + }, Identifier(path: NodePath) { // Look for variables bound to `stylex.create` calls that are used // outside of `stylex(...)` calls diff --git a/packages/babel-plugin/src/utils/state-manager.js b/packages/babel-plugin/src/utils/state-manager.js index b842fab01..15b4dc73f 100644 --- a/packages/babel-plugin/src/utils/state-manager.js +++ b/packages/babel-plugin/src/utils/state-manager.js @@ -78,6 +78,7 @@ export type StyleXOptions = $ReadOnly<{ genConditionalClasses: boolean, unstable_moduleResolution?: ?ModuleResolution, aliases?: ?$ReadOnly<{ [string]: string | $ReadOnlyArray }>, + rewriteAliases?: boolean, ... }>; @@ -85,6 +86,7 @@ type StyleXStateOptions = $ReadOnly<{ ...StyleXOptions, runtimeInjection: ?string | $ReadOnly<{ from: string, as: ?string }>, aliases?: ?$ReadOnly<{ [string]: $ReadOnlyArray }>, + rewriteAliases: boolean, ... }>; @@ -289,6 +291,10 @@ export default class StateManager { styleResolution, unstable_moduleResolution, treeshakeCompensation, + rewriteAliases: + typeof options.rewriteAliases === 'boolean' + ? options.rewriteAliases + : false, }; return opts; } @@ -704,7 +710,7 @@ export const filePathResolver = ( return null; }; -const EXTENSIONS = ['.js', '.ts', '.tsx', '.jsx', '.mjs', '.cjs']; +export const EXTENSIONS = ['.js', '.ts', '.tsx', '.jsx', '.mjs', '.cjs']; const addFileExtension = ( importedFilePath: string, @@ -750,3 +756,16 @@ const getProgramStatement = (path: NodePath<>): NodePath<> => { } return programPath; }; + +export function getRelativePath(from: string, to: string): string { + const relativePath = path.relative(path.parse(from).dir, to); + return formatRelativePath(toPosixPath(relativePath)); +} + +function toPosixPath(filePath: string): string { + return filePath.split(path.sep).join(path.posix.sep); +} + +function formatRelativePath(filePath: string) { + return filePath.startsWith('.') ? filePath : './' + filePath; +} diff --git a/packages/cli/src/plugins.js b/packages/cli/src/plugins.js index 52827fe8b..db9e2fdbd 100644 --- a/packages/cli/src/plugins.js +++ b/packages/cli/src/plugins.js @@ -12,8 +12,6 @@ import type { NodePath } from '@babel/traverse'; import { getRelativePath } from './files'; import { findModuleDir } from './modules'; import * as t from '@babel/types'; -import { moduleResolve } from '@dual-bundle/import-meta-resolve'; -import url from 'url'; import * as nodePath from 'path'; @@ -121,143 +119,3 @@ export const createModuleImportModifierPlugin = ( }, }; }; - -export const createAliasRewritePlugin = ( - sourceFilePath: string, - aliasConfig: $ReadOnly<{ [string]: string | $ReadOnlyArray }>, -): ImportModifierPlugin => { - return { - visitor: { - Program: { - exit(path: NodePath) { - path.traverse({ - ImportDeclaration: { - enter(path: NodePath) { - const source = path.node.source.value; - - const aliases = - aliasConfig == null - ? aliasConfig - : Object.fromEntries( - Object.entries(aliasConfig).map(([key, value]) => { - if (typeof value === 'string') { - return [key, [value]]; - } - return [key, value]; - }), - ); - - const themeFileExtension = '.stylex'; - if (!matchesFileSuffix(themeFileExtension)(source)) { - return; - } - const resolvedFilePath = filePathResolver( - source, - sourceFilePath, - aliases, - ); - - if (resolvedFilePath == null) { - return; - } - - let relativeFilePath = getRelativePath( - sourceFilePath, - resolvedFilePath, - ); - - const extension = EXTENSIONS.find((ext) => - relativeFilePath.endsWith(ext), - ); - if (extension != null) { - relativeFilePath = relativeFilePath.slice( - 0, - -extension.length, - ); - } - - path.node.source.value = relativeFilePath; - }, - }, - }); - }, - }, - }, - }; -}; - -const matchesFileSuffix = (allowedSuffix: string) => (filename: string) => - filename.endsWith(`${allowedSuffix}.js`) || - filename.endsWith(`${allowedSuffix}.ts`) || - filename.endsWith(`${allowedSuffix}.tsx`) || - filename.endsWith(`${allowedSuffix}.jsx`) || - filename.endsWith(`${allowedSuffix}.mjs`) || - filename.endsWith(`${allowedSuffix}.cjs`) || - filename.endsWith(allowedSuffix); - -const EXTENSIONS = ['.js', '.ts', '.tsx', '.jsx', '.mjs', '.cjs']; -const filePathResolver = ( - relativeFilePath: string, - sourceFilePath: string, - aliases: $ReadOnly<{ [string]: $ReadOnlyArray }>, -): ?string => { - // Try importing without adding any extension - // and then every supported extension - for (const ext of ['', ...EXTENSIONS]) { - const importPathStr = relativeFilePath + ext; - - // Try to resolve relative paths as is - if (importPathStr.startsWith('.')) { - try { - return moduleResolve(importPathStr, url.pathToFileURL(sourceFilePath)) - .pathname; - } catch { - continue; - } - } - - // Otherwise, try to resolve the path with aliases - const allAliases = possibleAliasedPaths(importPathStr, aliases); - for (const possiblePath of allAliases) { - try { - return moduleResolve(possiblePath, url.pathToFileURL(sourceFilePath)) - .pathname; - } catch { - continue; - } - } - } - // Failed to resolve the file path - return null; -}; - -function possibleAliasedPaths( - importPath: string, - aliases: $ReadOnly<{ [string]: $ReadOnlyArray }>, -): $ReadOnlyArray { - const result = [importPath]; - if (aliases == null || Object.keys(aliases).length === 0) { - return result; - } - - for (const [alias, value] of Object.entries(aliases)) { - if (alias.includes('*')) { - const [before, after] = alias.split('*'); - if (importPath.startsWith(before) && importPath.endsWith(after)) { - const replacementString = importPath.slice( - before.length, - after.length > 0 ? -after.length : undefined, - ); - value.forEach((v) => { - result.push(v.split('*').join(replacementString)); - }); - } - } else if (alias === importPath) { - value.forEach((v) => { - result.push(v); - }); - } - } - - return result; -} diff --git a/packages/cli/src/transform.js b/packages/cli/src/transform.js index fa6c6335f..14c297a83 100644 --- a/packages/cli/src/transform.js +++ b/packages/cli/src/transform.js @@ -32,7 +32,6 @@ import { getDefaultCachePath, } from './cache'; import { - createAliasRewritePlugin, createImportPlugin, createModuleImportModifierPlugin, } from './plugins'; @@ -161,8 +160,6 @@ export async function transformFile( : path.join(config.output, config.styleXBundleName), ); - const aliases = config.styleXConfig?.aliases; - const result = await babel.transformFileAsync(inputFilePath, { babelrc: false, presets: config.babelPresets, @@ -178,15 +175,11 @@ export async function transformFile( rootDir: path.parse(config.output).dir, }, ...(config.styleXConfig as $FlowFixMe), + rewriteAliases: true, }, ], createImportPlugin(relativeImport), - ...[ - aliases != null - ? createAliasRewritePlugin(inputFilePath, aliases) - : null, - ...(config.babelPluginsPost ?? []), - ].filter(Boolean), + ...(config.babelPluginsPost ?? []), ], }); if (result == null) { From 4af138d99b10174b4bff136131783075da7ac571 Mon Sep 17 00:00:00 2001 From: Naman Goel Date: Sun, 12 Jan 2025 19:43:03 -0800 Subject: [PATCH 3/3] fix: type annotation for export --- packages/babel-plugin/src/utils/state-manager.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/babel-plugin/src/utils/state-manager.js b/packages/babel-plugin/src/utils/state-manager.js index 15b4dc73f..8b1c34e96 100644 --- a/packages/babel-plugin/src/utils/state-manager.js +++ b/packages/babel-plugin/src/utils/state-manager.js @@ -727,8 +727,8 @@ const addFileExtension = ( return importedFilePath + fileExtension; }; -export const matchesFileSuffix = - (allowedSuffix: string) => (filename: string) => +export const matchesFileSuffix: (string) => (string) => boolean = + (allowedSuffix) => (filename) => ['', ...EXTENSIONS].some((extension) => filename.endsWith(`${allowedSuffix}${extension}`), );