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

feat: add download REPL button #2576

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
51 changes: 51 additions & 0 deletions js/repl/Repl.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,12 @@ import "regenerator-runtime/runtime";
import { cx, css } from "emotion";
import debounce from "lodash.debounce";
import React from "react";
import JSZip from "jszip";
import { prettySize, compareVersions } from "./Utils";
import ErrorBoundary from "./ErrorBoundary";
import CodeMirrorPanel from "./CodeMirrorPanel";
import ReplOptions from "./ReplOptions";
import baseBabelConfig from "./baseBabelConfig";
import StorageService from "./StorageService";
import UriUtils from "./UriUtils";
import loadBundle from "./loadBundle";
Expand Down Expand Up @@ -244,6 +246,7 @@ class Repl extends React.Component<Props, State> {
showOfficialExternalPlugins={state.showOfficialExternalPlugins}
loadingExternalPlugins={state.loadingExternalPlugins}
onAssumptionsChange={this._onAssumptionsChange}
onDownloadRepl={this._onDownloadRepl}
/>
<div className={styles.wrapperPanels}>
<div
Expand Down Expand Up @@ -504,6 +507,54 @@ class Repl extends React.Component<Props, State> {
});
};

_onDownloadRepl = (e: SyntheticEvent<*>) => {
const {
externalPlugins,
sourceType,
envConfig,
presets,
runtimePolyfillState,
} = this.state;
const zip = new JSZip();
const folder = zip.folder("my-babel-repl");

const packageJson = {
name: "my-babel-repl",
private: true,
version: "1.0.0",
main: "lib/index.js",
scripts: {
babel: "babel src --out-dir lib",
},
devDependencies: externalPlugins.reduce((prev: Object, plugin) => {
prev[plugin.name] = plugin.version;
return prev;
}, {}),
babel: baseBabelConfig({
sourceType,
envConfig,
presets,
plugins: externalPlugins.map(plugin => plugin.name),
sourceMap: runtimePolyfillState.isEnabled,
}),
};

packageJson.devDependencies["@babel/core"] = "latest";
Copy link
Member Author

Choose a reason for hiding this comment

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

It is using the latest core version but should use the last one available for the selected minor version. Still needs some logic for this.

packageJson.devDependencies["@babel/cli"] = "latest";
folder.file("package.json", JSON.stringify(packageJson));

const src = folder.folder("src");
src.file("index.js", this.state.code);
zip.generateAsync({ type: "blob" }).then(v => {
const anchor = document.createElement("a");
anchor.href = URL.createObjectURL(v);
anchor.download = "my-babel-repl.zip";
anchor.click();
document.removeChild(anchor);
});
e.preventDefault();
};

// Debounce compilation since it's expensive.
// This also avoids prematurely warning the user about invalid syntax,
// eg when in the middle of typing a variable name.
Expand Down
33 changes: 26 additions & 7 deletions js/repl/ReplOptions.js
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,7 @@ type Props = {
runtimePolyfillState: PluginState,
loadingExternalPlugins: boolean,
onAssumptionsChange: AssumptionsChange,
onDownloadRepl: (e: SyntheticEvent<*>) => void,
};

