convert to TypeScript and add bucket actions

This commit is contained in:
Andrea Franz 2020-03-30 15:54:59 +02:00
parent 88f019e6c6
commit 0937a83f93
No known key found for this signature in database
GPG Key ID: 4F0D2F2D9DE7F29D
22 changed files with 856 additions and 195 deletions

159
app/js/actions/bucket.ts Normal file
View File

@ -0,0 +1,159 @@
import { RootState } from '../reducers';
import GiftBucket from '../../../embarkArtifacts/contracts/GiftBucket';
import IERC20Detailed from '../../../embarkArtifacts/contracts/IERC20Detailed';
import { config } from "../config";
import { Contract } from 'web3-eth-contract';
import { Dispatch } from 'redux';
export const ERROR_GIFT_NOT_FOUND = "ERROR_GIFT_NOT_FOUND";
export interface ErrGiftNotFound {
type: typeof ERROR_GIFT_NOT_FOUND
}
export const ERROR_LOADING_GIFT = "ERROR_LOADING_GIFT";
export interface ErrLoadingGift {
type: typeof ERROR_LOADING_GIFT
message: string
}
export type BucketError =
ErrGiftNotFound;
const errGiftNotFound = () => ({
type: ERROR_GIFT_NOT_FOUND,
});
const errLoadingGift = (message: string) => ({
type: ERROR_LOADING_GIFT,
message,
});
export const BUCKET_GIFT_LOADING = "BUCKET_GIFT_LOADING";
export interface BucketGiftLoadingAction {
type: typeof BUCKET_GIFT_LOADING
address: string
}
export const BUCKET_GIFT_LOADING_ERROR = "BUCKET_GIFT_LOADING_ERROR";
export interface BucketGiftLoadingErrorAction {
type: typeof BUCKET_GIFT_LOADING_ERROR
error: BucketError
}
export const BUCKET_GIFT_LOADED = "BUCKET_GIFT_LOADED";
export interface BucketGiftLoadedAction {
type: typeof BUCKET_GIFT_LOADED
recipient: string
amount: string
codeHash: string
}
export const BUCKET_GIFT_NOT_FOUND = "BUCKET_GIFT_NOT_FOUND";
export interface BucketGiftNotFoundAction {
type: typeof BUCKET_GIFT_NOT_FOUND
error: BucketError
}
export const BUCKET_TOKEN_LOADING = "BUCKET_TOKEN_LOADING";
export interface BucketTokenLoadingAction {
type: typeof BUCKET_TOKEN_LOADING
address: string
}
export const BUCKET_TOKEN_LOADED = "BUCKET_TOKEN_LOADED";
export interface BucketTokenLoadedAction {
type: typeof BUCKET_TOKEN_LOADED
symbol: string
decimal: number
}
export type BucketActions =
BucketGiftLoadingAction |
BucketGiftLoadingErrorAction |
BucketGiftLoadedAction |
BucketGiftNotFoundAction |
BucketTokenLoadingAction |
BucketTokenLoadedAction;
export const loadingGift = (address: string): BucketLoadingAction => ({
type: BUCKET_GIFT_LOADING,
address,
});
export const giftLoaded = (recipient: string, amount: string, codeHash: string): BucketGiftLoadedAction => ({
type: BUCKET_GIFT_LOADED,
recipient,
amount,
codeHash,
});
export const giftNotFound = (recipient: string, amount: string, codeHash: string): BucketGiftNotFoundAction => ({
type: BUCKET_GIFT_NOT_FOUND,
error: errGiftNotFound(),
});
export const errorLoadingGift = (errorMessage: string): BucketGiftNotFoundAction => ({
type: BUCKET_GIFT_NOT_FOUND,
error: errLoadingGift(errorMessage),
});
export const loadingToken = (address: string): BucketTokenLoadingAction => ({
type: BUCKET_TOKEN_LOADING,
address,
});
export const tokenLoaded = (symbol: string, decimals: number): BucketTokenLoadedAction => ({
type: BUCKET_TOKEN_LOADED,
symbol,
decimals,
});
const newBucketContract = (address: string) => {
const bucketAbi = GiftBucket.options.jsonInterface;
const bucket = new config.web3!.eth.Contract(bucketAbi, address);
return bucket;
}
const newERC20Contract = (address: string) => {
const erc20Abi = IERC20Detailed.options.jsonInterface;
const erc20 = new config.web3!.eth.Contract(erc20Abi, address);
return erc20;
}
export const loadGift = (bucketAddress: string, recipientAddress: string) => {
return async (dispatch: Dispatch, getState: () => RootState) => {
dispatch(loadingGift(bucketAddress, recipientAddress));
const bucket = newBucketContract(bucketAddress);
bucket.methods.gifts(recipientAddress).call().then((result: Any) => {
const { recipient, amount, code } = result;
if (amount === "0") {
dispatch(giftNotFound())
return;
}
dispatch(giftLoaded(recipient, amount, code));
dispatch(loadToken(bucket))
}).catch(err => {
dispatch(errorLoadingGift(err))
console.error("err: ", err)
})
};
};
export const loadToken = (bucket: Contract) => {
return (dispatch: Dispatch, getState: () => RootState) => {
bucket.methods.tokenContract().call().then(async (address: string) => {
const erc20Abi = IERC20Detailed.options.jsonInterface;
const erc20 = new config.web3!.eth.Contract(erc20Abi, address);
dispatch(loadingToken(address));
const symbol = await erc20.methods.symbol().call();
const decimals = await erc20.methods.decimals().call();
dispatch(tokenLoaded(symbol, decimals));
}).catch((err: string) => {
//FIXME: manage error
console.error("ERROR: ", err);
})
}
}

