Webpack everything, reduce package size

Changelog-entry: Webpack everything, reduce package size
Change-type: patch
This commit is contained in:
Alexis Svinartchouk 2020-05-20 19:01:06 +02:00
parent 8b5a5241f2
commit 1ebc8e9362
48 changed files with 1793 additions and 10547 deletions

View File

@ -1,6 +1,6 @@
{ {
"electron": { "electron": {
"npm_version": "6.7.0", "npm_version": "6.14.5",
"dependencies": { "dependencies": {
"linux": [ "linux": [
"libudev-dev", "libudev-dev",
@ -19,27 +19,8 @@
"nodeGypRebuild": true, "nodeGypRebuild": true,
"afterPack": "./afterPack.js", "afterPack": "./afterPack.js",
"files": [ "files": [
"build/Release/elevator.node",
"generated", "generated",
"lib/shared/catalina-sudo/sudo-askpass.osascript.js", "lib/shared/catalina-sudo/sudo-askpass.osascript.js"
"lib/gui/app/index.html",
"lib/gui/css/*.css",
"lib/gui/css/fonts/*.woff2",
"lib/gui/assets/*.svg",
"assets/icon.png",
"!node_modules/**/**",
"node_modules/**/*.js",
"node_modules/**/*.json",
"node_modules/**/*.node",
"node_modules/**/*.dll",
"node_modules/node-raspberrypi-usbboot/blobs/**",
"node_modules/flexboxgrid/dist/flexboxgrid.css",
"node_modules/roboto-fontface/fonts/roboto/Roboto-Thin.woff",
"node_modules/roboto-fontface/fonts/roboto/Roboto-Light.woff",
"node_modules/roboto-fontface/fonts/roboto/Roboto-Regular.woff",
"node_modules/roboto-fontface/fonts/roboto/Roboto-Medium.woff",
"node_modules/roboto-fontface/fonts/roboto/Roboto-Bold.woff",
"node_modules/bootstrap-sass/assets/fonts/bootstrap/glyphicons-halflings-regular.woff2"
], ],
"afterSign": "./afterSignHook.js", "afterSign": "./afterSignHook.js",
"mac": { "mac": {

View File

@ -3,7 +3,7 @@
# --------------------------------------------------------------------- # ---------------------------------------------------------------------
RESIN_SCRIPTS ?= ./scripts/resin RESIN_SCRIPTS ?= ./scripts/resin
export NPM_VERSION ?= 6.7.0 export NPM_VERSION ?= 6.14.5
S3_BUCKET = artifacts.ci.balena-cloud.com S3_BUCKET = artifacts.ci.balena-cloud.com
# This directory will be completely deleted by the `clean` rule # This directory will be completely deleted by the `clean` rule
@ -144,10 +144,6 @@ webpack:
.PHONY: $(TARGETS) .PHONY: $(TARGETS)
sass:
npm rebuild node-sass
node-sass lib/gui/app/scss/main.scss > lib/gui/css/main.css
lint-ts: lint-ts:
balena-lint --fix --typescript typings lib tests scripts/clean-shrinkwrap.ts webpack.config.ts balena-lint --fix --typescript typings lib tests scripts/clean-shrinkwrap.ts webpack.config.ts
@ -190,7 +186,6 @@ info:
@echo "Target arch : $(TARGET_ARCH)" @echo "Target arch : $(TARGET_ARCH)"
sanity-checks: sanity-checks:
./scripts/ci/ensure-staged-sass.sh
./scripts/ci/ensure-all-file-extensions-in-gitattributes.sh ./scripts/ci/ensure-all-file-extensions-in-gitattributes.sh
clean: clean:

View File

@ -6,27 +6,8 @@ nodeGypRebuild: true
publish: null publish: null
afterPack: "./afterPack.js" afterPack: "./afterPack.js"
files: files:
- build/Release/elevator.node
- generated - generated
- lib/shared/catalina-sudo/sudo-askpass.osascript.js - lib/shared/catalina-sudo/sudo-askpass.osascript.js
- lib/gui/app/index.html
- lib/gui/css/*.css
- lib/gui/css/fonts/*.woff2
- lib/gui/assets/*.svg
- assets/icon.png
- "!node_modules/**/**"
- "node_modules/**/*.js"
- "node_modules/**/*.json"
- "node_modules/**/*.node"
- "node_modules/**/*.dll"
- node_modules/node-raspberrypi-usbboot/blobs/**
- node_modules/flexboxgrid/dist/flexboxgrid.css
- node_modules/roboto-fontface/fonts/roboto/Roboto-Thin.woff
- node_modules/roboto-fontface/fonts/roboto/Roboto-Light.woff
- node_modules/roboto-fontface/fonts/roboto/Roboto-Regular.woff
- node_modules/roboto-fontface/fonts/roboto/Roboto-Medium.woff
- node_modules/roboto-fontface/fonts/roboto/Roboto-Bold.woff
- node_modules/bootstrap-sass/assets/fonts/bootstrap/glyphicons-halflings-regular.woff2
mac: mac:
asar: false asar: false
icon: assets/icon.icns icon: assets/icon.icns

View File

@ -171,7 +171,7 @@ export function DriveSelectorModal({ close }: { close: () => void }) {
<img <img
className="list-group-item-section" className="list-group-item-section"
alt="Drive device type logo" alt="Drive device type logo"
src={`../assets/${drive.icon}.svg`} src={`media/${drive.icon}.svg`}
width="25" width="25"
height="30" height="30"
/> />

View File

@ -78,7 +78,7 @@ function FinishPage({ goToMain }: { goToMain: () => void }) {
} }
> >
<SVGIcon <SVGIcon
paths={['../../assets/etcher.svg']} paths={['etcher.svg']}
width="165px" width="165px"
height="auto" height="auto"
></SVGIcon> ></SVGIcon>
@ -87,7 +87,7 @@ function FinishPage({ goToMain }: { goToMain: () => void }) {
<div className="caption caption-small fallback-footer"> <div className="caption caption-small fallback-footer">
made with made with
<SVGIcon <SVGIcon
paths={['../../assets/love.svg']} paths={['love.svg']}
width="auto" width="auto"
height="20px" height="20px"
></SVGIcon> ></SVGIcon>
@ -99,7 +99,7 @@ function FinishPage({ goToMain }: { goToMain: () => void }) {
} }
> >
<SVGIcon <SVGIcon
paths={['../../assets/balena.svg']} paths={['balena.svg']}
width="auto" width="auto"
height="20px" height="20px"
></SVGIcon> ></SVGIcon>

View File

@ -74,7 +74,7 @@ export class ReducedFlashingInfos extends React.Component<
<SVGIcon <SVGIcon
disabled disabled
contents={[this.props.imageLogo]} contents={[this.props.imageLogo]}
paths={['../../assets/image.svg']} paths={['image.svg']}
width="20px" width="20px"
></SVGIcon> ></SVGIcon>
<Span>{this.props.imageName}</Span> <Span>{this.props.imageName}</Span>
@ -82,11 +82,7 @@ export class ReducedFlashingInfos extends React.Component<
</Span> </Span>
<Span className="step-name"> <Span className="step-name">
<SVGIcon <SVGIcon disabled paths={['drive.svg']} width="20px"></SVGIcon>
disabled
paths={['../../assets/drive.svg']}
width="20px"
></SVGIcon>
<Span>{this.props.driveTitle}</Span> <Span>{this.props.driveTitle}</Span>
</Span> </Span>
</Div> </Div>

View File

@ -477,7 +477,7 @@ export class SourceSelector extends React.Component<
<div className="center-block"> <div className="center-block">
<SVGIcon <SVGIcon
contents={[selectionState.getImageLogo()]} contents={[selectionState.getImageLogo()]}
paths={['../../assets/image.svg']} paths={['image.svg']}
/> />
</div> </div>

View File

@ -74,7 +74,7 @@ export class SVGIcon extends React.Component<SVGIconProps> {
// that's the only way to get the "real" __dirname. // that's the only way to get the "real" __dirname.
let baseDirectory: string; let baseDirectory: string;
if (path.isAbsolute(__dirname)) { if (path.isAbsolute(__dirname)) {
baseDirectory = path.join(__dirname, '..'); baseDirectory = path.join(__dirname);
} else { } else {
// @ts-ignore // @ts-ignore
baseDirectory = global.__dirname; baseDirectory = global.__dirname;
@ -99,7 +99,7 @@ export class SVGIcon extends React.Component<SVGIconProps> {
// relative to *this directory*. // relative to *this directory*.
// TODO: There might be a way to compute the path // TODO: There might be a way to compute the path
// relatively to the `index.html`. // relatively to the `index.html`.
const imagePath = path.join(baseDirectory, 'assets', relativePath); const imagePath = path.join(baseDirectory, 'media', relativePath);
const contents = _.attempt(() => { const contents = _.attempt(() => {
return fs.readFileSync(imagePath, { return fs.readFileSync(imagePath, {

View File

@ -3,12 +3,10 @@
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<title>Etcher</title> <title>Etcher</title>
<link rel="stylesheet" type="text/css" href="../../../node_modules/flexboxgrid/dist/flexboxgrid.css"> <link rel="stylesheet" type="text/css" href="index.css">
<link rel="stylesheet" type="text/css" href="../css/main.css">
<link rel="stylesheet" type="text/css" href="../css/desktop.css">
</head> </head>
<body> <body>
<main id="main"></main> <main id="main"></main>
<script src="../../../generated/gui.js"></script> <script src="gui.js"></script>
</body> </body>
</html> </html>

View File

@ -105,7 +105,7 @@ export const DriveSelector = ({
)} )}
<div className="center-block"> <div className="center-block">
<SVGIcon paths={['../../assets/drive.svg']} disabled={disabled} /> <SVGIcon paths={['drive.svg']} disabled={disabled} />
</div> </div>
<div className="space-vertical-large"> <div className="space-vertical-large">

View File

@ -90,7 +90,7 @@ async function flashImageToDrive(
// otherwise Windows throws EPERM // otherwise Windows throws EPERM
driveScanner.stop(); driveScanner.stop();
const iconPath = path.join('..', '..', '..', 'assets', 'icon.png'); const iconPath = path.join('media', 'icon.png');
const basename = path.basename(image.path); const basename = path.basename(image.path);
try { try {
await imageWriter.flash(image.path, drives, sourceOptions); await imageWriter.flash(image.path, drives, sourceOptions);
@ -228,7 +228,7 @@ export class FlashStep extends React.Component<FlashStepProps, FlashStepState> {
<div className="box text-center"> <div className="box text-center">
<div className="center-block"> <div className="center-block">
<SVGIcon <SVGIcon
paths={['../../assets/flash.svg']} paths={['flash.svg']}
disabled={this.props.shouldFlashStepBeDisabled} disabled={this.props.shouldFlashStepBeDisabled}
/> />
</div> </div>

View File

@ -153,11 +153,7 @@ export class MainPage extends React.Component<
} }
tabIndex={100} tabIndex={100}
> >
<SVGIcon <SVGIcon paths={['etcher.svg']} width="123px" height="22px" />
paths={['../../assets/etcher.svg']}
width="123px"
height="22px"
/>
</span> </span>
<span <span

View File

@ -14,7 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
$icon-font-path: "../../../node_modules/bootstrap-sass/assets/fonts/bootstrap/"; $icon-font-path: "../../../../node_modules/bootstrap-sass/assets/fonts/bootstrap/";
$font-size-base: 16px; $font-size-base: 16px;
$cursor-disabled: initial; $cursor-disabled: initial;
$link-hover-decoration: none; $link-hover-decoration: none;
@ -22,6 +22,7 @@ $btn-min-width: 170px;
$link-color: #ddd; $link-color: #ddd;
$disabled-opacity: 0.2; $disabled-opacity: 0.2;
@import "../../../../node_modules/flexboxgrid/dist/flexboxgrid.css";
@import "../../../../node_modules/bootstrap-sass/assets/stylesheets/bootstrap"; @import "../../../../node_modules/bootstrap-sass/assets/stylesheets/bootstrap";
@import "./modules/theme"; @import "./modules/theme";
@import "./modules/bootstrap"; @import "./modules/bootstrap";
@ -34,15 +35,15 @@ $disabled-opacity: 0.2;
@import "../components/drive-selector/styles/drive-selector"; @import "../components/drive-selector/styles/drive-selector";
@import "../pages/main/styles/main"; @import "../pages/main/styles/main";
@import "../pages/finish/styles/finish"; @import "../pages/finish/styles/finish";
@import "./desktop";
$fa-font-path: "../../../node_modules/@fortawesome/fontawesome-free-webfonts/webfonts"; $fa-font-path: "../../../../node_modules/@fortawesome/fontawesome-free-webfonts/webfonts";
@import "../../../../node_modules/@fortawesome/fontawesome-free-webfonts/scss/fontawesome"; @import "../../../../node_modules/@fortawesome/fontawesome-free-webfonts/scss/fontawesome";
@import "../../../../node_modules/@fortawesome/fontawesome-free-webfonts/scss/fa-solid"; @import "../../../../node_modules/@fortawesome/fontawesome-free-webfonts/scss/fa-solid";
@font-face { @font-face {
font-family: "Nunito"; font-family: "Nunito";
src: url("Nunito-Regular.eot");
src: url("./fonts/Nunito-Regular.eot?#iefix") format("embedded-opentype"), src: url("./fonts/Nunito-Regular.eot?#iefix") format("embedded-opentype"),
url("./fonts/Nunito-Regular.woff2") format("woff2"), url("./fonts/Nunito-Regular.woff2") format("woff2"),
url("./fonts/Nunito-Regular.woff") format("woff"), url("./fonts/Nunito-Regular.woff") format("woff"),
@ -54,7 +55,6 @@ $fa-font-path: "../../../node_modules/@fortawesome/fontawesome-free-webfonts/web
@font-face { @font-face {
font-family: "Nunito"; font-family: "Nunito";
src: url("Nunito-Bold.eot");
src: url("./fonts/Nunito-Bold.eot?#iefix") format("embedded-opentype"), src: url("./fonts/Nunito-Bold.eot?#iefix") format("embedded-opentype"),
url("./fonts/Nunito-Bold.woff2") format("woff2"), url("./fonts/Nunito-Bold.woff2") format("woff2"),
url("./fonts/Nunito-Bold.woff") format("woff"), url("./fonts/Nunito-Bold.woff") format("woff"),
@ -66,7 +66,6 @@ $fa-font-path: "../../../node_modules/@fortawesome/fontawesome-free-webfonts/web
@font-face { @font-face {
font-family: "Nunito"; font-family: "Nunito";
src: url("Nunito-Light.eot");
src: url("./fonts/Nunito-Light.eot?#iefix") format("embedded-opentype"), src: url("./fonts/Nunito-Light.eot?#iefix") format("embedded-opentype"),
url("./fonts/Nunito-Light.woff2") format("woff2"), url("./fonts/Nunito-Light.woff2") format("woff2"),
url("./fonts/Nunito-Light.woff") format("woff"), url("./fonts/Nunito-Light.woff") format("woff"),

File diff suppressed because it is too large Load Diff

View File

@ -76,7 +76,7 @@ async function createMainWindow() {
kiosk: fullscreen, kiosk: fullscreen,
autoHideMenuBar: true, autoHideMenuBar: true,
titleBarStyle: 'hiddenInset', titleBarStyle: 'hiddenInset',
icon: path.join(__dirname, '..', '..', 'assets', 'icon.png'), icon: path.join(__dirname, 'media', 'icon.png'),
darkTheme: true, darkTheme: true,
webPreferences: { webPreferences: {
backgroundThrottling: false, backgroundThrottling: false,
@ -102,9 +102,7 @@ async function createMainWindow() {
event.preventDefault(); event.preventDefault();
}); });
mainWindow.loadURL( mainWindow.loadURL(`file://${path.join(__dirname, 'index.html')}`);
`file://${path.join(__dirname, '..', 'lib', 'gui', 'app', 'index.html')}`,
);
const page = mainWindow.webContents; const page = mainWindow.webContents;

View File

@ -14,7 +14,6 @@
* limitations under the License. * limitations under the License.
*/ */
import bindings = require('bindings');
import * as Bluebird from 'bluebird'; import * as Bluebird from 'bluebird';
import * as childProcess from 'child_process'; import * as childProcess from 'child_process';
import { promises as fs } from 'fs'; import { promises as fs } from 'fs';
@ -109,7 +108,9 @@ async function elevateScriptWindows(
): Promise<{ cancelled: boolean }> { ): Promise<{ cancelled: boolean }> {
// 'elevator' imported here as it only exists on windows // 'elevator' imported here as it only exists on windows
// TODO: replace this with sudo-prompt once https://github.com/jorangreef/sudo-prompt/issues/96 is fixed // TODO: replace this with sudo-prompt once https://github.com/jorangreef/sudo-prompt/issues/96 is fixed
const elevateAsync = promisify(bindings('elevator').elevate); // @ts-ignore this is a native module
const { elevate } = await import('../../build/Release/elevator.node');
const elevateAsync = promisify(elevate);
// '&' needs to be escaped here (but not when written to a .cmd file) // '&' needs to be escaped here (but not when written to a .cmd file)
const cmd = ['cmd', '/c', escapeParamCmd(path).replace(/&/g, '^&')]; const cmd = ['cmd', '/c', escapeParamCmd(path).replace(/&/g, '^&')];

2104
npm-shrinkwrap.json generated

File diff suppressed because it is too large Load Diff

View File

@ -23,8 +23,8 @@
"webpack": "webpack", "webpack": "webpack",
"watch": "webpack --watch", "watch": "webpack --watch",
"concourse-build-electron": "make webpack", "concourse-build-electron": "make webpack",
"concourse-test": "npx npm@6.7.0 test", "concourse-test": "npx npm@6.14.5 test",
"concourse-test-electron": "npx npm@6.7.0 test" "concourse-test-electron": "npx npm@6.14.5 test"
}, },
"husky": { "husky": {
"hooks": { "hooks": {
@ -42,49 +42,17 @@
"fsevents", "fsevents",
"winusb-driver-generator" "winusb-driver-generator"
], ],
"dependencies": { "devDependencies": {
"@balena/lint": "^5.0.4",
"@fortawesome/fontawesome-free-webfonts": "^1.0.9", "@fortawesome/fontawesome-free-webfonts": "^1.0.9",
"@fortawesome/fontawesome-svg-core": "^1.2.25", "@fortawesome/fontawesome-svg-core": "^1.2.25",
"@fortawesome/free-brands-svg-icons": "^5.11.2", "@fortawesome/free-brands-svg-icons": "^5.11.2",
"@fortawesome/free-solid-svg-icons": "^5.11.2", "@fortawesome/free-solid-svg-icons": "^5.11.2",
"@fortawesome/react-fontawesome": "^0.1.7", "@fortawesome/react-fontawesome": "^0.1.7",
"bindings": "^1.3.0",
"bluebird": "^3.7.2",
"bootstrap-sass": "^3.3.6",
"d3": "^4.13.0",
"debug": "^4.1.1",
"electron-updater": "^4.3.1",
"etcher-sdk": "^4.1.3",
"flexboxgrid": "^6.3.0",
"immutable": "^3.8.1",
"inactivity-timer": "^1.0.0",
"lodash": "^4.17.10",
"mime-types": "^2.1.18",
"nan": "^2.14.0",
"node-ipc": "^9.1.1",
"path-is-inside": "^1.0.2",
"pretty-bytes": "^5.3.0",
"react": "^16.8.5",
"react-dom": "^16.8.5",
"redux": "^4.0.5",
"rendition": "^14.11.5",
"request": "^2.81.0",
"resin-corvus": "^2.0.5",
"roboto-fontface": "^0.10.0",
"semver": "^7.3.2",
"styled-components": "^5.1.0",
"styled-system": "^5.1.5",
"sudo-prompt": "^9.0.0",
"sys-class-rgb-led": "^2.1.0",
"tmp": "^0.2.1",
"uuid": "^8.0.0"
},
"devDependencies": {
"@balena/lint": "^5.0.4",
"@types/bindings": "^1.3.0",
"@types/bluebird": "^3.5.30", "@types/bluebird": "^3.5.30",
"@types/chai": "^4.2.7", "@types/chai": "^4.2.7",
"@types/mime-types": "^2.1.0", "@types/mime-types": "^2.1.0",
"@types/mini-css-extract-plugin": "^0.9.1",
"@types/mocha": "^7.0.2", "@types/mocha": "^7.0.2",
"@types/node": "^12.12.39", "@types/node": "^12.12.39",
"@types/node-ipc": "^9.1.2", "@types/node-ipc": "^9.1.2",
@ -94,26 +62,61 @@
"@types/sinon": "^9.0.0", "@types/sinon": "^9.0.0",
"@types/tmp": "^0.2.0", "@types/tmp": "^0.2.0",
"@types/webpack-node-externals": "^1.7.0", "@types/webpack-node-externals": "^1.7.0",
"bluebird": "^3.7.2",
"bootstrap-sass": "^3.3.6",
"chai": "^4.2.0", "chai": "^4.2.0",
"copy-webpack-plugin": "^6.0.1",
"css-loader": "^3.5.3",
"d3": "^4.13.0",
"debug": "^4.1.1",
"electron": "7.1.14", "electron": "7.1.14",
"electron-builder": "^22.1.0", "electron-builder": "^22.1.0",
"electron-mocha": "^8.2.0", "electron-mocha": "^8.2.0",
"electron-notarize": "^0.3.0", "electron-notarize": "^0.3.0",
"electron-updater": "^4.3.1",
"etcher-sdk": "^4.1.3",
"file-loader": "^6.0.0",
"flexboxgrid": "^6.3.0",
"husky": "^4.2.5", "husky": "^4.2.5",
"immutable": "^3.8.1",
"inactivity-timer": "^1.0.0",
"lint-staged": "^10.2.2", "lint-staged": "^10.2.2",
"lodash": "^4.17.10",
"mime-types": "^2.1.18",
"mini-css-extract-plugin": "^0.9.0",
"mocha": "^7.0.1", "mocha": "^7.0.1",
"nan": "^2.14.0",
"native-addon-loader": "^2.0.1",
"node-gyp": "^6.1.0", "node-gyp": "^6.1.0",
"node-sass": "^4.12.0", "node-ipc": "^9.1.1",
"omit-deep-lodash": "1.1.4", "omit-deep-lodash": "1.1.4",
"path-is-inside": "^1.0.2",
"pretty-bytes": "^5.3.0",
"react": "^16.8.5",
"react-dom": "^16.8.5",
"redux": "^4.0.5",
"rendition": "^14.11.5",
"request": "^2.81.0",
"resin-corvus": "^2.0.5",
"roboto-fontface": "^0.10.0",
"sass": "^1.26.5",
"sass-lint": "^1.12.1", "sass-lint": "^1.12.1",
"sass-loader": "^8.0.2",
"semver": "^7.3.2",
"simple-progress-webpack-plugin": "^1.1.2", "simple-progress-webpack-plugin": "^1.1.2",
"sinon": "^9.0.2", "sinon": "^9.0.2",
"spectron": "^9.0.0", "spectron": "^9.0.0",
"string-replace-loader": "^2.3.0",
"styled-components": "^5.1.0",
"styled-system": "^5.1.5",
"sudo-prompt": "^9.0.0",
"sys-class-rgb-led": "^2.1.0",
"tmp": "^0.2.1",
"ts-loader": "^7.0.4", "ts-loader": "^7.0.4",
"ts-node": "^8.3.0", "ts-node": "^8.3.0",
"typescript": "^3.5.3", "typescript": "^3.5.3",
"uuid": "^8.0.0",
"webpack": "^4.40.2", "webpack": "^4.40.2",
"webpack-cli": "^3.3.9", "webpack-cli": "^3.3.9"
"webpack-node-externals": "^1.7.2"
} }
} }

View File

@ -1,31 +0,0 @@
#!/bin/bash
###
# Copyright 2017 balena.io
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
###
set -e
set -u
make sass
# From http://stackoverflow.com/a/9393642/1641422
if [[ -n $(git status -s | grep "\\.css$" || true) ]]; then
echo "There are unstaged sass changes. Please commit the result of:" 1>&2
echo ""
echo " make sass" 1>&2
echo ""
exit 1
fi

View File

@ -14,11 +14,16 @@
* limitations under the License. * limitations under the License.
*/ */
// @ts-ignore @types for copy-webpack-plugin@6.0.1 not released yet
import * as CopyPlugin from 'copy-webpack-plugin';
import { readdirSync } from 'fs';
import * as _ from 'lodash'; import * as _ from 'lodash';
import * as MiniCssExtractPlugin from 'mini-css-extract-plugin';
import * as os from 'os';
import outdent from 'outdent';
import * as path from 'path'; import * as path from 'path';
import * as SimpleProgressWebpackPlugin from 'simple-progress-webpack-plugin'; import * as SimpleProgressWebpackPlugin from 'simple-progress-webpack-plugin';
import { BannerPlugin } from 'webpack'; import { BannerPlugin } from 'webpack';
import * as nodeExternals from 'webpack-node-externals';
/** /**
* Don't webpack package.json as mixpanel & sentry tokens * Don't webpack package.json as mixpanel & sentry tokens
@ -37,6 +42,63 @@ function externalPackageJson(packageJsonPath: string) {
}; };
} }
function platformSpecificModule(
platform: string,
module: string,
replacement = '{}',
) {
// Resolves module on platform, otherwise resolves the replacement
return (
_context: string,
request: string,
callback: (error?: Error, result?: string, type?: string) => void,
) => {
if (request === module && os.platform() !== platform) {
callback(undefined, replacement);
return;
}
callback();
};
}
function renameNodeModules(resourcePath: string) {
// electron-builder excludes the node_modules folder even if you specifically include it
// Work around by renaming it to "modules"
// See https://github.com/electron-userland/electron-builder/issues/4545
return (
path
.relative(__dirname, resourcePath)
.replace('node_modules', 'modules')
// file-loader expects posix paths, even on Windows
.replace(/\\/g, '/')
);
}
function findLzmaNativeBindingsFolder(): string {
const files = readdirSync(path.join('node_modules', 'lzma-native'));
const bindingsFolder = files.find((f) => f.startsWith('binding-'));
if (bindingsFolder === undefined) {
throw new Error('Could not find lzma_native binding');
}
return bindingsFolder;
}
const LZMA_BINDINGS_FOLDER = findLzmaNativeBindingsFolder();
interface ReplacementRule {
search: string;
replace: string | (() => string);
}
function replace(test: RegExp, ...replacements: ReplacementRule[]) {
return {
loader: 'string-replace-loader',
// Handle windows path separators
test: new RegExp(test.source.replace(/\\\//g, '(\\/|\\\\)')),
options: { multiple: replacements.map((r) => ({ ...r, strict: true })) },
};
}
const commonConfig = { const commonConfig = {
mode: 'production', mode: 'production',
optimization: { optimization: {
@ -48,10 +110,86 @@ const commonConfig = {
test: /\.tsx?$/, test: /\.tsx?$/,
use: 'ts-loader', use: 'ts-loader',
}, },
// remove bindings magic from drivelist
replace(
/node_modules\/drivelist\/js\/index\.js$/,
{
search: 'require("bindings");',
replace: "require('../build/Release/drivelist.node')",
},
{
search: "bindings('drivelist')",
replace: 'bindings',
},
),
// remove node-pre-gyp magic from lzma-native
replace(/node_modules\/lzma-native\/index\.js$/, {
search: 'require(binding_path)',
replace: () => {
return `require('./${path.posix.join(
LZMA_BINDINGS_FOLDER,
'lzma_native.node',
)}')`;
},
}),
// remove node-pre-gyp magic from usb
replace(/node_modules\/@balena.io\/usb\/usb\.js$/, {
search: 'require(binding_path)',
replace: "require('./build/Release/usb_bindings.node')",
}),
// remove bindings magic from ext2fs
replace(/node_modules\/ext2fs\/lib\/(ext2fs|binding)\.js$/, {
search: "require('bindings')('bindings')",
replace: "require('../build/Release/bindings.node')",
}),
// remove bindings magic from mountutils
replace(/node_modules\/mountutils\/index\.js$/, {
search: outdent`
require('bindings')({
bindings: 'MountUtils',
/* eslint-disable camelcase */
module_root: __dirname
/* eslint-enable camelcase */
})
`,
replace: "require('./build/Release/MountUtils.node')",
}),
// remove bindings magic from winusb-driver-generator
replace(/node_modules\/winusb-driver-generator\/index\.js$/, {
search: outdent`
require('bindings')({
bindings: 'Generator',
/* eslint-disable camelcase */
module_root: __dirname
/* eslint-enable camelcase */
});
`,
replace: "require('./build/Release/Generator.node')",
}),
// Use the copy of blobs in the generated folder and rename node_modules -> modules
// See the renameNodeModules function above
replace(/node_modules\/node-raspberrypi-usbboot\/build\/index\.js$/, {
search:
"return yield readFile(Path.join(__dirname, '..', 'blobs', filename));",
replace: outdent`
const { app, remote } = require('electron');
return yield readFile(Path.join((app || remote.app).getAppPath(), 'generated', __dirname.replace('node_modules', 'modules'), '..', 'blobs', filename));
`,
}),
// Copy native modules to generated folder
{
test: /\.node$/,
use: [
{
loader: 'native-addon-loader',
options: { name: renameNodeModules },
},
],
},
], ],
}, },
resolve: { resolve: {
extensions: ['.json', '.ts', '.tsx'], extensions: ['.node', '.js', '.json', '.ts', '.tsx'],
}, },
plugins: [ plugins: [
new SimpleProgressWebpackPlugin({ new SimpleProgressWebpackPlugin({
@ -62,8 +200,35 @@ const commonConfig = {
path: path.join(__dirname, 'generated'), path: path.join(__dirname, 'generated'),
filename: '[name].js', filename: '[name].js',
}, },
externals: [
// '../package.json' because we are in 'generated'
externalPackageJson('../package.json'),
// Only exists on windows
platformSpecificModule('win32', 'winusb-driver-generator'),
// Not needed but required by resin-corvus > os-locale > execa > cross-spawn
platformSpecificModule('none', 'spawn-sync'),
// Not needed as we replace all requires for it
platformSpecificModule('none', 'node-pre-gyp', '{ find: () => {} }'),
// Not needed as we replace all requires for it
platformSpecificModule('none', 'bindings'),
],
}; };
const guiConfigCopyPatterns = [
{
from: 'node_modules/node-raspberrypi-usbboot/blobs',
to: 'modules/node-raspberrypi-usbboot/blobs',
},
];
if (os.platform() === 'win32') {
// liblzma.dll is required on Windows for lzma-native
guiConfigCopyPatterns.push({
from: `node_modules/lzma-native/${LZMA_BINDINGS_FOLDER}/liblzma.dll`,
to: `modules/lzma-native/${LZMA_BINDINGS_FOLDER}/liblzma.dll`,
});
}
const guiConfig = { const guiConfig = {
...commonConfig, ...commonConfig,
target: 'electron-renderer', target: 'electron-renderer',
@ -71,16 +236,9 @@ const guiConfig = {
__dirname: true, __dirname: true,
__filename: true, __filename: true,
}, },
externals: [
nodeExternals(),
// '../../../package.json' because we are in 'lib/gui/app/index.html'
externalPackageJson('../../../package.json'),
],
entry: { entry: {
gui: path.join(__dirname, 'lib', 'gui', 'app', 'app.ts'), gui: path.join(__dirname, 'lib', 'gui', 'app', 'app.ts'),
}, },
devtool: 'source-map',
plugins: [ plugins: [
...commonConfig.plugins, ...commonConfig.plugins,
// Remove "Download the React DevTools for a better development experience" message // Remove "Download the React DevTools for a better development experience" message
@ -88,6 +246,7 @@ const guiConfig = {
banner: '__REACT_DEVTOOLS_GLOBAL_HOOK__ = { isDisabled: true };', banner: '__REACT_DEVTOOLS_GLOBAL_HOOK__ = { isDisabled: true };',
raw: true, raw: true,
}), }),
new CopyPlugin({ patterns: guiConfigCopyPatterns }),
], ],
}; };
@ -98,15 +257,62 @@ const etcherConfig = {
__dirname: false, __dirname: false,
__filename: true, __filename: true,
}, },
externals: [
nodeExternals(),
// '../package.json' because we are in 'generated/etcher.js'
externalPackageJson('../package.json'),
],
entry: { entry: {
etcher: path.join(__dirname, 'lib', 'start.ts'), etcher: path.join(__dirname, 'lib', 'start.ts'),
}, },
}; };
module.exports = [guiConfig, etcherConfig]; const cssConfig = {
mode: 'production',
optimization: {
minimize: false,
},
module: {
rules: [
{
test: /\.css$/i,
use: 'css-loader',
},
{
test: /\.s[ac]ss$/i,
use: [
MiniCssExtractPlugin.loader,
'css-loader',
{
loader: 'sass-loader',
options: {
sassOptions: {
fiber: false,
},
},
},
],
},
{
test: /\.(woff|woff2|eot|ttf|otf|svg)$/,
loader: 'file-loader',
options: { name: renameNodeModules },
},
],
},
plugins: [
new MiniCssExtractPlugin({ filename: '[name].css' }),
new CopyPlugin({
patterns: [
{ from: 'lib/gui/app/index.html', to: 'index.html' },
// electron-builder doesn't bundle folders named "assets"
// See https://github.com/electron-userland/electron-builder/issues/4545
{ from: 'lib/gui/assets', to: 'media' },
{ from: 'assets/icon.png', to: 'media/icon.png' },
],
}),
],
entry: {
index: path.join(__dirname, 'lib', 'gui', 'app', 'scss', 'main.scss'),
},
output: {
path: path.join(__dirname, 'generated'),
},
};
module.exports = [cssConfig, guiConfig, etcherConfig];