diff --git a/js/package.json b/js/package.json
index de52d0d0f..8c741fa56 100644
--- a/js/package.json
+++ b/js/package.json
@@ -117,6 +117,7 @@
"react-hot-loader": "3.0.0-beta.6",
"react-intl-aggregate-webpack-plugin": "0.0.1",
"rucksack-css": "0.9.1",
+ "serviceworker-webpack-plugin": "0.1.7",
"sinon": "1.17.6",
"sinon-as-promised": "4.0.2",
"sinon-chai": "2.8.0",
@@ -156,6 +157,7 @@
"mobx-react-devtools": "4.2.10",
"moment": "2.17.0",
"phoneformat.js": "1.0.3",
+ "promise-worker": "1.1.1",
"push.js": "0.0.11",
"qs": "6.3.0",
"react": "15.4.1",
@@ -182,7 +184,6 @@
"valid-url": "1.0.9",
"validator": "6.2.0",
"web3": "0.17.0-beta",
- "whatwg-fetch": "2.0.1",
- "worker-loader": "0.7.1"
+ "whatwg-fetch": "2.0.1"
}
}
diff --git a/js/src/redux/providers/compilerActions.js b/js/src/redux/providers/compilerActions.js
index c3b3a9bdd..d638c03a2 100644
--- a/js/src/redux/providers/compilerActions.js
+++ b/js/src/redux/providers/compilerActions.js
@@ -14,7 +14,26 @@
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see .
-import CompilerWorker from 'worker-loader!./compilerWorker.js';
+import PromiseWorker from 'promise-worker';
+import runtime from 'serviceworker-webpack-plugin/lib/runtime';
+
+let workerRegistration;
+
+// Setup the Service Worker
+if ('serviceWorker' in navigator) {
+ workerRegistration = runtime
+ .register()
+ .then(() => navigator.serviceWorker.ready)
+ .then((registration) => {
+ const _worker = registration.active;
+ _worker.controller = registration.active;
+ const worker = new PromiseWorker(_worker);
+
+ return worker;
+ });
+} else {
+ workerRegistration = Promise.reject('Service Worker is not available in your browser.');
+}
export function setWorker (worker) {
return {
@@ -23,6 +42,13 @@ export function setWorker (worker) {
};
}
+export function setError (error) {
+ return {
+ type: 'setError',
+ error
+ };
+}
+
export function setupWorker () {
return (dispatch, getState) => {
const state = getState();
@@ -31,7 +57,13 @@ export function setupWorker () {
return;
}
- const worker = new CompilerWorker();
- dispatch(setWorker(worker));
+ workerRegistration
+ .then((worker) => {
+ dispatch(setWorker(worker));
+ })
+ .catch((error) => {
+ console.error('sw', error);
+ dispatch(setWorker(null));
+ });
};
}
diff --git a/js/src/redux/providers/compilerReducer.js b/js/src/redux/providers/compilerReducer.js
index 7163ac7a5..e23bf3b16 100644
--- a/js/src/redux/providers/compilerReducer.js
+++ b/js/src/redux/providers/compilerReducer.js
@@ -17,13 +17,18 @@
import { handleActions } from 'redux-actions';
const initialState = {
- worker: null
+ worker: undefined,
+ error: null
};
export default handleActions({
setWorker (state, action) {
const { worker } = action;
-
return Object.assign({}, state, { worker });
+ },
+
+ setError (state, action) {
+ const { error } = action;
+ return Object.assign({}, state, { error });
}
}, initialState);
diff --git a/js/src/redux/providers/compilerWorker.js b/js/src/redux/providers/compilerWorker.js
deleted file mode 100644
index 60a07355f..000000000
--- a/js/src/redux/providers/compilerWorker.js
+++ /dev/null
@@ -1,177 +0,0 @@
-// Copyright 2015, 2016 Parity Technologies (UK) Ltd.
-// This file is part of Parity.
-
-// Parity is free software: you can redistribute it and/or modify
-// it under the terms of the GNU General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-
-// Parity is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-// GNU General Public License for more details.
-
-// You should have received a copy of the GNU General Public License
-// along with Parity. If not, see .
-
-import solc from 'solc/browser-wrapper';
-import { isWebUri } from 'valid-url';
-
-self.solcVersions = {};
-self.files = {};
-self.lastCompile = {
- sourcecode: '',
- result: '',
- version: ''
-};
-
-// eslint-disable-next-line no-undef
-onmessage = (event) => {
- const message = JSON.parse(event.data);
-
- switch (message.action) {
- case 'compile':
- compile(message.data);
- break;
- case 'load':
- load(message.data);
- break;
- case 'setFiles':
- setFiles(message.data);
- break;
- case 'close':
- close();
- break;
- }
-};
-
-function setFiles (files) {
- const prevFiles = self.files;
- const nextFiles = files.reduce((obj, file) => {
- obj[file.name] = file.sourcecode;
- return obj;
- }, {});
-
- self.files = {
- ...prevFiles,
- ...nextFiles
- };
-}
-
-function findImports (path) {
- if (self.files[path]) {
- if (self.files[path].error) {
- return { error: self.files[path].error };
- }
-
- return { contents: self.files[path] };
- }
-
- if (isWebUri(path)) {
- console.log('[worker] fetching', path);
-
- fetch(path)
- .then((r) => r.text())
- .then((c) => {
- console.log('[worker]', 'got content at ' + path);
- self.files[path] = c;
-
- postMessage(JSON.stringify({
- event: 'try-again'
- }));
- })
- .catch((e) => {
- console.error('[worker]', 'fetching', path, e);
- self.files[path] = { error: e };
- });
-
- return { error: '__parity_tryAgain' };
- }
-
- console.log(`[worker] path ${path} not found...`);
- return { error: 'File not found' };
-}
-
-function compile (data, optimized = 1) {
- const { sourcecode, build } = data;
- const { longVersion } = build;
-
- if (self.lastCompile.sourcecode === sourcecode && self.lastCompile.longVersion === longVersion) {
- return postMessage(JSON.stringify({
- event: 'compiled',
- data: self.lastCompile.result
- }));
- }
-
- fetchSolc(build)
- .then((compiler) => {
- const input = {
- '': sourcecode
- };
-
- const compiled = compiler.compile({ sources: input }, optimized, findImports);
-
- self.lastCompile = {
- version: longVersion, result: compiled,
- sourcecode
- };
-
- postMessage(JSON.stringify({
- event: 'compiled',
- data: compiled
- }));
- });
-}
-
-function load (build) {
- postMessage(JSON.stringify({
- event: 'loading',
- data: true
- }));
-
- fetchSolc(build)
- .then(() => {
- postMessage(JSON.stringify({
- event: 'loading',
- data: false
- }));
- })
- .catch(() => {
- postMessage(JSON.stringify({
- event: 'loading',
- data: false
- }));
- });
-}
-
-function fetchSolc (build) {
- const { path, longVersion } = build;
-
- if (self.solcVersions[path]) {
- return Promise.resolve(self.solcVersions[path]);
- }
-
- const URL = `https://raw.githubusercontent.com/ethereum/solc-bin/gh-pages/bin/${path}`;
- console.log(`[worker] fetching solc-bin ${longVersion} at ${URL}`);
-
- return fetch(URL)
- .then((r) => r.text())
- .then((code) => {
- const solcCode = code.replace(/^var Module;/, 'var Module=self.__solcModule;');
- self.__solcModule = {};
-
- console.log(`[worker] evaluating ${longVersion}`);
-
- // eslint-disable-next-line no-eval
- eval(solcCode);
-
- console.log(`[worker] done evaluating ${longVersion}`);
-
- const compiler = solc(self.__solcModule);
- self.solcVersions[path] = compiler;
- return compiler;
- })
- .catch((e) => {
- console.error('fetching solc', e);
- });
-}
diff --git a/js/src/serviceWorker.js b/js/src/serviceWorker.js
new file mode 100644
index 000000000..c558a57cf
--- /dev/null
+++ b/js/src/serviceWorker.js
@@ -0,0 +1,144 @@
+// Copyright 2015, 2016 Parity Technologies (UK) Ltd.
+// This file is part of Parity.
+
+// Parity is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+
+// Parity is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+
+// You should have received a copy of the GNU General Public License
+// along with Parity. If not, see .
+
+import registerPromiseWorker from 'promise-worker/register';
+import SolidityUtils from '~/util/solidity';
+
+const CACHE_NAME = 'parity-cache-v1';
+
+registerPromiseWorker((msg) => {
+ return handleMessage(msg);
+});
+
+self.addEventListener('install', (event) => {
+ event.waitUntil(self.skipWaiting());
+});
+
+self.addEventListener('activate', (event) => {
+ event.waitUntil(self.clients.claim());
+});
+
+self.addEventListener('fetch', (event) => {
+ const { url } = event.request;
+
+ if (/raw.githubusercontent.com\/ethereum\/solc-bin(.+)list\.json$/.test(url)) {
+ // Return the cached version, but still update it in background
+ return event.respondWith(cachedFetcher(event.request, true));
+ }
+
+ if (/raw.githubusercontent.com\/ethereum\/solc-bin(.+)soljson(.+)\.js$/.test(url)) {
+ return event.respondWith(cachedFetcher(event.request));
+ }
+});
+
+self.solc = {};
+self.files = {};
+
+function cachedFetcher (request, update = false) {
+ return caches
+ .match(request)
+ .then((response) => {
+ // Return cached response if exists and no
+ // updates needed
+ if (response && !update) {
+ return response;
+ }
+
+ const fetcher = fetch(request.clone())
+ .then((response) => {
+ // Check if we received a valid response
+ if (!response || response.status !== 200) {
+ return response;
+ }
+
+ return caches
+ .open(CACHE_NAME)
+ .then((cache) => {
+ cache.put(request, response.clone());
+ return response;
+ });
+ });
+
+ // Cache hit - return response
+ // Still want to perform the fetch (update)
+ if (response) {
+ return response;
+ }
+
+ return fetcher;
+ });
+}
+
+function handleMessage (message) {
+ switch (message.action) {
+ case 'compile':
+ return compile(message.data);
+
+ case 'load':
+ return getCompiler(message.data).then(() => 'ok');
+
+ case 'setFiles':
+ return setFiles(message.data);
+
+ default:
+ console.warn(`unknown action "${message.action}"`);
+ return null;
+ }
+}
+
+function compile (data) {
+ const { build } = data;
+
+ return getCompiler(build)
+ .then((compiler) => {
+ return SolidityUtils.compile(data, compiler);
+ });
+}
+
+function setFiles (files) {
+ const prevFiles = self.files;
+ const nextFiles = files.reduce((obj, file) => {
+ obj[file.name] = file.sourcecode;
+ return obj;
+ }, {});
+
+ self.files = {
+ ...prevFiles,
+ ...nextFiles
+ };
+
+ return 'ok';
+}
+
+function getCompiler (build) {
+ const { longVersion } = build;
+
+ const fetcher = (url) => {
+ const request = new Request(url);
+ return cachedFetcher(request);
+ };
+
+ if (!self.solc[longVersion]) {
+ self.solc[longVersion] = SolidityUtils
+ .getCompiler(build, fetcher)
+ .then((compiler) => {
+ self.solc[longVersion] = compiler;
+ return compiler;
+ });
+ }
+
+ return self.solc[longVersion];
+}
diff --git a/js/src/util/solidity.js b/js/src/util/solidity.js
new file mode 100644
index 000000000..d4c9686d5
--- /dev/null
+++ b/js/src/util/solidity.js
@@ -0,0 +1,89 @@
+// Copyright 2015, 2016 Parity Technologies (UK) Ltd.
+// This file is part of Parity.
+
+// Parity is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+
+// Parity is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+
+// You should have received a copy of the GNU General Public License
+// along with Parity. If not, see .
+
+import solc from 'solc/browser-wrapper';
+
+export default class SolidityUtils {
+
+ static compile (data, compiler) {
+ const { sourcecode, build, optimize, files } = data;
+
+ const start = Date.now();
+ console.log('[solidity] compiling...');
+
+ const input = {
+ '': sourcecode
+ };
+
+ const findFiles = (path) => {
+ const file = files.find((f) => f.name === path);
+
+ if (file) {
+ return { contents: file.sourcecode };
+ } else {
+ return { error: 'File not found' };
+ }
+ };
+
+ const compiled = compiler.compile({ sources: input }, optimize ? 1 : 0, findFiles);
+
+ const time = Math.round((Date.now() - start) / 100) / 10;
+ console.log(`[solidity] done compiling in ${time}s`);
+
+ compiled.version = build.longVersion;
+ compiled.sourcecode = sourcecode;
+
+ return compiled;
+ }
+
+ static getCompiler (build, _fetcher) {
+ const { longVersion, path } = build;
+
+ const URL = `https://raw.githubusercontent.com/ethereum/solc-bin/gh-pages/bin/${path}`;
+
+ const fetcher = typeof _fetcher === 'function'
+ ? _fetcher
+ : (url) => fetch(url);
+
+ const isWorker = typeof window !== 'object';
+
+ return fetcher(URL)
+ .then((r) => r.text())
+ .then((code) => {
+ // `window` for main thread, `self` for workers
+ const _self = isWorker ? self : window;
+ _self.Module = {};
+
+ const solcCode = code.replace('var Module;', `var Module=${isWorker ? 'self' : 'window'}.Module;`);
+
+ console.log(`[solidity] evaluating ${longVersion}`);
+
+ try {
+ // eslint-disable-next-line no-eval
+ eval(solcCode);
+ } catch (e) {
+ return Promise.reject(e);
+ }
+
+ console.log(`[solidity] done evaluating ${longVersion}`);
+
+ const compiler = solc(_self.Module);
+ delete _self.Module;
+
+ return compiler;
+ });
+ }
+}
diff --git a/js/src/views/WriteContract/writeContract.css b/js/src/views/WriteContract/writeContract.css
index 2502c4060..c5cefcf7a 100644
--- a/js/src/views/WriteContract/writeContract.css
+++ b/js/src/views/WriteContract/writeContract.css
@@ -26,6 +26,16 @@
color: #ccc;
}
+.toggles {
+ display: flex;
+ flex-direction: row;
+ margin: 1em 0 0;
+
+ > * {
+ flex: 1;
+ }
+}
+
.container {
padding: 1em 0;
display: flex;
@@ -45,6 +55,14 @@
}
}
+.error {
+ background-color: rgba(200, 0, 0, 0.25);
+ padding: 1em 0.5em;
+ margin-top: -0.5em;
+ font-family: monospace;
+ font-size: 0.9em;
+}
+
.mainEditor {
&:global(.ace-solarized-dark) {
background-color: rgba(0, 0, 0, 0.5);
@@ -87,13 +105,13 @@
display: flex;
flex-direction: column;
margin-right: 0.5em;
-
.panel {
background-color: rgba(0, 0, 0, 0.5);
padding: 1em;
flex: 1;
display: flex;
flex-direction: column;
+ box-sizing: border-box;
}
.compilation {
diff --git a/js/src/views/WriteContract/writeContract.js b/js/src/views/WriteContract/writeContract.js
index 31c4dd244..c95c09c04 100644
--- a/js/src/views/WriteContract/writeContract.js
+++ b/js/src/views/WriteContract/writeContract.js
@@ -16,7 +16,7 @@
import React, { PropTypes, Component } from 'react';
import { observer } from 'mobx-react';
-import { MenuItem } from 'material-ui';
+import { MenuItem, Toggle } from 'material-ui';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import CircularProgress from 'material-ui/CircularProgress';
@@ -42,10 +42,11 @@ class WriteContract extends Component {
static propTypes = {
accounts: PropTypes.object.isRequired,
setupWorker: PropTypes.func.isRequired,
- worker: PropTypes.object
+ worker: PropTypes.object,
+ workerError: PropTypes.any
};
- store = new WriteContractStore();
+ store = WriteContractStore.get();
state = {
resizing: false,
@@ -56,23 +57,32 @@ class WriteContract extends Component {
const { setupWorker, worker } = this.props;
setupWorker();
- if (worker) {
- this.store.setCompiler(worker);
+ if (worker !== undefined) {
+ this.store.setWorker(worker);
}
}
componentDidMount () {
this.store.setEditor(this.refs.editor);
+ if (this.props.workerError) {
+ this.store.setWorkerError(this.props.workerError);
+ }
+
// Wait for editor to be loaded
window.setTimeout(() => {
this.store.resizeEditor();
}, 2000);
}
+ // Set the worker if not set before (eg. first page loading)
componentWillReceiveProps (nextProps) {
- if (!this.props.worker && nextProps.worker) {
- this.store.setCompiler(nextProps.worker);
+ if (this.props.worker === undefined && nextProps.worker !== undefined) {
+ this.store.setWorker(nextProps.worker);
+ }
+
+ if (this.props.workerError !== nextProps.workerError) {
+ this.store.setWorkerError(nextProps.workerError);
}
}
@@ -217,7 +227,18 @@ class WriteContract extends Component {
}
renderParameters () {
- const { compiling, contract, selectedBuild, loading } = this.store;
+ const { compiling, contract, selectedBuild, loading, workerError } = this.store;
+
+ if (workerError) {
+ return (
+
+
+
Unfortuantely, an error occurred...
+
{ workerError.toString() }
+
+
+ );
+ }
if (selectedBuild < 0) {
return (
@@ -262,6 +283,24 @@ class WriteContract extends Component {
: null
}
+
{ this.renderSolidityVersions() }
{ this.renderCompilation() }
@@ -485,8 +524,8 @@ class WriteContract extends Component {
function mapStateToProps (state) {
const { accounts } = state.personal;
- const { worker } = state.compiler;
- return { accounts, worker };
+ const { worker, error } = state.compiler;
+ return { accounts, worker, workerError: error };
}
function mapDispatchToProps (dispatch) {
diff --git a/js/src/views/WriteContract/writeContractStore.js b/js/src/views/WriteContract/writeContractStore.js
index dd1985466..141569af2 100644
--- a/js/src/views/WriteContract/writeContractStore.js
+++ b/js/src/views/WriteContract/writeContractStore.js
@@ -14,10 +14,13 @@
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see .
-import { action, observable } from 'mobx';
+import { action, observable, transaction } from 'mobx';
import store from 'store';
import { debounce } from 'lodash';
+import { sha3 } from '~/api/util/sha3';
+import SolidityUtils from '~/util/solidity';
+
const WRITE_CONTRACT_STORE_KEY = '_parity::writeContractStore';
const SNIPPETS = {
@@ -43,6 +46,8 @@ const SNIPPETS = {
}
};
+let instance = null;
+
export default class WriteContractStore {
@observable sourcecode = '';
@@ -61,6 +66,9 @@ export default class WriteContractStore {
@observable builds = [];
@observable selectedBuild = -1;
+ @observable autocompile = false;
+ @observable optimize = false;
+
@observable showDeployModal = false;
@observable showSaveModal = false;
@observable showLoadModal = false;
@@ -68,45 +76,55 @@ export default class WriteContractStore {
@observable savedContracts = {};
@observable selectedContract = {};
+ @observable workerError = null;
+
+ loadingSolidity = false;
+ lastCompilation = {};
snippets = SNIPPETS;
+ worker = undefined;
+
+ useWorker = true;
+ solc = {};
constructor () {
- this.reloadContracts();
- this.fetchSolidityVersions();
-
this.debouncedCompile = debounce(this.handleCompile, 1000);
}
+ static get () {
+ if (!instance) {
+ instance = new WriteContractStore();
+ }
+
+ return instance;
+ }
+
+ @action setWorkerError (error) {
+ this.workerError = error;
+ }
+
@action setEditor (editor) {
this.editor = editor;
}
- @action setCompiler (compiler) {
- this.compiler = compiler;
+ @action setWorker (worker) {
+ if (this.worker !== undefined) {
+ return;
+ }
- this.compiler.onmessage = (event) => {
- const message = JSON.parse(event.data);
+ this.worker = worker;
- switch (message.event) {
- case 'compiled':
- this.parseCompiled(message.data);
- break;
- case 'loading':
- this.parseLoading(message.data);
- break;
- case 'try-again':
- this.handleCompile();
- break;
- }
- };
+ this
+ .fetchSolidityVersions()
+ .then(() => this.reloadContracts());
}
fetchSolidityVersions () {
- fetch('https://raw.githubusercontent.com/ethereum/solc-bin/gh-pages/bin/list.json')
+ return fetch('https://raw.githubusercontent.com/ethereum/solc-bin/gh-pages/bin/list.json')
.then((r) => r.json())
.then((data) => {
const { builds, releases, latestRelease } = data;
let latestIndex = -1;
+ let promise = Promise.resolve();
this.builds = builds.reverse().map((build, index) => {
if (releases[build.version] === build.path) {
@@ -114,7 +132,7 @@ export default class WriteContractStore {
if (build.version === latestRelease) {
build.latest = true;
- this.loadSolidityVersion(build);
+ promise = promise.then(() => this.loadSolidityVersion(build));
latestIndex = index;
}
}
@@ -123,29 +141,93 @@ export default class WriteContractStore {
});
this.selectedBuild = latestIndex;
+ return promise;
+ })
+ .catch((error) => {
+ this.setWorkerError(error);
});
}
- @action closeWorker = () => {
- this.compiler.postMessage(JSON.stringify({
- action: 'close'
- }));
- }
-
@action handleImport = (sourcecode) => {
this.reloadContracts(-1, sourcecode);
}
@action handleSelectBuild = (_, index, value) => {
this.selectedBuild = value;
- this.loadSolidityVersion(this.builds[value]);
+ return this
+ .loadSolidityVersion(this.builds[value])
+ .then(() => this.handleCompile());
+ }
+
+ getCompiler (build) {
+ const { longVersion } = build;
+
+ if (!this.solc[longVersion]) {
+ this.solc[longVersion] = SolidityUtils
+ .getCompiler(build)
+ .then((compiler) => {
+ this.solc[longVersion] = compiler;
+ return compiler;
+ })
+ .catch((error) => {
+ this.setWorkerError(error);
+ });
+ }
+
+ return Promise.resolve(this.solc[longVersion]);
}
@action loadSolidityVersion = (build) => {
- this.compiler.postMessage(JSON.stringify({
- action: 'load',
- data: build
- }));
+ if (this.worker === undefined) {
+ return;
+ } else if (this.worker === null) {
+ this.useWorker = false;
+ }
+
+ if (this.loadingSolidity) {
+ return this.loadingSolidity;
+ }
+
+ this.loading = true;
+
+ if (this.useWorker) {
+ this.loadingSolidity = this.worker
+ .postMessage({
+ action: 'load',
+ data: build
+ })
+ .then((result) => {
+ if (result !== 'ok') {
+ throw new Error('error while loading solidity: ' + result);
+ }
+
+ this.loadingSolidity = false;
+ this.loading = false;
+ })
+ .catch((error) => {
+ console.warn('error while loading solidity', error);
+ this.useWorker = false;
+ this.loadingSolidity = null;
+
+ return this.loadSolidityVersion(build);
+ });
+ } else {
+ this.loadingSolidity = this
+ .getCompiler(build)
+ .then(() => {
+ this.loadingSolidity = false;
+ this.loading = false;
+
+ return 'ok';
+ })
+ .catch((error) => {
+ this.setWorkerError(error);
+ this.loadingSolidity = false;
+ this.loading = false;
+ });
+ }
+
+ return this.loadingSolidity;
}
@action handleOpenDeployModal = () => {
@@ -177,23 +259,120 @@ export default class WriteContractStore {
this.contract = this.contracts[Object.keys(this.contracts)[value]];
}
+ compile = (data) => {
+ if (this.useWorker) {
+ return this.worker.postMessage({
+ action: 'compile',
+ data
+ });
+ }
+
+ return new Promise((resolve, reject) => {
+ window.setTimeout(() => {
+ this
+ .getCompiler(data.build)
+ .then((compiler) => {
+ return SolidityUtils.compile(data, compiler);
+ })
+ .then(resolve)
+ .catch(reject);
+ }, 0);
+ });
+ }
+
@action handleCompile = () => {
- this.compiled = false;
- this.compiling = true;
+ transaction(() => {
+ this.compiled = false;
+ this.compiling = true;
+ });
const build = this.builds[this.selectedBuild];
+ const version = build.longVersion;
+ const sourcecode = this.sourcecode.replace(/\n+/g, '\n').replace(/\s(\s+)/g, ' ');
+ const hash = sha3(JSON.stringify({ version, sourcecode, optimize: this.optimize }));
- if (this.compiler && typeof this.compiler.postMessage === 'function') {
- this.sendFilesToWorker();
+ let promise = Promise.resolve(null);
- this.compiler.postMessage(JSON.stringify({
- action: 'compile',
- data: {
- sourcecode: this.sourcecode,
- build: build
- }
- }));
+ if (hash === this.lastCompilation.hash) {
+ promise = new Promise((resolve) => {
+ window.setTimeout(() => {
+ resolve(this.lastCompilation);
+ }, 500);
+ });
+ } else {
+ promise = this
+ .compile({
+ sourcecode: sourcecode,
+ build: build,
+ optimize: this.optimize,
+ files: this.files
+ })
+ .then((data) => {
+ const result = this.parseCompiled(data);
+
+ this.lastCompilation = {
+ result: result,
+ date: new Date(),
+ version: data.version,
+ hash
+ };
+
+ return this.lastCompilation;
+ })
+ .catch((error) => {
+ this.setWorkerError(error);
+ });
}
+
+ return promise.then((data = null) => {
+ if (data) {
+ const {
+ contract, contractIndex,
+ annotations, contracts, errors
+ } = data.result;
+
+ this.contract = contract;
+ this.contractIndex = contractIndex;
+
+ this.annotations = annotations;
+ this.contracts = contracts;
+ this.errors = errors;
+ }
+
+ this.compiled = true;
+ this.compiling = false;
+ });
+ }
+
+ @action handleAutocompileToggle = () => {
+ this.autocompile = !this.autocompile;
+ }
+
+ @action handleOptimizeToggle = () => {
+ this.optimize = !this.optimize;
+ }
+
+ @action parseCompiled = (data) => {
+ const { contracts } = data;
+
+ const { errors = [] } = data;
+ const errorAnnotations = this.parseErrors(errors);
+ const formalAnnotations = this.parseErrors(data.formal && data.formal.errors, true);
+
+ const annotations = [].concat(
+ errorAnnotations,
+ formalAnnotations
+ );
+
+ const contractKeys = Object.keys(contracts || {});
+
+ const contract = contractKeys.length ? contracts[contractKeys[0]] : null;
+ const contractIndex = contractKeys.length ? 0 : -1;
+
+ return {
+ contract, contractIndex,
+ contracts, errors, annotations
+ };
}
parseErrors = (data, formal = false) => {
@@ -220,43 +399,6 @@ export default class WriteContractStore {
});
}
- @action parseCompiled = (data) => {
- const { contracts } = data;
-
- const { errors = [] } = data;
- const errorAnnotations = this.parseErrors(errors);
- const formalAnnotations = this.parseErrors(data.formal && data.formal.errors, true);
-
- const annotations = [].concat(
- errorAnnotations,
- formalAnnotations
- );
-
- if (annotations.findIndex((a) => /__parity_tryAgain/.test(a.text)) > -1) {
- return;
- }
-
- const contractKeys = Object.keys(contracts || {});
-
- this.contract = contractKeys.length ? contracts[contractKeys[0]] : null;
- this.contractIndex = contractKeys.length ? 0 : -1;
-
- this.contracts = contracts;
- this.errors = errors;
- this.annotations = annotations;
-
- this.compiled = true;
- this.compiling = false;
- }
-
- @action parseLoading = (isLoading) => {
- this.loading = isLoading;
-
- if (!isLoading) {
- this.handleCompile();
- }
- }
-
@action handleEditSourcecode = (value, compile = false) => {
this.sourcecode = value;
@@ -268,7 +410,7 @@ export default class WriteContractStore {
if (compile) {
this.handleCompile();
- } else {
+ } else if (this.autocompile) {
this.debouncedCompile();
}
}
@@ -327,8 +469,9 @@ export default class WriteContractStore {
current: this.sourcecode
});
- this.handleCompile();
this.resizeEditor();
+
+ return this.handleCompile();
}
@action handleLoadContract = (contract) => {
@@ -363,16 +506,13 @@ export default class WriteContractStore {
} catch (e) {}
}
- sendFilesToWorker = () => {
+ get files () {
const files = [].concat(
Object.values(this.snippets),
Object.values(this.savedContracts)
);
- this.compiler.postMessage(JSON.stringify({
- action: 'setFiles',
- data: files
- }));
+ return files;
}
}
diff --git a/js/webpack/app.js b/js/webpack/app.js
index cf38ec99c..df801533c 100644
--- a/js/webpack/app.js
+++ b/js/webpack/app.js
@@ -22,6 +22,7 @@ const WebpackErrorNotificationPlugin = require('webpack-error-notification');
const CopyWebpackPlugin = require('copy-webpack-plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const ExtractTextPlugin = require('extract-text-webpack-plugin');
+const ServiceWorkerWebpackPlugin = require('serviceworker-webpack-plugin');
const Shared = require('./shared');
const DAPPS = require('../src/dapps');
@@ -50,7 +51,7 @@ module.exports = {
rules: [
{
test: /\.js$/,
- exclude: /node_modules/,
+ exclude: /(node_modules)/,
// use: [ 'happypack/loader?id=js' ]
use: isProd ? ['babel-loader'] : [
'babel-loader?cacheDirectory=true'
@@ -136,7 +137,18 @@ module.exports = {
},
plugins: (function () {
- const plugins = Shared.getPlugins().concat([
+ const DappsHTMLInjection = DAPPS.map((dapp) => {
+ return new HtmlWebpackPlugin({
+ title: dapp.title,
+ filename: dapp.name + '.html',
+ template: './dapps/index.ejs',
+ favicon: FAVICON,
+ secure: dapp.secure,
+ chunks: [ isProd ? null : 'commons', dapp.name ]
+ });
+ });
+
+ const plugins = Shared.getPlugins().concat(
new CopyWebpackPlugin([{ from: './error_pages.css', to: 'styles.css' }], {}),
new WebpackErrorNotificationPlugin(),
@@ -151,17 +163,14 @@ module.exports = {
template: './index.ejs',
favicon: FAVICON,
chunks: [ isProd ? null : 'commons', 'index' ]
- })
- ], DAPPS.map((dapp) => {
- return new HtmlWebpackPlugin({
- title: dapp.title,
- filename: dapp.name + '.html',
- template: './dapps/index.ejs',
- favicon: FAVICON,
- secure: dapp.secure,
- chunks: [ isProd ? null : 'commons', dapp.name ]
- });
- }));
+ }),
+
+ new ServiceWorkerWebpackPlugin({
+ entry: path.join(__dirname, '../src/serviceWorker.js')
+ }),
+
+ DappsHTMLInjection
+ );
if (!isProd) {
const DEST_I18N = path.join(__dirname, '..', DEST, 'i18n');