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
21 changes: 21 additions & 0 deletions flow-typed/npm/@csstools/postcss-cascade-layers_v5.x.x.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

declare module '@csstools/postcss-cascade-layers' {
import type { Plugin } from 'postcss';
declare type PluginCreator<T> = (opts?: T) => Plugin;
declare type PluginOptions = {
/** Emit a warning when the "revert" keyword is found in your CSS. default: "warn" */
onRevertLayerKeyword?: 'warn' | false,
/** Emit a warning when conditional rules could change the layer order. default: "warn" */
onConditionalRulesChangingLayerOrder?: 'warn' | false,
/** Emit a warning when "layer" is used in "@import". default: "warn" */
onImportLayerRule?: 'warn' | false,
};

declare module.exports: PluginCreator<PluginOptions>;
}
128 changes: 128 additions & 0 deletions flow-typed/npm/postcss_v8.x.x.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

declare module 'postcss' {
declare export type PluginCreator<T> = (opts?: T) => Plugin;

declare export interface Plugin {
postcssPlugin: string;
Once?: (root: Root, postcss: Postcss) => void;
OnceExit?: (root: Root, postcss: Postcss) => void;
Root?: (root: Root, postcss: Postcss) => void;
RootExit?: (root: Root, postcss: Postcss) => void;
AtRule?: (atRule: AtRule, postcss: Postcss) => void;
AtRuleExit?: (atRule: AtRule, postcss: Postcss) => void;
Rule?: (rule: Rule, postcss: Postcss) => void;
RuleExit?: (rule: Rule, postcss: Postcss) => void;
Declaration?: (decl: Declaration, postcss: Postcss) => void;
DeclarationExit?: (decl: Declaration, postcss: Postcss) => void;
Comment?: (comment: Comment, postcss: Postcss) => void;
CommentExit?: (comment: Comment, postcss: Postcss) => void;
}

declare export interface Postcss {
version: string;
plugins: Array<Plugin>;
process: (
css: string | { toString(): string },
opts?: ProcessOptions,
) => Promise<Result>;
(plugins?: Array<Plugin>): Postcss;
}

declare export interface ProcessOptions {
from?: string;
to?: string;
map?: boolean | { inline: boolean, annotation: boolean };
parser?: any;
stringifier?: any;
syntax?: any;
}

declare export interface Result {
css: string;
map: any;
root: Root;
messages: Array<any>;
processor: Postcss;
opts: ProcessOptions;
warnings(): Array<any>;
toString(): string;
}

declare export interface Root {
type: 'root';
nodes: Array<ChildNode>;
source: Source;
raws: any;
parent: null;
lastEach: number;
indexes: { [key: string]: number };
rawCache: { [key: string]: string };
[key: string]: any;
}

declare export interface AtRule {
type: 'atrule';
name: string;
params: string;
nodes?: Array<ChildNode>;
parent: Container;
source: Source;
raws: any;
[key: string]: any;
}

declare export interface Rule {
type: 'rule';
selector: string;
nodes: Array<ChildNode>;
parent: Container;
source: Source;
raws: any;
[key: string]: any;
}

declare export interface Declaration {
type: 'decl';
prop: string;
value: string;
parent: Container;
source: Source;
raws: any;
[key: string]: any;
}

declare export interface Comment {
type: 'comment';
text: string;
parent: Container;
source: Source;
raws: any;
[key: string]: any;
}

declare export interface Container {
type: string;
nodes: Array<ChildNode>;
parent: Container | null;
source: Source;
raws: any;
[key: string]: any;
}

declare export type ChildNode = AtRule | Rule | Declaration | Comment;

declare export interface Source {
start?: { line: number, column: number };
end?: { line: number, column: number };
input: { css: string, id?: string };
}

declare const postcss: Postcss;
declare export default typeof postcss;
}
Original file line number Diff line number Diff line change
Expand Up @@ -94,19 +94,19 @@ export const styles = stylex.create({

describe('@stylexjs/babel-plugin', () => {
describe('[transform] stylexPlugin.processStylexRules', () => {
test('no rules', () => {
test('no rules', async () => {
const { code, metadata } = transform(`
import * as stylex from '@stylexjs/stylex';
`);
expect(code).toMatchInlineSnapshot(
'"import * as stylex from \'@stylexjs/stylex\';"',
);
expect(stylexPlugin.processStylexRules(metadata)).toMatchInlineSnapshot(
'":root, .x1nqdfg0{--x1i1e39s:blue;}"',
);
expect(
await stylexPlugin.processStylexRules(metadata),
).toMatchInlineSnapshot('":root, .x1nqdfg0{--x1i1e39s:blue;}"');
});

test('all rules (useLayers:false)', () => {
test('all rules (useLayers:false)', async () => {
const { code, metadata } = transform(fixture);
expect(code).toMatchInlineSnapshot(`
"import * as stylex from '@stylexjs/stylex';
Expand Down Expand Up @@ -135,25 +135,25 @@ describe('@stylexjs/babel-plugin', () => {
}]
};"
`);
expect(stylexPlugin.processStylexRules(metadata)).toMatchInlineSnapshot(`
expect(await stylexPlugin.processStylexRules(metadata))
.toMatchInlineSnapshot(`
"@property --color { syntax: "*"; inherits: false;}
@keyframes x4ssjuf-B{0%{box-shadow:1px 2px 3px 4px red;color:yellow;}100%{box-shadow:10px 20px 30px 40px green;color:var(--orange);}}
@keyframes x4ssjuf-B{0%{box-shadow:-1px 2px 3px 4px red;color:yellow;}100%{box-shadow:-10px 20px 30px 40px green;color:var(--orange);}}
:root, .x1nqdfg0{--x1i1e39s:blue;}
@keyframes x4ssjuf-B{0%{box-shadow:-1px 2px 3px 4px red;color:yellow;}100%{box-shadow:-10px 20px 30px 40px green;color:var(--orange);}}:root, .x1nqdfg0{--x1i1e39s:blue;}
.x1bg2uv5:not(#\\#){border-color:green}
.xdmqw5o:not(#\\#):not(#\\#){animation-name:x4ssjuf-B}
.xrkmrrc:not(#\\#):not(#\\#){background-color:red}
.xfx01vb:not(#\\#):not(#\\#){color:var(--color)}
html:not([dir='rtl']) .x1skrh0i:not(#\\#):not(#\\#){text-shadow:1px 2px 3px 4px red}
html[dir='rtl'] .x1skrh0i:not(#\\#):not(#\\#){text-shadow:-1px 2px 3px 4px red}
@media (min-width:320px){html:not([dir='rtl']) .x1cmij7u.x1cmij7u:not(#\\#):not(#\\#){text-shadow:10px 20px 30px 40px green}}
@media (min-width:320px){html[dir='rtl'] .x1cmij7u.x1cmij7u:not(#\\#):not(#\\#){text-shadow:-10px 20px 30px 40px green}}
html:not([dir='rtl']):not(#\\#):not(#\\#) .x1skrh0i{text-shadow:1px 2px 3px 4px red}
html[dir='rtl']:not(#\\#):not(#\\#) .x1skrh0i{text-shadow:-1px 2px 3px 4px red}
@media (min-width:320px){html:not([dir='rtl']):not(#\\#):not(#\\#) .x1cmij7u.x1cmij7u{text-shadow:10px 20px 30px 40px green}}
@media (min-width:320px){html[dir='rtl']:not(#\\#):not(#\\#) .x1cmij7u.x1cmij7u{text-shadow:-10px 20px 30px 40px green}}
@media (max-width: 1000px){.xwguixi.xwguixi:not(#\\#):not(#\\#):not(#\\#){border-color:var(--x1i1e39s)}}
@media (max-width: 500px){@media (max-width: 1000px){.x5i7zo.x5i7zo.x5i7zo:not(#\\#):not(#\\#):not(#\\#):not(#\\#){border-color:yellow}}}"
`);
});

test('all rules (useLayers:true)', () => {
test('all rules (useLayers:true)', async () => {
const { code, metadata } = transform(fixture, {
useLayers: true,
});
Expand Down Expand Up @@ -184,7 +184,7 @@ describe('@stylexjs/babel-plugin', () => {
}]
};"
`);
expect(stylexPlugin.processStylexRules(metadata, true))
expect(await stylexPlugin.processStylexRules(metadata, true))
.toMatchInlineSnapshot(`
"
@layer priority1, priority2, priority3, priority4, priority5;
Expand Down
1 change: 1 addition & 0 deletions packages/@stylexjs/babel-plugin/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
"@babel/core": "^7.26.8",
"@babel/traverse": "^7.26.8",
"@babel/types": "^7.26.8",
"@csstools/postcss-cascade-layers": "^5.0.1",
"@dual-bundle/import-meta-resolve": "^4.1.0",
"@stylexjs/stylex": "0.13.1",
"postcss-value-parser": "^4.1.0"
Expand Down
47 changes: 20 additions & 27 deletions packages/@stylexjs/babel-plugin/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ import transformStylexCall, {
} from './visitors/stylex-merge';
import transformStylexProps from './visitors/stylex-props';
import { skipStylexPropsChildren } from './visitors/stylex-props';
import postcss from 'postcss';
import cascadeLayers from '@csstools/postcss-cascade-layers';
import transformStyleXViewTransitionClass from './visitors/stylex-view-transition-class';

const NAME = 'stylex';
Expand Down Expand Up @@ -356,12 +358,12 @@ export type Rule = [
},
number,
];
function processStylexRules(
async function processStylexRules(
rules: Array<Rule>,
useLayers: boolean = false,
): string {
): Promise<string> {
if (rules.length === 0) {
return '';
return Promise.resolve('');
}

const constantRules = rules.filter(
Expand Down Expand Up @@ -470,14 +472,9 @@ function processStylexRules(
)
.flatMap((rule) => {
const { ltr, rtl } = rule;
let ltrRule = ltr,
const ltrRule = ltr,
rtlRule = rtl;

if (!useLayers) {
ltrRule = addSpecificityLevel(ltrRule, index);
rtlRule = rtlRule && addSpecificityLevel(rtlRule, index);
}

return rtlRule
? [
addAncestorSelector(ltrRule, "html:not([dir='rtl'])"),
Expand All @@ -487,14 +484,22 @@ function processStylexRules(
})
.join('\n');

if (!useLayers) {
return `@layer priority${index + 1}{\n${collectedCSS}\n}`;
}
// Don't put @property, @keyframe, @position-try in layers
return useLayers && pri > 0
return pri > 0
? `@layer priority${index + 1}{\n${collectedCSS}\n}`
: collectedCSS;
})
.join('\n');

return header + collectedCSS;
if (!useLayers) {
const css = await transformCollectedCSS(header + collectedCSS);
return css.trim();
}

return Promise.resolve(header + collectedCSS);
}

styleXTransform.processStylexRules = processStylexRules;
Expand Down Expand Up @@ -523,23 +528,11 @@ function addAncestorSelector(
}

/**
* Adds :not(#\#) to bump up specificity. as a polyfill for @layer
* Uses @csstools/postcss-cascade-layers to apply specificity adjustments
* via `:not(#\#)` as a polyfill for CSS @layer at-rules.
*/
function addSpecificityLevel(selector: string, index: number): string {
if (selector.startsWith('@keyframes')) {
return selector;
}
const pseudo = Array.from({ length: index })
.map(() => ':not(#\\#)')
.join('');

const lastOpenCurly = selector.includes('::')
? selector.indexOf('::')
: selector.lastIndexOf('{');
const beforeCurly = selector.slice(0, lastOpenCurly);
const afterCurly = selector.slice(lastOpenCurly);

return `${beforeCurly}${pseudo}${afterCurly}`;
async function transformCollectedCSS(collectedCSS: string): Promise<string> {
return (await postcss([cascadeLayers()]).process(collectedCSS)).css;
}

export type StyleXTransformObj = $ReadOnly<{
Expand Down
4 changes: 2 additions & 2 deletions packages/@stylexjs/rollup-plugin/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -73,10 +73,10 @@ export default function stylexPlugin({
buildStart() {
stylexRules = {};
},
generateBundle(this: PluginContext) {
async generateBundle(this: PluginContext) {
const rules: Array<Rule> = Object.values(stylexRules).flat();
if (rules.length > 0) {
const collectedCSS = stylexBabelPlugin.processStylexRules(
const collectedCSS = await stylexBabelPlugin.processStylexRules(
rules,
useCSSLayers,
);
Expand Down
4 changes: 3 additions & 1 deletion packages/docs/scripts/make-stylex-sheet.js
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,9 @@ async function genSheet() {

const ruleSets = await Promise.all(allFiles.map(transformFile));

const generatedCSS = stylexBabelPlugin.processStylexRules(ruleSets.flat());
const generatedCSS = await stylexBabelPlugin.processStylexRules(
ruleSets.flat(),
);

await mkdirp(path.join(__dirname, '../.stylex/'));

Expand Down