Skip to content

Commit

Permalink
feat: parse & transform
Browse files Browse the repository at this point in the history
  • Loading branch information
zyyv committed Oct 25, 2024
1 parent 03f4e9f commit 185f152
Show file tree
Hide file tree
Showing 12 changed files with 401 additions and 82 deletions.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@
},
"dependencies": {
"@types/css-tree": "^2.3.8",
"@unocss/core": "^0.63.6",
"css-tree": "^3.0.0",
"magic-string": "^0.30.12"
},
Expand Down
19 changes: 10 additions & 9 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

54 changes: 2 additions & 52 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,8 @@
import { parse } from 'css-tree'
import MagicString from 'magic-string'
import type { Declaration, Rule, StyleSheet } from 'css-tree'
import type { Rule, StyleSheet } from 'css-tree'

export function toArray<T>(value: T | T[] = []): T[] {
return Array.isArray(value) ? value : [value]
}
export * from './parser/declaration'

function blockSource(source: string) {
if (source.includes('{') && source.includes('}')) {
Expand All @@ -28,51 +26,3 @@ function handleRuleNode(source: MagicString, node: Rule) {
// console.dir(node)
// const selector = node.prelude.children
}

interface CssValueParsedMeta {
value: number | string
unit?: string
}

interface CssValueParsed {
prop: string
meta: CssValueParsedMeta[]
}

export function handleDeclarationNode(node: Declaration): CssValueParsed | undefined {
if (node.type !== 'Declaration') {
return
}

const prop = node.property
let meta: CssValueParsedMeta[]
if (node.value.type === 'Raw') {
meta = toArray<CssValueParsedMeta>({ value: node.value.value })
}
else {
meta = node.value.children.map((child) => {
switch (child.type) {
case 'Dimension':
return {
value: child.value,
unit: child.unit,
}

case 'Hash':
return {
value: child.value,
}

case 'Identifier':
return {
value: child.name,
}
}
}) as unknown as CssValueParsedMeta[]
}

return {
prop,
meta,
}
}
12 changes: 8 additions & 4 deletions src/maps/border.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
import { positions, positionShort } from './position'
import { position, positionShort } from './position'
import type { PropsAtomicMap } from '../types'

export const border = [
['border', 'border'],
['b', 'b'],
export const border: PropsAtomicMap[] = [
['border', ['border', 'b']],
[/^border-(top|bottom|left|right)(?:-\w+)?$/, ([, p]) => {
const index = position.indexOf(p)
return [`border-${positionShort[index]}`, `b-${positionShort[index]}`]
}],
]
9 changes: 9 additions & 0 deletions src/maps/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { border } from './border'
import { margin } from './margin'
import { positions } from './position'

export const maps = [
border,
positions,
margin,
].flat(1)
10 changes: 10 additions & 0 deletions src/maps/margin.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { position, positionShort } from './position'
import type { PropsAtomicMap } from '../types'

export const margin: PropsAtomicMap[] = [
['margin', ['margin', 'm']],
[/^margin-(top|bottom|left|right)(?:-\w+)?$/, ([, p]) => {
const index = position.indexOf(p)
return [`margin-${positionShort[index]}`, `m-${positionShort[index]}`]
}],
]
9 changes: 8 additions & 1 deletion src/maps/position.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,9 @@
export const positions = ['top', 'bottom', 'left', 'right']
import type { PropsAtomicMap } from '../types'

export const position = ['top', 'bottom', 'left', 'right']
export const positionShort = ['t', 'b', 'l', 'r']

export const positions: PropsAtomicMap[] = position.map((p, i) => [
p,
[p, positionShort[i]],
])
71 changes: 71 additions & 0 deletions src/parser/declaration.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import type { CssNode, Declaration, Dimension, List, Url } from 'css-tree'

Check failure on line 1 in src/parser/declaration.ts

View workflow job for this annotation

GitHub Actions / lint

'List' is defined but never used
import { toArray } from '../utils'
import type { CssValueParsed, CssValueParsedMeta } from '../types'

export function parseDeclarationNode(node: Declaration): CssValueParsed | undefined {
if (node.type !== 'Declaration') {
return
}

const prop = node.property
let meta: CssValueParsedMeta[]

if (node.value.type === 'Raw') {
meta = toArray<CssValueParsedMeta>({ value: node.value.value })

Check failure on line 14 in src/parser/declaration.ts

View workflow job for this annotation

GitHub Actions / test (lts/*, ubuntu-latest)

Argument of type '{ value: string; }' is not assignable to parameter of type 'CssValueParsedMeta | CssValueParsedMeta[] | undefined'.

Check failure on line 14 in src/parser/declaration.ts

View workflow job for this annotation

GitHub Actions / test (lts/*, windows-latest)

Argument of type '{ value: string; }' is not assignable to parameter of type 'CssValueParsedMeta | CssValueParsedMeta[] | undefined'.

Check failure on line 14 in src/parser/declaration.ts

View workflow job for this annotation

GitHub Actions / test (lts/*, macos-latest)

Argument of type '{ value: string; }' is not assignable to parameter of type 'CssValueParsedMeta | CssValueParsedMeta[] | undefined'.
}
else {
meta = node.value.children.map(child => parseChildNode(child)) as unknown as CssValueParsedMeta[]
}

return {
prop,
meta,
}
}

function parseChildNode(child: CssNode): CssValueParsedMeta | undefined {
switch (child.type) {
case 'Dimension':
case 'Number':
case 'String':
case 'Percentage':
case 'Hash':
case 'Url':
case 'Raw':
case 'Operator': {
let _v: any = child.value
if (child.type === 'Hash') {
_v = `#${child.value}`
}
else if (child.type === 'Percentage') {
_v = `${child.value}%`
}

const meta: CssValueParsedMeta = { value: _v, type: child.type }

if ((child as Dimension).unit)
meta.unit = (child as Dimension).unit

if ((child as Url).type === 'Url')
meta.fname = 'url'

return meta
}

case 'Identifier':
return {
value: child.name,
type: child.type,
}

case 'Function':
return {
value: child.children.map(child => parseChildNode(child)!).toArray(),
fname: child.name,
type: child.type,
}

default:
return undefined
}
}
93 changes: 93 additions & 0 deletions src/transfromer/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
import { maps } from '../maps'
import { toArray } from '../utils'
import type { AtomicComposed, CssValueParsed, CssValueParsedMeta, DynamicPropAtomicMap, StaticPropAtomicMap, TransfromOptions } from '../types'

const atomicCache: Record<string, string[]> = {}

Check failure on line 5 in src/transfromer/index.ts

View workflow job for this annotation

GitHub Actions / lint

'atomicCache' is assigned a value but never used. Allowed unused vars must match /^_/u

const nonTransfromPxProps = [
'border',
]

/**

Check warning on line 11 in src/transfromer/index.ts

View workflow job for this annotation

GitHub Actions / lint

JSDoc @returns declaration present but return expression not available in function
* 将 CssValueParsedMeta[] 转换为 atomic css
* @param meta CssValueParsedMeta[]

Check warning on line 13 in src/transfromer/index.ts

View workflow job for this annotation

GitHub Actions / lint

Expected @param names to be "metas, options". Got "meta"
* @returns

Check warning on line 14 in src/transfromer/index.ts

View workflow job for this annotation

GitHub Actions / lint

Missing JSDoc @returns description
*/
export function transfrom(metas: CssValueParsedMeta[], options: TransfromOptions = {}): string[] {

Check failure on line 16 in src/transfromer/index.ts

View workflow job for this annotation

GitHub Actions / lint

'options' is assigned a value but never used. Allowed unused args must match /^_/u
const atomics: string[] = []

Check failure on line 17 in src/transfromer/index.ts

View workflow job for this annotation

GitHub Actions / lint

'atomics' is assigned a value but never used. Allowed unused vars must match /^_/u
}

export function transfromParsed(parsed: CssValueParsed, options: TransfromOptions = {}) {
let key: string | undefined

const {
shortify = false,
} = options
const { prop, meta } = parsed

for (const map of maps) {
let atomics: AtomicComposed | undefined

if (typeof map[0] === 'string') {
if (prop === map[0]) {
atomics = toArray((map as StaticPropAtomicMap)[1]) as AtomicComposed
}
}
else {
const match = prop.match(map[0])
if (match) {
const matched = (map as DynamicPropAtomicMap)[1](match)
if (matched) {
atomics = toArray(matched) as AtomicComposed
}
}
}

if (atomics) {
if (shortify) {
key = atomics[1] || atomics[0]
}
else {
key = atomics[0]
}
break
}
}

if (!key) {
return
}

return analyzeMeta({
prop,
meta,
key,
})
}

function analyzeMeta(bundle: {
meta: CssValueParsed['meta']
key: string
prop: string
}): string[] {
const { meta, key, prop } = bundle
const atomics: string[] = []

for (const m of meta) {
if (Array.isArray(m)) {

Check failure on line 77 in src/transfromer/index.ts

View workflow job for this annotation

GitHub Actions / lint

Empty block statement

}
else {
if (m.unit === 'px' && /^\d+$/.test(m.value as string)) {
if (nonTransfromPxProps.some(p => prop.includes(p))) {
atomics.push(`${key}-[${m.value}${m.unit}]`)
}
else {
atomics.push(`${key}-${Number(m.value) / 4}`)
}
}
}
}

return atomics
}
Loading

0 comments on commit 185f152

Please sign in to comment.