type LinkProps = {
Expand Down Expand Up @@ -769,9 +770,20 @@ class ExpandedContainer extends Component<Props, State> {
/>
</div>
</div>
{babelVersion && (
<div className={styles.versionRow} title={`v${babelVersion}`}>
<select value={babelVersion} onChange={onVersionChange}>
<div className={styles.versionRow}>
<button
onClick={this.props.onDownloadRepl}
title="Download REPL"
className={styles.downloadReplButton}
>
Download REPL
</button>
{babelVersion && (
<select
title={`v${babelVersion}`}
value={babelVersion}
onChange={onVersionChange}
>
<option value="">v{babelVersion}</option>
<option value="latest">Latest</option>
{pastVersions.map(
Expand All @@ -783,8 +795,8 @@ class ExpandedContainer extends Component<Props, State> {
)
)}
</select>
</div>
)}
)}
</div>

<div
className={`${styles.closeButton} ${nestedCloseButton}`}
Expand Down Expand Up @@ -1232,9 +1244,9 @@ const styles = {
display: "flex",
fontFamily: "monospace",
fontSize: "0.75rem",
justifyContent: "flex-end",
justifyContent: "space-between",
overflow: "hidden",
padding: "0 1.5rem",
padding: "0.5rem 1.5rem",
textOverflow: "ellipsis",
whiteSpace: "nowrap",

Expand All @@ -1248,6 +1260,13 @@ const styles = {
padding: "0.625rem 0.9375rem",
},
}),
downloadReplButton: css({
backgroundColor: colors.selectBackground,
borderRadius: "4px",
border: 0,
color: colors.inverseForegroundLight,
cursor: "pointer",
}),
checkboxOfficial: css({
marginTop: 10,
marginBottom: 10,
Expand Down
100 changes: 100 additions & 0 deletions js/repl/baseBabelConfig.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
import { compareVersions } from "./Utils";
import type { CompileConfig } from "./types";

declare var Babel: any;

export default function baseBabelConfig(config: CompileConfig) {
const { envConfig, presetsOptions } = config;

let useBuiltIns = false;
let spec = false;
let loose = false;
let bugfixes = false;
let corejs = "3.6";

let presetEnvOptions = {};

if (envConfig && envConfig.isEnvPresetEnabled) {
const targets = {};
const { forceAllTransforms, shippedProposals } = envConfig;

if (envConfig.browsers) {
targets.browsers = envConfig.browsers
.split(",")
.map(value => value.trim())
.filter(value => value);
}
if (envConfig.isElectronEnabled) {
targets.electron = envConfig.electron;
}
if (envConfig.isBuiltInsEnabled) {
useBuiltIns = !config.evaluate && envConfig.builtIns;
if (envConfig.corejs) {
corejs = envConfig.corejs;
}
}
if (envConfig.isNodeEnabled) {
targets.node = envConfig.node;
}
if (envConfig.isSpecEnabled) {
spec = envConfig.isSpecEnabled;
}
if (envConfig.isLooseEnabled) {
loose = envConfig.isLooseEnabled;
}
if (envConfig.isBugfixesEnabled) {
bugfixes = envConfig.isBugfixesEnabled;
}

presetEnvOptions = {
targets,
forceAllTransforms,
shippedProposals,
useBuiltIns,
corejs,
spec,
loose,
};
if (Babel.version && compareVersions(Babel.version, "7.9.0") !== -1) {
(presetEnvOptions: any).bugfixes = bugfixes;
}
}

return {
sourceMap: config.sourceMap,
assumptions: envConfig?.assumptions ?? {},

presets: config.presets.map(preset => {
if (typeof preset !== "string") return preset;
if (preset === "env") {
return ["env", presetEnvOptions];
}
if (/^stage-[0-2]$/.test(preset)) {
const decoratorsLegacy = presetsOptions.decoratorsLegacy;
const decoratorsBeforeExport = decoratorsLegacy
? undefined
: presetsOptions.decoratorsBeforeExport;

return [
preset,
{
decoratorsLegacy,
decoratorsBeforeExport,
pipelineProposal: presetsOptions.pipelineProposal,
},
];
}
if (preset === "react") {
return [
"react",
{
runtime: presetsOptions.reactRuntime,
},
];
}
return preset;
}),
plugins: config.plugins,
sourceType: config.sourceType,
};
}
107 changes: 7 additions & 100 deletions js/repl/compile.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
// @flow

// Globals pre-loaded by Worker
import { compareVersions } from "./Utils";

import baseBabelConfig from "./baseBabelConfig";
declare var Babel: any;
declare var prettier: any;
declare var prettierPlugins: any;
Expand Down Expand Up @@ -51,115 +50,23 @@ function guessFileExtension(presets: BabelPresets): SupportedFileExtension {
}

export default function compile(code: string, config: CompileConfig): Return {
const { envConfig, presetsOptions } = config;

let compiled = null;
let compileErrorMessage = null;
let envPresetDebugInfo = null;
let sourceMap = null;
let useBuiltIns = false;
let spec = false;
let loose = false;
let bugfixes = false;
let corejs = "3.6";
const transitions = new Transitions();
const meta = {
compiledSize: 0,
rawSize: new Blob([code], { type: "text/plain" }).size,
};

let presetEnvOptions = {};

if (envConfig && envConfig.isEnvPresetEnabled) {
const targets = {};
const { forceAllTransforms, shippedProposals } = envConfig;

if (envConfig.browsers) {
targets.browsers = envConfig.browsers
.split(",")
.map(value => value.trim())
.filter(value => value);
}
if (envConfig.isElectronEnabled) {
targets.electron = envConfig.electron;
}
if (envConfig.isBuiltInsEnabled) {
useBuiltIns = !config.evaluate && envConfig.builtIns;
if (envConfig.corejs) {
corejs = envConfig.corejs;
}
}
if (envConfig.isNodeEnabled) {
targets.node = envConfig.node;
}
if (envConfig.isSpecEnabled) {
spec = envConfig.isSpecEnabled;
}
if (envConfig.isLooseEnabled) {
loose = envConfig.isLooseEnabled;
}
if (envConfig.isBugfixesEnabled) {
bugfixes = envConfig.isBugfixesEnabled;
}

presetEnvOptions = {
targets,
forceAllTransforms,
shippedProposals,
useBuiltIns,
corejs,
spec,
loose,
};
if (Babel.version && compareVersions(Babel.version, "7.9.0") !== -1) {
(presetEnvOptions: any).bugfixes = bugfixes;
}
}
const babelConfig = baseBabelConfig(config);
babelConfig.babelrc = false;
babelConfig.filename = "repl" + guessFileExtension(config.presets);
babelConfig.wrapPluginVisitorMethod = config.getTransitions
? transitions.wrapPluginVisitorMethod
: undefined;

try {
const babelConfig = {
babelrc: false,
filename: "repl" + guessFileExtension(config.presets),
sourceMap: config.sourceMap,
assumptions: envConfig?.assumptions ?? {},

presets: config.presets.map(preset => {
if (typeof preset !== "string") return preset;
if (preset === "env") {
return ["env", presetEnvOptions];
}
if (/^stage-[0-2]$/.test(preset)) {
const decoratorsLegacy = presetsOptions.decoratorsLegacy;
const decoratorsBeforeExport = decoratorsLegacy
? undefined
: presetsOptions.decoratorsBeforeExport;

return [
preset,
{
decoratorsLegacy,
decoratorsBeforeExport,
pipelineProposal: presetsOptions.pipelineProposal,
},
];
}
if (preset === "react") {
return [
"react",
{
runtime: presetsOptions.reactRuntime,
},
];
}
return preset;
}),
plugins: config.plugins,
sourceType: config.sourceType,
wrapPluginVisitorMethod: config.getTransitions
? transitions.wrapPluginVisitorMethod
: undefined,
};

const transformed = Babel.transform(code, babelConfig);
compiled = transformed.code;

Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@
"codemirror": "5.56.0",
"core-js": "^3.0.1",
"emotion": "^9.1.3",
"jszip": "^3.7.1",
"lodash.camelcase": "^4.3.0",
"lodash.debounce": "^4.0.8",
"lz-string": "^1.4.4",
Expand Down