diff --git a/.travis.yml b/.travis.yml index 7378d9e..f46d487 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,6 +1,6 @@ language: node_js node_js: - - '6' - - '8' + - 10 + - 12 + - 14 - 'node' - diff --git a/LICENSE-MIT b/LICENSE-MIT index 0733a63..bf5d57b 100644 --- a/LICENSE-MIT +++ b/LICENSE-MIT @@ -1,4 +1,4 @@ -Copyright (c) 2018 Kyle Robinson Young +Copyright (c) 2021 Kyle Robinson Young Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation diff --git a/gulpfile.js b/gulpfile.js index 1165cd8..2a61fe7 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -1,13 +1,14 @@ -var gulp = require('gulp'); -var webpack = require('./'); -var rimraf = require('rimraf'); -var named = require('vinyl-named'); +const gulp = require('gulp'); +const webpack = require('./'); +const rimraf = require('rimraf'); +const named = require('vinyl-named'); gulp.task('default', function () { rimraf.sync('tmp'); return gulp.src(['test/fixtures/entry.js', 'test/fixtures/anotherentrypoint.js']) .pipe(named()) .pipe(webpack({ + mode: 'production', devtool: 'source-map' })) .pipe(gulp.dest('tmp/')); diff --git a/index.js b/index.js index 5f092d6..f4b63bf 100644 --- a/index.js +++ b/index.js @@ -1,16 +1,16 @@ 'use strict'; -var fancyLog = require('fancy-log'); -var PluginError = require('plugin-error'); -var supportsColor = require('supports-color'); -var File = require('vinyl'); -var MemoryFileSystem = require('memory-fs'); -var nodePath = require('path'); -var through = require('through'); -var ProgressPlugin = require('webpack/lib/ProgressPlugin'); -var clone = require('lodash.clone'); - -var defaultStatsOptions = { +const fancyLog = require('fancy-log'); +const PluginError = require('plugin-error'); +const supportsColor = require('supports-color'); +const File = require('vinyl'); +const MemoryFileSystem = require('memory-fs'); +const nodePath = require('path'); +const through = require('through'); +const ProgressPlugin = require('webpack/lib/ProgressPlugin'); +const clone = require('lodash.clone'); + +const defaultStatsOptions = { colors: supportsColor.stdout.hasBasic, hash: false, timings: false, @@ -26,32 +26,42 @@ var defaultStatsOptions = { errorDetails: false }; -var cache = {}; - module.exports = function (options, wp, done) { - if (cache.wp !== wp || cache.options !== options) { - cache = {}; + const cache = { + options: options, + wp: wp + }; + + options = clone(options) || {}; + const config = options.config || options; + + const isInWatchMode = !!options.watch; + delete options.watch; + + if (typeof config === 'string') { + config = require(config); } - cache.options = options; - cache.wp = wp; + // Webpack 4 doesn't support the `quiet` attribute, however supports + // setting `stats` to a string within an array of configurations + // (errors-only|minimal|none|normal|verbose) or an object with an absurd + // amount of config + const isSilent = options.quiet || (typeof options.stats === 'string' && (options.stats.match(/^(errors-only|minimal|none)$/))); - options = clone(options) || {}; - var config = options.config || options; if (typeof done !== 'function') { - var callingDone = false; + let callingDone = false; done = function (err, stats) { if (err) { // The err is here just to match the API but isnt used return; } stats = stats || {}; - if (options.quiet || callingDone) { + if (isSilent || callingDone) { return; } // Debounce output a little for when in watch mode - if (options.watch) { + if (isInWatchMode) { callingDone = true; setTimeout(function () { callingDone = false; @@ -63,7 +73,7 @@ module.exports = function (options, wp, done) { colors: supportsColor.stdout.hasBasic })); } else { - var statsOptions = (options && options.stats) || {}; + const statsOptions = (options && options.stats) || {}; if (typeof statsOptions === 'object') { Object.keys(defaultStatsOptions).forEach(function (key) { @@ -72,7 +82,7 @@ module.exports = function (options, wp, done) { } }); } - var statusLog = stats.toString(statsOptions); + const statusLog = stats.toString(statsOptions); if (statusLog) { fancyLog(statusLog); } @@ -80,11 +90,11 @@ module.exports = function (options, wp, done) { }; } - var webpack = wp || require('webpack'); - var entry = []; - var entries = Object.create(null); + const webpack = wp || require('webpack'); + let entry = []; + const entries = Object.create(null); - var stream = through(function (file) { + const stream = through(function (file) { if (file.isNull()) { return; } @@ -98,10 +108,9 @@ module.exports = function (options, wp, done) { entry.push(file.path); } }, function () { - var self = this; - var handleConfig = function (config) { + const self = this; + const handleConfig = function (config) { config.output = config.output || {}; - config.watch = !!options.watch; // Determine pipe'd in entry if (Object.keys(entries).length > 0) { @@ -127,9 +136,9 @@ module.exports = function (options, wp, done) { return true; }; - var succeeded; + let succeeded; if (Array.isArray(config)) { - for (var i = 0; i < config.length; i++) { + for (let i = 0; i < config.length; i++) { succeeded = handleConfig(config[i]); if (!succeeded) { return false; @@ -143,7 +152,7 @@ module.exports = function (options, wp, done) { } // Cache compiler for future use - var compiler = cache.compiler || webpack(config); + const compiler = cache.compiler || webpack(config); cache.compiler = compiler; const callback = function (err, stats) { @@ -151,33 +160,54 @@ module.exports = function (options, wp, done) { self.emit('error', new PluginError('webpack-stream', err)); return; } - var jsonStats = stats ? stats.toJson() || {} : {}; - var errors = jsonStats.errors || []; + const jsonStats = stats ? stats.toJson() || {} : {}; + const errors = jsonStats.errors || []; if (errors.length) { - var errorMessage = errors.join('\n'); - var compilationError = new PluginError('webpack-stream', errorMessage); - if (!options.watch) { + const resolveErrorMessage = (err) => { + if ( + typeof err === 'object' && + err !== null && + Object.prototype.hasOwnProperty.call(err, 'message') + ) { + return err.message; + } else if ( + typeof err === 'object' && + err !== null && + 'toString' in err && + err.toString() !== '[object Object]' + ) { + return err.toString(); + } else if (Array.isArray(err)) { + return err.map(resolveErrorMessage).join('\n'); + } else { + return err; + } + }; + + const errorMessage = errors.map(resolveErrorMessage).join('\n'); + const compilationError = new PluginError('webpack-stream', errorMessage); + if (!isInWatchMode) { self.emit('error', compilationError); } self.emit('compilation-error', compilationError); } - if (!options.watch) { + if (!isInWatchMode) { self.queue(null); } done(err, stats); - if (options.watch && !options.quiet) { + if (isInWatchMode && !isSilent) { fancyLog('webpack is watching for changes'); } }; - if (options.watch) { - const watchOptions = {}; + if (isInWatchMode) { + const watchOptions = options.watchOptions || {}; compiler.watch(watchOptions, callback); } else { compiler.run(callback); } - var handleCompiler = function (compiler) { + const handleCompiler = function (compiler) { if (options.progress) { (new ProgressPlugin(function (percentage, msg) { percentage = Math.floor(percentage * 100); @@ -189,21 +219,17 @@ module.exports = function (options, wp, done) { cache.mfs = cache.mfs || new MemoryFileSystem(); - var fs = compiler.outputFileSystem = cache.mfs; + const fs = compiler.outputFileSystem = cache.mfs; - var afterEmitPlugin = compiler.hooks + const assetEmittedPlugin = compiler.hooks // Webpack 4 - ? function (callback) { compiler.hooks.afterEmit.tapAsync('WebpackStream', callback); } + ? function (callback) { compiler.hooks.assetEmitted.tapAsync('WebpackStream', callback); } // Webpack 2/3 - : function (callback) { compiler.plugin('after-emit', callback); }; + : function (callback) { compiler.plugin('asset-emitted', callback); }; - afterEmitPlugin(function (compilation, callback) { - Object.keys(compilation.assets).forEach(function (outname) { - if (compilation.assets[outname].emitted) { - var file = prepareFile(fs, compiler, outname); - self.queue(file); - } - }); + assetEmittedPlugin(function (outname, _, callback) { + const file = prepareFile(fs, compiler, outname); + self.queue(file); callback(); }); }; @@ -215,10 +241,23 @@ module.exports = function (options, wp, done) { } else { handleCompiler(compiler); } + + if (options.watch && !isSilent) { + const watchRunPlugin = compiler.hooks + // Webpack 4 + ? callback => compiler.hooks.watchRun.tapAsync('WebpackInfo', callback) + // Webpack 2/3 + : callback => compiler.plugin('watch-run', callback); + + watchRunPlugin((compilation, callback) => { + fancyLog('webpack compilation starting...'); + callback(); + }); + } }); // If entry point manually specified, trigger that - var hasEntry = Array.isArray(config) + const hasEntry = Array.isArray(config) ? config.some(function (c) { return c.entry; }) : config.entry; if (hasEntry) { @@ -229,14 +268,14 @@ module.exports = function (options, wp, done) { }; function prepareFile (fs, compiler, outname) { - var path = fs.join(compiler.outputPath, outname); + let path = fs.join(compiler.outputPath, outname); if (path.indexOf('?') !== -1) { path = path.split('?')[0]; } - var contents = fs.readFileSync(path); + const contents = fs.readFileSync(path); - var file = new File({ + const file = new File({ base: compiler.outputPath, path: nodePath.join(compiler.outputPath, outname), contents: contents diff --git a/package.json b/package.json index 4e41e7a..c02d775 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "webpack-stream", - "version": "5.1.1", + "version": "6.1.2", "description": "Run webpack as a stream", "license": "MIT", "homepage": "https://github.com/shama/webpack-stream", @@ -11,33 +11,47 @@ "url": "http://dontkry.com" }, "engines": { - "node": ">= 6.11.5" + "node": ">= 10.0.0" }, "scripts": { + "start": "gulp", + "debug": "node --inspect-brk ./node_modules/.bin/gulp", "test": "semistandard && node test/test.js" }, - "files": ["index.js"], + "files": [ + "index.js" + ], "semistandard": { - "ignore": ["test/fixtures", "examples"] + "ignore": [ + "test/fixtures", + "examples" + ] }, "dependencies": { - "fancy-log": "^1.3.2", + "fancy-log": "^1.3.3", "lodash.clone": "^4.3.2", "lodash.some": "^4.2.2", - "memory-fs": "^0.4.1", + "memory-fs": "^0.5.0", "plugin-error": "^1.0.1", - "supports-color": "^5.3.0", + "supports-color": "^8.1.1", "through": "^2.3.8", - "vinyl": "^2.1.0", - "webpack": "^4.7.0" + "vinyl": "^2.2.1" }, "devDependencies": { - "gulp": "^3.9.0", - "rimraf": "^2.4.4", - "semistandard": "^12.0.1", - "tape": "^4.9.0", + "gulp": "^4.0.2", + "rimraf": "^3.0.2", + "semistandard": "^16.0.1", + "tape": "^5.3.1", "vinyl-fs": "^3.0.2", - "vinyl-named": "^1.1.0" + "vinyl-named": "^1.1.0", + "webpack": "^5.21.2" }, - "keywords": ["gulpplugin", "webpack", "stream"] + "peerDependencies": { + "webpack": "^5.21.2" + }, + "keywords": [ + "gulpplugin", + "webpack", + "stream" + ] } diff --git a/readme.md b/readme.md index 28801ab..bdaeca9 100644 --- a/readme.md +++ b/readme.md @@ -83,9 +83,9 @@ To use gulp `watch`, it's required that you explicitly pass webpack in the 2nd a Please note that gulp `watch` and webpack `watch` are mutually exclusive. ```javascript -var gulp = require('gulp'); -var compiler = require('webpack'); -var webpack = require('webpack-stream'); +const gulp = require('gulp'); +const compiler = require('webpack'); +const webpack = require('webpack-stream'); gulp.task('build', function() { return gulp.src('src/entry.js') @@ -235,5 +235,5 @@ gulp.task('default', function() { * 0.1.0 - Initial release ## License -Copyright (c) 2018 Kyle Robinson Young +Copyright (c) 2021 Kyle Robinson Young Licensed under the MIT license. diff --git a/test/fake-error-loader.js b/test/fake-error-loader.js index 676bb89..2a5177e 100644 --- a/test/fake-error-loader.js +++ b/test/fake-error-loader.js @@ -1,10 +1,10 @@ -module.exports = function fakeErrorLoader (text) { - this.cacheable(); - - var fakeError = new Error('Fake error'); - // delete stack trace prevent it from showing up in webpack output - delete fakeError.stack; - this.emitError(fakeError); - - return text; -}; +module.exports = function fakeErrorLoader (text) { + this.cacheable(); + + const fakeError = new Error('Fake error'); + // delete stack trace prevent it from showing up in webpack output + delete fakeError.stack; + this.emitError(fakeError); + + return text; +}; diff --git a/test/fixtures/webpack.config.js b/test/fixtures/webpack.config.js new file mode 100644 index 0000000..9be46b3 --- /dev/null +++ b/test/fixtures/webpack.config.js @@ -0,0 +1,7 @@ +// Test config file for 'option file path' test. +const config = { + input: './entry.js' + // input: './nope.js' +}; + +module.exports = config; diff --git a/test/test.js b/test/test.js index 61feaca..bd54122 100644 --- a/test/test.js +++ b/test/test.js @@ -1,15 +1,15 @@ -var test = require('tape'); -var webpack = require('../'); -var path = require('path'); -var fs = require('vinyl-fs'); -var named = require('vinyl-named'); +const test = require('tape'); +const webpack = require('../'); +const path = require('path'); +const fs = require('vinyl-fs'); +const named = require('vinyl-named'); -var base = path.resolve(__dirname, 'fixtures'); +const base = path.resolve(__dirname, 'fixtures'); test('streams output assets', function (t) { t.plan(3); - var entry = fs.src('test/fixtures/entry.js'); - var stream = webpack({ + const entry = fs.src('test/fixtures/entry.js'); + const stream = webpack({ config: { mode: 'development', output: { @@ -19,14 +19,14 @@ test('streams output assets', function (t) { quiet: true }); stream.on('data', function (file) { - var basename = path.basename(file.path); - var contents = file.contents.toString(); + const basename = path.basename(file.path); + const contents = file.contents.toString(); switch (basename) { case 'bundle.js': t.ok(/__webpack_require__/i.test(contents), 'should contain "__webpack_require__"'); t.ok(/var one = true;/i.test(contents), 'should contain "var one = true;"'); break; - case '0.bundle.js': + case 'test_fixtures_chunk_js.bundle.js': t.ok(/var chunk = true;/i.test(contents), 'should contain "var chunk = true;"'); break; } @@ -36,12 +36,12 @@ test('streams output assets', function (t) { test('multiple entry points', function (t) { t.plan(3); - var stream = webpack({ + const stream = webpack({ config: { mode: 'development', entry: { - 'one': path.join(base, 'entry.js'), - 'two': path.join(base, 'anotherentrypoint.js') + one: path.join(base, 'entry.js'), + two: path.join(base, 'anotherentrypoint.js') }, output: { filename: '[name].bundle.js' @@ -50,42 +50,8 @@ test('multiple entry points', function (t) { quiet: true }); stream.on('data', function (file) { - var basename = path.basename(file.path); - var contents = file.contents.toString(); - switch (basename) { - case 'one.bundle.js': - t.ok(/__webpack_require__/i.test(contents), 'should contain "__webpack_require__"'); - t.ok(/var one = true;/i.test(contents), 'should contain "var one = true;"'); - break; - case 'two.bundle.js': - t.ok(/var anotherentrypoint = true;/i.test(contents), 'should contain "var anotherentrypoint = true;"'); - break; - } - }); - stream.end(); -}); - -test('subsequent runs served from cache', function (t) { - t.plan(4); - - var config = { - config: { - mode: 'development', - entry: { - 'one': path.join(base, 'entry.js'), - 'two': path.join(base, 'anotherentrypoint.js') - }, - output: { - filename: '[name].bundle.js' - } - }, - quiet: true - }; - - var stream = webpack(config); - stream.on('data', function (file) { - var basename = path.basename(file.path); - var contents = file.contents.toString(); + const basename = path.basename(file.path); + const contents = file.contents.toString(); switch (basename) { case 'one.bundle.js': t.ok(/__webpack_require__/i.test(contents), 'should contain "__webpack_require__"'); @@ -96,35 +62,21 @@ test('subsequent runs served from cache', function (t) { break; } }); - - stream.on('end', function () { - var cachedStream = webpack(config); - var data = null; - - cachedStream.on('data', function (file) { - data = file; - }); - - cachedStream.on('end', function () { - t.ok(data === null, 'should not write any output'); - }); - }); - stream.end(); }); test('stream multiple entry points', function (t) { t.plan(3); - var entries = fs.src(['test/fixtures/entry.js', 'test/fixtures/anotherentrypoint.js']); - var stream = webpack({ + const entries = fs.src(['test/fixtures/entry.js', 'test/fixtures/anotherentrypoint.js']); + const stream = webpack({ config: { mode: 'development' }, quiet: true }); stream.on('data', function (file) { - var basename = path.basename(file.path); - var contents = file.contents.toString(); + const basename = path.basename(file.path); + const contents = file.contents.toString(); switch (basename) { case 'entry.js': t.ok(/__webpack_require__/i.test(contents), 'should contain "__webpack_require__"'); @@ -141,12 +93,12 @@ test('stream multiple entry points', function (t) { test('empty input stream', function (t) { t.plan(1); - var entry = fs.src('test/path/to/nothing', { allowEmpty: true }); - var stream = webpack({ + const entry = fs.src('test/path/to/nothing', { allowEmpty: true }); + const stream = webpack({ config: {}, quiet: true }); - var data = null; + let data = null; stream.on('data', function (file) { data = file; @@ -162,12 +114,12 @@ test('empty input stream', function (t) { test('multi-compile', function (t) { t.plan(3); - var stream = webpack({ + const stream = webpack({ quiet: true, config: [{ mode: 'development', entry: { - 'one': path.join(base, 'entry.js') + one: path.join(base, 'entry.js') }, output: { filename: '[name].bundle.js' @@ -175,7 +127,7 @@ test('multi-compile', function (t) { }, { mode: 'development', entry: { - 'two': path.join(base, 'anotherentrypoint.js') + two: path.join(base, 'anotherentrypoint.js') }, output: { filename: '[name].bundle.js' @@ -183,8 +135,8 @@ test('multi-compile', function (t) { }] }); stream.on('data', function (file) { - var basename = path.basename(file.path); - var contents = file.contents.toString(); + const basename = path.basename(file.path); + const contents = file.contents.toString(); switch (basename) { case 'one.bundle.js': t.ok(/__webpack_require__/i.test(contents), 'should contain "__webpack_require__"'); @@ -200,20 +152,32 @@ test('multi-compile', function (t) { test('no options', function (t) { t.plan(1); - var stream = webpack(); + const stream = webpack(); stream.on('end', function () { t.ok(true, 'ended without error'); }); stream.end(); }); +test('config file path with webpack-stream options', function (t) { + t.plan(1); + var stream = webpack({ + quiet: true, + config: path.join(base, 'webpack.config.js') + }); + stream.on('end', function () { + t.ok(true, 'config successfully loaded from file, with webpack-stream options'); + }); + stream.end(); +}); + test('error formatting', function (t) { t.plan(2); // TODO: Fix this to test to rely less on large string outputs as those can change // and still be ok. - var expectedMessage = '\x1b[31mError\x1b[39m in plugin "\x1b[36mwebpack-stream\x1b[39m"\nMessage:\n ./test/fixtures/entry.js\nModule Error (from ./test/fak'; - var entry = fs.src('test/fixtures/entry.js'); - var stream = webpack({ + const expectedMessage = '\x1b[31mError\x1b[39m in plugin "\x1b[36mwebpack-stream\x1b[39m"\nMessage:\n Module Error (from ./test/fake-error-loader.js):\nFake '; + const entry = fs.src('test/fixtures/entry.js'); + const stream = webpack({ quiet: true, config: { module: {