Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ workflows:
tags:
only: /.*/


commands:
setup:
steps:
Expand Down
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,8 @@ Configuration options can be passed to the client by adding querystring paramete
* **dynamicPublicPath** - Set to `true` to use webpack `publicPath` as prefix of `path`. (We can set `__webpack_public_path__` dynamically at runtime in the entry point, see note of [output.publicPath](https://webpack.js.org/configuration/output/#output-publicpath))
* **autoConnect** - Set to `false` to use to prevent a connection being automatically opened from the client to the webpack back-end - ideal if you need to modify the options using the `setOptionsAndConnect` function
* **ansiColors** - An object to customize the client overlay colors as mentioned in the [ansi-html-community](https://github.com/mahdyar/ansi-html-community#set-colors) package.
* **overlayStyles** - An object to let you override or add new inline styles to the client overlay div.
* **overlayStyleMode** - Set to `'inline'` to use inline styling (default), or `'headless'` for custom or [CSP](https://webpack.js.org/guides/csp/) compatible styling (You can import `webpack-hot-middleware/client-overlay.css` to enable the default styles; make sure a proper loader have been configured for the CSS files).
* **overlayStyles** - An object to let you override or add new inline styles to the client overlay div. Requires `overlayStyleMode` to be `'inline'`.
* **overlayWarnings** - Set to `true` to enable client overlay on warnings in addition to errors.
* **statsOptions** - An object to customize stats options.

Expand Down
36 changes: 36 additions & 0 deletions client-overlay.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
.webpack-hot-middleware-clientOverlay {
background: rgba(0, 0, 0, 0.85);
color: #e8e8e8;
line-height: 1.6;
white-space: pre;
font-family: Menlo, Consolas, monospace;
font-size: 13px;
position: fixed;
z-index: 9999;
padding: 10px;
left: 0;
right: 0;
top: 0;
bottom: 0;
overflow: auto;
dir: ltr;
text-align: left;
}

.webpack-hot-middleware-clientOverlay__problem-group {
margin-bottom: 26px;
}

.webpack-hot-middleware-clientOverlay__problem {
color: #000000;
padding: 3px 6px;
border-radius: 4px;
}

.webpack-hot-middleware-clientOverlay__problem--error {
background-color: #ff3348;
}

.webpack-hot-middleware-clientOverlay__problem--warning {
background-color: #ffd30e;
}
50 changes: 36 additions & 14 deletions client-overlay.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

var clientOverlay = document.createElement('div');
clientOverlay.id = 'webpack-hot-middleware-clientOverlay';
clientOverlay.classList.add('webpack-hot-middleware-clientOverlay');

var styles = {
background: 'rgba(0,0,0,0.85)',
color: '#e8e8e8',
Expand Down Expand Up @@ -37,13 +39,16 @@ var colors = {

var htmlEntities = require('html-entities');

function showProblems(type, lines) {
function showProblems(type, lines, options) {
clientOverlay.innerHTML = '';
lines.forEach(function (msg) {
msg = ansiHTML(htmlEntities.encode(msg));
var div = document.createElement('div');
div.style.marginBottom = '26px';
div.innerHTML = problemType(type) + ' in ' + msg;
div.classList.add('webpack-hot-middleware-clientOverlay__problem-group');
if (options.overlayStyleMode === 'inline') {
div.style.marginBottom = '26px';
}
div.innerHTML = problemType(type, options) + ' in ' + msg;
clientOverlay.appendChild(div);
});
if (document.body) {
Expand All @@ -57,17 +62,32 @@ function clear() {
}
}

function problemType(type) {
function problemType(type, options) {
var problemTypeSingularNames = {
errors: 'error',
warnings: 'warning',
};
var typeName = problemTypeSingularNames[type] || 'error';
var className = 'webpack-hot-middleware-clientOverlay__problem--' + typeName;
var problemColors = {
errors: colors.red,
warnings: colors.yellow,
};
var color = problemColors[type] || colors.red;
var styleAttributeFragment = '';
if (options.overlayStyleMode === 'inline') {
var color = problemColors[type] || colors.red;
styleAttributeFragment =
' style="background-color:#' +
color +
'; color:#000000; padding:3px 6px; border-radius: 4px;"';
}
return (
'<span style="background-color:#' +
color +
'; color:#000000; padding:3px 6px; border-radius: 4px;">' +
type.slice(0, -1).toUpperCase() +
'<span class="webpack-hot-middleware-clientOverlay__problem ' +
className +
'"' +
styleAttributeFragment +
'>' +
typeName.toUpperCase() +
'</span>'
);
}
Expand All @@ -80,12 +100,14 @@ module.exports = function (options) {
ansiHTML.setColors(colors);
}

for (var style in options.overlayStyles) {
styles[style] = options.overlayStyles[style];
}
if (options.overlayStyleMode === 'inline') {
for (var style in options.overlayStyles) {
styles[style] = options.overlayStyles[style];
}

for (var key in styles) {
clientOverlay.style[key] = styles[key];
for (var key in styles) {
clientOverlay.style[key] = styles[key];
}
}

return {
Expand Down
23 changes: 18 additions & 5 deletions client.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ var options = {
overlayStyles: {},
overlayWarnings: false,
ansiColors: {},
overlayStyleMode: 'inline', // 'headless', 'inline'
};
if (__resourceQuery) {
var params = Array.from(new URLSearchParams(__resourceQuery.slice(1)));
Expand Down Expand Up @@ -74,6 +75,21 @@ function setOverrides(overrides) {
if (overrides.overlayWarnings) {
options.overlayWarnings = overrides.overlayWarnings == 'true';
}
if (overrides.overlayStyleMode) {
if (
overrides.overlayStyleMode === 'inline' ||
overrides.overlayStyleMode === 'headless'
) {
options.overlayStyleMode = overrides.overlayStyleMode;
} else {
console.warn(
'Unsupported `overlayStyleMode` value. Expected one of `inline`, or `headless`.' +
'Falling back to the default mode: `' +
options.overlayStyleMode +
'`.'
);
}
}
}

function EventSourceWrapper() {
Expand Down Expand Up @@ -167,10 +183,7 @@ function createReporter() {

var overlay;
if (typeof document !== 'undefined' && options.overlay) {
overlay = require('./client-overlay')({
ansiColors: options.ansiColors,
overlayStyles: options.overlayStyles,
});
overlay = require('./client-overlay')(options);
}

var styles = {
Expand Down Expand Up @@ -218,7 +231,7 @@ function createReporter() {
}
if (overlay) {
if (options.overlayWarnings || type === 'errors') {
overlay.showProblems(type, obj[type]);
overlay.showProblems(type, obj[type], options);
return false;
}
overlay.clear();
Expand Down
47 changes: 46 additions & 1 deletion example/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,15 @@
```sh
npm install
```
* Start server

## Basic Hot Reload Example

Start server:

```sh
npm start
```

* Open page in browser http://localhost:1616
* Open the developer console
* Edit `client.js` & save
Expand All @@ -27,3 +32,43 @@ npm run start:multientry

* Open page in browser http://localhost:1616/multientry
* Edit `client.js` or `extra.js` & save

## CSP-compatible Styling Examples

These scenarios run with a strict CSP configuration that disallow inline styles.

### CSP Violation

This scenario intentionally violates CSP rules by inlining the styles instead of importing from a CSS file.

```sh
npm run start:csp::violate
```

* Open the page in browser http://localhost:1616/csp
* Try making a syntax error in `client.js`.
* Check the console logs; CSP must be violated, styles must be blocked.

### CSP-compatible Styling

Since inline styles are disallowed, styles must be imported from a CSS file. To demonstrate this, the example uses `file-loader` and `style-loader` by setting the `injectType` option to `linkTag`.
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've added a description of why we install file-loader and style-loader in the example package.


```sh
npm run start:csp
```

* Open the page in browser http://localhost:1616/csp
* Try making a syntax error in `client.js`.
* Check the console logs; CSP must not be violated, styles must be applied.

## Headless Styling Example

This scenario demonstrates custom styling behavior.

```sh
npm run start:headless-styling
```

* Open the page in browser http://localhost:1616/headless-styling
* Try making a syntax error in `client.js`.
* The `ERROR` label must appear as white text on a black background only.
24 changes: 15 additions & 9 deletions example/client.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,25 @@ var time = document.getElementById('time');

var timer = setInterval(updateClock, 1000);

var isInlineStylingAllowed =
app.dataset.overlayStyleMode == null ||
app.dataset.overlayStyleMode === 'inline';

function updateClock() {
time.innerHTML = new Date().toString();
}

// Edit these styles to see them take effect immediately
app.style.display = 'table-cell';
app.style.width = '400px';
app.style.height = '400px';
app.style.border = '3px solid #339';
app.style.background = '#99d';
app.style.color = '#333';
app.style.textAlign = 'center';
app.style.verticalAlign = 'middle';
if (isInlineStylingAllowed) {
// Edit these styles to see them take effect immediately
app.style.display = 'table-cell';
app.style.width = '400px';
app.style.height = '400px';
app.style.border = '3px solid #339';
app.style.background = '#99d';
app.style.color = '#333';
app.style.textAlign = 'center';
app.style.verticalAlign = 'middle';
}

// Uncomment one of the following lines to see error handling
// require('unknown-module')
Expand Down
5 changes: 5 additions & 0 deletions example/csp.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
console.log('Testing CSP...');

if (module.hot) {
module.hot.accept();
}
5 changes: 5 additions & 0 deletions example/headless-styling.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
console.log('Testing headless styling...');

if (module.hot) {
module.hot.accept();
}
20 changes: 20 additions & 0 deletions example/index-csp.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Webpack Hot Middleware CSP Example</title>
</head>
<body>
<div id="app" data-overlay-style-mode="headless">
<p id="time">Date</p>
<input
type="text"
size="40"
placeholder="Type something in here to prove state isn't lost"
/>
</div>
<script src="/client.js"></script>
<script src="/csp.js"></script>
</body>
</html>
39 changes: 39 additions & 0 deletions example/index-headless-styling.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Webpack Hot Middleware Headless Styling Example</title>
<style>
.webpack-hot-middleware-clientOverlay__problem-group {
margin-bottom: 26px;
}

.webpack-hot-middleware-clientOverlay__problem {
color: #ffffff;
padding: 3px 6px;
border-radius: 4px;
}

.webpack-hot-middleware-clientOverlay__problem--error {
background-color: #000000;
}

.webpack-hot-middleware-clientOverlay__problem--warning {
background-color: #000000;
}
</style>
</head>
<body>
<div id="app" data-overlay-style-mode="headless">
<p id="time">Date</p>
<input
type="text"
size="40"
placeholder="Type something in here to prove state isn't lost"
/>
</div>
<script src="/client.js"></script>
<script src="/headless-styling.js"></script>
</body>
</html>
Loading