UNPKG

@codesandbox/sandpack-client

Version:

<img style="width:100%" src="https://user-images.githubusercontent.com/4838076/143581035-ebee5ba2-9cb1-4fe8-a05b-2f44bd69bb4b.gif" alt="Component toolkit for live running code editing experiences" />

669 lines (660 loc) 28.3 kB
'use strict'; var utils = require('../../utils-5d2e6c36.js'); var dequal = require('dequal'); var base = require('../../base-c7317183.js'); var mimeDB = require('mime-db'); require('outvariant'); /** * This file is a copy of the resolver from the `codesandbox-api` package. * We wanted to avoid to reference codesandbox-api because of the code that runs on load in the package. * The plan is to take some time and refactor codesandbox-api into what it was supposed to be in the first place, * an abstraction over the actions that can be dispatched between the bundler and the iframe. */ var Protocol = /** @class */ (function () { function Protocol(type, handleMessage, protocol) { var _this = this; this.type = type; this.handleMessage = handleMessage; this.protocol = protocol; this._disposeMessageListener = this.protocol.channelListen(function (msg) { return utils.__awaiter(_this, void 0, void 0, function () { var message, result, response, err_1, response; return utils.__generator(this, function (_a) { switch (_a.label) { case 0: if (!(msg.type === this.getTypeId() && msg.method)) return [3 /*break*/, 4]; message = msg; _a.label = 1; case 1: _a.trys.push([1, 3, , 4]); return [4 /*yield*/, this.handleMessage(message)]; case 2: result = _a.sent(); response = { type: this.getTypeId(), msgId: message.msgId, result: result, }; this.protocol.dispatch(response); return [3 /*break*/, 4]; case 3: err_1 = _a.sent(); response = { type: this.getTypeId(), msgId: message.msgId, error: { message: err_1.message, }, }; this.protocol.dispatch(response); return [3 /*break*/, 4]; case 4: return [2 /*return*/]; } }); }); }); } Protocol.prototype.getTypeId = function () { return "protocol-".concat(this.type); }; Protocol.prototype.dispose = function () { this._disposeMessageListener(); }; return Protocol; }()); var IFrameProtocol = /** @class */ (function () { function IFrameProtocol(iframe, origin) { // React to messages from any iframe this.globalListeners = {}; this.globalListenersCount = 0; // React to messages from the iframe owned by this instance this.channelListeners = {}; this.channelListenersCount = 0; // Random number to identify this instance of the client when messages are coming from multiple iframes this.channelId = Math.floor(Math.random() * 1000000); this.frameWindow = iframe.contentWindow; this.origin = origin; this.globalListeners = []; this.channelListeners = []; this.eventListener = this.eventListener.bind(this); if (typeof window !== "undefined") { window.addEventListener("message", this.eventListener); } } IFrameProtocol.prototype.cleanup = function () { window.removeEventListener("message", this.eventListener); this.globalListeners = {}; this.channelListeners = {}; this.globalListenersCount = 0; this.channelListenersCount = 0; }; // Sends the channelId and triggers an iframeHandshake promise to resolve, // so the iframe can start listening for messages (based on the id) IFrameProtocol.prototype.register = function () { if (!this.frameWindow) { return; } this.frameWindow.postMessage({ type: "register-frame", origin: document.location.origin, id: this.channelId, }, this.origin); }; // Messages are dispatched from the client directly to the instance iframe IFrameProtocol.prototype.dispatch = function (message) { if (!this.frameWindow) { return; } this.frameWindow.postMessage(utils.__assign({ $id: this.channelId, codesandbox: true }, message), this.origin); }; // Add a listener that is called on any message coming from an iframe in the page // This is needed for the `initialize` message which comes without a channelId IFrameProtocol.prototype.globalListen = function (listener) { var _this = this; if (typeof listener !== "function") { return function () { return; }; } var listenerId = this.globalListenersCount; this.globalListeners[listenerId] = listener; this.globalListenersCount++; return function () { delete _this.globalListeners[listenerId]; }; }; // Add a listener that is called on any message coming from an iframe with the instance channelId // All other messages (eg: from other iframes) are ignored IFrameProtocol.prototype.channelListen = function (listener) { var _this = this; if (typeof listener !== "function") { return function () { return; }; } var listenerId = this.channelListenersCount; this.channelListeners[listenerId] = listener; this.channelListenersCount++; return function () { delete _this.channelListeners[listenerId]; }; }; // Handles message windows coming from iframes IFrameProtocol.prototype.eventListener = function (evt) { // skip events originating from different iframes if (evt.source !== this.frameWindow) { return; } var message = evt.data; if (!message.codesandbox) { return; } Object.values(this.globalListeners).forEach(function (listener) { return listener(message); }); if (message.$id !== this.channelId) { return; } Object.values(this.channelListeners).forEach(function (listener) { return listener(message); }); }; return IFrameProtocol; }()); var extensionMap = new Map(); var entries = Object.entries(mimeDB); for (var _i = 0, entries_1 = entries; _i < entries_1.length; _i++) { var _a$1 = entries_1[_i], mimetype = _a$1[0], entry = _a$1[1]; // eslint-disable-next-line // @ts-ignore if (!entry.extensions) { continue; } // eslint-disable-next-line // @ts-ignore var extensions = entry.extensions; if (extensions.length) { for (var _b = 0, extensions_1 = extensions; _b < extensions_1.length; _b++) { var ext = extensions_1[_b]; extensionMap.set(ext, mimetype); } } } var EXTENSIONS_MAP = extensionMap; var CHANNEL_NAME = "$CSB_RELAY"; var MAX_CLIENT_DEPENDENCY_COUNT = 50; function getTemplate(pkg, /* eslint-disable @typescript-eslint/no-explicit-any */ modules) { if (!pkg) { return "static"; } var _a = pkg.dependencies, dependencies = _a === void 0 ? {} : _a, _b = pkg.devDependencies, devDependencies = _b === void 0 ? {} : _b; var totalDependencies = utils.__spreadArray(utils.__spreadArray([], Object.keys(dependencies), true), Object.keys(devDependencies), true); var moduleNames = Object.keys(modules); var adonis = ["@adonisjs/framework", "@adonisjs/core"]; if (totalDependencies.some(function (dep) { return adonis.indexOf(dep) > -1; })) { return "adonis"; } var nuxt = ["nuxt", "nuxt-edge", "nuxt-ts", "nuxt-ts-edge", "nuxt3"]; if (totalDependencies.some(function (dep) { return nuxt.indexOf(dep) > -1; })) { return "nuxt"; } if (totalDependencies.indexOf("next") > -1) { return "next"; } var apollo = [ "apollo-server", "apollo-server-express", "apollo-server-hapi", "apollo-server-koa", "apollo-server-lambda", "apollo-server-micro", ]; if (totalDependencies.some(function (dep) { return apollo.indexOf(dep) > -1; })) { return "apollo"; } if (totalDependencies.indexOf("mdx-deck") > -1) { return "mdx-deck"; } if (totalDependencies.indexOf("gridsome") > -1) { return "gridsome"; } if (totalDependencies.indexOf("vuepress") > -1) { return "vuepress"; } if (totalDependencies.indexOf("ember-cli") > -1) { return "ember"; } if (totalDependencies.indexOf("sapper") > -1) { return "sapper"; } if (totalDependencies.indexOf("gatsby") > -1) { return "gatsby"; } if (totalDependencies.indexOf("quasar") > -1) { return "quasar"; } if (totalDependencies.indexOf("@docusaurus/core") > -1) { return "docusaurus"; } if (totalDependencies.indexOf("remix") > -1) { return "remix"; } if (totalDependencies.indexOf("astro") > -1) { return "node"; } // CLIENT if (moduleNames.some(function (m) { return m.endsWith(".re"); })) { return "reason"; } var parcel = ["parcel-bundler", "parcel"]; if (totalDependencies.some(function (dep) { return parcel.indexOf(dep) > -1; })) { return "parcel"; } var dojo = ["@dojo/core", "@dojo/framework"]; if (totalDependencies.some(function (dep) { return dojo.indexOf(dep) > -1; })) { return "@dojo/cli-create-app"; } if (totalDependencies.indexOf("@nestjs/core") > -1 || totalDependencies.indexOf("@nestjs/common") > -1) { return "nest"; } if (totalDependencies.indexOf("react-styleguidist") > -1) { return "styleguidist"; } if (totalDependencies.indexOf("react-scripts") > -1) { return "create-react-app"; } if (totalDependencies.indexOf("react-scripts-ts") > -1) { return "create-react-app-typescript"; } if (totalDependencies.indexOf("@angular/core") > -1) { return "angular-cli"; } if (totalDependencies.indexOf("preact-cli") > -1) { return "preact-cli"; } if (totalDependencies.indexOf("@sveltech/routify") > -1 || totalDependencies.indexOf("@roxi/routify") > -1) { return "node"; } if (totalDependencies.indexOf("vite") > -1) { return "node"; } if (totalDependencies.indexOf("@frontity/core") > -1) { return "node"; } if (totalDependencies.indexOf("svelte") > -1) { return "svelte"; } if (totalDependencies.indexOf("vue") > -1) { return "vue-cli"; } if (totalDependencies.indexOf("cx") > -1) { return "cxjs"; } var nodeDeps = [ "express", "koa", "nodemon", "ts-node", "@tensorflow/tfjs-node", "webpack-dev-server", "snowpack", ]; if (totalDependencies.some(function (dep) { return nodeDeps.indexOf(dep) > -1; })) { return "node"; } if (Object.keys(dependencies).length >= MAX_CLIENT_DEPENDENCY_COUNT) { // The dependencies are too much for client sandboxes to handle return "node"; } return undefined; } function getExtension(filepath) { var parts = filepath.split("."); if (parts.length <= 1) { return ""; } else { var ext = parts[parts.length - 1]; return ext; } } var _a; var SUFFIX_PLACEHOLDER = "-{{suffix}}"; var BUNDLER_URL = "https://".concat((_a = "2.19.8") === null || _a === void 0 ? void 0 : _a.replace(/\./g, "-")).concat(SUFFIX_PLACEHOLDER, "-sandpack.codesandbox.io/"); var SandpackRuntime = /** @class */ (function (_super) { utils.__extends(SandpackRuntime, _super); function SandpackRuntime(selector, sandboxSetup, options) { if (options === void 0) { options = {}; } var _this = _super.call(this, selector, sandboxSetup, options) || this; _this.getTranspilerContext = function () { return new Promise(function (resolve) { var unsubscribe = _this.listen(function (message) { if (message.type === "transpiler-context") { resolve(message.data); unsubscribe(); } }); _this.dispatch({ type: "get-transpiler-context" }); }); }; _this.getTranspiledFiles = function () { return new Promise(function (resolve) { var unsubscribe = _this.listen(function (message) { if (message.type === "all-modules") { resolve(message.data); unsubscribe(); } }); _this.dispatch({ type: "get-modules" }); }); }; _this.bundlerURL = _this.createBundlerURL(); _this.bundlerState = undefined; _this.errors = []; _this.status = "initializing"; if (typeof selector === "string") { _this.selector = selector; var element = document.querySelector(selector); utils.nullthrows(element, "The element '".concat(selector, "' was not found")); _this.element = element; _this.iframe = document.createElement("iframe"); _this.initializeElement(); } else { _this.element = selector; _this.iframe = selector; } if (!_this.iframe.getAttribute("sandbox")) { _this.iframe.setAttribute("sandbox", "allow-forms allow-modals allow-popups allow-presentation allow-same-origin allow-scripts allow-downloads allow-pointer-lock"); _this.iframe.setAttribute("allow", "accelerometer; camera; encrypted-media; geolocation; gyroscope; hid; microphone; midi; clipboard-read; clipboard-write; xr-spatial-tracking;"); } _this.setLocationURLIntoIFrame(); _this.iframeProtocol = new IFrameProtocol(_this.iframe, _this.bundlerURL); _this.unsubscribeGlobalListener = _this.iframeProtocol.globalListen(function (mes) { if (mes.type !== "initialized" || !_this.iframe.contentWindow) { return; } _this.iframeProtocol.register(); if (_this.options.fileResolver) { _this.fileResolverProtocol = new Protocol("fs", function (data) { return utils.__awaiter(_this, void 0, void 0, function () { return utils.__generator(this, function (_a) { if (data.method === "isFile") { // eslint-disable-next-line @typescript-eslint/no-non-null-assertion return [2 /*return*/, this.options.fileResolver.isFile(data.params[0])]; } else if (data.method === "readFile") { // eslint-disable-next-line @typescript-eslint/no-non-null-assertion return [2 /*return*/, this.options.fileResolver.readFile(data.params[0])]; } else { throw new Error("Method not supported"); } }); }); }, _this.iframeProtocol); } _this.updateSandbox(_this.sandboxSetup, true); }); _this.unsubscribeChannelListener = _this.iframeProtocol.channelListen(function (mes) { switch (mes.type) { case "start": { _this.errors = []; break; } case "status": { _this.status = mes.status; break; } case "action": { if (mes.action === "show-error") { _this.errors = utils.__spreadArray(utils.__spreadArray([], _this.errors, true), [utils.extractErrorDetails(mes)], false); } break; } case "done": { _this.status = "done"; break; } case "state": { _this.bundlerState = mes.state; break; } } }); if (options.experimental_enableServiceWorker) { _this.serviceWorkerHandshake(); } return _this; } SandpackRuntime.prototype.createBundlerURL = function () { var _a; var bundlerURL = this.options.bundlerURL || BUNDLER_URL; // if it's a custom, skip the rest if (this.options.bundlerURL) { return bundlerURL; } if (this.options.teamId) { bundlerURL = bundlerURL.replace("https://", "https://" + this.options.teamId + "-") + "?cache=".concat(Date.now()); } if (this.options.experimental_enableServiceWorker) { var suffixes = []; suffixes.push(Math.random().toString(36).slice(4)); bundlerURL = bundlerURL.replace(SUFFIX_PLACEHOLDER, "-".concat((_a = this.options.experimental_stableServiceWorkerId) !== null && _a !== void 0 ? _a : suffixes.join("-"))); } else { bundlerURL = bundlerURL.replace(SUFFIX_PLACEHOLDER, ""); } return bundlerURL; }; SandpackRuntime.prototype.serviceWorkerHandshake = function () { var _this = this; var channel = new MessageChannel(); var iframeContentWindow = this.iframe.contentWindow; if (!iframeContentWindow) { throw new Error("Could not get iframe contentWindow"); } var port = channel.port1; port.onmessage = function (evt) { if (typeof evt.data === "object" && evt.data.$channel === CHANNEL_NAME) { switch (evt.data.$type) { case "preview/ready": // no op for now break; case "preview/request": _this.handleWorkerRequest(evt.data, port); break; } } }; var sendMessage = function () { var initMsg = { $channel: CHANNEL_NAME, $type: "preview/init", }; iframeContentWindow.postMessage(initMsg, "*", [channel.port2]); _this.iframe.removeEventListener("load", sendMessage); }; this.iframe.addEventListener("load", sendMessage); }; SandpackRuntime.prototype.handleWorkerRequest = function (request, port) { return utils.__awaiter(this, void 0, void 0, function () { var notFound, filepath_1, headers, files, file, modulesFromManager, body, extension, foundMimetype, responseMessage, err_1; return utils.__generator(this, function (_a) { switch (_a.label) { case 0: notFound = function () { var responseMessage = { $channel: CHANNEL_NAME, $type: "preview/response", id: request.id, headers: { "Content-Type": "text/html; charset=utf-8", }, status: 404, body: "File not found", }; port.postMessage(responseMessage); }; _a.label = 1; case 1: _a.trys.push([1, 4, , 5]); filepath_1 = new URL(request.url, this.bundlerURL).pathname; headers = {}; files = this.getFiles(); file = files[filepath_1]; if (!!file) return [3 /*break*/, 3]; return [4 /*yield*/, this.getTranspiledFiles()]; case 2: modulesFromManager = _a.sent(); file = modulesFromManager.find(function (item) { return item.path.endsWith(filepath_1); }); if (!file) { notFound(); return [2 /*return*/]; } _a.label = 3; case 3: body = file.code; if (!headers["Content-Type"]) { extension = getExtension(filepath_1); foundMimetype = EXTENSIONS_MAP.get(extension); if (foundMimetype) { headers["Content-Type"] = foundMimetype; } } responseMessage = { $channel: CHANNEL_NAME, $type: "preview/response", id: request.id, headers: headers, status: 200, body: body, }; port.postMessage(responseMessage); return [3 /*break*/, 5]; case 4: err_1 = _a.sent(); console.error(err_1); notFound(); return [3 /*break*/, 5]; case 5: return [2 /*return*/]; } }); }); }; SandpackRuntime.prototype.setLocationURLIntoIFrame = function () { var _a; var urlSource = this.options.startRoute ? new URL(this.options.startRoute, this.bundlerURL).toString() : this.bundlerURL; (_a = this.iframe.contentWindow) === null || _a === void 0 ? void 0 : _a.location.replace(urlSource); this.iframe.src = urlSource; }; SandpackRuntime.prototype.destroy = function () { this.unsubscribeChannelListener(); this.unsubscribeGlobalListener(); this.iframeProtocol.cleanup(); }; SandpackRuntime.prototype.updateOptions = function (options) { if (!dequal.dequal(this.options, options)) { this.options = options; this.updateSandbox(); } }; SandpackRuntime.prototype.updateSandbox = function (sandboxSetup, isInitializationCompile) { var _a, _b, _c, _d; if (sandboxSetup === void 0) { sandboxSetup = this.sandboxSetup; } this.sandboxSetup = utils.__assign(utils.__assign({}, this.sandboxSetup), sandboxSetup); var files = this.getFiles(); var modules = Object.keys(files).reduce(function (prev, next) { var _a; return (utils.__assign(utils.__assign({}, prev), (_a = {}, _a[next] = { code: files[next].code, path: next, }, _a))); }, {}); var packageJSON = JSON.parse(utils.createPackageJSON(this.sandboxSetup.dependencies, this.sandboxSetup.devDependencies, this.sandboxSetup.entry)); try { packageJSON = JSON.parse(files["/package.json"].code); } catch (e) { console.error(utils.createError("could not parse package.json file: " + e.message)); } // TODO move this to a common format var normalizedModules = Object.keys(files).reduce(function (prev, next) { var _a; return (utils.__assign(utils.__assign({}, prev), (_a = {}, _a[next] = { content: files[next].code, path: next, }, _a))); }, {}); this.dispatch(utils.__assign(utils.__assign({}, this.options), { type: "compile", codesandbox: true, version: 3, isInitializationCompile: isInitializationCompile, modules: modules, reactDevTools: this.options.reactDevTools, externalResources: this.options.externalResources || [], hasFileResolver: Boolean(this.options.fileResolver), disableDependencyPreprocessing: this.sandboxSetup.disableDependencyPreprocessing, experimental_enableServiceWorker: this.options.experimental_enableServiceWorker, template: this.sandboxSetup.template || getTemplate(packageJSON, normalizedModules), showOpenInCodeSandbox: (_a = this.options.showOpenInCodeSandbox) !== null && _a !== void 0 ? _a : true, showErrorScreen: (_b = this.options.showErrorScreen) !== null && _b !== void 0 ? _b : true, showLoadingScreen: (_c = this.options.showLoadingScreen) !== null && _c !== void 0 ? _c : false, skipEval: this.options.skipEval || false, clearConsoleDisabled: !this.options.clearConsoleOnFirstCompile, logLevel: (_d = this.options.logLevel) !== null && _d !== void 0 ? _d : utils.SandpackLogLevel.Info, customNpmRegistries: this.options.customNpmRegistries, teamId: this.options.teamId, sandboxId: this.options.sandboxId })); }; SandpackRuntime.prototype.dispatch = function (message) { /** * Intercept "refresh" dispatch: this will make sure * that the iframe is still in the location it's supposed to be. * External links inside the iframe will change the location and * prevent the user from navigating back. */ if (message.type === "refresh") { this.setLocationURLIntoIFrame(); if (this.options.experimental_enableServiceWorker) { this.serviceWorkerHandshake(); } } this.iframeProtocol.dispatch(message); }; SandpackRuntime.prototype.listen = function (listener) { return this.iframeProtocol.channelListen(listener); }; /** * Get the URL of the contents of the current sandbox */ SandpackRuntime.prototype.getCodeSandboxURL = function () { var files = this.getFiles(); var paramFiles = Object.keys(files).reduce(function (prev, next) { var _a; return (utils.__assign(utils.__assign({}, prev), (_a = {}, _a[next.replace("/", "")] = { content: files[next].code, isBinary: false, }, _a))); }, {}); return fetch("https://codesandbox.io/api/v1/sandboxes/define?json=1", { method: "POST", body: JSON.stringify({ files: paramFiles }), headers: { Accept: "application/json", "Content-Type": "application/json", }, }) .then(function (x) { return x.json(); }) .then(function (res) { return ({ sandboxId: res.sandbox_id, editorUrl: "https://codesandbox.io/s/".concat(res.sandbox_id), embedUrl: "https://codesandbox.io/embed/".concat(res.sandbox_id), }); }); }; SandpackRuntime.prototype.getFiles = function () { var sandboxSetup = this.sandboxSetup; if (sandboxSetup.files["/package.json"] === undefined) { return utils.addPackageJSONIfNeeded(sandboxSetup.files, sandboxSetup.dependencies, sandboxSetup.devDependencies, sandboxSetup.entry); } return this.sandboxSetup.files; }; SandpackRuntime.prototype.initializeElement = function () { this.iframe.style.border = "0"; this.iframe.style.width = this.options.width || "100%"; this.iframe.style.height = this.options.height || "100%"; this.iframe.style.overflow = "hidden"; utils.nullthrows(this.element.parentNode, "The given iframe does not have a parent."); this.element.parentNode.replaceChild(this.iframe, this.element); }; return SandpackRuntime; }(base.SandpackClient)); exports.SandpackRuntime = SandpackRuntime;