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

feat: Proof of concept non-component-based css prop. #84

Merged
merged 22 commits into from
Jan 24, 2025
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
16 changes: 9 additions & 7 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,21 @@
"version": "1.136.0",
"license": "ISC",
"scripts": {
"build": "ttsc --build",
"build": "tspc --build",
"test": "yarn workspaces foreach run test",
"semantic-release": "semantic-release",
"codegen": "yarn workspaces foreach run codegen"
},
"workspaces": [
"packages/fast-css-prop",
"packages/truss",
"packages/template-tachyons",
"packages/testing-tachyons",
"packages/testing-tachyons-rn",
"packages/testing-tachyons-emotion",
"packages/testing-tachyons-fela",
"packages/testing-tachyons-mui"
"packages/testing-tachyons-mui",
"packages/app-tachyons-fela-prop"
],
"devDependencies": {
"@semantic-release/changelog": "^6.0.1",
Expand All @@ -26,12 +28,12 @@
"@semantic-release/npm": "^9.0.1",
"@semantic-release/release-notes-generator": "^10.0.3",
"@types/node": "~20.8.8",
"prettier": "^2.7.1",
"prettier": "^2.8.8",
"semantic-release": "^19.0.5",
"ts-node": "^10.9.1",
"ttypescript": "^1.5.15",
"typescript": "^4.8.4",
"typescript-transform-paths": "^3.4.4"
"ts-node": "^10.9.2",
"ts-patch": "^3.3.0",
"typescript": "^5.7.3",
"typescript-transform-paths": "^3.5.3"
},
"packageManager": "[email protected]",
"repository": {
Expand Down
24 changes: 24 additions & 0 deletions packages/app-tachyons-fela-prop/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*

node_modules
dist
dist-ssr
*.local

# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
12 changes: 12 additions & 0 deletions packages/app-tachyons-fela-prop/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Vite + React + TS</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.tsx"></script>
</body>
</html>
29 changes: 29 additions & 0 deletions packages/app-tachyons-fela-prop/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
{
"name": "app-tachyons-fela-prop",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "tsc && vite build",
"preview": "vite preview"
},
"dependencies": {
"@homebound/fast-css-prop": "workspace:*",
"@homebound/truss-testing-tachyons": "workspace:*",
"react": "^19.0.0",
"react-dom": "^19.0.0"
},
"devDependencies": {
"@testing-library/dom": "^10.4.0",
"@testing-library/jest-dom": "^6.6.3",
"@testing-library/react": "^16.2.0",
"@testing-library/user-event": "^14.6.1",
"@types/react": "^18.0.24",
"@types/react-dom": "^18.0.8",
"@vitejs/plugin-react-swc": "^3.7.2",
"typescript": "^5.7.3",
"vite": "^6.0.11",
"vitest": "^3.0.4"
}
}
54 changes: 54 additions & 0 deletions packages/app-tachyons-fela-prop/src/App.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import { cleanup, render, screen } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import { afterEach, describe, expect, test, vi } from "vitest";
import { App } from "./App";
import "@testing-library/jest-dom/vitest";

if (process.env.JB_IDE_PORT) {
vi.setConfig({ testTimeout: 100_000 });
}

describe("App", () => {
test("renders hello world", () => {
const r = render(<App />);
expect(screen.getByText(/Vite1/)).toBeInTheDocument();
expect(r.baseElement).toMatchInlineSnapshot(`
<body>
<div>
<div>
<h1
class="a b"
>
Vite1
</h1>
<h1
style="color: rgb(53, 53, 53);"
>
Vite2
</h1>
<div>
<button>
count is
0
</button>
</div>
</div>
</div>
</body>
`);
});

test("clicking button increments counter", async () => {
render(<App />);
const user = userEvent.setup();

const button = screen.getByRole("button");
await user.click(button);

expect(screen.getByText("count is 1")).toBeInTheDocument();
});
});

afterEach(() => {
cleanup();
});
15 changes: 15 additions & 0 deletions packages/app-tachyons-fela-prop/src/App.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { useState } from "react";
import { Css } from "@homebound/truss-testing-tachyons";

