ff-stream-web

git clone git://archive.git.mtrnord.blog/MTRNord/ff-stream-web.git
Log | Files | Refs | README | LICENSE

commit 2f7d127894fd547700a784c57f83af276b3e24bd
parent 82a54939ee3407ba8cb90cc8111366be01813081
Author: Vladimir Kutepov <frenzzy.man@gmail.com>
Date:   Fri, 21 Oct 2016 23:03:55 +0400

Improve build scripts; integrate react-hot-loader v3; remove react-transform-hmr (#918)

First part of #697

Changes:
- Replace [React Transform](https://github.com/gaearon/babel-plugin-react-transform) with [React Hot Loader](https://github.com/gaearon/react-hot-loader) v3, closes #588
- New `deployToGitHubPages` task which deploys static version to [GitHub Pages](https://pages.github.com/)
- Update `clean` task to ignore `build/.git` and `build/public/.git` folders during cleaning up
- Update `copy` task to process files in parallel, and watching for `added`, `renamed` and `removed` files
- Update `render` task to render pages in parallel, add info about render time in milliseconds
- Update `runServer` task to return promise when server is ready
- Update `start` task to prevent restarting the server in case of hot compilation errors which helps to avoid restarting the task manually
- Remove `react-transform-hmr`, `react-transform-catch-errors`, and `babel-plugin-react-transform` dependencies in favor of `react-hot-loader`
- Remove `tools/lib/fetch.js` with `http` dependency in favor of `node-fetch`
- Remove `del` dependency (wrapper for `rimraf`) in favor of `rimraf`
- Remove `ncp` dependency in favor of simple `copyFile` function, see `tools/lib/fs.js`
Diffstat:
Mpackage.json | 10++++------
Mtools/README.md | 4++--
Mtools/clean.js | 20+++++++++++++++-----
Mtools/copy.js | 63++++++++++++++++++++++++++++++++++++++-------------------------
Dtools/deploy.js | 57---------------------------------------------------------
Atools/deployToAzureWebApps.js | 60++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Atools/deployToGitHubPages.js | 46++++++++++++++++++++++++++++++++++++++++++++++
Dtools/lib/fetch.js | 14--------------
Mtools/lib/fs.js | 54+++++++++++++++++++++++++++++++++++++++++++++++++++---
Mtools/render.js | 47++++++++++++++++++++++++++++++++---------------
Mtools/run.js | 4++--
Mtools/runServer.js | 63+++++++++++++++++++++++++++++++++------------------------------
Mtools/start.js | 87++++++++++++++++++++++++-------------------------------------------------------
13 files changed, 309 insertions(+), 220 deletions(-)

diff --git a/package.json b/package.json @@ -45,7 +45,6 @@ "babel-core": "^6.14.0", "babel-eslint": "^6.1.2", "babel-loader": "^6.2.5", - "babel-plugin-react-transform": "^2.0.2", "babel-plugin-rewire": "^1.0.0", "babel-plugin-transform-react-constant-elements": "^6.9.1", "babel-plugin-transform-react-inline-elements": "^6.8.0", @@ -61,7 +60,6 @@ "browser-sync": "^2.16.0", "chai": "^3.5.0", "css-loader": "^0.25.0", - "del": "^2.2.2", "enzyme": "^2.4.1", "eslint": "^3.6.0", "eslint-config-airbnb": "^12.0.0", @@ -77,7 +75,6 @@ "json-loader": "^0.5.4", "mkdirp": "^0.5.1", "mocha": "^3.0.2", - "ncp": "^2.0.0", "pixrem": "^3.0.2", "pleeease-filters": "^3.0.0", "postcss": "^5.2.2", @@ -96,9 +93,9 @@ "postcss-selector-not": "^2.0.0", "raw-loader": "^0.5.1", "react-addons-test-utils": "15.3.2", - "react-transform-catch-errors": "^1.0.2", - "react-transform-hmr": "^1.0.4", + "react-hot-loader": "^3.0.0-beta.6", "redbox-react": "^1.3.1", + "rimraf": "^2.5.4", "sinon": "^2.0.0-pre.2", "stylelint": "^7.3.1", "stylelint-config-standard": "^13.0.2", @@ -173,8 +170,9 @@ "copy": "babel-node tools/run copy", "bundle": "babel-node tools/run bundle", "build": "babel-node tools/run build", - "deploy": "babel-node tools/run deploy", + "deploy": "babel-node tools/run deployToAzureWebApps", "render": "babel-node tools/run render", + "serve": "babel-node tools/run runServer", "start": "babel-node tools/run start" } } diff --git a/tools/README.md b/tools/README.md @@ -7,8 +7,8 @@ * Launches [Webpack](https://webpack.github.io/) compiler in a watch mode (via [webpack-middleware](https://github.com/kriasoft/webpack-middleware)) * Launches Node.js server from the compiled output folder (`runServer.js`) * Launches [Browsersync](https://browsersync.io/), - [HMR](https://webpack.github.io/docs/hot-module-replacement), and - [React Transform](https://github.com/gaearon/babel-plugin-react-transform) + [Hot Module Replacement](https://webpack.github.io/docs/hot-module-replacement), and + [React Hot Loader](https://github.com/gaearon/react-hot-loader) ##### `npm run build` (`build.js`) diff --git a/tools/clean.js b/tools/clean.js @@ -7,15 +7,25 @@ * LICENSE.txt file in the root directory of this source tree. */ -import del from 'del'; -import fs from './lib/fs'; +import { cleanDir } from './lib/fs'; /** * Cleans up the output (build) directory. */ -async function clean() { - await del(['.tmp', 'build/*', '!build/.git'], { dot: true }); - await fs.makeDir('build/public'); +function clean() { + return Promise.all([ + cleanDir('build/*', { + nosort: true, + dot: true, + ignore: ['build/.git', 'build/public'], + }), + + cleanDir('build/public/*', { + nosort: true, + dot: true, + ignore: ['build/public/.git'], + }), + ]); } export default clean; diff --git a/tools/copy.js b/tools/copy.js @@ -9,42 +9,55 @@ import path from 'path'; import gaze from 'gaze'; -import Promise from 'bluebird'; -import fs from './lib/fs'; +import { writeFile, copyFile, makeDir, copyDir, cleanDir } from './lib/fs'; import pkg from '../package.json'; + /** * Copies static files such as robots.txt, favicon.ico to the * output (build) folder. */ -async function copy({ watch } = {}) { - const ncp = Promise.promisify(require('ncp')); - +async function copy() { + await makeDir('build'); await Promise.all([ - ncp('src/public', 'build/public'), - ncp('src/content', 'build/content'), + writeFile('build/package.json', JSON.stringify({ + private: true, + engines: pkg.engines, + dependencies: pkg.dependencies, + scripts: { + start: 'node server.js', + }, + }, null, 2)), + copyFile('LICENSE.txt', 'build/LICENSE.txt'), + copyDir('src/content', 'build/content'), + copyDir('src/public', 'build/public'), ]); - await fs.writeFile('./build/package.json', JSON.stringify({ - private: true, - engines: pkg.engines, - dependencies: pkg.dependencies, - scripts: { - start: 'node server.js', - }, - }, null, 2)); - - if (watch) { + if (process.argv.includes('--watch')) { const watcher = await new Promise((resolve, reject) => { - gaze('src/content/**/*.*', (err, val) => (err ? reject(err) : resolve(val))); + gaze([ + 'src/content/**/*', + 'src/public/**/*', + ], (err, val) => (err ? reject(err) : resolve(val))); }); - const cp = async (file) => { - const relPath = file.substr(path.join(__dirname, '../src/content/').length); - await ncp(`src/content/${relPath}`, `build/content/${relPath}`); - }; - - watcher.on('changed', cp); - watcher.on('added', cp); + watcher.on('all', async (event, filePath) => { + const dist = path.join('build/', path.relative('src', filePath)); + switch (event) { + case 'added': + case 'renamed': + case 'changed': + if (filePath.endsWith('/')) return; + await makeDir(path.dirname(dist)); + await copyFile(filePath, dist); + break; + case 'deleted': + cleanDir(dist, { nosort: true, dot: true }); + break; + default: + return; + } + console.log(`[file ${event}] ${dist}`); + }); } } diff --git a/tools/deploy.js b/tools/deploy.js @@ -1,57 +0,0 @@ -/** - * React Starter Kit (https://www.reactstarterkit.com/) - * - * Copyright © 2014-2016 Kriasoft, LLC. All rights reserved. - * - * This source code is licensed under the MIT license found in the - * LICENSE.txt file in the root directory of this source tree. - */ - -import GitRepo from 'git-repository'; -import run from './run'; -import fetch from './lib/fetch'; - -// TODO: Update deployment URL -// For more information visit http://gitolite.com/deploy.html -const getRemote = (slot) => ({ - name: slot || 'production', - url: `https://example${slot ? `-${slot}` : ''}.scm.azurewebsites.net:443/example.git`, - website: `http://example${slot ? `-${slot}` : ''}.azurewebsites.net`, -}); - -/** - * Deploy the contents of the `/build` folder to a remote - * server via Git. Example: `npm run deploy -- production` - */ -async function deploy() { - // By default deploy to the staging deployment slot - const remote = getRemote(process.argv.includes('--production') ? null : 'staging'); - - // Initialize a new Git repository inside the `/build` folder - // if it doesn't exist yet - const repo = await GitRepo.open('build', { init: true }); - await repo.setRemote(remote.name, remote.url); - - // Fetch the remote repository if it exists - if ((await repo.hasRef(remote.url, 'master'))) { - await repo.fetch(remote.name); - await repo.reset(`${remote.name}/master`, { hard: true }); - await repo.clean({ force: true }); - } - - // Build the project in RELEASE mode which - // generates optimized and minimized bundles - process.argv.push('--release'); - await run(require('./build')); - - // Push the contents of the build folder to the remote server via Git - await repo.add('--all .'); - await repo.commit('Update'); - await repo.push(remote.name, 'master'); - - // Check if the site was successfully deployed - const response = await fetch(remote.website); - console.log(`${remote.website} -> ${response.statusCode}`); -} - -export default deploy; diff --git a/tools/deployToAzureWebApps.js b/tools/deployToAzureWebApps.js @@ -0,0 +1,60 @@ +/** + * React Starter Kit (https://www.reactstarterkit.com/) + * + * Copyright © 2014-2016 Kriasoft, LLC. All rights reserved. + * + * This source code is licensed under the MIT license found in the + * LICENSE.txt file in the root directory of this source tree. + */ + +import GitRepo from 'git-repository'; +import fetch from 'node-fetch'; +import run from './run'; + +// For more information visit http://gitolite.com/deploy.html +function getRemote(slot) { + return { + name: slot || 'production', + url: `https://example${slot ? `-${slot}` : ''}.scm.azurewebsites.net:443/example.git`, + branch: 'master', + website: `http://example${slot ? `-${slot}` : ''}.azurewebsites.net`, + }; +} + +/** + * Deploy the contents of the `/build` folder to a remote + * server via Git. Example: `npm run deploy -- production` + */ +async function deployToAzureWebApps() { + // By default deploy to the staging deployment slot + const remote = getRemote(process.argv.includes('--production') ? null : 'staging'); + + // Initialize a new Git repository inside the `/build` folder + // if it doesn't exist yet + const repo = await GitRepo.open('build', { init: true }); + await repo.setRemote(remote.name, remote.url); + + // Fetch the remote repository if it exists + const isRefExists = await repo.hasRef(remote.url, remote.branch); + if (isRefExists) { + await repo.fetch(remote.name); + await repo.reset(`${remote.name}/${remote.branch}`, { hard: true }); + await repo.clean({ force: true }); + } + + // Build the project in RELEASE mode which + // generates optimized and minimized bundles + process.argv.push('--release'); + await run(require('./build')); + + // Push the contents of the build folder to the remote server via Git + await repo.add('--all .'); + await repo.commit(`Update ${new Date().toISOString()}`); + await repo.push(remote.name, `master:${remote.branch}`); + + // Check if the site was successfully deployed + const response = await fetch(remote.website); + console.log(`${remote.website} -> ${response.status}`); +} + +export default deployToAzureWebApps; diff --git a/tools/deployToGitHubPages.js b/tools/deployToGitHubPages.js @@ -0,0 +1,46 @@ +/** + * React Starter Kit (https://www.reactstarterkit.com/) + * + * Copyright © 2014-2016 Kriasoft, LLC. All rights reserved. + * + * This source code is licensed under the MIT license found in the + * LICENSE.txt file in the root directory of this source tree. + */ + +import GitRepo from 'git-repository'; +import run from './run'; +import build from './build'; + +const remote = { + name: 'github', + url: 'https://github.com/{user}/{repo}.git', + branch: 'gh-pages', +}; + +/** + * Deploy the contents of the `/build/public` folder to GitHub Pages. + */ +async function deployToGitHubPages() { + // Initialize a new Git repository inside the `/build` folder + // if it doesn't exist yet + const repo = await GitRepo.open('build/public', { init: true }); + await repo.setRemote(remote.name, remote.url); + const isRefExists = await repo.hasRef(remote.url, remote.branch); + if (isRefExists) { + await repo.fetch(remote.name); + await repo.reset(`${remote.name}/${remote.branch}`, { hard: true }); + await repo.clean({ force: true }); + } + + // Build the project in RELEASE mode which + // generates optimized and minimized bundles + process.argv.push('--static', '--release'); + await run(build); + + // Push the contents of the build folder to the remote server via Git + await repo.add('--all .'); + await repo.commit(`Update ${new Date().toISOString()}`); + await repo.push(remote.name, `master:${remote.branch}`); +} + +export default deployToGitHubPages; diff --git a/tools/lib/fetch.js b/tools/lib/fetch.js @@ -1,14 +0,0 @@ -/** - * React Starter Kit (https://www.reactstarterkit.com/) - * - * Copyright © 2014-2016 Kriasoft, LLC. All rights reserved. - * - * This source code is licensed under the MIT license found in the - * LICENSE.txt file in the root directory of this source tree. - */ - -import http from 'http'; - -export default async (url) => new Promise((resolve, reject) => - http.get(url, res => resolve(res)).on('error', err => reject(err)) -); diff --git a/tools/lib/fs.js b/tools/lib/fs.js @@ -8,14 +8,62 @@ */ import fs from 'fs'; +import path from 'path'; +import glob from 'glob'; import mkdirp from 'mkdirp'; +import rimraf from 'rimraf'; -const writeFile = (file, contents) => new Promise((resolve, reject) => { +export const readFile = (file) => new Promise((resolve, reject) => { + fs.readFile(file, 'utf8', (err, data) => (err ? reject(err) : resolve(data))); +}); + +export const writeFile = (file, contents) => new Promise((resolve, reject) => { fs.writeFile(file, contents, 'utf8', err => (err ? reject(err) : resolve())); }); -const makeDir = (name) => new Promise((resolve, reject) => { +export const copyFile = (source, target) => new Promise((resolve, reject) => { + let cbCalled = false; + function done(err) { + if (!cbCalled) { + cbCalled = true; + if (err) { + reject(err); + } else { + resolve(); + } + } + } + + const rd = fs.createReadStream(source); + rd.on('error', err => done(err)); + const wr = fs.createWriteStream(target); + wr.on('error', err => done(err)); + wr.on('close', err => done(err)); + rd.pipe(wr); +}); + +export const readDir = (pattern, options) => new Promise((resolve, reject) => + glob(pattern, options, (err, result) => (err ? reject(err) : resolve(result))) +); + +export const makeDir = (name) => new Promise((resolve, reject) => { mkdirp(name, err => (err ? reject(err) : resolve())); }); -export default { writeFile, makeDir }; +export const copyDir = async (source, target) => { + const dirs = await readDir('**/*.*', { + cwd: source, + nosort: true, + dot: true, + }); + await Promise.all(dirs.map(async dir => { + const from = path.resolve(source, dir); + const to = path.resolve(target, dir); + await makeDir(path.dirname(to)); + await copyFile(from, to); + })); +}; + +export const cleanDir = (pattern, options) => new Promise((resolve, reject) => + rimraf(pattern, { glob: options }, (err, result) => (err ? reject(err) : resolve(result))) +); diff --git a/tools/render.js b/tools/render.js @@ -7,12 +7,20 @@ * LICENSE.txt file in the root directory of this source tree. */ +import path from 'path'; import fetch from 'node-fetch'; +import { writeFile, makeDir } from './lib/fs'; import runServer from './runServer'; -import fs from './lib/fs'; -import { host } from '../src/config'; // Enter your paths here which you want to render as static +// Example: +// const routes = [ +// '/', // => build/public/index.html +// '/page', // => build/public/page.html +// '/page/', // => build/public/page/index.html +// '/page/name', // => build/public/page/name.html +// '/page/name/', // => build/public/page/name/index.html +// ]; const routes = [ '/', '/contact', @@ -24,20 +32,29 @@ const routes = [ ]; async function render() { - let server; - await new Promise(resolve => (server = runServer(resolve))); + const server = await runServer(); - await routes.reduce((promise, route) => promise.then(async () => { - const url = `http://${host}${route}`; - const dir = `build/public${route.replace(/[^\/]*$/, '')}`; - const name = route.endsWith('/') ? 'index.html' : `${route.match(/[^/]+$/)[0]}.html`; - const dist = `${dir}${name}`; - const res = await fetch(url); - const text = await res.text(); - await fs.makeDir(dir); - await fs.writeFile(dist, text); - console.log(`${dist} => ${res.status} ${res.statusText}`); - }), Promise.resolve()); + // add dynamic routes + // const products = await fetch(`http://${server.host}/api/products`).then(res => res.json()); + // products.forEach(product => routes.push( + // `/product/${product.uri}`, + // `/product/${product.uri}/specs` + // )); + + await Promise.all(routes.map(async (route, index) => { + const url = `http://${server.host}${route}`; + const fileName = route.endsWith('/') ? 'index.html' : `${path.basename(route, '.html')}.html`; + const dirName = path.join('build/public', route.endsWith('/') ? route : path.dirname(route)); + const dist = `${dirName}${fileName}`; + const timeStart = new Date(); + const response = await fetch(url); + const timeEnd = new Date(); + const text = await response.text(); + await makeDir(dirName); + await writeFile(dist, text); + const time = timeEnd.getTime() - timeStart.getTime(); + console.log(`#${index + 1} ${dist} => ${response.status} ${response.statusText} (${time} ms)`); + })); server.kill('SIGTERM'); } diff --git a/tools/run.js b/tools/run.js @@ -15,13 +15,13 @@ function run(fn, options) { const task = typeof fn.default === 'undefined' ? fn : fn.default; const start = new Date(); console.log( - `[${format(start)}] Starting '${task.name}${options ? `(${options})` : ''}'...` + `[${format(start)}] Starting '${task.name}${options ? ` (${options})` : ''}'...` ); return task(options).then(resolution => { const end = new Date(); const time = end.getTime() - start.getTime(); console.log( - `[${format(end)}] Finished '${task.name}${options ? `(${options})` : ''}' after ${time} ms` + `[${format(end)}] Finished '${task.name}${options ? ` (${options})` : ''}' after ${time} ms` ); return resolution; }); diff --git a/tools/runServer.js b/tools/runServer.js @@ -19,45 +19,48 @@ const { output } = webpackConfig.find(x => x.target === 'node'); const serverPath = path.join(output.path, output.filename); // Launch or restart the Node.js server -function runServer(cb) { - let cbIsPending = !!cb; +function runServer() { + return new Promise(resolve => { + let pending = true; - function onStdOut(data) { - const time = new Date().toTimeString(); - const match = data.toString('utf8').match(RUNNING_REGEXP); + function onStdOut(data) { + const time = new Date().toTimeString(); + const match = data.toString('utf8').match(RUNNING_REGEXP); - process.stdout.write(time.replace(/.*(\d{2}:\d{2}:\d{2}).*/, '[$1] ')); - process.stdout.write(data); + process.stdout.write(time.replace(/.*(\d{2}:\d{2}:\d{2}).*/, '[$1] ')); + process.stdout.write(data); - if (match) { - server.stdout.removeListener('data', onStdOut); - server.stdout.on('data', x => process.stdout.write(x)); - if (cb) { - cbIsPending = false; - cb(null, match[1]); + if (match) { + server.host = match[1]; + server.stdout.removeListener('data', onStdOut); + server.stdout.on('data', x => process.stdout.write(x)); + pending = false; + resolve(server); } } - } - if (server) { - server.kill('SIGTERM'); - } + if (server) { + server.kill('SIGTERM'); + } - server = cp.spawn('node', [serverPath], { - env: Object.assign({ NODE_ENV: 'development' }, process.env), - silent: false, - }); - if (cbIsPending) { - server.once('exit', (code, signal) => { - if (cbIsPending) { - throw new Error(`Server terminated unexpectedly with code: ${code} signal: ${signal}`); - } + server = cp.spawn('node', [serverPath], { + env: Object.assign({ NODE_ENV: 'development' }, process.env), + silent: false, }); - } - server.stdout.on('data', onStdOut); - server.stderr.on('data', x => process.stderr.write(x)); - return server; + if (pending) { + server.once('exit', (code, signal) => { + if (pending) { + throw new Error(`Server terminated unexpectedly with code: ${code} signal: ${signal}`); + } + }); + } + + server.stdout.on('data', onStdOut); + server.stderr.on('data', x => process.stderr.write(x)); + + return server; + }); } process.on('exit', () => { diff --git a/tools/start.js b/tools/start.js @@ -7,7 +7,7 @@ * LICENSE.txt file in the root directory of this source tree. */ -import Browsersync from 'browser-sync'; +import browserSync from 'browser-sync'; import webpack from 'webpack'; import webpackMiddleware from 'webpack-middleware'; import webpackHotMiddleware from 'webpack-hot-middleware'; @@ -17,7 +17,8 @@ import webpackConfig from './webpack.config'; import clean from './clean'; import copy from './copy'; -const DEBUG = !process.argv.includes('--release'); +process.argv.push('--watch'); +const [config] = webpackConfig; /** * Launches a development web server with "live reload" functionality - @@ -25,86 +26,50 @@ const DEBUG = !process.argv.includes('--release'); */ async function start() { await run(clean); - await run(copy.bind(undefined, { watch: true })); + await run(copy); await new Promise(resolve => { - // Patch the client-side bundle configurations - // to enable Hot Module Replacement (HMR) and React Transform - webpackConfig.filter(x => x.target !== 'node').forEach(config => { - /* eslint-disable no-param-reassign */ - config.entry = ['webpack-hot-middleware/client'].concat(config.entry); + // Hot Module Replacement (HMR) + React Hot Reload + if (config.debug) { + config.entry = ['react-hot-loader/patch', 'webpack-hot-middleware/client', config.entry]; config.output.filename = config.output.filename.replace('[chunkhash]', '[hash]'); config.output.chunkFilename = config.output.chunkFilename.replace('[chunkhash]', '[hash]'); + config.module.loaders.find(x => x.loader === 'babel-loader') + .query.plugins.unshift('react-hot-loader/babel'); config.plugins.push(new webpack.HotModuleReplacementPlugin()); config.plugins.push(new webpack.NoErrorsPlugin()); - config - .module - .loaders - .filter(x => x.loader === 'babel-loader') - .forEach(x => (x.query = { - ...x.query, - - // Wraps all React components into arbitrary transforms - // https://github.com/gaearon/babel-plugin-react-transform - plugins: [ - ...(x.query ? x.query.plugins : []), - ['react-transform', { - transforms: [ - { - transform: 'react-transform-hmr', - imports: ['react'], - locals: ['module'], - }, { - transform: 'react-transform-catch-errors', - imports: ['react', 'redbox-react'], - }, - ], - }, - ], - ], - })); - /* eslint-enable no-param-reassign */ - }); + } const bundler = webpack(webpackConfig); const wpMiddleware = webpackMiddleware(bundler, { - // IMPORTANT: webpack middleware can't access config, // so we should provide publicPath by ourselves - publicPath: webpackConfig[0].output.publicPath, + publicPath: config.output.publicPath, // Pretty colored output - stats: webpackConfig[0].stats, + stats: config.stats, // For other settings see // https://webpack.github.io/docs/webpack-dev-middleware }); - const hotMiddlewares = bundler - .compilers - .filter(compiler => compiler.options.target !== 'node') - .map(compiler => webpackHotMiddleware(compiler)); + const hotMiddleware = webpackHotMiddleware(bundler.compilers[0]); + + let handleBundleComplete = async () => { + handleBundleComplete = stats => !stats.stats[1].compilation.errors.length && runServer(); - let handleServerBundleComplete = () => { - runServer((err, host) => { - if (!err) { - const bs = Browsersync.create(); - bs.init({ - ...(DEBUG ? {} : { notify: false, ui: false }), + const server = await runServer(); + const bs = browserSync.create(); - proxy: { - target: host, - middleware: [wpMiddleware, ...hotMiddlewares], - }, + bs.init({ + ...(config.debug ? {} : { notify: false, ui: false }), - // no need to watch '*.js' here, webpack will take care of it for us, - // including full page reloads if HMR won't work - files: ['build/content/**/*.*'], - }, resolve); - handleServerBundleComplete = runServer; - } - }); + proxy: { + target: server.host, + middleware: [wpMiddleware, hotMiddleware], + }, + }, resolve); }; - bundler.plugin('done', () => handleServerBundleComplete()); + bundler.plugin('done', stats => handleBundleComplete(stats)); }); }