Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

sdk: export more types, add showSidebar option and vm.editor.setCurrentFile #1837

Merged
merged 9 commits into from
Apr 18, 2022
4 changes: 2 additions & 2 deletions sdk/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion sdk/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@stackblitz/sdk",
"version": "1.7.0-alpha.2",
"version": "1.7.0-alpha.3",
"description": "SDK for generating and embedding StackBlitz projects.",
"main": "./bundles/sdk.js",
"module": "./bundles/sdk.m.js",
Expand Down
57 changes: 0 additions & 57 deletions sdk/src/RDC.ts

This file was deleted.

27 changes: 12 additions & 15 deletions sdk/src/connection.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { VM } from './VM';
import { connectInterval, connectMaxAttempts } from './constants';
import { genID } from './helpers';
import { VM } from './vm';

const connections: Connection[] = [];

Expand All @@ -22,15 +23,13 @@ export class Connection {
};

const pingFrame = () => {
// Ping the Iframe.
this.element.contentWindow &&
this.element.contentWindow.postMessage(
{
action: 'SDK_INIT',
id: this.id,
},
'*'
);
this.element.contentWindow?.postMessage(
{
action: 'SDK_INIT',
id: this.id,
},
'*'
);
};

// Remove the listener and interval.
Expand All @@ -43,9 +42,7 @@ export class Connection {
window.addEventListener('message', listenForSuccess);
// Then, lets immediately ping the frame.
pingFrame();
// Every 500ms thereafter we'll ping until we get a response or timeout.
// Keep track of the current try # and the max #
const maxAttempts = 20;
// Keep track of the current try number
let attempts = 0;
const interval = window.setInterval(() => {
// If the VM connection is open, cleanup and return
Expand All @@ -56,7 +53,7 @@ export class Connection {
}

// If we've exceeded the max retries, fail this promise.
if (attempts >= maxAttempts) {
if (attempts >= connectMaxAttempts) {
cleanup();
reject('Timeout: Unable to establish a connection with the StackBlitz VM');
// Remove the (now) failed connection from the connections array
Expand All @@ -70,7 +67,7 @@ export class Connection {

attempts++;
pingFrame();
}, 500);
}, connectInterval);
});

connections.push(this);
Expand Down
33 changes: 33 additions & 0 deletions sdk/src/constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
/**
* Number of milliseconds between attempts to get a response from an embedded frame
*/
export const connectInterval = 500;

/**
* How many times should we try to get an init response from an embedded frame
*/
export const connectMaxAttempts = 20;

/**
* Default height attribute for iframes
*/
export const defaultFrameHeight = 300;

/**
* Origin of the StackBlitz instance
*/
export const defaultOrigin = 'https://stackblitz.com';

/**
* List of supported template names.
*/
export const projectTemplates = [
'angular-cli',
'create-react-app',
'html',
'javascript',
'node',
'polymer',
'typescript',
'vue',
] as const;
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This array is used to warn users when they use an unsupported template type (with a console.warn), and is also used as the source of truth for the ProjectTemplate type:

type ProjectTemplate = typeof projectTemplates[number]; // 'angular-cli' | 'create-react-app' | 'html' | …

19 changes: 5 additions & 14 deletions sdk/src/generate.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,7 @@
import { Project, EmbedOptions, OpenOptions } from './interfaces';
import type { Project, EmbedOptions, OpenOptions } from './interfaces';
import { projectTemplates } from './constants';
import { embedUrl, openTarget, openUrl } from './helpers';

const SUPPORTED_TEMPLATES: Project['template'][] = [
'angular-cli',
'create-react-app',
'html',
'javascript',
'node',
'polymer',
'typescript',
'vue',
];

