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

chore: migrate to PandaCSS #9

Merged
merged 1 commit into from
Sep 23, 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
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,7 @@ coverage

# Temp files
.tmp

## Panda
styled-system
styled-system-studio
7 changes: 4 additions & 3 deletions eslint.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,13 @@ const importPlugin = await import('eslint-plugin-import')

export default tseslint.config(
lovePreset,

// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
prettier,
{
files: ['**/*.{js,mjs,cjs}', '**/*.{ts,tsx,mts,cts}'],
},
{
ignores: ['dist/', 'vite.config.ts.timestamp-*.mjs'],
ignores: ['dist', 'styled-system', 'vite.config.ts.timestamp-*.mjs'],
},
{
languageOptions: {
Expand Down Expand Up @@ -44,7 +43,9 @@ export default tseslint.config(
groups: ['builtin', 'external', 'internal', 'parent', 'sibling', 'index'],
pathGroups: [
{ pattern: 'solid-js', group: 'external', position: 'before' },
{ pattern: '@linaria/core', group: 'external', position: 'before' },
{ pattern: 'solid-js/**', group: 'external', position: 'before' },
{ pattern: 'styled-system', group: 'external', position: 'before' },
{ pattern: 'styled-system/**', group: 'external', position: 'before' },
{ pattern: '@test/**', group: 'internal', position: 'after' },
{ pattern: '@/**', group: 'internal', position: 'after' },
],
Expand Down
103 changes: 103 additions & 0 deletions lib/panda/remove-unused-css.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
import postcss from 'postcss'

/**
* Based on https://panda-css.com/docs/concepts/hooks#remove-unused-variables-from-final-css
*/

interface UseRecord {
uses: number
dependencies: Set<string>
declarations: Set<postcss.Declaration>
}

const varRegex = /var\(\s*(?<name>--[^ ,);]+)/g

export default function removeUnusedCSS(css: string): string {
const root = postcss.parse(css)

const records = new Map<string, UseRecord>()
const keyframes = new Map<string, boolean>()

const getRecord = (variable: string): UseRecord => {
let record = records.get(variable)
if (record == null) {
record = { uses: 0, dependencies: new Set(), declarations: new Set() }
records.set(variable, record)
}
return record
}

const registerUse = (variable: string, ignoreList = new Set<string>()): void => {
const record = getRecord(variable)
record.uses++
ignoreList.add(variable)
for (const dependency of record.dependencies) {
if (!ignoreList.has(dependency)) registerUse(dependency, ignoreList)
}
}

const registerDependency = (variable: string, dependency: string): void => {
const record = getRecord(variable)
record.dependencies.add(dependency)
}

// Detect variable uses
root.walkDecls((decl) => {
const parent = decl.parent
if (parent == null) return

if (parent.type === 'rule' && (parent as postcss.Rule).selector === ':root') {
return
}

const isVar = decl.prop.startsWith('--')

// Initiate record
if (isVar) getRecord(decl.prop).declarations.add(decl)

if (!decl.value.includes('var(')) return

for (const match of decl.value.matchAll(varRegex)) {
const variable = match.groups?.name.trim()
if (variable == null || variable === '') continue

if (isVar) {
registerDependency(decl.prop, variable)
} else {
registerUse(variable)
}
}
})

root.walk((node) => {
if (node.type === 'atrule' && node.name === 'keyframes') {
// Record the keyframe and mark it as unused
keyframes.set(node.params, false)
} else if (node.type === 'decl') {
const decl = node
const animationName = decl.prop === 'animation' ? decl.value.split(' ')[0] : decl.value

if ((decl.prop === 'animation' || decl.prop === 'animation-name') && keyframes.has(animationName)) {
// Mark the keyframe as used
keyframes.set(animationName, true)
}
}
})

// Remove unused variables
for (const { uses, declarations } of records.values()) {
if (uses > 0) continue

for (const decl of declarations) {
const node = decl.parent?.nodes.length === 1 ? decl.parent : decl
node.remove()
}
}

// Remove unused keyframes
root.walkAtRules('keyframes', (rule) => {
if (keyframes.get(rule.params) === false) rule.remove()
})

return root.toString()
}
7 changes: 3 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
"node": "^18 || ^20"
},
"scripts": {
"prepare": "panda codegen --clean",
"dev": "vite",
"build": "vite build",
"test": "run-p --aggregate-output lint typecheck:* \"unit-tests --run --silent\"",
Expand All @@ -23,7 +24,6 @@
"*.css"
],
"dependencies": {
"@linaria/core": "^6.2.0",
"@modular-forms/solid": "^0.23.0",
"@total-typescript/ts-reset": "^0.6.1",
"date-fns": "^4.1.0",
Expand All @@ -36,7 +36,7 @@
"valibot": "^0.42.0"
},
"devDependencies": {
"@linaria/vite": "^5.0.4",
"@pandacss/dev": "^0.46.1",
"@solidjs/testing-library": "0.8.9",
"@testing-library/dom": "10.4.0",
"@testing-library/jest-dom": "^6.5.0",
Expand All @@ -45,7 +45,6 @@
"@types/chrome": "^0.0.271",
"@types/node": "^20.16.5",
"@vitest/coverage-v8": "^2.1.1",
"@wyw-in-js/vite": "^0.5.4",
"autoprefixer": "^10.4.20",
"browserslist": "^4.23.3",
"browserslist-to-esbuild": "^2.1.1",
Expand All @@ -58,8 +57,8 @@
"globals": "^15.9.0",
"happy-dom": "^15.7.4",
"npm-run-all": "^4.1.5",
"postcss": "^8.4.47",
"prettier": "^3.3.3",
"stylis": "^3.5.4",
"typescript": "~5.5.4",
"typescript-eslint": "^8.6.0",
"vite": "^5.4.6",
Expand Down
65 changes: 65 additions & 0 deletions panda.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import { defineConfig, defineGlobalStyles } from '@pandacss/dev'
import removeUnusedCSS from './lib/panda/remove-unused-css.js'
import * as theme from './src/theme/index.js'

export default defineConfig({
preflight: false,
importMap: 'styled-system',
include: ['./src/**/*.{js,jsx,ts,tsx}'],
exclude: [],
conditions: {
extend: {
dark: '@media (prefers-color-scheme: dark)',
typeDate: '&[type="date"]',
},
},
theme: {
extend: {
tokens: {
fonts: {
Digital7Mono: { value: theme.fonts.Digital7Mono },
},
colors: buildPallete(theme.colors),
},
},
},
globalCss: defineGlobalStyles({
html: {
'-webkit-text-size-adjust': '100%',
'-webkit-tap-highlight-color': 'rgba(0, 0, 0, 0)',
'-webkit-font-smoothing': 'antialiased',
fontFamily:
"'Helvetica Neue', Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'",
fontWeight: 400,
fontSize: '10px',
boxSizing: 'border-box',
background: { base: 'white', _dark: 'dark1' },
},
body: {
margin: 0,
touchAction: 'manipulation',
fontFamily: 'inherit',
fontSize: 'inherit',
},
'*, *::before, *::after': {
boxSizing: 'inherit',
},
'html, body, #app': {
height: 'full',
width: 'full',
},
}),
outdir: 'styled-system',
hooks: {
'cssgen:done': ({ artifact, content }) => {
if (artifact === 'styles.css') return removeUnusedCSS(content)
},
},
})

function buildPallete<T extends Record<string, string>>(colors: T): Record<keyof T, { value: string }> {
return Object.fromEntries(Object.entries(colors).map(([key, value]) => [key, { value }])) as Record<
keyof T,
{ value: string }
>
}
Loading