zss-engine 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,8 @@
1
+ export * from './types';
2
+ export * from './utils/helper';
3
+ export { genBase36Hash } from './utils/hash';
4
+ export { injectClientCSS } from './utils/inject-client-css';
5
+ export { injectClientGlobalCSS } from './utils/inject-client-global-css';
6
+ export { injectServerCSS, getServerCSS } from './utils/inject-server-css';
7
+ export { build } from './utils/build';
8
+ export { transpiler } from './utils/transpiler';
package/dist/index.js ADDED
@@ -0,0 +1,8 @@
1
+ export * from './types';
2
+ export * from './utils/helper';
3
+ export { genBase36Hash } from './utils/hash';
4
+ export { injectClientCSS } from './utils/inject-client-css';
5
+ export { injectClientGlobalCSS } from './utils/inject-client-global-css';
6
+ export { injectServerCSS, getServerCSS } from './utils/inject-server-css';
7
+ export { build } from './utils/build';
8
+ export { transpiler } from './utils/transpiler';
@@ -0,0 +1,48 @@
1
+ import type { CSSVariableValue } from './css-variables';
2
+ import type { Properties, Property } from 'csstype';
3
+ type ColorValue = Exclude<Property.Color, '-moz-initial'> | (string & {});
4
+ type CSSColorProperty = Exclude<ColorValue, SystemColorKeyword>;
5
+ type SystemColorKeyword = 'ActiveBorder' | 'ActiveCaption' | 'AppWorkspace' | 'Background' | 'ButtonFace' | 'ButtonHighlight' | 'ButtonShadow' | 'ButtonText' | 'CaptionText' | 'GrayText' | 'Highlight' | 'HighlightText' | 'InactiveBorder' | 'InactiveCaption' | 'InactiveCaptionText' | 'InfoBackground' | 'InfoText' | 'Menu' | 'MenuText' | 'Scrollbar' | 'ThreeDDarkShadow' | 'ThreeDFace' | 'ThreeDHighlight' | 'ThreeDLightShadow' | 'ThreeDShadow' | 'Window' | 'WindowFrame' | 'WindowText';
6
+ type ExcludeMozInitial<T> = Exclude<T, '-moz-initial'>;
7
+ type PropertiesWithoutMozInitial = {
8
+ [K in keyof Properties]: ExcludeMozInitial<Properties[K]>;
9
+ };
10
+ type CSSProperties = Omit<PropertiesWithoutMozInitial, 'fontSize'> & {
11
+ fontSize: PropertiesWithoutMozInitial['fontSize'] | number;
12
+ };
13
+ type BaseCSSProperties = {
14
+ [K in keyof CSSProperties]: CSSProperties[K] | CSSVariableValue;
15
+ };
16
+ interface CommonProperties extends BaseCSSProperties {
17
+ accentColor?: CSSColorProperty;
18
+ color?: CSSColorProperty;
19
+ borderLeftColor?: CSSColorProperty;
20
+ borderRightColor?: CSSColorProperty;
21
+ borderTopColor?: CSSColorProperty;
22
+ borderBottomColor?: CSSColorProperty;
23
+ borderBlockColor?: CSSColorProperty;
24
+ borderBlockStartColor?: CSSColorProperty;
25
+ borderBlockEndColor?: CSSColorProperty;
26
+ borderInlineColor?: CSSColorProperty;
27
+ borderInlineStartColor?: CSSColorProperty;
28
+ borderInlineEndColor?: CSSColorProperty;
29
+ backgroundColor?: CSSColorProperty;
30
+ outlineColor?: CSSColorProperty;
31
+ textDecorationColor?: CSSColorProperty;
32
+ caretColor?: CSSColorProperty;
33
+ columnRuleColor?: CSSColorProperty;
34
+ }
35
+ type AndString = `&${string}`;
36
+ type AndStringType = {
37
+ [key in AndString]: CommonProperties;
38
+ };
39
+ type Colon = `:${string}`;
40
+ type ColonType = {
41
+ [key in Colon]: CommonProperties;
42
+ };
43
+ export type MediaQuery = `@media ${string}`;
44
+ type MediaQueryType = {
45
+ [K in MediaQuery]: CommonProperties | ColonType;
46
+ };
47
+ export type CustomProperties = CommonProperties | ColonType | AndStringType | MediaQueryType;
48
+ export {};
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,5 @@
1
+ type PropertyValue = string | number | PropertyType;
2
+ export type PropertyType = {
3
+ [key: string]: PropertyValue;
4
+ };
5
+ export {};
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,3 @@
1
+ type CSSVariableKey = `--${string}`;
2
+ export type CSSVariableValue = `var(${CSSVariableKey})`;
3
+ export {};
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,5 @@
1
+ export * from './common/css-properties';
2
+ export * from './common/css-property';
3
+ export * from './main/create';
4
+ export * from './main/global';
5
+ export * from './main/vars';
@@ -0,0 +1,5 @@
1
+ export * from './common/css-properties';
2
+ export * from './common/css-property';
3
+ export * from './main/create';
4
+ export * from './main/global';
5
+ export * from './main/vars';
@@ -0,0 +1,10 @@
1
+ import type { CustomProperties } from '../common/css-properties';
2
+ export type CreateStyle<T> = {
3
+ readonly [K in keyof T]: T[K] extends CustomProperties ? CustomProperties : T[K];
4
+ };
5
+ export type ClassesStyle = {
6
+ [key: string]: CustomProperties;
7
+ };
8
+ export type ReturnType<T> = {
9
+ [key in keyof T]: string;
10
+ };
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,34 @@
1
+ import { JSX } from 'react';
2
+ import type { CustomProperties, MediaQuery } from '../common/css-properties';
3
+ type JSXType = keyof JSX.IntrinsicElements | '*' | ':root';
4
+ type HTMLType = {
5
+ [K in JSXType]: CustomProperties;
6
+ };
7
+ type ClassName = `.${string}`;
8
+ type ClassNameType = {
9
+ [K in ClassName]: CustomProperties;
10
+ };
11
+ type Attribute = `${string}[${string}]${string}`;
12
+ type AttributeType = {
13
+ [K in Attribute]: CustomProperties;
14
+ };
15
+ type Consecutive = `${JSXType} ${string}`;
16
+ type ConsecutiveType = {
17
+ [K in Consecutive]: CustomProperties;
18
+ };
19
+ type Pseudo = `${JSXType}:${string}`;
20
+ type PseudoType = {
21
+ [K in Pseudo]: CustomProperties;
22
+ };
23
+ type KeyframeSelector = 'from' | 'to' | `${number}%`;
24
+ export type KeyframesDefinition = {
25
+ [K in KeyframeSelector]?: CustomProperties;
26
+ };
27
+ type KeyframesType = {
28
+ [K in `@keyframes ${string}`]: KeyframesDefinition;
29
+ };
30
+ type MediaQueryHTMLType = {
31
+ [K in MediaQuery]: CustomHTMLType;
32
+ };
33
+ export type CustomHTMLType = HTMLType | ClassNameType | AttributeType | ConsecutiveType | PseudoType | KeyframesType | MediaQueryHTMLType;
34
+ export {};
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1 @@
1
+ export type VarsDefinition = Record<string, string | Record<string, string>>;
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1 @@
1
+ export declare const build: (styleSheet: string, filePath: string, global?: string) => Promise<void>;
@@ -0,0 +1,23 @@
1
+ 'use server';
2
+ import { isServer } from './helper';
3
+ import { styleText } from 'node:util';
4
+ export const build = async (styleSheet, filePath, global) => {
5
+ if (!isServer)
6
+ return;
7
+ const fs = require('fs');
8
+ const message = global === '--global' ? styleText('underline', `✅Generated global CSS\n\n`) : styleText('underline', `✅Generated create CSS\n\n`);
9
+ try {
10
+ if (fs.existsSync(filePath)) {
11
+ const cssData = fs.readFileSync(filePath, 'utf-8');
12
+ if (!cssData.includes(styleSheet)) {
13
+ fs.appendFileSync(filePath, styleSheet, 'utf-8');
14
+ if (process.argv.includes('--log'))
15
+ console.log(message + styleSheet);
16
+ }
17
+ }
18
+ return;
19
+ }
20
+ catch (error) {
21
+ console.error('Error writing to file:', error);
22
+ }
23
+ };
@@ -0,0 +1,2 @@
1
+ import { ClassesStyle, KeyframesDefinition, VarsDefinition } from '..';
2
+ export declare function genBase36Hash(object: ClassesStyle | KeyframesDefinition | VarsDefinition, n: number): string;
@@ -0,0 +1,31 @@
1
+ const chars = 'abcdefghijklmnopqrstuvwxyz0123456789';
2
+ function simpleHash(str) {
3
+ let hash = 0;
4
+ for (let i = 0; i < str.length; i++) {
5
+ const char = str.charCodeAt(i);
6
+ hash = (hash << 5) - hash + char;
7
+ }
8
+ return Math.abs(hash);
9
+ }
10
+ function encodeBase36(num) {
11
+ let result = '';
12
+ do {
13
+ result = chars[num % 36] + result;
14
+ num = Math.floor(num / 36);
15
+ } while (num > 0);
16
+ return result;
17
+ }
18
+ function getStartingChar(hash) {
19
+ const chars = 'abcdefghijklmnopqrstuvwxyz';
20
+ return chars[hash % chars.length];
21
+ }
22
+ export function genBase36Hash(object, n) {
23
+ const serialized = JSON.stringify(object);
24
+ const hash = simpleHash(serialized);
25
+ let base36Hash = encodeBase36(hash);
26
+ const startingChar = getStartingChar(hash);
27
+ while (base36Hash.length < n - 1) {
28
+ base36Hash = chars[hash % chars.length] + base36Hash;
29
+ }
30
+ return startingChar + base36Hash.slice(0, n - 1);
31
+ }
@@ -0,0 +1,6 @@
1
+ export declare const isServer: boolean;
2
+ export declare const isDevelopment: boolean;
3
+ export declare const isDevAndTest: boolean;
4
+ export declare const isDevServer: boolean;
5
+ export declare const applyCssValue: (value: string | number, cssProp: string) => string;
6
+ export declare const camelToKebabCase: (property: string) => string;
@@ -0,0 +1,16 @@
1
+ const isWindowDefined = typeof window !== 'undefined';
2
+ const isDocumentDefined = typeof document !== 'undefined';
3
+ export const isServer = !isWindowDefined || !isDocumentDefined;
4
+ export const isDevelopment = process.env.NODE_ENV === 'development';
5
+ export const isDevAndTest = process.env.NODE_ENV === 'development' || process.env.NODE_ENV === 'test';
6
+ export const isDevServer = isDevelopment && isServer;
7
+ const exception = ['line-height', 'font-weight', 'opacity', 'scale', 'z-index'];
8
+ export const applyCssValue = (value, cssProp) => {
9
+ if (typeof value === 'number') {
10
+ return exception.includes(cssProp) ? value.toString() : value + 'px';
11
+ }
12
+ return value;
13
+ };
14
+ export const camelToKebabCase = (property) => {
15
+ return property.replace(/([A-Z])/g, '-$1').toLowerCase();
16
+ };
@@ -0,0 +1,2 @@
1
+ export declare function createStyleElement(hash: string): HTMLStyleElement | null;
2
+ export declare function injectClientCSS(hash: string, sheet: string): void;
@@ -0,0 +1,47 @@
1
+ import { isServer } from '..';
2
+ const styleSheets = {};
3
+ const hashCache = {};
4
+ export function createStyleElement(hash) {
5
+ if (document.getElementById(hash))
6
+ return null;
7
+ const styleElement = document.createElement('style');
8
+ styleElement.setAttribute('id', hash);
9
+ styleElement.setAttribute('type', 'text/css');
10
+ styleSheets[hash] = styleElement;
11
+ document.head.appendChild(styleElement);
12
+ return styleSheets[hash];
13
+ }
14
+ export function injectClientCSS(hash, sheet) {
15
+ if (isServer)
16
+ return;
17
+ requestAnimationFrame(() => {
18
+ styleCleanUp();
19
+ });
20
+ hashCache[hash] = hash;
21
+ const styleElement = createStyleElement(hash);
22
+ if (styleElement == null)
23
+ return;
24
+ styleElement.textContent = sheet;
25
+ }
26
+ function styleCleanUp() {
27
+ requestAnimationFrame(() => {
28
+ for (const hash in hashCache) {
29
+ const classElements = document.querySelectorAll(`[class*="${hash}"]`);
30
+ if (classElements.length === 0) {
31
+ removeStyleElement(hashCache[hash]);
32
+ }
33
+ }
34
+ });
35
+ }
36
+ function removeStyleElement(hash) {
37
+ if (styleSheets[hash]) {
38
+ delete styleSheets[hash];
39
+ if (hashCache.hasOwnProperty.call(hashCache, hash)) {
40
+ delete hashCache[hash];
41
+ }
42
+ const styleElement = document.getElementById(hash);
43
+ if (styleElement) {
44
+ document.head.removeChild(styleElement);
45
+ }
46
+ }
47
+ }
@@ -0,0 +1 @@
1
+ export declare function injectClientGlobalCSS(sheet: string, scoped: string): void;
@@ -0,0 +1,15 @@
1
+ import { isServer } from '..';
2
+ export function injectClientGlobalCSS(sheet, scoped) {
3
+ if (isServer)
4
+ return;
5
+ const existingStyleElement = document.querySelector(`[data-scope="${scoped}"]`);
6
+ if (existingStyleElement instanceof HTMLStyleElement) {
7
+ existingStyleElement.textContent += sheet;
8
+ return;
9
+ }
10
+ const styleElement = document.createElement('style');
11
+ styleElement.setAttribute('data-scope', scoped);
12
+ styleElement.setAttribute('type', 'text/css');
13
+ styleElement.textContent = sheet;
14
+ document.head.appendChild(styleElement);
15
+ }
@@ -0,0 +1,2 @@
1
+ export declare function injectServerCSS(hash: string, sheet: string): void;
2
+ export declare function getServerCSS(): string;
@@ -0,0 +1,7 @@
1
+ const styleSheets = {};
2
+ export function injectServerCSS(hash, sheet) {
3
+ styleSheets[hash] = sheet;
4
+ }
5
+ export function getServerCSS() {
6
+ return Object.values(styleSheets).join('\n');
7
+ }
@@ -0,0 +1,4 @@
1
+ import type { ClassesStyle, CustomHTMLType, VarsDefinition } from '..';
2
+ export declare function transpiler(object: ClassesStyle | CustomHTMLType | VarsDefinition, base36Hash?: string, core?: string): {
3
+ styleSheet: string;
4
+ };
@@ -0,0 +1,122 @@
1
+ import { camelToKebabCase, applyCssValue } from '..';
2
+ const createKeyframes = (property, content) => {
3
+ let keyframesRules = `${property} {\n`;
4
+ for (const key in content) {
5
+ if (Object.prototype.hasOwnProperty.call(content, key)) {
6
+ const keyframeValue = content[key];
7
+ keyframesRules += ` ${key} {\n`;
8
+ for (const prop in keyframeValue) {
9
+ if (Object.prototype.hasOwnProperty.call(keyframeValue, prop)) {
10
+ const CSSProp = camelToKebabCase(prop);
11
+ const value = keyframeValue[prop];
12
+ if (typeof value === 'string' || typeof value === 'number') {
13
+ const applyValue = applyCssValue(value, CSSProp);
14
+ keyframesRules += ` ${CSSProp}: ${applyValue};\n`;
15
+ }
16
+ }
17
+ }
18
+ keyframesRules += ` }\n`;
19
+ }
20
+ }
21
+ keyframesRules += `}\n`;
22
+ return keyframesRules;
23
+ };
24
+ export function transpiler(object, base36Hash, core) {
25
+ let styleSheet = '';
26
+ const mediaQueries = [];
27
+ const classNameType = (property) => {
28
+ return core === '--global' ? property : `.${property}_${base36Hash}`;
29
+ };
30
+ const rules = (indent, rulesValue, property) => {
31
+ if (typeof property !== 'string')
32
+ return '';
33
+ const value = rulesValue[property];
34
+ const cssProp = camelToKebabCase(property);
35
+ return `${indent}${cssProp}: ${value};\n`;
36
+ };
37
+ const stringConverter = (className, properties, indentLevel = 0) => {
38
+ const classSelector = {};
39
+ const indent = ''.repeat(indentLevel);
40
+ const innerIndent = ' '.repeat(indentLevel + 1);
41
+ let cssRule = '';
42
+ for (const property in properties) {
43
+ if (Object.prototype.hasOwnProperty.call(properties, property)) {
44
+ const value = properties[property];
45
+ if (typeof value === 'string' || typeof value === 'number') {
46
+ let CSSProp = camelToKebabCase(property);
47
+ if (property.startsWith('ms')) {
48
+ CSSProp = `-${CSSProp}`;
49
+ }
50
+ const applyValue = applyCssValue(value, CSSProp);
51
+ cssRule += ` ${CSSProp}: ${applyValue};\n`;
52
+ }
53
+ else if (!property.startsWith('@')) {
54
+ const kebabPseudoSelector = camelToKebabCase(property.replace('&', ''));
55
+ const styles = stringConverter(className + kebabPseudoSelector, value, indentLevel);
56
+ Object.assign(classSelector, styles);
57
+ }
58
+ else if (property.startsWith('@media')) {
59
+ const mediaRule = property;
60
+ let nestedRules = '';
61
+ let regularRules = '';
62
+ for (const mediaProp in value) {
63
+ if (Object.prototype.hasOwnProperty.call(value, mediaProp)) {
64
+ const mediaValue = value[mediaProp];
65
+ const isColon = mediaProp.startsWith(':');
66
+ const isAnd = mediaProp.startsWith('&');
67
+ if (isColon || isAnd) {
68
+ const kebabMediaProp = camelToKebabCase(mediaProp.replace('&', ''));
69
+ let pseudoClassRule = '';
70
+ if (typeof mediaValue === 'object' && mediaValue !== null) {
71
+ for (const pseudoProp in mediaValue) {
72
+ if (Object.prototype.hasOwnProperty.call(mediaValue, pseudoProp)) {
73
+ const CSSProp = camelToKebabCase(pseudoProp);
74
+ const applyValue = applyCssValue(mediaValue[pseudoProp], CSSProp);
75
+ pseudoClassRule += rules(innerIndent + ' ', { [pseudoProp]: applyValue }, pseudoProp);
76
+ }
77
+ }
78
+ }
79
+ nestedRules += `${innerIndent}${className}${kebabMediaProp} {\n${pseudoClassRule}${innerIndent}}\n`;
80
+ }
81
+ else {
82
+ const CSSProp = camelToKebabCase(mediaProp);
83
+ const applyValue = applyCssValue(mediaValue, CSSProp);
84
+ regularRules += rules(innerIndent + ' ', { [mediaProp]: applyValue }, mediaProp);
85
+ }
86
+ }
87
+ }
88
+ if (regularRules) {
89
+ mediaQueries.push({
90
+ media: mediaRule,
91
+ css: `${mediaRule} {\n${innerIndent}${className} {\n${regularRules} }\n${nestedRules}${indent}}${indent}\n`,
92
+ });
93
+ }
94
+ else {
95
+ mediaQueries.push({
96
+ media: mediaRule,
97
+ css: `${mediaRule} {\n${nestedRules}${indent}}\n`,
98
+ });
99
+ }
100
+ }
101
+ }
102
+ }
103
+ classSelector[className] = cssRule;
104
+ return classSelector;
105
+ };
106
+ for (const property in object) {
107
+ if (property.startsWith('@keyframes')) {
108
+ const keyframesContent = object[property];
109
+ styleSheet += createKeyframes(property, keyframesContent);
110
+ }
111
+ const classSelectors = stringConverter(classNameType(property), object[property], 1);
112
+ for (const selector in classSelectors) {
113
+ if (!selector.startsWith('@keyframes') && classSelectors[selector]) {
114
+ styleSheet += selector + ' {\n' + classSelectors[selector] + '}\n';
115
+ }
116
+ }
117
+ }
118
+ mediaQueries.forEach(({ css }) => {
119
+ styleSheet += css;
120
+ });
121
+ return { styleSheet };
122
+ }
package/license ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 zss-in-js contributor
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/package.json ADDED
@@ -0,0 +1,24 @@
1
+ {
2
+ "name": "zss-engine",
3
+ "version": "0.1.0",
4
+ "description": "Zero-runtime Style Sheet Engine",
5
+ "keywords": [
6
+ "zero-runtime",
7
+ "style-sheet",
8
+ "engine",
9
+ "css-in-js"
10
+ ],
11
+ "author": "Refirst",
12
+ "repository": "github:zss-in-js/zss-engine",
13
+ "license": "MIT",
14
+ "main": "dist/index.js",
15
+ "typings": "dist/index.d.ts",
16
+ "devDependencies": {
17
+ "@types/node": "^22.8.5",
18
+ "@types/react": "^19.0.4",
19
+ "typescript": "^5.6.3"
20
+ },
21
+ "dependencies": {
22
+ "csstype": "^3.1.3"
23
+ }
24
+ }