@stackblitz_sdk.js 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523
  1. import "./chunk-JC4IRQUL.js";
  2. // node_modules/.pnpm/@stackblitz+sdk@1.9.0/node_modules/@stackblitz/sdk/bundles/sdk.m.js
  3. var CONNECT_INTERVAL = 500;
  4. var CONNECT_MAX_ATTEMPTS = 20;
  5. var DEFAULT_FRAME_HEIGHT = 300;
  6. var DEFAULT_ORIGIN = "https://stackblitz.com";
  7. var PROJECT_TEMPLATES = [
  8. "angular-cli",
  9. "create-react-app",
  10. "html",
  11. "javascript",
  12. "node",
  13. "polymer",
  14. "typescript",
  15. "vue"
  16. ];
  17. var UI_SIDEBAR_VIEWS = ["project", "search", "ports", "settings"];
  18. var UI_THEMES = ["light", "dark"];
  19. var UI_VIEWS = ["editor", "preview"];
  20. var generators = {
  21. clickToLoad: (value) => trueParam("ctl", value),
  22. devToolsHeight: (value) => percentParam("devtoolsheight", value),
  23. forceEmbedLayout: (value) => trueParam("embed", value),
  24. hideDevTools: (value) => trueParam("hidedevtools", value),
  25. hideExplorer: (value) => trueParam("hideExplorer", value),
  26. hideNavigation: (value) => trueParam("hideNavigation", value),
  27. openFile: (value) => stringParams("file", value),
  28. showSidebar: (value) => booleanParam("showSidebar", value),
  29. sidebarView: (value) => enumParam("sidebarView", value, UI_SIDEBAR_VIEWS),
  30. startScript: (value) => stringParams("startScript", value),
  31. terminalHeight: (value) => percentParam("terminalHeight", value),
  32. theme: (value) => enumParam("theme", value, UI_THEMES),
  33. view: (value) => enumParam("view", value, UI_VIEWS),
  34. zenMode: (value) => trueParam("zenMode", value)
  35. };
  36. function buildParams(options = {}) {
  37. const params = Object.entries(options).map(([key, value]) => {
  38. if (value != null && generators.hasOwnProperty(key)) {
  39. return generators[key](value);
  40. }
  41. return "";
  42. }).filter(Boolean);
  43. return params.length ? `?${params.join("&")}` : "";
  44. }
  45. function trueParam(name, value) {
  46. if (value === true) {
  47. return `${name}=1`;
  48. }
  49. return "";
  50. }
  51. function booleanParam(name, value) {
  52. if (typeof value === "boolean") {
  53. return `${name}=${value ? "1" : "0"}`;
  54. }
  55. return "";
  56. }
  57. function percentParam(name, value) {
  58. if (typeof value === "number" && !Number.isNaN(value)) {
  59. const clamped = Math.min(100, Math.max(0, value));
  60. return `${name}=${encodeURIComponent(Math.round(clamped))}`;
  61. }
  62. return "";
  63. }
  64. function enumParam(name, value = "", allowList = []) {
  65. if (allowList.includes(value)) {
  66. return `${name}=${encodeURIComponent(value)}`;
  67. }
  68. return "";
  69. }
  70. function stringParams(name, value) {
  71. const values = Array.isArray(value) ? value : [value];
  72. return values.filter((val) => typeof val === "string" && val.trim() !== "").map((val) => `${name}=${encodeURIComponent(val)}`).join("&");
  73. }
  74. function genID() {
  75. return Math.random().toString(36).slice(2, 6) + Math.random().toString(36).slice(2, 6);
  76. }
  77. function openUrl(route, options) {
  78. return `${getOrigin(options)}${route}${buildParams(options)}`;
  79. }
  80. function embedUrl(route, options) {
  81. const config = {
  82. forceEmbedLayout: true
  83. };
  84. if (options && typeof options === "object") {
  85. Object.assign(config, options);
  86. }
  87. return `${getOrigin(config)}${route}${buildParams(config)}`;
  88. }
  89. function getOrigin(options = {}) {
  90. const origin = typeof options.origin === "string" ? options.origin : DEFAULT_ORIGIN;
  91. return origin.replace(/\/$/, "");
  92. }
  93. function replaceAndEmbed(target, frame, options) {
  94. if (!frame || !target || !target.parentNode) {
  95. throw new Error("Invalid Element");
  96. }
  97. if (target.id) {
  98. frame.id = target.id;
  99. }
  100. if (target.className) {
  101. frame.className = target.className;
  102. }
  103. setFrameDimensions(frame, options);
  104. target.replaceWith(frame);
  105. }
  106. function findElement(elementOrId) {
  107. if (typeof elementOrId === "string") {
  108. const element = document.getElementById(elementOrId);
  109. if (!element) {
  110. throw new Error(`Could not find element with id '${elementOrId}'`);
  111. }
  112. return element;
  113. } else if (elementOrId instanceof HTMLElement) {
  114. return elementOrId;
  115. }
  116. throw new Error(`Invalid element: ${elementOrId}`);
  117. }
  118. function openTarget(options) {
  119. return options && options.newWindow === false ? "_self" : "_blank";
  120. }
  121. function setFrameDimensions(frame, options = {}) {
  122. const height = Object.hasOwnProperty.call(options, "height") ? `${options.height}` : `${DEFAULT_FRAME_HEIGHT}`;
  123. const width = Object.hasOwnProperty.call(options, "width") ? `${options.width}` : void 0;
  124. frame.setAttribute("height", height);
  125. if (width) {
  126. frame.setAttribute("width", width);
  127. } else {
  128. frame.setAttribute("style", "width:100%;");
  129. }
  130. }
  131. var RDC = class {
  132. constructor(port) {
  133. this.pending = {};
  134. this.port = port;
  135. this.port.onmessage = this.messageListener.bind(this);
  136. }
  137. request({ type, payload }) {
  138. return new Promise((resolve, reject) => {
  139. const id = genID();
  140. this.pending[id] = { resolve, reject };
  141. this.port.postMessage({
  142. type,
  143. payload: {
  144. ...payload,
  145. // Ensure the payload object includes the request ID
  146. __reqid: id
  147. }
  148. });
  149. });
  150. }
  151. messageListener(event) {
  152. var _a;
  153. if (typeof ((_a = event.data.payload) == null ? void 0 : _a.__reqid) !== "string") {
  154. return;
  155. }
  156. const { type, payload } = event.data;
  157. const { __reqid: id, __success: success, __error: error } = payload;
  158. if (this.pending[id]) {
  159. if (success) {
  160. this.pending[id].resolve(this.cleanResult(payload));
  161. } else {
  162. this.pending[id].reject(error ? `${type}: ${error}` : type);
  163. }
  164. delete this.pending[id];
  165. }
  166. }
  167. cleanResult(payload) {
  168. const result = { ...payload };
  169. delete result.__reqid;
  170. delete result.__success;
  171. delete result.__error;
  172. return Object.keys(result).length ? result : null;
  173. }
  174. };
  175. var VM = class {
  176. constructor(port, config) {
  177. this.editor = {
  178. /**
  179. * Open one of several files in tabs and/or split panes.
  180. *
  181. * @since 1.7.0 Added support for opening multiple files
  182. */
  183. openFile: (path) => {
  184. return this._rdc.request({
  185. type: "SDK_OPEN_FILE",
  186. payload: { path }
  187. });
  188. },
  189. /**
  190. * Set a project file as the currently selected file.
  191. *
  192. * - This may update the highlighted file in the file explorer,
  193. * and the currently open and/or focused editor tab.
  194. * - It will _not_ open a new editor tab if the provided path does not
  195. * match a currently open tab. See `vm.editor.openFile` to open files.
  196. *
  197. * @since 1.7.0
  198. * @experimental
  199. */
  200. setCurrentFile: (path) => {
  201. return this._rdc.request({
  202. type: "SDK_SET_CURRENT_FILE",
  203. payload: { path }
  204. });
  205. },
  206. /**
  207. * Change the color theme
  208. *
  209. * @since 1.7.0
  210. */
  211. setTheme: (theme) => {
  212. return this._rdc.request({
  213. type: "SDK_SET_UI_THEME",
  214. payload: { theme }
  215. });
  216. },
  217. /**
  218. * Change the display mode of the project:
  219. *
  220. * - `default`: show the editor and preview pane
  221. * - `editor`: show the editor pane only
  222. * - `preview`: show the preview pane only
  223. *
  224. * @since 1.7.0
  225. */
  226. setView: (view) => {
  227. return this._rdc.request({
  228. type: "SDK_SET_UI_VIEW",
  229. payload: { view }
  230. });
  231. },
  232. /**
  233. * Change the display mode of the sidebar:
  234. *
  235. * - `true`: show the sidebar
  236. * - `false`: hide the sidebar
  237. *
  238. * @since 1.7.0
  239. */
  240. showSidebar: (visible = true) => {
  241. return this._rdc.request({
  242. type: "SDK_TOGGLE_SIDEBAR",
  243. payload: { visible }
  244. });
  245. }
  246. };
  247. this.preview = {
  248. /**
  249. * The origin (protocol and domain) of the preview iframe.
  250. *
  251. * In WebContainers-based projects, the origin will always be `null`;
  252. * try using `vm.preview.getUrl` instead.
  253. *
  254. * @see https://developer.stackblitz.com/guides/user-guide/available-environments
  255. */
  256. origin: "",
  257. /**
  258. * Get the current preview URL.
  259. *
  260. * In both and EngineBlock and WebContainers-based projects, the preview URL
  261. * may not reflect the exact path of the current page, after user navigation.
  262. *
  263. * In WebContainers-based projects, the preview URL will be `null` initially,
  264. * and until the project starts a web server.
  265. *
  266. * @since 1.7.0
  267. * @experimental
  268. */
  269. getUrl: () => {
  270. return this._rdc.request({
  271. type: "SDK_GET_PREVIEW_URL",
  272. payload: {}
  273. }).then((data) => (data == null ? void 0 : data.url) ?? null);
  274. },
  275. /**
  276. * Change the path of the preview URL.
  277. *
  278. * In WebContainers-based projects, this will be ignored if there is no
  279. * currently running web server.
  280. *
  281. * @since 1.7.0
  282. * @experimental
  283. */
  284. setUrl: (path = "/") => {
  285. if (typeof path !== "string" || !path.startsWith("/")) {
  286. throw new Error(`Invalid argument: expected a path starting with '/', got '${path}'`);
  287. }
  288. return this._rdc.request({
  289. type: "SDK_SET_PREVIEW_URL",
  290. payload: { path }
  291. });
  292. }
  293. };
  294. this._rdc = new RDC(port);
  295. Object.defineProperty(this.preview, "origin", {
  296. value: typeof config.previewOrigin === "string" ? config.previewOrigin : null,
  297. writable: false
  298. });
  299. }
  300. /**
  301. * Apply batch updates to the project files in one call.
  302. */
  303. applyFsDiff(diff) {
  304. const isObject = (val) => val !== null && typeof val === "object";
  305. if (!isObject(diff) || !isObject(diff.create)) {
  306. throw new Error("Invalid diff object: expected diff.create to be an object.");
  307. } else if (!Array.isArray(diff.destroy)) {
  308. throw new Error("Invalid diff object: expected diff.destroy to be an array.");
  309. }
  310. return this._rdc.request({
  311. type: "SDK_APPLY_FS_DIFF",
  312. payload: diff
  313. });
  314. }
  315. /**
  316. * Get the project’s defined dependencies.
  317. *
  318. * In EngineBlock projects, version numbers represent the resolved dependency versions.
  319. * In WebContainers-based projects, returns data from the project’s `package.json` without resolving installed version numbers.
  320. */
  321. getDependencies() {
  322. return this._rdc.request({
  323. type: "SDK_GET_DEPS_SNAPSHOT",
  324. payload: {}
  325. });
  326. }
  327. /**
  328. * Get a snapshot of the project files and their content.
  329. */
  330. getFsSnapshot() {
  331. return this._rdc.request({
  332. type: "SDK_GET_FS_SNAPSHOT",
  333. payload: {}
  334. });
  335. }
  336. };
  337. var connections = [];
  338. var Connection = class {
  339. constructor(element) {
  340. this.id = genID();
  341. this.element = element;
  342. this.pending = new Promise((resolve, reject) => {
  343. const listenForSuccess = ({ data, ports }) => {
  344. if ((data == null ? void 0 : data.action) === "SDK_INIT_SUCCESS" && data.id === this.id) {
  345. this.vm = new VM(ports[0], data.payload);
  346. resolve(this.vm);
  347. cleanup();
  348. }
  349. };
  350. const pingFrame = () => {
  351. var _a;
  352. (_a = this.element.contentWindow) == null ? void 0 : _a.postMessage(
  353. {
  354. action: "SDK_INIT",
  355. id: this.id
  356. },
  357. "*"
  358. );
  359. };
  360. function cleanup() {
  361. window.clearInterval(interval);
  362. window.removeEventListener("message", listenForSuccess);
  363. }
  364. window.addEventListener("message", listenForSuccess);
  365. pingFrame();
  366. let attempts = 0;
  367. const interval = window.setInterval(() => {
  368. if (this.vm) {
  369. cleanup();
  370. return;
  371. }
  372. if (attempts >= CONNECT_MAX_ATTEMPTS) {
  373. cleanup();
  374. reject("Timeout: Unable to establish a connection with the StackBlitz VM");
  375. connections.forEach((connection, index) => {
  376. if (connection.id === this.id) {
  377. connections.splice(index, 1);
  378. }
  379. });
  380. return;
  381. }
  382. attempts++;
  383. pingFrame();
  384. }, CONNECT_INTERVAL);
  385. });
  386. connections.push(this);
  387. }
  388. };
  389. var getConnection = (identifier) => {
  390. const key = identifier instanceof Element ? "element" : "id";
  391. return connections.find((c) => c[key] === identifier) ?? null;
  392. };
  393. function createHiddenInput(name, value) {
  394. const input = document.createElement("input");
  395. input.type = "hidden";
  396. input.name = name;
  397. input.value = value;
  398. return input;
  399. }
  400. function encodeFilePath(path) {
  401. return path.replace(/\[/g, "%5B").replace(/\]/g, "%5D");
  402. }
  403. function createProjectForm({
  404. template,
  405. title,
  406. description,
  407. dependencies,
  408. files,
  409. settings
  410. }) {
  411. if (!PROJECT_TEMPLATES.includes(template)) {
  412. const names = PROJECT_TEMPLATES.map((t) => `'${t}'`).join(", ");
  413. console.warn(`Unsupported project.template: must be one of ${names}`);
  414. }
  415. const inputs = [];
  416. const addInput = (name, value, defaultValue = "") => {
  417. inputs.push(createHiddenInput(name, typeof value === "string" ? value : defaultValue));
  418. };
  419. addInput("project[title]", title);
  420. if (typeof description === "string" && description.length > 0) {
  421. addInput("project[description]", description);
  422. }
  423. addInput("project[template]", template, "javascript");
  424. if (dependencies) {
  425. if (template === "node") {
  426. console.warn(
  427. `Invalid project.dependencies: dependencies must be provided as a 'package.json' file when using the 'node' template.`
  428. );
  429. } else {
  430. addInput("project[dependencies]", JSON.stringify(dependencies));
  431. }
  432. }
  433. if (settings) {
  434. addInput("project[settings]", JSON.stringify(settings));
  435. }
  436. Object.entries(files).forEach(([path, contents]) => {
  437. addInput(`project[files][${encodeFilePath(path)}]`, contents);
  438. });
  439. const form = document.createElement("form");
  440. form.method = "POST";
  441. form.setAttribute("style", "display:none!important;");
  442. form.append(...inputs);
  443. return form;
  444. }
  445. function createProjectFrameHTML(project, options) {
  446. const form = createProjectForm(project);
  447. form.action = embedUrl("/run", options);
  448. form.id = "sb_run";
  449. const html = `<!doctype html>
  450. <html>
  451. <head><title></title></head>
  452. <body>
  453. ${form.outerHTML}
  454. <script>document.getElementById('${form.id}').submit();<\/script>
  455. </body>
  456. </html>`;
  457. return html;
  458. }
  459. function openNewProject(project, options) {
  460. const form = createProjectForm(project);
  461. form.action = openUrl("/run", options);
  462. form.target = openTarget(options);
  463. document.body.appendChild(form);
  464. form.submit();
  465. document.body.removeChild(form);
  466. }
  467. function connect(frameEl) {
  468. if (!(frameEl == null ? void 0 : frameEl.contentWindow)) {
  469. return Promise.reject("Provided element is not an iframe.");
  470. }
  471. const connection = getConnection(frameEl) ?? new Connection(frameEl);
  472. return connection.pending;
  473. }
  474. function openProject(project, options) {
  475. openNewProject(project, options);
  476. }
  477. function openProjectId(projectId, options) {
  478. const url = openUrl(`/edit/${projectId}`, options);
  479. const target = openTarget(options);
  480. window.open(url, target);
  481. }
  482. function openGithubProject(repoSlug, options) {
  483. const url = openUrl(`/github/${repoSlug}`, options);
  484. const target = openTarget(options);
  485. window.open(url, target);
  486. }
  487. function embedProject(elementOrId, project, options) {
  488. var _a;
  489. const element = findElement(elementOrId);
  490. const html = createProjectFrameHTML(project, options);
  491. const frame = document.createElement("iframe");
  492. replaceAndEmbed(element, frame, options);
  493. (_a = frame.contentDocument) == null ? void 0 : _a.write(html);
  494. return connect(frame);
  495. }
  496. function embedProjectId(elementOrId, projectId, options) {
  497. const element = findElement(elementOrId);
  498. const frame = document.createElement("iframe");
  499. frame.src = embedUrl(`/edit/${projectId}`, options);
  500. replaceAndEmbed(element, frame, options);
  501. return connect(frame);
  502. }
  503. function embedGithubProject(elementOrId, repoSlug, options) {
  504. const element = findElement(elementOrId);
  505. const frame = document.createElement("iframe");
  506. frame.src = embedUrl(`/github/${repoSlug}`, options);
  507. replaceAndEmbed(element, frame, options);
  508. return connect(frame);
  509. }
  510. var StackBlitzSDK = {
  511. connect,
  512. embedGithubProject,
  513. embedProject,
  514. embedProjectId,
  515. openGithubProject,
  516. openProject,
  517. openProjectId
  518. };
  519. export {
  520. StackBlitzSDK as default
  521. };
  522. //# sourceMappingURL=@stackblitz_sdk.js.map