View File

@ -1,94 +0,0 @@
import Web3 from 'web3';
import { Dispatch } from 'redux';
import { config } from '../config';
export const VALID_NETWORK_NAME = "Ropsten";
export const VALID_NETWORK_ID = 3;
// export const VALID_NETWORK_NAME = "Goerli";
// export const VALID_NETWORK_ID = 5;
export const LOCAL_NETWORK_ID = 1337;
export const WEB3_INITIALIZED = "WEB3_INITIALIZED";
export const WEB3_ERROR = "WEB3_ERROR";
export const WEB3_NETWORK_ID_LOADED = "WEB3_NETWORK_ID_LOADED";
export const WEB3_ACCOUNT_LOADED = "WEB3_ACCOUNT_LOADED";
export const web3Initialized = () => ({
type: WEB3_INITIALIZED,
})
export const web3NetworkIDLoaded = networkID => ({
type: WEB3_NETWORK_ID_LOADED,
networkID,
});
export const web3Error = error => ({
type: WEB3_ERROR,
error,
});
export const web3AccoutLoaded = account => ({
type: WEB3_ACCOUNT_LOADED,
account,
});
export const initWeb3 = () => {
if (window.ethereum) {
config.web3 = new Web3(window.ethereum);
return (dispatch, getState) => {
window.ethereum.enable()
.then(() => {
dispatch(web3Initialized());
dispatch(loadNetwordId());
})
.catch((err) => {
dispatch(web3Error(err));
});
}
} else if (window.web3) {
config.web3 = window.web3;
return (dispatch, getState) => {
dispatch(web3Initialized());
dispatch(loadNetwordId());
}
} else {
//FIXME: move to config
// const web3 = new Web3('https://ropsten.infura.io/v3/f315575765b14720b32382a61a89341a');
// const web3 = new Web3(new Web3.providers.HttpProvider('https://ropsten.infura.io/v3/f315575765b14720b32382a61a89341a'));
config.web3 = new Web3(new Web3.providers.WebsocketProvider('wss://ropsten.infura.io/ws/v3/f315575765b14720b32382a61a89341a'));
return (dispatch, getState) => {
dispatch(web3Initialized());
dispatch(loadNetwordId());
}
}
}
const loadNetwordId = () => {
return (dispatch, getState) => {
config.web3.eth.net.getId().then((id) => {
dispatch(web3NetworkIDLoaded(id))
if (id !== VALID_NETWORK_ID && id !== LOCAL_NETWORK_ID) {
dispatch(web3Error(`wrong network, please connect to ${VALID_NETWORK_NAME}`));
return;
}
dispatch(web3NetworkIDLoaded(id))
dispatch(loadMainAccount());
})
.catch((err) => {
dispatch(web3Error(err));
});
};
}
const loadMainAccount = () => {
return (dispatch, getState) => {
web3.eth.getAccounts()
.then(accounts => {
dispatch(web3AccoutLoaded(accounts[0]));
})
.catch((err) => {
dispatch(web3Error(err));
});
};
}