export function App() {
const [count, setCount] = useState(0);
return (
<div>
<h1 css={Css.black.top(count + 1).$}>Vite1</h1>
<h1 style={Css.black.$}>Vite2</h1>
<div>
<button onClick={() => setCount((count) => count + 1)}>count is {count}</button>
</div>
</div>
);
}
33 changes: 33 additions & 0 deletions packages/app-tachyons-fela-prop/src/main.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import React from "react";
import ReactDOM from "react-dom/client";
import { App } from "./App";

// Create a MutationObserver
const observer = new MutationObserver((mutationsList) => {
for (const mutation of mutationsList) {
// Check added nodes for stylesheets
if (mutation.type === "childList") {
mutation.addedNodes.forEach((node: any) => {
if (
(node.nodeType === Node.ELEMENT_NODE && node.tagName === "LINK" && node.rel === "stylesheet") ||
node.tagName === "STYLE"
) {
console.log("New stylesheet added:", node);
}
});
}
}
});
// Observer configuration
const config = { childList: true, subtree: true };
// Start observing the <head> and <body> for new stylesheets
observer.observe(document.head, config);
observer.observe(document.body, config);

// Stop observing if needed with: observer.disconnect();

ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render(
<React.StrictMode>
<App />
</React.StrictMode>,
);
1 change: 1 addition & 0 deletions packages/app-tachyons-fela-prop/src/vite-env.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
/// <reference types="vite/client" />
21 changes: 21 additions & 0 deletions packages/app-tachyons-fela-prop/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
{
"compilerOptions": {
"target": "ESNext",
"useDefineForClassFields": true,
"lib": ["DOM", "DOM.Iterable", "ESNext"],
"allowJs": false,
"skipLibCheck": true,
"esModuleInterop": false,
"allowSyntheticDefaultImports": true,
"strict": true,
"forceConsistentCasingInFileNames": true,
"module": "ESNext",
"moduleResolution": "Node",
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "react-jsx"
},
"include": ["src"],
"references": [{ "path": "./tsconfig.node.json" }]
}
9 changes: 9 additions & 0 deletions packages/app-tachyons-fela-prop/tsconfig.node.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"compilerOptions": {
"composite": true,
"module": "ESNext",
"moduleResolution": "Node",
"allowSyntheticDefaultImports": true
},
"include": ["vite.config.ts"]
}
7 changes: 7 additions & 0 deletions packages/app-tachyons-fela-prop/vite.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { defineConfig } from "vite";
import react from "@vitejs/plugin-react-swc";

// https://vitejs.dev/config/
export default defineConfig({
plugins: [react({ jsxImportSource: "@homebound/fast-css-prop" })],
});
12 changes: 12 additions & 0 deletions packages/app-tachyons-fela-prop/vitest.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { defineConfig, mergeConfig } from "vitest/config";
import viteConfig from "./vite.config";

export default mergeConfig(
viteConfig,
defineConfig({
test: {
environment: "jsdom",
// setupFiles: ["./src/test/setup.ts"],
},
}),
);
6 changes: 6 additions & 0 deletions packages/fast-css-prop/jsx-dev-runtime.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { jsxDEV as _jsxDEV } from "react/jsx-dev-runtime";
import { maybeExpandCssProp } from "./build/cssProp.js";

export function jsxDEV(type, props, ...children) {
return _jsxDEV(type, maybeExpandCssProp(props), ...children);
}
6 changes: 6 additions & 0 deletions packages/fast-css-prop/jsx-runtime.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { jsx as _jsx } from "react/jsx-runtime";
import { maybeExpandCssProp } from "./build/cssProp.js";

export function jsx(type, props, ...children) {
return _jsx(type, maybeExpandCssProp(props), ...children);
}
18 changes: 18 additions & 0 deletions packages/fast-css-prop/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
{
"name": "@homebound/fast-css-prop",
"version": "1.136.0",
"main": "build/index.js",
"exports": {
".": "./build/cssProp.js",
"./jsx-runtime": "./jsx-runtime.js",
"./jsx-dev-runtime": "./jsx-dev-runtime.js"
},
"license": "ISC",
"dependencies": {
"fela": "^12.2.1"
},
"devDependencies": {
"@homebound/tsconfig": "^1.0.3",
"typescript": "^5.7.3"
}
}
49 changes: 49 additions & 0 deletions packages/fast-css-prop/src/cssProp.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { createRenderer } from "fela";
import { render } from "fela-dom";
// import { names } from "@homebound/truss-testing-tachyons";

