xiangjsoncraft 1.2.0 → 2.0.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,111 @@
1
+ /**
2
+ * XiangJsonCraft 2.0 核心渲染逻辑
3
+ * 纯JSON驱动的前端框架核心 - Write JSON, Get a Website
4
+ * @version 2.0.0
5
+ */
6
+
7
+ // 驼峰转短横线(CSS属性名转换)
8
+ String.prototype.replaceCamelCase = function (separator = '-') {
9
+ return this.replace(/[A-Z]/g, (match) => `${separator}${match.toLowerCase()}`);
10
+ };
11
+
12
+ // 创建唯一样式块
13
+ function createStyleBlock() {
14
+ const style = document.createElement('style');
15
+ style.id = 'xiangjsoncraft-styles';
16
+ document.head.insertBefore(style, document.head.firstChild);
17
+ return style;
18
+ }
19
+
20
+ // 核心渲染函数
21
+ export function renderJsonStyles() {
22
+ // 配置文件路径(固定与HTML同目录)
23
+ const CONFIG_PATH = './config.json';
24
+
25
+ // 错误处理
26
+ const handleError = (msg) => {
27
+ console.error(chalk?.red(`❌ XiangJsonCraft 2.0 错误: ${msg}`) || `❌ XiangJsonCraft 2.0 错误: ${msg}`);
28
+ };
29
+
30
+ // 加载配置并渲染
31
+ fetch(CONFIG_PATH)
32
+ .then(async (res) => {
33
+ if (!res.ok) throw new Error('配置文件加载失败,请检查config.json是否存在');
34
+ return res.json();
35
+ })
36
+ .then((config) => {
37
+ // 配置校验
38
+ if (typeof config !== 'object' || config === null || Array.isArray(config)) {
39
+ return handleError('config.json 格式错误,必须是标准JSON对象');
40
+ }
41
+
42
+ // 1. 渲染样式
43
+ const styleBlock = document.getElementById('xiangjsoncraft-styles') || createStyleBlock();
44
+ const { styles = {} } = config;
45
+ let cssRules = '';
46
+
47
+ // 生成CSS规则
48
+ Object.entries(styles).forEach(([selector, styleObj]) => {
49
+ const selectorStr = String(selector).trim();
50
+ if (!selectorStr || typeof styleObj !== 'object' || styleObj === null || Array.isArray(styleObj)) return;
51
+
52
+ const styleProps = Object.entries(styleObj)
53
+ .map(([prop, value]) => {
54
+ const propStr = String(prop).trim();
55
+ if (!propStr || value === undefined || value === null || String(value).trim() === '') return '';
56
+ const cssProp = propStr.replaceCamelCase();
57
+ const cssValue = String(value).trim();
58
+ return `${cssProp}: ${cssValue};`;
59
+ })
60
+ .filter(Boolean)
61
+ .join('\n ');
62
+
63
+ if (styleProps) {
64
+ cssRules += `${selectorStr} {\n ${styleProps}\n}\n\n`;
65
+ }
66
+ });
67
+
68
+ // 注入样式
69
+ if (cssRules) {
70
+ styleBlock.innerHTML = cssRules;
71
+ }
72
+
73
+ // 2. 渲染内容
74
+ const { content = {} } = config;
75
+ Object.entries(content).forEach(([selector, contentObj]) => {
76
+ const selectorStr = String(selector).trim();
77
+ if (!selectorStr || typeof contentObj !== 'object' || contentObj === null || Array.isArray(contentObj)) return;
78
+ if (!contentObj.hasOwnProperty('value')) return;
79
+
80
+ const targetNodes = document.querySelectorAll(selectorStr);
81
+ if (targetNodes.length === 0) return;
82
+
83
+ const { value, isHtml = false } = contentObj;
84
+ const contentValue = String(value).trim();
85
+
86
+ targetNodes.forEach((node) => {
87
+ if (isHtml) {
88
+ node.innerHTML = contentValue;
89
+ } else {
90
+ node.textContent = contentValue;
91
+ }
92
+ });
93
+ });
94
+
95
+ // 渲染成功提示
96
+ console.log(chalk?.green('✅ XiangJsonCraft 2.0 渲染成功!') || '✅ XiangJsonCraft 2.0 渲染成功!');
97
+ console.log(chalk?.blue('ℹ️ 修改config.json后刷新页面即可生效') || 'ℹ️ 修改config.json后刷新页面即可生效');
98
+
99
+ })
100
+ .catch((err) => {
101
+ handleError(err.message);
102
+ });
103
+ }
104
+
105
+ // 暴露全局变量(兼容非模块化环境)
106
+ if (typeof window !== 'undefined') {
107
+ window.XiangJsonCraft = {
108
+ renderJsonStyles,
109
+ version: '2.0.0'
110
+ };
111
+ }
@@ -0,0 +1,36 @@
1
+ {
2
+ "styles": {
3
+ "*": {
4
+ "margin": 0,
5
+ "padding": 0,
6
+ "boxSizing": "border-box",
7
+ "fontFamily": "Segoe UI, Roboto, 微软雅黑, sans-serif"
8
+ },
9
+ "body": {
10
+ "backgroundColor": "#f0f4f8",
11
+ "color": "#334e68",
12
+ "lineHeight": "1.6",
13
+ "minHeight": "100vh",
14
+ "padding": "2rem 1rem"
15
+ },
16
+ ".container": {
17
+ "width": "300px",
18
+ "height": "300px",
19
+ "backgroundColor": "blue"
20
+ },
21
+ "#text": {
22
+ "padding": "20px",
23
+ "color": "white"
24
+ }
25
+ },
26
+ "content": {
27
+ "#text": {
28
+ "value": "<a href='/'>hahh</a>",
29
+ "isHtml": true
30
+ },
31
+ "#text2": {
32
+ "value": "hello world",
33
+ "isHtml": false
34
+ }
35
+ }
36
+ }
@@ -0,0 +1,44 @@
1
+ <!DOCTYPE html>
2
+ <html lang="zh-CN">
3
+
4
+ <head>
5
+ <meta charset="UTF-8">
6
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
7
+ <title>XiangJsonCraft 2.0 App</title>
8
+ <!-- 框架样式块(自动注入) -->
9
+ <style id="xiangjsoncraft-styles"></style>
10
+ <link rel="icon"
11
+ href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'><text y='90' font-size='90'>🟢</text></svg>">
12
+ </head>
13
+
14
+ <body>
15
+ <!-- 应用容器(仅DOM骨架,样式/内容由JSON控制) -->
16
+ <div class="app-container">
17
+ <h1 class="app-title"></h1>
18
+ <p class="app-desc"></p>
19
+ <div class="app-btn-group">
20
+ <a href="javascript:;" class="app-btn app-btn-primary"></a>
21
+ <a href="javascript:;" class="app-btn app-btn-secondary"></a>
22
+ </div>
23
+ </div>
24
+
25
+ <!-- 引入框架核心逻辑 -->
26
+ <script type="module">
27
+ // 模块化引入(现代浏览器)
28
+ import { renderJsonStyles } from './node_modules/xiangjsoncraft/src/renderJson.js';
29
+ // 页面加载完成后渲染
30
+ document.addEventListener('DOMContentLoaded', () => {
31
+ renderJsonStyles();
32
+ });
33
+ </script>
34
+
35
+ <!-- 兼容非模块化环境(备用) -->
36
+ <script nomodule src="./node_modules/xiangjsoncraft/dist/xiangjsoncraft.umd.js"></script>
37
+ <script nomodule>
38
+ document.addEventListener('DOMContentLoaded', () => {
39
+ XiangJsonCraft.renderJsonStyles();
40
+ });
41
+ </script>
42
+ </body>
43
+
44
+ </html>
package/config.json DELETED
@@ -1,113 +0,0 @@
1
- {
2
- "styles": {
3
- "*": {
4
- "margin": 0,
5
- "padding": 0,
6
- "boxSizing": "border-box",
7
- "fontFamily": "Segoe UI, Roboto, 微软雅黑, sans-serif"
8
- },
9
- "body": {
10
- "backgroundColor": "#f0f4f8",
11
- "color": "#334e68",
12
- "lineHeight": "1.6",
13
- "minHeight": "100vh",
14
- "padding": "2rem 1rem"
15
- },
16
- ".welcome-container": {
17
- "maxWidth": "1200px",
18
- "margin": "0 auto",
19
- "padding": "2rem"
20
- },
21
- ".main-title": {
22
- "fontSize": "2.2rem",
23
- "color": "#2d3748",
24
- "textAlign": "center",
25
- "marginBottom": "1.5rem",
26
- "fontWeight": 700
27
- },
28
- ".subtitle": {
29
- "fontSize": "1.1rem",
30
- "color": "#718096",
31
- "textAlign": "center",
32
- "maxWidth": "800px",
33
- "margin": "0 auto 3rem",
34
- "lineHeight": "1.8"
35
- },
36
- ".feature-card": {
37
- "backgroundColor": "#ffffff",
38
- "borderRadius": "12px",
39
- "padding": "2rem",
40
- "boxShadow": "0 4px 12px rgba(0,0,0,0.05)",
41
- "maxWidth": "600px",
42
- "margin": "0 auto 2rem",
43
- "transition": "transform 0.3s ease, boxShadow 0.3s ease"
44
- },
45
- ".feature-card:hover": {
46
- "transform": "translateY(-6px)",
47
- "boxShadow": "0 8px 20px rgba(0,0,0,0.08)"
48
- },
49
- ".feature-title": {
50
- "fontSize": "1.4rem",
51
- "color": "#4299e1",
52
- "marginBottom": "1rem",
53
- "fontWeight": 600
54
- },
55
- ".feature-desc": {
56
- "color": "#718096",
57
- "lineHeight": "1.7"
58
- },
59
- ".primary-btn": {
60
- "display": "block",
61
- "width": "200px",
62
- "height": "50px",
63
- "lineHeight": "50px",
64
- "textAlign": "center",
65
- "backgroundColor": "#4299e1",
66
- "color": "#ffffff",
67
- "border": "none",
68
- "borderRadius": "8px",
69
- "margin": "0 auto",
70
- "fontSize": "1rem",
71
- "fontWeight": 600,
72
- "cursor": "pointer",
73
- "transition": "backgroundColor 0.3s ease, transform 0.2s ease"
74
- },
75
- ".primary-btn:hover": {
76
- "backgroundColor": "#3182ce",
77
- "transform": "translateY(-2px)"
78
- },
79
- "@media (max-width: 768px)": {
80
- ".main-title": {
81
- "fontSize": "1.8rem"
82
- },
83
- ".welcome-container": {
84
- "padding": "1rem"
85
- },
86
- ".feature-card": {
87
- "padding": "1.5rem"
88
- }
89
- }
90
- },
91
- "content": {
92
- ".main-title": {
93
- "value": "XiangJsonCraft 测试页面",
94
- "isHtml": false
95
- },
96
- ".subtitle": {
97
- "value": "基于JSON配置渲染的页面,无需手写CSS,修改JSON即可定制样式和内容!",
98
- "isHtml": false
99
- },
100
- ".feature-title": {
101
- "value": "核心特色:JSON解耦",
102
- "isHtml": false
103
- },
104
- ".feature-desc": {
105
- "value": "HTML仅负责搭建DOM骨架,样式、内容全由JSON控制,后期维护只需修改JSON,无需触碰HTML/JS代码。",
106
- "isHtml": false
107
- },
108
- ".primary-btn": {
109
- "value": "<span onclick=\"window.open('https://www.npmjs.com/package/xiangjsoncraft', '_blank')\">点我查看 NPM 包地址</span>",
110
- "isHtml": true
111
- }
112
- }
113
- }
package/renderJson.js DELETED
@@ -1,133 +0,0 @@
1
- // 为String原型扩展驼峰转短横线方法,用于CSS属性名转换
2
- String.prototype.replaceCamelCase = function (separator = '-') {
3
- // 容错:如果是纯数字/无驼峰的字符串,直接返回
4
- return this.replace(/[A-Z]/g, (match) => `${separator}${match.toLowerCase()}`);
5
- };
6
-
7
- // 创建样式块并添加到文档头部,确保唯一且不重复创建
8
- function createStyleBlock() {
9
- const style = document.createElement('style');
10
- style.id = 'dynamic-styles';
11
- // 把样式块插入到head最前面,避免被其他样式覆盖
12
- document.head.insertBefore(style, document.head.firstChild);
13
- return style;
14
- }
15
-
16
- // 核心渲染函数:本地config.json最高优先级,CDN配置兜底,全量容错
17
- export function renderJsonStyles() {
18
- // 配置地址:本地自定义(最高优先级) + CDN官方兜底
19
- const LOCAL_CONFIG = './config.json';
20
- const CDN_CONFIG = 'https://cdn.jsdelivr.net/npm/xiangjsoncraft@1.1.1/config.json';
21
-
22
- // 统一错误处理函数,避免单个错误中断整体渲染
23
- const handleError = (msg) => {
24
- console.error(`❌ XiangJsonCraft 错误: ${msg}`);
25
- };
26
-
27
- // 核心逻辑:先加载本地配置,失败则加载CDN兜底配置
28
- fetch(LOCAL_CONFIG)
29
- .then(async (res) => {
30
- // 本地配置存在(状态码200-299),使用本地配置
31
- if (res.ok) {
32
- console.log('🔧 检测到本地config.json,优先加载用户自定义配置');
33
- return res.json();
34
- }
35
- // 本地配置不存在/加载失败,切换到CDN配置
36
- console.log('ℹ️ 本地未检测到config.json,加载CDN官方示例配置');
37
- const cdnRes = await fetch(CDN_CONFIG);
38
- if (!cdnRes.ok) throw new Error('CDN官方配置加载失败,请检查网络');
39
- return cdnRes.json();
40
- })
41
- .then((config) => {
42
- // 容错:如果配置不是对象,直接终止渲染
43
- if (typeof config !== 'object' || config === null || Array.isArray(config)) {
44
- return handleError('配置文件格式错误,必须是标准JSON对象');
45
- }
46
-
47
- // 获取/创建样式块,确保渲染容器存在
48
- const styleBlock = document.getElementById('dynamic-styles') || createStyleBlock();
49
- if (!styleBlock) return handleError('样式块创建失败,无法渲染CSS');
50
-
51
- // 生成CSS规则:处理styles节点,全量容错+格式美化
52
- let cssRules = '';
53
- const { styles = {} } = config; // 解构赋值,避免styles未定义
54
-
55
- // 遍历所有CSS选择器(支持类、ID、媒体查询等所有合法选择器)
56
- Object.entries(styles).forEach(([selector, styleObj]) => {
57
- // 容错1:选择器为空/非字符串,跳过
58
- const selectorStr = String(selector).trim();
59
- if (!selectorStr) return;
60
- // 容错2:选择器对应的不是样式对象,跳过
61
- if (typeof styleObj !== 'object' || styleObj === null || Array.isArray(styleObj)) return;
62
-
63
- // 处理单个选择器下的所有样式属性
64
- const styleProps = Object.entries(styleObj)
65
- .map(([prop, value]) => {
66
- // 容错3:属性名强制转字符串+去空格,空属性直接跳过
67
- const propStr = String(prop).trim();
68
- if (!propStr) return '';
69
- // 容错4:属性值为空/无效,直接跳过
70
- if (value === undefined || value === null || String(value).trim() === '') return '';
71
- // 驼峰属性名转CSS标准短横线(如fontSize → font-size)
72
- const cssProp = propStr.replaceCamelCase();
73
- // 属性值强制转字符串,避免数字/布尔值渲染异常
74
- const cssValue = String(value).trim();
75
- // 返回合法的CSS属性行
76
- return `${cssProp}: ${cssValue};`;
77
- })
78
- .filter(Boolean) // 过滤所有空字符串,只留合法属性
79
- .join('\n '); // 拼接成带缩进的整洁CSS
80
-
81
- // 只有当有合法样式属性时,才生成CSS规则,避免空选择器
82
- if (styleProps) {
83
- cssRules += `${selectorStr} {\n ${styleProps}\n}\n\n`;
84
- }
85
- });
86
-
87
- // 将生成的CSS注入到样式块,覆盖原有内容(避免重复渲染)
88
- if (cssRules) {
89
- styleBlock.innerHTML = cssRules;
90
- }
91
-
92
- // 渲染内容:处理content节点,向DOM注入文本/HTML
93
- const { content = {} } = config; // 解构赋值,避免content未定义
94
- Object.entries(content).forEach(([selector, contentObj]) => {
95
- // 容错1:选择器为空/非字符串,跳过
96
- const selectorStr = String(selector).trim();
97
- if (!selectorStr) return;
98
- // 容错2:内容配置不是对象,跳过
99
- if (typeof contentObj !== 'object' || contentObj === null || Array.isArray(contentObj)) return;
100
- // 容错3:缺少核心的value属性,跳过
101
- if (!contentObj.hasOwnProperty('value')) return;
102
-
103
- // 获取要注入内容的DOM节点,容错:节点不存在则跳过
104
- const targetNodes = document.querySelectorAll(selectorStr);
105
- if (targetNodes.length === 0) return;
106
-
107
- // 解析内容配置:isHtml默认为false(纯文本)
108
- const { value, isHtml = false } = contentObj;
109
- const contentValue = String(value).trim(); // 内容强制转字符串,避免异常
110
-
111
- // 向所有匹配的DOM节点注入内容
112
- targetNodes.forEach((node) => {
113
- if (isHtml) {
114
- node.innerHTML = contentValue; // HTML片段渲染
115
- } else {
116
- node.textContent = contentValue; // 纯文本渲染(防XSS)
117
- }
118
- });
119
- });
120
-
121
- // 渲染成功,打印友好提示
122
- console.log('✅ XiangJsonCraft 渲染成功!样式+内容已全部加载');
123
- })
124
- .catch((err) => {
125
- // 捕获所有异步错误,统一处理
126
- handleError(err.message);
127
- });
128
- }
129
-
130
- // 兼容UMD打包:暴露全局变量,方便CDN引入调用
131
- if (typeof window !== 'undefined') {
132
- window.XiangJsonCraft = { renderJsonStyles };
133
- }