function createHiddenInput(name: string, value: string) {
const input = document.createElement('input');
input.type = 'hidden';
Expand All @@ -21,8 +11,9 @@ function createHiddenInput(name: string, value: string) {
}

function createProjectForm(project: Project) {
if (!SUPPORTED_TEMPLATES.includes(project.template)) {
console.warn(`Unsupported project.template: must be one of ${SUPPORTED_TEMPLATES.join(', ')}`);
if (!projectTemplates.includes(project.template)) {
const names = projectTemplates.map((t) => `'${t}'`).join(', ');
console.warn(`Unsupported project.template: must be one of ${names}`);
}

const isWebContainers = project.template === 'node';
Expand Down
65 changes: 8 additions & 57 deletions sdk/src/helpers.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { EmbedOptions, OpenOptions } from './interfaces';

const DEFAULT_ORIGIN = 'https://stackblitz.com';
const DEFAULT_FRAME_HEIGHT = '300';
import type { EmbedOptions, OpenOptions } from './interfaces';
import { defaultFrameHeight, defaultOrigin } from './constants';
import { buildParams } from './params';

/**
* Pseudo-random id string for internal accounting.
Expand All @@ -12,7 +11,7 @@ export function genID() {
}

export function openUrl(route: string, options?: OpenOptions) {
return `${getOrigin(options)}${route}${buildProjectQuery(options)}`;
return `${getOrigin(options)}${route}${buildParams(options)}`;
}

export function embedUrl(route: string, options?: EmbedOptions) {
Expand All @@ -22,62 +21,14 @@ export function embedUrl(route: string, options?: EmbedOptions) {
if (options && typeof options === 'object') {
Object.assign(config, options);
}
return `${getOrigin(config)}${route}${buildProjectQuery(config)}`;
return `${getOrigin(config)}${route}${buildParams(config)}`;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for refactoring this!

}

function getOrigin(options: OpenOptions | EmbedOptions = {}) {
function getOrigin(options: OpenOptions & EmbedOptions = {}) {
if (typeof options.origin === 'string') {
return options.origin;
}
return DEFAULT_ORIGIN;
}

function buildProjectQuery(options: OpenOptions | EmbedOptions = {}) {
const params: string[] = [];

if (options.forceEmbedLayout) {
params.push('embed=1');
}

if (options.clickToLoad) {
params.push('ctl=1');
}

for (const file of Array.isArray(options.openFile) ? options.openFile : [options.openFile]) {
if (typeof file === 'string' && file.trim() !== '') {
params.push(`file=${encodeURIComponent(file.trim())}`);
}
}

if (options.view === 'preview' || options.view === 'editor') {
params.push(`view=${options.view}`);
}

if (options.theme === 'light' || options.theme === 'dark') {
params.push(`theme=${options.theme}`);
}

if (options.hideExplorer) {
params.push('hideExplorer=1');
}

if (options.hideNavigation) {
params.push('hideNavigation=1');
}

if (options.hideDevTools) {
params.push('hideDevTools=1');
}

if (
typeof options.devToolsHeight === 'number' &&
options.devToolsHeight >= 0 &&
options.devToolsHeight <= 100
) {
params.push(`devToolsHeight=${Math.round(options.devToolsHeight)}`);
}

return params.length ? `?${params.join('&')}` : '';
return defaultOrigin;
}

export function replaceAndEmbed(
Expand Down Expand Up @@ -122,7 +73,7 @@ function setFrameDimensions(frame: HTMLIFrameElement, options?: EmbedOptions) {
}

if (!frame.height) {
frame.height = DEFAULT_FRAME_HEIGHT;
frame.height = `${defaultFrameHeight}`;
}
if (!frame.width) {
frame.setAttribute('style', 'width:100%;');
Expand Down
43 changes: 39 additions & 4 deletions sdk/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,40 @@
export type { Project, OpenOptions, EmbedOptions } from './interfaces';
export type { VM } from './VM';
import {
connect,
embedGithubProject,
embedProject,
embedProjectId,
openGithubProject,
openProject,
openProjectId,
} from './lib';

import * as StackBlitzSDK from './lib';
export default StackBlitzSDK;
// Explicitly export public types (vs using `export * from './interfaces'`),
// so that additions to interfaces don't become a part of our public API by mistake.
export type {
Project,
ProjectOptions,
ProjectDependencies,
ProjectFiles,
ProjectSettings,
ProjectTemplate,
EmbedOptions,
OpenOptions,
OpenFileOption,
UiThemeOption,
UiViewOption,
} from './interfaces';
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

New typing feature: exporting more types from src/interfaces.ts

export type { FsDiff, VM } from './vm';

// Export a single object with methods, for compatibility with UMD and CommonJS.
// Ideally we would also have named exports, but that can create incompatibilities
// with some bundlers, and microbundle doesn't support it:
// https://github.com/developit/microbundle/issues/712
export default {
connect,
embedGithubProject,
embedProject,
embedProjectId,
openGithubProject,
openProject,
openProjectId,
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is technically the same as what we had before with

import * as StackBlitzSDK from './lib';
export default StackBlitzSDK;

EXCEPT that if we add a new export to src/lib.ts, it won't automatically end up as part of our public API.
This forces us to be more explicit and import-then-export it from src/index.ts. Maybe a bit verbose, but more intentional and thus safer.

};
Loading