diff --git a/.circleci/config.yml b/.circleci/config.yml index 1146d5f..490b953 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -17,25 +17,25 @@ jobs: key: dependency-cache-{{ checksum "package-lock.json" }} paths: - ./node_modules - node-v10-latest: - docker: - - image: rollupcabal/circleci-node-v10:latest + node-v12-latest: + machine: true steps: - checkout - restore_cache: key: dependency-cache-{{ checksum "package-lock.json" }} - run: - name: Node Info - command: node --version && npm --version - - run: - name: Workaround for GoogleChrome/puppeteer#290 - command: 'sh .circleci/setup-puppeteer.sh' - - run: - name: NPM Rebuild - command: npm install - - run: - name: Run unit tests. - command: npm run ci:coverage + name: Setup Node 12 and Run Tests + command: | + set +e + touch $BASH_ENV + curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.34.0/install.sh | bash + export NVM_DIR="/opt/circleci/.nvm" + [ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" + nvm install 12 + nvm alias default 12 + node --version && npm --version + npm install + npm run ci:coverage - run: name: Submit coverage data to codecov. command: bash <(curl -s https://codecov.io/bash) @@ -70,7 +70,7 @@ workflows: filters: tags: only: /.*/ - - node-v10-latest: + - node-v12-latest: requires: - analysis filters: diff --git a/README.md b/README.md index 0caeeb3..605b7eb 100644 --- a/README.md +++ b/README.md @@ -224,6 +224,17 @@ If [truthy](https://developer.mozilla.org/en-US/docs/Glossary/Truthy), the modul If a value of `'minimal'` is set, the progress indicator will render as a small, colored bar at the top of the window. This can be useful when the default fancy progress indicator interferes with elements in the page. +### `ramdisk` +Type: `boolean`
+Default: `false`
+Support: MacOS and Linux, Windows with WSL 2.0. + +If `true`, will apply [`webpack-plugin-ramdisk`](https://www.npmjs.com/package/webpack-plugin-ramdisk) to the build. `output` configuration does not have to be modified, a symlink will be created from the original output path to the output path on the ramdisk. _**Note:** This will remove an existing directory at the defined output path._ + +Leveraging this option can result in significant reduction of build time, which is especially useful when using `hmr: true` or `liveReload: true`. Typical build times can be cut by 25-32% or more depending on hardware and webpack configuration. This is also recommended for users with SSD, as it reduces hard disk thrashing. + +Windows users without WSL 2.0 are encouraged to install it to make use of this feature, or create a ramdisk manually using a tool like [ImDisk](https://sourceforge.net/projects/imdisk-toolkit/). + ### `static` Type: `String | Array(String) | Object`
Default: `compiler.context` diff --git a/lib/errors.js b/lib/errors.js index bd72754..df27a49 100644 --- a/lib/errors.js +++ b/lib/errors.js @@ -23,4 +23,12 @@ class PluginExistsError extends WebpackPluginServeError { } } -module.exports = { PluginExistsError, WebpackPluginServeError }; +class RamdiskPathError extends WebpackPluginServeError { + constructor(...args) { + super(...args); + this.name = 'RamdiskPathError'; + this.code = 'ERR_RAMDISK_PATH'; + } +} + +module.exports = { PluginExistsError, RamdiskPathError, WebpackPluginServeError }; diff --git a/lib/index.js b/lib/index.js index 04f6421..9e31f71 100644 --- a/lib/index.js +++ b/lib/index.js @@ -15,7 +15,8 @@ const Koa = require('koa'); const nanoid = require('nanoid/generate'); const { DefinePlugin, ProgressPlugin } = require('webpack'); -const { init: initHmrPlugin } = require('./hmr-plugin'); +const { init: initHmrPlugin } = require('./plugins/hmr'); +const { init: initRamdiskPlugin } = require('./plugins/ramdisk'); const { forceError, getLogger } = require('./log'); const { start } = require('./server'); const { validate } = require('./validate'); @@ -34,6 +35,7 @@ const defaults = { open: false, port: 55555, progress: true, + ramdisk: false, secure: false, static: null, status: true @@ -59,6 +61,7 @@ class WebpackPluginServe extends EventEmitter { // NOTE: undocumented option. this is used primarily in testing to allow for multiple instances // of the plugin to be tested within the same context. If you find this, use this at your own // peril. + /* istanbul ignore if */ if (!opts.allowMany && instance) { instance.log.error( 'Duplicate instances created. Only the first instance of this plugin will be active.' @@ -117,6 +120,7 @@ class WebpackPluginServe extends EventEmitter { this.compiler = compiler; // only allow once instance of the plugin to run for a build + /* istanbul ignore if */ if (instance !== this) { return; } @@ -169,6 +173,10 @@ class WebpackPluginServe extends EventEmitter { initHmrPlugin(compiler, this.log); } + if (this.options.ramdisk) { + initRamdiskPlugin.call(this, compiler); + } + if (!this.options.static.length) { this.options.static.push(compiler.context); } @@ -187,6 +195,7 @@ class WebpackPluginServe extends EventEmitter { // track subsequent builds from watching this.on('invalid', () => { + /* istanbul ignore next */ this.state.compiling = new Promise((resolve) => { this.once('done', () => resolve()); }); @@ -198,6 +207,7 @@ class WebpackPluginServe extends EventEmitter { // webpack still has a 4 year old bug whereby in watch mode, file timestamps aren't properly // accounted for, which will trigger multiple builds of the same hash. // see: https://github.com/egoist/time-fix-plugin + /* istanbul ignore if */ if (this.lastHash === compilation.hash) { return; } diff --git a/lib/log.js b/lib/log.js index a7bb307..973a0d9 100644 --- a/lib/log.js +++ b/lib/log.js @@ -20,6 +20,7 @@ const colors = { error: 'red' }; +/* istanbul ignore next */ const forceError = (...args) => { const { error } = console; error(chalk.red(`${symbols.whoops} wps:`), ...args); @@ -29,12 +30,14 @@ const getLogger = (options) => { const prefix = { level: ({ level }) => { const color = colors[level]; + /* istanbul ignore next */ const symbol = ['error', 'warn'].includes(level) ? symbols.whoops : symbols.ok; return chalk[color](`${symbol} wps: `); }, template: '{{level}}' }; + /* istanbul ignore if */ if (options.timestamp) { prefix.template = `[{{time}}] ${prefix.template}`; } diff --git a/lib/hmr-plugin.js b/lib/plugins/hmr.js similarity index 94% rename from lib/hmr-plugin.js rename to lib/plugins/hmr.js index 8e13e51..58affe8 100644 --- a/lib/hmr-plugin.js +++ b/lib/plugins/hmr.js @@ -10,7 +10,7 @@ */ const { HotModuleReplacementPlugin } = require('webpack'); -const { PluginExistsError } = require('./errors'); +const { PluginExistsError } = require('../errors'); const addPlugin = (compiler) => { const hmrPlugin = new HotModuleReplacementPlugin(); @@ -27,6 +27,8 @@ const init = function init(compiler, log) { const hasHMRPlugin = compiler.options.plugins.some( (plugin) => plugin instanceof HotModuleReplacementPlugin ); + + /* istanbul ignore else */ if (!hasHMRPlugin) { addPlugin(compiler); } else { diff --git a/lib/plugins/ramdisk.js b/lib/plugins/ramdisk.js new file mode 100644 index 0000000..f278613 --- /dev/null +++ b/lib/plugins/ramdisk.js @@ -0,0 +1,87 @@ +/* + Copyright © 2018 Andrew Powell + + This Source Code Form is subject to the terms of the Mozilla Public + License, v. 2.0. If a copy of the MPL was not distributed with this + file, You can obtain one at http://mozilla.org/MPL/2.0/. + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of this Source Code Form. +*/ +/* eslint-disable no-param-reassign */ +const crypto = require('crypto'); +const { existsSync, symlinkSync } = require('fs'); +const { basename, join, resolve } = require('path'); + +const isCwd = require('is-path-cwd'); +const readPkgUp = require('read-pkg-up'); +const rm = require('rimraf'); +const { WebpackPluginRamdisk } = require('webpack-plugin-ramdisk'); + +const { PluginExistsError, RamdiskPathError } = require('../errors'); + +module.exports = { + init(compiler) { + const hasPlugin = compiler.options.plugins.some( + (plugin) => plugin instanceof WebpackPluginRamdisk + ); + + /* istanbul ignore if */ + if (hasPlugin) { + this.log.error( + 'webpack-plugin-serve adds WebpackRamdiskPlugin automatically. Please remove it from your config.' + ); + throw new PluginExistsError('WebpackRamdiskPlugin exists in the specified configuration.'); + } + + const pkg = readPkgUp.sync() || {}; + const { path } = compiler.options.output; + const lastSegment = basename(path); + const errorInfo = `The ramdisk option creates a symlink from \`output.path\`, to the ramdisk build output path, and must remove any existing \`output.path\` to do so.`; + + // if output.path is /batman/batcave, and the user is running the build with wsp from + // /batman/batcave, then the process will try and delete cwd, which we don't want for a number + // of reasons + // console.log('output.path:', resolve(path)); + // console.log('process.cwd:', process.cwd()); + if (isCwd(path)) { + throw new RamdiskPathError( + `Cannot remove current working directory. ${errorInfo} Please run from another path, or choose a different \`output.path\`.` + ); + } + + // if output.path is /batman/batcave, and the compiler context is not set and cwd is + // /batman/batcave, or the context is the same as output.path, then the process will try and + // delete the context directory, which probably contains src, configs, etc. throw an error + // and be overly cautious rather than let the user do something bad. oddly enough, webpack + // doesn't protect against context === output.path. + if (resolve(compiler.context) === resolve(path)) { + throw new RamdiskPathError( + `Cannot remove ${path}. ${errorInfo} Please set the \`context\` to a another path, choose a different \`output.path\`.` + ); + } + + if (!pkg.name) { + // use md5 for a short hash that'll be consistent between wps starts + const md5 = crypto.createHash('md5'); + md5.update(path); + pkg.name = md5.digest('hex'); + } + + const newPath = join(pkg.name, lastSegment); + + // clean up the output path in prep for the ramdisk plugin + compiler.options.output.path = newPath; + + this.log.info(`Ramdisk enabled`); + + const plugin = new WebpackPluginRamdisk({ name: 'wps' }); + plugin.apply(compiler); + + if (existsSync(path)) { + rm.sync(path); + } + + symlinkSync(compiler.options.output.path, path); + } +}; diff --git a/lib/validate.js b/lib/validate.js index 73ffb45..38b8065 100644 --- a/lib/validate.js +++ b/lib/validate.js @@ -1,3 +1,13 @@ +/* + Copyright © 2018 Andrew Powell + + This Source Code Form is subject to the terms of the Mozilla Public + License, v. 2.0. If a copy of the MPL was not distributed with this + file, You can obtain one at http://mozilla.org/MPL/2.0/. + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of this Source Code Form. +*/ const Joi = require('@hapi/joi'); const isPromise = require('is-promise'); @@ -51,6 +61,7 @@ module.exports = { // prettier-ignore port: [number().integer().max(65535), any().promise()], progress: [boolean(), string().valid('minimal')], + ramdisk: [boolean()], secure: any().forbidden(), // prettier-ignore static: [ diff --git a/package-lock.json b/package-lock.json index 8d58533..4dab6d5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -366,19 +366,19 @@ } }, "@commitlint/cli": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/@commitlint/cli/-/cli-8.0.0.tgz", - "integrity": "sha512-wFu+g9v73I2rMRTv27ItIbcrhWqge0ZpUNUIJ9fw8TF7XpmhaUFvGqa2kU6st1F0TyEOrq5ZMzwI8kQZNVLuXg==", + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/@commitlint/cli/-/cli-8.1.0.tgz", + "integrity": "sha512-83K5C2nIAgoZlzMegf0/MEBjX+ampUyc/u79RxgX9ZYjzos+RQtNyO7I43dztVxPXSwAnX9XRgoOfkGWA4nbig==", "dev": true, "requires": { - "@commitlint/format": "^8.0.0", - "@commitlint/lint": "^8.0.0", - "@commitlint/load": "^8.0.0", - "@commitlint/read": "^8.0.0", + "@commitlint/format": "^8.1.0", + "@commitlint/lint": "^8.1.0", + "@commitlint/load": "^8.1.0", + "@commitlint/read": "^8.1.0", "babel-polyfill": "6.26.0", "chalk": "2.3.1", "get-stdin": "7.0.0", - "lodash": "4.17.11", + "lodash": "4.17.14", "meow": "5.0.0", "resolve-from": "5.0.0", "resolve-global": "1.0.0" @@ -394,12 +394,6 @@ "escape-string-regexp": "^1.0.5", "supports-color": "^5.2.0" } - }, - "lodash": { - "version": "4.17.11", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz", - "integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==", - "dev": true } } }, @@ -410,102 +404,85 @@ "dev": true }, "@commitlint/ensure": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/@commitlint/ensure/-/ensure-8.0.0.tgz", - "integrity": "sha512-rhBO79L9vXeb26JU+14cxZQq46KyyVqlo31C33VIe7oJndUtWrDhZTvMjJeB1pdXh4EU4XWdMo+yzBmuypFgig==", + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/@commitlint/ensure/-/ensure-8.1.0.tgz", + "integrity": "sha512-dBU4CcjN0vJSDNOeSpaHNgQ1ra444u4USvI6PTaHVAS4aeDpZ5Cds1rxkZNsocu48WNycUu0jP84+zjcw2pPLQ==", "dev": true, "requires": { - "lodash": "4.17.11" - }, - "dependencies": { - "lodash": { - "version": "4.17.11", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz", - "integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==", - "dev": true - } + "lodash": "4.17.14" } }, "@commitlint/execute-rule": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/@commitlint/execute-rule/-/execute-rule-8.0.0.tgz", - "integrity": "sha512-E/A2xHqx3syclXAFl8vJY2o/+xtL9axrqbFFF42Bzke+Eflf0mOJviPxDodu2xP0wXMRQ9UokAi/reK9dMtA/A==", - "dev": true, - "requires": { - "babel-runtime": "6.26.0" - } + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/@commitlint/execute-rule/-/execute-rule-8.1.0.tgz", + "integrity": "sha512-+vpH3RFuO6ypuCqhP2rSqTjFTQ7ClzXtUvXphpROv9v9+7zH4L+Ex+wZLVkL8Xj2cxefSLn/5Kcqa9XyJTn3kg==", + "dev": true }, "@commitlint/format": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/@commitlint/format/-/format-8.0.0.tgz", - "integrity": "sha512-dFxKGLp1T4obi7+YZ2NcSAebJA/dBQwnerRJGz0hWtsO6pheJRe+qC50+GCb2fYGWUc5lIWawaRts0m7RkFGUw==", + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/@commitlint/format/-/format-8.1.0.tgz", + "integrity": "sha512-D0cmabUTQIKdABgt08d9JAvO9+lMRAmkcsZx8TMScY502R67HCw77JhzRDcw1RmqX5rN8JO6ZjDHO92Pbwlt+Q==", "dev": true, "requires": { "chalk": "^2.0.1" } }, "@commitlint/is-ignored": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/@commitlint/is-ignored/-/is-ignored-8.0.0.tgz", - "integrity": "sha512-geWr/NXGMrZ3qc3exDM+S1qV+nMDxp1LwN3rLpEN2gXTwW3rIXq49RQQUkn0n3BHcpqJJ9EBhjqFoMU1TYx7Ng==", + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/@commitlint/is-ignored/-/is-ignored-8.1.0.tgz", + "integrity": "sha512-HUSxx6kuLbqrQ8jb5QRzo+yR+CIXgA9HNcIcZ1qWrb+O9GOixt3mlW8li1IcfIgfODlaWoxIz0jYCxR08IoQLg==", "dev": true, "requires": { - "semver": "6.0.0" + "@types/semver": "^6.0.1", + "semver": "6.1.1" + }, + "dependencies": { + "semver": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.1.1.tgz", + "integrity": "sha512-rWYq2e5iYW+fFe/oPPtYJxYgjBm8sC4rmoGdUOgBB7VnwKt6HrL793l2voH1UlsyYZpJ4g0wfjnTEO1s1NP2eQ==", + "dev": true + } } }, "@commitlint/lint": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/@commitlint/lint/-/lint-8.0.0.tgz", - "integrity": "sha512-5nKiJpBDR2iei+fre4+6M7FUrSX1cIMoxXKdrnb1GMOXkw9CsZSF5OvdrX08zHAFmOAeDaohoCV+XN/UN/vWYg==", + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/@commitlint/lint/-/lint-8.1.0.tgz", + "integrity": "sha512-WYjbUgtqvnlVH3S3XPZMAa+N7KO0yQ+GuUG20Qra+EtER6SRYawykmEs4wAyrmY8VcFXUnKgSlIQUsqmGKwNZQ==", "dev": true, "requires": { - "@commitlint/is-ignored": "^8.0.0", - "@commitlint/parse": "^8.0.0", - "@commitlint/rules": "^8.0.0", + "@commitlint/is-ignored": "^8.1.0", + "@commitlint/parse": "^8.1.0", + "@commitlint/rules": "^8.1.0", "babel-runtime": "^6.23.0", - "lodash": "4.17.11" - }, - "dependencies": { - "lodash": { - "version": "4.17.11", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz", - "integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==", - "dev": true - } + "lodash": "4.17.14" } }, "@commitlint/load": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/@commitlint/load/-/load-8.0.0.tgz", - "integrity": "sha512-JXC3YjO7hN7Rv2Z/SaYz+oIvShsQWLL7gnOCe8+YgI1EusBqjV4mPI0HnBXVe9volfdxbl+Af/GoQZs2dvyOFA==", + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/@commitlint/load/-/load-8.1.0.tgz", + "integrity": "sha512-ra02Dvmd7Gp1+uFLzTY3yGOpHjPzl5T9wYg/xrtPJNiOWXvQ0Mw7THw+ucd1M5iLUWjvdavv2N87YDRc428wHg==", "dev": true, "requires": { - "@commitlint/execute-rule": "^8.0.0", - "@commitlint/resolve-extends": "^8.0.0", + "@commitlint/execute-rule": "^8.1.0", + "@commitlint/resolve-extends": "^8.1.0", "babel-runtime": "^6.23.0", + "chalk": "2.4.2", "cosmiconfig": "^5.2.0", - "lodash": "4.17.11", + "lodash": "4.17.14", "resolve-from": "^5.0.0" - }, - "dependencies": { - "lodash": { - "version": "4.17.11", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz", - "integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==", - "dev": true - } } }, "@commitlint/message": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/@commitlint/message/-/message-8.0.0.tgz", - "integrity": "sha512-2oGUV8630nzsj17t6akq3mFguzWePADO069IwKJi+CN5L0YRBQj9zGRCB0P+zvh4EngjqMnuMwhEhaBEM8TTzA==", + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/@commitlint/message/-/message-8.1.0.tgz", + "integrity": "sha512-AjHq022G8jQQ/3YrBOjwVBD4xF75hvC3vcvFoBIb7cC8vad1QWq+1w+aks0KlEK5IW+/+7ORZXIH+oyW7h3+8A==", "dev": true }, "@commitlint/parse": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/@commitlint/parse/-/parse-8.0.0.tgz", - "integrity": "sha512-6CyweJrBkI+Jqx7qkpYgVx2muBMoUZAZHWhUTgqHIDDmI+3d4UPZ2plGS2G0969KkHCgjtlwnwTjWqA9HLMwPA==", + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/@commitlint/parse/-/parse-8.1.0.tgz", + "integrity": "sha512-n4fEbZ5kdK5HChvne7Mj8rGGkKMfA4H11IuWiWmmMzgmZTNb/B04LPrzdUm4lm3f10XzM2JMM7PLXqofQJOGvA==", "dev": true, "requires": { "conventional-changelog-angular": "^1.3.3", @@ -514,63 +491,106 @@ } }, "@commitlint/read": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/@commitlint/read/-/read-8.0.0.tgz", - "integrity": "sha512-IhNMiKPqkB5yxphe/FiOKgX2uCysbR8fGK6KOXON3uJaVND0dctxnfdv+vY9gDv2CtjIXgNFO+v6FLnqMfIvwA==", + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/@commitlint/read/-/read-8.1.0.tgz", + "integrity": "sha512-PKsGMQFEr2sX/+orI71b82iyi8xFqb7F4cTvsLxzB5x6/QutxPVM3rg+tEVdi6rBKIDuqRIp2puDZQuREZs3vg==", "dev": true, "requires": { - "@commitlint/top-level": "^8.0.0", + "@commitlint/top-level": "^8.1.0", "@marionebl/sander": "^0.6.0", "babel-runtime": "^6.23.0", "git-raw-commits": "^1.3.0" } }, "@commitlint/resolve-extends": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/@commitlint/resolve-extends/-/resolve-extends-8.0.0.tgz", - "integrity": "sha512-SPkH+dXMCpYboVwpIhtOhpg1xYdE7L77fuHmEJWveXSmgfi0GosFm4aJ7Cer9DjNjW+KbD0TUfzZU0TrYUESjQ==", + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/@commitlint/resolve-extends/-/resolve-extends-8.1.0.tgz", + "integrity": "sha512-r/y+CeKW72Oa9BUctS1+I/MFCDiI3lfhwfQ65Tpfn6eZ4CuBYKzrCRi++GTHeAFKE3y8q1epJq5Rl/1GBejtBw==", "dev": true, "requires": { - "babel-runtime": "6.26.0", + "@types/node": "^12.0.2", "import-fresh": "^3.0.0", - "lodash": "4.17.11", + "lodash": "4.17.14", "resolve-from": "^5.0.0", "resolve-global": "^1.0.0" - }, - "dependencies": { - "lodash": { - "version": "4.17.11", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz", - "integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==", - "dev": true - } } }, "@commitlint/rules": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/@commitlint/rules/-/rules-8.0.0.tgz", - "integrity": "sha512-s9BehZQP5uAc/V4lMaUxwxFabVZTw5fZ18Ase1e5tbMKVIwq/7E00Ny1czN7xSFXfgffukWznsexpfFXYpbVsg==", + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/@commitlint/rules/-/rules-8.1.0.tgz", + "integrity": "sha512-hlM8VfNjsOkbvMteFyqn0c3akiUjqG09Iid28MBLrXl/d+8BR3eTzwJ4wMta4oz/iqGyrIywvg1FpHrV977MPA==", "dev": true, "requires": { - "@commitlint/ensure": "^8.0.0", - "@commitlint/message": "^8.0.0", - "@commitlint/to-lines": "^8.0.0", + "@commitlint/ensure": "^8.1.0", + "@commitlint/message": "^8.1.0", + "@commitlint/to-lines": "^8.1.0", "babel-runtime": "^6.23.0" } }, "@commitlint/to-lines": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/@commitlint/to-lines/-/to-lines-8.0.0.tgz", - "integrity": "sha512-qqgNeyj+NJ1Xffwv6hGsipKlVFj30NmfPup751MS/me0GV8IBd//njTjiqHvf/3sKm/OcGn4Re4D7YXwTcC2RA==", + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/@commitlint/to-lines/-/to-lines-8.1.0.tgz", + "integrity": "sha512-Lh4OH1bInI8GME/7FggS0/XkIMEJdTObMbXRyPRGaPcWH5S7zpB6y+b4qjzBHXAbEv2O46QAAMjZ+ywPQCpmYQ==", "dev": true }, "@commitlint/top-level": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/@commitlint/top-level/-/top-level-8.0.0.tgz", - "integrity": "sha512-If9hwfISHV8HXGKeXUKsUvOo4DuISWiU/VC2qHsKpeHSREAxkWESmQzzwYvOtyBjMiOTfAXfzgth18g36Fz2ow==", + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/@commitlint/top-level/-/top-level-8.1.0.tgz", + "integrity": "sha512-EvQuofuA/+0l1w9pkG/PRyIwACmZdIh9qxyax7w7mR8qqmSHscqf2jARIylh1TOx0uI9egO8MuPLiwC1RwyREA==", "dev": true, "requires": { - "find-up": "^2.1.0" + "find-up": "^4.0.0" + }, + "dependencies": { + "find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "requires": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + } + }, + "locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "requires": { + "p-locate": "^4.1.0" + } + }, + "p-limit": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.2.0.tgz", + "integrity": "sha512-pZbTJpoUsCzV48Mc9Nh51VbwO0X9cuPFE8gYwx9BTCt9SF8/b7Zljd2fVgOxhIF/HDTKgpVzs+GPhyKfjLLFRQ==", + "dev": true, + "requires": { + "p-try": "^2.0.0" + } + }, + "p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "requires": { + "p-limit": "^2.2.0" + } + }, + "p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true + }, + "path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true + } } }, "@concordance/react": { @@ -723,6 +743,12 @@ "resolved": "https://registry.npmjs.org/@types/node/-/node-12.6.2.tgz", "integrity": "sha512-gojym4tX0FWeV2gsW4Xmzo5wxGjXGm550oVUII7f7G5o4BV6c7DBdiG1RRQd+y1bvqRyYtPfMK85UM95vsapqQ==" }, + "@types/semver": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/@types/semver/-/semver-6.0.1.tgz", + "integrity": "sha512-ffCdcrEE5h8DqVxinQjo+2d1q+FV5z7iNtPofw3JsrltSoSVlOGaW0rY8XxtO9XukdTn8TaCGWmk2VFGhI70mg==", + "dev": true + }, "@webassemblyjs/ast": { "version": "1.8.5", "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.8.5.tgz", diff --git a/package.json b/package.json index 0d9eecc..f516a30 100644 --- a/package.json +++ b/package.json @@ -43,6 +43,7 @@ "connect-history-api-fallback": "^1.5.0", "globby": "^10.0.1", "http-proxy-middleware": "^0.19.0", + "is-path-cwd": "^2.2.0", "is-promise": "^2.1.0", "koa": "^2.5.3", "koa-compress": "^3.0.0", @@ -54,11 +55,14 @@ "onetime": "^5.1.0", "opn": "^6.0.0", "p-defer": "^3.0.0", + "read-pkg-up": "^6.0.0", + "rimraf": "^2.6.3", "strip-ansi": "^5.0.0", + "webpack-plugin-ramdisk": "^0.1.2", "ws": "^7.1.0" }, "devDependencies": { - "@commitlint/cli": "^8.0.0", + "@commitlint/cli": "^8.1.0", "@commitlint/config-conventional": "^8.0.0", "ava": "^2.2.0", "cpy": "^7.0.1", @@ -101,7 +105,7 @@ }, "nyc": { "include": [ - "lib/*.js" + "lib/**/*.js" ], "exclude": [ "lib/client*.js", diff --git a/test/errors.test.js b/test/errors.test.js new file mode 100644 index 0000000..8b1dfe2 --- /dev/null +++ b/test/errors.test.js @@ -0,0 +1,8 @@ +const test = require('ava'); + +const { PluginExistsError, WebpackPluginServeError } = require('../lib/errors'); + +test('errors', (t) => { + t.snapshot(new PluginExistsError()); + t.snapshot(new WebpackPluginServeError()); +}); diff --git a/test/fixtures/ramdisk/app.js b/test/fixtures/ramdisk/app.js new file mode 100644 index 0000000..ad15d30 --- /dev/null +++ b/test/fixtures/ramdisk/app.js @@ -0,0 +1,9 @@ +require('./component'); + +if (module.hot) { + module.hot.accept((err) => { + if (err) { + console.error('HMR', err); + } + }); +} diff --git a/test/fixtures/ramdisk/component.js b/test/fixtures/ramdisk/component.js new file mode 100644 index 0000000..9262226 --- /dev/null +++ b/test/fixtures/ramdisk/component.js @@ -0,0 +1,2 @@ +const main = document.querySelector('main'); +main.innerHTML = 'main'; diff --git a/test/fixtures/ramdisk/config-context-error.js b/test/fixtures/ramdisk/config-context-error.js new file mode 100644 index 0000000..fa0f821 --- /dev/null +++ b/test/fixtures/ramdisk/config-context-error.js @@ -0,0 +1,29 @@ +const { resolve } = require('path'); + +const getPort = require('get-port'); + +const { WebpackPluginServe: Serve } = require('../../../lib/'); + +module.exports = { + context: __dirname, + entry: ['./app.js', 'webpack-plugin-serve/client'], + mode: 'development', + output: { + filename: './output.js', + path: __dirname, + publicPath: 'output/' + }, + plugins: [ + new Serve({ + host: 'localhost', + port: getPort({ port: 55555 }), + ramdisk: true + }) + ], + resolve: { + alias: { + 'webpack-plugin-serve/client': resolve(__dirname, '../../../client') + } + }, + watch: true +}; diff --git a/test/fixtures/ramdisk/config-cwd-error.js b/test/fixtures/ramdisk/config-cwd-error.js new file mode 100644 index 0000000..b21c810 --- /dev/null +++ b/test/fixtures/ramdisk/config-cwd-error.js @@ -0,0 +1,29 @@ +const { resolve } = require('path'); + +const getPort = require('get-port'); + +const { WebpackPluginServe: Serve } = require('../../../lib/'); + +module.exports = { + context: __dirname, + entry: ['./app.js', 'webpack-plugin-serve/client'], + mode: 'development', + output: { + filename: './output.js', + path: resolve(__dirname, 'cwd-error'), + publicPath: 'output/' + }, + plugins: [ + new Serve({ + host: 'localhost', + port: getPort({ port: 55555 }), + ramdisk: true + }) + ], + resolve: { + alias: { + 'webpack-plugin-serve/client': resolve(__dirname, '../../../client') + } + }, + watch: true +}; diff --git a/test/fixtures/ramdisk/cwd-error/.gitkeep b/test/fixtures/ramdisk/cwd-error/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/test/fixtures/ramdisk/index.html b/test/fixtures/ramdisk/index.html new file mode 100644 index 0000000..fca1533 --- /dev/null +++ b/test/fixtures/ramdisk/index.html @@ -0,0 +1,10 @@ + + + + fixture: single + + +
+ + + diff --git a/test/fixtures/ramdisk/webpack.config.js b/test/fixtures/ramdisk/webpack.config.js new file mode 100644 index 0000000..3a4d0bb --- /dev/null +++ b/test/fixtures/ramdisk/webpack.config.js @@ -0,0 +1,29 @@ +const { resolve } = require('path'); + +const getPort = require('get-port'); + +const { WebpackPluginServe: Serve } = require('../../../lib/'); + +module.exports = { + context: __dirname, + entry: ['./app.js', 'webpack-plugin-serve/client'], + mode: 'development', + output: { + filename: './output.js', + path: resolve(__dirname, './output'), + publicPath: 'output/' + }, + plugins: [ + new Serve({ + host: 'localhost', + port: getPort({ port: 55555 }), + ramdisk: true + }) + ], + resolve: { + alias: { + 'webpack-plugin-serve/client': resolve(__dirname, '../../../client') + } + }, + watch: true +}; diff --git a/test/plugin.test.js b/test/plugin.test.js index 59fff19..3397158 100644 --- a/test/plugin.test.js +++ b/test/plugin.test.js @@ -12,6 +12,15 @@ test('defaults', (t) => { t.snapshot(plugin.options); }); +test('options manipulation', (t) => { + const plugin = new WebpackPluginServe({ + allowMany: true, + compress: true, + historyFallback: true + }); + t.snapshot(plugin.options); +}); + test('static → string', (t) => { const { options } = new WebpackPluginServe({ allowMany: true, diff --git a/test/ramdisk.test.js b/test/ramdisk.test.js new file mode 100644 index 0000000..386bf5c --- /dev/null +++ b/test/ramdisk.test.js @@ -0,0 +1,68 @@ +const { existsSync } = require('fs'); +const { join, resolve } = require('path'); + +const test = require('ava'); +const execa = require('execa'); +const strip = require('strip-ansi'); + +const fixturePath = join(__dirname, 'fixtures/ramdisk'); + +const waitFor = (text, stream) => { + return { + then(r, f) { + stream.on('data', (data) => { + const content = strip(data.toString()); + if (content.includes(text)) { + r(content.slice(content.lastIndexOf(text) + text.length)); + } + }); + + stream.on('error', f); + } + }; +}; + +test('ramdisk', async (t) => { + const proc = execa('wp', [], { cwd: fixturePath }); + const { stderr, stdout } = proc; + const pathTest = 'Build being written to '; + const doneTest = '[emitted]'; + + const path = await waitFor(pathTest, stdout); + + t.regex(path, /(volumes|mnt)\/wps\/[a-f0-9]{32}\/output/i); + + await waitFor(doneTest, stderr); + + const exists = existsSync(join(fixturePath, 'output/output.js')); + + t.truthy(exists); + + proc.kill('SIGTERM'); +}); + +test('context error', async (t) => { + try { + await execa('wp', ['--config', 'ramdisk/config-context-error.js'], { + cwd: resolve(fixturePath, '..') + }); + } catch (e) { + t.regex(e.stderr, /Please set the `context` to a another path/); + t.is(e.exitCode, 1); + return; + } + t.fail(); +}); + +test('cwd error', async (t) => { + try { + await execa('wp', ['--config', '../config-cwd-error.js'], { + cwd: join(fixturePath, 'cwd-error') + }); + } catch (e) { + t.regex(e.stderr, /Please run from another path/); + t.is(e.exitCode, 1); + return; + } + t.fail(); +}); diff --git a/test/snapshots/errors.test.js.md b/test/snapshots/errors.test.js.md new file mode 100644 index 0000000..6415408 --- /dev/null +++ b/test/snapshots/errors.test.js.md @@ -0,0 +1,20 @@ +# Snapshot report for `test/errors.test.js` + +The actual snapshot is saved in `errors.test.js.snap`. + +Generated by [AVA](https://ava.li). + +## errors + +> Snapshot 1 + + PluginExistsError { + code: 'ERR_PLUGIN_EXISTS', + message: '', + } + +> Snapshot 2 + + WebpackPluginServeError { + message: '', + } diff --git a/test/snapshots/errors.test.js.snap b/test/snapshots/errors.test.js.snap new file mode 100644 index 0000000..b0975ab Binary files /dev/null and b/test/snapshots/errors.test.js.snap differ diff --git a/test/snapshots/plugin.test.js.md b/test/snapshots/plugin.test.js.md index 98b55ea..0ef986a 100644 --- a/test/snapshots/plugin.test.js.md +++ b/test/snapshots/plugin.test.js.md @@ -29,6 +29,39 @@ Generated by [AVA](https://ava.li). then: Function then {}, }, progress: true, + ramdisk: false, + secure: false, + static: [], + status: true, + } + +## options manipulation + +> Snapshot 1 + + { + allowMany: true, + compress: {}, + headers: null, + historyFallback: {}, + hmr: true, + host: null, + liveReload: false, + log: { + level: 'info', + name: 'webpack-plugin-serve', + prefix: { + level: Function level {}, + template: '{{level}}', + }, + }, + middleware: Function middleware {}, + open: false, + port: { + then: Function then {}, + }, + progress: true, + ramdisk: false, secure: false, static: [], status: true, @@ -49,6 +82,8 @@ Generated by [AVA](https://ava.li). [ 'test/fixtures/multi', 'test/fixtures/proxy', + 'test/fixtures/ramdisk', + 'test/fixtures/ramdisk/cwd-error', 'test/fixtures/simple', 'test/fixtures/wait-for-build', ] diff --git a/test/snapshots/plugin.test.js.snap b/test/snapshots/plugin.test.js.snap index e9523aa..acecc92 100644 Binary files a/test/snapshots/plugin.test.js.snap and b/test/snapshots/plugin.test.js.snap differ diff --git a/test/snapshots/ramdisk.test.js.md b/test/snapshots/ramdisk.test.js.md new file mode 100644 index 0000000..2591098 --- /dev/null +++ b/test/snapshots/ramdisk.test.js.md @@ -0,0 +1,39 @@ +# Snapshot report for `test/ramdisk.test.js` + +The actual snapshot is saved in `ramdisk.test.js.snap`. + +Generated by [AVA](https://ava.li). + +## ramdisk + +> Snapshot 1 + + Error { + all: `/Users/powella/code/webpack/webpack-plugin-serve/test/fixtures/ramdisk␊ + /Users/powella/code/webpack/webpack-plugin-serve/test/fixtures␊ + RamdiskPathError: Cannot remove /Users/powella/code/webpack/webpack-plugin-serve/test/fixtures/ramdisk. The ramdisk option creates a symlink from `output.path`, to the ramdisk build output path, and must remove any existing `output.path` to do so. Please set the `context` to a another path, choose a different `output.path`.␊ + at WebpackPluginServe.init (/Users/powella/code/webpack/webpack-plugin-serve/lib/plugins/ramdisk.js:59:13)␊ + at WebpackPluginServe.hook (/Users/powella/code/webpack/webpack-plugin-serve/lib/index.js:177:25)␊ + at WebpackPluginServe.apply (/Users/powella/code/webpack/webpack-plugin-serve/lib/index.js:128:10)␊ + at webpack (/Users/powella/code/webpack/webpack-plugin-serve/node_modules/webpack/lib/webpack.js:49:13)␊ + at run (/Users/powella/code/webpack/webpack-plugin-serve/node_modules/webpack-nano/lib/compiler.js:16:20)␊ + at doeet (/Users/powella/code/webpack/webpack-plugin-serve/node_modules/webpack-nano/bin/wp.js:78:3)`, + command: 'wp --config ramdisk/config-context-error.js', + exitCode: 1, + exitCodeName: 'EPERM', + failed: true, + isCanceled: false, + killed: false, + signal: undefined, + stderr: `RamdiskPathError: Cannot remove /Users/powella/code/webpack/webpack-plugin-serve/test/fixtures/ramdisk. The ramdisk option creates a symlink from `output.path`, to the ramdisk build output path, and must remove any existing `output.path` to do so. Please set the `context` to a another path, choose a different `output.path`.␊ + at WebpackPluginServe.init (/Users/powella/code/webpack/webpack-plugin-serve/lib/plugins/ramdisk.js:59:13)␊ + at WebpackPluginServe.hook (/Users/powella/code/webpack/webpack-plugin-serve/lib/index.js:177:25)␊ + at WebpackPluginServe.apply (/Users/powella/code/webpack/webpack-plugin-serve/lib/index.js:128:10)␊ + at webpack (/Users/powella/code/webpack/webpack-plugin-serve/node_modules/webpack/lib/webpack.js:49:13)␊ + at run (/Users/powella/code/webpack/webpack-plugin-serve/node_modules/webpack-nano/lib/compiler.js:16:20)␊ + at doeet (/Users/powella/code/webpack/webpack-plugin-serve/node_modules/webpack-nano/bin/wp.js:78:3)`, + stdout: `/Users/powella/code/webpack/webpack-plugin-serve/test/fixtures/ramdisk␊ + /Users/powella/code/webpack/webpack-plugin-serve/test/fixtures`, + timedOut: false, + message: 'Command failed with exit code 1 (EPERM): wp --config ramdisk/config-context-error.js', + } diff --git a/test/snapshots/ramdisk.test.js.snap b/test/snapshots/ramdisk.test.js.snap new file mode 100644 index 0000000..02991f3 Binary files /dev/null and b/test/snapshots/ramdisk.test.js.snap differ diff --git a/test/snapshots/validate.test.js.md b/test/snapshots/validate.test.js.md index 578b188..a5969d5 100644 --- a/test/snapshots/validate.test.js.md +++ b/test/snapshots/validate.test.js.md @@ -99,3 +99,31 @@ Generated by [AVA](https://ava.li). }, }, } + +## throws + +> Snapshot 1 + + ValidationError (Error) { + _object: { + batman: 'nanananana', + }, + annotate: Function {}, + details: [ + { + context: { + child: 'batman', + key: 'batman', + label: 'batman', + value: 'nanananana', + }, + message: '"batman" is not allowed', + path: [ + 'batman', + ], + type: 'object.allowUnknown', + }, + ], + isJoi: true, + message: '"batman" is not allowed', + } diff --git a/test/snapshots/validate.test.js.snap b/test/snapshots/validate.test.js.snap index 4231ada..e89873e 100644 Binary files a/test/snapshots/validate.test.js.snap and b/test/snapshots/validate.test.js.snap differ diff --git a/test/validate.test.js b/test/validate.test.js index 0430339..372641f 100644 --- a/test/validate.test.js +++ b/test/validate.test.js @@ -1,6 +1,6 @@ const test = require('ava'); -const { defaults } = require('../lib'); +const { defaults, WebpackPluginServe } = require('../lib'); const { validate } = require('../lib/validate'); test('defaults', (t) => { @@ -32,3 +32,8 @@ test('promise', (t) => { t.falsy(result.error); t.snapshot(result); }); + +test('throws', (t) => { + const error = t.throws(() => new WebpackPluginServe({ batman: 'nanananana' })); + t.snapshot(error); +});