mirror of
https://github.com/lainbo/component-party.git
synced 2026-04-05 13:09:03 +08:00
refactor(playground): simplify playground url generator code
This commit is contained in:
@@ -635,9 +635,8 @@ We believe that deep understanding should precede optimization, enabling learner
|
||||
4. In `frameworks.mjs`, add a new entry with SVG link and ESLint configuration
|
||||
5. If the framework needs a language syntax highlight, add it to the call to `getHighlighter`’s `langs` argument in `build/lib/generateContent.js`
|
||||
6. To make a playground link:
|
||||
1. Add a `create${FRAMEWORK}Playground.js` file in `build/lib/playground`.
|
||||
2. That file should export a function that returns an object with a `fromContentByFilename` method that accepts an object of filepath keys and file content values, then returns an absolute URL to a framework’s online REPL with those files loaded.
|
||||
3. Register its export in `build/lib/playground/index.js`
|
||||
1. In file `build/lib/playgroundUrlByFramework.js`, add your framework id.
|
||||
2. The method accepts an object of filepath keys and file content values, then returns a playground URL to the framework’s online REPL with those files loaded.
|
||||
|
||||
## 🧑💻 Contributors
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ import { packageDirectory } from "pkg-dir";
|
||||
import path from "node:path";
|
||||
import kebabCase from "lodash.kebabcase";
|
||||
import FRAMEWORKS from "../../frameworks.mjs";
|
||||
import frameworkPlayground from "./playground/index.js";
|
||||
import playgroundUrlByFramework from "./playgroundUrlByFramework.js";
|
||||
import prettier from "prettier";
|
||||
import {
|
||||
highlightAngularComponent,
|
||||
@@ -207,7 +207,7 @@ async function writeJsFile(filepath, jsCode) {
|
||||
}
|
||||
|
||||
async function generatePlaygroundURL(frameworkId, files, title) {
|
||||
const frameworkIdPlayground = frameworkPlayground[frameworkId];
|
||||
const frameworkIdPlayground = playgroundUrlByFramework[frameworkId];
|
||||
if (!frameworkIdPlayground) {
|
||||
return;
|
||||
}
|
||||
@@ -221,10 +221,7 @@ async function generatePlaygroundURL(frameworkId, files, title) {
|
||||
return acc;
|
||||
}, {});
|
||||
|
||||
const playgroundURL = await frameworkIdPlayground.fromContentByFilename(
|
||||
contentByFilename,
|
||||
title
|
||||
);
|
||||
const playgroundURL = await frameworkIdPlayground(contentByFilename, title);
|
||||
|
||||
return playgroundURL;
|
||||
}
|
||||
|
||||
@@ -1,38 +0,0 @@
|
||||
import { getParameters } from "codesandbox/lib/api/define.js";
|
||||
|
||||
export default function createAlpineREPL() {
|
||||
const BASE_URL =
|
||||
"https://codesandbox.io/api/v1/sandboxes/define?embed=1¶meters=";
|
||||
const BASE_PREFIX = `<!DOCTYPE html>\n<html lang="en">\n <head>\n <meta charset="UTF-8" />\n <meta name="viewport" content="width=device-width, initial-scale=1.0" />\n <meta http-equiv="X-UA-Compatible" content="ie=edge" />\n <title>Alpine.js Playground</title>\n <script defer src="https://unpkg.com/alpinejs@3.x.x/dist/cdn.min.js"></script>\n </head>\n <body>\n\n`;
|
||||
const BASE_SUFFIX = `\n </body>\n</html>`;
|
||||
|
||||
function generateURLFromData(parameters) {
|
||||
return `${BASE_URL}${parameters}`;
|
||||
}
|
||||
|
||||
function fromContentByFilename(contentByFilename) {
|
||||
const parameters = getParameters({
|
||||
files: {
|
||||
...contentByFilename,
|
||||
"package.json": {
|
||||
content: { dependencies: {} },
|
||||
},
|
||||
"index.html": {
|
||||
content:
|
||||
BASE_PREFIX +
|
||||
(contentByFilename["index.html"]?.content || "") +
|
||||
BASE_SUFFIX,
|
||||
},
|
||||
"sandbox.config.json": {
|
||||
content: '{\n "template": "static"\n}',
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
return generateURLFromData(parameters);
|
||||
}
|
||||
|
||||
return {
|
||||
fromContentByFilename,
|
||||
};
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
import nodePath from "node:path";
|
||||
import { compressToURL } from "@matschik/lz-string";
|
||||
|
||||
const BASE = "https://markojs.com/playground/#";
|
||||
|
||||
export default function createMarkoPlayground() {
|
||||
return {
|
||||
fromContentByFilename(contentByFilename) {
|
||||
const data = Object.entries(contentByFilename).map(([path, content]) => ({
|
||||
name: nodePath.parse(path).base,
|
||||
path: `/components/${path}`,
|
||||
content,
|
||||
}));
|
||||
|
||||
return BASE + compressToURL(JSON.stringify(data));
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -1,40 +0,0 @@
|
||||
import nodePath from "node:path";
|
||||
import { compressToURL } from "@matschik/lz-string";
|
||||
|
||||
export default () => {
|
||||
const BASE_URL = "https://playground.solidjs.com/#";
|
||||
const SOURCE_PREFIX = `import { render } from "solid-js/web";\n`;
|
||||
const getSourceSuffix = (componentName) =>
|
||||
`\n\nrender(() => <${componentName} />, document.getElementById("app"));\n`;
|
||||
|
||||
function generateURLFromData(data) {
|
||||
return `${BASE_URL}${compressToURL(JSON.stringify(data))}`;
|
||||
}
|
||||
|
||||
function fromContentByFilename(contentByFilename) {
|
||||
const data = Object.keys(contentByFilename).map((filename) => {
|
||||
const content = contentByFilename[filename];
|
||||
const parsedFilename = nodePath.parse(filename);
|
||||
const ext = parsedFilename.ext.split(".").pop();
|
||||
|
||||
return {
|
||||
name: parsedFilename.name,
|
||||
type: ext === "jsx" ? "tsx" : ext,
|
||||
source: content.replaceAll(".jsx", ".tsx"),
|
||||
};
|
||||
});
|
||||
|
||||
const mainFile = data[0];
|
||||
const mainComponentName = mainFile.name;
|
||||
mainFile.name = "main";
|
||||
mainFile.type = "tsx";
|
||||
mainFile.source =
|
||||
SOURCE_PREFIX + mainFile.source + getSourceSuffix(mainComponentName);
|
||||
|
||||
return generateURLFromData(data);
|
||||
}
|
||||
|
||||
return {
|
||||
fromContentByFilename,
|
||||
};
|
||||
};
|
||||
@@ -1,57 +0,0 @@
|
||||
import path from "node:path";
|
||||
|
||||
export default function createSvelte5Playground() {
|
||||
const BASE_URL = "https://svelte.dev/playground/untitled?version=5#";
|
||||
|
||||
async function fromContentByFilename(contentByFilename, title) {
|
||||
const filenames = Object.keys(contentByFilename);
|
||||
if (filenames.some((f) => f.includes(".html"))) {
|
||||
return;
|
||||
}
|
||||
|
||||
const files = filenames.map((filename, index) => {
|
||||
const contents = contentByFilename[filename];
|
||||
const name = index === 0 ? "App.svelte" : path.parse(filename).base;
|
||||
return {
|
||||
type: "file",
|
||||
name,
|
||||
basename: name,
|
||||
contents,
|
||||
text: true,
|
||||
};
|
||||
});
|
||||
|
||||
const payload = { title, files };
|
||||
|
||||
const hash = await compress_and_encode_text(JSON.stringify(payload));
|
||||
|
||||
const url = `${BASE_URL}${hash}`;
|
||||
|
||||
return url;
|
||||
}
|
||||
|
||||
return {
|
||||
fromContentByFilename,
|
||||
};
|
||||
}
|
||||
|
||||
// method `compress_and_encode_text` from https://github.com/sveltejs/svelte.dev/blob/main/apps/svelte.dev/src/routes/(authed)/playground/%5Bid%5D/gzip.js
|
||||
export async function compress_and_encode_text(input) {
|
||||
const reader = new Blob([input])
|
||||
.stream()
|
||||
.pipeThrough(new CompressionStream("gzip"))
|
||||
.getReader();
|
||||
let buffer = "";
|
||||
for (;;) {
|
||||
const { done, value } = await reader.read();
|
||||
if (done) {
|
||||
reader.releaseLock();
|
||||
return btoa(buffer).replaceAll("+", "-").replaceAll("/", "_");
|
||||
} else {
|
||||
for (let i = 0; i < value.length; i++) {
|
||||
// decoding as utf-8 will make btoa reject the string
|
||||
buffer += String.fromCharCode(value[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,32 +0,0 @@
|
||||
import nodePath from "node:path";
|
||||
|
||||
export default function createSvelteREPL() {
|
||||
const BASE_URL = "https://svelte-repl.vercel.app/#";
|
||||
|
||||
function utoa(data) {
|
||||
return btoa(unescape(encodeURIComponent(data)));
|
||||
}
|
||||
|
||||
function generateURLFromData(data) {
|
||||
return `${BASE_URL}${utoa(JSON.stringify(data))}`;
|
||||
}
|
||||
|
||||
function fromContentByFilename(contentByFilename) {
|
||||
const data = Object.keys(contentByFilename).map((filename) => {
|
||||
const content = contentByFilename[filename];
|
||||
const parsedFilename = nodePath.parse(filename);
|
||||
return {
|
||||
name: parsedFilename.name,
|
||||
type: parsedFilename.ext.split(".").pop(),
|
||||
source: content,
|
||||
};
|
||||
});
|
||||
|
||||
const url = generateURLFromData(data);
|
||||
return url;
|
||||
}
|
||||
|
||||
return {
|
||||
fromContentByFilename,
|
||||
};
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
export default function createVue3REPL() {
|
||||
const BASE_URL = "https://sfc.vuejs.org/#";
|
||||
|
||||
function utoa(data) {
|
||||
return btoa(unescape(encodeURIComponent(data)));
|
||||
}
|
||||
|
||||
function generateURLFromData(data) {
|
||||
return `${BASE_URL}${utoa(JSON.stringify(data))}`;
|
||||
}
|
||||
|
||||
function fromContentByFilename(contentByFilename) {
|
||||
const data = Object.assign({}, contentByFilename, {
|
||||
"import-map.json": JSON.stringify({
|
||||
vue: "https://sfc.vuejs.org/vue.runtime.esm-browser.js",
|
||||
}),
|
||||
});
|
||||
const url = generateURLFromData(data);
|
||||
return url;
|
||||
}
|
||||
|
||||
return {
|
||||
fromContentByFilename,
|
||||
};
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
import createAlpinePlayground from "./createAlpinePlayground.js";
|
||||
import createSvelte5Playground from "./createSvelte5Playground.js";
|
||||
import createVue3Playground from "./createVue3Playground.js";
|
||||
import createSolidPlayground from "./createSolidPlayground.js";
|
||||
import createMarkoPlayground from "./createMarkoPlayground.js";
|
||||
|
||||
export default {
|
||||
vue3: createVue3Playground(),
|
||||
svelte5: createSvelte5Playground(),
|
||||
alpine: createAlpinePlayground(),
|
||||
solid: createSolidPlayground(),
|
||||
marko: createMarkoPlayground(),
|
||||
};
|
||||
141
build/lib/playgroundUrlByFramework.js
Normal file
141
build/lib/playgroundUrlByFramework.js
Normal file
@@ -0,0 +1,141 @@
|
||||
import nodePath from "node:path";
|
||||
import { compressToURL } from "@matschik/lz-string";
|
||||
import { getParameters } from "codesandbox/lib/api/define.js";
|
||||
|
||||
export default {
|
||||
vue3: (contentByFilename) => {
|
||||
const BASE_URL = "https://sfc.vuejs.org/#";
|
||||
|
||||
function utoa(data) {
|
||||
return btoa(unescape(encodeURIComponent(data)));
|
||||
}
|
||||
|
||||
function generateURLFromData(data) {
|
||||
return `${BASE_URL}${utoa(JSON.stringify(data))}`;
|
||||
}
|
||||
const data = Object.assign({}, contentByFilename, {
|
||||
"import-map.json": JSON.stringify({
|
||||
vue: "https://sfc.vuejs.org/vue.runtime.esm-browser.js",
|
||||
}),
|
||||
});
|
||||
const url = generateURLFromData(data);
|
||||
return url;
|
||||
},
|
||||
svelte5: async (contentByFilename, title) => {
|
||||
const BASE_URL = "https://svelte.dev/playground/untitled?version=5#";
|
||||
|
||||
const filenames = Object.keys(contentByFilename);
|
||||
if (filenames.some((f) => f.includes(".html"))) {
|
||||
return;
|
||||
}
|
||||
|
||||
const files = filenames.map((filename, index) => {
|
||||
const contents = contentByFilename[filename];
|
||||
const name = index === 0 ? "App.svelte" : nodePath.parse(filename).base;
|
||||
return {
|
||||
type: "file",
|
||||
name,
|
||||
basename: name,
|
||||
contents,
|
||||
text: true,
|
||||
};
|
||||
});
|
||||
|
||||
const payload = { title, files };
|
||||
|
||||
const hash = await compress_and_encode_text(JSON.stringify(payload));
|
||||
|
||||
const url = `${BASE_URL}${hash}`;
|
||||
|
||||
return url;
|
||||
},
|
||||
alpine: (contentByFilename) => {
|
||||
const BASE_URL =
|
||||
"https://codesandbox.io/api/v1/sandboxes/define?embed=1¶meters=";
|
||||
const BASE_PREFIX = `<!DOCTYPE html>\n<html lang="en">\n <head>\n <meta charset="UTF-8" />\n <meta name="viewport" content="width=device-width, initial-scale=1.0" />\n <meta http-equiv="X-UA-Compatible" content="ie=edge" />\n <title>Alpine.js Playground</title>\n <script defer src="https://unpkg.com/alpinejs@3.x.x/dist/cdn.min.js"></script>\n </head>\n <body>\n\n`;
|
||||
const BASE_SUFFIX = `\n </body>\n</html>`;
|
||||
|
||||
const parameters = getParameters({
|
||||
files: {
|
||||
...contentByFilename,
|
||||
"package.json": {
|
||||
content: { dependencies: {} },
|
||||
},
|
||||
"index.html": {
|
||||
content:
|
||||
BASE_PREFIX +
|
||||
(contentByFilename["index.html"]?.content || "") +
|
||||
BASE_SUFFIX,
|
||||
},
|
||||
"sandbox.config.json": {
|
||||
content: '{\n "template": "static"\n}',
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
return `${BASE_URL}${parameters}`;
|
||||
},
|
||||
solid: (contentByFilename) => {
|
||||
const BASE_URL = "https://playground.solidjs.com/#";
|
||||
const SOURCE_PREFIX = `import { render } from "solid-js/web";\n`;
|
||||
const getSourceSuffix = (componentName) =>
|
||||
`\n\nrender(() => <${componentName} />, document.getElementById("app"));\n`;
|
||||
|
||||
function generateURLFromData(data) {
|
||||
return `${BASE_URL}${compressToURL(JSON.stringify(data))}`;
|
||||
}
|
||||
|
||||
const data = Object.keys(contentByFilename).map((filename) => {
|
||||
const content = contentByFilename[filename];
|
||||
const parsedFilename = nodePath.parse(filename);
|
||||
const ext = parsedFilename.ext.split(".").pop();
|
||||
|
||||
return {
|
||||
name: parsedFilename.name,
|
||||
type: ext === "jsx" ? "tsx" : ext,
|
||||
source: content.replaceAll(".jsx", ".tsx"),
|
||||
};
|
||||
});
|
||||
|
||||
const mainFile = data[0];
|
||||
const mainComponentName = mainFile.name;
|
||||
mainFile.name = "main";
|
||||
mainFile.type = "tsx";
|
||||
mainFile.source =
|
||||
SOURCE_PREFIX + mainFile.source + getSourceSuffix(mainComponentName);
|
||||
|
||||
return generateURLFromData(data);
|
||||
},
|
||||
marko: (contentByFilename) => {
|
||||
const BASE_URL = "https://markojs.com/playground/#";
|
||||
|
||||
const data = Object.entries(contentByFilename).map(([path, content]) => ({
|
||||
name: nodePath.parse(path).base,
|
||||
path: `/components/${path}`,
|
||||
content,
|
||||
}));
|
||||
|
||||
return BASE_URL + compressToURL(JSON.stringify(data));
|
||||
},
|
||||
};
|
||||
|
||||
// method `compress_and_encode_text` from https://github.com/sveltejs/svelte.dev/blob/main/apps/svelte.dev/src/routes/(authed)/playground/%5Bid%5D/gzip.js
|
||||
async function compress_and_encode_text(input) {
|
||||
const reader = new Blob([input])
|
||||
.stream()
|
||||
.pipeThrough(new CompressionStream("gzip"))
|
||||
.getReader();
|
||||
let buffer = "";
|
||||
for (;;) {
|
||||
const { done, value } = await reader.read();
|
||||
if (done) {
|
||||
reader.releaseLock();
|
||||
return btoa(buffer).replaceAll("+", "-").replaceAll("/", "_");
|
||||
} else {
|
||||
for (let i = 0; i < value.length; i++) {
|
||||
// decoding as utf-8 will make btoa reject the string
|
||||
buffer += String.fromCharCode(value[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user