Skip to content

Commit

Permalink
feat: add css cope pkg
Browse files Browse the repository at this point in the history
  • Loading branch information
imtaotao committed Jun 25, 2021
1 parent 1a94519 commit 459caa0
Show file tree
Hide file tree
Showing 10 changed files with 1,172 additions and 0 deletions.
5 changes: 5 additions & 0 deletions packages/runtime/css-scope/.npmignore
@@ -0,0 +1,5 @@
src/
node_modules/
__tests__/
api-extractor.json
.npmignore
15 changes: 15 additions & 0 deletions packages/runtime/css-scope/README.md
@@ -0,0 +1,15 @@
# `@garfish/css-scope`

> 简化了 [css](https://github.com/reworkcss/css) 这个库
这套方案有两种实现,一套依赖于浏览器平台,使用 dom 一些 api,一套走纯 parser 流程,适用于 ssr 等场景。

## Usage

```js
import { parse, stringify } from '@garfish/css-scope';

const text = `a { color: #fff; }`;
const ast = parse(text);
stringify(ast, '#App');
```
5 changes: 5 additions & 0 deletions packages/runtime/css-scope/__tests__/index.spec.ts
@@ -0,0 +1,5 @@
import toolApp from '../src/index';

describe('css-scope', () => {
it('needs tests');
});
7 changes: 7 additions & 0 deletions packages/runtime/css-scope/api-extractor.json
@@ -0,0 +1,7 @@
{
"extends": "../../../api-extractor.json",
"mainEntryPointFilePath": "./dist/packages/runtime/css-scope/src/index.d.ts",
"dtsRollup": {
"publicTrimmedFilePath": "./dist/css-scope.d.ts"
}
}
37 changes: 37 additions & 0 deletions packages/runtime/css-scope/package.json
@@ -0,0 +1,37 @@
{
"name": "@garfish/garfish-css-scope",
"version": "0.0.30",
"description": "css-scope module.",
"keywords": [
"garfish",
"css-scope"
],
"author": "chentao.arthur <chentao.arthur@bytedance.com>",
"homepage": "https://github.com/bytedance/garfish",
"license": "Apache-2.0",
"main": "index.js",
"module": "dist/css-scope.esm-bundler.js",
"repository": {
"type": "git",
"url": "git+https://github.com/bytedance/garfish.git"
},
"bugs": {
"url": "https://github.com/bytedance/garfish/issues"
},
"scripts": {},
"types": "dist/css-scope.d.ts",
"buildOptions": {
"name": "CssScope",
"devTemplate": "module",
"formats": [
"esm-bundler",
"esm-browser",
"cjs",
"umd"
]
},
"dependencies": {},
"publishConfig": {
"registry": "https://registry.npmjs.org"
}
}
264 changes: 264 additions & 0 deletions packages/runtime/css-scope/src/animation.ts
@@ -0,0 +1,264 @@
/* eslint-disable indent */
// https://drafts.csswg.org/css-animations-1/#typedef-single-animation
// https://developer.mozilla.org/zh-CN/docs/Web/CSS/animation#%E8%AF%AD%E6%B3%95

// time: s | ms
const timeReg = /^[-\d\.]+(s|ms)$/;
function isTime(p: string) {
return timeReg.test(p);
}

// single-animation-iteration-count: infinite | <number>
const numberReg = /^[-\d\.]+$/;
function isIterationCount(p: string) {
return p === 'infinite' || numberReg.test(p);
}

// single-animation-play-state: running | paused
function isPlayState(p: string) {
return p === 'running' || p === 'paused';
}

// time-function: ease | ease-in | ease-out | ease-in-out | linear | step-start | step-end | cubic-bezier() | steps()
function isTimeFunction(p: string | Props) {
if (Array.isArray(p)) return true;
switch (p) {
case 'ease':
case 'ease-in':
case 'ease-out':
case 'ease-in-out':
case 'linear':
case 'step-start':
case 'step-end':
case 'cubic-bezier':
case 'steps':
return true;
default:
return false;
}
}

// single-animation-direction: normal | reverse | alternate | alternate-reverse
function isDirection(p: string) {
switch (p) {
case 'normal':
case 'reverse':
case 'alternate':
case 'alternate-reverse':
return true;
default:
return false;
}
}

// single-animation-fill-mode: none | forwards | backwards | both
function isFillMode(p: string) {
switch (p) {
case 'none':
case 'forwards':
case 'backwards':
case 'both':
return true;
default:
return false;
}
}

// https://developer.mozilla.org/zh-CN/docs/Web/CSS/custom-ident#%E8%AF%AD%E6%B3%95
// 配合 parser,只需要过滤关键字和 token 里面的特殊符号
const symbols = /[,'"\(\)!;]/;
function isLegalName(p: string) {
if (symbols.test(p)) return false;
switch (p) {
case 'unset':
case 'initial':
case 'inherit':
case 'none':
return false;
default:
return true;
}
}

// none | keyframes-name: <custom-ident> | <string>
function isName(p: string) {
if (
!(
isTime(p) ||
isPlayState(p) ||
isIterationCount(p) ||
isFillMode(p) ||
isDirection(p) ||
isTimeFunction(p)
)
) {
return isLegalName(p);
}
return false;
}

function tokenizer(input: string) {
let buf = '';
const tokens = [];
const push = () => {
buf && tokens.push(buf);
buf = '';
};

for (const char of input) {
if (char === ',' || char === ')' || char === ';') {
push();
buf += char;
push();
} else if (char === '(') {
push();
if (tokens[tokens.length - 1] === ' ') {
if (__DEV__ && !__TEST__) {
console.error(`[Garfish warn]: Invalid property value: "${input}"`);
}
return false;
}
buf += char;
push();
} else if (char === ' ') {
push();
if (tokens[tokens.length - 1] !== ' ') {
tokens.push(' ');
}
} else {
buf += char;
}
}
push();
return tokens;
}

function parse(tokens: Array<string>) {
let mode = 1; // 1 | 2 | 3
let scope = [];
let stash = false;
const parent = [];
scope[0] = parent;

const up = () => {
scope[0].push(scope);
scope = scope[0];
};

const down = () => {
const ns = [];
ns[0] = scope;
scope = ns;
};

const parallel = () => {
scope[0].push(scope);
scope = [];
scope[0] = parent;
};

const toThreeMode = (t: string) => {
mode = 3;
down();
scope.push(t);
};

for (let i = 0; i < tokens.length; i++) {
const t = tokens[i];
if (mode === 1) {
if (t === ',') {
mode = 2;
stash = false;
scope.push(t);
} else if (t === '(') {
toThreeMode(t);
} else if (t === ' ') {
stash = true;
} else {
stash && parallel();
stash = false;
scope.push(t);
}
} else if (mode === 2) {
if (t === '(') {
toThreeMode(t);
} else if (t === ' ') {
if (tokens[i - 1] !== ',') {
mode = 1;
stash = true;
}
} else {
scope.push(t);
}
} else if (mode === 3) {
if (t === ')') {
mode = 2;
scope.push(t);
up();
} else {
scope.push(t);
}
}
}
parallel();
return parent;
}

type Props = Array<string | Props>;
function stringify(tree: Array<Props>, prefix: string) {
let output = '';
const splice = (p) => (isName(p) ? `${p}-${prefix}` : p);

const child = (ps: Array<string>) => {
let buf = '';
for (let i = 1; i < ps.length; i++) {
buf += ps[i];
}
return buf;
};

tree.forEach((ps) => {
if (ps.length === 2) {
// prettier-ignore
output += (
Array.isArray(ps[1])
? child(ps[1] as Array<string>)
: splice(ps[1])
) + ' ';
} else {
for (let i = 1; i < ps.length; i++) {
const next = ps[i + 1];
const nextIsArray = Array.isArray(next);
// prettier-ignore
let cur = Array.isArray(ps[i])
? child(ps[i] as Array<string>)
: nextIsArray
? ps[i]
: splice(ps[i] as string);

if (next === ',' || next === ';') {
// 不加空格
} else if (nextIsArray) {
const fillUp = ps[i + 2] === ',' ? '' : ' ';
cur += `${child(next as Array<string>)}${fillUp}`;
i++;
} else {
cur += ' ';
}
output += cur;
}
}
});
return output.trim();
}

export function processAnimation(input: string, prefix: string) {
if (!input || !prefix) return input;
const tokens = tokenizer(input);
// 如果语法不正确,就直接返回原文本
if (tokens === false) {
return input;
}
const tree = parse(tokens);
return stringify(tree, prefix);
}
4 changes: 4 additions & 0 deletions packages/runtime/css-scope/src/index.ts
@@ -0,0 +1,4 @@
import { parse } from './parser';
import { stringify } from './stringify';

export { parse, stringify };

0 comments on commit 459caa0

Please sign in to comment.