Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 15 additions & 15 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -70,7 +70,7 @@ workflows:
filters:
tags:
only: /.*/
- node-v10-latest:
- node-v12-latest:
requires:
- analysis
filters:
Expand Down
11 changes: 11 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`<br>
Default: `false`<br>
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`<br>
Default: `compiler.context`
Expand Down
10 changes: 9 additions & 1 deletion lib/errors.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 };
12 changes: 11 additions & 1 deletion lib/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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');
Expand All @@ -34,6 +35,7 @@ const defaults = {
open: false,
port: 55555,
progress: true,
ramdisk: false,
secure: false,
static: null,
status: true
Expand All @@ -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.'
Expand Down Expand Up @@ -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;
}
Expand Down Expand Up @@ -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);
}
Expand All @@ -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());
});
Expand All @@ -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;
}
Expand Down
3 changes: 3 additions & 0 deletions lib/log.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ const colors = {
error: 'red'
};

/* istanbul ignore next */
const forceError = (...args) => {
const { error } = console;
error(chalk.red(`${symbols.whoops} wps:`), ...args);
Expand All @@ -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}`;
}
Expand Down
4 changes: 3 additions & 1 deletion lib/hmr-plugin.js → lib/plugins/hmr.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
*/
const { HotModuleReplacementPlugin } = require('webpack');

const { PluginExistsError } = require('./errors');
const { PluginExistsError } = require('../errors');

const addPlugin = (compiler) => {
const hmrPlugin = new HotModuleReplacementPlugin();
Expand All @@ -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 {
Expand Down
87 changes: 87 additions & 0 deletions lib/plugins/ramdisk.js
Original file line number Diff line number Diff line change
@@ -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);
}
};
11 changes: 11 additions & 0 deletions lib/validate.js
Original file line number Diff line number Diff line change
@@ -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');

Expand Down Expand Up @@ -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: [
Expand Down
Loading