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

add cacheAddedFiles option #109

Open
wants to merge 13 commits into
base: master
Choose a base branch
from
6 changes: 2 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ module.exports = {

| Name | Type | n Default | Description |
| :-------------------: | :----------------------------------------------: | :-----------------------------------------------------: | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **`cacheAddedFiles`** | `{Boolean}` | `false` | Allow to caches the added files. By default it's not cached. |
| **`cacheContext`** | `{String}` | `undefined` | Allows you to override the default cache context in order to generate the cache relatively to a path. By default it will use absolute paths |
| **`cacheKey`** | `{Function(options, request) -> {String}}` | `undefined` | Allows you to override default cache key generator |
| **`cacheDirectory`** | `{String}` | `findCacheDir({ name: 'cache-loader' }) or os.tmpdir()` | Provide a cache directory where cache items should be stored (used for default read/write implementation) |
Expand Down Expand Up @@ -94,10 +95,7 @@ const crypto = require('crypto');
const BUILD_CACHE_TIMEOUT = 24 * 3600; // 1 day

function digest(str) {
return crypto
.createHash('md5')
.update(str)
.digest('hex');
return crypto.createHash('md5').update(str).digest('hex');
}

// Generate own cache key
Expand Down
18,234 changes: 11,187 additions & 7,047 deletions package-lock.json

Large diffs are not rendered by default.

62 changes: 31 additions & 31 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,46 +34,46 @@
"dist"
],
"peerDependencies": {
"webpack": "^4.0.0"
"webpack": "^4.44.1"
},
"dependencies": {
"buffer-json": "^2.0.0",
"find-cache-dir": "^3.0.0",
"loader-utils": "^1.2.3",
"mkdirp": "^0.5.1",
"neo-async": "^2.6.1",
"schema-utils": "^2.0.0"
"find-cache-dir": "^3.3.1",
"loader-utils": "^2.0.0",
"mkdirp": "^1.0.4",
"neo-async": "^2.6.2",
"schema-utils": "^2.7.0"
},
"devDependencies": {
"@babel/cli": "^7.5.5",
"@babel/core": "^7.5.5",
"@babel/preset-env": "^7.5.5",
"@commitlint/cli": "^8.1.0",
"@commitlint/config-conventional": "^8.1.0",
"@webpack-contrib/defaults": "^5.0.2",
"@babel/cli": "^7.10.5",
"@babel/core": "^7.11.1",
"@babel/preset-env": "^7.11.0",
"@commitlint/cli": "^9.1.2",
"@commitlint/config-conventional": "^10.0.0",
"@webpack-contrib/defaults": "^6.3.0",
"@webpack-contrib/eslint-config-webpack": "^3.0.0",
"babel-jest": "^24.8.0",
"babel-loader": "^8.0.6",
"commitlint-azure-pipelines-cli": "^1.0.2",
"cross-env": "^5.2.0",
"del": "^5.0.0",
"del-cli": "^2.0.0",
"eslint": "^6.0.1",
"eslint-config-prettier": "^6.0.0",
"eslint-plugin-import": "^2.18.0",
"file-loader": "^4.1.0",
"husky": "^3.0.0",
"jest": "^24.8.0",
"jest-junit": "^6.4.0",
"lint-staged": "^9.2.0",
"memory-fs": "^0.4.1",
"babel-jest": "^26.3.0",
"babel-loader": "^8.1.0",
"commitlint-azure-pipelines-cli": "^1.0.3",
"cross-env": "^7.0.2",
"del": "^5.1.0",
"del-cli": "^3.0.1",
"eslint": "^7.7.0",
"eslint-config-prettier": "^6.11.0",
"eslint-plugin-import": "^2.22.0",
"file-loader": "^6.0.0",
"husky": "^4.2.5",
"jest": "^25.5.4",
"jest-junit": "^11.1.0",
"lint-staged": "^10.2.11",
"memory-fs": "^0.5.0",
"normalize-path": "^3.0.0",
"npm-run-all": "^4.1.5",
"prettier": "^1.18.2",
"standard-version": "^6.0.1",
"uuid": "^3.3.2",
"prettier": "^2.0.5",
"standard-version": "^9.0.0",
"uuid": "^8.3.0",
"webpack": "^4.36.1",
"webpack-cli": "^3.3.6"
"webpack-cli": "^3.3.12"
},
"keywords": [
"webpack"
Expand Down
52 changes: 31 additions & 21 deletions src/index.js
Original file line number Diff line number Diff line change
@@ -1,14 +1,12 @@
/* eslint-disable
import/order
*/
/* eslint-disable import/order */
const fs = require('fs');
const os = require('os');
const v8 = require('v8');
const path = require('path');
const async = require('neo-async');
const crypto = require('crypto');
const mkdirp = require('mkdirp');
const findCacheDir = require('find-cache-dir');
const BJSON = require('buffer-json');

const { getOptions } = require('loader-utils');
const validateOptions = require('schema-utils');
Expand Down Expand Up @@ -134,6 +132,7 @@ function loader(...args) {
),
dependencies: deps,
contextDependencies: contextDeps,
addedFiles: data.addedFiles,
result: args,
},
() => {
Expand Down Expand Up @@ -168,9 +167,19 @@ function pitch(remainingRequest, prevRequest, dataInput) {

const callback = this.async();
const data = dataInput;
const emitFile = this.emitFile.bind(this);

data.remainingRequest = remainingRequest;
data.cacheKey = cacheKeyFn(options, data.remainingRequest);

data.addedFiles = [];
if (options.cacheAddedFiles) {
this.emitFile = (name, content, sourceMap, assetInfo) => {
data.addedFiles.push({ name, content, sourceMap, assetInfo });
return emitFile(name, content, sourceMap, assetInfo);
};
}

readFn(data.cacheKey, (readErr, cacheData) => {
if (readErr) {
callback();
Expand Down Expand Up @@ -249,51 +258,52 @@ function pitch(remainingRequest, prevRequest, dataInput) {
pathWithCacheContext(cacheContext, dep.path)
)
);
cacheData.addedFiles.forEach(
({ name, content, sourceMap, assetInfo }) => {
emitFile(name, content, sourceMap, assetInfo);
}
);
callback(null, ...cacheData.result);
}
);
});
}