117
app/js/actions/web3.ts Normal file
View File

@ -0,0 +1,117 @@
import Web3 from 'web3';
import { config } from '../config';
import {
Dispatch,
} from 'redux';
import { RootState } from '../reducers';
export const VALID_NETWORK_NAME = "Ropsten";
export const VALID_NETWORK_ID = 3;
// export const VALID_NETWORK_NAME = "Goerli";
// export const VALID_NETWORK_ID = 5;
export const LOCAL_NETWORK_ID = 1337;
enum Web3Type {
Generic,
Remote,
Status,
}
export const WEB3_INITIALIZED = "WEB3_INITIALIZED";
export interface Web3InitializedAction {
type: typeof WEB3_INITIALIZED
web3Type: Web3Type
}
export const WEB3_ERROR = "WEB3_ERROR";
export interface Web3ErrorAction {
type: typeof WEB3_ERROR
error: string
}
export const WEB3_NETWORK_ID_LOADED = "WEB3_NETWORK_ID_LOADED";
export interface Web3NetworkIDLoadedAction {
type: typeof WEB3_NETWORK_ID_LOADED
networkID: number
}
export const WEB3_ACCOUNT_LOADED = "WEB3_ACCOUNT_LOADED";
export interface Web3AccountLoadedAction {
type: typeof WEB3_ACCOUNT_LOADED
account: string
}
export type Web3Actions =
Web3InitializedAction |
Web3ErrorAction |
Web3NetworkIDLoadedAction |
Web3AccountLoadedAction;
export const web3Initialized = (t: Web3Type): Web3Actions => ({
type: WEB3_INITIALIZED,
web3Type: t,
})
export const web3NetworkIDLoaded = (id: number): Web3Actions => ({
type: WEB3_NETWORK_ID_LOADED,
networkID: id,
});
export const web3Error = (error: string): Web3Actions => ({
type: WEB3_ERROR,
error: error,
});
export const accountLoaded = (account: string): Web3Actions => ({
type: WEB3_ACCOUNT_LOADED,
account
});
export const initializeWeb3 = () => {
const w = window as any;
return (dispatch: Dispatch, getState: () => RootState) => {
if (w.ethereum) {
config.web3 = new Web3(w.ethereum);
w.ethereum.enable()
.then(() => {
const t: Web3Type = w.ethereum.isStatus ? Web3Type.Status : Web3Type.Generic;
dispatch(web3Initialized(t));
config.web3!.eth.net.getId().then((id: number) => {
if (id !== VALID_NETWORK_ID && id !== LOCAL_NETWORK_ID) {
dispatch(web3Error(`wrong network, please connect to ${VALID_NETWORK_NAME}`));
return;
}
dispatch(web3NetworkIDLoaded(id))
dispatch(loadAddress());
});
})
.catch((err: string) => {
//FIXME: handle error
console.log("error", err)
});
} else if (config.web3) {
const t: Web3Type = w.ethereum.isStatus ? Web3Type.Status : Web3Type.Generic;
dispatch(web3Initialized(t));
config.web3!.eth.net.getId().then((id: number) => {
dispatch(web3NetworkIDLoaded(id))
dispatch(loadAddress());
})
.catch((err: string) => {
//FIXME: handle error
console.log("error", err)
});
} else {
dispatch(web3Error("web3 not supported"));
}
};
}
const loadAddress = () => {
return (dispatch: Dispatch, getState: () => RootState) => {
web3.eth.getAccounts().then((accounts: string[]) => {
dispatch(accountLoaded(accounts[0]));
});
};
}

