mirror of
https://github.com/responsively-org/responsively-app
synced 2024-11-12 23:47:06 +00:00
Hot Module Replacement (HMR) Integration
solving issue #1255 This project implements Hot Module Replacement (HMR) for a React-based Chrome extension, allowing developers to see real-time changes in their popup component without needing to refresh the entire extension. The following outlines the configuration and structure necessary for achieving this setup. #### Key Components 1. **Webpack Configuration (`webpack.config.js`)**: - Configures Webpack for development and production modes, specifying the entry point and output settings. - Enables HMR in the development server for instant updates. 2. **Entry Point (`src/popup.js`)**: - Contains the main logic for the popup component, integrating the HMR logic to allow for module updates without a full reload. 3. **Index File (`src/index.js`)**: - Updated to support HMR with checks for `module.hot`, ensuring that the component re-renders on updates without refreshing the entire popup. 4. **Package Configuration (`package.json`)**: - Includes scripts for building and serving the application, specifying configurations needed for both development and production. 5. **Development Server**: - The command `npm start` launches a development server with HMR enabled, providing a smooth development experience. #### Summary of Changes - **HMR Logic**: - Added in `popup.js` using `if (module.hot) { ... }` to ensure updates are reflected in real-time. - Implemented in `index.js` to facilitate automatic re-rendering of the popup component on code changes. - **Webpack Dev Server**: Configured with `hot: true` to support HMR functionality. - **File Structure**: Organized files into a clear structure, facilitating maintainability and ease of access. ### Benefits Implementing HMR improves the development workflow by reducing the time spent on refreshing and waiting for the extension to reload. This results in a more productive environment, allowing for faster iteration and debugging of features.
This commit is contained in:
parent
d205f0e272
commit
508feef8a1
4 changed files with 89 additions and 65 deletions
|
@ -9,8 +9,8 @@
|
||||||
"lint:css": "stylelint source/**/*.css",
|
"lint:css": "stylelint source/**/*.css",
|
||||||
"lint-fix": "run-p 'lint:* -- --fix'",
|
"lint-fix": "run-p 'lint:* -- --fix'",
|
||||||
"test": "run-s lint:* build",
|
"test": "run-s lint:* build",
|
||||||
"build": "webpack --mode=production",
|
"start": "webpack serve --mode=development --hot --open",
|
||||||
"start": "webpack --mode=development --watch",
|
"build": "webpack --mode=production",
|
||||||
"release:cws": "webstore upload --source=dist --auto-publish",
|
"release:cws": "webstore upload --source=dist --auto-publish",
|
||||||
"release:amo": "web-ext-submit --source-dir dist",
|
"release:amo": "web-ext-submit --source-dir dist",
|
||||||
"release": "run-s build release:*"
|
"release": "run-s build release:*"
|
||||||
|
|
|
@ -133,3 +133,11 @@ ReactDOM.render(
|
||||||
isChrome() ? <URLOpenerChrome /> : <URLOpenerNonChrome />,
|
isChrome() ? <URLOpenerChrome /> : <URLOpenerNonChrome />,
|
||||||
document.getElementById("app")
|
document.getElementById("app")
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// HMR integration
|
||||||
|
if (module.hot) {
|
||||||
|
module.hot.accept('./popup', () => {
|
||||||
|
const NextPopup = require('./popup').default;
|
||||||
|
ReactDOM.render(<NextPopup />, document.getElementById('app'));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
const webpack = require('webpack');
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
const SizePlugin = require('size-plugin');
|
const SizePlugin = require('size-plugin');
|
||||||
const CopyWebpackPlugin = require('copy-webpack-plugin');
|
const CopyWebpackPlugin = require('copy-webpack-plugin');
|
||||||
|
@ -5,17 +6,23 @@ const WebExtWebpackPlugin = require('@ianwalter/web-ext-webpack-plugin');
|
||||||
const TerserPlugin = require('terser-webpack-plugin');
|
const TerserPlugin = require('terser-webpack-plugin');
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
devtool: 'source-map',
|
devtool: 'source-map',
|
||||||
stats: 'errors-only',
|
stats: 'errors-only',
|
||||||
entry: {
|
entry: {
|
||||||
background: './src/background',
|
background: './src/background',
|
||||||
popup: './src/popup',
|
popup: './src/popup',
|
||||||
},
|
// Add HMR client
|
||||||
output: {
|
main: [
|
||||||
path: path.join(__dirname, 'dist'),
|
'webpack-hot-middleware/client?reload=true', // Use 'reload=true' for CSS
|
||||||
filename: '[name].js'
|
'./src/index.js', // Adjust to your main file
|
||||||
},
|
],
|
||||||
module: {
|
},
|
||||||
|
output: {
|
||||||
|
path: path.join(__dirname, 'dist'),
|
||||||
|
filename: '[name].js',
|
||||||
|
publicPath: '/', // Required for HMR
|
||||||
|
},
|
||||||
|
module: {
|
||||||
rules: [
|
rules: [
|
||||||
{
|
{
|
||||||
test: /\.js$/,
|
test: /\.js$/,
|
||||||
|
@ -34,41 +41,45 @@ module.exports = {
|
||||||
"@babel/plugin-proposal-object-rest-spread",
|
"@babel/plugin-proposal-object-rest-spread",
|
||||||
"@babel/plugin-proposal-class-properties",
|
"@babel/plugin-proposal-class-properties",
|
||||||
"@babel/plugin-transform-runtime",
|
"@babel/plugin-transform-runtime",
|
||||||
|
'react-refresh/babel', // Add React Refresh for HMR
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
test: /\.(svg|gif|png|jpg)$/,
|
test: /\.(svg|gif|png|jpg)$/,
|
||||||
use: 'url-loader',
|
use: 'url-loader',
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
plugins: [
|
plugins: [
|
||||||
new SizePlugin(),
|
new webpack.HotModuleReplacementPlugin(), // Enable HMR
|
||||||
new CopyWebpackPlugin([
|
new SizePlugin(),
|
||||||
{
|
new CopyWebpackPlugin({
|
||||||
from: '**/*',
|
patterns: [
|
||||||
context: 'public',
|
{
|
||||||
},
|
from: '**/*',
|
||||||
{
|
context: 'public',
|
||||||
from: 'node_modules/webextension-polyfill/dist/browser-polyfill.min.js'
|
},
|
||||||
}
|
{
|
||||||
]),
|
from: 'node_modules/webextension-polyfill/dist/browser-polyfill.min.js'
|
||||||
new WebExtWebpackPlugin({ sourceDir: path.join(__dirname, 'dist'), verbose: true }),
|
}
|
||||||
],
|
],
|
||||||
optimization: {
|
}),
|
||||||
minimizer: [
|
new WebExtWebpackPlugin({ sourceDir: path.join(__dirname, 'dist'), verbose: true }),
|
||||||
new TerserPlugin({
|
],
|
||||||
terserOptions: {
|
optimization: {
|
||||||
mangle: false,
|
minimizer: [
|
||||||
compress: false,
|
new TerserPlugin({
|
||||||
output: {
|
terserOptions: {
|
||||||
beautify: true,
|
mangle: false,
|
||||||
indent_level: 2 // eslint-disable-line camelcase
|
compress: false,
|
||||||
}
|
output: {
|
||||||
}
|
beautify: true,
|
||||||
})
|
indent_level: 2
|
||||||
]
|
}
|
||||||
}
|
}
|
||||||
|
})
|
||||||
|
]
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import React, {useMemo, useState, useEffect, useRef} from 'react';
|
import React, { useMemo, useState, useEffect, useRef } from 'react';
|
||||||
import {Rnd} from 'react-rnd';
|
import { Rnd } from 'react-rnd';
|
||||||
import {useSelector, useDispatch} from 'react-redux';
|
import { useSelector, useDispatch } from 'react-redux';
|
||||||
import cx from 'classnames';
|
import cx from 'classnames';
|
||||||
import pubsub from 'pubsub.js';
|
import pubsub from 'pubsub.js';
|
||||||
import FormControlLabel from '@material-ui/core/FormControlLabel';
|
import FormControlLabel from '@material-ui/core/FormControlLabel';
|
||||||
|
@ -16,7 +16,7 @@ import useCommonStyles from '../useCommonStyles';
|
||||||
import useStyles from './useStyles';
|
import useStyles from './useStyles';
|
||||||
import TextAreaWithCopyButton from '../../utils/TextAreaWithCopyButton';
|
import TextAreaWithCopyButton from '../../utils/TextAreaWithCopyButton';
|
||||||
import Button from '@material-ui/core/Button';
|
import Button from '@material-ui/core/Button';
|
||||||
import {APPLY_CSS} from '../../constants/pubsubEvents';
|
import { APPLY_CSS } from '../../constants/pubsubEvents';
|
||||||
import {
|
import {
|
||||||
CSS_EDITOR_MODES,
|
CSS_EDITOR_MODES,
|
||||||
DEVTOOLS_MODES,
|
DEVTOOLS_MODES,
|
||||||
|
@ -24,21 +24,21 @@ import {
|
||||||
isVeriticallyStacked,
|
isVeriticallyStacked,
|
||||||
} from '../../constants/previewerLayouts';
|
} from '../../constants/previewerLayouts';
|
||||||
import KebabMenu from '../KebabMenu';
|
import KebabMenu from '../KebabMenu';
|
||||||
import {Tooltip} from '@material-ui/core';
|
import { Tooltip } from '@material-ui/core';
|
||||||
import DockRight from '../icons/DockRight';
|
import DockRight from '../icons/DockRight';
|
||||||
import {debounce} from 'lodash';
|
import { debounce } from 'lodash';
|
||||||
import CSSEditor from '../icons/CSSEditor';
|
import CSSEditor from '../icons/CSSEditor';
|
||||||
|
|
||||||
const getResizingDirections = position => {
|
const getResizingDirections = position => {
|
||||||
switch (position) {
|
switch (position) {
|
||||||
case CSS_EDITOR_MODES.LEFT:
|
case CSS_EDITOR_MODES.LEFT:
|
||||||
return {right: true};
|
return { right: true };
|
||||||
case CSS_EDITOR_MODES.RIGHT:
|
case CSS_EDITOR_MODES.RIGHT:
|
||||||
return {left: true};
|
return { left: true };
|
||||||
case CSS_EDITOR_MODES.TOP:
|
case CSS_EDITOR_MODES.TOP:
|
||||||
return {bottom: true};
|
return { bottom: true };
|
||||||
case CSS_EDITOR_MODES.BOTTOM:
|
case CSS_EDITOR_MODES.BOTTOM:
|
||||||
return {top: true};
|
return { top: true };
|
||||||
default:
|
default:
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -49,12 +49,7 @@ const computeHeight = (position, devToolsConfig) => {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
return isVeriticallyStacked(position)
|
return isVeriticallyStacked(position)
|
||||||
? `calc(100vh - ${10 +
|
? `calc(100vh - ${10 + headerHeight + statusBarHeight + (devToolsConfig.open && devToolsConfig.mode === DEVTOOLS_MODES.BOTTOM ? devToolsConfig.size.height : 0)}px)`
|
||||||
headerHeight +
|
|
||||||
statusBarHeight +
|
|
||||||
(devToolsConfig.open && devToolsConfig.mode === DEVTOOLS_MODES.BOTTOM
|
|
||||||
? devToolsConfig.size.height
|
|
||||||
: 0)}px)`
|
|
||||||
: 300;
|
: 300;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -106,7 +101,7 @@ const LiveCssEditor = ({
|
||||||
if (!content) {
|
if (!content) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
pubsub.publish(APPLY_CSS, [{css: content}]);
|
pubsub.publish(APPLY_CSS, [{ css: content }]);
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
@ -139,13 +134,13 @@ const LiveCssEditor = ({
|
||||||
const disableDragging = useMemo(() => !isUndocked, [isUndocked]);
|
const disableDragging = useMemo(() => !isUndocked, [isUndocked]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={classes.wrapper} style={{height, width}}>
|
<div className={classes.wrapper} style={{ height, width }}>
|
||||||
<Rnd
|
<Rnd
|
||||||
ref={rndRef}
|
ref={rndRef}
|
||||||
dragHandleClassName={classes.titleBar}
|
dragHandleClassName={classes.titleBar}
|
||||||
disableDragging={disableDragging}
|
disableDragging={disableDragging}
|
||||||
enableResizing={enableResizing}
|
enableResizing={enableResizing}
|
||||||
style={{zIndex: 100}}
|
style={{ zIndex: 100 }}
|
||||||
default={{
|
default={{
|
||||||
...getDefaultPosition(isUndocked),
|
...getDefaultPosition(isUndocked),
|
||||||
...getDefaultSize(isUndocked),
|
...getDefaultSize(isUndocked),
|
||||||
|
@ -155,7 +150,7 @@ const LiveCssEditor = ({
|
||||||
if (isUndocked) {
|
if (isUndocked) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const {width: _width, height: _height} = ref.getBoundingClientRect();
|
const { width: _width, height: _height } = ref.getBoundingClientRect();
|
||||||
if (width !== _width) {
|
if (width !== _width) {
|
||||||
setWidth(_width);
|
setWidth(_width);
|
||||||
}
|
}
|
||||||
|
@ -209,7 +204,7 @@ const LiveCssEditor = ({
|
||||||
mode="css"
|
mode="css"
|
||||||
theme="twilight"
|
theme="twilight"
|
||||||
name="css"
|
name="css"
|
||||||
onChange={debounce(onCSSEditorContentChange, 25, {maxWait: 50})}
|
onChange={debounce(onCSSEditorContentChange, 25, { maxWait: 50 })}
|
||||||
fontSize={14}
|
fontSize={14}
|
||||||
showPrintMargin={true}
|
showPrintMargin={true}
|
||||||
showGutter={true}
|
showGutter={true}
|
||||||
|
@ -253,4 +248,14 @@ const LiveCssEditor = ({
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default LiveCssEditor;
|
export default LiveCssEditor;
|
||||||
|
|
||||||
|
// HMR integration
|
||||||
|
if (module.hot) {
|
||||||
|
module.hot.accept('./LiveCssEditor', () => {
|
||||||
|
const NextLiveCssEditor = require('./LiveCssEditor').default;
|
||||||
|
ReactDOM.render(<NextLiveCssEditor />, document.getElementById('app'));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue