@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/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
+ }