View File

@ -1,19 +0,0 @@
import React from 'react';
export default function App(props) {
if (!props.initialized) {
return "initializing...";
}
if (props.error) {
return <>
<p>Error: {props.error}</p>
</>;
}
return <>
<p>Network ID {props.networkID}</p>
<p>Hello {props.account}</p>
</>;
}

26
app/js/components/App.tsx Normal file
View File

@ -0,0 +1,26 @@
import React from 'react';
import {
shallowEqual,
useSelector,
useDispatch,
} from 'react-redux';
export default function(ownProps: any) {
const props = useSelector(state => {
return {
initialized: state.web3.networkID,
networkID: state.web3.networkID,
error: state.web3.error,
}
}, shallowEqual);
if (props.error) {
return `Error: ${props.error}`;
}
if (!props.initialized) {
return "initializing...";
}
return ownProps.children;
}

View File

@ -0,0 +1,28 @@
import React from 'react';
class ErrorBoundary extends React.Component {
constructor(props) {
super(props);
this.state = { hasError: false };
}
static getDerivedStateFromError(error) {
return { hasError: true };
}
componentDidCatch(error, errorInfo) {
console.error(error);
}
render() {
if (this.state.hasError) {
return <div>
<p>Something went wrong.</p>
</div>;
}
return this.props.children;
}
}
export default ErrorBoundary;

View File

@ -0,0 +1,103 @@
import React, { useEffect } from 'react';
import { useRouteMatch } from 'react-router-dom';
import {
shallowEqual,
useSelector,
useDispatch,
} from 'react-redux';
import { redeemPath } from '../config';
import {
loadGift,
BucketError,
ERROR_LOADING_GIFT,
ERROR_GIFT_NOT_FOUND,
} from '../actions/bucket';
import Web3Utils from "web3-utils";
const BN = Web3Utils.BN;
const errorMessage = (error: BucketError): string => {
switch (error.type) {
case ERROR_LOADING_GIFT:
return "couldn't load gift.";
case ERROR_GIFT_NOT_FOUND:
return "gift not found";
default:
return "something went wrong";
}
}
const toBaseUnit = (fullAmount: string, decimalsSize: number, roundDecimals: number) => {
const amount = new BN(fullAmount);
const base = new BN(10).pow(new BN(decimalsSize));
const whole = amount.div(base).toString();
let decimals = amount.mod(base).toString();
for (let i = decimals.length; i < decimalsSize; i++) {
decimals = `0${decimals}`;
}
const full = `${whole}.${decimals}`;
const rounded = `${whole}.${decimals.slice(0, roundDecimals)}`;
return [full, rounded];
}
export default function(ownProps: any) {
const dispatch = useDispatch()
const match = useRouteMatch({
path: redeemPath,
exact: true,
});
const bucketAddress = match.params.bucketAddress;
const recipientAddress = match.params.recipientAddress;
const props = useSelector(state => {
return {
bucketAddress: state.bucket.address,
loading: state.bucket.loading,
found: state.bucket.found,
error: state.bucket.error,
recipient: state.bucket.recipient,
amount: state.bucket.amount,
codeHash: state.bucket.codeHash,
tokenAddress: state.bucket.tokenAddress,
tokenSymbol: state.bucket.tokenSymbol,
tokenDecimals: state.bucket.tokenDecimals,
receiver: state.web3.account,
}
}, shallowEqual);
useEffect(() => {
dispatch(loadGift(bucketAddress, recipientAddress));
}, [bucketAddress, recipientAddress]);
if (props.error) {
return `Error: ${errorMessage(props.error)}`;
}
if (props.loading) {
return "loading bucket...";
}
if (props.tokenSymbol === undefined || props.tokenDecimals === undefined) {
return "loading token info...";
}
const [displayAmount, roundedDisplayAmount] = toBaseUnit(props.amount, props.tokenDecimals, 2);
return <>
Bucket Address: {props.bucketAddress}<br />
Recipient: {props.recipient}<br />
Amount: {props.amount}<br />
Code Hash: {props.codeHash}<br />
Token Address: {props.tokenAddress}<br />
Token Symbol: {props.tokenSymbol}<br />
Token Decimals: {props.tokenDecimals}<br />
Display Amount: {displayAmount} <br />
Rounded Display Amount: {roundedDisplayAmount} <br />
Receiver: {props.receiver} <br />
</>;
}