function digest(str) {
return crypto
.createHash('md5')
.update(str)
.digest('hex');
return crypto.createHash('md5').update(str).digest('hex');
}

const directories = new Set();

function write(key, data, callback) {
const dirname = path.dirname(key);
const content = BJSON.stringify(data);
const content = v8.serialize(data);

if (directories.has(dirname)) {
// for performance skip creating directory
fs.writeFile(key, content, 'utf-8', callback);
fs.writeFile(key, content, callback);
} else {
mkdirp(dirname, (mkdirErr) => {
if (mkdirErr) {
mkdirp(dirname).then(
() => {
directories.add(dirname);
fs.writeFile(key, content, callback);
},
(mkdirErr) => {
callback(mkdirErr);
return;
}

directories.add(dirname);

fs.writeFile(key, content, 'utf-8', callback);
});
);
}
}

function read(key, callback) {
fs.readFile(key, 'utf-8', (err, content) => {
fs.readFile(key, (err, content) => {
if (err) {
callback(err);
return;
}

try {
const data = BJSON.parse(content);
const data = v8.deserialize(content);
callback(null, data);
} catch (e) {
callback(e);
Expand All @@ -305,7 +315,7 @@ function cacheKey(options, request) {
const { cacheIdentifier, cacheDirectory } = options;
const hash = digest(`${cacheIdentifier}\n${request}`);

return path.join(cacheDirectory, `${hash}.json`);
return path.join(cacheDirectory, `${hash}.bin`);
}

function compare(stats, dep) {
Expand Down
4 changes: 4 additions & 0 deletions src/options.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
{
"type": "object",
"properties": {
"cacheAddedFiles": {
"description": "Allow to caches the added files. By default it's not cached.",
"type": "boolean"
},
"cacheContext": {
"description": "The default cache context in order to generate the cache relatively to a path. By default it will use absolute paths.",
"type": "string"
Expand Down
17 changes: 17 additions & 0 deletions test/__snapshots__/cacheAddedFiles-option.test.js.snap
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`cacheAddedFiles option should cache added files: errors 1`] = `Array []`;

exports[`cacheAddedFiles option should cache added files: errors 2`] = `Array []`;

exports[`cacheAddedFiles option should cache added files: warnings 1`] = `Array []`;

exports[`cacheAddedFiles option should cache added files: warnings 2`] = `Array []`;

exports[`cacheAddedFiles option should not cache added files: errors 1`] = `Array []`;

exports[`cacheAddedFiles option should not cache added files: errors 2`] = `Array []`;

exports[`cacheAddedFiles option should not cache added files: warnings 1`] = `Array []`;

exports[`cacheAddedFiles option should not cache added files: warnings 2`] = `Array []`;
3 changes: 3 additions & 0 deletions test/__snapshots__/cacheContext-option.test.js.snap
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ exports[`cacheContext option should generate relative paths to the project root:
}
],
\\"contextDependencies\\": [],
\\"addedFiles\\": [],
\\"result\\": [
{
\\"type\\": \\"Buffer\\",
Expand All @@ -41,6 +42,7 @@ exports[`cacheContext option should generate relative paths to the project root:
}
],
\\"contextDependencies\\": [],
\\"addedFiles\\": [],
\\"result\\": [
{
\\"type\\": \\"Buffer\\",
Expand All @@ -61,6 +63,7 @@ exports[`cacheContext option should generate relative paths to the project root:
}
],
\\"contextDependencies\\": [],
\\"addedFiles\\": [],
\\"result\\": [
{
\\"type\\": \\"Buffer\\",
Expand Down
12 changes: 6 additions & 6 deletions test/__snapshots__/validate-options.test.js.snap
Original file line number Diff line number Diff line change
@@ -1,25 +1,25 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`validate options error (pitch) 1`] = `
"Invalid options object. Cache Loader (Pitch) has been initialised using an options object that does not match the API schema.
"Invalid options object. Cache Loader (Pitch) has been initialized using an options object that does not match the API schema.
- options.cacheIdentifier should be a string.
-> Provide an invalidation identifier which is used to generate the hashes. You can use it for extra dependencies of loaders (used for default read/write implementation)."
`;

exports[`validate options error 1`] = `
"Invalid options object. Cache Loader has been initialised using an options object that does not match the API schema.
"Invalid options object. Cache Loader has been initialized using an options object that does not match the API schema.
- options.cacheIdentifier should be a string.
-> Provide an invalidation identifier which is used to generate the hashes. You can use it for extra dependencies of loaders (used for default read/write implementation)."
`;

exports[`validate options unknown (pitch) 1`] = `
"Invalid options object. Cache Loader (Pitch) has been initialised using an options object that does not match the API schema.
"Invalid options object. Cache Loader (Pitch) has been initialized using an options object that does not match the API schema.
- options has an unknown property 'unknown'. These properties are valid:
object { cacheContext?, cacheKey?, cacheIdentifier?, cacheDirectory?, compare?, precision?, read?, readOnly?, write? }"
object { cacheAddedFiles?, cacheContext?, cacheKey?, cacheIdentifier?, cacheDirectory?, compare?, precision?, read?, readOnly?, write? }"
`;

exports[`validate options unknown 1`] = `
"Invalid options object. Cache Loader has been initialised using an options object that does not match the API schema.
"Invalid options object. Cache Loader has been initialized using an options object that does not match the API schema.
- options has an unknown property 'unknown'. These properties are valid:
object { cacheContext?, cacheKey?, cacheIdentifier?, cacheDirectory?, compare?, precision?, read?, readOnly?, write? }"
object { cacheAddedFiles?, cacheContext?, cacheKey?, cacheIdentifier?, cacheDirectory?, compare?, precision?, read?, readOnly?, write? }"
`;
68 changes: 68 additions & 0 deletions test/cacheAddedFiles-option.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
const del = require('del');

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

const mockRandomTmpDir = getRandomTmpDir();
const mockRandomTmpDirForAddedFiles = getRandomTmpDir();

const mockWebpackConfig = {
loader: {
options: {
cacheDirectory: mockRandomTmpDir,
},
},
};
const mockWebpackWithAddedFilesConfig = {
loader: {
options: {
cacheDirectory: mockRandomTmpDirForAddedFiles,
cacheAddedFiles: true,
},
},
};

describe('cacheAddedFiles option', () => {
beforeEach(() => {});

afterAll(() => {
del.sync(mockRandomTmpDir, { force: true });
del.sync(mockRandomTmpDirForAddedFiles, { force: true });
});

let cachedImageFilename;

it('should not cache added files', async () => {
const testId = './img/index.js';
const stats = await webpack(testId, mockWebpackConfig);
const cachedStats = await webpack(testId, mockWebpackConfig);

cachedImageFilename = Object.keys(stats.compilation.assets).find((c) =>
c.endsWith('.png')
);

expect(stats.compilation.warnings).toMatchSnapshot('warnings');
expect(stats.compilation.errors).toMatchSnapshot('errors');
expect(cachedStats.compilation.warnings).toMatchSnapshot('warnings');
expect(cachedStats.compilation.errors).toMatchSnapshot('errors');

expect(stats.compilation.assets[cachedImageFilename]).toBeDefined();
expect(
cachedStats.compilation.assets[cachedImageFilename]
).not.toBeDefined();
});

it('should cache added files', async () => {
const testId = './img/index.js';
const stats = await webpack(testId, mockWebpackWithAddedFilesConfig);
const cachedStats = await webpack(testId, mockWebpackWithAddedFilesConfig);

expect(stats.compilation.warnings).toMatchSnapshot('warnings');
expect(stats.compilation.errors).toMatchSnapshot('errors');
expect(cachedStats.compilation.warnings).toMatchSnapshot('warnings');
expect(cachedStats.compilation.errors).toMatchSnapshot('errors');

expect(stats.compilation.assets[cachedImageFilename]).toEqual(
cachedStats.compilation.assets[cachedImageFilename]
);
});
});
2 changes: 1 addition & 1 deletion test/compare-option.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ describe('compare option', () => {
});

afterAll(() => {
del.sync(mockRandomTmpDir);
del.sync(mockRandomTmpDir, { force: true });
});

it('should call compare function', async () => {
Expand Down
2 changes: 1 addition & 1 deletion test/helpers.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ const path = require('path');

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

const moduleConfig = (config) => {
Expand Down