Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[zero] Add support for css import #40541

Merged
merged 2 commits into from
Jan 18, 2024
Merged
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
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;
brijeshb42 marked this conversation as resolved.
Show resolved Hide resolved
}

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';
siriwatknp marked this conversation as resolved.
Show resolved Hide resolved
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);
brijeshb42 marked this conversation as resolved.
Show resolved Hide resolved
}
});
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
Loading