View File

@ -1,3 +0,0 @@
export const config = {
web3: undefined,
};

7
app/js/config.ts Normal file
View File

@ -0,0 +1,7 @@
import Web3 from "web3";
export const config = {
web3: Web3 | undefined
};
export const redeemPath = "/redeem/:bucketAddress/:recipientAddress";

View File

@ -1,17 +0,0 @@
import { connect } from 'react-redux';
import App from '../components/App';
const mapStateToProps = state => ({
initialized: state.web3.networkID,
networkID: state.web3.networkID,
account: state.web3.account,
error: state.web3.error,
});
const mapDispatchToProps = dispatch => ({
});
export default connect(
mapStateToProps,
mapDispatchToProps
)(App);

View File

@ -1,47 +0,0 @@
import EmbarkJS from 'Embark/EmbarkJS';
import React, { useEffect } from 'react';
import ReactDOM from 'react-dom';
import thunkMiddleware from 'redux-thunk';
import { Provider } from 'react-redux';
import { createStore, applyMiddleware } from 'redux';
import createRootReducer from './reducers';
import { initWeb3 } from './actions/web3';
import App from './containers/App';
const logger = (store) => {
return (next) => {
return (action) => {
console.log('dispatching\n', action);
const result = next(action);
console.log('next state\n', store.getState());
return result;
}
}
};
let middlewares = [
thunkMiddleware,
];
if (true || process.env.NODE_ENV !== 'production') {
middlewares = [
...middlewares,
logger
];
}
const store = createStore(
createRootReducer(),
applyMiddleware(...middlewares),
);
EmbarkJS.onReady((err) => {
store.dispatch(initWeb3());
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById("root")
);
});

63
app/js/index.tsx Normal file
View File

@ -0,0 +1,63 @@
import EmbarkJS from 'Embark/EmbarkJS';
import React, { useEffect } from 'react';
import ReactDOM from 'react-dom';
import thunkMiddleware from 'redux-thunk';
import { Provider } from 'react-redux';
import { createStore, applyMiddleware, Middleware, MiddlewareAPI, Dispatch } from 'redux';
import createRootReducer from './reducers';
import { initializeWeb3 } from './actions/web3';
import { routerMiddleware, ConnectedRouter } from 'connected-react-router';
import { Route, Switch } from 'react-router';
import { createHashHistory } from 'history';
import ErrorBoundary from './components/ErrorBoundary';
import App from './components/App';
import Home from './components/Home';
import Redeem from './components/Redeem';
import { redeemPath } from './config';
const logger: Middleware = ({ getState }: MiddlewareAPI) => (next: Dispatch) => action => {
console.log('will dispatch', action);
const returnValue = next(action);
console.log('state after dispatch', getState());
return returnValue;
}
const history = createHashHistory();
let middlewares: Middleware[] = [
routerMiddleware(history),
thunkMiddleware,
];
if (true || process.env.NODE_ENV !== 'production') {
middlewares = [
...middlewares,
logger
];
}
const store = createStore(
createRootReducer(history),
applyMiddleware(...middlewares),
);
EmbarkJS.onReady(err => {
store.dispatch<any>(initializeWeb3());
ReactDOM.render(
<ErrorBoundary>
<Provider store={store}>
<App>
<ConnectedRouter history={history}>
<Switch>
<Route exact path="/"><Home /></Route>
<Route exact path={redeemPath}><Redeem /></Route>
<Route render={() => "page not found"} />
</Switch>
</ConnectedRouter>
</App>
</Provider>
</ErrorBoundary>,
document.getElementById("root")
);
});

