提交 0c8f7396 编写于 作者: fxy060608's avatar fxy060608

wip(app): nvue styler

上级 2ffe6b14
......@@ -264,6 +264,17 @@ zIndex: 4;
}
`)
expect(json).toEqual({
'@TRANSITION': {
bar: {
property: 'height',
},
foo: {
property: 'marginTop',
},
foobar: {
property: 'marginTop,height',
},
},
foo: {
transitionProperty: 'marginTop',
},
......@@ -293,6 +304,15 @@ zIndex: 4;
}
`)
expect(json).toEqual({
'@TRANSITION': {
bar: {
duration: 200,
},
foo: {
delay: 500,
duration: 200,
},
},
foo: {
transitionDuration: 200,
transitionDelay: 500,
......@@ -330,6 +350,14 @@ zIndex: 4;
}
`)
expect(json).toEqual({
'@TRANSITION': {
bar: {
timingFunction: 'cubic-bezier(0.88,1,-0.67,1.37)',
},
foo: {
timingFunction: 'ease-in-out',
},
},
foo: {
transitionTimingFunction: 'ease-in-out',
},
......
......@@ -93,7 +93,131 @@ describe('nvue-styler: parse', () => {
'.foo {transition-property: margin-top; transition-duration: 300ms; transition-delay: 0.2s; transition-timing-function: ease-in;}'
const { json, messages } = await objectifierRoot(code)
expect(json).toEqual({
'@TRANSITION': {
foo: {
delay: 200,
duration: 300,
property: 'marginTop',
timingFunction: 'ease-in',
},
},
foo: {
transitionDelay: 200,
transitionDuration: 300,
transitionProperty: 'marginTop',
transitionTimingFunction: 'ease-in',
},
})
expect(messages[0]).toEqual(
expect.objectContaining({
type: 'warning',
text: 'NOTE: property value `300ms` is autofixed to `300`',
})
)
expect(messages[1]).toEqual(
expect.objectContaining({
type: 'warning',
text: 'NOTE: property value `0.2s` is autofixed to `200`',
})
)
})
test('transition transform', async () => {
const code =
'.foo {transition-property: transform; transition-duration: 300ms; transition-delay: 0.2s; transition-timing-function: ease-in-out;}'
const { json, messages } = await objectifierRoot(code)
expect(json).toEqual({
'@TRANSITION': {
foo: {
property: 'transform',
duration: 300,
delay: 200,
timingFunction: 'ease-in-out',
},
},
foo: {
transitionDelay: 200,
transitionDuration: 300,
transitionProperty: 'transform',
transitionTimingFunction: 'ease-in-out',
},
})
expect(messages[0]).toEqual(
expect.objectContaining({
type: 'warning',
text: 'NOTE: property value `300ms` is autofixed to `300`',
})
)
expect(messages[1]).toEqual(
expect.objectContaining({
type: 'warning',
text: 'NOTE: property value `0.2s` is autofixed to `200`',
})
)
})
test('multi transition properties', async () => {
const code =
'.foo {transition-property: margin-top, height; transition-duration: 300ms; transition-delay: 0.2s; transition-timing-function: ease-in-out;}'
const { json, messages } = await objectifierRoot(code)
expect(json).toEqual({
'@TRANSITION': {
foo: {
property: 'marginTop,height',
duration: 300,
delay: 200,
timingFunction: 'ease-in-out',
},
},
foo: {
transitionDelay: 200,
transitionDuration: 300,
transitionProperty: 'marginTop,height',
transitionTimingFunction: 'ease-in-out',
},
})
expect(messages[0]).toEqual(
expect.objectContaining({
type: 'warning',
text: 'NOTE: property value `300ms` is autofixed to `300`',
})
)
expect(messages[1]).toEqual(
expect.objectContaining({
type: 'warning',
text: 'NOTE: property value `0.2s` is autofixed to `200`',
})
)
})
test('complex transition', async () => {
const code =
'.foo {font-size: 20; color: #000000}\n\n .foo, .bar {color: #ff5000; height: 30; transition-property: margin-top; transition-duration: 300ms; transition-delay: 0.2s; transition-timing-function: ease-in;}'
const { json, messages } = await objectifierRoot(code)
expect(json).toEqual({
'@TRANSITION': {
foo: {
property: 'marginTop',
duration: 300,
delay: 200,
timingFunction: 'ease-in',
},
bar: {
property: 'marginTop',
duration: 300,
delay: 200,
timingFunction: 'ease-in',
},
},
foo: {
fontSize: 20,
color: '#ff5000',
height: 30,
transitionDelay: 200,
transitionDuration: 300,
transitionProperty: 'marginTop',
transitionTimingFunction: 'ease-in',
},
bar: {
color: '#ff5000',
height: 30,
transitionDelay: 200,
transitionDuration: 300,
transitionProperty: 'marginTop',
......@@ -113,4 +237,117 @@ describe('nvue-styler: parse', () => {
})
)
})
test('transition shorthand', async () => {
const code =
'.foo {font-size: 20; transition: margin-top 500ms ease-in-out 1s}'
const { json, messages } = await objectifierRoot(code)
expect(json).toEqual({
'@TRANSITION': {
foo: {
property: 'marginTop',
duration: 500,
delay: 1000,
timingFunction: 'ease-in-out',
},
},
foo: {
fontSize: 20,
transitionDelay: 1000,
transitionDuration: 500,
transitionProperty: 'marginTop',
transitionTimingFunction: 'ease-in-out',
},
})
expect(messages[0]).toEqual(
expect.objectContaining({
type: 'warning',
text: 'NOTE: property value `500ms` is autofixed to `500`',
})
)
expect(messages[1]).toEqual(
expect.objectContaining({
type: 'warning',
text: 'NOTE: property value `1s` is autofixed to `1000`',
})
)
})
test('padding & margin shorthand', async () => {
const code =
'.foo { padding: 20px; margin: 30px 40; } .bar { margin: 10px 20 30; padding: 10 20px 30px 40;}'
const { json } = await objectifierRoot(code)
expect(json).toEqual({
foo: {
paddingTop: '20px',
paddingRight: '20px',
paddingBottom: '20px',
paddingLeft: '20px',
marginTop: '30px',
marginRight: 40,
marginBottom: '30px',
marginLeft: 40,
},
bar: {
paddingTop: 10,
paddingRight: '20px',
paddingBottom: '30px',
paddingLeft: 40,
marginTop: '10px',
marginRight: 20,
marginBottom: 30,
marginLeft: 20,
},
})
})
test('override padding & margin shorthand', async () => {
const code =
'.foo { padding: 20px; padding-left: 30px; } .bar { margin: 10px 20; margin-bottom: 30px;}'
const { json } = await objectifierRoot(code)
expect(json).toEqual({
foo: {
paddingTop: '20px',
paddingRight: '20px',
paddingBottom: '20px',
paddingLeft: '30px',
},
bar: {
marginTop: '10px',
marginRight: 20,
marginBottom: '30px',
marginLeft: 20,
},
})
})
test('pseudo class', async () => {
const code =
'.class-a {color: #0000ff;} .class-a:last-child:focus {color: #ff0000;}'
const { json } = await objectifierRoot(code)
expect(json).toEqual({
'class-a': {
color: '#0000ff',
'color:last-child:focus': '#ff0000',
},
})
})
test('iconfont', async () => {
const code =
'@font-face {font-family: "font-family-name-1"; src: url("font file url 1-1") format("truetype");} @font-face {font-family: "font-family-name-2"; src: url("font file url 2-1") format("truetype"), url("font file url 2-2") format("woff");}'
const { json } = await objectifierRoot(code)
expect(json).toEqual({
'@FONT-FACE': [
{
fontFamily: 'font-family-name-1',
src: 'url("font file url 1-1") format("truetype")',
},
{
fontFamily: 'font-family-name-2',
src: 'url("font file url 2-1") format("truetype"), url("font file url 2-2") format("woff")',
},
],
})
})
test('syntax error', async () => {
const code = 'asdf'
const { messages } = await objectifierRoot(code)
expect(messages[0].text).toContain('Unknown word')
})
})
import postcss from 'postcss'
import { expand, normalize } from '../src'
export function parseCss(input: string, filename: string = 'foo.css') {
return postcss([
expand,
normalize({ descendant: false, logLevel: 'NOTE' }),
]).process(input, {
return postcss([expand, normalize({ descendant: false, logLevel: 'NOTE' })])
.process(input, {
from: filename,
})
.catch((err: any) => {
return {
root: null,
messages: [
{
type: 'warning',
text: err.message,
},
],
}
})
}
import { AtRule, Container, Root, Document } from 'postcss'
import { extend, hasOwn, isArray } from './utils'
import { Container, Root, Document } from 'postcss'
import { extend } from './utils'
interface ObjectifierContext {
TRANSITION: Record<string, Record<string, string | number>>
'FONT-FACE': Record<string, unknown>[]
TRANSITION: Record<string, Record<string, unknown>>
}
export function objectifier(
export function objectifier(node: Root | Document | Container | null) {
if (!node) {
return {}
}
const context: ObjectifierContext = { 'FONT-FACE': [], TRANSITION: {} }
const result = transform(node, context)
if (context['FONT-FACE'].length) {
result['@FONT-FACE'] = context['FONT-FACE']
}
if (Object.keys(context.TRANSITION).length) {
result['@TRANSITION'] = context.TRANSITION
}
return result
}
function transform(
node: Root | Document | Container,
context: ObjectifierContext = { TRANSITION: {} }
context: ObjectifierContext
) {
let name: string
const result: Record<string, Record<string, unknown> | unknown> = {}
node.each((child) => {
if (child.type === 'atrule') {
name = '@' + child.name
if (child.params) name += ' ' + child.params
if (!hasOwn(result, name)) {
result[name] = atRule(child)
} else if (isArray(result[name])) {
;(result[name] as unknown[]).push(atRule(child))
} else {
result[name] = [result[name], atRule(child)]
const body = transform(child, context)
const fontFamily = body.fontFamily as string
if (fontFamily && '"\''.indexOf(fontFamily[0]) > -1) {
body.fontFamily = fontFamily.slice(1, fontFamily.length - 1)
}
context['FONT-FACE'].push(body)
} else if (child.type === 'rule') {
const body = objectifier(child, context)
const body = transform(child, context)
child.selectors.forEach((selector) => {
const className = selector.slice(1)
let className = selector.slice(1)
const pseudoIndex = className.indexOf(':')
if (pseudoIndex > -1) {
const pseudoClass = className.slice(pseudoIndex)
className = className.slice(0, pseudoIndex)
Object.keys(body).forEach(function (name) {
body[name + pseudoClass] = body[name]
delete body[name]
})
}
transition(className, body, context)
if (result[className]) {
// clone
result[className] = extend({}, result[className], body)
} else {
result[className] = body
}
transition(body, context)
})
} else if (child.type === 'decl') {
name = child.prop
const value = child.value
if (!hasOwn(result, name)) {
result[name] = value
} else if (isArray(result[name])) {
;(result[name] as unknown[]).push(value)
} else {
result[name] = [result[name], value]
}
result[child.prop] = child.value
}
})
return result
}
function transition(
className: string,
body: Record<string, unknown>,
context: ObjectifierContext
) {}
function atRule(node: AtRule) {}
{ TRANSITION }: ObjectifierContext
) {
Object.keys(body).forEach((prop) => {
if (prop.indexOf('transition') === 0 && prop !== 'transition') {
const realProp = prop.replace('transition', '')
TRANSITION[className] = TRANSITION[className] || {}
TRANSITION[className][realProp[0].toLowerCase() + realProp.slice(1)] =
body[prop]
}
})
}
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册