diff options
Diffstat (limited to 'stylis.js/src')
-rw-r--r-- | stylis.js/src/Enum.js | 20 | ||||
-rw-r--r-- | stylis.js/src/Middleware.js | 107 | ||||
-rw-r--r-- | stylis.js/src/Parser.js | 174 | ||||
-rw-r--r-- | stylis.js/src/Prefixer.js | 114 | ||||
-rw-r--r-- | stylis.js/src/Serializer.js | 34 | ||||
-rw-r--r-- | stylis.js/src/Tokenizer.js | 218 | ||||
-rw-r--r-- | stylis.js/src/Utility.js | 109 |
7 files changed, 776 insertions, 0 deletions
diff --git a/stylis.js/src/Enum.js b/stylis.js/src/Enum.js new file mode 100644 index 0000000..0d611ab --- /dev/null +++ b/stylis.js/src/Enum.js @@ -0,0 +1,20 @@ +export var MS = '-ms-' +export var MOZ = '-moz-' +export var WEBKIT = '-webkit-' + +export var COMMENT = 'comm' +export var RULESET = 'rule' +export var DECLARATION = 'decl' + +export var PAGE = '@page' +export var MEDIA = '@media' +export var IMPORT = '@import' +export var CHARSET = '@charset' +export var VIEWPORT = '@viewport' +export var SUPPORTS = '@supports' +export var DOCUMENT = '@document' +export var NAMESPACE = '@namespace' +export var KEYFRAMES = '@keyframes' +export var FONT_FACE = '@font-face' +export var COUNTER_STYLE = '@counter-style' +export var FONT_FEATURE_VALUES = '@font-feature-values' diff --git a/stylis.js/src/Middleware.js b/stylis.js/src/Middleware.js new file mode 100644 index 0000000..cec5f62 --- /dev/null +++ b/stylis.js/src/Middleware.js @@ -0,0 +1,107 @@ +import {MS, MOZ, WEBKIT, RULESET, KEYFRAMES, DECLARATION} from './Enum.js' +import {match, charat, substr, strlen, sizeof, replace, combine} from './Utility.js' +import {copy, tokenize} from './Tokenizer.js' +import {serialize} from './Serializer.js' +import {prefix} from './Prefixer.js' + +/** + * @param {function[]} collection + * @return {function} + */ +export function middleware (collection) { + var length = sizeof(collection) + + return function (element, index, children, callback) { + var output = '' + + for (var i = 0; i < length; i++) + output += collection[i](element, index, children, callback) || '' + + return output + } +} + +/** + * @param {function} callback + * @return {function} + */ +export function rulesheet (callback) { + return function (element) { + if (!element.root) + if (element = element.return) + callback(element) + } +} + +/** + * @param {object} element + * @param {number} index + * @param {object[]} children + * @param {function} callback + */ +export function prefixer (element, index, children, callback) { + if (!element.return) + switch (element.type) { + case DECLARATION: element.return = prefix(element.value, element.length) + break + case KEYFRAMES: + return serialize([copy(replace(element.value, '@', '@' + WEBKIT), element, '')], callback) + case RULESET: + if (element.length) + return combine(element.props, function (value) { + switch (match(value, /(::plac\w+|:read-\w+)/)) { + // :read-(only|write) + case ':read-only': case ':read-write': + return serialize([copy(replace(value, /:(read-\w+)/, ':' + MOZ + '$1'), element, '')], callback) + // :placeholder + case '::placeholder': + return serialize([ + copy(replace(value, /:(plac\w+)/, ':' + WEBKIT + 'input-$1'), element, ''), + copy(replace(value, /:(plac\w+)/, ':' + MOZ + '$1'), element, ''), + copy(replace(value, /:(plac\w+)/, MS + 'input-$1'), element, '') + ], callback) + } + + return '' + }) + } +} + +/** + * @param {object} element + * @param {number} index + * @param {object[]} children + */ +export function namespace (element) { + switch (element.type) { + case RULESET: + element.props = element.props.map(function (value) { + return combine(tokenize(value), function (value, index, children) { + switch (charat(value, 0)) { + // \f + case 12: + return substr(value, 1, strlen(value)) + // \0 ( + > ~ + case 0: case 40: case 43: case 62: case 126: + return value + // : + case 58: + if (children[index + 1] === 'global') + children[index + 1] = '', children[index + 2] = '\f' + substr(children[index + 2], index = 1, -1) + // \s + case 32: + return index === 1 ? '' : value + default: + switch (index) { + case 0: element = value + return sizeof(children) > 1 ? '' : value + case index = sizeof(children) - 1: case 2: + return index === 2 ? value + element + element : value + element + default: + return value + } + } + }) + }) + } +} diff --git a/stylis.js/src/Parser.js b/stylis.js/src/Parser.js new file mode 100644 index 0000000..4089514 --- /dev/null +++ b/stylis.js/src/Parser.js @@ -0,0 +1,174 @@ +import {COMMENT, RULESET, DECLARATION} from './Enum.js' +import {abs, trim, from, sizeof, strlen, substr, append, replace} from './Utility.js' +import {node, char, next, peek, caret, alloc, dealloc, delimit, whitespace, identifier, commenter} from './Tokenizer.js' + +/** + * @param {string} value + * @return {object[]} + */ +export function compile (value) { + return dealloc(parse('', null, null, null, [''], value = alloc(value), 0, [0], value)) +} + +/** + * @param {string} value + * @param {object} root + * @param {object?} parent + * @param {string[]} rule + * @param {string[]} rules + * @param {string[]} rulesets + * @param {number[]} pseudo + * @param {number[]} points + * @param {string[]} declarations + * @return {object} + */ +export function parse (value, root, parent, rule, rules, rulesets, pseudo, points, declarations) { + var index = 0 + var offset = 0 + var length = pseudo + var atrule = 0 + var property = 0 + var previous = 0 + var variable = 1 + var scanning = 1 + var ampersand = 1 + var character = 0 + var type = '' + var props = rules + var children = rulesets + var reference = rule + var characters = type + + while (scanning) + switch (previous = character, character = next()) { + // " ' [ ( + case 34: case 39: case 91: case 40: + characters += delimit(character) + break + // \t \n \r \s + case 9: case 10: case 13: case 32: + characters += whitespace(previous) + break + // / + case 47: + switch (peek()) { + case 42: case 47: + append(comment(commenter(next(), caret()), root, parent), declarations) + break + default: + characters += '/' + } + break + // { + case 123 * variable: + points[index++] = strlen(characters) * ampersand + // } ; \0 + case 125 * variable: case 59: case 0: + switch (character) { + // \0 } + case 0: case 125: scanning = 0 + // ; + case 59 + offset: + if (property > 0) + append(property > 32 ? declaration(characters + ';', rule, parent, length - 1) : declaration(replace(characters, ' ', '') + ';', rule, parent, length - 2), declarations) + break + // @ ; + case 59: characters += ';' + // { rule/at-rule + default: + append(reference = ruleset(characters, root, parent, index, offset, rules, points, type, props = [], children = [], length), rulesets) + + if (character === 123) + if (offset === 0) + parse(characters, root, reference, reference, props, rulesets, length, points, children) + else + switch (atrule) { + // d m s + case 100: case 109: case 115: + parse(value, reference, reference, rule && append(ruleset(value, reference, reference, 0, 0, rules, points, type, rules, props = [], length), children), rules, children, length, points, rule ? props : children) + break + default: + parse(characters, reference, reference, reference, [''], children, length, points, children) + } + } + + index = offset = property = 0, variable = ampersand = 1, type = characters = '', length = pseudo + break + // : + case 58: + length = 1 + strlen(characters), property = previous + default: + switch (characters += from(character), character * variable) { + // & + case 38: + ampersand = offset > 0 ? 1 : (characters += '\f', -1) + break + // , + case 44: + points[index++] = (strlen(characters) - 1) * ampersand, ampersand = 1 + break + // @ + case 64: + // - + if (peek() === 45) + characters += delimit(next()) + + atrule = peek(), offset = strlen(type = characters += identifier(caret())), character++ + break + // - + case 45: + if (previous === 45 && strlen(characters) == 2) + variable = 0 + } + } + + return rulesets +} + +/** + * @param {string} value + * @param {object} root + * @param {object?} parent + * @param {number} index + * @param {number} offset + * @param {string[]} rules + * @param {number[]} points + * @param {string} type + * @param {string[]} props + * @param {string[]} children + * @param {number} length + * @return {object} + */ +export function ruleset (value, root, parent, index, offset, rules, points, type, props, children, length) { + var post = offset - 1 + var rule = offset === 0 ? rules : [''] + var size = sizeof(rule) + + for (var i = 0, j = 0, k = 0; i < index; ++i) + for (var x = 0, y = substr(value, post + 1, post = abs(j = points[i])), z = value; x < size; ++x) + if (z = trim(j > 0 ? rule[x] + ' ' + y : replace(y, /&\f/g, rule[x]))) + props[k++] = z + + return node(value, root, parent, offset === 0 ? RULESET : type, props, children, length) +} + +/** + * @param {number} value + * @param {object} root + * @param {object?} parent + * @return {object} + */ +export function comment (value, root, parent) { + return node(value, root, parent, COMMENT, from(char()), substr(value, 2, -2), 0) +} + +/** + * @param {string} value + * @param {object} root + * @param {object?} parent + * @param {number} length + * @return {object} + */ +export function declaration (value, root, parent, length) { + return node(value, root, parent, DECLARATION, substr(value, 0, length), substr(value, length + 1, -1), length) +} diff --git a/stylis.js/src/Prefixer.js b/stylis.js/src/Prefixer.js new file mode 100644 index 0000000..e156c21 --- /dev/null +++ b/stylis.js/src/Prefixer.js @@ -0,0 +1,114 @@ +import {MS, MOZ, WEBKIT} from './Enum.js' +import {hash, charat, strlen, indexof, replace} from './Utility.js' + +/** + * @param {string} value + * @param {number} length + * @return {string} + */ +export function prefix (value, length) { + switch (hash(value, length)) { + // animation, animation-(delay|direction|duration|fill-mode|iteration-count|name|play-state|timing-function) + case 5737: case 4201: case 3177: case 3433: case 1641: case 4457: case 2921: + // text-decoration, filter, clip-path, backface-visibility, column, box-decoration-break + case 5572: case 6356: case 5844: case 3191: case 6645: case 3005: + // mask, mask-image, mask-(mode|clip|size), mask-(repeat|origin), mask-position, mask-composite, + case 6391: case 5879: case 5623: case 6135: case 4599: case 4855: + // background-clip, columns, column-(count|fill|gap|rule|rule-color|rule-style|rule-width|span|width) + case 4215: case 6389: case 5109: case 5365: case 5621: case 3829: + return WEBKIT + value + value + // appearance, user-select, transform, hyphens, text-size-adjust + case 5349: case 4246: case 4810: case 6968: case 2756: + return WEBKIT + value + MOZ + value + MS + value + value + // flex, flex-direction + case 6828: case 4268: + return WEBKIT + value + MS + value + value + // order + case 6165: + return WEBKIT + value + MS + 'flex-' + value + value + // align-items + case 5187: + return WEBKIT + value + replace(value, /(\w+).+(:[^]+)/, WEBKIT + 'box-$1$2' + MS + 'flex-$1$2') + value + // align-self + case 5443: + return WEBKIT + value + MS + 'flex-item-' + replace(value, /flex-|-self/, '') + value + // align-content + case 4675: + return WEBKIT + value + MS + 'flex-line-pack' + replace(value, /align-content|flex-|-self/, '') + value + // flex-shrink + case 5548: + return WEBKIT + value + MS + replace(value, 'shrink', 'negative') + value + // flex-basis + case 5292: + return WEBKIT + value + MS + replace(value, 'basis', 'preferred-size') + value + // flex-grow + case 6060: + return WEBKIT + 'box-' + replace(value, '-grow', '') + WEBKIT + value + MS + replace(value, 'grow', 'positive') + value + // transition + case 4554: + return WEBKIT + replace(value, /([^-])(transform)/g, '$1' + WEBKIT + '$2') + value + // cursor + case 6187: + return replace(replace(replace(value, /(zoom-|grab)/, WEBKIT + '$1'), /(image-set)/, WEBKIT + '$1'), value, '') + value + // background, background-image + case 5495: case 3959: + return replace(value, /(image-set\([^]*)/, WEBKIT + '$1' + '$`$1') + // justify-content + case 4968: + return replace(replace(value, /(.+:)(flex-)?(.*)/, WEBKIT + 'box-pack:$3' + MS + 'flex-pack:$3'), /s.+-b[^;]+/, 'justify') + WEBKIT + value + value + // (margin|padding)-inline-(start|end) + case 4095: case 3583: case 4068: case 2532: + return replace(value, /(.+)-inline(.+)/, WEBKIT + '$1$2') + value + // (min|max)?(width|height|inline-size|block-size) + case 8116: case 7059: case 5753: case 5535: + case 5445: case 5701: case 4933: case 4677: + case 5533: case 5789: case 5021: case 4765: + // stretch, max-content, min-content, fill-available + if (strlen(value) - 1 - length > 6) + switch (charat(value, length + 1)) { + // (m)ax-content, (m)in-content + case 109: + return replace(value, /(.+:)(.+)-([^]+)/, '$1' + WEBKIT + '$2-$3' + '$1' + MOZ + '$2-$3') + value + // (f)ill-available + case 102: + return replace(value, /(.+:)(.+)-([^]+)/, '$1' + WEBKIT + '$2-$3' + '$1' + MOZ + '$3') + value + // (s)tretch + case 115: + return prefix(replace(value, 'stretch', 'fill-available'), length) + value + } + break + // position: sticky + case 4949: + // (s)ticky? + if (charat(value, length + 1) !== 115) + break + // display: (flex|inline-flex|inline-box) + case 6444: + switch (charat(value, strlen(value) - 3 - (~indexof(value, '!important') && 10))) { + // stic(k)y, inline-b(o)x + case 107: case 111: + return replace(value, value, WEBKIT + value) + value + // (inline-)?fl(e)x + case 101: + return replace(value, /(.+:)([^;!]+)(;|!.+)?/, '$1' + WEBKIT + (charat(value, 14) === 45 ? 'inline-' : '') + 'box$3' + '$1' + WEBKIT + '$2$3' + '$1' + MS + '$2box$3') + value + } + break + // writing-mode + case 5936: + switch (charat(value, length + 11)) { + // vertical-l(r) + case 114: + return WEBKIT + value + MS + replace(value, /[svh]\w+-[tblr]{2}/, 'tb') + value + // vertical-r(l) + case 108: + return WEBKIT + value + MS + replace(value, /[svh]\w+-[tblr]{2}/, 'tb-rl') + value + // horizontal(-)tb + case 45: + return WEBKIT + value + MS + replace(value, /[svh]\w+-[tblr]{2}/, 'lr') + value + } + + return WEBKIT + value + MS + value + value + } + + return value +} diff --git a/stylis.js/src/Serializer.js b/stylis.js/src/Serializer.js new file mode 100644 index 0000000..c82f0b4 --- /dev/null +++ b/stylis.js/src/Serializer.js @@ -0,0 +1,34 @@ +import {IMPORT, COMMENT, RULESET, DECLARATION} from './Enum.js' +import {strlen, sizeof} from './Utility.js' + +/** + * @param {object[]} children + * @param {function} callback + * @return {string} + */ +export function serialize (children, callback) { + var output = '' + var length = sizeof(children) + + for (var i = 0; i < length; i++) + output += callback(children[i], i, children, callback) || '' + + return output +} + +/** + * @param {object} element + * @param {number} index + * @param {object[]} children + * @param {function} callback + * @return {string} + */ +export function stringify (element, index, children, callback) { + switch (element.type) { + case IMPORT: case DECLARATION: return element.return = element.return || element.value + case COMMENT: return '' + case RULESET: element.value = element.props.join(',') + } + + return strlen(children = serialize(element.children, callback)) ? element.return = element.value + '{' + children + '}' : '' +} diff --git a/stylis.js/src/Tokenizer.js b/stylis.js/src/Tokenizer.js new file mode 100644 index 0000000..d14c136 --- /dev/null +++ b/stylis.js/src/Tokenizer.js @@ -0,0 +1,218 @@ +import {from, trim, charat, strlen, substr, append} from './Utility.js' + +export var line = 1 +export var column = 1 +export var length = 0 +export var position = 0 +export var character = 0 +export var characters = '' + +/** + * @param {string} value + * @param {object} root + * @param {object?} parent + * @param {string} type + * @param {string[]} props + * @param {object[]} children + * @param {number} length + */ +export function node (value, root, parent, type, props, children, length) { + return {value: value, root: root, parent: parent, type: type, props: props, children: children, line: line, column: column, length: length, return: ''} +} + +/** + * @param {string} value + * @param {object} root + * @param {string} type + */ +export function copy (value, root, type) { + return node(value, root.root, root.parent, type, root.props, root.children, 0) +} + +/** + * @return {number} + */ +export function char () { + return character +} + +/** + * @return {number} + */ +export function next () { + character = position < length ? charat(characters, position++) : 0 + + if (column++, character === 10) + column = 1, line++ + + return character +} + +/** + * @return {number} + */ +export function peek () { + return charat(characters, position) +} + +/** + * @return {number} + */ +export function caret () { + return position +} + +/** + * @param {number} begin + * @param {number} end + * @return {string} + */ +export function slice (begin, end) { + return substr(characters, begin, end) +} + +/** + * @param {number} type + * @return {number} + */ +export function token (type) { + switch (type) { + // \0 \t \n \r \s whitespace token + case 0: case 9: case 10: case 13: case 32: + return 5 + // ! + , / > @ ~ isolate token + case 33: case 43: case 44: case 47: case 62: case 64: case 126: + // ; { } / breakpoint token + case 59: case 123: case 125: + return 4 + // : accompanied token + case 58: + return 3 + // " ' ( [ opening delimit token + case 34: case 39: case 40: case 91: + return 2 + // ) ] closing delimit token + case 41: case 93: + return 1 + } + + return 0 +} + +/** + * @param {string} value + * @return {any[]} + */ +export function alloc (value) { + return line = column = 1, length = strlen(characters = value), position = 0, [] +} + +/** + * @param {any} value + * @return {any} + */ +export function dealloc (value) { + return characters = '', value +} + +/** + * @param {number} type + * @return {string} + */ +export function delimit (type) { + return trim(slice(position - 1, delimiter(type === 91 ? type + 2 : type === 40 ? type + 1 : type))) +} + +/** + * @param {string} value + * @return {string[]} + */ +export function tokenize (value) { + return dealloc(tokenizer(alloc(value))) +} + +/** + * @param {number} type + * @return {string} + */ +export function whitespace (type) { + while (character = peek()) + if (character < 33) + next() + else + break + + return token(type) > 2 || token(character) > 3 ? '' : ' ' +} + +/** + * @param {string[]} children + * @return {string[]} + */ +export function tokenizer (children) { + while (next()) + switch (token(character)) { + case 0: append(identifier(position - 1), children) + break + case 2: append(delimit(character), children) + break + default: append(from(character), children) + } + + return children +} + +/** + * @param {number} type + * @return {number} + */ +export function delimiter (type) { + while (next()) + switch (character) { + // ] ) " ' + case type: + return position + // " ' + case 34: case 39: + return delimiter(type === 34 || type === 39 ? type : character) + // ( + case 40: + if (type === 41) + delimiter(type) + break + // \ + case 92: + next() + break + } + + return position +} + +/** + * @param {number} type + * @param {number} index + * @return {number} + */ +export function commenter (type, index) { + while (next()) + // // + if (type + character === 47 + 10) + break + // /* + else if (type + character === 42 + 42 && peek() === 47) + break + + return '/*' + slice(index, position - 1) + '*' + from(type === 47 ? type : next()) +} + +/** + * @param {number} index + * @return {string} + */ +export function identifier (index) { + while (!token(peek())) + next() + + return slice(index, position) +} diff --git a/stylis.js/src/Utility.js b/stylis.js/src/Utility.js new file mode 100644 index 0000000..f29590e --- /dev/null +++ b/stylis.js/src/Utility.js @@ -0,0 +1,109 @@ +/** + * @param {number} + * @return {number} + */ +export var abs = Math.abs + +/** + * @param {number} + * @return {string} + */ +export var from = String.fromCharCode + +/** + * @param {string} value + * @param {number} length + * @return {number} + */ +export function hash (value, length) { + return (((((((length << 2) ^ charat(value, 0)) << 2) ^ charat(value, 1)) << 2) ^ charat(value, 2)) << 2) ^ charat(value, 3) +} + +/** + * @param {string} value + * @return {string} + */ +export function trim (value) { + return value.trim() +} + +/** + * @param {string} value + * @param {RegExp} pattern + * @return {string?} + */ +export function match (value, pattern) { + return (value = pattern.exec(value)) ? value[0] : value +} + +/** + * @param {string} value + * @param {(string|RegExp)} pattern + * @param {string} replacement + * @return {string} + */ +export function replace (value, pattern, replacement) { + return value.replace(pattern, replacement) +} + +/** + * @param {string} value + * @param {string} value + * @return {number} + */ +export function indexof (value, search) { + return value.indexOf(search) +} + +/** + * @param {string} value + * @param {number} index + * @return {number} + */ +export function charat (value, index) { + return value.charCodeAt(index) | 0 +} + +/** + * @param {string} value + * @param {number} begin + * @param {number} end + * @return {string} + */ +export function substr (value, begin, end) { + return value.slice(begin, end) +} + +/** + * @param {string} value + * @return {number} + */ +export function strlen (value) { + return value.length +} + +/** + * @param {any[]} value + * @return {number} + */ +export function sizeof (value) { + return value.length +} + +/** + * @param {any} value + * @param {any[]} array + * @return {any} + */ +export function append (value, array) { + return array.push(value), value +} + +/** + * @param {string[]} array + * @param {function} callback + * @return {string} + */ +export function combine (array, callback) { + return array.map(callback).join('') +} |