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) { if ('serviceWorker' in navigator) {
workerRegistration = runtime workerRegistration = runtime
.register() .register()
.then(() => { .then(() => navigator.serviceWorker.ready)
console.log('registering service worker');
return navigator.serviceWorker.ready;
})
.then((registration) => { .then((registration) => {
console.log('registered service worker');
const _worker = registration.active; const _worker = registration.active;
_worker.controller = registration.active; _worker.controller = registration.active;
const worker = new PromiseWorker(_worker); const worker = new PromiseWorker(_worker);
@ -68,7 +63,7 @@ export function setupWorker () {
}) })
.catch((error) => { .catch((error) => {
console.error('sw', error); console.error('sw', error);
dispatch(setError(error)); dispatch(setWorker(null));
}); });
}; };
} }

View File

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

View File

@ -14,9 +14,8 @@
// You should have received a copy of the GNU General Public License // You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>. // 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 registerPromiseWorker from 'promise-worker/register';
import SolidityUtils from '~/util/solidity';
const CACHE_NAME = 'parity-cache-v1'; const CACHE_NAME = 'parity-cache-v1';
@ -25,25 +24,71 @@ registerPromiseWorker((msg) => {
}); });
self.addEventListener('install', (event) => { self.addEventListener('install', (event) => {
console.warn('installing sw');
event.waitUntil(self.skipWaiting()); event.waitUntil(self.skipWaiting());
}); });
self.addEventListener('activate', (event) => { self.addEventListener('activate', (event) => {
console.warn('activating sw');
event.waitUntil(self.clients.claim()); 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 = {}; 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) { function handleMessage (message) {
switch (message.action) { switch (message.action) {
case 'compile': case 'compile':
return compile(message.data); return compile(message.data);
case 'load': case 'load':
return load(message.data); return getCompiler(message.data).then(() => 'ok');
case 'setFiles': case 'setFiles':
return setFiles(message.data); 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) { function setFiles (files) {
const prevFiles = self.files; const prevFiles = self.files;
const nextFiles = files.reduce((obj, file) => { const nextFiles = files.reduce((obj, file) => {
@ -69,120 +123,22 @@ function setFiles (files) {
return 'ok'; return 'ok';
} }
// @todo re-implement find imports (with ASYNC fetch) function getCompiler (build) {
// function findImports (path) { const { longVersion } = build;
// if (self.files[path]) {
// if (self.files[path].error) {
// return Promise.reject(self.files[path].error);
// }
// 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); if (!self.solc[longVersion]) {
self.solc[longVersion] = SolidityUtils
const time = Math.round((Date.now() - start) / 100) / 10; .getCompiler(build, fetcher)
console.log(`[sw] done compiling in ${time}s`); .then((compiler) => {
self.solc[longVersion] = compiler;
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;
return 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; const { setupWorker, worker } = this.props;
setupWorker(); setupWorker();
if (worker) { if (worker !== undefined) {
this.store.setWorker(worker); this.store.setWorker(worker);
} }
} }
@ -77,7 +77,7 @@ class WriteContract extends Component {
// Set the worker if not set before (eg. first page loading) // Set the worker if not set before (eg. first page loading)
componentWillReceiveProps (nextProps) { componentWillReceiveProps (nextProps) {
if (!this.props.worker && nextProps.worker) { if (this.props.worker === undefined && nextProps.worker !== undefined) {
this.store.setWorker(nextProps.worker); this.store.setWorker(nextProps.worker);
} }

View File

@ -14,11 +14,12 @@
// You should have received a copy of the GNU General Public License // You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>. // 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 store from 'store';
import { debounce } from 'lodash'; import { debounce } from 'lodash';
import { sha3 } from '~/api/util/sha3'; import { sha3 } from '~/api/util/sha3';
import SolidityUtils from '~/util/solidity';
const WRITE_CONTRACT_STORE_KEY = '_parity::writeContractStore'; const WRITE_CONTRACT_STORE_KEY = '_parity::writeContractStore';
@ -79,6 +80,9 @@ export default class WriteContractStore {
snippets = SNIPPETS; snippets = SNIPPETS;
worker = null; worker = null;
useWorker = true;
solc = {};
constructor () { constructor () {
this.debouncedCompile = debounce(this.handleCompile, 1000); this.debouncedCompile = debounce(this.handleCompile, 1000);
} }
@ -131,6 +135,9 @@ export default class WriteContractStore {
this.selectedBuild = latestIndex; this.selectedBuild = latestIndex;
return promise; return promise;
})
.catch((error) => {
this.setWorkerError(error);
}); });
} }
@ -143,15 +150,36 @@ export default class WriteContractStore {
return this.loadSolidityVersion(this.builds[value]); 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) => { @action loadSolidityVersion = (build) => {
if (!this.worker) { if (this.worker === undefined) {
return; return;
} else if (this.worker === null) {
this.useWorker = false;
} }
if (this.loadingSolidity) { if (this.loadingSolidity) {
return this.loadingSolidity; return this.loadingSolidity;
} }
if (this.useWorker) {
this.loadingSolidity = this.worker this.loadingSolidity = this.worker
.postMessage({ .postMessage({
action: 'load', action: 'load',
@ -159,16 +187,34 @@ export default class WriteContractStore {
}) })
.then((result) => { .then((result) => {
if (result !== 'ok') { if (result !== 'ok') {
this.setWorkerError(result); throw new Error('error while loading solidity: ' + result);
} }
this.loadingSolidity = false;
this.loading = false;
}) })
.catch((error) => { .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(() => { .then(() => {
this.loadingSolidity = false; this.loadingSolidity = false;
this.loading = false; this.loading = false;
return 'ok';
})
.catch((error) => {
this.setWorkerError(error);
this.loadingSolidity = false;
this.loading = false;
}); });
}
return this.loadingSolidity; return this.loadingSolidity;
} }
@ -202,9 +248,32 @@ export default class WriteContractStore {
this.contract = this.contracts[Object.keys(this.contracts)[value]]; 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) => { @action handleCompile = (loadFiles = false) => {
transaction(() => {
this.compiled = false; this.compiled = false;
this.compiling = true; this.compiling = true;
});
const build = this.builds[this.selectedBuild]; const build = this.builds[this.selectedBuild];
const version = build.longVersion; const version = build.longVersion;
@ -219,19 +288,16 @@ export default class WriteContractStore {
resolve(this.lastCompilation); resolve(this.lastCompilation);
}, 500); }, 500);
}); });
} else if (this.worker) { } else {
promise = loadFiles promise = loadFiles && this.useWorker
? this.sendFilesToWorker() ? this.sendFilesToWorker()
: Promise.resolve(); : Promise.resolve();
promise = promise promise = promise
.then(() => { .then(() => {
return this.worker.postMessage({ return this.compile({
action: 'compile',
data: {
sourcecode: sourcecode, sourcecode: sourcecode,
build: build build: build
}
}); });
}) })
.then((data) => { .then((data) => {
@ -427,6 +493,10 @@ export default class WriteContractStore {
} }
sendFilesToWorker = () => { sendFilesToWorker = () => {
if (!this.useWorker) {
return Promise.resolve();
}
const files = [].concat( const files = [].concat(
Object.values(this.snippets), Object.values(this.snippets),
Object.values(this.savedContracts) Object.values(this.savedContracts)