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 PageNotFound from 'components/PageNotFound';
|
||||
import LogOutPrompt from 'components/LogOutPrompt';
|
||||
import { TitleBar } from 'components/ui';
|
||||
import { Store } from 'redux';
|
||||
import { pollOfflineStatus } from 'actions/config';
|
||||
import { AppState } from 'reducers';
|
||||
|
@ -73,12 +74,16 @@ export default class Root extends Component<Props, State> {
|
|||
</CaptureRouteNotFound>
|
||||
);
|
||||
|
||||
const Router = process.env.BUILD_DOWNLOADABLE ? HashRouter : BrowserRouter;
|
||||
const Router =
|
||||
process.env.BUILD_DOWNLOADABLE && process.env.NODE_ENV === 'production'
|
||||
? HashRouter
|
||||
: BrowserRouter;
|
||||
|
||||
return (
|
||||
<Provider store={store} key={Math.random()}>
|
||||
<Router key={Math.random()}>
|
||||
<React.Fragment>
|
||||
{process.env.BUILD_ELECTRON && <TitleBar />}
|
||||
{routes}
|
||||
<LegacyRoutes />
|
||||
<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,
|
||||
languages,
|
||||
NODES,
|
||||
VERSION,
|
||||
NodeConfig,
|
||||
CustomNodeConfig,
|
||||
CustomNetworkConfig
|
||||
|
@ -25,6 +24,7 @@ import GasPriceDropdown from './components/GasPriceDropdown';
|
|||
import Navigation from './components/Navigation';
|
||||
import CustomNodeModal from './components/CustomNodeModal';
|
||||
import OnlineStatus from './components/OnlineStatus';
|
||||
import Version from './components/Version';
|
||||
import { getKeyByValue } from 'utils/helpers';
|
||||
import { makeCustomNodeId } from 'utils/node';
|
||||
import { getNetworkConfigFromId } from 'utils/network';
|
||||
|
@ -128,7 +128,9 @@ export default class Header extends Component<Props, State> {
|
|||
/>
|
||||
</Link>
|
||||
<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">
|
||||
<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 { openInBrowser } from 'utils/electron';
|
||||
|
||||
interface AAttributes {
|
||||
charset?: string;
|
||||
|
@ -35,10 +36,21 @@ interface NewTabLinkProps extends AAttributes {
|
|||
children?: React.ReactElement<any> | string;
|
||||
}
|
||||
|
||||
const NewTabLink = ({ content, children, ...rest }: NewTabLinkProps) => (
|
||||
<a target="_blank" rel="noopener noreferrer" {...rest}>
|
||||
{content || children}
|
||||
</a>
|
||||
);
|
||||
export class NewTabLink extends React.Component<NewTabLinkProps> {
|
||||
public render() {
|
||||
const { content, children, ...rest } = this.props;
|
||||
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;
|
||||
|
|
|
@ -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 SwapDropdown } from './SwapDropdown';
|
||||
export { default as Tooltip } from './Tooltip';
|
||||
export { default as TitleBar } from './TitleBar';
|
||||
export * from './ConditionalInput';
|
||||
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) {
|
||||
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)$":
|
||||
"<rootDir>/jest_config/__mocks__/fileMock.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"],
|
||||
"setupFiles": [
|
||||
|
|
|
@ -9,7 +9,8 @@
|
|||
"moduleNameMapper": {
|
||||
"\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$":
|
||||
"<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"],
|
||||
"setupFiles": [
|
||||
|
|
26
package.json
26
package.json
|
@ -1,8 +1,10 @@
|
|||
{
|
||||
"name": "MyEtherWallet",
|
||||
"author": "MyEtherWallet",
|
||||
"version": "4.0.0-alpha.1",
|
||||
"main": "common/index.jsx",
|
||||
"main": "main.js",
|
||||
"description": "MyEtherWallet v4",
|
||||
"repository": "https://github.com/wbobeirne/MyEtherWallet-electron",
|
||||
"engines": {
|
||||
"node": ">= 8.0.0",
|
||||
"npm": ">= 5.0.0"
|
||||
|
@ -13,6 +15,7 @@
|
|||
"bn.js": "4.11.8",
|
||||
"bootstrap-sass": "3.3.7",
|
||||
"classnames": "2.2.5",
|
||||
"electron-updater": "2.18.2",
|
||||
"ethereum-blockies": "git+https://github.com/MyEtherWallet/blockies.git",
|
||||
"ethereumjs-abi": "0.6.5",
|
||||
"ethereumjs-tx": "1.3.3",
|
||||
|
@ -74,8 +77,11 @@
|
|||
"bs58": "4.0.1",
|
||||
"cache-loader": "1.2.0",
|
||||
"check-node-version": "3.2.0",
|
||||
"concurrently": "3.5.1",
|
||||
"copy-webpack-plugin": "4.3.1",
|
||||
"css-loader": "0.28.8",
|
||||
"electron": "1.7.10",
|
||||
"electron-builder": "19.52.1",
|
||||
"empty": "0.10.1",
|
||||
"enzyme": "3.3.0",
|
||||
"enzyme-adapter-react-16": "1.1.1",
|
||||
|
@ -131,21 +137,27 @@
|
|||
"freezer": "webpack --config=./webpack_config/webpack.freezer.js && node ./dist/freezer.js",
|
||||
"freezer:validate": "npm run freezer -- --validate",
|
||||
"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",
|
||||
"postbuild": "node ./utils/postBuild.js",
|
||||
"build:downloadable": "BUILD_DOWNLOADABLE=true rimraf dist && webpack --config webpack_config/webpack.prod.js",
|
||||
"prebuild:demo": "check-node-version --package",
|
||||
"build:downloadable": "webpack --config webpack_config/webpack.html.js",
|
||||
"prebuild:downloadable": "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": "jest --config=jest_config/jest.config.json",
|
||||
"test:unit": "jest --config=jest_config/jest.config.json --coverage",
|
||||
"test:int": "jest --config=jest_config/jest.int.config.json --coverage",
|
||||
"updateSnapshot": "jest --config=jest_config/jest.config.json --updateSnapshot",
|
||||
"pretest": "check-node-version --package",
|
||||
"dev": "node webpack_config/server.js",
|
||||
"dev": "node webpack_config/devServer.js",
|
||||
"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",
|
||||
"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/**/*",
|
||||
"tscheck": "tsc --noEmit",
|
||||
"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",
|
||||
"allowJs": true,
|
||||
"baseUrl": "./common/",
|
||||
"paths": {
|
||||
"shared*": ["../shared*"]
|
||||
},
|
||||
"lib": [
|
||||
"es2017",
|
||||
"dom"
|
||||
|
@ -20,6 +23,7 @@
|
|||
},
|
||||
"include": [
|
||||
"./common/",
|
||||
"./electron/",
|
||||
"spec",
|
||||
"./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';
|
||||
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 = {
|
||||
// Configuration
|
||||
port: process.env.HTTPS ? 3443 : 3000,
|
||||
title: 'MEW',
|
||||
publicPath: process.env.BUILD_DOWNLOADABLE ? './' : '/',
|
||||
srcPath: path.join(__dirname, './../common'),
|
||||
// add these dependencies to a standalone vendor bundle
|
||||
vendor: [
|
||||
path: paths,
|
||||
|
||||
// Typescript rule config
|
||||
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',
|
||||
'bn.js',
|
||||
'classnames',
|
||||
|
|
|
@ -6,7 +6,7 @@ const https = require('https');
|
|||
const fs = require('fs');
|
||||
const webpackConfig = require('./webpack.dev');
|
||||
const config = require('./config');
|
||||
const LogPlugin = require('./log-plugin');
|
||||
const LogPlugin = require('./plugins/serverLog');
|
||||
|
||||
const app = express();
|
||||
|
||||
|
@ -25,13 +25,13 @@ let compiler;
|
|||
try {
|
||||
compiler = webpack(webpackConfig);
|
||||
} catch (err) {
|
||||
console.log(err.message);
|
||||
console.error(err.message);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const devMiddleWare = require('webpack-dev-middleware')(compiler, {
|
||||
publicPath: webpackConfig.output.publicPath,
|
||||
quiet: true,
|
||||
logLevel: 'warn',
|
||||
inline: true,
|
||||
headers: {
|
||||
'Access-Control-Allow-Origin': '*',
|
||||
|
@ -45,7 +45,7 @@ const devMiddleWare = require('webpack-dev-middleware')(compiler, {
|
|||
app.use(devMiddleWare);
|
||||
app.use(
|
||||
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
|
||||
'use strict';
|
||||
const baseConfig = require('./webpack.base');
|
||||
const path = require('path');
|
||||
const config = require('./config');
|
||||
|
||||
const derivationConfig = Object.assign({}, baseConfig, {
|
||||
// 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
|
||||
const derivationConfig = {
|
||||
target: 'node',
|
||||
entry: {
|
||||
'derivation-checker': './common/derivation-checker.ts'
|
||||
}
|
||||
});
|
||||
entry: './common/derivation-checker.ts',
|
||||
output: {
|
||||
path: config.path.output,
|
||||
filename: 'derivation-checker.js'
|
||||
},
|
||||
module: {
|
||||
rules: [config.typescriptRule],
|
||||
},
|
||||
resolve: config.resolve,
|
||||
};
|
||||
|
||||
module.exports = derivationConfig;
|
||||
|
|
|
@ -1,87 +1,7 @@
|
|||
'use strict';
|
||||
process.env.NODE_ENV = 'development';
|
||||
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 makeConfig = require('./makeConfig');
|
||||
|
||||
const fullSourceMap = process.env.SLOW_BUILD_SPEED;
|
||||
if (fullSourceMap) {
|
||||
base.devtool = fullSourceMap ? 'source-map' : 'cheap-module-eval-source-map';
|
||||
|
||||
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;
|
||||
module.exports = makeConfig({
|
||||
isProduction: false,
|
||||
isElectronBuild: !!process.env.BUILD_ELECTRON
|
||||
});
|
||||
|
|
|
@ -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
|
||||
'use strict';
|
||||
const baseConfig = require('./webpack.base');
|
||||
const path = require('path');
|
||||
const config = require('./config');
|
||||
|
||||
const freezerConfig = Object.assign({}, baseConfig, {
|
||||
// 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
|
||||
const freezerConfig = {
|
||||
target: 'node',
|
||||
entry: {
|
||||
'freezer': './common/freezer'
|
||||
}
|
||||
});
|
||||
entry: './common/freezer',
|
||||
output: {
|
||||
path: config.path.output,
|
||||
filename: 'freezer.js'
|
||||
},
|
||||
module: {
|
||||
rules: [config.typescriptRule],
|
||||
},
|
||||
resolve: config.resolve,
|
||||
};
|
||||
|
||||
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';
|
||||
process.env.NODE_ENV = 'production';
|
||||
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/';
|
||||
const makeConfig = require('./makeConfig');
|
||||
|
||||
// Clear out build folder
|
||||
rimraf.sync(distFolder, { rmdirSync: true });
|
||||
|
||||
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;
|
||||
module.exports = makeConfig({
|
||||
isProduction: true,
|
||||
outputDir: 'prod'
|
||||
});
|
||||
|
|
Loading…
Reference in New Issue