Fallback in Contract Dev if no worker

This commit is contained in:
Nicolas Gotchac 2016-12-20 01:55:57 +01:00
parent e377ec3194
commit f70e808056
6 changed files with 257 additions and 157 deletions

View File

@ -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));
});
};
}

View File

@ -17,7 +17,7 @@
import { handleActions } from 'redux-actions';
const initialState = {
worker: null,
worker: undefined,
error: null
};

View File

@ -14,9 +14,8 @@
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
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]);
// }
// 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 fetcher = (url) => {
const request = new Request(url);
return cachedFetcher(request);
};
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]);
}
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;
if (!self.solc[longVersion]) {
self.solc[longVersion] = SolidityUtils
.getCompiler(build, fetcher)
.then((compiler) => {
self.solc[longVersion] = compiler;
return compiler;
});
}
return self.solc[longVersion];
}

79
js/src/util/solidity.js Normal file
View File

@ -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 <http://www.gnu.org/licenses/>.
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;
});
}
}

View File

@ -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);
}

View File

@ -14,11 +14,12 @@
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
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,15 +150,36 @@ 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;
}
if (this.useWorker) {
this.loadingSolidity = this.worker
.postMessage({
action: 'load',
@ -159,16 +187,34 @@ export default class WriteContractStore {
})
.then((result) => {
if (result !== 'ok') {
this.setWorkerError(result);
throw new Error('error while loading solidity: ' + result);
}
this.loadingSolidity = false;
this.loading = false;
})
.catch((error) => {
this.setWorkerError(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) => {
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: {
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)