From 4a2820597c6da9e482d49f42b437b4462483c9c5 Mon Sep 17 00:00:00 2001 From: SrGooglo Date: Wed, 24 May 2023 17:43:01 +0000 Subject: [PATCH] split functions --- .../widgets/routes/get/:widgetId.js | 178 ++---------------- .../widgets/routes/get/:widgetId/debug.jsx | 54 ++++++ .../src/utils/compileWidgetCode/index.js | 78 ++++++++ .../src/utils/getWidgetCode/index.js | 86 +++++++++ 4 files changed, 233 insertions(+), 163 deletions(-) create mode 100644 packages/marketplace_server/src/controllers/widgets/routes/get/:widgetId/debug.jsx create mode 100644 packages/marketplace_server/src/utils/compileWidgetCode/index.js create mode 100644 packages/marketplace_server/src/utils/getWidgetCode/index.js diff --git a/packages/marketplace_server/src/controllers/widgets/routes/get/:widgetId.js b/packages/marketplace_server/src/controllers/widgets/routes/get/:widgetId.js index ea36d445..e7ff35dc 100644 --- a/packages/marketplace_server/src/controllers/widgets/routes/get/:widgetId.js +++ b/packages/marketplace_server/src/controllers/widgets/routes/get/:widgetId.js @@ -1,173 +1,25 @@ -import { Widget } from "@models" - -import { transform } from "sucrase" -import UglifyJS from "uglify-js" - -import path from "path" -import resolveUrl from "@utils/resolveUrl" -import replaceImportsWithRemoteURL from "@utils/replaceImportsWithRemoteURL" - -async function compileWidgetCode(widgetCode, manifest, rootURL) { - if (!widgetCode) { - throw new Error("Widget code not defined.") - } - - if (!manifest) { - throw new Error("Manifest not defined.") - } - - if (!rootURL) { - throw new Error("Root URL not defined.") - } - - let renderComponentName = null - let cssFiles = [] - - // inject react with cdn - widgetCode = `import React from "https://cdn.skypack.dev/react@17?dts" \n${widgetCode}` - - widgetCode = await replaceImportsWithRemoteURL(widgetCode, resolveUrl(rootURL, manifest.main)) - - // remove css imports and append to manifest (Only its used in the entry file) - widgetCode = widgetCode.replace(/import\s+["'](.*)\.css["']/g, (match, p1) => { - cssFiles.push(resolveUrl(rootURL, `${p1}.css`)) - - return "" - }) - - // transform jsx to js - widgetCode = await transform(widgetCode, { - transforms: ["jsx"], - //jsxRuntime: "automatic", - //production: true, - }).code - - // search export default and get the name of the function/const/class - const exportDefaultRegex = /export\s+default\s+(?:function|const|class)\s+([a-zA-Z0-9]+)/g - - const exportDefaultMatch = exportDefaultRegex.exec(widgetCode) - - if (exportDefaultMatch) { - renderComponentName = exportDefaultMatch[1] - } - - // remove export default keywords - widgetCode = widgetCode.replace("export default", "") - - let manifestProcessed = { - ...manifest, - } - - manifestProcessed.cssFiles = cssFiles - manifestProcessed.main = resolveUrl(rootURL, manifest.main) - manifestProcessed.icon = resolveUrl(rootURL, manifest.icon) - - let result = ` - ${widgetCode} - - export default { - manifest: ${JSON.stringify(manifestProcessed)}, - renderComponent: ${renderComponentName}, - } - ` - - // minify code - result = UglifyJS.minify(result, { - compress: true, - mangle: true, - }).code - - return result -} +import getWidgetCode from "@utils/getWidgetCode" export default async (req, res) => { const widgetId = req.params.widgetId const useCache = req.query["use-cache"] ? toBoolean(req.query["use-cache"]) : true - // try to find Widget - const widget = await Widget.findOne({ - _id: widgetId, - }).catch(() => { - return null + const origin = `${toBoolean(process.env.FORCE_CODE_SSL) ? "https" : req.protocol}://${req.get("host")}` + + let widgetCode = await getWidgetCode(widgetId, { + useCache, + origin, + }).catch((error) => { + res.status(500).json({ + error: error.message, + }) + + return false }) - if (!widget) { - return res.status(404).json({ - error: "Widget not found.", - }) + if (widgetCode) { + res.setHeader("Content-Type", "application/javascript") + return res.status(200).send(widgetCode) } - - if (!widget.manifest.main) { - return res.status(404).json({ - error: "Widget entry file not defined", - }) - } - - const requestedVersion = widgetId.split("@")[1] ?? widget.manifest.version - - let widgetCode = null - - // get origin from request url - const origin = `${toBoolean(process.env.FORCE_CODE_SSL) ? "https" : req.protocol}://${req.get("host")}/static/widgets/${widgetId}@${requestedVersion}/` - const remotePath = `/widgets/${widgetId}@${requestedVersion}/` - - const remoteEntyFilePath = path.join(remotePath, widget.manifest.main) - - if (useCache) { - widgetCode = await global.redis.get(`${origin}:widget:${widgetId}@${requestedVersion}}`) - } - - if (!widgetCode) { - try { - widgetCode = await new Promise(async (resolve, reject) => { - await global.storage.getObject(process.env.S3_BUCKET, remoteEntyFilePath, (error, dataStream) => { - if (error) { - return reject(error) - } - - let data = "" - - dataStream.on("data", (chunk) => { - data += chunk - }) - - dataStream.on("end", () => { - resolve(data) - }) - - dataStream.on("error", (error) => { - reject(error) - }) - }) - }) - } catch (error) { - return res.status(500).json({ - error: `Unable to get widget code from storage. ${error.message}`, - requestedFile: remoteEntyFilePath - }) - } - - try { - console.log(`🔌 [widget:${widgetId}] Compiling widget code...`) - - widgetCode = await compileWidgetCode(widgetCode, widget.manifest, origin) - - await global.redis.set(`${origin}:widget:${widgetId}@${requestedVersion}}`, widgetCode) - } catch (error) { - return res.status(500).json({ - message: "Unable to transform widget code.", - error: error.message, - }) - } - - if (!widgetCode) { - return res.status(404).json({ - error: "Widget not found.", - }) - } - } - - res.setHeader("Content-Type", "application/javascript") - return res.status(200).send(widgetCode) } \ No newline at end of file diff --git a/packages/marketplace_server/src/controllers/widgets/routes/get/:widgetId/debug.jsx b/packages/marketplace_server/src/controllers/widgets/routes/get/:widgetId/debug.jsx new file mode 100644 index 00000000..19ff6055 --- /dev/null +++ b/packages/marketplace_server/src/controllers/widgets/routes/get/:widgetId/debug.jsx @@ -0,0 +1,54 @@ +import { Widget } from "@models" +import getWidgetCode from "@utils/getWidgetCode" + +export default async (req, res) => { + const widget_id = req.params.widgetId + + const widget = await Widget.findOne({ + _id: widget_id, + }).catch(() => { + return false + }) + + if (!widget) { + return res.status(404).json({ + error: "Widget not found", + }) + } + + const widgetCode = await getWidgetCode(widget_id, { + useCache: false, + origin: `${toBoolean(process.env.FORCE_CODE_SSL) ? "https" : req.protocol}://${req.get("host")}`, + }).catch((error) => { + res.status(500).json({ + error: error.message, + }) + return false + }) + + // create a development web preview using react app + + return res.status(200).send(` + + + + Widget Preview + + + + + + + + + +

Widget Preview

+
+ + + `) +} \ No newline at end of file diff --git a/packages/marketplace_server/src/utils/compileWidgetCode/index.js b/packages/marketplace_server/src/utils/compileWidgetCode/index.js new file mode 100644 index 00000000..17cea8ed --- /dev/null +++ b/packages/marketplace_server/src/utils/compileWidgetCode/index.js @@ -0,0 +1,78 @@ +import { transform } from "sucrase" +import UglifyJS from "uglify-js" + +import resolveUrl from "@utils/resolveUrl" +import replaceImportsWithRemoteURL from "@utils/replaceImportsWithRemoteURL" + +export default async (widgetCode, manifest, rootURL) => { + if (!widgetCode) { + throw new Error("Widget code not defined.") + } + + if (!manifest) { + throw new Error("Manifest not defined.") + } + + if (!rootURL) { + throw new Error("Root URL not defined.") + } + + let renderComponentName = null + let cssFiles = [] + + // inject react with cdn + widgetCode = `import React from "https://cdn.skypack.dev/react@17?dts" \n${widgetCode}` + + widgetCode = await replaceImportsWithRemoteURL(widgetCode, resolveUrl(rootURL, manifest.main)) + + // remove css imports and append to manifest (Only its used in the entry file) + widgetCode = widgetCode.replace(/import\s+["'](.*)\.css["']/g, (match, p1) => { + cssFiles.push(resolveUrl(rootURL, `${p1}.css`)) + + return "" + }) + + // transform jsx to js + widgetCode = await transform(widgetCode, { + transforms: ["jsx"], + //jsxRuntime: "automatic", + //production: true, + }).code + + // search export default and get the name of the function/const/class + const exportDefaultRegex = /export\s+default\s+(?:function|const|class)\s+([a-zA-Z0-9]+)/g + + const exportDefaultMatch = exportDefaultRegex.exec(widgetCode) + + if (exportDefaultMatch) { + renderComponentName = exportDefaultMatch[1] + } + + // remove export default keywords + widgetCode = widgetCode.replace("export default", "") + + let manifestProcessed = { + ...manifest, + } + + manifestProcessed.cssFiles = cssFiles + manifestProcessed.main = resolveUrl(rootURL, manifest.main) + manifestProcessed.icon = resolveUrl(rootURL, manifest.icon) + + let result = ` + ${widgetCode} + + export default { + manifest: ${JSON.stringify(manifestProcessed)}, + renderComponent: ${renderComponentName}, + } + ` + + // minify code + result = UglifyJS.minify(result, { + compress: true, + mangle: true, + }).code + + return result +} \ No newline at end of file diff --git a/packages/marketplace_server/src/utils/getWidgetCode/index.js b/packages/marketplace_server/src/utils/getWidgetCode/index.js new file mode 100644 index 00000000..902b6e6b --- /dev/null +++ b/packages/marketplace_server/src/utils/getWidgetCode/index.js @@ -0,0 +1,86 @@ +import { Widget } from "@models" +import compileWidgetCode from "@utils/compileWidgetCode" +import path from "path" + +export default async ( + widgetId, + { + useCache = true, + origin = null, + } +) => { + if (!widgetId) { + throw new Error("Widget ID not defined.") + } + if (!origin) { + throw new Error("Origin not defined.") + } + + // try to find Widget + const widget = await Widget.findOne({ + _id: widgetId, + }).catch(() => { + return null + }) + + if (!widget) { + throw new Error("Widget not found.") + } + + if (!widget.manifest.main) { + throw new Error("Widget entry file not defined") + } + + const requestedVersion = widgetId.split("@")[1] ?? widget.manifest.version + + let widgetCode = null + + const finalOrigin = `${origin}/static/widgets/${widgetId}@${requestedVersion}/` + const remotePath = `/widgets/${widgetId}@${requestedVersion}/` + + const remoteEntyFilePath = path.join(remotePath, widget.manifest.main) + + if (useCache) { + widgetCode = await global.redis.get(`${origin}:widget:${widgetId}@${requestedVersion}}`) + } + + if (!widgetCode) { + try { + widgetCode = await new Promise(async (resolve, reject) => { + await global.storage.getObject(process.env.S3_BUCKET, remoteEntyFilePath, (error, dataStream) => { + if (error) { + return reject(error) + } + + let data = "" + + dataStream.on("data", (chunk) => { + data += chunk + }) + + dataStream.on("end", () => { + resolve(data) + }) + + dataStream.on("error", (error) => { + reject(error) + }) + }) + }) + } catch (error) { + throw new Error(`Unable to fetch widget code. ${error.message}`) + } + + try { + console.log(`🔌 [widget:${widgetId}] Compiling widget code...`) + + widgetCode = await compileWidgetCode(widgetCode, widget.manifest, finalOrigin) + + await global.redis.set(`${origin}:widget:${widgetId}@${requestedVersion}}`, widgetCode) + } catch (error) { + throw new Error(`Unable to transform widget code. ${error.message}`) + } + } + + return widgetCode +} \ No newline at end of file