// Create a renderer
const renderer = createRenderer();
// Auto-inject any CSS it creates into the DOM
render(renderer);

// const existingGenerateClassName = (renderer as any).generateClassName.bind(renderer);
// (renderer as any).generateClassName = (
// property: string,
// value: string,
// pseudo: string,
// media: string,
// support: string,
// ) => {
// const ref = property + value + pseudo + media + support;
// return names[ref] ?? existingGenerateClassName(property, value, pseudo, media, support);
// };

/**
* Wraps React's JSX runtime (i.e. `createElement`) with `css` prop support.
*
* Unlike the Emotion or Fela's native `css` props, we don't create a wrapping
* `EmotionCssPropInternal` or `FelaComponent` component, which adds another
* React component to the component tree.
*
* Instead, we directly rewrite the `css` prop into the `className` prop, and otherwise
* leave the `createElement(div, ...)` as-is.
*
* Granted, to do this we have to cheat and access the Fela `renderer` from a global
* const, instead of a context, but "it's fine" (...speaking of its-fine, we could
* potentially use that to access our Fiber/Context from this function, sans hook).
*/
export function maybeExpandCssProp(props: any): any {
if (props) {
const { css, className, ...otherProps } = props;
if (css) {
// We expect css to be a truss-created object like `{ color: "#121212" }`
const classNames = renderer.renderRule(() => css, {});
return {
className: className ? classNames + " " + className : classNames,
...otherProps,
};
}
}
return props;
}
27 changes: 27 additions & 0 deletions packages/fast-css-prop/src/jsx-namespace.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import "react";

// unpack all here to avoid infinite self-referencing when defining our own JSX namespace
type ReactJSXElement = JSX.Element;
type ReactJSXElementClass = JSX.ElementClass;
type ReactJSXElementAttributesProperty = JSX.ElementAttributesProperty;
type ReactJSXElementChildrenAttribute = JSX.ElementChildrenAttribute;
type ReactJSXLibraryManagedAttributes<C, P> = JSX.LibraryManagedAttributes<C, P>;
type ReactJSXIntrinsicAttributes = JSX.IntrinsicAttributes;
type ReactJSXIntrinsicClassAttributes<T> = JSX.IntrinsicClassAttributes<T>;
type ReactJSXIntrinsicElements = JSX.IntrinsicElements;

export namespace EmotionJSX {
interface Element extends ReactJSXElement {}
interface ElementClass extends ReactJSXElementClass {}
interface ElementAttributesProperty extends ReactJSXElementAttributesProperty {}
interface ElementChildrenAttribute extends ReactJSXElementChildrenAttribute {}
type LibraryManagedAttributes<C, P> = WithConditionalCSSProp<P> & ReactJSXLibraryManagedAttributes<C, P>;
interface IntrinsicAttributes extends ReactJSXIntrinsicAttributes {}
interface IntrinsicClassAttributes<T> extends ReactJSXIntrinsicClassAttributes<T> {}

type IntrinsicElements = {
[K in keyof ReactJSXIntrinsicElements]: ReactJSXIntrinsicElements[K] & {
css?: object;
};
};
}
15 changes: 15 additions & 0 deletions packages/fast-css-prop/src/jsx.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { EmotionJSX } from "./css";

export namespace jsx {
export namespace JSX {
export type ElementType = EmotionJSX.ElementType;
export interface Element extends EmotionJSX.Element {}
export interface ElementClass extends EmotionJSX.ElementClass {}
export interface ElementAttributesProperty extends EmotionJSX.ElementAttributesProperty {}
export interface ElementChildrenAttribute extends EmotionJSX.ElementChildrenAttribute {}
export type LibraryManagedAttributes<C, P> = EmotionJSX.LibraryManagedAttributes<C, P>;
export interface IntrinsicAttributes extends EmotionJSX.IntrinsicAttributes {}
export interface IntrinsicClassAttributes<T> extends EmotionJSX.IntrinsicClassAttributes<T> {}
export type IntrinsicElements = EmotionJSX.IntrinsicElements;
}
}
Loading
Loading