diff --git a/README.md b/README.md index cb9e82bb..caa5972e 100644 --- a/README.md +++ b/README.md @@ -15,12 +15,21 @@ module.exports = { context: // ... entry: // ... output: // ... - // Can be constructed with __dirname and path.join. - recordsPath: '/absolute/path/to/records.json', plugins: [ new HardSourceWebpackPlugin({ // Either an absolute path or relative to output.path. - cacheDirectory: 'path/to/cache', + cacheDirectory: 'path/to/cache/[confighash]', + // Either an absolute path or relative to output.path. Sets webpack's + // recordsPath if not already set. + recordsPath: 'path/to/cache/[confighash]/records.json', + // Optional field. Either a string value or function that returns a + // string value. + configHash: function(webpackConfig) { + // Build a string value used by HardSource to determine which cache to + // use if [confighash] is in cacheDirectory or if the cache should be + // replaced if [confighash] does not appear in cacheDirectory. + return process.env.NODE_ENV; + }, // Optional field. This field determines when to throw away the whole // cache if for example npm modules were updated. environmentPaths: { @@ -43,9 +52,35 @@ A absolute or relative path to store intermediate webpack details to support fas This directory is best unique per webpack configuration in a project. HardSourceWebpackPlugin cannot detect differences in webpack configurations inbetween runs. Instead using the same directory will pollute a build with build items from another configuration. You can naturally have sub directories for each config like `hard-source-cache/dev` and `hard-source-cache/prod`. -### `environmentPaths` +Building a unique cacheDirectory can also be done with help with the `configHash` option. + +### `recordsPath` + +Set webpack's `recordsPath` option if not already set with a value constructed like `cacheDirectory`. `recordsInputPath` and `recordsOutputPath` may also be set this way. + +### `configHash` + +An optional field that takes a string or function. The function is called and passed the webpack config that the plugin first sees. Its called at this time so any changes to your config between reading it from disk and creating the compiler can be considered. Often this will help HardSource distinguish between webpack configs and if the previous cache can be used in cases like combining webpack config partials or turning on Hot Module Replacement through an option to the webpack's dev server. + +#### Using configHash in the cacheDirectory + +One of configHash's use cases is when building a webpack config from multiple parts and having HardSource's config in one of those common parts. In that case you need a different cache folder for each combination of webpack config parts. Including `[confighash]` in your cacheDirectory will use `configHash` HardSource option in where the cache is read and stored. -Requires Node > 4. Other versions will silently disable this option. +Since using `[confighash]` in cacheDirectory means multiple caches it is also best to have multiple webpack id records. Records means modules get the same ids over multiple builds. It also means different webpack configs with different loaders will mean different ids. If the same records used in a development build were then used in a production build the module ids will be further apart than records for just production builds or records without this plugin. So to help using different records per config, `[confighash]` can also be used in the `recordsPath` option passed to HardSourceWebpackPlugin. + +#### Why hash the config? + +You should use `environmentPaths` to help build an stamp of the build environment's npm modules and configuration, and so include any webpack config files in it. `configHash` fulfills a second check in a way. `configHash` is used to consider if the config at runtime is different than what it used to be during a previous run. This checks values that are different each webpack run without the timestamp of the configuration files having changed. + +Differences reflected through this could be changes to a configuration like: + +- An environment variable like NODE_ENV used to change parts of a configuration +- Combining webpack configs with webpack-merge +- Running in the webpack-dev-server cli tool with `--hot` versus without `--hot` + +There isn't a way for HardSource to produce this value so its up to users to make it best reflect any webpack config that your specific project reasonably may have. Many project may use a library like `node-object-hash` to do this. Two reasons HardSource doesn't just do that is because of recursive configs or configs with non-deterministic values. If neither of those issues are part of your config `node-object-hash` will work here. In cases where you have recursive objects or non-deterministic values in your config, you will need to construct this value on your own or by creating a clone of your config that removes those issues first. + +### `environmentPaths` The options to `environmentPaths` are passed to [`env-hash`](https://www.npmjs.com/package/env-hash). Using `env-hash`, HardSourceWebpackPlugin tries to detect when changes in the configuration environment have changed such that it should ignore any cache. You can disable this check, though its best not to, by setting `environmentPaths` to `false`. diff --git a/index.js b/index.js index 967f1676..6df05abe 100644 --- a/index.js +++ b/index.js @@ -160,19 +160,71 @@ function HardSourceWebpackPlugin(options) { this.options = options; } +HardSourceWebpackPlugin.prototype.getPath = function(dirName, suffix) { + var confighashIndex = dirName.search(/\[confighash\]/); + if (confighashIndex !== -1) { + dirName = dirName.replace(/\[confighash\]/, this.configHash); + } + var cachePath = path.resolve( + process.cwd(), this.compilerOutputOptions.path, dirName + ); + if (suffix) { + cachePath = path.join(cachePath, suffix); + } + return cachePath; +}; + +HardSourceWebpackPlugin.prototype.getCachePath = function(suffix) { + return this.getPath(this.options.cacheDirectory, suffix); +}; + module.exports = HardSourceWebpackPlugin; HardSourceWebpackPlugin.prototype.apply = function(compiler) { var options = this.options; var active = true; - var cacheDirName = options.cacheDirectory; - if (!cacheDirName) { + if (!options.cacheDirectory) { console.error('HardSourceWebpackPlugin requires a cacheDirectory setting.'); active = false; return; } - var cacheDirPath = path.resolve( - process.cwd(), compiler.options.output.path, cacheDirName - ); + + this.compilerOutputOptions = compiler.options.output; + if (options.configHash) { + if (typeof options.configHash === 'string') { + this.configHash = options.configHash; + } + else if (typeof options.configHash === 'function') { + this.configHash = options.configHash(compiler.options); + } + } + var configHashInDirectory = + options.cacheDirectory.search(/\[confighash\]/) !== -1; + if (configHashInDirectory && !this.configHash) { + console.error('HardSourceWebpackPlugin cannot use [confighash] in cacheDirectory without configHash option being set and returning a non-falsy value.'); + active = false; + return; + } + + if (options.recordsInputPath || options.recordsPath) { + if (compiler.options.recordsInputPath || compiler.options.recordsPath) { + console.error('HardSourceWebpackPlugin will not set recordsInputPath when it is already set. Using current value:', compiler.options.recordsInputPath || compiler.options.recordsPath); + } + else { + compiler.options.recordsInputPath = + this.getPath(options.recordsInputPath || options.recordsPath); + } + } + if (options.recordsOutputPath || options.recordsPath) { + if (compiler.options.recordsOutputPath || compiler.options.recordsPath) { + console.error('HardSourceWebpackPlugin will not set recordsOutputPath when it is already set. Using current value:', compiler.options.recordsOutputPath || compiler.options.recordsPath); + } + else { + compiler.options.recordsOutputPath = + this.getPath(options.recordsOutputPath || options.recordsPath); + } + } + + var cacheDirPath = this.getCachePath(); var cacheAssetDirPath = path.join(cacheDirPath, 'assets'); var resolveCachePath = path.join(cacheDirPath, 'resolve.json'); @@ -191,10 +243,11 @@ HardSourceWebpackPlugin.prototype.apply = function(compiler) { var dataCacheSerializer = this.dataCacheSerializer = new LevelDbSerializer({cacheDirPath: path.join(cacheDirPath, 'data')}); + var _this = this; + compiler.plugin('after-plugins', function() { if ( - !compiler.recordsInputPath || - compiler.recordsInputPath !== compiler.recordsOutputPath + !compiler.recordsInputPath || !compiler.recordsOutputPath ) { console.error('HardSourceWebpackPlugin requires recordsPath to be set.'); active = false; @@ -204,7 +257,15 @@ HardSourceWebpackPlugin.prototype.apply = function(compiler) { compiler.plugin(['watch-run', 'run'], function(compiler, cb) { if (!active) {return cb();} - mkdirp.sync(cacheAssetDirPath); + try { + fs.statSync(cacheAssetDirPath); + } + catch (_) { + mkdirp.sync(cacheAssetDirPath); + if (configHashInDirectory) { + console.log('HardSourceWebpackPlugin is writing to a new confighash path for the first time:', cacheDirPath); + } + } var start = Date.now(); Promise.all([ @@ -222,6 +283,10 @@ HardSourceWebpackPlugin.prototype.apply = function(compiler) { var stamp = stamps[0]; var hash = stamps[1]; + if (!configHashInDirectory && options.configHash) { + hash += '_' + _this.configHash; + } + currentStamp = hash; if (!hash || hash !== stamp) { if (hash && stamp) {