Electron App (#854)
* Basic webpack build started. * Get build working with electron-packager. Not fully satisfied, might investigate electron-builder. * Custom title bar * Rewrite all webpack configs to use common function. Organize webpack utils. Split into multiple dist folders. * Replace electron build with electron-builder. Leave around packager for a bit. * Check in progress on updater. * Update modal flow. * Fix tscheck. * Adjust publish info. * Arbitrary version bump. * Bump version again. * 5.0.2 bump fix autodownload. * 5.0.2 bump again, readd dmg * 5.0.3 bump * Turn auto update back off. Log errors. Revert versions. * Add os-specific builds. Improve update failure. * Open external links in browser in electron. * Remove custom title bar temporarily. * Add info about the update download to the modal. * Turn off development changes. * Take the postBuild sorting script and move it into a webpack config. * Initial conversion to typescript and electron-webpack. * Switch from electron-webpack back to custom config, clean up unused code, typify electron bridge. * Better typing for bridge. * Remove unnecessary file. * Reminify. * Add shared folder resolving to jest config. * Add enum to electron events
This commit is contained in:
parent
81beab8bc0
commit
182eaa4329
|
@ -13,6 +13,7 @@ import BroadcastTx from 'containers/Tabs/BroadcastTx';
|
||||||
import ErrorScreen from 'components/ErrorScreen';
|
import ErrorScreen from 'components/ErrorScreen';
|
||||||
import PageNotFound from 'components/PageNotFound';
|
import PageNotFound from 'components/PageNotFound';
|
||||||
import LogOutPrompt from 'components/LogOutPrompt';
|
import LogOutPrompt from 'components/LogOutPrompt';
|
||||||
|
import { TitleBar } from 'components/ui';
|
||||||
import { Store } from 'redux';
|
import { Store } from 'redux';
|
||||||
import { pollOfflineStatus } from 'actions/config';
|
import { pollOfflineStatus } from 'actions/config';
|
||||||
import { AppState } from 'reducers';
|
import { AppState } from 'reducers';
|
||||||
|
@ -73,12 +74,16 @@ export default class Root extends Component<Props, State> {
|
||||||
</CaptureRouteNotFound>
|
</CaptureRouteNotFound>
|
||||||
);
|
);
|
||||||
|
|
||||||
const Router = process.env.BUILD_DOWNLOADABLE ? HashRouter : BrowserRouter;
|
const Router =
|
||||||
|
process.env.BUILD_DOWNLOADABLE && process.env.NODE_ENV === 'production'
|
||||||
|
? HashRouter
|
||||||
|
: BrowserRouter;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Provider store={store} key={Math.random()}>
|
<Provider store={store} key={Math.random()}>
|
||||||
<Router key={Math.random()}>
|
<Router key={Math.random()}>
|
||||||
<React.Fragment>
|
<React.Fragment>
|
||||||
|
{process.env.BUILD_ELECTRON && <TitleBar />}
|
||||||
{routes}
|
{routes}
|
||||||
<LegacyRoutes />
|
<LegacyRoutes />
|
||||||
<LogOutPrompt />
|
<LogOutPrompt />
|
||||||
|
|
|
@ -0,0 +1,61 @@
|
||||||
|
@import 'common/sass/variables';
|
||||||
|
|
||||||
|
@keyframes new-update-popin {
|
||||||
|
0% {
|
||||||
|
opacity: 0;
|
||||||
|
transform: scale(0.7);
|
||||||
|
}
|
||||||
|
60% {
|
||||||
|
opacity: 1;
|
||||||
|
transform: scale(1.2);
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
opacity: 1;
|
||||||
|
transform: scale(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes new-update-glow {
|
||||||
|
0% {
|
||||||
|
opacity: 1;
|
||||||
|
transform: scale(1);
|
||||||
|
},
|
||||||
|
80%, 100% {
|
||||||
|
opacity: 0;
|
||||||
|
transform: scale(2.5);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.Version {
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
&-text {
|
||||||
|
&.has-update:hover {
|
||||||
|
cursor: pointer;
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&-new {
|
||||||
|
position: absolute;
|
||||||
|
top: 0px;
|
||||||
|
left: -12px;
|
||||||
|
width: 8px;
|
||||||
|
height: 8px;
|
||||||
|
border-radius: 100%;
|
||||||
|
background: $brand-warning;
|
||||||
|
animation: new-update-popin 500ms ease 1;
|
||||||
|
|
||||||
|
&:before {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
border-radius: 100%;
|
||||||
|
box-shadow: 0 0 3px $brand-warning;
|
||||||
|
animation: new-update-glow 1200ms ease infinite;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,48 @@
|
||||||
|
import React from 'react';
|
||||||
|
import { VERSION } from 'config/data';
|
||||||
|
import UpdateModal, { UpdateInfo } from 'components/UpdateModal';
|
||||||
|
import { addListener } from 'utils/electron';
|
||||||
|
import EVENTS from 'shared/electronEvents';
|
||||||
|
import './Version.scss';
|
||||||
|
|
||||||
|
interface State {
|
||||||
|
updateInfo: UpdateInfo | null;
|
||||||
|
isModalOpen: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default class Version extends React.Component<{}, State> {
|
||||||
|
public state: State = {
|
||||||
|
updateInfo: null,
|
||||||
|
isModalOpen: false
|
||||||
|
};
|
||||||
|
|
||||||
|
public componentDidMount() {
|
||||||
|
addListener(EVENTS.UPDATE.UPDATE_AVAILABLE, updateInfo => {
|
||||||
|
this.setState({ updateInfo });
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public render() {
|
||||||
|
const { updateInfo, isModalOpen } = this.state;
|
||||||
|
return (
|
||||||
|
<div className="Version">
|
||||||
|
<span className={`Version-text ${updateInfo ? 'has-update' : ''}`} onClick={this.openModal}>
|
||||||
|
v{VERSION}
|
||||||
|
</span>
|
||||||
|
{updateInfo && (
|
||||||
|
<span>
|
||||||
|
<span className="Version-new" />
|
||||||
|
<UpdateModal
|
||||||
|
isOpen={isModalOpen}
|
||||||
|
updateInfo={updateInfo}
|
||||||
|
handleClose={this.closeModal}
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private openModal = () => this.setState({ isModalOpen: true });
|
||||||
|
private closeModal = () => this.setState({ isModalOpen: false });
|
||||||
|
}
|
|
@ -16,7 +16,6 @@ import {
|
||||||
ANNOUNCEMENT_TYPE,
|
ANNOUNCEMENT_TYPE,
|
||||||
languages,
|
languages,
|
||||||
NODES,
|
NODES,
|
||||||
VERSION,
|
|
||||||
NodeConfig,
|
NodeConfig,
|
||||||
CustomNodeConfig,
|
CustomNodeConfig,
|
||||||
CustomNetworkConfig
|
CustomNetworkConfig
|
||||||
|
@ -25,6 +24,7 @@ import GasPriceDropdown from './components/GasPriceDropdown';
|
||||||
import Navigation from './components/Navigation';
|
import Navigation from './components/Navigation';
|
||||||
import CustomNodeModal from './components/CustomNodeModal';
|
import CustomNodeModal from './components/CustomNodeModal';
|
||||||
import OnlineStatus from './components/OnlineStatus';
|
import OnlineStatus from './components/OnlineStatus';
|
||||||
|
import Version from './components/Version';
|
||||||
import { getKeyByValue } from 'utils/helpers';
|
import { getKeyByValue } from 'utils/helpers';
|
||||||
import { makeCustomNodeId } from 'utils/node';
|
import { makeCustomNodeId } from 'utils/node';
|
||||||
import { getNetworkConfigFromId } from 'utils/network';
|
import { getNetworkConfigFromId } from 'utils/network';
|
||||||
|
@ -128,7 +128,9 @@ export default class Header extends Component<Props, State> {
|
||||||
/>
|
/>
|
||||||
</Link>
|
</Link>
|
||||||
<div className="Header-branding-right">
|
<div className="Header-branding-right">
|
||||||
<span className="Header-branding-right-version hidden-xs">v{VERSION}</span>
|
<span className="Header-branding-right-version hidden-xs">
|
||||||
|
<Version />
|
||||||
|
</span>
|
||||||
|
|
||||||
<div className="Header-branding-right-online">
|
<div className="Header-branding-right-online">
|
||||||
<OnlineStatus isOffline={isOffline} />
|
<OnlineStatus isOffline={isOffline} />
|
||||||
|
|
|
@ -0,0 +1,62 @@
|
||||||
|
@import 'common/sass/variables';
|
||||||
|
|
||||||
|
.UpdateModal {
|
||||||
|
cursor: default;
|
||||||
|
@media (min-width: 680px) {
|
||||||
|
min-width: 680px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-title {
|
||||||
|
margin-top: 0;
|
||||||
|
margin-bottom: $space-xs;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-date {
|
||||||
|
font-size: $font-size-small;
|
||||||
|
color: $gray;
|
||||||
|
padding-bottom: $space-sm;
|
||||||
|
border-bottom: 1px solid $gray-lighter;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-downloader {
|
||||||
|
padding: 50px 30px 80px;
|
||||||
|
text-align: center;
|
||||||
|
|
||||||
|
&-bar {
|
||||||
|
position: relative;
|
||||||
|
margin: 0 20px;
|
||||||
|
height: 16px;
|
||||||
|
border-radius: 4px;
|
||||||
|
background: $gray-lighter;
|
||||||
|
margin-bottom: $space-sm;
|
||||||
|
|
||||||
|
&-inner {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
bottom: 0;
|
||||||
|
border-radius: 4px;
|
||||||
|
background: $brand-primary;
|
||||||
|
transition: width 100ms ease;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&-info {
|
||||||
|
color: $gray-light;
|
||||||
|
font-size: $font-size-xs;
|
||||||
|
|
||||||
|
&-bit {
|
||||||
|
&:after {
|
||||||
|
display: inline-block;
|
||||||
|
content: "•";
|
||||||
|
padding: 0 $space-xs;
|
||||||
|
opacity: 0.8;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:last-child:after {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,142 @@
|
||||||
|
import React from 'react';
|
||||||
|
import { connect } from 'react-redux';
|
||||||
|
import moment from 'moment';
|
||||||
|
import { showNotification, TShowNotification } from 'actions/notifications';
|
||||||
|
import { Spinner, NewTabLink } from 'components/ui';
|
||||||
|
import Modal, { IButton } from 'components/ui/Modal';
|
||||||
|
import { addListener, sendEvent } from 'utils/electron';
|
||||||
|
import EVENTS from 'shared/electronEvents';
|
||||||
|
import { bytesToHuman } from 'utils/formatters';
|
||||||
|
import './UpdateModal.scss';
|
||||||
|
|
||||||
|
export interface UpdateInfo {
|
||||||
|
version: string;
|
||||||
|
sha512: string;
|
||||||
|
releaseDate: string;
|
||||||
|
releaseName: string;
|
||||||
|
releaseNotes: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface DownloadProgress {
|
||||||
|
bytesPerSecond: number;
|
||||||
|
percent: number;
|
||||||
|
transferred: number;
|
||||||
|
total: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
isOpen: boolean;
|
||||||
|
updateInfo: UpdateInfo;
|
||||||
|
showNotification: TShowNotification;
|
||||||
|
handleClose(): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface State {
|
||||||
|
isDownloading: boolean;
|
||||||
|
downloadProgress: DownloadProgress | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
class UpdateModal extends React.Component<Props, State> {
|
||||||
|
public state: State = {
|
||||||
|
isDownloading: false,
|
||||||
|
downloadProgress: null
|
||||||
|
};
|
||||||
|
|
||||||
|
public componentDidMount() {
|
||||||
|
addListener(EVENTS.UPDATE.UPDATE_DOWNLOADED, () => {
|
||||||
|
sendEvent(EVENTS.UPDATE.QUIT_AND_INSTALL);
|
||||||
|
});
|
||||||
|
addListener(EVENTS.UPDATE.DOWNLOAD_PROGRESS, downloadProgress => {
|
||||||
|
this.setState({ downloadProgress });
|
||||||
|
});
|
||||||
|
addListener(EVENTS.UPDATE.ERROR, err => {
|
||||||
|
console.error('Update failed:', err);
|
||||||
|
this.setState({ isDownloading: false });
|
||||||
|
this.props.showNotification(
|
||||||
|
'danger',
|
||||||
|
<span>
|
||||||
|
Update could not be downloaded, please visit{' '}
|
||||||
|
<NewTabLink href="https://github.com/MyEtherWallet/MyEtherWallet/releases">
|
||||||
|
our github
|
||||||
|
</NewTabLink>{' '}
|
||||||
|
to download the latest release
|
||||||
|
</span>,
|
||||||
|
Infinity
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public render() {
|
||||||
|
const { isOpen, updateInfo, handleClose } = this.props;
|
||||||
|
const { isDownloading, downloadProgress } = this.state;
|
||||||
|
const buttons: IButton[] | undefined = downloadProgress
|
||||||
|
? undefined
|
||||||
|
: [
|
||||||
|
{
|
||||||
|
text: <span>{isDownloading && <Spinner />} Download Update</span>,
|
||||||
|
type: 'primary',
|
||||||
|
onClick: this.downloadUpdate,
|
||||||
|
disabled: isDownloading
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: 'Close',
|
||||||
|
type: 'default',
|
||||||
|
onClick: handleClose
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Modal
|
||||||
|
isOpen={isOpen}
|
||||||
|
title={`Update Information`}
|
||||||
|
handleClose={handleClose}
|
||||||
|
buttons={buttons}
|
||||||
|
>
|
||||||
|
<div className="UpdateModal">
|
||||||
|
{downloadProgress ? (
|
||||||
|
<div className="UpdateModal-downloader">
|
||||||
|
<h3 className="UpdateModal-downloader-title">Downloading...</h3>
|
||||||
|
<div className="UpdateModal-downloader-bar">
|
||||||
|
<div
|
||||||
|
className="UpdateModal-downloader-bar-inner"
|
||||||
|
style={{
|
||||||
|
width: `${downloadProgress.percent}%`
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="UpdateModal-downloader-info">
|
||||||
|
<span className="UpdateModal-downloader-info-bit">
|
||||||
|
Downloaded {downloadProgress.percent.toFixed(1)}%
|
||||||
|
</span>
|
||||||
|
<span className="UpdateModal-downloader-info-bit">
|
||||||
|
{bytesToHuman(downloadProgress.bytesPerSecond)}/s
|
||||||
|
</span>
|
||||||
|
<span className="UpdateModal-downloader-info-bit">
|
||||||
|
Total Size {bytesToHuman(downloadProgress.total)}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div>
|
||||||
|
<h1 className="UpdateModal-title">{updateInfo.releaseName}</h1>
|
||||||
|
<div className="UpdateModal-date">{moment(updateInfo.releaseDate).format('LL')}</div>
|
||||||
|
<div
|
||||||
|
className="UpdateModal-content"
|
||||||
|
dangerouslySetInnerHTML={{
|
||||||
|
__html: updateInfo.releaseNotes
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private downloadUpdate = () => {
|
||||||
|
this.setState({ isDownloading: true });
|
||||||
|
sendEvent('UPDATE:download-update');
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export default connect(undefined, { showNotification })(UpdateModal);
|
|
@ -1,4 +1,5 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import { openInBrowser } from 'utils/electron';
|
||||||
|
|
||||||
interface AAttributes {
|
interface AAttributes {
|
||||||
charset?: string;
|
charset?: string;
|
||||||
|
@ -35,10 +36,21 @@ interface NewTabLinkProps extends AAttributes {
|
||||||
children?: React.ReactElement<any> | string;
|
children?: React.ReactElement<any> | string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const NewTabLink = ({ content, children, ...rest }: NewTabLinkProps) => (
|
export class NewTabLink extends React.Component<NewTabLinkProps> {
|
||||||
<a target="_blank" rel="noopener noreferrer" {...rest}>
|
public render() {
|
||||||
{content || children}
|
const { content, children, ...rest } = this.props;
|
||||||
</a>
|
return (
|
||||||
);
|
<a target="_blank" rel="noopener noreferrer" onClick={this.handleClick} {...rest}>
|
||||||
|
{content || children}
|
||||||
|
</a>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private handleClick(ev: React.MouseEvent<HTMLAnchorElement>) {
|
||||||
|
if (openInBrowser(ev.currentTarget.href)) {
|
||||||
|
ev.preventDefault();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export default NewTabLink;
|
export default NewTabLink;
|
||||||
|
|
|
@ -0,0 +1,27 @@
|
||||||
|
@import 'common/sass/variables';
|
||||||
|
|
||||||
|
$height: 22px;
|
||||||
|
|
||||||
|
// TODO - Implement styles for custom title bar on all platforms
|
||||||
|
.TitleBar,
|
||||||
|
.TitleBarPlaceholder {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.TitleBar {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
height: $height;
|
||||||
|
line-height: $height;
|
||||||
|
-webkit-user-select: none;
|
||||||
|
-webkit-app-region: drag;
|
||||||
|
background: $body-bg;
|
||||||
|
z-index: $zindex-top;
|
||||||
|
box-shadow: 0 1px 1px rgba(#000, 0.08);
|
||||||
|
}
|
||||||
|
|
||||||
|
.TitleBarPlaceholder {
|
||||||
|
height: $height;
|
||||||
|
}
|
|
@ -0,0 +1,11 @@
|
||||||
|
import React from 'react';
|
||||||
|
import './TitleBar.scss';
|
||||||
|
|
||||||
|
const TitleBar: React.SFC<{}> = () => (
|
||||||
|
<React.Fragment>
|
||||||
|
<div className="TitleBar" />
|
||||||
|
<div className="TitleBarPlaceholder" />
|
||||||
|
</React.Fragment>
|
||||||
|
);
|
||||||
|
|
||||||
|
export default TitleBar;
|
|
@ -10,5 +10,6 @@ export { default as UnitDisplay } from './UnitDisplay';
|
||||||
export { default as Spinner } from './Spinner';
|
export { default as Spinner } from './Spinner';
|
||||||
export { default as SwapDropdown } from './SwapDropdown';
|
export { default as SwapDropdown } from './SwapDropdown';
|
||||||
export { default as Tooltip } from './Tooltip';
|
export { default as Tooltip } from './Tooltip';
|
||||||
|
export { default as TitleBar } from './TitleBar';
|
||||||
export * from './ConditionalInput';
|
export * from './ConditionalInput';
|
||||||
export * from './Expandable';
|
export * from './Expandable';
|
||||||
|
|
|
@ -0,0 +1,25 @@
|
||||||
|
// Handles integrations with Electron. Wherever possible, should stub out
|
||||||
|
// behavior with noop's if not in the Electron environment, to simplify code.
|
||||||
|
import { ElectronBridgeFunctions } from 'shared/electronBridge';
|
||||||
|
const bridge: ElectronBridgeFunctions | null = (window as any).electronBridge;
|
||||||
|
|
||||||
|
export const addListener: ElectronBridgeFunctions['addListener'] = (event, cb) => {
|
||||||
|
if (bridge && bridge.addListener) {
|
||||||
|
// @ts-ignore unused ev
|
||||||
|
bridge.addListener(event, (ev, data) => cb(data));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const sendEvent: ElectronBridgeFunctions['sendEvent'] = (event, data) => {
|
||||||
|
if (bridge && bridge.sendEvent) {
|
||||||
|
bridge.sendEvent(event, data);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const openInBrowser: ElectronBridgeFunctions['openInBrowser'] = url => {
|
||||||
|
if (bridge && bridge.openInBrowser) {
|
||||||
|
bridge.openInBrowser(url);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
};
|
|
@ -100,3 +100,12 @@ export function formatGasLimit(limit: Wei, transactionUnit: string = 'ether') {
|
||||||
export function formatMnemonic(phrase: string) {
|
export function formatMnemonic(phrase: string) {
|
||||||
return phrase.replace(/(\r\n|\n|\r|\s+|,)/gm, ' ').trim();
|
return phrase.replace(/(\r\n|\n|\r|\s+|,)/gm, ' ').trim();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function bytesToHuman(bytes: number) {
|
||||||
|
if (bytes <= 0) {
|
||||||
|
return '0 b';
|
||||||
|
}
|
||||||
|
const sizes = ['b', 'kb', 'mb', 'gb', 'tb'];
|
||||||
|
const i = Math.round(Math.floor(Math.log(bytes) / Math.log(1024)));
|
||||||
|
return Math.round(bytes / Math.pow(1024, i)) + ' ' + sizes[i];
|
||||||
|
}
|
||||||
|
|
Binary file not shown.
Binary file not shown.
After Width: | Height: | Size: 58 KiB |
|
@ -0,0 +1,86 @@
|
||||||
|
import { app, BrowserWindow, Menu } from 'electron';
|
||||||
|
import * as path from 'path';
|
||||||
|
import updater from './updater';
|
||||||
|
import MENU from './menu';
|
||||||
|
|
||||||
|
const isDevelopment = process.env.NODE_ENV !== 'production';
|
||||||
|
|
||||||
|
// Global reference to mainWindow
|
||||||
|
// Necessary to prevent window from being garbage collected
|
||||||
|
let mainWindow;
|
||||||
|
|
||||||
|
function createMainWindow() {
|
||||||
|
// Construct new BrowserWindow
|
||||||
|
const window = new BrowserWindow({
|
||||||
|
title: 'MyEtherWallet',
|
||||||
|
backgroundColor: '#fbfbfb',
|
||||||
|
width: 1220,
|
||||||
|
height: 800,
|
||||||
|
minWidth: 320,
|
||||||
|
minHeight: 400,
|
||||||
|
// TODO - Implement styles for custom title bar in components/ui/TitleBar.scss
|
||||||
|
// frame: false,
|
||||||
|
// titleBarStyle: 'hidden',
|
||||||
|
webPreferences: {
|
||||||
|
devTools: true,
|
||||||
|
nodeIntegration: false,
|
||||||
|
preload: path.resolve(__dirname, 'preload.js')
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const url = isDevelopment
|
||||||
|
? `http://localhost:${process.env.HTTPS ? 3443 : 3000}`
|
||||||
|
: `file://${__dirname}/index.html`;
|
||||||
|
window.loadURL(url);
|
||||||
|
|
||||||
|
window.on('closed', () => {
|
||||||
|
mainWindow = null;
|
||||||
|
});
|
||||||
|
|
||||||
|
window.webContents.on('devtools-opened', () => {
|
||||||
|
window.focus();
|
||||||
|
setImmediate(() => {
|
||||||
|
window.focus();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
if (isDevelopment) {
|
||||||
|
window.webContents.on('did-fail-load', () => {
|
||||||
|
setTimeout(() => {
|
||||||
|
if (window && window.webContents) {
|
||||||
|
window.webContents.reload();
|
||||||
|
}
|
||||||
|
}, 500);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Menu.setApplicationMenu(Menu.buildFromTemplate(MENU));
|
||||||
|
|
||||||
|
return window;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Quit application when all windows are closed
|
||||||
|
app.on('window-all-closed', () => {
|
||||||
|
// On macOS it is common for applications to stay open
|
||||||
|
// until the user explicitly quits
|
||||||
|
if (process.platform !== 'darwin') {
|
||||||
|
app.quit();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
app.on('activate', () => {
|
||||||
|
// On macOS it is common to re-create a window
|
||||||
|
// even after all windows have been closed
|
||||||
|
if (mainWindow === null) {
|
||||||
|
mainWindow = createMainWindow();
|
||||||
|
updater(app, mainWindow);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Create main BrowserWindow when electron is ready
|
||||||
|
app.on('ready', () => {
|
||||||
|
mainWindow = createMainWindow();
|
||||||
|
mainWindow.webContents.on('did-finish-load', () => {
|
||||||
|
updater(app, mainWindow);
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,81 @@
|
||||||
|
import { MenuItemConstructorOptions, shell } from 'electron';
|
||||||
|
|
||||||
|
const MENU: MenuItemConstructorOptions[] = [
|
||||||
|
{
|
||||||
|
label: 'Edit',
|
||||||
|
submenu: [
|
||||||
|
{ role: 'undo' },
|
||||||
|
{ role: 'redo' },
|
||||||
|
{ type: 'separator' },
|
||||||
|
{ role: 'cut' },
|
||||||
|
{ role: 'copy' },
|
||||||
|
{ role: 'paste' },
|
||||||
|
{ role: 'pasteandmatchstyle' },
|
||||||
|
{ role: 'delete' },
|
||||||
|
{ role: 'selectall' }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'View',
|
||||||
|
submenu: [
|
||||||
|
{ role: 'reload' },
|
||||||
|
{ role: 'forcereload' },
|
||||||
|
{ type: 'separator' },
|
||||||
|
{ role: 'resetzoom' },
|
||||||
|
{ role: 'zoomin' },
|
||||||
|
{ role: 'zoomout' },
|
||||||
|
{ role: 'togglefullscreen' },
|
||||||
|
{ type: 'separator' },
|
||||||
|
{ role: 'toggledevtools' }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
const HELP_MENU = {
|
||||||
|
role: 'help',
|
||||||
|
submenu: [
|
||||||
|
{
|
||||||
|
label: 'Help / FAQ',
|
||||||
|
click() {
|
||||||
|
shell.openExternal('https://myetherwallet.github.io/knowledge-base/');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Report a Bug',
|
||||||
|
click() {
|
||||||
|
shell.openExternal('https://github.com/MyEtherWallet/MyEtherWallet/issues/new');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
|
if (process.platform === 'darwin') {
|
||||||
|
MENU.unshift({
|
||||||
|
label: 'MyEtherWallet',
|
||||||
|
submenu: [
|
||||||
|
{ role: 'about' },
|
||||||
|
{ type: 'separator' },
|
||||||
|
{ role: 'hide' },
|
||||||
|
{ role: 'hideothers' },
|
||||||
|
{ role: 'unhide' },
|
||||||
|
{ type: 'separator' },
|
||||||
|
{ role: 'quit' }
|
||||||
|
]
|
||||||
|
});
|
||||||
|
|
||||||
|
// Modified help menu
|
||||||
|
MENU.push({
|
||||||
|
...HELP_MENU,
|
||||||
|
submenu: [
|
||||||
|
...HELP_MENU.submenu,
|
||||||
|
{
|
||||||
|
label: 'Speech',
|
||||||
|
submenu: [{ role: 'startspeaking' }, { role: 'stopspeaking' }]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
MENU.push(HELP_MENU);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default MENU;
|
|
@ -0,0 +1,19 @@
|
||||||
|
{
|
||||||
|
"version": "5.0.0",
|
||||||
|
"files": [
|
||||||
|
{
|
||||||
|
"url": "MyEtherWallet-5.0.0-mac.zip",
|
||||||
|
"sha512": "b95kHlKspcJTo3Bh5tYAZKOrpjncOGOpS2GVA+nRPw6sLy/90SR/alijbe96m+T2CB9ajxDbYMBfs+wknBHZ4g=="
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "MyEtherWallet-5.0.0.dmg",
|
||||||
|
"sha512": "p+tMwE0t2svyppZVK1pFXVLEspRsVPYJtGIp07ppRtQWzkz+krz+dyU9sn2wMdtfIVpSjAGWDpoS2nhvLbHb5A==",
|
||||||
|
"size": 49786443
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"path": "MyEtherWallet-5.0.0-mac.zip",
|
||||||
|
"sha512": "b95kHlKspcJTo3Bh5tYAZKOrpjncOGOpS2GVA+nRPw6sLy/90SR/alijbe96m+T2CB9ajxDbYMBfs+wknBHZ4g==",
|
||||||
|
"releaseDate": "2018-01-14T20:45:04.007Z",
|
||||||
|
"releaseName": "v5.0.0 - Fake Test Release",
|
||||||
|
"releaseNotes": "<h2>Bug Fixes</h2> <ul> <li>Fixed crash in custom protocols. <a href=\"https://github.com/electron/electron/pull/11020\" class=\"issue-link js-issue-link\" data-error-text=\"Failed to load issue title\" data-id=\"271302062\" data-permission-text=\"Issue title is private\" data-url=\"https://github.com/electron/electron/issues/11020\">#11020</a></li> <li>Fixed webrtc crash. <a href=\"https://github.com/electron/libchromiumcontent/pull/393\" class=\"issue-link js-issue-link\" data-error-text=\"Failed to load issue title\" data-id=\"275548554\" data-permission-text=\"Issue title is private\" data-url=\"https://github.com/electron/libchromiumcontent/issues/393\">electron/libchromiumcontent#393</a></li> </ul> <h3>Linux</h3> <ul> <li>Fixed subpixel font rendering with freetype. <a href=\"https://github.com/electron/electron/issues/11402\" class=\"issue-link js-issue-link\" data-error-text=\"Failed to load issue title\" data-id=\"281001023\" data-permission-text=\"Issue title is private\" data-url=\"https://github.com/electron/electron/issues/11402\">#11402</a></li> </ul> <h3>macOS</h3> <ul> <li>Fixed rendering issues with Nvidia GPU on High Sierra. <a href=\"https://github.com/electron/electron/pull/10898\" class=\"issue-link js-issue-link\" data-error-text=\"Failed to load issue title\" data-id=\"268161077\" data-permission-text=\"Issue title is private\" data-url=\"https://github.com/electron/electron/issues/10898\">#10898</a></li> <li>Fixed incorrectly cropped TouchBar items. <a href=\"https://github.com/electron/electron/pull/11141\" class=\"issue-link js-issue-link\" data-error-text=\"Failed to load issue title\" data-id=\"274633253\" data-permission-text=\"Issue title is private\" data-url=\"https://github.com/electron/electron/issues/11141\">#11141</a></li> </ul>"
|
||||||
|
}
|
|
@ -0,0 +1,97 @@
|
||||||
|
import { App, BrowserWindow, ipcMain } from 'electron';
|
||||||
|
import { autoUpdater } from 'electron-updater';
|
||||||
|
import EVENTS from '../../shared/electronEvents';
|
||||||
|
import TEST_RELEASE from './testrelease.json';
|
||||||
|
autoUpdater.autoDownload = false;
|
||||||
|
|
||||||
|
enum AutoUpdaterEvents {
|
||||||
|
CHECKING_FOR_UPDATE = 'checking-for-update',
|
||||||
|
UPDATE_NOT_AVAILABLE = 'update-not-available',
|
||||||
|
UPDATE_AVAILABLE = 'update-available',
|
||||||
|
DOWNLOAD_PROGRESS = 'download-progress',
|
||||||
|
UPDATE_DOWNLOADED = 'update-downloaded',
|
||||||
|
ERROR = 'error'
|
||||||
|
}
|
||||||
|
|
||||||
|
export default (app: App, window: BrowserWindow) => {
|
||||||
|
// Set to 'true' if you want to test update behavior. Requires a recompile.
|
||||||
|
const shouldMockUpdate = true && process.env.NODE_ENV !== 'production';
|
||||||
|
|
||||||
|
// Report update status
|
||||||
|
autoUpdater.on(AutoUpdaterEvents.CHECKING_FOR_UPDATE, () => {
|
||||||
|
window.webContents.send(EVENTS.UPDATE.CHECKING_FOR_UPDATE);
|
||||||
|
});
|
||||||
|
|
||||||
|
autoUpdater.on(AutoUpdaterEvents.UPDATE_NOT_AVAILABLE, () => {
|
||||||
|
window.webContents.send(EVENTS.UPDATE.UPDATE_NOT_AVAILABLE);
|
||||||
|
});
|
||||||
|
|
||||||
|
autoUpdater.on(AutoUpdaterEvents.UPDATE_AVAILABLE, info => {
|
||||||
|
window.webContents.send(EVENTS.UPDATE.UPDATE_AVAILABLE, info);
|
||||||
|
});
|
||||||
|
|
||||||
|
autoUpdater.on(AutoUpdaterEvents.DOWNLOAD_PROGRESS, progress => {
|
||||||
|
window.webContents.send(EVENTS.UPDATE.DOWNLOAD_PROGRESS, progress);
|
||||||
|
});
|
||||||
|
|
||||||
|
autoUpdater.on(AutoUpdaterEvents.UPDATE_DOWNLOADED, () => {
|
||||||
|
window.webContents.send(EVENTS.UPDATE.UPDATE_DOWNLOADED);
|
||||||
|
});
|
||||||
|
|
||||||
|
autoUpdater.on(AutoUpdaterEvents.ERROR, (err, msg) => {
|
||||||
|
console.error('Update failed with an error');
|
||||||
|
console.error(err);
|
||||||
|
window.webContents.send(EVENTS.UPDATE.ERROR, msg);
|
||||||
|
});
|
||||||
|
|
||||||
|
autoUpdater.checkForUpdatesAndNotify();
|
||||||
|
|
||||||
|
// Listen for restart request
|
||||||
|
ipcMain.on(EVENTS.UPDATE.DOWNLOAD_UPDATE, () => {
|
||||||
|
if (shouldMockUpdate) {
|
||||||
|
mockDownload(window);
|
||||||
|
} else {
|
||||||
|
autoUpdater.downloadUpdate();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
ipcMain.on(EVENTS.UPDATE.QUIT_AND_INSTALL, () => {
|
||||||
|
if (shouldMockUpdate) {
|
||||||
|
app.quit();
|
||||||
|
} else {
|
||||||
|
autoUpdater.quitAndInstall();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Simulate a test release
|
||||||
|
if (shouldMockUpdate) {
|
||||||
|
mockUpdateCheck(window);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Mock functions for dev testing
|
||||||
|
function mockUpdateCheck(window: BrowserWindow) {
|
||||||
|
window.webContents.send(EVENTS.UPDATE.CHECKING_FOR_UPDATE);
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
window.webContents.send(EVENTS.UPDATE.UPDATE_AVAILABLE, TEST_RELEASE);
|
||||||
|
}, 3000);
|
||||||
|
}
|
||||||
|
|
||||||
|
function mockDownload(window: BrowserWindow) {
|
||||||
|
for (let i = 0; i < 101; i++) {
|
||||||
|
setTimeout(() => {
|
||||||
|
const total = 150000000;
|
||||||
|
window.webContents.send(EVENTS.UPDATE.DOWNLOAD_PROGRESS, {
|
||||||
|
bytesPerSecond: Math.round(Math.random() * 100000),
|
||||||
|
percent: i,
|
||||||
|
transferred: total / i,
|
||||||
|
total
|
||||||
|
});
|
||||||
|
|
||||||
|
if (i === 100) {
|
||||||
|
window.webContents.send(EVENTS.UPDATE.UPDATE_DOWNLOADED);
|
||||||
|
}
|
||||||
|
}, 50 * i);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,19 @@
|
||||||
|
// Selectively expose node integration, since all node integrations are
|
||||||
|
// disabled by default for security purposes.
|
||||||
|
import { ipcRenderer, shell } from 'electron';
|
||||||
|
import { ElectronBridgeFunctions } from '../shared/electronBridge';
|
||||||
|
const win = window as any;
|
||||||
|
|
||||||
|
const functions: ElectronBridgeFunctions = {
|
||||||
|
addListener(event, cb) {
|
||||||
|
ipcRenderer.on(event, cb);
|
||||||
|
},
|
||||||
|
sendEvent(event, data) {
|
||||||
|
ipcRenderer.send(event, data);
|
||||||
|
},
|
||||||
|
openInBrowser(url) {
|
||||||
|
return shell.openExternal(url);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
win.electronBridge = functions;
|
|
@ -10,7 +10,8 @@
|
||||||
"\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$":
|
"\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$":
|
||||||
"<rootDir>/jest_config/__mocks__/fileMock.ts",
|
"<rootDir>/jest_config/__mocks__/fileMock.ts",
|
||||||
"\\.(css|scss|less)$": "<rootDir>/jest_config/__mocks__/styleMock.ts",
|
"\\.(css|scss|less)$": "<rootDir>/jest_config/__mocks__/styleMock.ts",
|
||||||
"\\.worker.ts":"<rootDir>/jest_config/__mocks__/workerMock.js"
|
"\\.worker.ts":"<rootDir>/jest_config/__mocks__/workerMock.js",
|
||||||
|
"^shared(.*)$": "<rootDir>/shared$1"
|
||||||
},
|
},
|
||||||
"testPathIgnorePatterns": ["<rootDir>/common/config"],
|
"testPathIgnorePatterns": ["<rootDir>/common/config"],
|
||||||
"setupFiles": [
|
"setupFiles": [
|
||||||
|
|
|
@ -9,7 +9,8 @@
|
||||||
"moduleNameMapper": {
|
"moduleNameMapper": {
|
||||||
"\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$":
|
"\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$":
|
||||||
"<rootDir>/jest_config/__mocks__/fileMock.ts",
|
"<rootDir>/jest_config/__mocks__/fileMock.ts",
|
||||||
"\\.(css|scss|less)$": "<rootDir>/jest_config/__mocks__/styleMock.ts"
|
"\\.(css|scss|less)$": "<rootDir>/jest_config/__mocks__/styleMock.ts",
|
||||||
|
"^shared(.*)$": "<rootDir>/shared$1"
|
||||||
},
|
},
|
||||||
"testPathIgnorePatterns": ["<rootDir>/common/config"],
|
"testPathIgnorePatterns": ["<rootDir>/common/config"],
|
||||||
"setupFiles": [
|
"setupFiles": [
|
||||||
|
|
26
package.json
26
package.json
|
@ -1,8 +1,10 @@
|
||||||
{
|
{
|
||||||
"name": "MyEtherWallet",
|
"name": "MyEtherWallet",
|
||||||
|
"author": "MyEtherWallet",
|
||||||
"version": "4.0.0-alpha.1",
|
"version": "4.0.0-alpha.1",
|
||||||
"main": "common/index.jsx",
|
"main": "main.js",
|
||||||
"description": "MyEtherWallet v4",
|
"description": "MyEtherWallet v4",
|
||||||
|
"repository": "https://github.com/wbobeirne/MyEtherWallet-electron",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 8.0.0",
|
"node": ">= 8.0.0",
|
||||||
"npm": ">= 5.0.0"
|
"npm": ">= 5.0.0"
|
||||||
|
@ -13,6 +15,7 @@
|
||||||
"bn.js": "4.11.8",
|
"bn.js": "4.11.8",
|
||||||
"bootstrap-sass": "3.3.7",
|
"bootstrap-sass": "3.3.7",
|
||||||
"classnames": "2.2.5",
|
"classnames": "2.2.5",
|
||||||
|
"electron-updater": "2.18.2",
|
||||||
"ethereum-blockies": "git+https://github.com/MyEtherWallet/blockies.git",
|
"ethereum-blockies": "git+https://github.com/MyEtherWallet/blockies.git",
|
||||||
"ethereumjs-abi": "0.6.5",
|
"ethereumjs-abi": "0.6.5",
|
||||||
"ethereumjs-tx": "1.3.3",
|
"ethereumjs-tx": "1.3.3",
|
||||||
|
@ -74,8 +77,11 @@
|
||||||
"bs58": "4.0.1",
|
"bs58": "4.0.1",
|
||||||
"cache-loader": "1.2.0",
|
"cache-loader": "1.2.0",
|
||||||
"check-node-version": "3.2.0",
|
"check-node-version": "3.2.0",
|
||||||
|
"concurrently": "3.5.1",
|
||||||
"copy-webpack-plugin": "4.3.1",
|
"copy-webpack-plugin": "4.3.1",
|
||||||
"css-loader": "0.28.8",
|
"css-loader": "0.28.8",
|
||||||
|
"electron": "1.7.10",
|
||||||
|
"electron-builder": "19.52.1",
|
||||||
"empty": "0.10.1",
|
"empty": "0.10.1",
|
||||||
"enzyme": "3.3.0",
|
"enzyme": "3.3.0",
|
||||||
"enzyme-adapter-react-16": "1.1.1",
|
"enzyme-adapter-react-16": "1.1.1",
|
||||||
|
@ -131,21 +137,27 @@
|
||||||
"freezer": "webpack --config=./webpack_config/webpack.freezer.js && node ./dist/freezer.js",
|
"freezer": "webpack --config=./webpack_config/webpack.freezer.js && node ./dist/freezer.js",
|
||||||
"freezer:validate": "npm run freezer -- --validate",
|
"freezer:validate": "npm run freezer -- --validate",
|
||||||
"db": "nodemon ./db",
|
"db": "nodemon ./db",
|
||||||
"build": "rimraf dist && webpack --config webpack_config/webpack.prod.js",
|
"build": "webpack --config webpack_config/webpack.prod.js",
|
||||||
"prebuild": "check-node-version --package",
|
"prebuild": "check-node-version --package",
|
||||||
"postbuild": "node ./utils/postBuild.js",
|
"build:downloadable": "webpack --config webpack_config/webpack.html.js",
|
||||||
"build:downloadable": "BUILD_DOWNLOADABLE=true rimraf dist && webpack --config webpack_config/webpack.prod.js",
|
"prebuild:downloadable": "check-node-version --package",
|
||||||
"prebuild:demo": "check-node-version --package",
|
"build:electron": "webpack --config webpack_config/webpack.electron-prod.js && node webpack_config/buildElectron.js",
|
||||||
|
"build:electron:osx": "webpack --config webpack_config/webpack.electron-prod.js && ELECTRON_OS=osx node webpack_config/buildElectron.js",
|
||||||
|
"build:electron:windows": "webpack --config webpack_config/webpack.electron-prod.js && ELECTRON_OS=windows node webpack_config/buildElectron.js",
|
||||||
|
"build:electron:linux": "webpack --config webpack_config/webpack.electron-prod.js && ELECTRON_OS=linux node webpack_config/buildElectron.js",
|
||||||
|
"prebuild:electron": "check-node-version --package",
|
||||||
"test:coverage": "jest --config=jest_config/jest.config.json --coverage",
|
"test:coverage": "jest --config=jest_config/jest.config.json --coverage",
|
||||||
"test": "jest --config=jest_config/jest.config.json",
|
"test": "jest --config=jest_config/jest.config.json",
|
||||||
"test:unit": "jest --config=jest_config/jest.config.json --coverage",
|
"test:unit": "jest --config=jest_config/jest.config.json --coverage",
|
||||||
"test:int": "jest --config=jest_config/jest.int.config.json --coverage",
|
"test:int": "jest --config=jest_config/jest.int.config.json --coverage",
|
||||||
"updateSnapshot": "jest --config=jest_config/jest.config.json --updateSnapshot",
|
"updateSnapshot": "jest --config=jest_config/jest.config.json --updateSnapshot",
|
||||||
"pretest": "check-node-version --package",
|
"pretest": "check-node-version --package",
|
||||||
"dev": "node webpack_config/server.js",
|
"dev": "node webpack_config/devServer.js",
|
||||||
"predev": "check-node-version --package",
|
"predev": "check-node-version --package",
|
||||||
"dev:https": "HTTPS=true node webpack_config/server.js",
|
"dev:https": "HTTPS=true node webpack_config/devServer.js",
|
||||||
"predev:https": "check-node-version --package",
|
"predev:https": "check-node-version --package",
|
||||||
|
"dev:electron": "concurrently --kill-others --names 'webpack,electron' 'BUILD_ELECTRON=true node webpack_config/devServer.js' 'webpack --config webpack_config/webpack.electron-dev.js && electron dist/electron-js/main.js'",
|
||||||
|
"dev:electron:https": "concurrently --kill-others --names 'webpack,electron' 'BUILD_ELECTRON=true HTTPS=true node webpack_config/devServer.js' 'HTTPS=true webpack --config webpack_config/webpack.electron-dev.js && electron dist/electron-js/main.js'",
|
||||||
"tslint": "tslint --project . --exclude common/vendor/**/*",
|
"tslint": "tslint --project . --exclude common/vendor/**/*",
|
||||||
"tscheck": "tsc --noEmit",
|
"tscheck": "tsc --noEmit",
|
||||||
"start": "npm run dev",
|
"start": "npm run dev",
|
||||||
|
|
|
@ -0,0 +1,9 @@
|
||||||
|
// Provide typescript definitions / mappings between `electron-app/preload.ts`
|
||||||
|
// and 'common/utils/electron.ts'
|
||||||
|
export type ElectronBridgeCallback = (data?: any) => void;
|
||||||
|
|
||||||
|
export interface ElectronBridgeFunctions {
|
||||||
|
addListener(event: string, cb: ElectronBridgeCallback);
|
||||||
|
sendEvent(event: string, data?: any);
|
||||||
|
openInBrowser(url: string): boolean;
|
||||||
|
}
|
|
@ -0,0 +1,12 @@
|
||||||
|
export default {
|
||||||
|
UPDATE: {
|
||||||
|
CHECKING_FOR_UPDATE: 'UPDATE:checking-for-update',
|
||||||
|
UPDATE_NOT_AVAILABLE: 'UPDATE:update-not-available',
|
||||||
|
UPDATE_AVAILABLE: 'UPDATE:update-available',
|
||||||
|
DOWNLOAD_PROGRESS: 'UPDATE:download-progress',
|
||||||
|
UPDATE_DOWNLOADED: 'UPDATE:update-downloaded',
|
||||||
|
ERROR: 'UPDATE:error',
|
||||||
|
DOWNLOAD_UPDATE: 'UPDATE:download-update',
|
||||||
|
QUIT_AND_INSTALL: 'UPDATE:quit-and-install'
|
||||||
|
}
|
||||||
|
};
|
|
@ -8,6 +8,9 @@
|
||||||
"target": "es2015",
|
"target": "es2015",
|
||||||
"allowJs": true,
|
"allowJs": true,
|
||||||
"baseUrl": "./common/",
|
"baseUrl": "./common/",
|
||||||
|
"paths": {
|
||||||
|
"shared*": ["../shared*"]
|
||||||
|
},
|
||||||
"lib": [
|
"lib": [
|
||||||
"es2017",
|
"es2017",
|
||||||
"dom"
|
"dom"
|
||||||
|
@ -20,6 +23,7 @@
|
||||||
},
|
},
|
||||||
"include": [
|
"include": [
|
||||||
"./common/",
|
"./common/",
|
||||||
|
"./electron/",
|
||||||
"spec",
|
"spec",
|
||||||
"./node_modules/types-rlp/index.d.ts"
|
"./node_modules/types-rlp/index.d.ts"
|
||||||
],
|
],
|
||||||
|
|
|
@ -1,46 +0,0 @@
|
||||||
|
|
||||||
/**
|
|
||||||
* (1) Parses the '.cache' file in the 'dist/icons' folder
|
|
||||||
* (2) Sorts the 'cache.result.files' property
|
|
||||||
* (3) Rewrites the file to ensure a deterministic build
|
|
||||||
*/
|
|
||||||
|
|
||||||
|
|
||||||
const fs = require('fs')
|
|
||||||
const path = require('path')
|
|
||||||
const klawSync = require('klaw-sync')
|
|
||||||
|
|
||||||
const DIST_PATH = path.resolve('./dist/')
|
|
||||||
const CACHE_FILE_REGEX = /.*icons-[a-z0-9]*\/\.cache$/
|
|
||||||
|
|
||||||
const findCacheFile = item => CACHE_FILE_REGEX.test(item.path)
|
|
||||||
|
|
||||||
console.log('postBuild start')
|
|
||||||
|
|
||||||
try {
|
|
||||||
const cacheFilePaths = klawSync(DIST_PATH, { filter: findCacheFile })
|
|
||||||
|
|
||||||
if (!cacheFilePaths.length) {
|
|
||||||
throw new Error('Could not find .cache file')
|
|
||||||
}
|
|
||||||
|
|
||||||
if (cacheFilePaths.length > 1) {
|
|
||||||
throw new Error('More than one possible .cache file detected')
|
|
||||||
}
|
|
||||||
|
|
||||||
const cacheFilePath = cacheFilePaths[0].path
|
|
||||||
const rawCacheFile = fs.readFileSync(cacheFilePath, 'utf8')
|
|
||||||
const cache = JSON.parse(rawCacheFile)
|
|
||||||
|
|
||||||
cache.result.files = cache.result.files.sort()
|
|
||||||
|
|
||||||
fs.writeFileSync(cacheFilePath, JSON.stringify(cache), 'utf8')
|
|
||||||
|
|
||||||
} catch(err) {
|
|
||||||
console.log('postBuild fail', err)
|
|
||||||
process.exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log('postBuild finish')
|
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,68 @@
|
||||||
|
'use strict';
|
||||||
|
const path = require('path');
|
||||||
|
const fs = require('fs');
|
||||||
|
const rimraf = require('rimraf');
|
||||||
|
const builder = require('electron-builder');
|
||||||
|
const config = require('./config');
|
||||||
|
|
||||||
|
function shouldBuildOs(os) {
|
||||||
|
return !process.env.ELECTRON_OS || process.env.ELECTRON_OS === os;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function build() {
|
||||||
|
console.log('Beginning Electron build process...');
|
||||||
|
const jsBuildDir = path.join(config.path.output, 'electron-js');
|
||||||
|
const electronBuildsDir = path.join(config.path.output, 'electron-builds');
|
||||||
|
const compression = 'store';
|
||||||
|
|
||||||
|
console.log('Clearing out old builds...');
|
||||||
|
rimraf.sync(electronBuildsDir);
|
||||||
|
|
||||||
|
// Builder requires package.json be in the app directory, so copy it in
|
||||||
|
fs.copyFileSync(
|
||||||
|
path.join(config.path.root, 'package.json'),
|
||||||
|
path.join(jsBuildDir, 'package.json')
|
||||||
|
);
|
||||||
|
|
||||||
|
console.log('Building...');
|
||||||
|
await builder.build({
|
||||||
|
mac: shouldBuildOs('mac') ? ['zip', 'dmg'] : undefined,
|
||||||
|
win: shouldBuildOs('windows') ? ['nsis'] : undefined,
|
||||||
|
linux: shouldBuildOs('linux') ? ['AppImage'] : undefined,
|
||||||
|
x64: true,
|
||||||
|
ia32: true,
|
||||||
|
config: {
|
||||||
|
appId: 'com.github.myetherwallet.myetherwallet',
|
||||||
|
productName: 'MyEtherWallet',
|
||||||
|
directories: {
|
||||||
|
app: jsBuildDir,
|
||||||
|
output: electronBuildsDir,
|
||||||
|
},
|
||||||
|
mac: {
|
||||||
|
category: 'public.app-category.finance',
|
||||||
|
icon: path.join(config.path.electron, 'icons/icon.icns'),
|
||||||
|
compression
|
||||||
|
},
|
||||||
|
win: {
|
||||||
|
icon: path.join(config.path.electron, 'icons/icon.ico'),
|
||||||
|
compression
|
||||||
|
},
|
||||||
|
linux: {
|
||||||
|
category: 'Finance',
|
||||||
|
compression
|
||||||
|
},
|
||||||
|
publish: {
|
||||||
|
provider: 'github',
|
||||||
|
owner: 'MyEtherWallet',
|
||||||
|
repo: 'MyEtherWallet',
|
||||||
|
vPrefixedTagName: false
|
||||||
|
},
|
||||||
|
// IMPORTANT: Prevents extending configs in node_modules
|
||||||
|
extends: null
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
console.info(`Electron builds are finished! Available at ${electronBuildsDir}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
build();
|
|
@ -1,13 +1,45 @@
|
||||||
'use strict';
|
'use strict';
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
|
|
||||||
|
const paths = {
|
||||||
|
root: path.join(__dirname, '../'),
|
||||||
|
src: path.join(__dirname, '../common'),
|
||||||
|
output: path.join(__dirname, '../dist'),
|
||||||
|
assets: path.join(__dirname, '../common/assets'),
|
||||||
|
static: path.join(__dirname, '../static'),
|
||||||
|
electron: path.join(__dirname, '../electron-app'),
|
||||||
|
shared: path.join(__dirname, '../shared'),
|
||||||
|
modules: path.join(__dirname, '../node_modules'),
|
||||||
|
}
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
|
// Configuration
|
||||||
port: process.env.HTTPS ? 3443 : 3000,
|
port: process.env.HTTPS ? 3443 : 3000,
|
||||||
title: 'MEW',
|
title: 'MEW',
|
||||||
publicPath: process.env.BUILD_DOWNLOADABLE ? './' : '/',
|
path: paths,
|
||||||
srcPath: path.join(__dirname, './../common'),
|
|
||||||
// add these dependencies to a standalone vendor bundle
|
// Typescript rule config
|
||||||
vendor: [
|
typescriptRule: {
|
||||||
|
test: /\.(ts|tsx)$/,
|
||||||
|
include: [paths.src, paths.shared, paths.electron],
|
||||||
|
use: [{ loader: 'ts-loader', options: { happyPackMode: true, logLevel: 'info' } }],
|
||||||
|
exclude: ['assets', 'sass', 'vendor', 'translations/lang']
|
||||||
|
.map(dir => path.resolve(paths.src, dir))
|
||||||
|
.concat([paths.modules])
|
||||||
|
},
|
||||||
|
|
||||||
|
// File resolution
|
||||||
|
resolve: {
|
||||||
|
extensions: ['.ts', '.tsx', '.js', '.css', '.json', '.scss', '.less'],
|
||||||
|
modules: [
|
||||||
|
paths.src,
|
||||||
|
paths.modules,
|
||||||
|
paths.root,
|
||||||
|
]
|
||||||
|
},
|
||||||
|
|
||||||
|
// Vendor modules
|
||||||
|
vendorModules: [
|
||||||
'bip39',
|
'bip39',
|
||||||
'bn.js',
|
'bn.js',
|
||||||
'classnames',
|
'classnames',
|
||||||
|
|
|
@ -6,7 +6,7 @@ const https = require('https');
|
||||||
const fs = require('fs');
|
const fs = require('fs');
|
||||||
const webpackConfig = require('./webpack.dev');
|
const webpackConfig = require('./webpack.dev');
|
||||||
const config = require('./config');
|
const config = require('./config');
|
||||||
const LogPlugin = require('./log-plugin');
|
const LogPlugin = require('./plugins/serverLog');
|
||||||
|
|
||||||
const app = express();
|
const app = express();
|
||||||
|
|
||||||
|
@ -25,13 +25,13 @@ let compiler;
|
||||||
try {
|
try {
|
||||||
compiler = webpack(webpackConfig);
|
compiler = webpack(webpackConfig);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.log(err.message);
|
console.error(err.message);
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
const devMiddleWare = require('webpack-dev-middleware')(compiler, {
|
const devMiddleWare = require('webpack-dev-middleware')(compiler, {
|
||||||
publicPath: webpackConfig.output.publicPath,
|
publicPath: webpackConfig.output.publicPath,
|
||||||
quiet: true,
|
logLevel: 'warn',
|
||||||
inline: true,
|
inline: true,
|
||||||
headers: {
|
headers: {
|
||||||
'Access-Control-Allow-Origin': '*',
|
'Access-Control-Allow-Origin': '*',
|
||||||
|
@ -45,7 +45,7 @@ const devMiddleWare = require('webpack-dev-middleware')(compiler, {
|
||||||
app.use(devMiddleWare);
|
app.use(devMiddleWare);
|
||||||
app.use(
|
app.use(
|
||||||
require('webpack-hot-middleware')(compiler, {
|
require('webpack-hot-middleware')(compiler, {
|
||||||
log: console.log
|
log: console.info
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
|
@ -0,0 +1,321 @@
|
||||||
|
'use strict';
|
||||||
|
const path = require('path');
|
||||||
|
const webpack = require('webpack');
|
||||||
|
const threadLoader = require('thread-loader');
|
||||||
|
|
||||||
|
const HtmlWebpackPlugin = require('html-webpack-plugin');
|
||||||
|
const CopyWebpackPlugin = require('copy-webpack-plugin');
|
||||||
|
const FriendlyErrorsPlugin = require('friendly-errors-webpack-plugin');
|
||||||
|
const AutoDllPlugin = require('autodll-webpack-plugin');
|
||||||
|
const HardSourceWebpackPlugin = require('hard-source-webpack-plugin');
|
||||||
|
const FaviconsWebpackPlugin = require('favicons-webpack-plugin');
|
||||||
|
const ExtractTextPlugin = require('extract-text-webpack-plugin');
|
||||||
|
const ProgressPlugin = require('webpack/lib/ProgressPlugin');
|
||||||
|
const BabelMinifyPlugin = require('babel-minify-webpack-plugin');
|
||||||
|
const SriPlugin = require('webpack-subresource-integrity');
|
||||||
|
const ClearDistPlugin = require('./plugins/clearDist');
|
||||||
|
const SortCachePlugin = require('./plugins/sortCache');
|
||||||
|
|
||||||
|
const config = require('./config');
|
||||||
|
|
||||||
|
const DEFAULT_OPTIONS = {
|
||||||
|
isProduction: false,
|
||||||
|
isElectronBuild: false,
|
||||||
|
isHTMLBuild: false,
|
||||||
|
outputDir: ''
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = function(opts = {}) {
|
||||||
|
const options = Object.assign({}, DEFAULT_OPTIONS, opts);
|
||||||
|
const isDownloadable = options.isHTMLBuild || options.isElectronBuild;
|
||||||
|
|
||||||
|
// ====================
|
||||||
|
// ====== Entry =======
|
||||||
|
// ====================
|
||||||
|
const entry = {
|
||||||
|
client: './common/index.tsx'
|
||||||
|
};
|
||||||
|
|
||||||
|
if (options.isProduction) {
|
||||||
|
entry.vendor = config.vendorModules;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ====================
|
||||||
|
// ====== Rules =======
|
||||||
|
// ====================
|
||||||
|
const rules = [];
|
||||||
|
|
||||||
|
// Typescript
|
||||||
|
if (options.isProduction || !process.env.SLOW_BUILD_SPEED) {
|
||||||
|
rules.push(config.typescriptRule);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
threadLoader.warmup(
|
||||||
|
config.typescriptRule.use[0].options,
|
||||||
|
[config.typescriptRule.use[0].loader]
|
||||||
|
);
|
||||||
|
rules.push({
|
||||||
|
...config.typescriptRule,
|
||||||
|
use: [{
|
||||||
|
loader: 'thread-loader',
|
||||||
|
options: {
|
||||||
|
workers: 4
|
||||||
|
}
|
||||||
|
}, ...config.typescriptRule.use],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Styles (CSS, SCSS, LESS)
|
||||||
|
if (options.isProduction) {
|
||||||
|
rules.push({
|
||||||
|
test: /\.css$/,
|
||||||
|
use: ExtractTextPlugin.extract({
|
||||||
|
fallback: 'style-loader',
|
||||||
|
use: 'css-loader'
|
||||||
|
})
|
||||||
|
}, {
|
||||||
|
test: /\.scss$/,
|
||||||
|
use: ExtractTextPlugin.extract({
|
||||||
|
fallback: 'style-loader',
|
||||||
|
use: ['css-loader', 'sass-loader']
|
||||||
|
})
|
||||||
|
}, {
|
||||||
|
test: /\.less$/,
|
||||||
|
use: ExtractTextPlugin.extract({
|
||||||
|
fallback: 'style-loader',
|
||||||
|
use: ['css-loader', 'less-loader']
|
||||||
|
})
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
rules.push({
|
||||||
|
test: /\.css$/,
|
||||||
|
include: path.resolve(config.path.src, 'vendor'),
|
||||||
|
use: ['style-loader', 'css-loader']
|
||||||
|
}, {
|
||||||
|
test: /\.scss$/,
|
||||||
|
include: ['components', 'containers', 'sass']
|
||||||
|
.map(dir => path.resolve(config.path.src, dir))
|
||||||
|
.concat([config.path.modules]),
|
||||||
|
|
||||||
|
exclude: /node_modules(?!\/font-awesome)/,
|
||||||
|
use: ['style-loader', 'css-loader', 'sass-loader']
|
||||||
|
}, {
|
||||||
|
test: /\.less$/,
|
||||||
|
include: path.resolve(config.path.assets, 'styles'),
|
||||||
|
use: ['style-loader', 'css-loader', 'less-loader']
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Web workers
|
||||||
|
rules.push({
|
||||||
|
test: /\.worker\.js$/,
|
||||||
|
loader: 'worker-loader'
|
||||||
|
});
|
||||||
|
|
||||||
|
// Images
|
||||||
|
rules.push({
|
||||||
|
include: [
|
||||||
|
path.resolve(config.path.assets),
|
||||||
|
path.resolve(config.path.modules)
|
||||||
|
],
|
||||||
|
exclude: /node_modules(?!\/font-awesome)/,
|
||||||
|
test: /\.(gif|png|jpe?g|svg)$/i,
|
||||||
|
use: [
|
||||||
|
{
|
||||||
|
loader: 'file-loader',
|
||||||
|
options: {
|
||||||
|
hash: 'sha512',
|
||||||
|
digest: 'hex',
|
||||||
|
name: '[path][name].[ext]?[hash:6]'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
loader: 'image-webpack-loader',
|
||||||
|
options: {
|
||||||
|
bypassOnDebug: true,
|
||||||
|
optipng: {
|
||||||
|
optimizationLevel: 4
|
||||||
|
},
|
||||||
|
gifsicle: {
|
||||||
|
interlaced: false
|
||||||
|
},
|
||||||
|
mozjpeg: {
|
||||||
|
quality: 80
|
||||||
|
},
|
||||||
|
svgo: {
|
||||||
|
plugins: [{ removeViewBox: true }, { removeEmptyAttrs: false }, { sortAttrs: true }]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
});
|
||||||
|
|
||||||
|
// Fonts
|
||||||
|
rules.push({
|
||||||
|
include: [
|
||||||
|
path.resolve(config.path.assets),
|
||||||
|
path.resolve(config.path.modules)
|
||||||
|
],
|
||||||
|
exclude: /node_modules(?!\/font-awesome)/,
|
||||||
|
test: /\.(ico|eot|otf|webp|ttf|woff|woff2)(\?.*)?$/,
|
||||||
|
loader: 'file-loader'
|
||||||
|
});
|
||||||
|
|
||||||
|
// ====================
|
||||||
|
// ====== Plugins =====
|
||||||
|
// ====================
|
||||||
|
const plugins = [
|
||||||
|
new HtmlWebpackPlugin({
|
||||||
|
title: config.title,
|
||||||
|
template: path.resolve(config.path.src, 'index.html'),
|
||||||
|
inject: true
|
||||||
|
}),
|
||||||
|
|
||||||
|
new CopyWebpackPlugin([
|
||||||
|
{
|
||||||
|
from: config.path.static,
|
||||||
|
// to the root of dist path
|
||||||
|
to: './'
|
||||||
|
}
|
||||||
|
]),
|
||||||
|
|
||||||
|
new webpack.LoaderOptionsPlugin({
|
||||||
|
minimize: options.isProduction,
|
||||||
|
debug: !options.isProduction,
|
||||||
|
options: {
|
||||||
|
// css-loader relies on context
|
||||||
|
context: process.cwd()
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
|
||||||
|
new webpack.DefinePlugin({
|
||||||
|
'process.env.NODE_ENV': JSON.stringify(options.isProduction ? 'production' : 'development'),
|
||||||
|
'process.env.BUILD_DOWNLOADABLE': JSON.stringify(isDownloadable),
|
||||||
|
'process.env.BUILD_HTML': JSON.stringify(options.isHTMLBuild),
|
||||||
|
'process.env.BUILD_ELECTRON': JSON.stringify(options.isElectronBuild)
|
||||||
|
}),
|
||||||
|
];
|
||||||
|
|
||||||
|
if (options.isProduction) {
|
||||||
|
plugins.push(
|
||||||
|
new BabelMinifyPlugin({
|
||||||
|
// Mangle seems to be reusing variable identifiers, causing errors
|
||||||
|
mangle: false,
|
||||||
|
// These two on top of a lodash file are causing illegal characters for
|
||||||
|
// safari and ios browsers
|
||||||
|
evaluate: false,
|
||||||
|
propertyLiterals: false,
|
||||||
|
}, {
|
||||||
|
comments: false
|
||||||
|
}),
|
||||||
|
new webpack.optimize.CommonsChunkPlugin({
|
||||||
|
name: 'vendor',
|
||||||
|
filename: 'vendor.[chunkhash:8].js'
|
||||||
|
}),
|
||||||
|
new ExtractTextPlugin('[name].[chunkhash:8].css'),
|
||||||
|
new FaviconsWebpackPlugin({
|
||||||
|
logo: path.resolve(config.path.static, 'favicon/android-chrome-384x384.png'),
|
||||||
|
background: '#163151',
|
||||||
|
inject: true
|
||||||
|
}),
|
||||||
|
new SriPlugin({
|
||||||
|
hashFuncNames: ['sha256', 'sha384'],
|
||||||
|
enabled: true
|
||||||
|
}),
|
||||||
|
new ProgressPlugin(),
|
||||||
|
new ClearDistPlugin(),
|
||||||
|
new SortCachePlugin()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
plugins.push(
|
||||||
|
new AutoDllPlugin({
|
||||||
|
inject: true, // will inject the DLL bundles to index.html
|
||||||
|
filename: '[name]_[hash].js',
|
||||||
|
debug: true,
|
||||||
|
context: path.join(config.path.root),
|
||||||
|
entry: {
|
||||||
|
vendor: [
|
||||||
|
...config.vendorModules,
|
||||||
|
'babel-polyfill',
|
||||||
|
'bootstrap-sass',
|
||||||
|
'font-awesome'
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
new HardSourceWebpackPlugin({
|
||||||
|
environmentHash: {
|
||||||
|
root: process.cwd(),
|
||||||
|
directories: ['webpack_config'],
|
||||||
|
files: ['package.json']
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
new webpack.HotModuleReplacementPlugin(),
|
||||||
|
new webpack.NoEmitOnErrorsPlugin(),
|
||||||
|
new FriendlyErrorsPlugin()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options.isElectronBuild) {
|
||||||
|
// target: 'electron-renderer' kills scrypt, so manually pull in some
|
||||||
|
// of its configuration instead
|
||||||
|
plugins.push(new webpack.ExternalsPlugin("commonjs", [
|
||||||
|
"desktop-capturer",
|
||||||
|
"electron",
|
||||||
|
"ipc",
|
||||||
|
"ipc-renderer",
|
||||||
|
"remote",
|
||||||
|
"web-frame",
|
||||||
|
"clipboard",
|
||||||
|
"crash-reporter",
|
||||||
|
"native-image",
|
||||||
|
"screen",
|
||||||
|
"shell"
|
||||||
|
]));
|
||||||
|
}
|
||||||
|
|
||||||
|
// ====================
|
||||||
|
// ====== DevTool =====
|
||||||
|
// ====================
|
||||||
|
let devtool = false;
|
||||||
|
if (!options.isProduction) {
|
||||||
|
if (process.env.SLOW_BUILD_SPEED) {
|
||||||
|
devtool = 'source-map';
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
devtool = 'cheap-module-eval-source-map';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ====================
|
||||||
|
// ====== Output ======
|
||||||
|
// ====================
|
||||||
|
const output = {
|
||||||
|
path: path.resolve(config.path.output, options.outputDir),
|
||||||
|
filename: options.isProduction ? '[name].[chunkhash:8].js' : '[name].js',
|
||||||
|
publicPath: isDownloadable && options.isProduction ? './' : '/',
|
||||||
|
crossOriginLoading: 'anonymous'
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// The final bundle
|
||||||
|
return {
|
||||||
|
entry,
|
||||||
|
output,
|
||||||
|
module: { rules },
|
||||||
|
plugins,
|
||||||
|
target: 'web',
|
||||||
|
resolve: config.resolve,
|
||||||
|
performance: {
|
||||||
|
hints: options.isProduction ? 'warning' : false
|
||||||
|
},
|
||||||
|
stats: {
|
||||||
|
// Reduce build output
|
||||||
|
children: false,
|
||||||
|
chunks: false,
|
||||||
|
chunkModules: false,
|
||||||
|
chunkOrigins: false,
|
||||||
|
modules: false
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
|
@ -0,0 +1,11 @@
|
||||||
|
'use strict';
|
||||||
|
const rimraf = require('rimraf');
|
||||||
|
|
||||||
|
function ClearDistPlugin() {};
|
||||||
|
ClearDistPlugin.prototype.apply = function(compiler) {
|
||||||
|
compiler.plugin('before-run', (params, done) => {
|
||||||
|
rimraf(params.outputPath, () => done());
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = ClearDistPlugin;
|
|
@ -0,0 +1,15 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
const DelayPlugin = function(delayMs) {
|
||||||
|
this.delayMs = delayMs;
|
||||||
|
};
|
||||||
|
|
||||||
|
DelayPlugin.prototype.apply = function(compiler) {
|
||||||
|
compiler.plugin('before-run', (compiler, done) => {
|
||||||
|
setTimeout(() => {
|
||||||
|
done();
|
||||||
|
}, this.delayMs);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = DelayPlugin;
|
|
@ -0,0 +1,32 @@
|
||||||
|
// Makes for a deterministic cache file by sorting it
|
||||||
|
'use strict';
|
||||||
|
const fs = require('fs');
|
||||||
|
const klawSync = require('klaw-sync');
|
||||||
|
|
||||||
|
const CACHE_FILE_REGEX = /.*icons-[a-z0-9]*\/\.cache$/;
|
||||||
|
const findCacheFile = item => CACHE_FILE_REGEX.test(item.path);
|
||||||
|
|
||||||
|
function SortCachePlugin() {};
|
||||||
|
SortCachePlugin.prototype.apply = function(compiler) {
|
||||||
|
compiler.plugin('done', (stats) => {
|
||||||
|
const buildDir = stats.compilation.compiler.outputPath;
|
||||||
|
const cacheFilePaths = klawSync(buildDir, { filter: findCacheFile });
|
||||||
|
|
||||||
|
if (!cacheFilePaths.length) {
|
||||||
|
throw new Error('Could not find .cache file');
|
||||||
|
}
|
||||||
|
if (cacheFilePaths.length > 1) {
|
||||||
|
throw new Error('More than one possible .cache file detected');
|
||||||
|
}
|
||||||
|
|
||||||
|
const cacheFilePath = cacheFilePaths[0].path;
|
||||||
|
const rawCacheFile = fs.readFileSync(cacheFilePath, 'utf8');
|
||||||
|
const cache = JSON.parse(rawCacheFile);
|
||||||
|
|
||||||
|
cache.result.files = cache.result.files.sort();
|
||||||
|
|
||||||
|
fs.writeFileSync(cacheFilePath, JSON.stringify(cache), 'utf8');
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = SortCachePlugin;
|
|
@ -1,28 +0,0 @@
|
||||||
'use strict';
|
|
||||||
const path = require('path');
|
|
||||||
const config = require('./config');
|
|
||||||
|
|
||||||
const _ = (module.exports = {});
|
|
||||||
|
|
||||||
_.cwd = file => {
|
|
||||||
return path.join(process.cwd(), file || '');
|
|
||||||
};
|
|
||||||
|
|
||||||
_.outputPath = path.join(__dirname, '../dist');
|
|
||||||
|
|
||||||
_.outputIndexPath = path.join(__dirname, '../dist/index.html');
|
|
||||||
|
|
||||||
_.target = 'web';
|
|
||||||
|
|
||||||
_.loadersOptions = () => {
|
|
||||||
const isProd = process.env.NODE_ENV === 'production';
|
|
||||||
|
|
||||||
return {
|
|
||||||
minimize: isProd,
|
|
||||||
debug: !isProd,
|
|
||||||
options: {
|
|
||||||
// css-loader relies on context
|
|
||||||
context: process.cwd()
|
|
||||||
}
|
|
||||||
};
|
|
||||||
};
|
|
|
@ -1,115 +0,0 @@
|
||||||
'use strict';
|
|
||||||
const path = require('path');
|
|
||||||
const webpack = require('webpack');
|
|
||||||
const HtmlWebpackPlugin = require('html-webpack-plugin');
|
|
||||||
const CopyWebpackPlugin = require('copy-webpack-plugin');
|
|
||||||
const FaviconsWebpackPlugin = require('favicons-webpack-plugin');
|
|
||||||
const SriPlugin = require('webpack-subresource-integrity');
|
|
||||||
|
|
||||||
const config = require('./config');
|
|
||||||
const _ = require('./utils');
|
|
||||||
|
|
||||||
const webpackConfig = {
|
|
||||||
entry: {
|
|
||||||
client: './common/index.tsx'
|
|
||||||
},
|
|
||||||
output: {
|
|
||||||
path: _.outputPath,
|
|
||||||
filename: '[name].js',
|
|
||||||
publicPath: config.publicPath,
|
|
||||||
crossOriginLoading: "anonymous"
|
|
||||||
},
|
|
||||||
resolve: {
|
|
||||||
extensions: ['.ts', '.tsx', '.js', '.css', '.json', '.scss', '.less'],
|
|
||||||
modules: [
|
|
||||||
// places where to search for required modules
|
|
||||||
config.srcPath,
|
|
||||||
_.cwd('node_modules'),
|
|
||||||
_.cwd('./')
|
|
||||||
]
|
|
||||||
},
|
|
||||||
module: {
|
|
||||||
rules: [
|
|
||||||
{
|
|
||||||
test: /\.(ts|tsx)$/,
|
|
||||||
include: path.resolve(__dirname, '../common'),
|
|
||||||
use: [{ loader: 'ts-loader', options: { happyPackMode: true, logLevel: 'info' } }],
|
|
||||||
exclude: ['assets', 'sass', 'vendor', 'translations/lang']
|
|
||||||
.map(dir => path.resolve(__dirname, `../common/${dir}`))
|
|
||||||
.concat([path.resolve(__dirname, '../node_modules')])
|
|
||||||
},
|
|
||||||
{
|
|
||||||
test: /\.worker\.js$/,
|
|
||||||
loader: 'worker-loader'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
include: [
|
|
||||||
path.resolve(__dirname, '../common/assets'),
|
|
||||||
path.resolve(__dirname, '../node_modules')
|
|
||||||
],
|
|
||||||
exclude: /node_modules(?!\/font-awesome)/,
|
|
||||||
test: /\.(gif|png|jpe?g|svg)$/i,
|
|
||||||
use: [
|
|
||||||
{
|
|
||||||
loader: 'file-loader',
|
|
||||||
options: {
|
|
||||||
hash: 'sha512',
|
|
||||||
digest: 'hex',
|
|
||||||
name: '[path][name].[ext]?[hash:6]'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
loader: 'image-webpack-loader',
|
|
||||||
options: {
|
|
||||||
bypassOnDebug: true,
|
|
||||||
optipng: {
|
|
||||||
optimizationLevel: 4
|
|
||||||
},
|
|
||||||
gifsicle: {
|
|
||||||
interlaced: false
|
|
||||||
},
|
|
||||||
mozjpeg: {
|
|
||||||
quality: 80
|
|
||||||
},
|
|
||||||
svgo: {
|
|
||||||
plugins: [{ removeViewBox: true }, { removeEmptyAttrs: false }, { sortAttrs: true }]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
include: [
|
|
||||||
path.resolve(__dirname, '../common/assets'),
|
|
||||||
path.resolve(__dirname, '../node_modules')
|
|
||||||
],
|
|
||||||
exclude: /node_modules(?!\/font-awesome)/,
|
|
||||||
test: /\.(ico|eot|otf|webp|ttf|woff|woff2)(\?.*)?$/,
|
|
||||||
loader: 'file-loader'
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
plugins: [
|
|
||||||
new SriPlugin({
|
|
||||||
hashFuncNames: ['sha256', 'sha384'],
|
|
||||||
enabled: true
|
|
||||||
}),
|
|
||||||
new HtmlWebpackPlugin({
|
|
||||||
title: config.title,
|
|
||||||
template: path.resolve(__dirname, '../common/index.html'),
|
|
||||||
inject: true,
|
|
||||||
filename: _.outputIndexPath
|
|
||||||
}),
|
|
||||||
new CopyWebpackPlugin([
|
|
||||||
{
|
|
||||||
from: _.cwd('./static'),
|
|
||||||
// to the root of dist path
|
|
||||||
to: './'
|
|
||||||
}
|
|
||||||
]),
|
|
||||||
|
|
||||||
new webpack.LoaderOptionsPlugin(_.loadersOptions())
|
|
||||||
],
|
|
||||||
target: _.target
|
|
||||||
};
|
|
||||||
module.exports = webpackConfig;
|
|
|
@ -1,22 +1,19 @@
|
||||||
// Compile derivation checker using the (mostly) same webpack config
|
// Compile derivation checker using the (mostly) same webpack config
|
||||||
'use strict';
|
'use strict';
|
||||||
const baseConfig = require('./webpack.base');
|
const path = require('path');
|
||||||
|
const config = require('./config');
|
||||||
|
|
||||||
const derivationConfig = Object.assign({}, baseConfig, {
|
const derivationConfig = {
|
||||||
// Remove the cruft we don't need
|
|
||||||
plugins: undefined,
|
|
||||||
target: undefined,
|
|
||||||
performance: undefined,
|
|
||||||
module: {
|
|
||||||
// Typescript loader
|
|
||||||
loaders: [baseConfig.module.loaders[0]]
|
|
||||||
},
|
|
||||||
|
|
||||||
// Point at derivation checker, make sure it's setup to run in node
|
|
||||||
target: 'node',
|
target: 'node',
|
||||||
entry: {
|
entry: './common/derivation-checker.ts',
|
||||||
'derivation-checker': './common/derivation-checker.ts'
|
output: {
|
||||||
}
|
path: config.path.output,
|
||||||
});
|
filename: 'derivation-checker.js'
|
||||||
|
},
|
||||||
|
module: {
|
||||||
|
rules: [config.typescriptRule],
|
||||||
|
},
|
||||||
|
resolve: config.resolve,
|
||||||
|
};
|
||||||
|
|
||||||
module.exports = derivationConfig;
|
module.exports = derivationConfig;
|
||||||
|
|
|
@ -1,87 +1,7 @@
|
||||||
'use strict';
|
'use strict';
|
||||||
process.env.NODE_ENV = 'development';
|
const makeConfig = require('./makeConfig');
|
||||||
const path = require('path');
|
|
||||||
const webpack = require('webpack');
|
|
||||||
const base = require('./webpack.base');
|
|
||||||
const FriendlyErrors = require('friendly-errors-webpack-plugin');
|
|
||||||
const AutoDllPlugin = require('autodll-webpack-plugin');
|
|
||||||
const config = require('./config');
|
|
||||||
const HardSourceWebpackPlugin = require('hard-source-webpack-plugin');
|
|
||||||
const threadLoader = require('thread-loader');
|
|
||||||
|
|
||||||
const fullSourceMap = process.env.SLOW_BUILD_SPEED;
|
module.exports = makeConfig({
|
||||||
if (fullSourceMap) {
|
isProduction: false,
|
||||||
base.devtool = fullSourceMap ? 'source-map' : 'cheap-module-eval-source-map';
|
isElectronBuild: !!process.env.BUILD_ELECTRON
|
||||||
|
});
|
||||||
threadLoader.warmup(
|
|
||||||
{
|
|
||||||
// pool options, like passed to loader options
|
|
||||||
// must match loader options to boot the correct pool
|
|
||||||
happyPackMode: true,
|
|
||||||
logLevel: 'info'
|
|
||||||
},
|
|
||||||
[
|
|
||||||
// modules to load
|
|
||||||
// can be any module, i. e.
|
|
||||||
'ts-loader'
|
|
||||||
]
|
|
||||||
);
|
|
||||||
base.module.rules[0].use.unshift({
|
|
||||||
loader: 'thread-loader',
|
|
||||||
options: {
|
|
||||||
workers: 4
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
base.performance = { hints: false };
|
|
||||||
|
|
||||||
base.module.rules.push(
|
|
||||||
{
|
|
||||||
test: /\.css$/,
|
|
||||||
include: path.resolve(__dirname, '../common/vendor'),
|
|
||||||
use: ['style-loader', 'css-loader']
|
|
||||||
},
|
|
||||||
{
|
|
||||||
test: /\.scss$/,
|
|
||||||
include: ['components', 'containers', 'sass']
|
|
||||||
.map(dir => path.resolve(__dirname, `../common/${dir}`))
|
|
||||||
.concat([path.resolve(__dirname, '../node_modules')]),
|
|
||||||
|
|
||||||
exclude: /node_modules(?!\/font-awesome)/,
|
|
||||||
use: ['style-loader', 'css-loader', 'sass-loader']
|
|
||||||
},
|
|
||||||
{
|
|
||||||
test: /\.less$/,
|
|
||||||
include: path.resolve(__dirname, '../common/assets/styles'),
|
|
||||||
use: ['style-loader', 'css-loader', 'less-loader']
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
base.plugins.push(
|
|
||||||
new AutoDllPlugin({
|
|
||||||
inject: true, // will inject the DLL bundles to index.html
|
|
||||||
filename: '[name]_[hash].js',
|
|
||||||
debug: true,
|
|
||||||
context: path.join(__dirname, '..'),
|
|
||||||
entry: {
|
|
||||||
vendor: [...config.vendor, 'babel-polyfill', 'bootstrap-sass', 'font-awesome']
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
new HardSourceWebpackPlugin({
|
|
||||||
environmentHash: {
|
|
||||||
root: process.cwd(),
|
|
||||||
directories: ['webpack_config'],
|
|
||||||
files: ['package.json']
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
|
|
||||||
new webpack.DefinePlugin({
|
|
||||||
'process.env.NODE_ENV': JSON.stringify('development')
|
|
||||||
}),
|
|
||||||
new webpack.HotModuleReplacementPlugin(),
|
|
||||||
new webpack.NoEmitOnErrorsPlugin(),
|
|
||||||
new FriendlyErrors()
|
|
||||||
);
|
|
||||||
|
|
||||||
module.exports = base;
|
|
||||||
|
|
|
@ -0,0 +1,36 @@
|
||||||
|
'use strict';
|
||||||
|
const webpack = require('webpack');
|
||||||
|
const path = require('path');
|
||||||
|
const ClearDistPlugin = require('./plugins/clearDist');
|
||||||
|
const config = require('./config');
|
||||||
|
const makeConfig = require('./makeConfig');
|
||||||
|
|
||||||
|
const electronConfig = {
|
||||||
|
target: 'electron-main',
|
||||||
|
entry: {
|
||||||
|
main: path.join(config.path.electron, 'main/index.ts'),
|
||||||
|
preload: path.join(config.path.electron, 'preload.ts')
|
||||||
|
},
|
||||||
|
module: {
|
||||||
|
rules: [config.typescriptRule]
|
||||||
|
},
|
||||||
|
resolve: {
|
||||||
|
extensions: ['.ts', '.js', '.json']
|
||||||
|
},
|
||||||
|
output: {
|
||||||
|
filename: '[name].js',
|
||||||
|
path: path.resolve(config.path.output, 'electron-js')
|
||||||
|
},
|
||||||
|
plugins: [
|
||||||
|
new ClearDistPlugin(),
|
||||||
|
new webpack.DefinePlugin({
|
||||||
|
'process.env.NODE_ENV': JSON.stringify('development')
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
node: {
|
||||||
|
__dirname: false,
|
||||||
|
__filename: false
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = electronConfig;
|
|
@ -0,0 +1,24 @@
|
||||||
|
'use strict';
|
||||||
|
const webpack = require('webpack');
|
||||||
|
const path = require('path');
|
||||||
|
const ClearDistPlugin = require('./plugins/clearDist');
|
||||||
|
const DelayPlugin = require('./plugins/delay');
|
||||||
|
const makeConfig = require('./makeConfig');
|
||||||
|
const electronConfig = require('./webpack.electron-dev.js');
|
||||||
|
|
||||||
|
const jsConfig = makeConfig({
|
||||||
|
isProduction: true,
|
||||||
|
isElectronBuild: true,
|
||||||
|
outputDir: 'electron-js'
|
||||||
|
});
|
||||||
|
|
||||||
|
// Redefine plugins with prod specific stuff
|
||||||
|
electronConfig.plugins = [
|
||||||
|
new ClearDistPlugin(),
|
||||||
|
new webpack.DefinePlugin({
|
||||||
|
'process.env.NODE_ENV': JSON.stringify('production')
|
||||||
|
}),
|
||||||
|
new DelayPlugin(500)
|
||||||
|
];
|
||||||
|
|
||||||
|
module.exports = [electronConfig, jsConfig];
|
|
@ -1,22 +1,19 @@
|
||||||
// Compile freezer using the (mostly) same webpack config
|
// Compile freezer using the (mostly) same webpack config
|
||||||
'use strict';
|
'use strict';
|
||||||
const baseConfig = require('./webpack.base');
|
const path = require('path');
|
||||||
|
const config = require('./config');
|
||||||
|
|
||||||
const freezerConfig = Object.assign({}, baseConfig, {
|
const freezerConfig = {
|
||||||
// Remove the cruft we don't need
|
|
||||||
plugins: undefined,
|
|
||||||
target: undefined,
|
|
||||||
performance: undefined,
|
|
||||||
module: {
|
|
||||||
// Typescript loader
|
|
||||||
loaders: [baseConfig.module.rules[0]]
|
|
||||||
},
|
|
||||||
|
|
||||||
// Point at freezer, make sure it's setup to run in node
|
|
||||||
target: 'node',
|
target: 'node',
|
||||||
entry: {
|
entry: './common/freezer',
|
||||||
'freezer': './common/freezer'
|
output: {
|
||||||
}
|
path: config.path.output,
|
||||||
});
|
filename: 'freezer.js'
|
||||||
|
},
|
||||||
|
module: {
|
||||||
|
rules: [config.typescriptRule],
|
||||||
|
},
|
||||||
|
resolve: config.resolve,
|
||||||
|
};
|
||||||
|
|
||||||
module.exports = freezerConfig;
|
module.exports = freezerConfig;
|
||||||
|
|
|
@ -0,0 +1,8 @@
|
||||||
|
'use strict';
|
||||||
|
const makeConfig = require('./makeConfig');
|
||||||
|
|
||||||
|
module.exports = makeConfig({
|
||||||
|
isProduction: true,
|
||||||
|
isHTMLBuild: true,
|
||||||
|
outputDir: 'download'
|
||||||
|
});
|
|
@ -1,101 +1,7 @@
|
||||||
'use strict';
|
'use strict';
|
||||||
process.env.NODE_ENV = 'production';
|
const makeConfig = require('./makeConfig');
|
||||||
const FaviconsWebpackPlugin = require('favicons-webpack-plugin');
|
|
||||||
const path = require('path');
|
|
||||||
const webpack = require('webpack');
|
|
||||||
const ExtractTextPlugin = require('extract-text-webpack-plugin');
|
|
||||||
const ProgressPlugin = require('webpack/lib/ProgressPlugin');
|
|
||||||
const BabelMinifyPlugin = require('babel-minify-webpack-plugin');
|
|
||||||
// const OfflinePlugin = require('offline-plugin')
|
|
||||||
const base = require('./webpack.base');
|
|
||||||
const config = require('./config');
|
|
||||||
const rimraf = require('rimraf');
|
|
||||||
const distFolder = 'dist/';
|
|
||||||
|
|
||||||
// Clear out build folder
|
module.exports = makeConfig({
|
||||||
rimraf.sync(distFolder, { rmdirSync: true });
|
isProduction: true,
|
||||||
|
outputDir: 'prod'
|
||||||
base.devtool = false;
|
});
|
||||||
base.module.rules.push(
|
|
||||||
{
|
|
||||||
test: /\.css$/,
|
|
||||||
use: ExtractTextPlugin.extract({
|
|
||||||
fallback: 'style-loader',
|
|
||||||
use: 'css-loader'
|
|
||||||
})
|
|
||||||
},
|
|
||||||
{
|
|
||||||
test: /\.scss$/,
|
|
||||||
use: ExtractTextPlugin.extract({
|
|
||||||
fallback: 'style-loader',
|
|
||||||
use: ['css-loader', 'sass-loader']
|
|
||||||
})
|
|
||||||
},
|
|
||||||
{
|
|
||||||
test: /\.less$/,
|
|
||||||
use: ExtractTextPlugin.extract({
|
|
||||||
fallback: 'style-loader',
|
|
||||||
use: ['css-loader', 'less-loader']
|
|
||||||
})
|
|
||||||
}
|
|
||||||
);
|
|
||||||
// a white list to add dependencies to vendor chunk
|
|
||||||
base.entry.vendor = config.vendor;
|
|
||||||
// use hash filename to support long-term caching
|
|
||||||
base.output.filename = '[name].[chunkhash:8].js';
|
|
||||||
// add webpack plugins
|
|
||||||
base.plugins.unshift(
|
|
||||||
new FaviconsWebpackPlugin({
|
|
||||||
logo: path.resolve(__dirname, '../static/favicon/android-chrome-384x384.png'),
|
|
||||||
background: '#163151',
|
|
||||||
inject: true
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
base.plugins.push(
|
|
||||||
new ProgressPlugin(),
|
|
||||||
new ExtractTextPlugin('[name].[chunkhash:8].css'),
|
|
||||||
new webpack.DefinePlugin({
|
|
||||||
'process.env.BUILD_DOWNLOADABLE': JSON.stringify(!!process.env.BUILD_DOWNLOADABLE)
|
|
||||||
}),
|
|
||||||
new webpack.DefinePlugin({
|
|
||||||
'process.env.NODE_ENV': JSON.stringify('production')
|
|
||||||
}),
|
|
||||||
new BabelMinifyPlugin({
|
|
||||||
// Mangle seems to be reusing variable identifiers, causing errors
|
|
||||||
mangle: false,
|
|
||||||
// These two on top of a lodash file are causing illegal characters for
|
|
||||||
// safari and ios browsers
|
|
||||||
evaluate: false,
|
|
||||||
propertyLiterals: false,
|
|
||||||
}, {
|
|
||||||
comments: false
|
|
||||||
}),
|
|
||||||
// extract vendor chunks
|
|
||||||
new webpack.optimize.CommonsChunkPlugin({
|
|
||||||
name: 'vendor',
|
|
||||||
filename: 'vendor.[chunkhash:8].js'
|
|
||||||
})
|
|
||||||
// For progressive web apps
|
|
||||||
// new OfflinePlugin({
|
|
||||||
// relativePaths: false,
|
|
||||||
// AppCache: false,
|
|
||||||
// ServiceWorker: {
|
|
||||||
// events: true
|
|
||||||
// }
|
|
||||||
// })
|
|
||||||
);
|
|
||||||
|
|
||||||
// minimize webpack output
|
|
||||||
base.stats = {
|
|
||||||
// Add children information
|
|
||||||
children: false,
|
|
||||||
// Add chunk information (setting this to `false` allows for a less verbose output)
|
|
||||||
chunks: false,
|
|
||||||
// Add built modules information to chunk information
|
|
||||||
chunkModules: false,
|
|
||||||
chunkOrigins: false,
|
|
||||||
modules: false
|
|
||||||
};
|
|
||||||
|
|
||||||
module.exports = base;
|
|
||||||
|
|
Loading…
Reference in New Issue