@emberkit/core 0.1.2-alpha.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.
- package/LICENSE +199 -0
- package/dist/boundaries/error-boundary.js +70 -0
- package/dist/boundaries/errors.js +72 -0
- package/dist/boundaries/index.js +3 -0
- package/dist/boundaries/loading-boundary.js +106 -0
- package/dist/cache/index.js +213 -0
- package/dist/compiler/compiler.js +44 -0
- package/dist/compiler/helpers/attributes.js +35 -0
- package/dist/compiler/helpers/utils.js +31 -0
- package/dist/compiler/index.js +4 -0
- package/dist/compiler/types.js +3 -0
- package/dist/context/index.js +51 -0
- package/dist/context/types.js +1 -0
- package/dist/dev-server/index.js +121 -0
- package/dist/forms/index.js +164 -0
- package/dist/forms/mutations.js +258 -0
- package/dist/hmr/client.js +84 -0
- package/dist/hmr/index.js +2 -0
- package/dist/hmr/types.js +133 -0
- package/dist/hydration/helpers/analyzer.js +94 -0
- package/dist/hydration/helpers/hydration.js +129 -0
- package/dist/hydration/index.js +3 -0
- package/dist/hydration/types.js +18 -0
- package/dist/image/index.js +34 -0
- package/dist/image/processor.js +143 -0
- package/dist/index.js +16 -0
- package/dist/jsx-dev-runtime.js +7 -0
- package/dist/jsx-runtime.js +7 -0
- package/dist/loader/helpers/loader.js +61 -0
- package/dist/loader/index.js +2 -0
- package/dist/loader/types.js +14 -0
- package/dist/markdown/index.js +365 -0
- package/dist/mdx/index.js +156 -0
- package/dist/mdx/loader.js +6 -0
- package/dist/meta/head-registry.js +15 -0
- package/dist/meta/head.js +100 -0
- package/dist/meta/index.js +210 -0
- package/dist/navigation/helpers/navigation.js +53 -0
- package/dist/navigation/helpers/useNavigate.js +10 -0
- package/dist/navigation/index.js +3 -0
- package/dist/navigation/types.js +2 -0
- package/dist/plugin/index.js +74 -0
- package/dist/router/helpers/path.js +74 -0
- package/dist/router/helpers/route.js +109 -0
- package/dist/router/index.js +110 -0
- package/dist/router/types.js +5 -0
- package/dist/runtime/helpers/element.js +52 -0
- package/dist/runtime/helpers/render.js +121 -0
- package/dist/runtime/index.js +132 -0
- package/dist/runtime/types.js +1 -0
- package/dist/signals/helpers/core.js +96 -0
- package/dist/signals/helpers/utils.js +22 -0
- package/dist/signals/index.js +3 -0
- package/dist/signals/types.js +1 -0
- package/dist/ssg/index.js +119 -0
- package/dist/ssr/helpers/render-html.js +55 -0
- package/dist/ssr/helpers/ssr.js +90 -0
- package/dist/ssr/index.js +3 -0
- package/dist/ssr/types.js +24 -0
- package/dist/vite-plugin/index.js +650 -0
- package/dist/vite-plugin/types.js +13 -0
- package/package.json +66 -0
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { compileAttributes, compileChildren } from './helpers/attributes.js';
|
|
2
|
+
export function compileToTemplate(tag, props, children) {
|
|
3
|
+
const parts = [];
|
|
4
|
+
parts.push({ type: 'string', value: `<${tag}` });
|
|
5
|
+
const attributeParts = compileAttributes(props);
|
|
6
|
+
parts.push(...attributeParts);
|
|
7
|
+
parts.push({ type: 'string', value: '>' });
|
|
8
|
+
const childrenParts = compileChildren(children);
|
|
9
|
+
parts.push(...childrenParts);
|
|
10
|
+
parts.push({ type: 'string', value: `</${tag}>` });
|
|
11
|
+
return {
|
|
12
|
+
parts,
|
|
13
|
+
dependencies: [],
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
export function compileSelfClosing(tag, props) {
|
|
17
|
+
const parts = [];
|
|
18
|
+
parts.push({ type: 'string', value: `<${tag}` });
|
|
19
|
+
const attributeParts = compileAttributes(props);
|
|
20
|
+
parts.push(...attributeParts);
|
|
21
|
+
parts.push({ type: 'string', value: '/>' });
|
|
22
|
+
return {
|
|
23
|
+
parts,
|
|
24
|
+
dependencies: [],
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
export function compileTextContent(text) {
|
|
28
|
+
return {
|
|
29
|
+
parts: [{ type: 'string', value: text }],
|
|
30
|
+
dependencies: [],
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
export function assembleTemplate(parts) {
|
|
34
|
+
let result = '';
|
|
35
|
+
for (const part of parts) {
|
|
36
|
+
if (part.type === 'string') {
|
|
37
|
+
result += part.value;
|
|
38
|
+
}
|
|
39
|
+
else {
|
|
40
|
+
result += '${' + part.value + '}';
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
return result;
|
|
44
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { escapeString } from './utils.js';
|
|
2
|
+
export function compileAttributes(props) {
|
|
3
|
+
const parts = [];
|
|
4
|
+
for (const [key, value] of Object.entries(props)) {
|
|
5
|
+
if (key === 'children' || key === 'key')
|
|
6
|
+
continue;
|
|
7
|
+
if (value === true) {
|
|
8
|
+
parts.push({ type: 'string', value: ` ${key}` });
|
|
9
|
+
}
|
|
10
|
+
else if (value === false) {
|
|
11
|
+
continue;
|
|
12
|
+
}
|
|
13
|
+
else if (typeof value === 'string') {
|
|
14
|
+
parts.push({ type: 'string', value: ` ${key}="${escapeString(value)}"` });
|
|
15
|
+
}
|
|
16
|
+
else {
|
|
17
|
+
parts.push({ type: 'string', value: ` ${key}=` });
|
|
18
|
+
parts.push({ type: 'expression', value: String(value) });
|
|
19
|
+
parts.push({ type: 'string', value: '"' });
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
return parts;
|
|
23
|
+
}
|
|
24
|
+
export function compileChildren(children) {
|
|
25
|
+
const parts = [];
|
|
26
|
+
for (const child of children) {
|
|
27
|
+
if (typeof child === 'string') {
|
|
28
|
+
parts.push({ type: 'string', value: escapeString(child) });
|
|
29
|
+
}
|
|
30
|
+
else {
|
|
31
|
+
parts.push({ type: 'expression', value: String(child) });
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
return parts;
|
|
35
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
export function escapeString(str) {
|
|
2
|
+
return str.replace(/[`\\${}]/g, '\\$&');
|
|
3
|
+
}
|
|
4
|
+
export function generateTemplateParts(parts) {
|
|
5
|
+
const templateParts = [];
|
|
6
|
+
const expressions = [];
|
|
7
|
+
for (const part of parts) {
|
|
8
|
+
if (part.type === 'string') {
|
|
9
|
+
templateParts.push(escapeString(part.value));
|
|
10
|
+
}
|
|
11
|
+
else {
|
|
12
|
+
templateParts.push('${');
|
|
13
|
+
templateParts.push(part.value);
|
|
14
|
+
templateParts.push('}');
|
|
15
|
+
expressions.push(part.value);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
return {
|
|
19
|
+
template: '`' + templateParts.join('') + '`',
|
|
20
|
+
expressions,
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
export function toKebabCase(str) {
|
|
24
|
+
return str.replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase();
|
|
25
|
+
}
|
|
26
|
+
export function isValidComponentName(name) {
|
|
27
|
+
return /^[A-Z]/.test(name) || name === 'Fragment';
|
|
28
|
+
}
|
|
29
|
+
export function isHtmlElement(name) {
|
|
30
|
+
return /^[a-z]/.test(name);
|
|
31
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
const contextRegistry = new Map();
|
|
2
|
+
const contextValues = new Map();
|
|
3
|
+
export function createContext(defaultValue) {
|
|
4
|
+
const context = {
|
|
5
|
+
id: Symbol('emberkit.context'),
|
|
6
|
+
defaultValue,
|
|
7
|
+
};
|
|
8
|
+
contextRegistry.set(context.id, context);
|
|
9
|
+
const Provider = createContextProvider(context);
|
|
10
|
+
return {
|
|
11
|
+
id: context.id,
|
|
12
|
+
defaultValue,
|
|
13
|
+
Provider,
|
|
14
|
+
use: () => useContext(context),
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
export function setContextValue(context, value) {
|
|
18
|
+
contextValues.set(context.id, value);
|
|
19
|
+
}
|
|
20
|
+
export function getContextValue(context) {
|
|
21
|
+
const value = contextValues.get(context.id);
|
|
22
|
+
if (value === undefined)
|
|
23
|
+
return context.defaultValue;
|
|
24
|
+
return value;
|
|
25
|
+
}
|
|
26
|
+
export function hasContext(context) {
|
|
27
|
+
return contextRegistry.has(context.id);
|
|
28
|
+
}
|
|
29
|
+
export function clearAllContexts() {
|
|
30
|
+
contextRegistry.clear();
|
|
31
|
+
contextValues.clear();
|
|
32
|
+
}
|
|
33
|
+
export function useContext(context) {
|
|
34
|
+
const value = contextValues.get(context.id);
|
|
35
|
+
if (value === undefined) {
|
|
36
|
+
if (context.defaultValue === undefined) {
|
|
37
|
+
throw new Error(`Context ${String(context.id)} has no value`);
|
|
38
|
+
}
|
|
39
|
+
return context.defaultValue;
|
|
40
|
+
}
|
|
41
|
+
return value;
|
|
42
|
+
}
|
|
43
|
+
export function createContextProvider(context) {
|
|
44
|
+
return function Provider(props) {
|
|
45
|
+
setContextValue(context, props.value);
|
|
46
|
+
return {
|
|
47
|
+
type: 'Fragment',
|
|
48
|
+
props: { children: props.children },
|
|
49
|
+
};
|
|
50
|
+
};
|
|
51
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const CONTEXT_MARKER = Symbol.for('emberkit.context');
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
export class DevServer {
|
|
2
|
+
server = null;
|
|
3
|
+
options;
|
|
4
|
+
startTime = 0;
|
|
5
|
+
requestCount = 0;
|
|
6
|
+
errorCount = 0;
|
|
7
|
+
constructor(options = {}) {
|
|
8
|
+
this.options = {
|
|
9
|
+
port: options.port ?? 3000,
|
|
10
|
+
host: options.host ?? 'localhost',
|
|
11
|
+
cors: options.cors ?? true,
|
|
12
|
+
hmr: options.hmr ?? true,
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
async start() {
|
|
16
|
+
this.startTime = Date.now();
|
|
17
|
+
const http = await import('http');
|
|
18
|
+
const handler = this.createRequestHandler();
|
|
19
|
+
this.server = http.createServer(handler);
|
|
20
|
+
await new Promise((resolve, reject) => {
|
|
21
|
+
this.server.listen(this.options.port, this.options.host, () => {
|
|
22
|
+
console.log(`Dev server running at http://${this.options.host}:${this.options.port}`);
|
|
23
|
+
resolve();
|
|
24
|
+
});
|
|
25
|
+
this.server.on('error', (err) => {
|
|
26
|
+
console.error('Server error:', err);
|
|
27
|
+
reject(err);
|
|
28
|
+
});
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
async stop() {
|
|
32
|
+
return new Promise((resolve, reject) => {
|
|
33
|
+
if (!this.server) {
|
|
34
|
+
resolve();
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
this.server.close((err) => {
|
|
38
|
+
if (err) {
|
|
39
|
+
reject(err);
|
|
40
|
+
}
|
|
41
|
+
else {
|
|
42
|
+
this.server = null;
|
|
43
|
+
resolve();
|
|
44
|
+
}
|
|
45
|
+
});
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
getStats() {
|
|
49
|
+
return {
|
|
50
|
+
uptime: Date.now() - this.startTime,
|
|
51
|
+
requests: this.requestCount,
|
|
52
|
+
errors: this.errorCount,
|
|
53
|
+
memoryUsage: process.memoryUsage(),
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
createRequestHandler() {
|
|
57
|
+
return async (req, res) => {
|
|
58
|
+
this.requestCount++;
|
|
59
|
+
try {
|
|
60
|
+
await this.handleRequest(req, res);
|
|
61
|
+
}
|
|
62
|
+
catch (error) {
|
|
63
|
+
this.errorCount++;
|
|
64
|
+
console.error('Request error:', error);
|
|
65
|
+
this.sendError(res, 500, 'Internal Server Error');
|
|
66
|
+
}
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
async handleRequest(req, res) {
|
|
70
|
+
const url = new URL(req.url ?? '/', `http://${req.headers.host}`);
|
|
71
|
+
if (url.pathname === '/__emberkit_hmr') {
|
|
72
|
+
this.handleWebSocket(req, res);
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
if (this.options.cors) {
|
|
76
|
+
res.setHeader('Access-Control-Allow-Origin', '*');
|
|
77
|
+
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');
|
|
78
|
+
res.setHeader('Access-Control-Allow-Headers', 'Content-Type');
|
|
79
|
+
}
|
|
80
|
+
if (req.method === 'OPTIONS') {
|
|
81
|
+
res.writeHead(204);
|
|
82
|
+
res.end();
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
res.setHeader('Content-Type', 'text/html');
|
|
86
|
+
res.setHeader('Cache-Control', 'no-cache');
|
|
87
|
+
const html = await this.generateHTML(url.pathname);
|
|
88
|
+
res.writeHead(200);
|
|
89
|
+
res.end(html);
|
|
90
|
+
}
|
|
91
|
+
handleWebSocket(req, res) {
|
|
92
|
+
res.writeHead(101, {
|
|
93
|
+
'Upgrade': 'websocket',
|
|
94
|
+
'Connection': 'Upgrade',
|
|
95
|
+
});
|
|
96
|
+
res.end();
|
|
97
|
+
}
|
|
98
|
+
async generateHTML(pathname) {
|
|
99
|
+
return `<!DOCTYPE html>
|
|
100
|
+
<html>
|
|
101
|
+
<head>
|
|
102
|
+
<meta charset="utf-8">
|
|
103
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
104
|
+
<title>EmberKit Dev</title>
|
|
105
|
+
<script type="module" src="/@vite/client"></script>
|
|
106
|
+
</head>
|
|
107
|
+
<body>
|
|
108
|
+
<div id="app"></div>
|
|
109
|
+
</body>
|
|
110
|
+
</html>`;
|
|
111
|
+
}
|
|
112
|
+
sendError(res, code, message) {
|
|
113
|
+
res.writeHead(code, { 'Content-Type': 'text/plain' });
|
|
114
|
+
res.end(message);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
export async function createDevServer(options) {
|
|
118
|
+
const server = new DevServer(options);
|
|
119
|
+
await server.start();
|
|
120
|
+
return server;
|
|
121
|
+
}
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
export class FormValidator {
|
|
2
|
+
schema;
|
|
3
|
+
constructor(schema) {
|
|
4
|
+
this.schema = schema;
|
|
5
|
+
}
|
|
6
|
+
validate(data) {
|
|
7
|
+
const errors = {};
|
|
8
|
+
for (const [field, validator] of Object.entries(this.schema.fields)) {
|
|
9
|
+
const value = data[field];
|
|
10
|
+
const error = this.validateField(field, value, validator);
|
|
11
|
+
if (error) {
|
|
12
|
+
errors[field] = error;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
return errors;
|
|
16
|
+
}
|
|
17
|
+
validateField(name, value, validator) {
|
|
18
|
+
if (validator.required && (value === undefined || value === null || value === '')) {
|
|
19
|
+
return `${name} is required`;
|
|
20
|
+
}
|
|
21
|
+
if (typeof value === 'string') {
|
|
22
|
+
if (validator.minLength && value.length < validator.minLength) {
|
|
23
|
+
return `${name} must be at least ${validator.minLength} characters`;
|
|
24
|
+
}
|
|
25
|
+
if (validator.maxLength && value.length > validator.maxLength) {
|
|
26
|
+
return `${name} must be at most ${validator.maxLength} characters`;
|
|
27
|
+
}
|
|
28
|
+
if (validator.pattern && !validator.pattern.test(value)) {
|
|
29
|
+
return `${name} is invalid`;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
if (validator.custom) {
|
|
33
|
+
const customError = validator.custom(value);
|
|
34
|
+
if (customError) {
|
|
35
|
+
return customError;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
return undefined;
|
|
39
|
+
}
|
|
40
|
+
validateFieldOnly(name, value) {
|
|
41
|
+
const validator = this.schema.fields[name];
|
|
42
|
+
if (!validator)
|
|
43
|
+
return undefined;
|
|
44
|
+
return this.validateField(name, value, validator);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
export function createFormValidator(schema) {
|
|
48
|
+
return new FormValidator(schema);
|
|
49
|
+
}
|
|
50
|
+
export function parseFormData(form) {
|
|
51
|
+
const formData = new FormData(form);
|
|
52
|
+
const data = {};
|
|
53
|
+
for (const [key, value] of formData.entries()) {
|
|
54
|
+
if (data[key] !== undefined) {
|
|
55
|
+
if (!Array.isArray(data[key])) {
|
|
56
|
+
data[key] = [data[key]];
|
|
57
|
+
}
|
|
58
|
+
data[key].push(value);
|
|
59
|
+
}
|
|
60
|
+
else {
|
|
61
|
+
data[key] = value;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
return data;
|
|
65
|
+
}
|
|
66
|
+
export function createFormState(initial = {}) {
|
|
67
|
+
return {
|
|
68
|
+
values: { ...initial },
|
|
69
|
+
errors: {},
|
|
70
|
+
touched: new Set(),
|
|
71
|
+
dirty: false,
|
|
72
|
+
submitting: false,
|
|
73
|
+
submitted: false,
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
export function updateFormState(state, updates) {
|
|
77
|
+
return { ...state, ...updates };
|
|
78
|
+
}
|
|
79
|
+
export function getFieldValue(state, name) {
|
|
80
|
+
return state.values[name];
|
|
81
|
+
}
|
|
82
|
+
export function setFieldValue(state, name, value) {
|
|
83
|
+
return {
|
|
84
|
+
...state,
|
|
85
|
+
values: { ...state.values, [name]: value },
|
|
86
|
+
dirty: true,
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
export function setFieldError(state, name, error) {
|
|
90
|
+
return {
|
|
91
|
+
...state,
|
|
92
|
+
errors: { ...state.errors, [name]: error },
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
export function clearFormState(state) {
|
|
96
|
+
return {
|
|
97
|
+
values: {},
|
|
98
|
+
errors: {},
|
|
99
|
+
touched: new Set(),
|
|
100
|
+
dirty: false,
|
|
101
|
+
submitting: false,
|
|
102
|
+
submitted: false,
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
export async function handleFormSubmit(event, config) {
|
|
106
|
+
event.preventDefault();
|
|
107
|
+
const form = event.target;
|
|
108
|
+
const data = parseFormData(form);
|
|
109
|
+
if (config.validation) {
|
|
110
|
+
const validator = new FormValidator(config.validation);
|
|
111
|
+
const errors = validator.validate(data);
|
|
112
|
+
if (Object.keys(errors).length > 0) {
|
|
113
|
+
config.onError?.(errors, event);
|
|
114
|
+
return false;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
if (config.onSubmit) {
|
|
118
|
+
const formData = new FormData(form);
|
|
119
|
+
try {
|
|
120
|
+
await config.onSubmit(formData, event);
|
|
121
|
+
return true;
|
|
122
|
+
}
|
|
123
|
+
catch (err) {
|
|
124
|
+
return false;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
return true;
|
|
128
|
+
}
|
|
129
|
+
export const DEFAULT_VALIDATORS = {
|
|
130
|
+
email: {
|
|
131
|
+
pattern: /^[^\s@]+@[^\s@]+\.[^\s@]+$/,
|
|
132
|
+
custom: (v) => typeof v === 'string' && !v.includes('@') ? 'Invalid email' : null,
|
|
133
|
+
},
|
|
134
|
+
url: {
|
|
135
|
+
pattern: /^https?:\/\/.+/,
|
|
136
|
+
},
|
|
137
|
+
phone: {
|
|
138
|
+
pattern: /^\+?[\d\s-]{10,}$/,
|
|
139
|
+
},
|
|
140
|
+
};
|
|
141
|
+
export function applyDefaultValidator(field, validator) {
|
|
142
|
+
const defaultValidator = DEFAULT_VALIDATORS[field];
|
|
143
|
+
if (!defaultValidator)
|
|
144
|
+
return validator;
|
|
145
|
+
return {
|
|
146
|
+
...defaultValidator,
|
|
147
|
+
...validator,
|
|
148
|
+
custom: (value) => {
|
|
149
|
+
const defaultError = defaultValidator.custom?.(value);
|
|
150
|
+
if (defaultError)
|
|
151
|
+
return defaultError;
|
|
152
|
+
return validator.custom?.(value);
|
|
153
|
+
},
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
export function buildFormAction(action, method, params) {
|
|
157
|
+
const url = new URL(action, 'http://localhost');
|
|
158
|
+
if (params) {
|
|
159
|
+
for (const [key, value] of Object.entries(params)) {
|
|
160
|
+
url.searchParams.set(key, value);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
return url.toString();
|
|
164
|
+
}
|