12 Commits

Author SHA1 Message Date
Josh Creek 75425e954e chore(#5): Update package version 2022-10-08 16:37:32 +01:00
Josh Creek b5e578acdc Merge pull request #10 from jcreek/5-add-nicer-styling-overall
5 add nicer styling overall
2022-10-08 16:27:07 +01:00
Josh Creek f5c404f58b feat(#5): Change privacy policy title 2022-10-08 16:26:01 +01:00
Josh Creek c7f6d43bdc feat(#7): Add start over button 2022-10-08 16:21:30 +01:00
Josh Creek 29b4822732 feat(#5): Add Google font caching 2022-10-08 16:21:00 +01:00
Josh Creek 797d04c403 feat(*): Add privacy policy 2022-10-08 16:20:46 +01:00
Josh Creek eb449b0e62 feat(#5): Improve styling and responsiveness 2022-10-08 16:20:26 +01:00
Josh Creek 43edb4062d fix(#6): Ensure install button is hidden when PWA cannot be installed 2022-10-08 00:55:16 +01:00
Josh Creek 0dd3cb56e4 fix(#6): Fix prod webpack merge 2022-10-07 23:47:24 +00:00
Josh Creek 90240af8d0 Merge pull request #9 from jcreek/6-add-service-worker-so-it-can-be-used-offline-and-installed-as-a-pwa
6 add service worker so it can be used offline and installed as a pwa
2022-10-08 00:45:52 +01:00
Josh Creek e524702312 chore(#6): Update version 2022-10-08 00:43:42 +01:00
Josh Creek 4ba77a358a feat(#6): Add PWA functionality and service worker 2022-10-08 00:41:44 +01:00
12 changed files with 5247 additions and 771 deletions
+4700 -718
View File
File diff suppressed because it is too large Load Diff
+15 -7
View File
@@ -1,11 +1,11 @@
{ {
"name": "SelectionWheel", "name": "SelectionWheel",
"version": "1.0.0", "version": "1.2.0",
"description": "A fun and simple selection wheel.", "description": "A fun and simple selection wheel.",
"main": "index.js", "main": "index.js",
"scripts": { "scripts": {
"build": "webpack", "build": "webpack --config webpack.prod.js",
"dev": "webpack serve", "dev": "webpack serve --hot --config webpack.dev.js",
"lint": "eslint --ignore-path .eslintignore --ext .js,.ts ." "lint": "eslint --ignore-path .eslintignore --ext .js,.ts ."
}, },
"repository": { "repository": {
@@ -27,8 +27,6 @@
"eslint-config-airbnb-base": "^15.0.0", "eslint-config-airbnb-base": "^15.0.0",
"eslint-plugin-import": "^2.26.0", "eslint-plugin-import": "^2.26.0",
"file-loader": "^6.2.0", "file-loader": "^6.2.0",
"html-webpack-plugin": "^5.5.0",
"react-dev-utils": "^12.0.1",
"sass": "^1.52.3", "sass": "^1.52.3",
"sass-loader": "^13.0.0", "sass-loader": "^13.0.0",
"style-loader": "^3.3.1", "style-loader": "^3.3.1",
@@ -36,10 +34,20 @@
"webpack": "^5.73.0", "webpack": "^5.73.0",
"webpack-cli": "^4.10.0", "webpack-cli": "^4.10.0",
"webpack-dev-server": "^4.9.2", "webpack-dev-server": "^4.9.2",
"webpack-webmanifest-loader": "^2.0.2" "webpack-webmanifest-loader": "^2.0.2",
"workbox-cli": "^6.5.4"
}, },
"dependencies": { "dependencies": {
"@types/d3": "^7.4.0", "@types/d3": "^7.4.0",
"d3": "^7.6.1" "d3": "^7.6.1",
"html-webpack-plugin": "^5.5.0",
"react-dev-utils": "^12.0.1",
"webpack-manifest-plugin": "^5.0.0",
"webpack-merge": "^5.8.0",
"workbox-navigation-preload": "^6.5.4",
"workbox-precaching": "^6.5.4",
"workbox-routing": "^6.5.4",
"workbox-strategies": "^6.5.4",
"workbox-webpack-plugin": "^6.5.4"
} }
} }
+2 -2
View File
@@ -4,12 +4,12 @@
"description": "A fun and simple selection wheel.", "description": "A fun and simple selection wheel.",
"icons": [ "icons": [
{ {
"src": "./android-chrome-192x192.png", "src": "android-chrome-192x192.png",
"sizes": "192x192", "sizes": "192x192",
"type": "image/png" "type": "image/png"
}, },
{ {
"src": "./android-chrome-512x512.png", "src": "android-chrome-512x512.png",
"sizes": "512x512", "sizes": "512x512",
"type": "image/png" "type": "image/png"
} }
+27 -4
View File
@@ -1,8 +1,10 @@
import * as d3 from 'd3'; import * as d3 from 'd3';
import loadServiceWorker from './loadServiceWorker';
require('./assets/favicon.ico'); require('./assets/favicon.ico');
require('./assets/android-chrome-192x192.png'); require('./assets/android-chrome-192x192.png');
require('./assets/android-chrome-512x512.png'); require('./assets/android-chrome-512x512.png');
require('./styles/main.scss');
const padding = { const padding = {
top: 20, top: 20,
@@ -10,8 +12,11 @@ const padding = {
bottom: 0, bottom: 0,
left: 0, left: 0,
}; };
const w = 500 - padding.left - padding.right;
const h = 500 - padding.top - padding.bottom; const baseSpinnerSizePx = 800;
const w = baseSpinnerSizePx - padding.left - padding.right;
const h = baseSpinnerSizePx - padding.top - padding.bottom;
const r = Math.min(w, h) / 2; const r = Math.min(w, h) / 2;
let rotation = 0; let rotation = 0;
let oldrotation = 0; let oldrotation = 0;
@@ -75,8 +80,8 @@ function drawWheel() {
.select('#chart') .select('#chart')
.append('svg') .append('svg')
.data([data]) .data([data])
.attr('width', w + padding.left + padding.right) .attr('preserveAspectRatio', 'xMinYMin meet')
.attr('height', h + padding.top + padding.bottom); .attr('viewBox', `0 0 ${w + padding.left + padding.right} ${h + padding.top + padding.bottom}`);
const container = svg const container = svg
.append('g') .append('g')
.attr('class', 'chartholder') .attr('class', 'chartholder')
@@ -170,6 +175,10 @@ function drawWheel() {
makeArrowAndCircle(svg, container); makeArrowAndCircle(svg, container);
} }
function startOver() {
stopSpinning();
}
function startSpinning() { function startSpinning() {
const textArea = <HTMLInputElement>document.getElementById('input-lines'); const textArea = <HTMLInputElement>document.getElementById('input-lines');
const inputData = textArea.value.trim() const inputData = textArea.value.trim()
@@ -191,9 +200,19 @@ function startSpinning() {
// Make the elements // Make the elements
const chartElement = document.createElement('div'); const chartElement = document.createElement('div');
chartElement.setAttribute('id', 'chart'); chartElement.setAttribute('id', 'chart');
chartElement.setAttribute('class', 'spinner-items');
document.getElementById('spinner-container').appendChild(chartElement);
const startOverElement = document.createElement('button');
startOverElement.setAttribute('id', 'start-over');
startOverElement.setAttribute('class', 'button button-blue');
startOverElement.setAttribute('role', 'button');
startOverElement.onclick = startOver;
startOverElement.textContent = 'Start over';
chartElement.appendChild(startOverElement);
document.getElementById('spinner-container').appendChild(chartElement); document.getElementById('spinner-container').appendChild(chartElement);
const questionElement = document.createElement('div'); const questionElement = document.createElement('div');
questionElement.setAttribute('id', 'question'); questionElement.setAttribute('id', 'question');
questionElement.setAttribute('class', 'spinner-items');
const h1Element = document.createElement('h1'); const h1Element = document.createElement('h1');
questionElement.appendChild(h1Element); questionElement.appendChild(h1Element);
document.getElementById('spinner-container').appendChild(questionElement); document.getElementById('spinner-container').appendChild(questionElement);
@@ -208,3 +227,7 @@ function startSpinning() {
const button = document.getElementById('startSpinning'); const button = document.getElementById('startSpinning');
button.onclick = startSpinning; button.onclick = startSpinning;
if (process.env.NODE_ENV === 'production') {
loadServiceWorker();
}
+28
View File
@@ -0,0 +1,28 @@
export default function loadServiceWorker() {
let deferredPrompt;
window.addEventListener('beforeinstallprompt', (e) => {
const installApp = document.getElementById('install-app-btn-container');
installApp.addEventListener('click', async () => {
if (deferredPrompt !== null) {
deferredPrompt.prompt();
const { outcome } = await deferredPrompt.userChoice;
if (outcome === 'accepted') {
deferredPrompt = null;
}
}
});
document.getElementById('install-app-btn-container').style.display = 'block';
deferredPrompt = e;
});
window.addEventListener('load', () => {
navigator.serviceWorker.register('/service-worker.js');
});
if (window.matchMedia('(display-mode: standalone)').matches) {
document.getElementById('install-app-btn-container').style.display = 'none';
}
}
+115
View File
@@ -0,0 +1,115 @@
<!DOCTYPE html>
<html lang="en">
<head>
<!-- Google tag (gtag.js) -->
<script async src="https://www.googletagmanager.com/gtag/js?id=G-EZ2WT7NQMK"></script>
<script>
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());
gtag('config', 'G-EZ2WT7NQMK');
</script>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="description" content="A fun and simple selection wheel - input names and get click to select them at random.">
<title><%= htmlWebpackPlugin.options.title %></title>
<link rel="manifest" href="<%= require('../src/assets/manifest.webmanifest') %>" />
<link rel="shortcut icon" href="favicon.ico">
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Fuzzy+Bubbles:wght@400;700&display=swap" rel="stylesheet">
</head>
<body>
<header class="topbar-container">
<div class="topbar-items"><h1><a href="/">Selection Wheel</a></h1></div>
<p id="install-app-btn-container" class="topbar-items">
<button id="install-offline-app" class="button button-black" role="button">
<span>Install the app (you can even use it offline!)</span>
</button>
</p>
</header>
<div id="privacy-container">
<h1>Privacy Policy</h1>
<p>This Website stores none of your information remotely. The only information stored locally is a saved list of your input names, on your own device.</p>
<h2>What data do we collect?</h2>
<p>None.</p>
<h2>How do we collect your data?</h2>
<p>I don't.</p>
<h2>How will we use your data?</h2>
<p>I won't.</p>
<h2>How do we store your data?</h2>
<p>I don't.</p>
<h2>Marketing</h2>
<p>I don't market anything to you.</p>
<h2>What are your data protection rights?</h2>
<p>I would like to make sure you are fully aware of all of your data protection rights. Every user is entitled to
the following:</p>
<ul>
<li>
<p>The right to access - You have the right to request copies of your personal data. We may
charge you a small fee for this service.</p>
</li>
<li>
<p>The right to rectification - You have the right to request that we correct any information you
believe is inaccurate. You also have the right to request we complete the information you
believe is incomplete.</p>
</li>
<li>
<p>The right to erasure - You have the right to request that we erase your personal data, under
certain conditions.</p>
</li>
<li>
<p>The right to restrict processing - You have the right to request that we restrict the processing
of your personal data, under certain conditions.</p>
</li>
<li>
<p>The right to object to processing - You have the right to object to our processing of your
personal data, under certain conditions.</p>
</li>
<li>
<p>The right to data portability - You have the right to request that we transfer the data that we
have collected to another organization, or directly to you, under certain conditions.</p>
</li>
</ul>
<p>If you make a request, I have one month to respond to you. If you would like to exercise any of these rights,
please contact me via <a href="https://www.twitter.com/jcreek23">Twitter</a>.</p>
<h2 >What are cookies?</h2>
<p>Cookies are text files placed on your computer to collect standard Internet log information and visitor behavior
information. When you visit our websites, we may collect information from you automatically through cookies or
similar technology</p>
<p>For further information, visit <a href="https://allaboutcookies.org">allaboutcookies.org</a>.</p>
<h2 >How do we use cookies?</h2>
<p>I don't.</p>
<h2>What types of cookies do we use?</h2>
<p>None.</p>
<h2>How to manage your cookies</h2>
<p>You can set your browser not to accept cookies, and the above website tells you how to remove cookies from your
browser. However, in a few cases, some of our website features may not function as a result.</p>
<h2></h2>Privacy policies of other websites</h2>
<p>This website contains links to other websites. My privacy policy applies only to this website, so if you click on
a link to another website, you should read their privacy policy.</p>
<h2></h2>Changes to our privacy policy</h2>
<p>I keep my privacy policy under regular review and places any updates on this web page. This privacy policy was
last updated on 8th October 2022.</p>
<h2>How to contact us</h2>
<p>If you have any questions about this privacy policy, the data I hold on you, or you would like to exercise one of
your data protection rights, please do not hesitate to contact me via <a href="https://www.twitter.com/jcreek23">Twitter</a>.</p>
<h2>How to contact the appropriate authorities</h2>
<p>Should you wish to report a complaint or if you feel that I have not addressed your concern in a satisfactory
manner, you may contact the Information Commissioner's Office.</p>
</div>
<footer class="footer-container">
<hr>
<span class="footer-items">Copyright &copy; 2022 Josh Creek</span>
<span> | </span>
<span class="footer-items"><a href="/privacy-policy.html">Privacy Policy</a></span>
</footer>
</body>
</html>
+29
View File
@@ -0,0 +1,29 @@
import { precacheAndRoute } from 'workbox-precaching';
import * as navigationPreload from 'workbox-navigation-preload';
import { NetworkFirst, CacheFirst, StaleWhileRevalidate } from 'workbox-strategies';
import { registerRoute, NavigationRoute, Route } from 'workbox-routing';
// Precache the manifest
// eslint-disable-next-line no-underscore-dangle, no-restricted-globals
precacheAndRoute(self.__WB_MANIFEST);
// Enable navigation preload - this should reduce navigation latency
navigationPreload.enable();
const navigationRoute = new NavigationRoute(new NetworkFirst({
cacheName: 'navigations',
}));
const imageAssetRoute = new Route(({ request }) => request.destination === 'image', new CacheFirst({
cacheName: 'image-assets',
}));
registerRoute(navigationRoute);
registerRoute(imageAssetRoute);
registerRoute(
({ url }) => url.origin === 'https://fonts.googleapis.com',
new StaleWhileRevalidate({
cacheName: 'google-fonts-stylesheets',
}),
);
+250 -19
View File
@@ -1,30 +1,261 @@
body {
margin: 0;
font-family: Arial, sans-serif;
}
.topbar-container {
display: flex;
flex-direction: row;
flex-wrap: nowrap;
justify-content: space-around;
align-items: flex-start;
align-content: flex-start;
background-color: #ff8800;
margin-bottom: 25px;
gap: 5px;
}
.topbar-container h1 {
color: white;
font-family: 'Fuzzy Bubbles', cursive;
font-weight: 700;
margin-block-start: 0.2em;
margin-block-end: 0.2em;
a {
color: white;
}
a:link {
text-decoration: none;
}
}
.topbar-container p {
margin-block-start: 0.2em;
margin-block-end: 0.2em;
}
.topbar-items:nth-child(1) {
display: block;
flex-grow: 0;
flex-shrink: 1;
flex-basis: auto;
align-self: flex-start;
order: 0;
}
.topbar-items:nth-child(2) {
display: block;
flex-grow: 0;
flex-shrink: 1;
flex-basis: 200px;
align-self: center;
order: 0;
}
#input-container {
display: flex;
flex-direction: row;
flex-wrap: wrap;
justify-content: center;
align-items: flex-start;
align-content: center;
gap: 25px;
}
.input-items:nth-child(1) {
display: block;
flex-grow: 0;
flex-shrink: 1;
flex-basis: 500px;
align-self: auto;
order: 0;
}
.input-items:nth-child(2) {
display: block;
flex-grow: 0;
flex-shrink: 1;
flex-basis: auto;
align-self: center;
order: 0;
}
#input-lines {
max-width: 100%;
}
#start-over {
position: absolute;
top: 100px;
left: auto;
}
#spinner-container {
display: flex;
flex-direction: row;
flex-wrap: wrap;
justify-content: center;
align-items: flex-start;
align-content: center;
}
.spinner-items:nth-child(1) {
display: block;
flex-grow: 0;
flex-shrink: 1;
flex-basis: 800px;
align-self: auto;
order: 0;
}
.spinner-items:nth-child(2) {
display: block;
flex-grow: 0;
flex-shrink: 1;
flex-basis: auto;
align-self: auto;
order: 0;
width: 100%;
}
text{ text{
font-family:Helvetica, Arial, sans-serif; font-family: Arial, sans-serif;
font-size:11px; font-size:35px;
pointer-events:none; pointer-events:none;
} }
#chart{ #chart{
position:absolute; max-width:800px;
width:500px; margin-left: auto;
height:500px; margin-right: auto;
top:0;
left:0;
}
#question{
position: absolute;
width:400px;
height:500px;
top:0;
left:520px;
} }
#question h1{ #question h1{
font-size: 50px; font-size: 50px;
font-weight: bold; font-weight: bold;
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; font-family: Arial, sans-serif;
position: absolute;
padding: 0;
margin: 0;
top:50%;
-webkit-transform:translate(0,-50%); -webkit-transform:translate(0,-50%);
transform:translate(0,-50%); transform:translate(0,-50%);
text-align: center;
}
#install-app-btn-container {
display: none;
}
.footer-container {
position: fixed;
left: 0;
bottom: 0;
width: 100%;
text-align: center;
color: #423E37;
padding-bottom: 10px;
background-color: white;
}
.footer-container hr {
max-width: 500px;
border-top: 0.5px solid #423E37;
margin-left: auto;
margin-right: auto;
}
.footer-container a, #privacy-container a {
color: #423E37;
}
.footer-container a:hover, #privacy-container a:hover {
color: #81796c;
}
#privacy-container {
margin-left: auto;
margin-right: auto;
max-width: 800px;
margin-bottom: 50px;
}
/* Buttons */
.button {
appearance: none;
border: 1px solid rgba(27, 31, 35, .15);
border-radius: 6px;
box-shadow: rgba(27, 31, 35, .1) 0 1px 0;
box-sizing: border-box;
color: #fff;
cursor: pointer;
display: inline-block;
font-family: -apple-system,system-ui,"Segoe UI",Helvetica,Arial,sans-serif,"Apple Color Emoji","Segoe UI Emoji";
font-size: 14px;
font-weight: 600;
line-height: 20px;
padding: 6px 16px;
position: relative;
text-align: center;
text-decoration: none;
user-select: none;
-webkit-user-select: none;
touch-action: manipulation;
vertical-align: middle;
white-space: nowrap;
}
.button:focus:not(:focus-visible):not(.focus-visible) {
box-shadow: none;
outline: none;
}
.button:disabled {
border-color: rgba(27, 31, 35, .1);
color: rgba(255, 255, 255, .8);
cursor: default;
}
.button:focus {
outline: none;
}
.button-green {
background-color: #2ea44f;
}
.button-green:hover {
background-color: #2c974b;
}
.button-green:focus {
box-shadow: rgba(46, 164, 79, .4) 0 0 0 3px;
outline: none;
}
.button-green:disabled {
background-color: #94d3a2;
}
.button-green:active {
background-color: #298e46;
box-shadow: rgba(20, 70, 32, .2) 0 1px 0 inset;
}
.button-black {
background-color: #423E37;
}
.button-black:hover {
background-color: #2e2b26;
}
.button-blue {
background-color: #388697;
}
.button-blue:hover {
background-color: #275d69;
}
.button-white {
background-color: #FFFFFF;
color: black;
} }
+26 -2
View File
@@ -18,13 +18,37 @@
<title><%= htmlWebpackPlugin.options.title %></title> <title><%= htmlWebpackPlugin.options.title %></title>
<link rel="manifest" href="<%= require('../src/assets/manifest.webmanifest') %>" /> <link rel="manifest" href="<%= require('../src/assets/manifest.webmanifest') %>" />
<link rel="shortcut icon" href="favicon.ico"> <link rel="shortcut icon" href="favicon.ico">
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Fuzzy+Bubbles:wght@400;700&display=swap" rel="stylesheet">
</head> </head>
<body> <body>
<header class="topbar-container">
<div class="topbar-items"><h1><a href="/">Selection Wheel</a></h1></div>
<p id="install-app-btn-container" class="topbar-items">
<button id="install-offline-app" class="button button-black" role="button">
<span>Install the app (you can even use it offline!)</span>
</button>
</p>
</header>
<div id="spinner-container"> <div id="spinner-container">
</div> </div>
<textarea id="input-lines" rows="15" cols="60" name="text" <div id="input-container">
<textarea id="input-lines" class="input-items" rows="15" cols="60" name="text"
placeholder="Enter names to be shuffled, one per line. Leave this area empty in order to use a default set of generated names."></textarea> placeholder="Enter names to be shuffled, one per line. Leave this area empty in order to use a default set of generated names."></textarea>
<button id="startSpinning">Start spinning</button> <button id="startSpinning" class="input-items button button-green" role="button">Start spinning</button>
</div>
<footer class="footer-container">
<hr>
<span class="footer-items">Copyright &copy; 2022 Josh Creek</span>
<span> | </span>
<span class="footer-items"><a href="/privacy-policy.html">Privacy Policy</a></span>
</footer>
</body> </body>
</html> </html>
+12 -18
View File
@@ -1,9 +1,12 @@
const path = require('path'); const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin'); const HtmlWebpackPlugin = require('html-webpack-plugin');
const InlineChunkHtmlPlugin = require('react-dev-utils/InlineChunkHtmlPlugin'); const InlineChunkHtmlPlugin = require('react-dev-utils/InlineChunkHtmlPlugin');
const { WebpackManifestPlugin } = require('webpack-manifest-plugin');
const options = {};
module.exports = { module.exports = {
mode: 'development', target: 'web',
entry: { entry: {
bundle: path.resolve(__dirname, 'src/index.ts'), bundle: path.resolve(__dirname, 'src/index.ts'),
}, },
@@ -12,23 +15,7 @@ module.exports = {
filename: '[name].[contenthash].js', filename: '[name].[contenthash].js',
clean: true, clean: true,
assetModuleFilename: '[name].[contenthash][ext]', assetModuleFilename: '[name].[contenthash][ext]',
publicPath: './', publicPath: '',
},
devtool: 'source-map',
devServer: {
static: {
directory: path.resolve(__dirname, 'dist'),
},
port: 3000,
open: true,
hot: true,
compress: true,
historyApiFallback: true,
devMiddleware: {
index: true,
mimeTypes: { phtml: 'text/html' },
writeToDisk: true,
},
}, },
watchOptions: { poll: true }, watchOptions: { poll: true },
module: { module: {
@@ -99,6 +86,13 @@ module.exports = {
filename: 'index.html', filename: 'index.html',
template: 'src/template.html', template: 'src/template.html',
}), }),
new HtmlWebpackPlugin({
inject: 'body',
title: 'Privacy Policy',
filename: 'privacy-policy.html',
template: 'src/privacy.html',
}),
new InlineChunkHtmlPlugin(HtmlWebpackPlugin, [/.*/]), new InlineChunkHtmlPlugin(HtmlWebpackPlugin, [/.*/]),
new WebpackManifestPlugin(options),
], ],
}; };
+23
View File
@@ -0,0 +1,23 @@
const path = require('path');
const { merge } = require('webpack-merge');
const common = require('./webpack.common');
module.exports = merge(common, {
mode: 'development',
devtool: 'inline-source-map',
devServer: {
static: {
directory: path.resolve(__dirname, 'dist'),
},
port: 3000,
open: true,
hot: true,
compress: true,
historyApiFallback: true,
devMiddleware: {
index: true,
mimeTypes: { phtml: 'text/html' },
writeToDisk: true,
},
},
});
+19
View File
@@ -0,0 +1,19 @@
const { merge } = require('webpack-merge');
const { InjectManifest } = require('workbox-webpack-plugin');
const common = require('./webpack.common');
common.plugins.push(new InjectManifest({
swSrc: './src/serviceWorker.js',
swDest: 'service-worker.js',
maximumFileSizeToCacheInBytes: 50000000,
}));
module.exports = merge(common, {
mode: 'production',
devtool: 'source-map',
performance: {
hints: false,
maxEntrypointSize: 512000,
maxAssetSize: 512000,
},
});