From defbe017ff1bb1d9895de8744e20eb773b01236c Mon Sep 17 00:00:00 2001 From: Brijesh Bittu Date: Thu, 11 Jan 2024 20:09:01 +0530 Subject: [PATCH 1/2] [zero] Add support for css import --- apps/zero-runtime-next-app/src/app/layout.tsx | 9 +- package.json | 2 +- packages/zero-runtime/exports/css.js | 5 + packages/zero-runtime/package.json | 5 +- packages/zero-runtime/src/css.d.ts | 22 +++ packages/zero-runtime/src/css.js | 5 + packages/zero-runtime/src/index.ts | 1 + packages/zero-runtime/src/processors/css.ts | 186 ++++++++++++++++++ packages/zero-runtime/tsup.config.ts | 2 +- pnpm-lock.yaml | 13 +- 10 files changed, 239 insertions(+), 11 deletions(-) create mode 100644 packages/zero-runtime/exports/css.js create mode 100644 packages/zero-runtime/src/css.d.ts create mode 100644 packages/zero-runtime/src/css.js create mode 100644 packages/zero-runtime/src/processors/css.ts diff --git a/apps/zero-runtime-next-app/src/app/layout.tsx b/apps/zero-runtime-next-app/src/app/layout.tsx index 386d6dccbc2cb9..04e531708cbb3f 100644 --- a/apps/zero-runtime-next-app/src/app/layout.tsx +++ b/apps/zero-runtime-next-app/src/app/layout.tsx @@ -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'; @@ -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 ( - + {children} - + ); } diff --git a/package.json b/package.json index 6f0f5bccf969af..8a048d3a9b556c 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/packages/zero-runtime/exports/css.js b/packages/zero-runtime/exports/css.js new file mode 100644 index 00000000000000..5e83514b4dc37a --- /dev/null +++ b/packages/zero-runtime/exports/css.js @@ -0,0 +1,5 @@ +Object.defineProperty(exports, '__esModule', { + value: true, +}); + +exports.default = require('../processors/css').CssProcessor; diff --git a/packages/zero-runtime/package.json b/packages/zero-runtime/package.json index f10bbd6f0b8b11..e26d5d29a18b19 100644 --- a/packages/zero-runtime/package.json +++ b/packages/zero-runtime/package.json @@ -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" @@ -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", @@ -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": [ diff --git a/packages/zero-runtime/src/css.d.ts b/packages/zero-runtime/src/css.d.ts new file mode 100644 index 00000000000000..511f19ab1e154f --- /dev/null +++ b/packages/zero-runtime/src/css.d.ts @@ -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; diff --git a/packages/zero-runtime/src/css.js b/packages/zero-runtime/src/css.js new file mode 100644 index 00000000000000..ac7121318de31a --- /dev/null +++ b/packages/zero-runtime/src/css.js @@ -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.', + ); +} diff --git a/packages/zero-runtime/src/index.ts b/packages/zero-runtime/src/index.ts index 7f2e376b6ce020..ac8805ce428e8f 100644 --- a/packages/zero-runtime/src/index.ts +++ b/packages/zero-runtime/src/index.ts @@ -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'; diff --git a/packages/zero-runtime/src/processors/css.ts b/packages/zero-runtime/src/processors/css.ts new file mode 100644 index 00000000000000..904d35852c430c --- /dev/null +++ b/packages/zero-runtime/src/processors/css.ts @@ -0,0 +1,186 @@ +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'; + +type Primitive = string | number | boolean | null | undefined; + +type TemplateCallback = (params: Record | undefined) => string | number; + +/** + * @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, + * })) + * ``` + * + * + */ +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 | 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); + } +} diff --git a/packages/zero-runtime/tsup.config.ts b/packages/zero-runtime/tsup.config.ts index c87ce3ef6fa047..346f60915c1945 100644 --- a/packages/zero-runtime/tsup.config.ts +++ b/packages/zero-runtime/tsup.config.ts @@ -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 = { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 25a6f52fd164af..8a446f3f30cb1d 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -2338,6 +2338,9 @@ importers: lodash.get: specifier: ^4.4.2 version: 4.4.2 + lodash.merge: + specifier: ^4.6.2 + version: 4.6.2 lodash.set: specifier: ^4.3.2 version: 4.3.2 @@ -2360,6 +2363,9 @@ importers: '@types/lodash.get': specifier: ^4.4.9 version: 4.4.9 + '@types/lodash.merge': + specifier: ^4.6.9 + version: 4.6.9 '@types/lodash.set': specifier: ^4.3.9 version: 4.3.9 @@ -7759,6 +7765,12 @@ packages: '@types/lodash': 4.14.202 dev: true + /@types/lodash.merge@4.6.9: + resolution: {integrity: sha512-23sHDPmzd59kUgWyKGiOMO2Qb9YtqRO/x4IhkgNUiPQ1+5MUVqi6bCZeq9nBJ17msjIMbEIO5u+XW4Kz6aGUhQ==} + dependencies: + '@types/lodash': 4.14.202 + dev: true + /@types/lodash.mergewith@4.6.7: resolution: {integrity: sha512-3m+lkO5CLRRYU0fhGRp7zbsGi6+BZj0uTVSwvcKU+nSlhjA9/QRNfuSGnD2mX6hQA7ZbmcCkzk5h4ZYGOtk14A==} dependencies: @@ -15269,7 +15281,6 @@ packages: /lodash.merge@4.6.2: resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} - dev: true /lodash.mergewith@4.6.2: resolution: {integrity: sha512-GK3g5RPZWTRSeLSpgP8Xhra+pnjBC56q9FZYe1d5RN3TJ35dbkGy3YqBSMbyCrlbi+CM9Z3Jk5yTL7RCsqboyQ==} From 34ce35f0f4ceb3462631d07d96e2792dea8e4512 Mon Sep 17 00:00:00 2001 From: Brijesh Bittu Date: Tue, 16 Jan 2024 13:25:05 +0530 Subject: [PATCH 2/2] Add same features in keyframes function --- apps/zero-runtime-vite-app/src/App.tsx | 8 +++++-- packages/zero-runtime/src/processors/css.ts | 5 +--- .../zero-runtime/src/processors/keyframes.ts | 23 ++++++++++++------- 3 files changed, 22 insertions(+), 14 deletions(-) diff --git a/apps/zero-runtime-vite-app/src/App.tsx b/apps/zero-runtime-vite-app/src/App.tsx index 1819e27088ceda..ec8dfb2bd40fda 100644 --- a/apps/zero-runtime-vite-app/src/App.tsx +++ b/apps/zero-runtime-vite-app/src/App.tsx @@ -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'; @@ -57,7 +57,11 @@ export function App({ isRed }: AppProps) { const [isHorizontal, setIsHorizontal] = React.useState(true); return ( -
+
diff --git a/packages/zero-runtime/src/processors/css.ts b/packages/zero-runtime/src/processors/css.ts index 904d35852c430c..59f2f4f5ecbc62 100644 --- a/packages/zero-runtime/src/processors/css.ts +++ b/packages/zero-runtime/src/processors/css.ts @@ -14,10 +14,7 @@ import deepMerge from 'lodash.merge'; import BaseProcessor from './base-processor'; import type { IOptions } from './styled'; import { cache, css } from '../utils/emotion'; - -type Primitive = string | number | boolean | null | undefined; - -type TemplateCallback = (params: Record | undefined) => string | number; +import type { Primitive, TemplateCallback } from './keyframes'; /** * @description Scope css class generation similar to css from emotion. diff --git a/packages/zero-runtime/src/processors/keyframes.ts b/packages/zero-runtime/src/processors/keyframes.ts index cb8401df388322..217f399b302b84 100644 --- a/packages/zero-runtime/src/processors/keyframes.ts +++ b/packages/zero-runtime/src/processors/keyframes.ts @@ -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 | undefined) => string | number; export class KeyframesProcessor extends BaseProcessor { callParam: CallParam | TemplateParam; @@ -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); } @@ -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);