@codespark/framework 1.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.
- package/dist/html.d.ts +66 -0
- package/dist/html.js +442 -0
- package/dist/index.d.ts +76 -0
- package/dist/index.js +87 -0
- package/dist/loaders.d.ts +87 -0
- package/dist/loaders.js +175 -0
- package/dist/markdown.d.ts +52 -0
- package/dist/markdown.js +75 -0
- package/dist/react.d.ts +59 -0
- package/dist/react.js +358 -0
- package/package.json +55 -0
package/dist/react.js
ADDED
|
@@ -0,0 +1,358 @@
|
|
|
1
|
+
import { parse } from "@babel/parser";
|
|
2
|
+
import { Framework as Framework$1 } from "@codespark/framework";
|
|
3
|
+
import MagicString from "magic-string";
|
|
4
|
+
import { availablePresets, transform } from "@babel/standalone";
|
|
5
|
+
import DOMPurify from "dompurify";
|
|
6
|
+
import { marked } from "marked";
|
|
7
|
+
|
|
8
|
+
//#region src/loaders/types.ts
|
|
9
|
+
let LoaderType = /* @__PURE__ */ function(LoaderType$1) {
|
|
10
|
+
LoaderType$1["ESModule"] = "esmodule";
|
|
11
|
+
LoaderType$1["Style"] = "style";
|
|
12
|
+
LoaderType$1["Script"] = "script";
|
|
13
|
+
LoaderType$1["Asset"] = "asset";
|
|
14
|
+
return LoaderType$1;
|
|
15
|
+
}({});
|
|
16
|
+
|
|
17
|
+
//#endregion
|
|
18
|
+
//#region src/loaders/css-loader.ts
|
|
19
|
+
var CSSLoader = class {
|
|
20
|
+
name = "css-loader";
|
|
21
|
+
test = /\.css$/;
|
|
22
|
+
constructor(config) {
|
|
23
|
+
this.config = config;
|
|
24
|
+
}
|
|
25
|
+
transform(source, ctx) {
|
|
26
|
+
const { enabled = true, match } = this.config?.tailwind || {};
|
|
27
|
+
const isTailwind = match ? match.test(ctx.resourcePath) : ctx.resourcePath.endsWith(".tw.css");
|
|
28
|
+
const imports = [];
|
|
29
|
+
const content = source.replace(/@import\s+(?:url\()?['"]?([^'")]+)['"]?\)?\s*;?/g, (_, importPath) => {
|
|
30
|
+
const resolved = ctx.resolve(importPath);
|
|
31
|
+
if (resolved) imports.push(resolved);
|
|
32
|
+
return "";
|
|
33
|
+
});
|
|
34
|
+
return {
|
|
35
|
+
type: LoaderType.Style,
|
|
36
|
+
content,
|
|
37
|
+
imports,
|
|
38
|
+
attributes: enabled && isTailwind ? { type: "text/tailwindcss" } : {}
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
//#endregion
|
|
44
|
+
//#region src/loaders/es-loader.ts
|
|
45
|
+
const parseCode = (code) => parse(code, {
|
|
46
|
+
sourceType: "module",
|
|
47
|
+
plugins: ["jsx", "typescript"]
|
|
48
|
+
}).program.body;
|
|
49
|
+
const collectIdentifiers = (ast) => {
|
|
50
|
+
const ids = /* @__PURE__ */ new Set();
|
|
51
|
+
const walk = (node) => {
|
|
52
|
+
if (!node || typeof node !== "object") return;
|
|
53
|
+
if (node.type === "Identifier" || node.type === "JSXIdentifier") ids.add(node.name);
|
|
54
|
+
for (const k of Object.keys(node)) {
|
|
55
|
+
if (k === "loc" || k === "range") continue;
|
|
56
|
+
const val = node[k];
|
|
57
|
+
if (Array.isArray(val)) val.forEach(walk);
|
|
58
|
+
else if (val && typeof val === "object") walk(val);
|
|
59
|
+
}
|
|
60
|
+
};
|
|
61
|
+
ast.forEach(walk);
|
|
62
|
+
return ids;
|
|
63
|
+
};
|
|
64
|
+
const analyzeImports = (ast) => {
|
|
65
|
+
const imports = ast.filter((node) => node.type === "ImportDeclaration");
|
|
66
|
+
const usedIds = collectIdentifiers(ast.filter((node) => node.type !== "ImportDeclaration"));
|
|
67
|
+
const importMap = /* @__PURE__ */ new Map();
|
|
68
|
+
imports.forEach((imp) => {
|
|
69
|
+
if (imp.importKind === "type") return;
|
|
70
|
+
imp.specifiers.forEach((spec) => {
|
|
71
|
+
if (spec.type === "ImportSpecifier" && spec.importKind === "type") return;
|
|
72
|
+
importMap.set(spec.local.name, imp.source.value);
|
|
73
|
+
});
|
|
74
|
+
});
|
|
75
|
+
const usedSources = /* @__PURE__ */ new Set();
|
|
76
|
+
usedIds.forEach((id) => {
|
|
77
|
+
if (importMap.has(id)) usedSources.add(importMap.get(id));
|
|
78
|
+
});
|
|
79
|
+
return {
|
|
80
|
+
imports,
|
|
81
|
+
usedSources
|
|
82
|
+
};
|
|
83
|
+
};
|
|
84
|
+
const buildExternalDeps = (imports, usedSources) => {
|
|
85
|
+
const externals = /* @__PURE__ */ new Map();
|
|
86
|
+
imports.forEach((imp) => {
|
|
87
|
+
if (imp.importKind === "type") return;
|
|
88
|
+
const source = imp.source.value;
|
|
89
|
+
if (!usedSources.has(source) || source.startsWith(".") || source.startsWith("/")) return;
|
|
90
|
+
const namedImports = imp.specifiers.filter((spec) => spec.type === "ImportSpecifier" && spec.importKind !== "type").map((spec) => spec.imported.name);
|
|
91
|
+
const existing = externals.get(source);
|
|
92
|
+
if (existing) namedImports.forEach((name) => existing.imported.add(name));
|
|
93
|
+
else externals.set(source, {
|
|
94
|
+
name: source,
|
|
95
|
+
imported: new Set(namedImports)
|
|
96
|
+
});
|
|
97
|
+
});
|
|
98
|
+
return [...externals.values()].map((dep) => ({
|
|
99
|
+
...dep,
|
|
100
|
+
imported: [...dep.imported]
|
|
101
|
+
}));
|
|
102
|
+
};
|
|
103
|
+
var ESLoader = class {
|
|
104
|
+
name = "es-loader";
|
|
105
|
+
test = /\.(tsx?|jsx?)$/;
|
|
106
|
+
jsxPreset;
|
|
107
|
+
isTSX;
|
|
108
|
+
constructor(options) {
|
|
109
|
+
const { jsxPreset, isTSX = false } = options || {};
|
|
110
|
+
this.jsxPreset = jsxPreset;
|
|
111
|
+
this.isTSX = isTSX;
|
|
112
|
+
}
|
|
113
|
+
transform(source, ctx) {
|
|
114
|
+
const { imports, usedSources } = analyzeImports(parseCode(source));
|
|
115
|
+
const externals = buildExternalDeps(imports, usedSources);
|
|
116
|
+
const dependencies = {};
|
|
117
|
+
for (const imp of imports) {
|
|
118
|
+
if (imp.importKind === "type") continue;
|
|
119
|
+
const importPath = imp.source.value;
|
|
120
|
+
if (imp.specifiers.length === 0) {
|
|
121
|
+
const resolved$1 = ctx.resolve(importPath);
|
|
122
|
+
if (resolved$1) dependencies[importPath] = resolved$1;
|
|
123
|
+
continue;
|
|
124
|
+
}
|
|
125
|
+
if (!usedSources.has(importPath)) continue;
|
|
126
|
+
const resolved = ctx.resolve(importPath);
|
|
127
|
+
if (resolved) dependencies[importPath] = resolved;
|
|
128
|
+
}
|
|
129
|
+
const { typescript } = availablePresets;
|
|
130
|
+
const defaultPresets = [typescript, {
|
|
131
|
+
isTSX: this.isTSX,
|
|
132
|
+
allExtensions: true
|
|
133
|
+
}];
|
|
134
|
+
const { code } = transform(source, {
|
|
135
|
+
filename: ctx.resourcePath,
|
|
136
|
+
presets: this.jsxPreset ? [this.jsxPreset, defaultPresets] : [defaultPresets]
|
|
137
|
+
});
|
|
138
|
+
return {
|
|
139
|
+
type: LoaderType.ESModule,
|
|
140
|
+
content: code || "",
|
|
141
|
+
dependencies,
|
|
142
|
+
externals
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
};
|
|
146
|
+
|
|
147
|
+
//#endregion
|
|
148
|
+
//#region src/loaders/json-loader.ts
|
|
149
|
+
var JSONLoader = class {
|
|
150
|
+
name = "json-loader";
|
|
151
|
+
test = /\.json$/;
|
|
152
|
+
transform(source) {
|
|
153
|
+
return {
|
|
154
|
+
type: LoaderType.ESModule,
|
|
155
|
+
content: `export default ${source};`,
|
|
156
|
+
dependencies: {},
|
|
157
|
+
externals: []
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
};
|
|
161
|
+
|
|
162
|
+
//#endregion
|
|
163
|
+
//#region src/loaders/markdown-loader.ts
|
|
164
|
+
var MarkdownLoader = class {
|
|
165
|
+
name = "markdown-loader";
|
|
166
|
+
test = /\.md$/;
|
|
167
|
+
transform(source) {
|
|
168
|
+
const html = DOMPurify.sanitize(marked.parse(source, { async: false }));
|
|
169
|
+
return {
|
|
170
|
+
type: LoaderType.Asset,
|
|
171
|
+
content: html
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
};
|
|
175
|
+
|
|
176
|
+
//#endregion
|
|
177
|
+
//#region src/react/analyze.ts
|
|
178
|
+
const EXTENSIONS = [
|
|
179
|
+
".tsx",
|
|
180
|
+
".ts",
|
|
181
|
+
".jsx",
|
|
182
|
+
".js"
|
|
183
|
+
];
|
|
184
|
+
const LOADERS = [
|
|
185
|
+
new ESLoader({
|
|
186
|
+
jsxPreset: [availablePresets.react, { runtime: "automatic" }],
|
|
187
|
+
isTSX: true
|
|
188
|
+
}),
|
|
189
|
+
new CSSLoader(),
|
|
190
|
+
new JSONLoader(),
|
|
191
|
+
new MarkdownLoader()
|
|
192
|
+
];
|
|
193
|
+
function matchLoader(path) {
|
|
194
|
+
return LOADERS.find((loader) => loader.test.test(path)) ?? null;
|
|
195
|
+
}
|
|
196
|
+
function getOutputList(outputs, type) {
|
|
197
|
+
return outputs.get(type);
|
|
198
|
+
}
|
|
199
|
+
function createOutputsMap() {
|
|
200
|
+
const outputs = /* @__PURE__ */ new Map();
|
|
201
|
+
outputs.set(LoaderType.ESModule, []);
|
|
202
|
+
outputs.set(LoaderType.Style, []);
|
|
203
|
+
outputs.set(LoaderType.Script, []);
|
|
204
|
+
outputs.set(LoaderType.Asset, []);
|
|
205
|
+
return outputs;
|
|
206
|
+
}
|
|
207
|
+
function resolve(source, from, files) {
|
|
208
|
+
if (!source.startsWith(".") && !source.startsWith("/")) return null;
|
|
209
|
+
const fromDir = from.split("/").slice(0, -1);
|
|
210
|
+
for (const part of source.split("/")) if (part === "..") fromDir.pop();
|
|
211
|
+
else if (part !== ".") fromDir.push(part);
|
|
212
|
+
const resolved = fromDir.join("/") || ".";
|
|
213
|
+
if (files[resolved] !== void 0) return resolved;
|
|
214
|
+
for (const ext of EXTENSIONS) if (files[resolved + ext] !== void 0) return resolved + ext;
|
|
215
|
+
for (const ext of EXTENSIONS) {
|
|
216
|
+
const indexPath = `${resolved}/index${ext}`;
|
|
217
|
+
if (files[indexPath] !== void 0) return indexPath;
|
|
218
|
+
}
|
|
219
|
+
return null;
|
|
220
|
+
}
|
|
221
|
+
function processFile(path, files, outputs, visited) {
|
|
222
|
+
if (visited.has(path)) return;
|
|
223
|
+
visited.add(path);
|
|
224
|
+
const source = files[path];
|
|
225
|
+
if (source === void 0) return;
|
|
226
|
+
const loader = matchLoader(path);
|
|
227
|
+
if (!loader) return;
|
|
228
|
+
const output = loader.transform(source, {
|
|
229
|
+
resourcePath: path,
|
|
230
|
+
getSource: (p) => files[p],
|
|
231
|
+
resolve: (src) => resolve(src, path, files)
|
|
232
|
+
});
|
|
233
|
+
switch (output.type) {
|
|
234
|
+
case LoaderType.ESModule: {
|
|
235
|
+
const { content, dependencies, externals } = output;
|
|
236
|
+
getOutputList(outputs, LoaderType.ESModule).push({
|
|
237
|
+
path,
|
|
238
|
+
content,
|
|
239
|
+
dependencies,
|
|
240
|
+
externals
|
|
241
|
+
});
|
|
242
|
+
for (const depPath of Object.values(dependencies)) processFile(depPath, files, outputs, visited);
|
|
243
|
+
break;
|
|
244
|
+
}
|
|
245
|
+
case LoaderType.Style: {
|
|
246
|
+
const { content, imports, attributes } = output;
|
|
247
|
+
getOutputList(outputs, LoaderType.Style).push({
|
|
248
|
+
path,
|
|
249
|
+
content,
|
|
250
|
+
imports,
|
|
251
|
+
attributes
|
|
252
|
+
});
|
|
253
|
+
for (const depPath of imports) processFile(depPath, files, outputs, visited);
|
|
254
|
+
break;
|
|
255
|
+
}
|
|
256
|
+
default: {
|
|
257
|
+
const { content } = output;
|
|
258
|
+
getOutputList(outputs, LoaderType.ESModule).push({
|
|
259
|
+
path,
|
|
260
|
+
content: `import { jsx as _jsx } from 'react/jsx-runtime';
|
|
261
|
+
export default function MarkdownContent() {
|
|
262
|
+
return _jsx('div', { dangerouslySetInnerHTML: { __html: ${JSON.stringify(content)} } });
|
|
263
|
+
}`,
|
|
264
|
+
dependencies: {},
|
|
265
|
+
externals: []
|
|
266
|
+
});
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
function analyze(entry, files) {
|
|
271
|
+
const outputs = createOutputsMap();
|
|
272
|
+
processFile(entry, files, outputs, /* @__PURE__ */ new Set());
|
|
273
|
+
return outputs;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
//#endregion
|
|
277
|
+
//#region src/react/index.ts
|
|
278
|
+
var Framework = class extends Framework$1 {
|
|
279
|
+
name = "react";
|
|
280
|
+
imports = {
|
|
281
|
+
react: "https://esm.sh/react@18.2.0",
|
|
282
|
+
"react/jsx-runtime": "https://esm.sh/react@18.2.0/jsx-runtime",
|
|
283
|
+
"react-dom/client": "https://esm.sh/react-dom@18.2.0/client"
|
|
284
|
+
};
|
|
285
|
+
outputs = /* @__PURE__ */ new Map();
|
|
286
|
+
blobUrlMap = /* @__PURE__ */ new Map();
|
|
287
|
+
analyze(entry, files) {
|
|
288
|
+
this.outputs = analyze(entry, files);
|
|
289
|
+
}
|
|
290
|
+
compile() {
|
|
291
|
+
const transformed = this.transformModulesToBlob([...this.getOutput(LoaderType.ESModule)].reverse());
|
|
292
|
+
const builder = this.createBuilder(transformed);
|
|
293
|
+
const ast = parse(transformed, {
|
|
294
|
+
sourceType: "module",
|
|
295
|
+
plugins: ["jsx", "typescript"]
|
|
296
|
+
}).program.body;
|
|
297
|
+
let name;
|
|
298
|
+
for (const node of ast) if (node.type === "ExportNamedDeclaration" && node.declaration?.type === "FunctionDeclaration") name = node.declaration?.id?.name;
|
|
299
|
+
else if (node.type === "ExportDefaultDeclaration") {
|
|
300
|
+
const declaration = node.declaration;
|
|
301
|
+
switch (declaration.type) {
|
|
302
|
+
case "Identifier":
|
|
303
|
+
name = declaration.name;
|
|
304
|
+
break;
|
|
305
|
+
case "ArrowFunctionExpression":
|
|
306
|
+
if (declaration.async) throw new Error("Export an async function");
|
|
307
|
+
name = "App";
|
|
308
|
+
builder.update(declaration.start, declaration.body.start - 1, "function App() ");
|
|
309
|
+
break;
|
|
310
|
+
case "FunctionDeclaration":
|
|
311
|
+
if (declaration.async) throw new Error("Export an async function");
|
|
312
|
+
if (declaration.id) name = declaration.id.name;
|
|
313
|
+
else {
|
|
314
|
+
name = "App";
|
|
315
|
+
builder.update(declaration.start, declaration.body.start - 1, "function App() ");
|
|
316
|
+
}
|
|
317
|
+
break;
|
|
318
|
+
default: throw new Error(`Default export type is invalid: expect a FunctionExpression but got ${declaration.type}`);
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
builder.async(`const [{ createRoot }, { jsx }] = await Promise.all([import('react-dom/client'), import('react/jsx-runtime')]);
|
|
322
|
+
window.__root__ = window.__root__ || createRoot(${builder.root});
|
|
323
|
+
window.__root__.render(${name ? `jsx(${name}, {})` : "null"});`);
|
|
324
|
+
return builder.toString();
|
|
325
|
+
}
|
|
326
|
+
transformModulesToBlob(modules) {
|
|
327
|
+
let entryCode = "";
|
|
328
|
+
modules.forEach((mod, index) => {
|
|
329
|
+
const code = this.transformCodeWithBlobUrls(mod);
|
|
330
|
+
if (index === modules.length - 1) entryCode = code;
|
|
331
|
+
else {
|
|
332
|
+
const blob = new Blob([code], { type: "application/javascript" });
|
|
333
|
+
this.blobUrlMap.set(mod.path, URL.createObjectURL(blob));
|
|
334
|
+
}
|
|
335
|
+
});
|
|
336
|
+
return entryCode;
|
|
337
|
+
}
|
|
338
|
+
transformCodeWithBlobUrls(mod) {
|
|
339
|
+
const s = new MagicString(mod.content);
|
|
340
|
+
const ast = parse(mod.content, {
|
|
341
|
+
sourceType: "module",
|
|
342
|
+
plugins: ["jsx", "typescript"]
|
|
343
|
+
}).program.body;
|
|
344
|
+
for (const node of ast) {
|
|
345
|
+
if (node.type !== "ImportDeclaration") continue;
|
|
346
|
+
const resolved = mod.dependencies[node.source.value];
|
|
347
|
+
if (!resolved) continue;
|
|
348
|
+
const blobUrl = this.blobUrlMap.get(resolved);
|
|
349
|
+
if (blobUrl) s.update(node.source.start + 1, node.source.end - 1, blobUrl);
|
|
350
|
+
else s.remove(node.start, node.end);
|
|
351
|
+
}
|
|
352
|
+
return s.toString();
|
|
353
|
+
}
|
|
354
|
+
};
|
|
355
|
+
const react = new Framework();
|
|
356
|
+
|
|
357
|
+
//#endregion
|
|
358
|
+
export { Framework, react };
|
package/package.json
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@codespark/framework",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"description": "Framework registration API for codespark ecosystem",
|
|
6
|
+
"keywords": [
|
|
7
|
+
"framework-system",
|
|
8
|
+
"react",
|
|
9
|
+
"markdown",
|
|
10
|
+
"html",
|
|
11
|
+
"codespark"
|
|
12
|
+
],
|
|
13
|
+
"exports": {
|
|
14
|
+
".": {
|
|
15
|
+
"import": "./dist/index.js",
|
|
16
|
+
"types": "./dist/index.d.ts"
|
|
17
|
+
},
|
|
18
|
+
"./*": {
|
|
19
|
+
"import": "./dist/*.js",
|
|
20
|
+
"types": "./dist/*.d.ts"
|
|
21
|
+
}
|
|
22
|
+
},
|
|
23
|
+
"files": [
|
|
24
|
+
"dist"
|
|
25
|
+
],
|
|
26
|
+
"scripts": {
|
|
27
|
+
"build": "tsdown",
|
|
28
|
+
"test": "vitest run"
|
|
29
|
+
},
|
|
30
|
+
"author": "TonyL1u",
|
|
31
|
+
"license": "MIT",
|
|
32
|
+
"repository": {
|
|
33
|
+
"type": "git",
|
|
34
|
+
"url": "git+https://github.com/codesparkjs/codespark.git",
|
|
35
|
+
"directory": "packages/framework"
|
|
36
|
+
},
|
|
37
|
+
"homepage": "https://codesparkjs.com",
|
|
38
|
+
"dependencies": {
|
|
39
|
+
"@babel/parser": "^7.28.5",
|
|
40
|
+
"@babel/standalone": "^7.28.5",
|
|
41
|
+
"@mdx-js/mdx": "^3.1.1",
|
|
42
|
+
"dom-serializer": "^2.0.0",
|
|
43
|
+
"dompurify": "^3.3.1",
|
|
44
|
+
"htmlparser2": "^10.1.0",
|
|
45
|
+
"magic-string": "^0.30.21",
|
|
46
|
+
"marked": "^17.0.1"
|
|
47
|
+
},
|
|
48
|
+
"devDependencies": {
|
|
49
|
+
"@babel/types": "^7.28.5",
|
|
50
|
+
"@types/babel__standalone": "^7.1.9",
|
|
51
|
+
"@types/node": "^22.19.2",
|
|
52
|
+
"domhandler": "^5.0.3",
|
|
53
|
+
"tsdown": "^0.17.3"
|
|
54
|
+
}
|
|
55
|
+
}
|