Skip to content

Commit

Permalink
feat: add support for makeResetStyles transforms (#236)
Browse files Browse the repository at this point in the history
* feat: add support for makeResetStyles transforms

* restore to previous condition
  • Loading branch information
layershifter authored Oct 6, 2022
1 parent 48239dc commit b360b56
Show file tree
Hide file tree
Showing 19 changed files with 279 additions and 88 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"type": "minor",
"comment": "feat: add support for makeResetStyles transforms",
"packageName": "@griffel/babel-preset",
"email": "[email protected]",
"dependentChangeType": "patch"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"type": "minor",
"comment": "feat: add support for makeResetStyles transforms",
"packageName": "@griffel/webpack-loader",
"email": "[email protected]",
"dependentChangeType": "patch"
}
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
11 changes: 11 additions & 0 deletions packages/babel-preset/__fixtures__/assets-reset-styles/code.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { makeResetStyles } from '@griffel/react';

import blank from './blank.jpg';
import blankDuplicate from './blank.jpg';
import empty from './empty.jpg';

export const useStyles = makeResetStyles({
backgroundImage: `url(${blank})`,
':hover': { backgroundImage: `url(${blankDuplicate})` },
':focus': { backgroundImage: `url(${empty})` },
});
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
11 changes: 11 additions & 0 deletions packages/babel-preset/__fixtures__/assets-reset-styles/output.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import _asset2 from './empty.jpg';
import _asset from './blank.jpg';
import { __resetStyles } from '@griffel/react';
import blank from './blank.jpg';
import blankDuplicate from './blank.jpg';
import empty from './empty.jpg';
export const useStyles = __resetStyles('ra9m047', null, [
`.ra9m047{background-image:url(${_asset});}`,
`.ra9m047:hover{background-image:url(${_asset});}`,
`.ra9m047:focus{background-image:url(${_asset2});}`,
]);
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
const react_make_styles_1 = require('@griffel/react');