79
app/js/reducers/bucket.ts Normal file
View File

@ -0,0 +1,79 @@
import {
BucketActions,
BucketError,
BUCKET_GIFT_LOADING,
BUCKET_GIFT_NOT_FOUND,
BUCKET_GIFT_LOADED,
BUCKET_TOKEN_LOADING,
BUCKET_TOKEN_LOADED,
} from "../actions/bucket";
export interface BucketState {
loading: boolean
address: string | undefined
tokenAddress: string | undefined
tokenDecimals: number | undefined
error: BucketState | undefined
recipient: string | undefined
amount: string | undefined
codeHash: string | undefined
}
const initialState: BucketState = {
loading: false,
address: undefined,
tokenAddress: undefined,
tokenDecimals: undefined,
error: undefined,
recipient: undefined,
amount: undefined,
codeHash: undefined,
}
export const bucketReducer = (state: BucketState = initialState, action: BucketActions): BucketState => {
switch (action.type) {
case BUCKET_GIFT_LOADING: {
return {
...initialState,
loading: true,
address: action.address,
}
}
case BUCKET_GIFT_NOT_FOUND: {
return {
...state,
loading: false,
error: action.error,
}
}
case BUCKET_GIFT_LOADED: {
return {
...state,
loading: false,
recipient: action.recipient,
amount: action.amount,
codeHash: action.codeHash,
}
}
case BUCKET_TOKEN_LOADING: {
return {
...state,
tokenAddress: action.address,
}
}
case BUCKET_TOKEN_LOADED: {
return {
...state,
tokenSymbol: action.symbol,
tokenDecimals: action.decimals,
}
}
default:
return state
}
}

View File

@ -1,8 +0,0 @@
import { combineReducers } from 'redux';
import { web3Reducer } from './web3';
export default function() {
return combineReducers({
web3: web3Reducer,
});
}

24
app/js/reducers/index.ts Normal file
View File

@ -0,0 +1,24 @@
import { combineReducers } from 'redux';
import { connectRouter } from 'connected-react-router';
import {
Web3State,
web3Reducer,
} from './web3';
import {
BucketState,
bucketReducer,
} from './bucket';
export interface RootState {
web3: Web3State,
bucket: BucketState,
}
export default function(history) {
return combineReducers({
web3: web3Reducer,
router: connectRouter(history),
bucket: bucketReducer,
});
}

View File

