Skip to content
This repository was archived by the owner on Oct 27, 2020. It is now read-only.

Always use absolute paths after reading from cache #83

Merged
merged 18 commits into from
Jun 25, 2019
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
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@
"npm-run-all": "^4.1.5",
"prettier": "^1.17.1",
"standard-version": "^6.0.1",
"uuid": "^3.3.2",
"webpack": "^4.33.0",
"webpack-cli": "^3.3.2"
},
Expand Down
34 changes: 29 additions & 5 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,10 @@ function roundMs(mtime, precision) {
return Math.floor(mtime / precision) * precision;
}

// NOTE: We should only apply `pathWithCacheContext` transformations
// right before writing. Every other internal steps with the paths
// should be accomplish over absolute paths. Otherwise we have the risk
// to break watchpack -> chokidar watch logic over webpack@4 --watch
function loader(...args) {
const options = Object.assign({}, defaults, getOptions(this));
validateOptions(schema, options, 'Cache Loader');
Expand Down Expand Up @@ -120,7 +124,10 @@ function loader(...args) {
writeFn(
data.cacheKey,
{
remainingRequest: data.remainingRequest,
remainingRequest: pathWithCacheContext(
options.cacheContext,
data.remainingRequest
),
dependencies: deps,
contextDependencies: contextDeps,
result: args,
Expand All @@ -134,6 +141,10 @@ function loader(...args) {
);
}

// NOTE: We should apply `pathWithCacheContext` transformations
// right after reading. Every other internal steps with the paths
// should be accomplish over absolute paths. Otherwise we have the risk
// to break watchpack -> chokidar watch logic over webpack@4 --watch
function pitch(remainingRequest, prevRequest, dataInput) {
const options = Object.assign({}, defaults, getOptions(this));

Expand All @@ -151,14 +162,20 @@ function pitch(remainingRequest, prevRequest, dataInput) {
const callback = this.async();
const data = dataInput;

data.remainingRequest = pathWithCacheContext(cacheContext, remainingRequest);
data.remainingRequest = remainingRequest;
data.cacheKey = cacheKeyFn(options, data.remainingRequest);
readFn(data.cacheKey, (readErr, cacheData) => {
if (readErr) {
callback();
return;
}
if (cacheData.remainingRequest !== data.remainingRequest) {

// We need to patch every path within data on cache with the cacheContext,
// or it would cause problems when watching
if (
pathWithCacheContext(options.cacheContext, cacheData.remainingRequest) !==
data.remainingRequest
) {
// in case of a hash conflict
callback();
return;
Expand All @@ -167,7 +184,14 @@ function pitch(remainingRequest, prevRequest, dataInput) {
async.each(
cacheData.dependencies.concat(cacheData.contextDependencies),
(dep, eachCallback) => {
FS.stat(dep.path, (statErr, stats) => {
// Applying reverse path transformation, in case they are relatives, when
// reading from cache
const contextDep = {
...dep,
path: pathWithCacheContext(options.cacheContext, dep.path),
};

FS.stat(contextDep.path, (statErr, stats) => {
if (statErr) {
eachCallback(statErr);
return;
Expand All @@ -182,7 +206,7 @@ function pitch(remainingRequest, prevRequest, dataInput) {
}

const compStats = stats;
const compDep = dep;
const compDep = contextDep;
if (precision > 1) {
['atime', 'mtime', 'ctime', 'birthtime'].forEach((key) => {
const msKey = `${key}Ms`;
Expand Down
5 changes: 5 additions & 0 deletions test/__snapshots__/compare-option.test.js.snap
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`compare option should call compare with contextualized dep: errors 1`] = `Array []`;

exports[`compare option should call compare with contextualized dep: warnings 1`] = `Array []`;
17 changes: 14 additions & 3 deletions test/cacheContext-option.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -73,8 +73,14 @@ const buildCacheLoaderCallsData = (calls, normalizePaths = true) =>
).sort(sortData);

describe('cacheContext option', () => {
beforeEach(() => {
mockCacheLoaderWriteFn.mockClear();
});

it('should generate relative paths to the project root', async () => {
const testId = './basic/index.js';
await webpack(testId, mockBaseWebpackConfig);
mockCacheLoaderWriteFn.mockClear();
const stats = await webpack(testId, mockRelativeWebpackConfig);

const cacheLoaderCallsData = buildCacheLoaderCallsData(
Expand All @@ -83,7 +89,8 @@ describe('cacheContext option', () => {

expect(
cacheLoaderCallsData.every(
(call) => !call.remainingRequest.includes(path.resolve('.'))
(call) =>
!call.remainingRequest.includes(normalizePath(path.resolve('.')))
)
).toBeTruthy();
expect(BJSON.stringify(cacheLoaderCallsData, 2)).toMatchSnapshot(
Expand All @@ -95,6 +102,7 @@ describe('cacheContext option', () => {

it('should generate non normalized relative paths to the project root on windows', async () => {
const testId = './basic/index.js';
await webpack(testId, mockBaseWebpackConfig);
await webpack(testId, mockRelativeWebpackConfig);

const cacheLoaderCallsData = buildCacheLoaderCallsData(
Expand Down Expand Up @@ -123,6 +131,8 @@ describe('cacheContext option', () => {

it('should generate absolute paths to the project root', async () => {
const testId = './basic/index.js';
await webpack(testId, mockRelativeWebpackConfig);
mockCacheLoaderWriteFn.mockClear();
const stats = await webpack(testId, mockBaseWebpackConfig);

const cacheLoaderCallsData = buildCacheLoaderCallsData(
Expand All @@ -131,15 +141,16 @@ describe('cacheContext option', () => {

expect(
cacheLoaderCallsData.every((call) =>
call.remainingRequest.includes(path.resolve('.'))
call.remainingRequest.includes(normalizePath(path.resolve('.')))
)
).toBeFalsy();
).toBeTruthy();
expect(stats.compilation.warnings).toMatchSnapshot('warnings');
expect(stats.compilation.errors).toMatchSnapshot('errors');
});

it('should load as a raw loader to support images', async () => {
const testId = './img/index.js';
await webpack(testId, mockRelativeWebpackConfig);
const stats = await webpack(testId, mockBaseWebpackConfig);

const cacheLoaderCallsData = buildCacheLoaderCallsData(
Expand Down
43 changes: 42 additions & 1 deletion test/compare-option.test.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,17 @@
const fs = require('fs');
const path = require('path');

const { webpack } = require('./helpers');
const del = require('del');

const { getRandomTmpDir, webpack } = require('./helpers');

const mockRandomTmpDir = getRandomTmpDir();

const mockCacheLoaderCompareFn = jest.fn();
const mockWebpackConfig = {
loader: {
options: {
cacheDirectory: mockRandomTmpDir,
compare: (stats, dep) => {
mockCacheLoaderCompareFn(stats, dep);
return true;
Expand All @@ -14,9 +20,27 @@ const mockWebpackConfig = {
},
};

const mockCacheLoaderCompareOnRelativeFn = jest.fn();
const mockRelativeWebpackConfig = {
loader: {
options: {
cacheContext: path.resolve('./'),
cacheDirectory: mockRandomTmpDir,
compare: (stats, dep) => {
mockCacheLoaderCompareOnRelativeFn(stats, dep);
return true;
},
},
},
};
describe('compare option', () => {
beforeEach(() => {
mockCacheLoaderCompareFn.mockClear();
mockCacheLoaderCompareOnRelativeFn.mockClear();
});

afterAll(() => {
del.sync(mockRandomTmpDir);
});

it('should call compare function', async () => {
Expand Down Expand Up @@ -50,4 +74,21 @@ describe('compare option', () => {
expect(dep.mtime).toBeDefined();
expect(dep.path).toBeDefined();
});

it('should call compare with contextualized dep', async () => {
const testId = './basic/index.js';
await webpack(testId, mockWebpackConfig);
await webpack(testId, mockRelativeWebpackConfig);
mockCacheLoaderCompareFn.mockClear();

const stats = await webpack(testId, mockRelativeWebpackConfig);

expect(stats.compilation.warnings).toMatchSnapshot('warnings');
expect(stats.compilation.errors).toMatchSnapshot('errors');
expect(mockCacheLoaderCompareOnRelativeFn).toHaveBeenCalled();

// eslint-disable-next-line
const dep = mockCacheLoaderCompareOnRelativeFn.mock.calls[0][1];
expect(path.isAbsolute(dep.path)).toBeTruthy();
});
});
9 changes: 8 additions & 1 deletion test/helpers.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
const os = require('os');
const path = require('path');

const del = require('del');
const webpack = require('webpack');
const MemoryFS = require('memory-fs');
const uuidV4 = require('uuid/v4');
const webpack = require('webpack');

const moduleConfig = (config) => {
return {
Expand Down Expand Up @@ -91,6 +93,11 @@ function compile(fixture, config = {}, options = {}) {
);
}

function getRandomTmpDir() {
return path.resolve(os.tmpdir(), `test_${uuidV4()}`);
}

module.exports = {
getRandomTmpDir,
webpack: compile,
};
12 changes: 11 additions & 1 deletion test/precision-option.test.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,15 @@
const { webpack } = require('./helpers');
const del = require('del');

const { getRandomTmpDir, webpack } = require('./helpers');

const mockRandomTmpDir = getRandomTmpDir();

const mockCacheLoaderCompareFn = jest.fn();
const mockCacheLoaderCompareWithPrecisionFn = jest.fn();
const mockWebpackConfig = {
loader: {
options: {
cacheDirectory: mockRandomTmpDir,
compare: (stats, dep) => {
mockCacheLoaderCompareFn(stats, dep);
return true;
Expand All @@ -15,6 +20,7 @@ const mockWebpackConfig = {
const mockWebpackWithPrecisionConfig = {
loader: {
options: {
cacheDirectory: mockRandomTmpDir,
compare: (stats, dep) => {
mockCacheLoaderCompareWithPrecisionFn(stats, dep);
return true;
Expand All @@ -30,6 +36,10 @@ describe('precision option', () => {
mockCacheLoaderCompareWithPrecisionFn.mockClear();
});

afterAll(() => {
del.sync(mockRandomTmpDir);
});

it('should not apply precision', async () => {
const testId = './basic/index.js';
await webpack(testId, mockWebpackConfig);
Expand Down