export const useStyles = react_make_styles_1.makeResetStyles({
fontSize: '14px',
lineHeight: 1,
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
const react_make_styles_1 = require('@griffel/react');

export const useStyles = react_make_styles_1.__resetStyles('r1qlvv59', null, [
'.r1qlvv59{font-size:14px;line-height:1;}',
]);
6 changes: 6 additions & 0 deletions packages/babel-preset/__fixtures__/reset-styles/code.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { makeResetStyles } from '@griffel/react';

export const useStyles = makeResetStyles({
color: 'red',
paddingLeft: '4px',
});
5 changes: 5 additions & 0 deletions packages/babel-preset/__fixtures__/reset-styles/output.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { __resetStyles } from '@griffel/react';
export const useStyles = __resetStyles('rjefjbm', 'r7z97ji', [
'.rjefjbm{color:red;padding-left:4px;}',
'.r7z97ji{color:red;padding-right:4px;}',
]);
20 changes: 20 additions & 0 deletions packages/babel-preset/src/transformPlugin.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,20 @@ pluginTester({
},
},

// Reset styles
//
//
{
title: 'reset: default',
fixture: path.resolve(fixturesDir, 'reset-styles', 'code.ts'),
outputFixture: path.resolve(fixturesDir, 'reset-styles', 'output.ts'),
},
{
title: 'reset: assets',
fixture: path.resolve(fixturesDir, 'assets-reset-styles', 'code.ts'),
outputFixture: path.resolve(fixturesDir, 'assets-reset-styles', 'output.ts'),
},

// Imports
//
//
Expand Down Expand Up @@ -182,6 +196,12 @@ pluginTester({
},
},

{
title: 'imports: require() for makeResetStyles',
fixture: path.resolve(fixturesDir, 'require-reset-styles', 'code.ts'),
outputFixture: path.resolve(fixturesDir, 'require-reset-styles', 'output.ts'),
},

// Errors
//
//
Expand Down
176 changes: 121 additions & 55 deletions packages/babel-preset/src/transformPlugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,13 @@ import { NodePath, PluginObj, PluginPass, types as t } from '@babel/core';
import { declare } from '@babel/helper-plugin-utils';
import { Module } from '@linaria/babel-preset';
import shakerEvaluator from '@linaria/shaker';
import { resolveStyleRulesForSlots, CSSRulesByBucket, StyleBucketName, GriffelStyle } from '@griffel/core';
import {
resolveStyleRulesForSlots,
CSSRulesByBucket,
StyleBucketName,
GriffelStyle,
resolveResetStyleRules,
} from '@griffel/core';
import * as path from 'path';

import { normalizeStyleRules } from './assets/normalizeStyleRules';
Expand All @@ -12,45 +18,54 @@ import { evaluatePaths } from './utils/evaluatePaths';
import { BabelPluginOptions } from './types';
import { validateOptions } from './validateOptions';

type FunctionKinds = 'makeStyles' | 'makeResetStyles';

type BabelPluginState = PluginPass & {
importDeclarationPaths?: NodePath<t.ImportDeclaration>[];
requireDeclarationPath?: NodePath<t.VariableDeclarator>;

definitionPaths?: NodePath<t.ObjectExpression>[];
definitionPaths?: { functionKind: FunctionKinds; path: NodePath<t.Expression | t.SpreadElement> }[];
calleePaths?: NodePath<t.Identifier>[];
};

function getDefinitionPathFromMakeStylesCallExpression(
function getDefinitionPathFromCallExpression(
functionKind: FunctionKinds,
callExpression: NodePath<t.CallExpression>,
): NodePath<t.ObjectExpression> {
): NodePath<t.Expression | t.SpreadElement> {
const argumentPaths = callExpression.get('arguments') as NodePath<t.Node>[];
const hasValidArguments = Array.isArray(argumentPaths) && argumentPaths.length === 1;

if (!hasValidArguments) {
throw new Error('makeStyles() function accepts only a single param');
throw callExpression.buildCodeFrameError(`${functionKind}() function accepts only a single param`);
}

const definitionsPath = argumentPaths[0];

if (!definitionsPath.isObjectExpression()) {
throw definitionsPath.buildCodeFrameError('makeStyles() function accepts only an object as a param');
throw definitionsPath.buildCodeFrameError(`${functionKind}() function accepts only an object as a param`);
}

return definitionsPath;
}

/**
* Checks that passed callee imports makesStyles().
* Gets a kind of passed callee.
*/
function isMakeStylesCallee(
path: NodePath<t.Expression | t.V8IntrinsicIdentifier>,
function getCalleeFunctionKind(
path: NodePath<t.Identifier>,
modules: NonNullable<BabelPluginOptions['modules']>,
): path is NodePath<t.Identifier> {
if (path.isIdentifier()) {
return Boolean(modules.find(module => path.referencesImport(module.moduleSource, module.importName)));
): FunctionKinds | null {
for (const module of modules) {
if (path.referencesImport(module.moduleSource, module.importName)) {
return 'makeStyles';
}

if (path.referencesImport(module.moduleSource, module.resetImportName || 'makeResetStyles')) {
return 'makeResetStyles';
}
}

return false;
return null;
}

/**
Expand Down Expand Up @@ -164,39 +179,69 @@ export const transformPlugin = declare<Partial<BabelPluginOptions>, PluginObj<Ba

if (state.definitionPaths) {
// Runs Babel AST processing or module evaluation for Node once for all arguments of makeStyles() calls once
evaluatePaths(programPath, state.file.opts.filename!, state.definitionPaths, pluginOptions);
evaluatePaths(
programPath,
state.file.opts.filename!,
state.definitionPaths.map(p => p.path),
pluginOptions,
);

state.definitionPaths.forEach(definitionPath => {
const callExpressionPath = definitionPath.findParent(parentPath =>
const callExpressionPath = definitionPath.path.findParent(parentPath =>
parentPath.isCallExpression(),
) as NodePath<t.CallExpression>;
const evaluationResult = definitionPath.evaluate();
const evaluationResult = definitionPath.path.evaluate();

if (!evaluationResult.confident) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const deoptPath = (evaluationResult as any).deopt as NodePath | undefined;
throw (deoptPath || definitionPath).buildCodeFrameError(
throw (deoptPath || definitionPath.path).buildCodeFrameError(
'Evaluation of a code fragment failed, this is a bug, please report it',
);
}

const stylesBySlots: Record<string /* slot */, GriffelStyle> = evaluationResult.value;
const [classnamesMapping, cssRulesByBucket] = resolveStyleRulesForSlots(
// Heads up!
// Style rules should be normalized *before* they will be resolved to CSS rules to have deterministic
// results across different build targets.
normalizeStyleRules(
path,
pluginOptions.projectRoot,
// Presence of "state.filename" is validated on `Program.enter()`
state.filename as string,
stylesBySlots,
),
);
const uniqueCSSRules = dedupeCSSRules(cssRulesByBucket);

(callExpressionPath.get('arguments.0') as NodePath).remove();
callExpressionPath.pushContainer('arguments', [astify(classnamesMapping), astify(uniqueCSSRules)]);
if (definitionPath.functionKind === 'makeStyles') {
const stylesBySlots: Record<string /* slot */, GriffelStyle> = evaluationResult.value;
const [classnamesMapping, cssRulesByBucket] = resolveStyleRulesForSlots(
// Heads up!
// Style rules should be normalized *before* they will be resolved to CSS rules to have deterministic
// results across different build targets.
normalizeStyleRules(
path,
pluginOptions.projectRoot,
// Presence of "state.filename" is validated on `Program.enter()`
state.filename as string,
stylesBySlots,
),
);
const uniqueCSSRules = dedupeCSSRules(cssRulesByBucket);

(callExpressionPath.get('arguments.0') as NodePath).remove();
callExpressionPath.pushContainer('arguments', [astify(classnamesMapping), astify(uniqueCSSRules)]);
}

if (definitionPath.functionKind === 'makeResetStyles') {
const styles: GriffelStyle = evaluationResult.value;
const [ltrClassName, rtlClassName, cssRules] = resolveResetStyleRules(
// Heads up!
// Style rules should be normalized *before* they will be resolved to CSS rules to have deterministic
// results across different build targets.
normalizeStyleRules(
path,
pluginOptions.projectRoot,
// Presence of "state.filename" is validated on `Program.enter()`
state.filename as string,
styles,
),
);

(callExpressionPath.get('arguments.0') as NodePath).remove();
callExpressionPath.pushContainer('arguments', [
astify(ltrClassName),
astify(rtlClassName),
astify(cssRules),
]);
}

replaceAssetsWithImports(pluginOptions.projectRoot, state.filename!, programPath, callExpressionPath);
});
Expand All @@ -208,26 +253,31 @@ export const transformPlugin = declare<Partial<BabelPluginOptions>, PluginObj<Ba

specifiers.forEach(specifier => {
if (specifier.isImportSpecifier()) {
// TODO: should use generated modifier to avoid collisions

const importedPath = specifier.get('imported');
const importIdentifierPath = pluginOptions.modules.find(module => {
return (
module.moduleSource === source.node.value &&
// 👆 "moduleSource" should match "importDeclarationPath.source" to skip unrelated ".importName"
importedPath.isIdentifier({ name: module.importName })
);
});

if (importIdentifierPath) {
specifier.replaceWith(t.identifier('__styles'));
for (const module of pluginOptions.modules) {
if (module.moduleSource !== source.node.value) {
// 👆 "moduleSource" should match "importDeclarationPath.source" to skip unrelated ".importName"
continue;
}

if (importedPath.isIdentifier({ name: module.importName })) {
specifier.replaceWith(t.identifier('__styles'));
} else if (importedPath.isIdentifier({ name: module.resetImportName || 'makeResetStyles' })) {
specifier.replaceWith(t.identifier('__resetStyles'));
}
}
}
});
});

if (state.calleePaths) {
state.calleePaths.forEach(calleePath => {
if (calleePath.node.name === 'makeResetStyles') {
calleePath.replaceWith(t.identifier('__resetStyles'));
return;
}

calleePath.replaceWith(t.identifier('__styles'));
});
}
Expand Down Expand Up @@ -261,12 +311,17 @@ export const transformPlugin = declare<Partial<BabelPluginOptions>, PluginObj<Ba

const calleePath = path.get('callee');

if (!isMakeStylesCallee(calleePath, pluginOptions.modules)) {
return;
}
if (calleePath.isIdentifier()) {
const functionKind = getCalleeFunctionKind(calleePath, pluginOptions.modules);

state.definitionPaths!.push(getDefinitionPathFromMakeStylesCallExpression(path));
state.calleePaths!.push(calleePath);
if (functionKind) {
state.definitionPaths!.push({
functionKind,
path: getDefinitionPathFromCallExpression(functionKind, path),
});
state.calleePaths!.push(calleePath);
}
}
},

// eslint-disable-next-line @typescript-eslint/naming-convention
Expand All @@ -283,11 +338,19 @@ export const transformPlugin = declare<Partial<BabelPluginOptions>, PluginObj<Ba
const objectPath = expressionPath.get('object');
const propertyPath = expressionPath.get('property');

const isMakeStylesCall =
objectPath.isIdentifier({ name: (state.requireDeclarationPath.node.id as t.Identifier).name }) &&
propertyPath.isIdentifier({ name: 'makeStyles' });
if (!objectPath.isIdentifier({ name: (state.requireDeclarationPath.node.id as t.Identifier).name })) {
return;
}

let functionKind: FunctionKinds | null = null;

if (propertyPath.isIdentifier({ name: 'makeStyles' })) {
functionKind = 'makeStyles';
} else if (propertyPath.isIdentifier({ name: 'makeResetStyles' })) {
functionKind = 'makeResetStyles';
}

if (!isMakeStylesCall) {
if (!functionKind) {
return;
}

Expand All @@ -297,7 +360,10 @@ export const transformPlugin = declare<Partial<BabelPluginOptions>, PluginObj<Ba
return;
}

state.definitionPaths!.push(getDefinitionPathFromMakeStylesCallExpression(parentPath));
state.definitionPaths!.push({
functionKind,
path: getDefinitionPathFromCallExpression(functionKind, parentPath),
});
state.calleePaths!.push(propertyPath as NodePath<t.Identifier>);
},
},
Expand Down
6 changes: 5 additions & 1 deletion packages/babel-preset/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,11 @@ import type { EvalRule } from '@linaria/babel-preset';

export type BabelPluginOptions = {
/** Defines set of modules and imports handled by a transformPlugin. */
modules?: { moduleSource: string; importName: string }[];
modules?: {
moduleSource: string;
importName: string;
resetImportName?: string;
}[];

/**
* If you need to specify custom Babel configuration, you can pass them here. These options will be used by the
Expand Down
7 changes: 7 additions & 0 deletions packages/webpack-loader/__fixtures__/reset/code.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { makeResetStyles } from '@griffel/react';
import { tokens } from './tokens';

export const useStyles = makeResetStyles({
color: tokens.colorBrandStroke1,
paddingLeft: '4px',
});
Loading

0 comments on commit b360b56

Please sign in to comment.