diff --git a/src/CodeMirror.js b/src/CodeMirror.js
index 0dfe9f7..8726047 100644
--- a/src/CodeMirror.js
+++ b/src/CodeMirror.js
@@ -8,6 +8,7 @@ require('./paraiso-light.css');
require('codemirror/mode/xml/xml');
require('codemirror/mode/javascript/javascript');
require('codemirror/keymap/sublime');
+require('codemirror/addon/display/placeholder');
const DEFAULT_CODE_MIRROR_OPTIONS = {
autoCloseBrackets: true,
diff --git a/src/Repl.js b/src/Repl.js
index 143525b..98a3775 100644
--- a/src/Repl.js
+++ b/src/Repl.js
@@ -2,24 +2,27 @@ import React, { Component } from 'react';
import { debounce, cloneDeep } from 'lodash-es';
import CodeMirrorPanel from './CodeMirrorPanel';
-import { getCodeSizeInBytes } from './lib/helpers';
+import { getCodeSizeInBytes, loadState, saveState } from './lib/helpers';
import terserOptions, { evalOptions } from './lib/terser-options';
import styles from './Repl.module.css';
const DEBOUNCE_DELAY = 500;
+const defaultState = {
+ optionsCode: terserOptions,
+ code: '',
+ minified: '',
+ terserOptions: evalOptions(),
+ rawSize: 0,
+ minifiedSize: 0,
+};
+
class Repl extends Component {
- state = {
- optionsCode: terserOptions,
- code: '// write or paste code here\n\n',
- minified: "// terser's output will be shown here",
- terserOptions: evalOptions(),
- rawSize: 0,
- minifiedSize: 0
- };
+ state = loadState() || defaultState;
- options = {
+ _minifyId = 0;
+ _options = {
lineWrapping: true,
fileSize: true
};
@@ -37,26 +40,26 @@ class Repl extends Component {
options={{ lineWrapping: true }}
theme="paraiso-light"
errorMessage={this.state.optionsErrorMessage}
- placeholder="Edit terser config here"
+ placeholder="// Edit terser config here"
/>
@@ -84,21 +87,20 @@ class Repl extends Component {
this.setState({ optionsErrorMessage: e.message });
}
- this._minify(this.state.code);
+ this._minifyToState(this.state.code);
};
- _minifyToState = debounce(
- code => this._minify(code, this._persistState),
- DEBOUNCE_DELAY
- );
+ _minifyToState = debounce(code => this._minify(code), DEBOUNCE_DELAY);
_minify = async (code, setStateCallback) => {
// we need to clone this because terser mutates the options object :(
const terserOpts = cloneDeep(this.state.terserOptions);
+ const minifyId = ++this._minifyId;
// TODO: put this in a worker to avoid blocking the UI on heavy content
try {
const result = await this.props.terser.minify(code, terserOpts);
+ if (this._minifyId !== minifyId) return;
if (result.error) {
this.setState({ errorMessage: result.error.message });
@@ -108,6 +110,7 @@ class Repl extends Component {
minifiedSize: getCodeSizeInBytes(result.code),
errorMessage: null
});
+ saveState(this.state);
}
} catch (e) {
this.setState({ errorMessage: e.message });
diff --git a/src/lib/helpers.js b/src/lib/helpers.js
index 863aae2..584ebfd 100644
--- a/src/lib/helpers.js
+++ b/src/lib/helpers.js
@@ -1,3 +1,34 @@
export const getCodeSizeInBytes = code => {
return new Blob([code], { type: 'text/plain' }).size;
};
+
+const base64UrlDecodeChars = {'-': '+', '_': '/', '.': '='};
+const base64UrlDecode = input => {
+ try {
+ return atob(input.replace(/[-_.]/g, c => base64UrlDecodeChars[c]));
+ } catch {
+ return null;
+ }
+}
+
+const base64UrlEncodeChars = {'+': '-', '/': '_', '=': '.'};
+const base64UrlEncode = input => {
+ try {
+ return btoa(input).replace(/[+/=]/g, c => base64UrlEncodeChars[c]);
+ } catch {
+ return null;
+ }
+}
+
+export const saveState = state => {
+ const base64 = base64UrlEncode(JSON.stringify(state));
+ window.history.replaceState(state, '', `#${base64}`);
+};
+
+export const loadState = () => {
+ try {
+ const base64 = window.location.hash.slice(1);
+ return JSON.parse(base64UrlDecode(base64));
+ } catch {}
+ return null;
+}
diff --git a/src/lib/terser-options.js b/src/lib/terser-options.js
index 5bc4464..29750de 100644
--- a/src/lib/terser-options.js
+++ b/src/lib/terser-options.js
@@ -141,7 +141,15 @@ const options = `// edit terser options
rename: {},
}`;
-/* eslint-disable-next-line no-eval */
-export const evalOptions = (opts) => eval(`(${opts||options})`)
+export const evalOptions = (opts) => {
+ opts = opts || options;
+ // Strip line comments
+ opts = opts.replace(/\/\/.*/g, '');
+ // Trim trailing commas
+ opts = opts.replace(/,\s*([\]}])/g, '$1');
+ // Quote property names
+ opts = opts.replace(/^\s*(\w+):/gm, '"$1":');
+ return JSON.parse(opts);
+}
export default options;