diff --git a/js/src/redux/providers/compilerActions.js b/js/src/redux/providers/compilerActions.js
index 9c4eb9535..d638c03a2 100644
--- a/js/src/redux/providers/compilerActions.js
+++ b/js/src/redux/providers/compilerActions.js
@@ -23,13 +23,8 @@ let workerRegistration;
if ('serviceWorker' in navigator) {
workerRegistration = runtime
.register()
- .then(() => {
- console.log('registering service worker');
- return navigator.serviceWorker.ready;
- })
+ .then(() => navigator.serviceWorker.ready)
.then((registration) => {
- console.log('registered service worker');
-
const _worker = registration.active;
_worker.controller = registration.active;
const worker = new PromiseWorker(_worker);
@@ -68,7 +63,7 @@ export function setupWorker () {
})
.catch((error) => {
console.error('sw', error);
- dispatch(setError(error));
+ dispatch(setWorker(null));
});
};
}
diff --git a/js/src/redux/providers/compilerReducer.js b/js/src/redux/providers/compilerReducer.js
index 7470f0751..e23bf3b16 100644
--- a/js/src/redux/providers/compilerReducer.js
+++ b/js/src/redux/providers/compilerReducer.js
@@ -17,7 +17,7 @@
import { handleActions } from 'redux-actions';
const initialState = {
- worker: null,
+ worker: undefined,
error: null
};
diff --git a/js/src/serviceWorker.js b/js/src/serviceWorker.js
index a7052ad84..c558a57cf 100644
--- a/js/src/serviceWorker.js
+++ b/js/src/serviceWorker.js
@@ -14,9 +14,8 @@
// 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';
import registerPromiseWorker from 'promise-worker/register';
+import SolidityUtils from '~/util/solidity';
const CACHE_NAME = 'parity-cache-v1';
@@ -25,25 +24,71 @@ registerPromiseWorker((msg) => {
});
self.addEventListener('install', (event) => {
- console.warn('installing sw');
event.waitUntil(self.skipWaiting());
});
self.addEventListener('activate', (event) => {
- console.warn('activating sw');
event.waitUntil(self.clients.claim());
});
-self.solcVersions = {};
+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 load(message.data);
+ return getCompiler(message.data).then(() => 'ok');
case 'setFiles':
return setFiles(message.data);
@@ -54,6 +99,15 @@ function handleMessage (message) {
}
}
+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) => {
@@ -69,120 +123,22 @@ function setFiles (files) {
return 'ok';
}
-// @todo re-implement find imports (with ASYNC fetch)
-// function findImports (path) {
-// if (self.files[path]) {
-// if (self.files[path].error) {
-// return Promise.reject(self.files[path].error);
-// }
+function getCompiler (build) {
+ const { longVersion } = build;
-// return Promise.resolve(self.files[path]);
-// }
+ const fetcher = (url) => {
+ const request = new Request(url);
+ return cachedFetcher(request);
+ };
-// if (isWebUri(path)) {
-// console.log('[sw] fetching', path);
-
-// return fetch(path)
-// .then((r) => r.text())
-// .then((c) => {
-// console.log('[sw]', 'got content at ' + path);
-// self.files[path] = c;
-// return c;
-// })
-// .catch((e) => {
-// console.error('[sw]', 'fetching', path, e);
-// self.files[path] = { error: e };
-// throw e;
-// });
-// }
-
-// console.log(`[sw] path ${path} not found...`);
-// return Promise.reject('File not found');
-// }
-
-function compile (data, optimized = 1) {
- const { sourcecode, build } = data;
-
- return fetchSolidity(build)
- .then((compiler) => {
- const start = Date.now();
- console.log('[sw] compiling...');
-
- const input = {
- '': sourcecode
- };
-
- const compiled = compiler.compile({ sources: input }, optimized);
-
- const time = Math.round((Date.now() - start) / 100) / 10;
- console.log(`[sw] done compiling in ${time}s`);
-
- compiled.version = build.longVersion;
-
- return compiled;
- });
-}
-
-function load (build) {
- return fetchSolidity(build)
- .then(() => 'ok');
-}
-
-function fetchSolc (build) {
- const { path, longVersion } = build;
- const URL = `https://raw.githubusercontent.com/ethereum/solc-bin/gh-pages/bin/${path}`;
-
- return caches
- .match(URL)
- .then((response) => {
- if (response) {
- return response;
- }
-
- console.log(`[sw] fetching solc-bin ${longVersion} at ${URL}`);
-
- return fetch(URL)
- .then((response) => {
- if (!response || response.status !== 200) {
- return response;
- }
-
- const responseToCache = response.clone();
-
- return caches.open(CACHE_NAME)
- .then((cache) => {
- return cache.put(URL, responseToCache);
- })
- .then(() => {
- return response;
- });
- });
- });
-}
-
-function fetchSolidity (build) {
- const { path, longVersion } = build;
-
- if (self.solcVersions[path]) {
- return Promise.resolve(self.solcVersions[path]);
+ if (!self.solc[longVersion]) {
+ self.solc[longVersion] = SolidityUtils
+ .getCompiler(build, fetcher)
+ .then((compiler) => {
+ self.solc[longVersion] = compiler;
+ return compiler;
+ });
}
- return fetchSolc(build)
- .then((r) => r.text())
- .then((code) => {
- const solcCode = code.replace(/^var Module;/, 'var Module=self.__solcModule;');
- self.__solcModule = {};
-
- console.log(`[sw] evaluating ${longVersion}`);
-
- // eslint-disable-next-line no-eval
- eval(solcCode);
-
- console.log(`[sw] done evaluating ${longVersion}`);
-
- const compiler = solc(self.__solcModule);
- self.solcVersions[path] = 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..f09213ac5
--- /dev/null
+++ b/js/src/util/solidity.js
@@ -0,0 +1,79 @@
+// 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, optimized = 1 } = data;
+
+ const start = Date.now();
+ console.log('[solidity] compiling...');
+
+ const input = {
+ '': sourcecode
+ };
+
+ const compiled = compiler.compile({ sources: input }, optimized);
+
+ 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.js b/js/src/views/WriteContract/writeContract.js
index 2293eff73..5266fb625 100644
--- a/js/src/views/WriteContract/writeContract.js
+++ b/js/src/views/WriteContract/writeContract.js
@@ -57,7 +57,7 @@ class WriteContract extends Component {
const { setupWorker, worker } = this.props;
setupWorker();
- if (worker) {
+ if (worker !== undefined) {
this.store.setWorker(worker);
}
}
@@ -77,7 +77,7 @@ class WriteContract extends Component {
// Set the worker if not set before (eg. first page loading)
componentWillReceiveProps (nextProps) {
- if (!this.props.worker && nextProps.worker) {
+ if (this.props.worker === undefined && nextProps.worker !== undefined) {
this.store.setWorker(nextProps.worker);
}
diff --git a/js/src/views/WriteContract/writeContractStore.js b/js/src/views/WriteContract/writeContractStore.js
index 96d9c4d07..3a9a40aab 100644
--- a/js/src/views/WriteContract/writeContractStore.js
+++ b/js/src/views/WriteContract/writeContractStore.js
@@ -14,11 +14,12 @@
// 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';
@@ -79,6 +80,9 @@ export default class WriteContractStore {
snippets = SNIPPETS;
worker = null;
+ useWorker = true;
+ solc = {};
+
constructor () {
this.debouncedCompile = debounce(this.handleCompile, 1000);
}
@@ -131,6 +135,9 @@ export default class WriteContractStore {
this.selectedBuild = latestIndex;
return promise;
+ })
+ .catch((error) => {
+ this.setWorkerError(error);
});
}
@@ -143,32 +150,71 @@ export default class WriteContractStore {
return this.loadSolidityVersion(this.builds[value]);
}
+ 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) => {
- if (!this.worker) {
+ if (this.worker === undefined) {
return;
+ } else if (this.worker === null) {
+ this.useWorker = false;
}
if (this.loadingSolidity) {
return this.loadingSolidity;
}
- this.loadingSolidity = this.worker
- .postMessage({
- action: 'load',
- data: build
- })
- .then((result) => {
- if (result !== 'ok') {
- this.setWorkerError(result);
- }
- })
- .catch((error) => {
- this.setWorkerError(error);
- })
- .then(() => {
- this.loadingSolidity = false;
- this.loading = false;
- });
+ 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;
}
@@ -202,9 +248,32 @@ 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 = (loadFiles = false) => {
- this.compiled = false;
- this.compiling = true;
+ transaction(() => {
+ this.compiled = false;
+ this.compiling = true;
+ });
const build = this.builds[this.selectedBuild];
const version = build.longVersion;
@@ -219,19 +288,16 @@ export default class WriteContractStore {
resolve(this.lastCompilation);
}, 500);
});
- } else if (this.worker) {
- promise = loadFiles
+ } else {
+ promise = loadFiles && this.useWorker
? this.sendFilesToWorker()
: Promise.resolve();
promise = promise
.then(() => {
- return this.worker.postMessage({
- action: 'compile',
- data: {
- sourcecode: sourcecode,
- build: build
- }
+ return this.compile({
+ sourcecode: sourcecode,
+ build: build
});
})
.then((data) => {
@@ -427,6 +493,10 @@ export default class WriteContractStore {
}
sendFilesToWorker = () => {
+ if (!this.useWorker) {
+ return Promise.resolve();
+ }
+
const files = [].concat(
Object.values(this.snippets),
Object.values(this.savedContracts)