Skip to content

Commit

Permalink
feat: store low level resolutions from built module
Browse files Browse the repository at this point in the history
NormalModule can resolve other files while building. Store these
resolutions and compare them in future builds to determine if the
module should be rebuilt.
  • Loading branch information
mzgoddard committed Jul 1, 2018
1 parent 25d660b commit f27757a
Show file tree
Hide file tree
Showing 11 changed files with 209 additions and 12 deletions.
18 changes: 9 additions & 9 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -378,15 +378,6 @@ class HardSourceWebpackPlugin {
});
}

function runVerify(_compiler) {
if (!active) {
return Promise.resolve();
}

const stats = {};
return pluginCompat.promise(compiler, '_hardSourceVerifyCache', []);
}

compilerHooks.watchRun.tapPromise(
'HardSource - index - readOrReset',
runReadOrReset,
Expand Down Expand Up @@ -515,6 +506,15 @@ class HardSourceWebpackPlugin {

new ChalkLoggerPlugin(this.options.info).apply(compiler);

function runVerify(_compiler) {
if (!active) {
return Promise.resolve();
}

const stats = {};
return pluginCompat.promise(compiler, '_hardSourceVerifyCache', []);
}

compilerHooks.watchRun.tapPromise('HardSource - index - verify', runVerify);
compilerHooks.run.tapPromise('HardSource - index - verify', runVerify);

Expand Down
35 changes: 35 additions & 0 deletions lib/TransformNormalModuleFactoryPlugin.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,41 @@ class NormalModuleFactoryPlugin {
// compilation.__hardSourceFileMd5s = fileMd5s;
// compilation.__hardSourceCachedMd5s = cachedMd5s;

const compilationHooks = pluginCompat.hooks(compilation);
compilationHooks.buildModule.tap(
'HardSource - TransformNormalModuleFactoryPlugin',
module => {
if (module.constructor.name === 'NormalModule') {
const _createLoaderContext = module.createLoaderContext;
module.__hardSource_resolved = {};
module.createLoaderContext = (...args) => {
const loaderContext = _createLoaderContext.call(
module,
...args,
);
const _resolve = loaderContext.resolve;
loaderContext.resolve = (context, request, callback) => {
_resolve.call(
loaderContext,
context,
request,
(err, result) => {
module.__hardSource_resolved[
JSON.stringify({ context, request })
] = {
resource: result,
resolveOptions: module.resolveOptions,
};
callback(err, result);
},
);
};
return loaderContext;
};
}
},
);

const normalModuleFactoryHooks = pluginCompat.hooks(
normalModuleFactory,
);
Expand Down
108 changes: 106 additions & 2 deletions lib/TransformNormalModulePlugin.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
const NormalModule = require('webpack/lib/NormalModule');
const Module = require('webpack/lib/Module');

const nodeObjectHash = require('node-object-hash');

const logMessages = require('./util/log-messages');
const {
relateNormalPath,
Expand All @@ -11,6 +13,44 @@ const {
const pluginCompat = require('./util/plugin-compat');
const serial = require('./util/serial');

const serialResolveRequest = serial.created({
context: serial.path,
request: serial.request,
});

const serialResolved = serial.created({
// context: serial.path,
// request: serial.request,
// userRequest: serial.request,
// rawRequest: serial.request,
resource: serial.request,
resolveOptions: serial.identity,
// loaders: serial.loaders,
});

const serialResolvedMap = {
freeze(arg, module, extra) {
const resolved = [];
for (const key in arg) {
const thawedKey = JSON.parse(key);
resolved.push([
serialResolveRequest.freeze(thawedKey, thawedKey, extra),
serialResolved.freeze(arg[key], arg[key], extra),
]);
}
return resolved;
},
thaw(arg, frozen, extra) {
const resolved = {};
for (const item of arg) {
const key = serialResolveRequest.thaw(item[0], item[0], extra);
const value = serialResolved.thaw(item[1], item[1], extra);
resolved[JSON.stringify(key)] = value;
}
return resolved;
},
};

const serialNormalModule4 = serial.serial('NormalModule', {
constructor: serial.constructed(NormalModule, {
data: serial.pipe(
Expand Down Expand Up @@ -124,6 +164,8 @@ const serialNormalModule4 = serial.serial('NormalModule', {
_buildHash: serial.identity,
hash: serial.identity,
_lastSuccessfulBuildMeta: serial.identity,

__hardSource_resolved: serialResolvedMap,
}),

dependencyBlock: serial.dependencyBlock,
Expand Down Expand Up @@ -159,6 +201,9 @@ const needRebuild4 = function() {
}
const fileHashes = this.__hardSourceFileMd5s;
const cachedHashes = this.__hardSourceCachedMd5s;
const resolvedLast = this.__hardSource_resolved;
const missingCache = this.__hardSource_missingCache;

for (const file of this.buildInfo.fileDependencies) {
if (!cachedHashes[file] || fileHashes[file] !== cachedHashes[file]) {
this.cacheItem.invalid = true;
Expand All @@ -173,7 +218,33 @@ const needRebuild4 = function() {
return true;
}
}
return false;

let resolvedNeedRebuild = false;
for (const _resolveKey in resolvedLast) {
const resolveKey = JSON.parse(_resolveKey);
const resolved = resolvedLast[_resolveKey];
let normalId = 'normal';
if (resolved.resolveOptions) {
normalId = `normal-${new nodeObjectHash({ sort: false }).hash(
resolved.resolveOptions,
)}`;
}
const resolvedMissing =
missingCache[normalId] &&
missingCache[normalId][
JSON.stringify([resolveKey.context, resolved.resource.split('?')[0]])
];
if (!resolvedMissing || resolvedMissing.invalid) {
resolved.invalid = true;
resolved.invalidReason = `resolved normal invalid${
resolvedMissing
? ` ${resolvedMissing.invalidReason}`
: ': resolve entry not in cache'
}`;
resolvedNeedRebuild = true;
}
}
return resolvedNeedRebuild;
};

const serialNormalModule3 = serial.serial('NormalModule', {
Expand Down Expand Up @@ -275,6 +346,8 @@ const serialNormalModule3 = serial.serial('NormalModule', {
warnings: serial.moduleWarning,
errors: serial.moduleError,
_source: serial.source,

__hardSource_resolved: serialResolvedMap,
}),

hash: {
Expand Down Expand Up @@ -320,6 +393,9 @@ const needRebuild3 = function() {
}
const fileHashes = this.__hardSourceFileMd5s;
const cachedHashes = this.__hardSourceCachedMd5s;
const resolvedLast = this.__hardSource_resolved;
const missingCache = this.__hardSource_missingCache;

for (const file of this.fileDependencies) {
if (!cachedHashes[file] || fileHashes[file] !== cachedHashes[file]) {
this.cacheItem.invalid = true;
Expand All @@ -334,7 +410,34 @@ const needRebuild3 = function() {
return true;
}
}
return false;

let resolvedNeedRebuild = false;
for (const _resolveKey in resolvedLast) {
const resolveKey = JSON.parse(_resolveKey);
const resolved = resolvedLast[_resolveKey];
let normalId = 'normal';
if (resolved.resolveOptions) {
normalId = `normal-${new nodeObjectHash({ sort: false }).hash(
resolved.resolveOptions,
)}`;
}
const resolvedMissing =
missingCache[normalId] &&
missingCache[normalId][
JSON.stringify([resolveKey.context, resolved.resource.split('?')[0]])
];
if (!resolvedMissing || resolvedMissing.invalid) {
resolved.invalid = true;
resolved.invalidReason = `resolved normal invalid${
resolvedMissing
? ` ${resolvedMissing.invalidReason}`
: ': resolve entry not in cache'
}`;
resolvedNeedRebuild = true;
}
}

return resolvedNeedRebuild;
};

const cacheable = module =>
Expand Down Expand Up @@ -498,6 +601,7 @@ class TransformNormalModulePlugin {
m.cacheItem = frozen;
m.__hardSourceFileMd5s = compilation.__hardSourceFileMd5s;
m.__hardSourceCachedMd5s = compilation.__hardSourceCachedMd5s;
m.__hardSource_missingCache = compiler.__hardSource_missingCache;
m.needRebuild = needRebuild;

// Unbuild if there is no cache. The module will be rebuilt. Not
Expand Down
1 change: 1 addition & 0 deletions lib/util/plugin-compat.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ const knownPluginRegistrations = {
Compilation: {
needAdditionalPass: ['sync', []],
succeedModule: ['sync', ['module']],
buildModule: ['sync', ['module']],
},
Compiler: {
afterCompile: ['asyncSerial', ['compilation']],
Expand Down
3 changes: 3 additions & 0 deletions tests/fixtures/loader-custom-resolve-request/fab/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
module.exports = function(n) {
return n * (n > 1 ? n - 1 : 1);
};
3 changes: 3 additions & 0 deletions tests/fixtures/loader-custom-resolve-request/fib.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
module.exports = function(n) {
return n + (n > 0 ? n - 2 : 0);
};
3 changes: 3 additions & 0 deletions tests/fixtures/loader-custom-resolve-request/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
var fib = require('./loader!./fib');

console.log(fib(3));
7 changes: 7 additions & 0 deletions tests/fixtures/loader-custom-resolve-request/loader.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
module.exports = function(source) {
this.cacheable && this.cacheable();
return new Promise(
(f, e) => this.resolve(this.context, './fab', (er, v) => er ? e(er) : f(v))
)
.then(v => JSON.stringify(v));
};
18 changes: 18 additions & 0 deletions tests/fixtures/loader-custom-resolve-request/webpack.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
var HardSourceWebpackPlugin = require('../../..');

module.exports = {
context: __dirname,
entry: './index.js',
output: {
path: __dirname + '/tmp',
filename: 'main.js',
},
plugins: [
new HardSourceWebpackPlugin({
cacheDirectory: 'cache',
environmentHash: {
root: __dirname + '/../../..',
},
}),
],
};
2 changes: 1 addition & 1 deletion tests/fixtures/plugin-mini-css-extract-change/index.css
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
.hello {
color: blue;
color: red;
}
23 changes: 23 additions & 0 deletions tests/loaders-webpack-3.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,26 @@ describeWP(3)('loader webpack 3 use', function() {
itCompilesTwice('loader-worker-1dep', {exportStats: true});

});

describeWP(3)('loader webpack 3 use - builds changes', function() {

itCompilesChange('loader-custom-resolve-request', {
'fab.js': null,
}, {
'fab.js': 'bar',
}, function(output) {
expect(output.run1['main.js'].toString()).to.match(/fab(\/|\\\\|\\\\\\\\)index\.js/);
expect(output.run2['main.js'].toString()).to.match(/fab\.js/);
});

itCompilesChange('loader-custom-resolve-request', {
'fab.js': 'bar',
}, {
'fab.js': null,
}, function(output) {
expect(output.run1['main.js'].toString()).to.match(/fab\.js/);
expect(output.run2['main.js'].toString()).to.match(/fab(\/|\\\\|\\\\\\\\)index\.js/);
});

});

0 comments on commit f27757a

Please sign in to comment.