webpack-plugin-serve
Version:
A Development Server in a Webpack Plugin
405 lines (346 loc) • 8.47 kB
JavaScript
/*
Copyright © 2018 Andrew Powell
This Source Code Form is subject to the terms of the Mozilla Public
License, v. 2.0. If a copy of the MPL was not distributed with this
file, You can obtain one at http://mozilla.org/MPL/2.0/.
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of this Source Code Form.
*/
const { addCss, addHtml, socketMessage } = require('./util');
const ns = 'wps-status';
const css = `
#${ns} {
background: #282d35;
border-radius: 0.6em;
display: flex;
flex-direction: column;
font-family: 'Open Sans', Helvetica, Arial, sans-serif;
font-size: 10px;
height: 90%;
min-height: 20em;
left: 50%;
opacity: 1;
overflow: hidden;
padding-bottom: 3em;
position: absolute;
top: 2rem;
transform: translateX(-50%);
transition: opacity .25s ease-in-out;
width: 95%;
z-index: 2147483645;
}
@keyframes ${ns}-hidden-display {
0% {
opacity: 1;
}
99% {
display: inline-flex;
opacity: 0;
}
100% {
display: none;
opacity: 0;
}
}
#${ns}.${ns}-hidden {
animation: ${ns}-hidden-display .3s;
animation-fill-mode:forwards;
display: none;
}
#${ns}.${ns}-min {
animation: minimize 10s;
bottom: 2em;
cursor: pointer;
height: 6em;
left: auto;
min-height: 6em;
padding-bottom: 0;
position: absolute;
right: 2em;
top: auto;
transform: none;
width: 6em;
}
#${ns}.${ns}-min #${ns}-beacon {
display: block;
}
#${ns}-title {
color: #fff;
font-size: 1.2em;
font-weight: normal;
margin: 0;
padding: 0.6em 0;
text-align: center;
width: 100%;
}
#${ns}.${ns}-min #${ns}-title {
display: none;
}
#${ns}-title-errors {
color: #ff5f58;
font-style: normal;
padding-left: 1em;
}
#${ns}-title-warnings {
color: #ffbd2e;
font-style: normal;
padding-left: 1em;
}
#${ns}-problems {
overflow-y: auto;
padding: 1em 2em;
}
#${ns}-problems pre {
color: #ddd;
background: #282d35;
display: block;
font-size: 1.3em;
font-family: 'Open Sans', Helvetica, Arial, sans-serif;
white-space: pre-wrap;
}
#${ns}-problems pre em {
background: #ff5f58;
border-radius: 0.3em;
color: #641e16;
font-style: normal;
line-height: 3em;
margin-right: 0.4em;
padding: 0.1em 0.4em;
text-transform: uppercase;
}
pre#${ns}-warnings em {
background: #ffbd2e;
color: #3e2723;
}
pre#${ns}-success {
display: none;
text-align: center;
}
pre#${ns}-success em {
background: #7fb900;
color: #004d40;
}
#${ns}-problems.${ns}-success #${ns}-success {
display: block;
}
#${ns}.${ns}-min #${ns}-problems {
display: none;
}
#${ns}-nav {
opacity: 0.5;
padding: 1.2em;
position: absolute;
}
#${ns}.${ns}-min #${ns}-nav {
display: none;
}
#${ns}-nav:hover {
opacity: 1;
}
#${ns}-nav div {
background: #ff5f58;
border-radius: 1.2em;
cursor: pointer;
display: inline-block;
height: 1.2em;
position: relative;
width: 1.2em;
}
div#${ns}-min {
background: #ffbd2e;
margin-left: 0.8em;
}
#${ns}-beacon {
border-radius: 3em;
display: none;
font-size: 10px;
height: 3em;
margin: 1.6em auto;
position: relative;
width: 3em;
}
#${ns}-beacon:before, #${ns}-beacon:after {
content: '';
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(127,185,0, 0.2);
border-radius: 3em;
opacity: 0;
}
#${ns}-beacon:before {
animation: ${ns}-pulse 3s infinite linear;
transform: scale(1);
}
#${ns}-beacon:after {
animation: ${ns}-pulse 3s 2s infinite linear;
}
@keyframes ${ns}-pulse {
0% {
opacity: 0;
transform: scale(0.6);
}
33% {
opacity: 1;
transform: scale(1);
}
100% {
opacity: 0;
transform: scale(1.4);
}
}
#${ns}-beacon mark {
background: rgba(127, 185, 0, 1);
border-radius: 100% 100%;
height: 1em;
left: 1em;
position: absolute;
top: 1em;
width: 1em;
}
#${ns}-beacon.${ns}-error mark {
background: #ff5f58;
}
#${ns}-beacon.${ns}-error:before, #${ns}-beacon.error:after {
background: rgba(255, 95, 88, 0.2);
}
#${ns}-beacon.${ns}-warning mark {
background: #ffbd2e;
}
#${ns}-beacon.${ns}-warning:before, #${ns}-beacon.warning:after {
background: rgba(255, 189, 46, 0.2);
}
/* Put google web font at the end, or you'll see FOUC in Firefox */
@import url('https://fonts.googleapis.com/css?family=Open+Sans:400,700');
`;
const html = `
<aside id="${ns}" class="${ns}-hidden" title="build status">
<figure id="${ns}-beacon">
<mark/>
</figure>
<nav id="${ns}-nav">
<div id="${ns}-close" title="close"></div>
<div id="${ns}-min" title="minmize"></div>
</nav>
<h1 id="${ns}-title">
build status
<em id="${ns}-title-errors"></em>
<em id="${ns}-title-warnings"></em>
</h1>
<article id="${ns}-problems">
<pre id="${ns}-success"><em>Build Successful</em></pre>
<pre id="${ns}-errors"></pre>
<pre id="${ns}-warnings"></pre>
</article>
</aside>
`;
const init = (options, socket) => {
const hidden = `${ns}-hidden`;
let hasProblems = false;
let aside;
let beacon;
let problems;
let preErrors;
let preWarnings;
let titleErrors;
let titleWarnings;
const reset = () => {
preErrors.innerHTML = '';
preWarnings.innerHTML = '';
problems.classList.remove(`${ns}-success`);
beacon.className = '';
titleErrors.innerText = '';
titleWarnings.innerText = '';
};
const addErrors = (errors) => {
if (errors.length) {
problems.classList.remove(`${ns}-success`);
beacon.classList.add(`${ns}-error`);
for (const error of errors) {
const markup = `<div><em>Error</em> in ${error}</div>`;
addHtml(markup, preErrors);
}
titleErrors.innerText = `${errors.length} Error(s)`;
} else {
titleErrors.innerText = '';
}
aside.classList.remove(hidden);
};
const addWarnings = (warnings) => {
if (warnings.length) {
problems.classList.remove(`${ns}-success`);
if (!beacon.classList.contains(`${ns}-error`)) {
beacon.classList.add(`${ns}-warning`);
}
for (const warning of warnings) {
const markup = `<div><em>Warning</em> in ${warning}</div>`;
addHtml(markup, preWarnings);
}
titleWarnings.innerText = `${warnings.length} Warning(s)`;
} else {
titleWarnings.innerText = '';
}
aside.classList.remove(hidden);
};
if (options.firstInstance) {
document.addEventListener('DOMContentLoaded', () => {
addCss(css);
[aside] = addHtml(html);
beacon = document.querySelector(`#${ns}-beacon`);
problems = document.querySelector(`#${ns}-problems`);
preErrors = document.querySelector(`#${ns}-errors`);
preWarnings = document.querySelector(`#${ns}-warnings`);
titleErrors = document.querySelector(`#${ns}-title-errors`);
titleWarnings = document.querySelector(`#${ns}-title-warnings`);
const close = document.querySelector(`#${ns}-close`);
const min = document.querySelector(`#${ns}-min`);
aside.addEventListener('click', () => {
aside.classList.remove(`${ns}-min`);
});
close.addEventListener('click', () => {
aside.classList.add(`${ns}-hidden`);
});
min.addEventListener('click', (e) => {
aside.classList.add(`${ns}-min`);
e.stopImmediatePropagation();
});
});
}
socketMessage(socket, (action, data) => {
if (!aside) {
return;
}
const { compilers } = window.webpackPluginServe;
switch (action) {
case 'build':
// clear errors and warnings when a new build begins
reset();
break;
case 'problems':
addErrors(data.errors);
addWarnings(data.warnings);
aside.classList.remove(hidden);
hasProblems = data.errors.length || data.warnings.length;
break;
case 'replace':
// if there's a compiler that isn't done yet, hold off and let it run the show
for (const compilerName of Object.keys(compilers)) {
if (!compilers[compilerName]) {
return;
}
}
if (hasProblems && !preErrors.children.length && !preWarnings.children.length) {
reset();
hasProblems = false;
problems.classList.add(`${ns}-success`);
aside.classList.remove(hidden);
setTimeout(() => aside.classList.add(hidden), 3e3);
}
break;
default:
}
});
};
module.exports = { init };