Skip to content

Commit

Permalink
[zero] Add support for css import (#40541)
Browse files Browse the repository at this point in the history
  • Loading branch information
brijeshb42 authored Jan 18, 2024
1 parent bd2bd9a commit 334f6c4
Show file tree
Hide file tree
Showing 12 changed files with 257 additions and 21 deletions.
9 changes: 2 additions & 7 deletions apps/zero-runtime-next-app/src/app/layout.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import type { Metadata } from 'next';
import { styled } from '@mui/zero-runtime';
import { Inter } from 'next/font/google';
import '@mui/zero-runtime/styles.css';

Expand All @@ -12,14 +11,10 @@ export const metadata: Metadata = {
description: 'Generated by create next app',
};

const Html = styled.html({
color: 'red',
});

export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<Html lang="en">
<html lang="en">
<body className={inter.className}>{children}</body>
</Html>
</html>
);
}
8 changes: 6 additions & 2 deletions apps/zero-runtime-vite-app/src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import * as React from 'react';
import { styled, generateAtomics } from '@mui/zero-runtime';
import { styled, generateAtomics, css } from '@mui/zero-runtime';
import type { Breakpoint } from '@mui/system';
import { Button, bounceAnim } from 'local-ui-lib';
import Slider from './Slider/ZeroSlider';
Expand Down Expand Up @@ -57,7 +57,11 @@ export function App({ isRed }: AppProps) {
const [isHorizontal, setIsHorizontal] = React.useState(true);

return (
<div>
<div
className={css`
color: red;
`}
>
<ShowCaseDiv>
<Button>This button&apos;s text color has been overridden.</Button>
</ShowCaseDiv>
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@
"typescript:ci": "lerna run --concurrency 5 --no-bail --no-sort typescript",
"validate-declarations": "tsx scripts/validateTypescriptDeclarations.mts",
"generate-codeowners": "node scripts/generateCodeowners.mjs",
"watch:zero": "lerna watch -- lerna run watch --scope=$LERNA_PACKAGE_NAME"
"watch:zero": "nx run-many -t watch --projects=\"@mui/zero-*\" --parallel"
},
"dependencies": {
"@googleapis/sheets": "^5.0.5",
Expand Down
5 changes: 5 additions & 0 deletions packages/zero-runtime/exports/css.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
Object.defineProperty(exports, '__esModule', {
value: true,
});

exports.default = require('../processors/css').CssProcessor;
5 changes: 4 additions & 1 deletion packages/zero-runtime/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
"@linaria/tags": "^5.0.2",
"@linaria/utils": "^5.0.2",
"@mui/system": "workspace:^",
"lodash.merge": "^4.6.2",
"lodash.set": "^4.3.2",
"lodash.get": "^4.4.2",
"stylis": "^4.2.0"
Expand All @@ -36,6 +37,7 @@
"@types/babel__helper-plugin-utils": "^7.10.3",
"@types/cssesc": "^3.0.2",
"@types/lodash.get": "^4.4.9",
"@types/lodash.merge": "^4.6.9",
"@types/lodash.set": "^4.3.9",
"@types/node": "^18.19.7",
"@types/react": "^18.2.31",
Expand All @@ -55,7 +57,8 @@
"default": "./exports/styled.js",
"sx": "./exports/sx.js",
"keyframes": "./exports/keyframes.js",
"generateAtomics": "./exports/generateAtomics.js"
"generateAtomics": "./exports/generateAtomics.js",
"css": "./exports/css.js"
}
},
"files": [
Expand Down
22 changes: 22 additions & 0 deletions packages/zero-runtime/src/css.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import type { CSSObjectNoCallback } from './base';
import type { ThemeArgs } from './theme';

type Primitve = string | null | undefined | boolean | number;

type CssArg = ((themeArgs: ThemeArgs) => CSSObjectNoCallback) | CSSObjectNoCallback;
type CssFn = (themeArgs: ThemeArgs) => string | number;

interface Css {
/**
* @returns {string} The generated css class name to be referenced.
*/
(...arg: CssArg[]): string;
/**
* @returns {string} The generated css class name to be referenced.
*/
(arg: TemplateStringsArray, ...templateArgs: (Primitve | CssFn)[]): string;
}

declare const css: Css;

export default css;
5 changes: 5 additions & 0 deletions packages/zero-runtime/src/css.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export default function css() {
throw new Error(
'MUI: You were trying to call "css" function without configuring your bundler. Make sure to install the bundler specific plugin and use it. @mui/zero-vite-plugin for Vite integration or @mui/zero-next-plugin for Next.js integration.',
);
}
1 change: 1 addition & 0 deletions packages/zero-runtime/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@ export { default as styled, type StyledComponent } from './styled';
export { default as sx } from './sx';
export { default as keyframes } from './keyframes';
export { generateAtomics, atomics } from './generateAtomics';
export { default as css } from './css';
183 changes: 183 additions & 0 deletions packages/zero-runtime/src/processors/css.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,183 @@
import type { Expression } from '@babel/types';
import { validateParams } from '@linaria/tags';
import type {
CallParam,
TemplateParam,
Params,
TailProcessorParams,
ValueCache,
} from '@linaria/tags';
import type { Replacements, Rules } from '@linaria/utils';
import { ValueType } from '@linaria/utils';
import type { CSSInterpolation } from '@emotion/css';
import deepMerge from 'lodash.merge';
import BaseProcessor from './base-processor';
import type { IOptions } from './styled';
import { cache, css } from '../utils/emotion';
import type { Primitive, TemplateCallback } from './keyframes';

/**
* @description Scope css class generation similar to css from emotion.
*
* @example
* ```ts
* import { css } from '@mui/zero-runtime';
*
* const class1 = css(({theme}) => ({
* color: (theme.vars || theme).palette.primary.main,
* }))
* ```
*
* <html className={class1} />
*/
export class CssProcessor extends BaseProcessor {
callParam: CallParam | TemplateParam;

constructor(params: Params, ...args: TailProcessorParams) {
super(params, ...args);
if (params.length < 2) {
throw BaseProcessor.SKIP;
}
validateParams(
params,
['callee', ['call', 'template']],
`Invalid use of ${this.tagSource.imported} tag.`,
);

const [, callParams] = params;
if (callParams[0] === 'call') {
const [, ...callArgs] = callParams;
this.dependencies.push(...callArgs);
} else if (callParams[0] === 'template') {
callParams[1].forEach((element) => {
if ('kind' in element && element.kind !== ValueType.CONST) {
this.dependencies.push(element);
}
});
}
this.callParam = callParams;
}

build(values: ValueCache) {
if (this.artifacts.length > 0) {
throw new Error(`MUI: "${this.tagSource.imported}" is already built`);
}

const [callType] = this.callParam;

if (callType === 'template') {
this.handleTemplate(this.callParam, values);
} else {
this.handleCall(this.callParam, values);
}
}

private handleTemplate([, callArgs]: TemplateParam, values: ValueCache) {
const templateStrs: string[] = [];
// @ts-ignore @TODO - Fix this. No idea how to initialize a Tagged String array.
templateStrs.raw = [];
const templateExpressions: Primitive[] = [];
const { themeArgs } = this.options as IOptions;

callArgs.forEach((item) => {
if ('kind' in item) {
switch (item.kind) {
case ValueType.FUNCTION: {
const value = values.get(item.ex.name) as TemplateCallback;
templateExpressions.push(value(themeArgs));
break;
}
case ValueType.CONST:
templateExpressions.push(item.value);
break;
case ValueType.LAZY: {
const evaluatedValue = values.get(item.ex.name);
if (typeof evaluatedValue === 'function') {
templateExpressions.push(evaluatedValue(themeArgs));
} else {
templateExpressions.push(evaluatedValue as Primitive);
}
break;
}
default:
break;
}
} else if (item.type === 'TemplateElement') {
templateStrs.push(item.value.cooked as string);
// @ts-ignore
templateStrs.raw.push(item.value.raw);
}
});
this.generateArtifacts(templateStrs, ...templateExpressions);
}

generateArtifacts(styleObjOrTaggged: CSSInterpolation | string[], ...args: Primitive[]) {
const cssClassName = css(styleObjOrTaggged, ...args);
const cssText = cache.registered[cssClassName] as string;

const rules: Rules = {
[this.asSelector]: {
className: this.className,
cssText,
displayName: this.displayName,
start: this.location?.start ?? null,
},
};
const sourceMapReplacements: Replacements = [
{
length: cssText.length,
original: {
start: {
column: this.location?.start.column ?? 0,
line: this.location?.start.line ?? 0,
},
end: {
column: this.location?.end.column ?? 0,
line: this.location?.end.line ?? 0,
},
},
},
];
this.artifacts.push(['css', [rules, sourceMapReplacements]]);
}

private handleCall([, ...callArgs]: CallParam, values: ValueCache) {
const mergedStyleObj: CSSInterpolation = {};

callArgs.forEach((callArg) => {
let styleObj: CSSInterpolation;
if (callArg.kind === ValueType.LAZY) {
styleObj = values.get(callArg.ex.name) as CSSInterpolation;
} else if (callArg.kind === ValueType.FUNCTION) {
const { themeArgs } = this.options as IOptions;
const value = values.get(callArg.ex.name) as (
args: Record<string, unknown> | undefined,
) => CSSInterpolation;
styleObj = value(themeArgs);
}

if (styleObj) {
deepMerge(mergedStyleObj, styleObj);
}
});
if (Object.keys(mergedStyleObj).length > 0) {
this.generateArtifacts(mergedStyleObj);
}
}

doEvaltimeReplacement() {
this.replacer(this.value, false);
}

doRuntimeReplacement() {
this.doEvaltimeReplacement();
}

get asSelector() {
return `.${this.className}`;
}

get value(): Expression {
return this.astService.stringLiteral(this.className);
}
}
23 changes: 15 additions & 8 deletions packages/zero-runtime/src/processors/keyframes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,9 @@ import BaseProcessor from './base-processor';
import type { IOptions } from './styled';
import { cache, keyframes } from '../utils/emotion';

type Primitive = string | number | boolean | null | undefined;
export type Primitive = string | number | boolean | null | undefined;

export type TemplateCallback = (params: Record<string, unknown> | undefined) => string | number;

export class KeyframesProcessor extends BaseProcessor {
callParam: CallParam | TemplateParam;
Expand Down Expand Up @@ -59,23 +61,26 @@ export class KeyframesProcessor extends BaseProcessor {

private handleTemplate([, callArgs]: TemplateParam, values: ValueCache) {
const templateStrs: string[] = [];
// @ts-ignore @TODO - Fix this. No idea how to initialize a Tagged String array.
templateStrs.raw = [];
const templateExpressions: Primitive[] = [];
const { themeArgs } = this.options as IOptions;

callArgs.forEach((item) => {
if ('kind' in item) {
switch (item.kind) {
case ValueType.FUNCTION:
throw item.buildCodeFrameError(
'Functions are not allowed to be interpolated in keyframes tag.',
);
case ValueType.FUNCTION: {
const value = values.get(item.ex.name) as TemplateCallback;
templateExpressions.push(value(themeArgs));
break;
}
case ValueType.CONST:
templateExpressions.push(item.value);
break;
case ValueType.LAZY: {
const evaluatedValue = values.get(item.ex.name);
if (typeof evaluatedValue === 'function') {
throw item.buildCodeFrameError(
'Functions are not allowed to be interpolated in keyframes tag.',
);
templateExpressions.push(evaluatedValue(themeArgs));
} else {
templateExpressions.push(evaluatedValue as Primitive);
}
Expand All @@ -86,6 +91,8 @@ export class KeyframesProcessor extends BaseProcessor {
}
} else if (item.type === 'TemplateElement') {
templateStrs.push(item.value.cooked as string);
// @ts-ignore
templateStrs.raw.push(item.value.raw);
}
});
this.generateArtifacts(templateStrs, ...templateExpressions);
Expand Down
2 changes: 1 addition & 1 deletion packages/zero-runtime/tsup.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { Options, defineConfig } from 'tsup';
import config from '../../tsup.config';
import packageJson from './package.json';

const processors = ['styled', 'sx', 'keyframes', 'generateAtomics'];
const processors = ['styled', 'sx', 'keyframes', 'generateAtomics', 'css'];
const external = ['react', 'react-is', 'prop-types'];

const baseConfig: Options = {
Expand Down
Loading

0 comments on commit 334f6c4

Please sign in to comment.