Skip to content

Commit

Permalink
finished hotreloading
Browse files Browse the repository at this point in the history
  • Loading branch information
alexlobera committed Jun 7, 2017
1 parent 942c69b commit fd5f56e
Show file tree
Hide file tree
Showing 8 changed files with 150 additions and 45 deletions.
19 changes: 12 additions & 7 deletions config/webpack.config.client.dev.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,9 @@ const paths = require('./paths');

const base = require('./webpack.config.base');

base.entry = [
const config = Object.assign({}, base)

config.entry = [
// Include an alternative client for WebpackDevServer. A client's job is to
// connect to WebpackDevServer by a socket and get notified about changes.
// When you save a file, the client will either apply hot updates (in case
Expand All @@ -32,7 +34,7 @@ base.entry = [
// changing JS code would still trigger a refresh.
]

base.output = {
config.output = {
// Next line is not used in dev but WebpackDevServer crashes without it:
path: paths.appBuild,
// Add /* filename */ comments to generated require()s in the output.
Expand All @@ -45,12 +47,14 @@ base.output = {
chunkFilename: 'static/js/[name].chunk.js',
// This is the URL that app is served from. We use "/" in development.
publicPath: '/',
hotUpdateChunkFilename: 'static/[id].[hash].hot-update.js',
hotUpdateMainFilename: 'static/[hash].hot-update.json',
// Point sourcemap entries to original disk location
devtoolModuleFilenameTemplate: info =>
path.resolve(info.absoluteResourcePath),
}

base.module.rules = base.module.rules.concat([
config.module.rules = config.module.rules.concat([
// "postcss" loader applies autoprefixer to our CSS.
// "css" loader resolves paths in CSS and adds assets as dependencies.
// "style" loader turns CSS into JS modules that inject <style> tags.
Expand Down Expand Up @@ -87,7 +91,8 @@ base.module.rules = base.module.rules.concat([
],
},
])
base.plugins = base.plugins.concat([

config.plugins = config.plugins.concat([
// This is necessary to emit hot updates (currently CSS only):
new webpack.HotModuleReplacementPlugin(),
// Watcher doesn't work well if you mistype casing in a path so we use
Expand All @@ -104,16 +109,16 @@ base.plugins = base.plugins.concat([
// Turn off performance hints during development because we don't do any
// splitting or minification in interest of speed. These warnings become
// cumbersome.
base.performance = {
config.performance = {
hints: false,
}

// Some libraries import Node modules but don't use them in the browser.
// Tell Webpack to provide empty mocks for them so importing them works.
base.node = {
config.node = {
fs: 'empty',
net: 'empty',
tls: 'empty',
},

module.exports = base
module.exports = config
14 changes: 8 additions & 6 deletions config/webpack.config.client.prod.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ const getClientEnvironment = require('./env');

const base = require('./webpack.config.base');

const config = Object.assign({}, base);

// Webpack uses `publicPath` to determine where the app is being served from.
// It requires a trailing slash, or the file assets will get an incorrect path.
const publicPath = paths.servedPath;
Expand Down Expand Up @@ -50,8 +52,8 @@ const extractTextPluginOptions = shouldUseRelativeAssetPaths
// The development configuration is different and lives in a separate file.

// In production, we only want to load the polyfills and the app code.
base.entry = [require.resolve('./polyfills'), paths.appIndexJs]
base.output = {
config.entry = [require.resolve('./polyfills'), paths.appIndexJs]
config.output = {
// The build folder.
path: paths.appBuild,
// Generated JS file names (with nested folders).
Expand All @@ -65,7 +67,7 @@ base.output = {
devtoolModuleFilenameTemplate: info =>
path.relative(paths.appSrc, info.absoluteResourcePath),
}
base.module.rules = base.module.rules.concat([
config.module.rules = config.module.rules.concat([
// The notation here is somewhat confusing.
// "postcss" loader applies autoprefixer to our CSS.
// "css" loader resolves paths in CSS and adds assets as dependencies.
Expand Down Expand Up @@ -122,7 +124,7 @@ base.module.rules = base.module.rules.concat([
// Remember to add the new extension(s) to the "file" loader exclusion list.
])

base.plugins = base.plugins.concat([
config.plugins = config.plugins.concat([
// Note: this won't work without ExtractTextPlugin.extract(..) in `loaders`.
new ExtractTextPlugin({
filename: cssFilename,
Expand Down Expand Up @@ -179,10 +181,10 @@ base.plugins = base.plugins.concat([

// Some libraries import Node modules but don't use them in the browser.
// Tell Webpack to provide empty mocks for them so importing them works.
base.node = {
config.node = {
fs: 'empty',
net: 'empty',
tls: 'empty',
}

module.exports = base
module.exports = config
16 changes: 15 additions & 1 deletion config/webpack.config.server.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,21 @@ config.output = {
filename: 'bundle.js',
publicPath: '/'
},

/*
config.plugins = config.plugins.concat([
// This is necessary to emit hot updates (currently CSS only):
new webpack.HotModuleReplacementPlugin(),
// Watcher doesn't work well if you mistype casing in a path so we use
// a plugin that prints an error when you attempt to do this.
// See https://github.com/facebookincubator/create-react-app/issues/240
new CaseSensitivePathsPlugin(),
// If you require a missing module and then `npm install` it, you still have
// to restart the development server for Webpack to discover it. This plugin
// makes the discovery automatic so you don't have to restart.
// See https://github.com/facebookincubator/create-react-app/issues/186
new WatchMissingNodeModulesPlugin(paths.appNodeModules),
])
*/
config.node = {
console: false,
global: false,
Expand Down
92 changes: 92 additions & 0 deletions config/webpackDevServer.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
'use strict';

const errorOverlayMiddleware = require('react-error-overlay/middleware');
const noopServiceWorkerMiddleware = require('react-dev-utils/noopServiceWorkerMiddleware');
const config = require('./webpack.config.client.dev');
const paths = require('./paths');

const protocol = process.env.HTTPS === 'true' ? 'https' : 'http';
const host = process.env.HOST || '0.0.0.0';

module.exports = function(proxy, allowedHost) {
return {
// WebpackDevServer 2.4.3 introduced a security fix that prevents remote
// websites from potentially accessing local content through DNS rebinding:
// https://github.com/webpack/webpack-dev-server/issues/887
// https://medium.com/webpack/webpack-dev-server-middleware-security-issues-1489d950874a
// However, it made several existing use cases such as development in cloud
// environment or subdomains in development significantly more complicated:
// https://github.com/facebookincubator/create-react-app/issues/2271
// https://github.com/facebookincubator/create-react-app/issues/2233
// While we're investigating better solutions, for now we will take a
// compromise. Since our WDS configuration only serves files in the `public`
// folder we won't consider accessing them a vulnerability. However, if you
// use the `proxy` feature, it gets more dangerous because it can expose
// remote code execution vulnerabilities in backends like Django and Rails.
// So we will disable the host check normally, but enable it if you have
// specified the `proxy` setting. Finally, we let you override it if you
// really know what you're doing with a special environment variable.
disableHostCheck: !proxy ||
process.env.DANGEROUSLY_DISABLE_HOST_CHECK === 'true',
// Enable gzip compression of generated files.
compress: true,
// Silence WebpackDevServer's own logs since they're generally not useful.
// It will still show compile warnings and errors with this setting.
clientLogLevel: 'none',
// By default WebpackDevServer serves physical files from current directory
// in addition to all the virtual build products that it serves from memory.
// This is confusing because those files won’t automatically be available in
// production build folder unless we copy them. However, copying the whole
// project directory is dangerous because we may expose sensitive files.
// Instead, we establish a convention that only files in `public` directory
// get served. Our build script will copy `public` into the `build` folder.
// In `index.html`, you can get URL of `public` folder with %PUBLIC_URL%:
// <link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico">
// In JavaScript code, you can access it with `process.env.PUBLIC_URL`.
// Note that we only recommend to use `public` folder as an escape hatch
// for files like `favicon.ico`, `manifest.json`, and libraries that are
// for some reason broken when imported through Webpack. If you just want to
// use an image, put it in `src` and `import` it from JavaScript instead.
contentBase: paths.appPublic,
// By default files from `contentBase` will not trigger a page reload.
watchContentBase: true,
// Enable hot reloading server. It will provide /sockjs-node/ endpoint
// for the WebpackDevServer client so it can learn when the files were
// updated. The WebpackDevServer client is included as an entry point
// in the Webpack development configuration. Note that only changes
// to CSS are currently hot reloaded. JS changes will refresh the browser.
hot: true,
// It is important to tell WebpackDevServer to use the same "root" path
// as we specified in the config. In development, we always serve from /.
publicPath: config.output.publicPath,
// WebpackDevServer is noisy by default so we emit custom message instead
// by listening to the compiler events with `compiler.plugin` calls above.
quiet: true,
// Reportedly, this avoids CPU overload on some systems.
// https://github.com/facebookincubator/create-react-app/issues/293
watchOptions: {
ignored: /node_modules/,
},
// Enable HTTPS if the HTTPS environment variable is set to 'true'
https: protocol === 'https',
host: host,
overlay: false,
historyApiFallback: {
// Paths with dots should still use the history fallback.
// See https://github.com/facebookincubator/create-react-app/issues/387.
disableDotRule: true,
},
public: allowedHost,
proxy,
setup(app) {
// This lets us open files from the runtime error overlay.
app.use(errorOverlayMiddleware());
// This service worker file is effectively a 'no-op' that will reset any
// previous service worker registered for the same host:port combination.
// We do this in development to avoid hitting the production cache if
// it used the same host and port.
// https://github.com/facebookincubator/create-react-app/issues/2272#issuecomment-302832432
app.use(noopServiceWorkerMiddleware());
},
};
};
7 changes: 1 addition & 6 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,8 @@
"version": "0.1.0",
"scripts": {
"serve": "NODE_ENV=production node ./build/server/bundle.js",
"start-client": "node scripts/start-client.js",
"start-universal": "node scripts/start-universal.js",
"start": "NODE_ENV=development concurrently -c blue,grey,magenta \"npm run dev-build-server\" \"sleep 3 && npm run dev-run-server\" \"npm run dev-build-and-run-client\"",
"start": "node scripts/start.js",
"clean-build": "node_modules/.bin/rimraf ./build",
"dev-build-server": "NODE_ENV=development webpack --watch --config config/webpack.config.server.js",
"dev-run-server": "NODE_ENV=development nodemon --watch build/server build/server/bundle.js",
"dev-build-and-run-client": "NODE_ENV=development webpack-dev-server -d --inline --no-info --port 3020 --config config/webpack.config.client.dev.js",
"build-client": "node scripts/build-client.js",
"build-server": "NODE_ENV=production webpack --config config/webpack.config.server.js",
"build": "npm run clean-build && npm run build-client && npm run build-server",
Expand Down
38 changes: 20 additions & 18 deletions scripts/start-universal.js → scripts/start.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,15 +27,15 @@ const {
prepareUrls,
} = require('react-dev-utils/WebpackDevServerUtils');
const openBrowser = require('react-dev-utils/openBrowser');
const createDevServerConfig = require('../config/webpackDevServer.config');
const paths = require('../config/paths');
const configWebpackClient = require('../config/webpack.config.client.dev');

// const createDevServerConfig = require('../config/webpackDevServer.config');

const useYarn = fs.existsSync(paths.yarnLockFile);
// const useYarn = fs.existsSync(paths.yarnLockFile);
const isInteractive = process.stdout.isTTY;

// Warn and crash if required files are missing
//if (!checkRequiredFiles([paths.appHtml, paths.appIndexJs])) {
if (!checkRequiredFiles([paths.appIndexJs])) {
process.exit(1);
}
Expand All @@ -58,20 +58,23 @@ choosePort(HOST, DEFAULT_CLIENT_PORT)
const appName = require(paths.appPackageJson).name;
const urls = prepareUrls(protocol, HOST, port);

global.CLIENT_PORT = port

// Create a webpack compiler that is configured with custom messages.
//const compiler = createCompiler(webpack, config, appName, urls, useYarn);
// we use different compiler
//const compiler = createCompiler(webpack, configWebpackClient, appName, urls, useYarn);
const compiler = webpack(configWebpackClient);

// Load proxy config
const proxySetting = require(paths.appPackageJson).proxy;
const proxyConfig = prepareProxy(proxySetting, paths.appPublic);
const serverConfig = {
hot: false,
port,
inline: true
}
// Serve webpack assets generated by the compiler over a web sever.
const serverConfig = createDevServerConfig(
proxyConfig,
urls.lanUrlForConfig
);

const clientServer = new WebpackDevServer(compiler, serverConfig);
global.CLIENT_PORT = port

// Launch WebpackDevServer.
clientServer.listen(port, HOST, err => {
if (err) {
Expand All @@ -89,21 +92,20 @@ choosePort(HOST, DEFAULT_CLIENT_PORT)
return;
}

global.SERVER_PORT = portServer

global.SERVER_PORT = portServer;
const configWebpackServer = require('../config/webpack.config.server');
const compiler = webpack(configWebpackServer)
const compiler = webpack(configWebpackServer);

compiler.watch({ // watch options:
aggregateTimeout: 300, // wait so long for more changes
}, function(err, stats) {
if (err)
console.log('error on webpack server', err)
console.log('error on webpack server', err);

const server = require('../build/server/bundle.js')
const server = require('../build/server/bundle.js');
const urls = prepareUrls(protocol, HOST, portServer);
openBrowser(urls.localUrlForBrowser);
});

})
.catch(err => {
if (err && err.message) {
Expand All @@ -114,7 +116,7 @@ choosePort(HOST, DEFAULT_CLIENT_PORT)
});
['SIGINT', 'SIGTERM'].forEach(function(sig) {
process.on(sig, function() {
devServer.close();
clientServer.close();
process.exit();
})
});
Expand Down
6 changes: 2 additions & 4 deletions src/server/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,6 @@ import proxy from 'http-proxy-middleware'
import config from '../config'
import reactApp from './app'

// import routes from './routes'

const app = express()

app.use('/public', express.static('public'))
Expand All @@ -19,11 +17,11 @@ if (process.env.NODE_ENV === 'production') {
app.use('/static', express.static(path.join(process.cwd(), 'build/client/static')));
} else {
// Otherwise we want to proxy the webpack development server.

app.use('/static', proxy({
target: `http://localhost:${global.CLIENT_PORT || 3020}`,
//target: `http://localhost:3020`,
ws: true,
logLevel: 'warn'
logLevel: 'info'
}));
}

Expand Down
3 changes: 0 additions & 3 deletions src/shared/app.css

This file was deleted.

0 comments on commit fd5f56e

Please sign in to comment.