aboutsummaryrefslogtreecommitdiffstats
path: root/packages/instant
diff options
context:
space:
mode:
Diffstat (limited to 'packages/instant')
-rw-r--r--packages/instant/.dogfood.discharge.json4
-rw-r--r--packages/instant/.staging.discharge.json4
-rw-r--r--packages/instant/package.json3
-rw-r--r--packages/instant/src/components/zero_ex_instant_provider.tsx2
-rw-r--r--packages/instant/src/constants.ts8
-rw-r--r--packages/instant/src/redux/async_data.ts4
-rw-r--r--packages/instant/src/util/asset.ts17
-rw-r--r--packages/instant/src/util/buy_quote_updater.ts29
-rw-r--r--packages/instant/src/util/error_reporter.ts61
-rw-r--r--packages/instant/src/util/gas_price_estimator.ts5
-rw-r--r--packages/instant/src/util/heap.ts3
-rw-r--r--packages/instant/test/util/asset.test.ts33
-rw-r--r--packages/instant/webpack.config.js116
13 files changed, 245 insertions, 44 deletions
diff --git a/packages/instant/.dogfood.discharge.json b/packages/instant/.dogfood.discharge.json
index ca36b3861..cea579c27 100644
--- a/packages/instant/.dogfood.discharge.json
+++ b/packages/instant/.dogfood.discharge.json
@@ -1,12 +1,12 @@
{
"domain": "0x-instant-dogfood",
- "build_command": "WEBPACK_OUTPUT_PATH=public yarn build:umd:prod",
+ "build_command": "WEBPACK_OUTPUT_PATH=public yarn build:umd:prod --env.dogfood",
"upload_directory": "public",
"index_key": "index.html",
"error_key": "index.html",
"trailing_slashes": true,
"cache": 3600,
- "aws_profile": "default",
+ "aws_profile": "0xproject",
"aws_region": "us-east-1",
"cdn": false,
"dns_configured": true
diff --git a/packages/instant/.staging.discharge.json b/packages/instant/.staging.discharge.json
index c917a650b..9e63cb110 100644
--- a/packages/instant/.staging.discharge.json
+++ b/packages/instant/.staging.discharge.json
@@ -1,12 +1,12 @@
{
"domain": "0x-instant-staging",
- "build_command": "WEBPACK_OUTPUT_PATH=public yarn build:umd:prod",
+ "build_command": "WEBPACK_OUTPUT_PATH=public yarn build:umd:prod --env.staging",
"upload_directory": "public",
"index_key": "index.html",
"error_key": "index.html",
"trailing_slashes": true,
"cache": 3600,
- "aws_profile": "default",
+ "aws_profile": "0xproject",
"aws_region": "us-east-1",
"cdn": false,
"dns_configured": true
diff --git a/packages/instant/package.json b/packages/instant/package.json
index 69319e854..e37ca1ea2 100644
--- a/packages/instant/package.json
+++ b/packages/instant/package.json
@@ -63,6 +63,7 @@
"react-redux": "^5.0.7",
"redux": "^4.0.0",
"redux-devtools-extension": "^2.13.5",
+ "rollbar": "^2.5.0",
"styled-components": "^4.0.2",
"ts-optchain": "^0.1.1"
},
@@ -87,7 +88,9 @@
"make-promises-safe": "^1.1.0",
"npm-run-all": "^4.1.2",
"nyc": "^11.0.1",
+ "rollbar-sourcemap-webpack-plugin": "^2.4.0",
"shx": "^0.2.2",
+ "source-map-loader": "^0.2.4",
"svg-react-loader": "^0.4.6",
"ts-jest": "^23.10.3",
"tslint": "5.11.0",
diff --git a/packages/instant/src/components/zero_ex_instant_provider.tsx b/packages/instant/src/components/zero_ex_instant_provider.tsx
index 9814aabf8..1f53f2d96 100644
--- a/packages/instant/src/components/zero_ex_instant_provider.tsx
+++ b/packages/instant/src/components/zero_ex_instant_provider.tsx
@@ -15,6 +15,7 @@ import { AccountState, AffiliateInfo, AssetMetaData, Network, OrderSource } from
import { analytics, disableAnalytics } from '../util/analytics';
import { assetUtils } from '../util/asset';
import { errorFlasher } from '../util/error_flasher';
+import { setupRollbar } from '../util/error_reporter';
import { gasPriceEstimator } from '../util/gas_price_estimator';
import { Heartbeater } from '../util/heartbeater';
import { generateAccountHeartbeater, generateBuyQuoteHeartbeater } from '../util/heartbeater_factory';
@@ -86,6 +87,7 @@ export class ZeroExInstantProvider extends React.Component<ZeroExInstantProvider
}
constructor(props: ZeroExInstantProviderProps) {
super(props);
+ setupRollbar();
fonts.include();
const initialAppState = ZeroExInstantProvider._mergeDefaultStateWithProps(this.props);
this._store = store.create(initialAppState);
diff --git a/packages/instant/src/constants.ts b/packages/instant/src/constants.ts
index be6077ca9..dfa7520f8 100644
--- a/packages/instant/src/constants.ts
+++ b/packages/instant/src/constants.ts
@@ -20,6 +20,14 @@ export const HEAP_ANALYTICS_ID = process.env.HEAP_ANALYTICS_ID;
export const COINBASE_API_BASE_URL = 'https://api.coinbase.com/v2';
export const PROGRESS_STALL_AT_WIDTH = '95%';
export const PROGRESS_FINISH_ANIMATION_TIME_MS = 200;
+export const ROLLBAR_CLIENT_TOKEN = process.env.ROLLBAR_CLIENT_TOKEN;
+export const INSTANT_ENVIRONMENT = process.env.INSTANT_ENVIRONMENT as
+ | 'dogfood'
+ | 'staging'
+ | 'development'
+ | 'production'
+ | undefined;
+export const ROLLBAR_ENABLED = process.env.ROLLBAR_ENABLED;
export const COINBASE_WALLET_IOS_APP_STORE_URL = 'https://itunes.apple.com/us/app/coinbase-wallet/id1278383455?mt=8';
export const COINBASE_WALLET_ANDROID_APP_STORE_URL = 'https://play.google.com/store/apps/details?id=org.toshi&hl=en';
export const COINBASE_WALLET_SITE_URL = 'https://wallet.coinbase.com/';
diff --git a/packages/instant/src/redux/async_data.ts b/packages/instant/src/redux/async_data.ts
index 5d30388b8..5382494f2 100644
--- a/packages/instant/src/redux/async_data.ts
+++ b/packages/instant/src/redux/async_data.ts
@@ -9,6 +9,7 @@ import { assetUtils } from '../util/asset';
import { buyQuoteUpdater } from '../util/buy_quote_updater';
import { coinbaseApi } from '../util/coinbase_api';
import { errorFlasher } from '../util/error_flasher';
+import { errorReporter } from '../util/error_reporter';
import { actions } from './actions';
import { State } from './reducer';
@@ -22,6 +23,7 @@ export const asyncData = {
const errorMessage = 'Error fetching ETH/USD price';
errorFlasher.flashNewErrorMessage(dispatch, errorMessage);
dispatch(actions.updateEthUsdPrice(BIG_NUMBER_ZERO));
+ errorReporter.report(e);
}
},
fetchAvailableAssetDatasAndDispatchToStore: async (state: State, dispatch: Dispatch) => {
@@ -36,6 +38,7 @@ export const asyncData = {
errorFlasher.flashNewErrorMessage(dispatch, errorMessage);
// On error, just specify that none are available
dispatch(actions.setAvailableAssets([]));
+ errorReporter.report(e);
}
},
fetchAccountInfoAndDispatchToStore: async (
@@ -75,6 +78,7 @@ export const asyncData = {
const ethBalanceInWei = await web3Wrapper.getBalanceInWeiAsync(address);
dispatch(actions.updateAccountEthBalance({ address, ethBalanceInWei }));
} catch (e) {
+ errorReporter.report(e);
// leave balance as is
return;
}
diff --git a/packages/instant/src/util/asset.ts b/packages/instant/src/util/asset.ts
index 40560d3eb..08f3642e3 100644
--- a/packages/instant/src/util/asset.ts
+++ b/packages/instant/src/util/asset.ts
@@ -1,3 +1,4 @@
+import { AssetBuyerError } from '@0x/asset-buyer';
import { AssetProxyId, ObjectMap } from '@0x/types';
import * as _ from 'lodash';
@@ -106,4 +107,20 @@ export const assetUtils = {
);
return _.compact(erc20sOrUndefined);
},
+ assetBuyerErrorMessage: (asset: ERC20Asset, error: Error): string | undefined => {
+ if (error.message === AssetBuyerError.InsufficientAssetLiquidity) {
+ const assetName = assetUtils.bestNameForAsset(asset, 'of this asset');
+ return `Not enough ${assetName} available`;
+ } else if (error.message === AssetBuyerError.InsufficientZrxLiquidity) {
+ return 'Not enough ZRX available';
+ } else if (
+ error.message === AssetBuyerError.StandardRelayerApiError ||
+ error.message.startsWith(AssetBuyerError.AssetUnavailable)
+ ) {
+ const assetName = assetUtils.bestNameForAsset(asset, 'This asset');
+ return `${assetName} is currently unavailable`;
+ }
+
+ return undefined;
+ },
};
diff --git a/packages/instant/src/util/buy_quote_updater.ts b/packages/instant/src/util/buy_quote_updater.ts
index 2fd16d781..4f97622e1 100644
--- a/packages/instant/src/util/buy_quote_updater.ts
+++ b/packages/instant/src/util/buy_quote_updater.ts
@@ -9,6 +9,7 @@ import { Action, actions } from '../redux/actions';
import { AffiliateInfo, ERC20Asset } from '../types';
import { assetUtils } from '../util/asset';
import { errorFlasher } from '../util/error_flasher';
+import { errorReporter } from '../util/error_reporter';
export const buyQuoteUpdater = {
updateBuyQuoteAsync: async (
@@ -29,29 +30,17 @@ export const buyQuoteUpdater = {
try {
newBuyQuote = await assetBuyer.getBuyQuoteAsync(asset.assetData, baseUnitValue, { feePercentage });
} catch (error) {
+ const errorMessage = assetUtils.assetBuyerErrorMessage(asset, error);
+
+ if (_.isUndefined(errorMessage)) {
+ // This is an unknown error, report it to rollbar
+ errorReporter.report(error);
+ }
+
if (options.dispatchErrors) {
dispatch(actions.setQuoteRequestStateFailure());
- let errorMessage;
- if (error.message === AssetBuyerError.InsufficientAssetLiquidity) {
- const assetName = assetUtils.bestNameForAsset(asset, 'of this asset');
- errorMessage = `Not enough ${assetName} available`;
- } else if (error.message === AssetBuyerError.InsufficientZrxLiquidity) {
- errorMessage = 'Not enough ZRX available';
- } else if (
- error.message === AssetBuyerError.StandardRelayerApiError ||
- error.message.startsWith(AssetBuyerError.AssetUnavailable)
- ) {
- const assetName = assetUtils.bestNameForAsset(asset, 'This asset');
- errorMessage = `${assetName} is currently unavailable`;
- }
- if (!_.isUndefined(errorMessage)) {
- errorFlasher.flashNewErrorMessage(dispatch, errorMessage);
- } else {
- throw error;
- }
+ errorFlasher.flashNewErrorMessage(dispatch, errorMessage || 'Error fetching price, please try again');
}
- // TODO: report to error reporter on else
-
return;
}
// We have a successful new buy quote
diff --git a/packages/instant/src/util/error_reporter.ts b/packages/instant/src/util/error_reporter.ts
new file mode 100644
index 000000000..89c5b6bd3
--- /dev/null
+++ b/packages/instant/src/util/error_reporter.ts
@@ -0,0 +1,61 @@
+import { logUtils } from '@0x/utils';
+import * as _ from 'lodash';
+
+import { INSTANT_ENVIRONMENT, ROLLBAR_CLIENT_TOKEN, ROLLBAR_ENABLED } from '../constants';
+
+// Import version of Rollbar designed for embedded components
+// See https://docs.rollbar.com/docs/using-rollbarjs-inside-an-embedded-component
+// tslint:disable-next-line:no-var-requires
+const Rollbar = require('rollbar/dist/rollbar.noconflict.umd');
+
+let rollbar: any;
+// Configures rollbar and sets up error catching
+export const setupRollbar = (): any => {
+ if (_.isUndefined(rollbar) && ROLLBAR_CLIENT_TOKEN && INSTANT_ENVIRONMENT && ROLLBAR_ENABLED) {
+ rollbar = new Rollbar({
+ accessToken: ROLLBAR_CLIENT_TOKEN,
+ captureUncaught: true,
+ captureUnhandledRejections: true,
+ enabled: true,
+ itemsPerMinute: 10,
+ maxItems: 500,
+ payload: {
+ environment: INSTANT_ENVIRONMENT,
+ client: {
+ javascript: {
+ source_map_enabled: true,
+ code_version: process.env.GIT_SHA,
+ guess_uncaught_frames: true,
+ },
+ },
+ },
+ uncaughtErrorLevel: 'error',
+ ignoredMessages: [
+ // Errors from the third-party scripts
+ 'Script error',
+ // Network errors or ad-blockers
+ 'TypeError: Failed to fetch',
+ 'Exchange has not been deployed to detected network (network/artifact mismatch)',
+ // Source: https://groups.google.com/a/chromium.org/forum/#!topic/chromium-discuss/7VU0_VvC7mE
+ "undefined is not an object (evaluating '__gCrWeb.autofill.extractForms')",
+ // Source: http://stackoverflow.com/questions/43399818/securityerror-from-facebook-and-cross-domain-messaging
+ 'SecurityError (DOM Exception 18)',
+ ],
+ });
+ }
+};
+
+export const errorReporter = {
+ report(err: Error): void {
+ if (!rollbar) {
+ logUtils.log('Not reporting to rollbar because not configured', err);
+ return;
+ }
+
+ rollbar.error(err, (rollbarErr: Error) => {
+ if (rollbarErr) {
+ logUtils.log(`Error reporting to rollbar, ignoring: ${rollbarErr}`);
+ }
+ });
+ },
+};
diff --git a/packages/instant/src/util/gas_price_estimator.ts b/packages/instant/src/util/gas_price_estimator.ts
index 6b15809a3..332c8d00a 100644
--- a/packages/instant/src/util/gas_price_estimator.ts
+++ b/packages/instant/src/util/gas_price_estimator.ts
@@ -7,6 +7,8 @@ import {
GWEI_IN_WEI,
} from '../constants';
+import { errorReporter } from './error_reporter';
+
interface EthGasStationResult {
average: number;
fastestWait: number;
@@ -42,8 +44,9 @@ export class GasPriceEstimator {
let fetchedAmount: GasInfo | undefined;
try {
fetchedAmount = await fetchFastAmountInWeiAsync();
- } catch {
+ } catch (e) {
fetchedAmount = undefined;
+ errorReporter.report(e);
}
if (fetchedAmount) {
diff --git a/packages/instant/src/util/heap.ts b/packages/instant/src/util/heap.ts
index 78ec3b3cc..10670b278 100644
--- a/packages/instant/src/util/heap.ts
+++ b/packages/instant/src/util/heap.ts
@@ -5,6 +5,7 @@ import * as _ from 'lodash';
import { HEAP_ANALYTICS_ID } from '../constants';
import { AnalyticsEventOptions, AnalyticsUserOptions } from './analytics';
+import { errorReporter } from './error_reporter';
export interface HeapAnalytics {
loaded: boolean;
@@ -105,8 +106,8 @@ export const heapUtil = {
heapFunctionCall(curHeap);
} catch (e) {
// We never want analytics to crash our React component
- // TODO(sk): error reporter here
logUtils.log('Analytics error', e);
+ errorReporter.report(e);
}
}
},
diff --git a/packages/instant/test/util/asset.test.ts b/packages/instant/test/util/asset.test.ts
index 4229b24ed..fc4e4e2e4 100644
--- a/packages/instant/test/util/asset.test.ts
+++ b/packages/instant/test/util/asset.test.ts
@@ -1,6 +1,7 @@
+import { AssetBuyerError } from '@0x/asset-buyer';
import { AssetProxyId, ObjectMap } from '@0x/types';
-import { Asset, AssetMetaData, ERC20AssetMetaData, Network, ZeroExInstantError } from '../../src/types';
+import { Asset, AssetMetaData, ERC20Asset, ERC20AssetMetaData, Network, ZeroExInstantError } from '../../src/types';
import { assetUtils } from '../../src/util/asset';
const ZRX_ASSET_DATA = '0xf47261b0000000000000000000000000e41d2489571d322189246dafa5ebde1f4699f498';
@@ -11,7 +12,7 @@ const ZRX_META_DATA: ERC20AssetMetaData = {
decimals: 18,
name: '0x',
};
-const ZRX_ASSET: Asset = {
+const ZRX_ASSET: ERC20Asset = {
assetData: ZRX_ASSET_DATA,
metaData: ZRX_META_DATA,
};
@@ -45,4 +46,32 @@ describe('assetDataUtil', () => {
).toThrowError(ZeroExInstantError.AssetMetaDataNotAvailable);
});
});
+ describe('assetBuyerErrorMessage', () => {
+ it('should return message for InsufficientAssetLiquidity', () => {
+ const insufficientAssetError = new Error(AssetBuyerError.InsufficientAssetLiquidity);
+ expect(assetUtils.assetBuyerErrorMessage(ZRX_ASSET, insufficientAssetError)).toEqual(
+ 'Not enough ZRX available',
+ );
+ });
+ it('should return message for InsufficientAssetLiquidity', () => {
+ const insufficientZrxError = new Error(AssetBuyerError.InsufficientZrxLiquidity);
+ expect(assetUtils.assetBuyerErrorMessage(ZRX_ASSET, insufficientZrxError)).toEqual(
+ 'Not enough ZRX available',
+ );
+ });
+ it('should message for StandardRelayerApiError', () => {
+ const standardRelayerError = new Error(AssetBuyerError.StandardRelayerApiError);
+ expect(assetUtils.assetBuyerErrorMessage(ZRX_ASSET, standardRelayerError)).toEqual(
+ 'ZRX is currently unavailable',
+ );
+ });
+ it('should return error for AssetUnavailable error', () => {
+ const assetUnavailableError = new Error(
+ `${AssetBuyerError.AssetUnavailable}: For assetData ${ZRX_ASSET_DATA}`,
+ );
+ expect(assetUtils.assetBuyerErrorMessage(ZRX_ASSET, assetUnavailableError)).toEqual(
+ 'ZRX is currently unavailable',
+ );
+ });
+ });
});
diff --git a/packages/instant/webpack.config.js b/packages/instant/webpack.config.js
index 41276809c..7149793c4 100644
--- a/packages/instant/webpack.config.js
+++ b/packages/instant/webpack.config.js
@@ -1,6 +1,7 @@
const childProcess = require('child_process');
const ip = require('ip');
const path = require('path');
+const RollbarSourceMapPlugin = require('rollbar-sourcemap-webpack-plugin');
const webpack = require('webpack');
// The common js bundle (not this one) is built using tsc.
@@ -11,22 +12,104 @@ const GIT_SHA = childProcess
.toString()
.trim();
-const HEAP_PRODUCTION_ENV_VAR_NAME = 'INSTANT_HEAP_ANALYTICS_ID_PRODUCTION';
-const HEAP_DEVELOPMENT_ENV_VAR_NAME = 'INSTANT_HEAP_ANALYTICS_ID_DEVELOPMENT';
-const getHeapAnalyticsId = modeName => {
- if (modeName === 'production') {
- return process.env[HEAP_PRODUCTION_ENV_VAR_NAME];
+const getEnvironmentName = (env, argv) => {
+ if (env && env.dogfood) {
+ return 'dogfood';
+ } else if (env && env.staging) {
+ return 'staging';
}
- if (modeName === 'development') {
- return process.env[HEAP_DEVELOPMENT_ENV_VAR_NAME];
+ // argv.mode should be 'development' or 'production'
+ return argv.mode;
+};
+
+const getHeapAnalyticsId = environmentName => {
+ if (environmentName === 'production') {
+ return process.env['INSTANT_HEAP_ANALYTICS_ID_PRODUCTION'];
+ }
+
+ if (environmentName === 'development' || environmentName === 'dogfood' || environmentName === 'staging') {
+ return process.env['INSTANT_HEAP_ANALYTICS_ID_DEVELOPMENT'];
}
return undefined;
};
+const ROLLBAR_PUBLISH_TOKEN_ENV_NAME = 'INSTANT_ROLLBAR_PUBLISH_TOKEN';
+const ROLLBAR_CLIENT_TOKEN_ENV_NAME = 'INSTANT_ROLLBAR_CLIENT_TOKEN';
+const getRollbarSourceMapPlugin = environmentName => {
+ if (!environmentName) {
+ return undefined;
+ }
+
+ const publishToken = process.env[ROLLBAR_PUBLISH_TOKEN_ENV_NAME];
+ if (!publishToken) {
+ return undefined;
+ }
+
+ let rollbarPublicPath;
+ if (environmentName === 'dogfood') {
+ rollbarPublicPath = 'http://0x-instant-dogfood.s3-website-us-east-1.amazonaws.com';
+ } else if (environmentName === 'staging') {
+ rollbarPublicPath = 'http://0x-instant-staging.s3-website-us-east-1.amazonaws.com';
+ } // TODO(sk): When we decide on JS cdn, add public path here
+
+ if (!rollbarPublicPath) {
+ console.log('No rollbar public path');
+ return undefined;
+ }
+
+ const rollbarPluginOptions = {
+ accessToken: publishToken,
+ version: GIT_SHA,
+ publicPath: rollbarPublicPath,
+ };
+ return new RollbarSourceMapPlugin(rollbarPluginOptions);
+};
+const validateRollbarPresence = (environmentName, rollbarEnabled, rollbarSourceMapPlugin) => {
+ const requiresRollbar = environmentName === 'dogfood' || environmentName === 'staging';
+ if (!requiresRollbar) {
+ return;
+ }
+ if (!rollbarEnabled || !rollbarSourceMapPlugin) {
+ throw new Error(
+ `Rollbar env vars must be set to build for ${environmentName}. Please set ${ROLLBAR_CLIENT_TOKEN_ENV_NAME} to a rollbar access token with post_client_item permissions, and ${ROLLBAR_PUBLISH_TOKEN_ENV_NAME} to a rollbar access token with post_server_item permissions.`,
+ );
+ }
+};
+
module.exports = (env, argv) => {
+ const environmentName = getEnvironmentName(env, argv);
const outputPath = process.env.WEBPACK_OUTPUT_PATH || 'umd';
+
+ const envVars = {
+ GIT_SHA: JSON.stringify(GIT_SHA),
+ NPM_PACKAGE_VERSION: JSON.stringify(process.env.npm_package_version),
+ HEAP_ANALYTICS_ID: getHeapAnalyticsId(environmentName),
+ INSTANT_ENVIRONMENT: JSON.stringify(environmentName),
+ ROLLBAR_CLIENT_TOKEN: JSON.stringify(process.env[ROLLBAR_CLIENT_TOKEN_ENV_NAME]),
+ };
+
+ const canRollbarBeEnabled =
+ environmentName === 'development' ? process.env.INSTANT_ROLLBAR_FORCE_DEVELOPMENT_REPORT : true;
+ if (envVars.INSTANT_ENVIRONMENT && envVars.ROLLBAR_CLIENT_TOKEN && canRollbarBeEnabled) {
+ envVars['ROLLBAR_ENABLED'] = JSON.stringify(true);
+ }
+
+ let plugins = [
+ new webpack.DefinePlugin({
+ 'process.env': envVars,
+ }),
+ ];
+ const rollbarSourceMapPlugin = getRollbarSourceMapPlugin(environmentName);
+ if (rollbarSourceMapPlugin) {
+ console.log('Using rollbar source map plugin');
+ plugins = plugins.concat(rollbarSourceMapPlugin);
+ } else {
+ console.log('Not using rollbar source map plugin');
+ }
+ validateRollbarPresence(environmentName, envVars['ROLLBAR_ENABLED'], rollbarSourceMapPlugin);
+
const config = {
entry: {
instant: './src/index.umd.ts',
@@ -37,15 +120,7 @@ module.exports = (env, argv) => {
library: 'zeroExInstant',
libraryTarget: 'umd',
},
- plugins: [
- new webpack.DefinePlugin({
- 'process.env': {
- GIT_SHA: JSON.stringify(GIT_SHA),
- HEAP_ANALYTICS_ID: getHeapAnalyticsId(argv.mode),
- NPM_PACKAGE_VERSION: JSON.stringify(process.env.npm_package_version),
- },
- }),
- ],
+ plugins,
devtool: 'source-map',
resolve: {
extensions: ['.js', '.json', '.ts', '.tsx'],
@@ -60,6 +135,15 @@ module.exports = (env, argv) => {
test: /\.svg$/,
loader: 'svg-react-loader',
},
+ {
+ test: /\.js$/,
+ loader: 'source-map-loader',
+ exclude: [
+ // instead of /\/node_modules\//
+ path.join(process.cwd(), 'node_modules'),
+ path.join(process.cwd(), '../..', 'node_modules'),
+ ],
+ },
],
},
devServer: {