diff --git a/README.md b/README.md index ac5f3db31..7f856a8f9 100644 --- a/README.md +++ b/README.md @@ -351,19 +351,19 @@ Examples of use with other servers will follow here. Fastify interop will require the use of `fastify-express` instead of `middie` for providing middleware support. As the authors of `fastify-express` recommend, this should only be used as a stopgap while full Fastify support is worked on. ```js -const fastify = require('fastify')() -const webpack = require('webpack') -const webpackConfig = require('./webpack.config.js') -const devMiddleware = require('webpack-dev-middleware') - -const compiler = webpack(webpackConfig) -const { publicPath } = webpackConfig.output - -;(async () => { - await fastify.register(require('fastify-express')) - await fastify.use(devMiddleware(compiler, { publicPath })) - await fastify.listen(3000) -})() +const fastify = require('fastify')(); +const webpack = require('webpack'); +const webpackConfig = require('./webpack.config.js'); +const devMiddleware = require('webpack-dev-middleware'); + +const compiler = webpack(webpackConfig); +const { publicPath } = webpackConfig.output; + +(async () => { + await fastify.register(require('fastify-express')); + await fastify.use(devMiddleware(compiler, { publicPath })); + await fastify.listen(3000); +})(); ``` ## Contributing diff --git a/package-lock.json b/package-lock.json index fb5386dfa..59aee6a68 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5418,9 +5418,9 @@ } }, "enhanced-resolve": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.2.0.tgz", - "integrity": "sha512-NZlGLl8DxmZoq0uqPPtJfsCAir68uR047+Udsh1FH4+5ydGQdMurn/A430A1BtxASVmMEuS7/XiJ5OxJ9apAzQ==", + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.3.0.tgz", + "integrity": "sha512-EENz3E701+77g0wfbOITeI8WLPNso2kQNMBIBEi/TH/BEa9YXtS01X7sIEk5XXsfFq1jNkhIpu08hBPH1TRLIQ==", "dev": true, "requires": { "graceful-fs": "^4.2.4", @@ -12470,9 +12470,9 @@ } }, "terser": { - "version": "5.3.5", - "resolved": "https://registry.npmjs.org/terser/-/terser-5.3.5.tgz", - "integrity": "sha512-Qw3CZAMmmfU824AoGKalx+riwocSI5Cs0PoGp9RdSLfmxkmJgyBxqLBP/isDNtFyhHnitikvRMZzyVgeq+U+Tg==", + "version": "5.3.7", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.3.7.tgz", + "integrity": "sha512-lJbKdfxWvjpV330U4PBZStCT9h3N9A4zZVA5Y4k9sCWXknrpdyxi1oMsRKLmQ/YDMDxSBKIh88v0SkdhdqX06w==", "dev": true, "requires": { "commander": "^2.20.0", diff --git a/src/index.js b/src/index.js index f0436bbc1..e5edae42b 100644 --- a/src/index.js +++ b/src/index.js @@ -48,51 +48,46 @@ export default function wdm(compiler, options = {}) { setupOutputFileSystem(context); - let watchOptions; - - if (Array.isArray(context.compiler.compilers)) { - watchOptions = context.compiler.compilers.map( - (childCompiler) => childCompiler.options.watchOptions || {} - ); - } else { - watchOptions = context.compiler.options.watchOptions || {}; - } - // Start watching - context.watching = context.compiler.watch(watchOptions, (error) => { - if (error) { - // TODO: improve that in future - // For example - `writeToDisk` can throw an error and right now it is ends watching. - // We can improve that and keep watching active, but it is require API on webpack side. - // Let's implement that in webpack@5 because it is rare case. - context.logger.error(error); + if (context.compiler.watching) { + context.watching = context.compiler.watching; + } else { + let watchOptions; + + if (Array.isArray(context.compiler.compilers)) { + watchOptions = context.compiler.compilers.map( + (childCompiler) => childCompiler.options.watchOptions || {} + ); + } else { + watchOptions = context.compiler.options.watchOptions || {}; } - }); - - return Object.assign(middleware(context), { - waitUntilValid(callback) { - // eslint-disable-next-line no-param-reassign - callback = callback || noop; - ready(context, callback); - }, - - invalidate(callback) { - // eslint-disable-next-line no-param-reassign - callback = callback || noop; - - ready(context, callback); + context.watching = context.compiler.watch(watchOptions, (error) => { + if (error) { + // TODO: improve that in future + // For example - `writeToDisk` can throw an error and right now it is ends watching. + // We can improve that and keep watching active, but it is require API on webpack side. + // Let's implement that in webpack@5 because it is rare case. + context.logger.error(error); + } + }); + } - context.watching.invalidate(); - }, + const instance = middleware(context); - close(callback) { - // eslint-disable-next-line no-param-reassign - callback = callback || noop; + // API + instance.waitUntilValid = (callback = noop) => { + ready(context, callback); + }; + instance.invalidate = (callback = noop) => { + ready(context, callback); - context.watching.close(callback); - }, + context.watching.invalidate(); + }; + instance.close = (callback = noop) => { + context.watching.close(callback); + }; + instance.context = context; - context, - }); + return instance; } diff --git a/test/api.test.js b/test/api.test.js index 8e9b3bf46..169665979 100644 --- a/test/api.test.js +++ b/test/api.test.js @@ -1,5 +1,5 @@ import express from 'express'; -import { Stats } from 'webpack'; +import webpack, { Stats } from 'webpack'; import middleware from '../src'; @@ -13,36 +13,163 @@ describe('API', () => { let app; let compiler; - beforeEach((done) => { - compiler = getCompiler(webpackConfig); + describe('constructor', () => { + describe('should accept compiler', () => { + beforeEach((done) => { + compiler = webpack(webpackConfig); - instance = middleware(compiler); + instance = middleware(compiler); - app = express(); - app.use(instance); + app = express(); + app.use(instance); - listen = app.listen((error) => { - if (error) { - return done(error); - } + listen = app.listen((error) => { + if (error) { + return done(error); + } + + return done(); + }); + }); + + afterEach((done) => { + if (instance.context.watching.closed) { + if (listen) { + listen.close(done); + } else { + done(); + } + + return; + } + + instance.close(() => { + if (listen) { + listen.close(done); + } else { + done(); + } + }); + }); + + it('should work', (done) => { + const doneSpy = jest.spyOn(getCompilerHooks(compiler).done[0], 'fn'); + + instance.waitUntilValid(() => { + instance.close(); - return done(); + expect(compiler.running).toBe(false); + expect(doneSpy).toHaveBeenCalledTimes(1); + + doneSpy.mockRestore(); + + done(); + }); + }); }); - }); - afterEach((done) => { - if (instance) { - instance.close(); - } + if (webpack.version[0] === 5) { + describe('should accept compiler in watch mode', () => { + beforeEach((done) => { + compiler = webpack( + { ...webpackConfig, ...{ watch: true } }, + (error) => { + if (error) { + throw error; + } + } + ); + + instance = middleware(compiler); + + app = express(); + app.use(instance); + + listen = app.listen((error) => { + if (error) { + return done(error); + } + + return done(); + }); + }); + + afterEach((done) => { + if (instance.context.watching.closed) { + if (listen) { + listen.close(done); + } else { + done(); + } + + return; + } + + instance.close(() => { + if (listen) { + listen.close(done); + } else { + done(); + } + }); + }); - if (listen) { - listen.close(done); - } else { - done(); + it('should work', (done) => { + const doneSpy = jest.spyOn(getCompilerHooks(compiler).done[0], 'fn'); + + instance.waitUntilValid(() => { + instance.close(); + + expect(compiler.running).toBe(false); + expect(doneSpy).toHaveBeenCalledTimes(1); + + doneSpy.mockRestore(); + + done(); + }); + }); + }); } }); describe('waitUntilValid method', () => { + beforeEach((done) => { + compiler = getCompiler(webpackConfig); + + instance = middleware(compiler); + + app = express(); + app.use(instance); + + listen = app.listen((error) => { + if (error) { + return done(error); + } + + return done(); + }); + }); + + afterEach((done) => { + if (instance.context.watching.closed) { + if (listen) { + listen.close(done); + } else { + done(); + } + + return; + } + + instance.close(() => { + if (listen) { + listen.close(done); + } else { + done(); + } + }); + }); + it('should work without callback', (done) => { const doneSpy = jest.spyOn(getCompilerHooks(compiler).done[0], 'fn'); @@ -120,6 +247,43 @@ describe('API', () => { }); describe('invalidate method', () => { + beforeEach((done) => { + compiler = getCompiler(webpackConfig); + + instance = middleware(compiler); + + app = express(); + app.use(instance); + + listen = app.listen((error) => { + if (error) { + return done(error); + } + + return done(); + }); + }); + + afterEach((done) => { + if (instance.context.watching.closed) { + if (listen) { + listen.close(done); + } else { + done(); + } + + return; + } + + instance.close(() => { + if (listen) { + listen.close(done); + } else { + done(); + } + }); + }); + it('should work without callback', (done) => { const doneSpy = jest.spyOn(getCompilerHooks(compiler).done[0], 'fn'); @@ -166,6 +330,43 @@ describe('API', () => { }); describe('close method', () => { + beforeEach((done) => { + compiler = getCompiler(webpackConfig); + + instance = middleware(compiler); + + app = express(); + app.use(instance); + + listen = app.listen((error) => { + if (error) { + return done(error); + } + + return done(); + }); + }); + + afterEach((done) => { + if (instance.context.watching.closed) { + if (listen) { + listen.close(done); + } else { + done(); + } + + return; + } + + instance.close(() => { + if (listen) { + listen.close(done); + } else { + done(); + } + }); + }); + it('should work without callback', (done) => { const doneSpy = jest.spyOn(getCompilerHooks(compiler).done[0], 'fn'); @@ -198,6 +399,43 @@ describe('API', () => { }); describe('context property', () => { + beforeEach((done) => { + compiler = getCompiler(webpackConfig); + + instance = middleware(compiler); + + app = express(); + app.use(instance); + + listen = app.listen((error) => { + if (error) { + return done(error); + } + + return done(); + }); + }); + + afterEach((done) => { + if (instance.context.watching.closed) { + if (listen) { + listen.close(done); + } else { + done(); + } + + return; + } + + instance.close(() => { + if (listen) { + listen.close(done); + } else { + done(); + } + }); + }); + it('should contain public properties', (done) => { expect(instance.context.state).toBeDefined(); expect(instance.context.options).toBeDefined();