diff --git a/.circleci/config.yml b/.circleci/config.yml index 9844e61..78f3e83 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -8,22 +8,6 @@ version: 2 jobs: - test-node6: - docker: - - image: circleci/node:6 - steps: - - checkout - - run: node --version > _node_version && npm --version > _npm_version && cat _node_version && cat _npm_version - - restore_cache: - keys: - - v1-dependencies-{{ checksum "_node_version" }}-{{ checksum "_npm_version" }} - - run: node --version && npm --version - - run: npm install - - save_cache: - paths: - - ~/.npm - key: v1-dependencies-{{ checksum "_node_version" }}-{{ checksum "_npm_version" }} - - run: npm run test.ci test-node8: docker: - image: circleci/node:8 @@ -77,7 +61,6 @@ workflows: version: 2 build_and_test: jobs: - - test-node6 - test-node8 - test-node10 - test-node12 diff --git a/.eslintrc.js b/.eslintrc.js index 01f31be..5070bb4 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -2,6 +2,9 @@ const eslint = { extends: '@chrisblossom/eslint-config', + rules: { + 'import/no-default-export': 'off', + }, overrides: [ { files: ['dev-utils/**/*.js', 'dev-utils/**/.*.js'], diff --git a/.prettierrc.js b/.prettierrc.js index 59e0c1a..009e38e 100644 --- a/.prettierrc.js +++ b/.prettierrc.js @@ -6,21 +6,6 @@ const prettier = { singleQuote: true, trailingComma: 'all', arrowParens: 'always', - overrides: [ - { - files: ['*.js', '.*.js'], - excludeFiles: ['*/**', '*/.**'], - options: { - trailingComma: 'es5', - }, - }, - { - files: ['dev-utils/**/*.js', 'dev-utils/**/.*.js'], - options: { - trailingComma: 'es5', - }, - }, - ], }; module.exports = prettier; diff --git a/README.md b/README.md index 44e3230..0eef71c 100644 --- a/README.md +++ b/README.md @@ -19,7 +19,7 @@ A webpack plugin to remove/clean your build folder(s). -> NOTE: Node v6+ and Webpack v2+ are supported and tested. +> NOTE: Node v8+ and webpack v3+ are supported and tested. ## About @@ -47,7 +47,7 @@ const webpackConfig = { * During rebuilds, all webpack assets that are not used anymore * will be removed automatically. * - * See `Options and Defaults` for for information + * See `Options and Defaults` for information */ new CleanWebpackPlugin(), ], @@ -109,7 +109,7 @@ new CleanWebpackPlugin({ // // Use !negative patterns to exclude files // - // default: disabled + // default: [] cleanAfterEveryBuildPatterns: ['static*.*', '!static1.js'], // Allow clean patterns outside of process.cwd() diff --git a/appveyor.yml b/appveyor.yml index 402da4f..b2c3b0b 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -1,13 +1,14 @@ environment: matrix: - - nodejs_version: '6' - nodejs_version: '8' - nodejs_version: '10' -# TODO: Uncomment and remove once appveyor supports node 12 https://github.com/appveyor/ci/issues/2921 -# - nodejs_version: '12' - + - nodejs_version: '12' +platform: + - x86 + - x64 install: - - ps: Install-Product node $env:nodejs_version + # https://github.com/appveyor/ci/issues/2921#issuecomment-486727727 + - ps: Update-NodeJsInstallation (Get-NodeJsLatestBuild $env:nodejs_version) $env:PLATFORM - set PATH=%APPDATA%\npm;%PATH% - npm install matrix: diff --git a/dev-utils/get-webpack-version.js b/dev-utils/get-webpack-version.js index 110ce26..cc6ff6a 100644 --- a/dev-utils/get-webpack-version.js +++ b/dev-utils/get-webpack-version.js @@ -9,9 +9,11 @@ function getWebpackVersion() { const webpackPath = require.resolve('webpack'); const { dir } = path.parse(webpackPath); - const webpackPkg = readPkgUp.sync({ cwd: dir, normalize: false }); + const webpackPackageJson = readPkgUp.sync({ cwd: dir, normalize: false }); - const version = webpackPkg.pkg.version ? webpackPkg.pkg.version : null; + const version = webpackPackageJson.package.version + ? webpackPackageJson.package.version + : null; return version; } diff --git a/dev-utils/get-webpack-version.test.js b/dev-utils/get-webpack-version.test.js index 70613bb..7ab294e 100644 --- a/dev-utils/get-webpack-version.test.js +++ b/dev-utils/get-webpack-version.test.js @@ -5,7 +5,7 @@ const getWebpackVersionTest = () => require('./get-webpack-version')(); describe('webpackVersion', () => { test('returns major only and is type number', () => { jest.doMock('read-pkg-up', () => ({ - sync: () => ({ pkg: { version: '4.29.0' } }), + sync: () => ({ package: { version: '4.29.0' } }), })); const version = getWebpackVersionTest(); @@ -14,7 +14,7 @@ describe('webpackVersion', () => { test('handles alpha', () => { jest.doMock('read-pkg-up', () => ({ - sync: () => ({ pkg: { version: '5.0.0-alpha.8' } }), + sync: () => ({ package: { version: '5.0.0-alpha.8' } }), })); const version = getWebpackVersionTest(); @@ -22,7 +22,7 @@ describe('webpackVersion', () => { }); test('returns null if no version found', () => { - jest.doMock('read-pkg-up', () => ({ sync: () => ({ pkg: {} }) })); + jest.doMock('read-pkg-up', () => ({ sync: () => ({ package: {} }) })); const version = getWebpackVersionTest(); expect(version).toEqual(null); diff --git a/dev-utils/node-version.js b/dev-utils/node-version.js index 6ee923c..f22805e 100644 --- a/dev-utils/node-version.js +++ b/dev-utils/node-version.js @@ -4,9 +4,9 @@ const readPkgUp = require('read-pkg-up'); const semver = require('semver'); function getNodeVersion() { - const pkg = - readPkgUp.sync({ cwd: process.cwd(), normalize: false }).pkg || {}; - const engines = pkg.engines || {}; + const packageJson = + readPkgUp.sync({ cwd: process.cwd(), normalize: false }).package || {}; + const engines = packageJson.engines || {}; const node = engines.node || '8.9.0'; const nodeVersion = semver.coerce(node).raw; diff --git a/dev-utils/node-version.test.js b/dev-utils/node-version.test.js index 3990a14..097d248 100644 --- a/dev-utils/node-version.test.js +++ b/dev-utils/node-version.test.js @@ -11,7 +11,7 @@ test('handles undefined pkg', () => { }); test('handles undefined engines', () => { - jest.doMock('read-pkg-up', () => ({ sync: () => ({ pkg: {} }) })); + jest.doMock('read-pkg-up', () => ({ sync: () => ({ package: {} }) })); const nodeVersion = require('./node-version'); @@ -20,7 +20,7 @@ test('handles undefined engines', () => { test('handles undefined node', () => { jest.doMock('read-pkg-up', () => ({ - sync: () => ({ pkg: { engines: { npm: '^5.0.0' } } }), + sync: () => ({ package: { engines: { npm: '^5.0.0' } } }), })); const nodeVersion = require('./node-version'); @@ -30,7 +30,7 @@ test('handles undefined node', () => { test('handles non-digit characters', () => { jest.doMock('read-pkg-up', () => ({ - sync: () => ({ pkg: { engines: { node: '>=10.0.0' } } }), + sync: () => ({ package: { engines: { node: '>=10.0.0' } } }), })); const nodeVersion = require('./node-version'); @@ -40,7 +40,7 @@ test('handles non-digit characters', () => { test('handles empty node', () => { jest.doMock('read-pkg-up', () => ({ - sync: () => ({ pkg: { engines: { node: '' } } }), + sync: () => ({ package: { engines: { node: '' } } }), })); const nodeVersion = require('./node-version'); diff --git a/dev-utils/test-supported-webpack-versions.js b/dev-utils/test-supported-webpack-versions.js index bbdd694..4999d2f 100755 --- a/dev-utils/test-supported-webpack-versions.js +++ b/dev-utils/test-supported-webpack-versions.js @@ -5,7 +5,6 @@ 'use strict'; const execa = require('execa'); -const semver = require('semver'); const Listr = require('listr'); const del = require('del'); const readPkgUp = require('read-pkg-up'); @@ -15,7 +14,6 @@ const ciEnabled = process.argv[process.argv.length - 1] === '--ci'; const supported = [ // - '2', '3', '4', 'next', @@ -26,14 +24,14 @@ const webpackTestTasks = supported.map((version) => { /** * Webpack version 5 (currently @next) removed support for node 6. */ - if ( - (version === 'next' || version === '5') && - semver.lte(process.version, '8.0.0') === true - ) { - return `node ${ - process.version - } is not supported by webpack@${version}...node >=8 required`; - } + // if ( + // (version === 'next' || version === '5') && + // semver.lte(process.version, '8.0.0') === true + // ) { + // return `node ${ + // process.version + // } is not supported by webpack@${version}...node >=8 required`; + // } return false; }; @@ -106,7 +104,7 @@ tasks const packageJsonWebpackVersion = readPkgUp.sync({ cwd: process.cwd(), normalize: false, - }).pkg.devDependencies.webpack; + }).package.devDependencies.webpack; return new Listr( [ @@ -120,12 +118,12 @@ tasks '--no-save', `webpack@${packageJsonWebpackVersion}`, ], - { env: { FORCE_COLOR: true } } + { env: { FORCE_COLOR: true } }, ), skip: () => ciEnabled === true, }, ], - listrOptions + listrOptions, ).run(); }) .catch((error) => { diff --git a/package.json b/package.json index 8a6c7ff..f2939cf 100644 --- a/package.json +++ b/package.json @@ -11,7 +11,7 @@ "dist/" ], "engines": { - "node": ">=6.9.0" + "node": ">=8.9.0" }, "keywords": [ "webpack", @@ -53,26 +53,27 @@ "@babel/core": "^7.4.4", "@babel/preset-env": "^7.4.4", "@babel/preset-typescript": "^7.3.3", - "@chrisblossom/eslint-config": "^4.0.10", - "@types/jest": "^24.0.12", - "@types/node": "^12.0.0", + "@chrisblossom/eslint-config": "^5.0.0", + "@types/jest": "^24.0.13", + "@types/node": "^12.0.2", "@types/read-pkg-up": "^3.0.1", "babel-jest": "^24.8.0", - "codecov": "^3.3.0", + "babel-plugin-add-module-exports": "^1.0.2", + "codecov": "^3.5.0", "cross-env": "^5.2.0", "del-cli": "^1.1.0", "eslint": "^5.16.0", "execa": "^1.0.0", - "husky": "^2.2.0", + "husky": "^2.3.0", "jest": "^24.8.0", - "lint-staged": "^8.1.6", + "lint-staged": "^8.1.7", "listr": "^0.14.3", - "prettier": "^1.17.0", - "read-pkg-up": "^4.0.0", + "prettier": "^1.17.1", + "read-pkg-up": "^6.0.0", "semver": "^6.0.0", - "temp-sandbox": "^2.0.0", + "temp-sandbox": "^3.0.0", "typescript": "^3.4.5", - "webpack": "^4.30.0" + "webpack": "^4.32.0" }, "dependencies": { "@types/webpack": "^4.4.31", diff --git a/src/clean-webpack-plugin.test.ts b/src/clean-webpack-plugin.test.ts index 8702b24..97b164f 100644 --- a/src/clean-webpack-plugin.test.ts +++ b/src/clean-webpack-plugin.test.ts @@ -1134,7 +1134,72 @@ describe('verbose option', () => { }); describe('webpack errors', () => { - test('does nothing when webpack errors are present', async () => { + test('does nothing when webpack errors are present on initial build', async () => { + createSrcBundle(2); + + const cleanWebpackPluginPrevious = new CleanWebpackPlugin(); + + const compilerPrevious = webpack({ + entry: entryFileFull, + output: { + path: outputPathFull, + filename: 'bundle.js', + chunkFilename: '[name].bundle.js', + }, + plugins: [cleanWebpackPluginPrevious], + }); + + expect(cleanWebpackPluginPrevious.currentAssets).toEqual([]); + + /** + * Run successful build to place files inside dist folder but not in current assets + */ + await compilerPrevious.run(); + + const cleanWebpackPlugin = new CleanWebpackPlugin({ + verbose: true, + }); + + const compiler = webpack({ + entry: entryFileFull, + output: { + path: outputPathFull, + filename: 'bundle.js', + chunkFilename: '[name].bundle.js', + }, + plugins: [cleanWebpackPlugin], + }); + + expect(sandbox.getFileListSync(outputPathFull)).toEqual([ + '1.bundle.js', + 'bundle.js', + ]); + + expect(consoleSpy.mock.calls).toEqual([]); + + /** + * remove entry file to create webpack compile error + */ + sandbox.deleteSync(entryFile); + + try { + await compiler.run(); + // eslint-disable-next-line no-empty + } catch (error) {} + + expect(sandbox.getFileListSync(outputPathFull)).toEqual([ + '1.bundle.js', + 'bundle.js', + ]); + + expect(cleanWebpackPlugin.currentAssets).toEqual([]); + + expect(consoleSpy.mock.calls).toEqual([ + ['clean-webpack-plugin: pausing due to webpack errors'], + ]); + }); + + test('does nothing when webpack errors are present on rebuild', async () => { createSrcBundle(2); const cleanWebpackPlugin = new CleanWebpackPlugin({ diff --git a/src/clean-webpack-plugin.ts b/src/clean-webpack-plugin.ts index aa89e6d..72c230c 100644 --- a/src/clean-webpack-plugin.ts +++ b/src/clean-webpack-plugin.ts @@ -1,6 +1,8 @@ import path from 'path'; import { sync as delSync } from 'del'; -import { Compiler, Stats } from 'webpack'; +import { Compiler, Stats, compilation as compilationType } from 'webpack'; + +type Compilation = compilationType.Compilation; export interface Options { /** @@ -48,7 +50,7 @@ export interface Options { * * Use !negative patterns to exclude files * - * default: disabled + * default: [] */ cleanAfterEveryBuildPatterns?: string[]; @@ -62,6 +64,16 @@ export interface Options { dangerouslyAllowCleanPatternsOutsideProject?: boolean; } +// Copied from https://github.com/sindresorhus/is-plain-obj/blob/97480673cf12145b32ec2ee924980d66572e8a86/index.js +function isPlainObject(value: unknown): boolean { + if (Object.prototype.toString.call(value) !== '[object Object]') { + return false; + } + + const prototype = Object.getPrototypeOf(value); + return prototype === null || prototype === Object.getPrototypeOf({}); +} + class CleanWebpackPlugin { private readonly dry: boolean; private readonly verbose: boolean; @@ -75,7 +87,7 @@ class CleanWebpackPlugin { private outputPath: string; constructor(options: Options = {}) { - if (typeof options !== 'object' || Array.isArray(options) === true) { + if (isPlainObject(options) === false) { throw new Error(`clean-webpack-plugin only accepts an options object. See: https://github.com/johnagan/clean-webpack-plugin#options-and-defaults-optional`); } @@ -173,12 +185,18 @@ class CleanWebpackPlugin { if (this.cleanOnceBeforeBuildPatterns.length !== 0) { if (hooks) { - hooks.compile.tap('clean-webpack-plugin', () => { - this.handleInitial(); + hooks.emit.tap('clean-webpack-plugin', (compilation) => { + this.handleInitial(compilation); }); } else { - compiler.plugin('compile', () => { - this.handleInitial(); + compiler.plugin('emit', (compilation, callback) => { + try { + this.handleInitial(compilation); + + callback(); + } catch (error) { + callback(error); + } }); } } @@ -201,11 +219,21 @@ class CleanWebpackPlugin { * * Warning: It is recommended to initially clean your build directory outside of webpack to minimize unexpected behavior. */ - handleInitial() { + handleInitial(compilation: Compilation) { if (this.initialClean) { return; } + /** + * Do not remove files if there are compilation errors + * + * Handle logging inside this.handleDone + */ + const stats = compilation.getStats(); + if (stats.hasErrors()) { + return; + } + this.initialClean = true; this.removeFiles(this.cleanOnceBeforeBuildPatterns); diff --git a/wallaby.config.js b/wallaby.config.js index 563ee13..fd70705 100644 --- a/wallaby.config.js +++ b/wallaby.config.js @@ -58,11 +58,11 @@ module.exports = (wallabyInitial) => { const path = require('path'); const realModules = path.join( wallabySetup.localProjectDir, - 'node_modules' + 'node_modules', ); const linkedModules = path.join( wallabySetup.projectCacheDir, - 'node_modules' + 'node_modules', ); try {