@ -1,10 +1,18 @@
import {
Web3Actions,
WEB3_INITIALIZED,
WEB3_ERROR,
WEB3_NETWORK_ID_LOADED,
WEB3_ACCOUNT_LOADED,
} from '../actions/web3';
export interface Web3State {
initialized: boolean
networkID: number | undefined
error: string | undefined
account: string | undefined
}
const initialState: Web3State = {
initialized: false,
networkID: undefined,
@ -12,7 +20,7 @@ const initialState: Web3State = {
account: undefined,
};
export const web3Reducer = (state = initialState, action) => {
export const web3Reducer = (state: Web3State = initialState, action: Web3Actions): Web3State => {
switch (action.type) {
case WEB3_INITIALIZED: {
return {
@ -41,7 +49,8 @@ export const web3Reducer = (state = initialState, action) => {
account: action.account,
}
}
}
return state;
default:
return state
}
}

View File

@ -3,8 +3,8 @@ module.exports = {
default: {
// order of connections the dapp should connect to
dappConnection: [
"$EMBARK",
"$WEB3", // uses pre existing web3 object if available (e.g in Mist)
"$EMBARK",
"ws://localhost:8546",
"http://localhost:8545"
],
@ -22,7 +22,7 @@ module.exports = {
// when not specified
// - explicit will only attempt to deploy the contracts that are explicitly specified inside the
// contracts section.
// strategy: 'implicit',
strategy: 'explicit',
// minimalContractSize, when set to true, tells Embark to generate contract files without the heavy bytecodes
// Using filteredFields lets you customize which field you want to filter out of the contract file (requires minimalContractSize: true)

View File

@ -2,7 +2,7 @@
"contracts": ["contracts/**"],
"app": {
"css/app.css": ["app/css/**"],
"js/app.js": ["app/js/index.js"],
"js/app.js": ["app/js/index.tsx"],
"images/": ["app/images/**"],
"index.html": "app/index.html"
},
@ -12,6 +12,13 @@
"solc": "0.6.1"
},
"plugins": {
"embark-ipfs": {},
"embark-swarm": {},
"embark-whisper-geth": {},
"embark-geth": {},
"embark-parity": {},
"embark-profiler": {},
"embark-graph": {}
},
"options": {
"solc": {

View File

@ -15,6 +15,7 @@
"react-dom": "^16.12.0",
"react-redux": "^7.2.0",
"react-router": "^5.1.2",
"react-router-dom": "^5.1.2",
"redux": "^4.0.5",
"redux-thunk": "^2.3.0",
"web3": "^1.2.6"

25
tsconfig.json Normal file
View File

@ -0,0 +1,25 @@
{
"compilerOptions": {
"target": "es5",
"lib": [
"dom",
"dom.iterable",
"esnext"
],
"allowJs": true,
"skipLibCheck": true,
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"strict": true,
"forceConsistentCasingInFileNames": true,
"module": "esnext",
"moduleResolution": "node",
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "react"
},
"include": [
"src"
]
}

188
webpack.config.js Normal file
View File

@ -0,0 +1,188 @@
// some packages, plugins, and presets referenced/required in this webpack
// config are deps of embark and will be transitive dapp deps unless specified
// in the dapp's own package.json
// embark modifies process.env.NODE_PATH so that when running dapp scripts in
// embark's child processes, embark's own node_modules directory will be
// searched by node's require(); however, webpack and babel do not directly
// support NODE_PATH, so modules such as babel plugins and presets must be
// resolved with require.resolve(); that is only necessary if a plugin/preset
// is in embark's node_modules vs. the dapp's node_modules
const cloneDeep = require('lodash.clonedeep');
// const CompressionPlugin = require('compression-webpack-plugin');
const glob = require('glob');
const HardSourceWebpackPlugin = require('hard-source-webpack-plugin');
const path = require('path');
const dappPath = process.env.DAPP_PATH;
const embarkPath = process.env.EMBARK_PATH;
const embarkAliases = require(path.join(dappPath, '.embark/embark-aliases.json'));
const embarkAssets = require(path.join(dappPath, '.embark/embark-assets.json'));
const embarkNodeModules = path.join(embarkPath, 'node_modules');
const embarkJson = require(path.join(dappPath, 'embark.json'));
const buildDir = path.join(dappPath, embarkJson.buildDir);
// it's important to `embark reset` if a pkg version is specified in
// embark.json and changed/removed later, otherwise pkg resolution may behave
// unexpectedly
let versions;
try {
versions = glob.sync(path.join(dappPath, '.embark/versions/*/*'));
} catch (e) {
versions = [];
}
const entry = Object.keys(embarkAssets)
.filter(key => key.match(/\.js$/))
.reduce((obj, key) => {
// webpack entry paths should start with './' if they're relative to the
// webpack context; embark.json "app" keys correspond to lists of .js
// source paths relative to the top-level dapp dir and may be missing the
// leading './'
obj[key] = embarkAssets[key]
.map(file => {
let file_path = file.path;
if (!file.path.match(/^\.\//)) {
file_path = './' + file_path;
}
return file_path;
});
return obj;
}, {});
function resolve(pkgName) {
if (Array.isArray(pkgName)) {
const _pkgName = pkgName[0];
pkgName[0] = require.resolve(_pkgName);
return pkgName;
}
return require.resolve(pkgName);
}
// base config
// -----------------------------------------------------------------------------
const base = {
context: dappPath,
entry: entry,
module: {
rules: [
{
test: /\.css$/,
use: [{loader: 'style-loader'}, {loader: 'css-loader'}]
},
{
test: /\.scss$/,
use: [{loader: 'style-loader'}, {loader: 'css-loader'}]
},
{
test: /\.(png|woff|woff2|eot|ttf|svg)$/,
loader: 'url-loader?limit=100000'
},
{
test: /\.(js|jsx|tsx|ts)$/,
loader: 'babel-loader',
exclude: /(node_modules|bower_components|\.embark[\\/]versions)/,
options: {
plugins: [
[
'babel-plugin-module-resolver', {
'alias': embarkAliases
}
],
[
'@babel/plugin-transform-runtime', {
corejs: 2,
useESModules: true
}
]
].map(resolve),
presets: [
[
'@babel/preset-env', {
modules: false,
targets: {
browsers: ['last 1 version', 'not dead', '> 0.2%']
}
}
],
'@babel/preset-react',
'@babel/preset-typescript'
].map(resolve)
}
}
]
},
output: {
filename: (chunkData) => chunkData.chunk.name,
// globalObject workaround for node-compatible UMD builds with webpack 4
// see: https://github.com/webpack/webpack/issues/6522#issuecomment-371120689
globalObject: 'typeof self !== \'undefined\' ? self : this',
libraryTarget: 'umd',
path: buildDir
},
plugins: [new HardSourceWebpackPlugin()],
// profiling and generating verbose stats increases build time; if stats
// are generated embark will write the output to:
// path.join(dappPath, '.embark/stats.[json,report]')
// to visualize the stats info in a browser run:
// npx webpack-bundle-analyzer .embark/stats.json <buildDir>
profile: true, stats: 'verbose',
resolve: {
alias: embarkAliases,
modules: [
...versions,
'node_modules',
embarkNodeModules
]
},
resolveLoader: {
modules: [
'node_modules',
embarkNodeModules
]
}
};
// typescript mods
// -----------------------------------------------------------------------------
base.resolve.extensions = [
// webpack defaults
// see: https://webpack.js.org/configuration/resolve/#resolve-extensions
'.wasm', '.mjs', '.js', '.json',
// typescript extensions
'.ts', '.tsx'
];
// development config
// -----------------------------------------------------------------------------
const development = cloneDeep(base);
// full source maps increase build time but are useful during dapp development
development.devtool = 'source-map';
development.mode = 'development';
// alternatively:
// development.mode = 'none';
development.name = 'development';
const devBabelLoader = development.module.rules[3];
devBabelLoader.options.compact = false;
// production config
// -----------------------------------------------------------------------------
const production = cloneDeep(base);
production.mode = 'production';
production.name = 'production';
// compression of webpack's JS output not enabled by default
// production.plugins.push(new CompressionPlugin());
// export a list of named configs
// -----------------------------------------------------------------------------
module.exports = [
development,
production
];

View File

@ -9623,7 +9623,20 @@ react-redux@^7.2.0:
prop-types "^15.7.2"
react-is "^16.9.0"
react-router@^5.1.2:
react-router-dom@^5.1.2:
version "5.1.2"
resolved "https://registry.yarnpkg.com/react-router-dom/-/react-router-dom-5.1.2.tgz#06701b834352f44d37fbb6311f870f84c76b9c18"
integrity sha512-7BPHAaIwWpZS074UKaw1FjVdZBSVWEk8IuDXdB+OkLb8vd/WRQIpA4ag9WQk61aEfQs47wHyjWUoUGGZxpQXew==
dependencies:
"@babel/runtime" "^7.1.2"
history "^4.9.0"
loose-envify "^1.3.1"
prop-types "^15.6.2"
react-router "5.1.2"
tiny-invariant "^1.0.2"
tiny-warning "^1.0.0"
react-router@5.1.2, react-router@^5.1.2:
version "5.1.2"
resolved "https://registry.yarnpkg.com/react-router/-/react-router-5.1.2.tgz#6ea51d789cb36a6be1ba5f7c0d48dd9e817d3418"
integrity sha512-yjEuMFy1ONK246B+rsa0cUam5OeAQ8pyclRDgpxuSCrAlJ1qN9uZ5IgyKC7gQg0w8OM50NXHEegPh/ks9YuR2A==