Skip to content

Commit

Permalink
Merge pull request #9 from khmm12/chore/migrate-to-panda-css
Browse files Browse the repository at this point in the history
chore: migrate to PandaCSS
  • Loading branch information
khmm12 authored Sep 23, 2024
2 parents 5bbd711 + 0142daf commit d6716d2
Show file tree
Hide file tree
Showing 42 changed files with 1,987 additions and 2,541 deletions.
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

0 comments on commit d6716d2

Please sign in to comment.