123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301 |
- "use strict";
- Object.defineProperty(exports, "__esModule", {
- value: true
- });
- exports.default = loader;
- exports.pitch = pitch;
- exports.raw = void 0;
- /* eslint-disable
- import/order
- */
- const fs = require('fs');
- const os = require('os');
- 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');
- const pkg = require('../package.json');
- const env = process.env.NODE_ENV || 'development';
- const schema = require('./options.json');
- const defaults = {
- cacheContext: '',
- cacheDirectory: findCacheDir({
- name: 'cache-loader'
- }) || os.tmpdir(),
- cacheIdentifier: `cache-loader:${pkg.version} ${env}`,
- cacheKey,
- compare,
- precision: 0,
- read,
- readOnly: false,
- write
- };
- function pathWithCacheContext(cacheContext, originalPath) {
- if (!cacheContext) {
- return originalPath;
- }
- if (originalPath.includes(cacheContext)) {
- return originalPath.split('!').map(subPath => path.relative(cacheContext, subPath)).join('!');
- }
- return originalPath.split('!').map(subPath => path.resolve(cacheContext, subPath)).join('!');
- }
- 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, {
- name: 'Cache Loader',
- baseDataPath: 'options'
- });
- const {
- readOnly,
- write: writeFn
- } = options; // In case we are under a readOnly mode on cache-loader
- // we don't want to write or update any cache file
- if (readOnly) {
- this.callback(null, ...args);
- return;
- }
- const callback = this.async();
- const {
- data
- } = this;
- const dependencies = this.getDependencies().concat(this.loaders.map(l => l.path));
- const contextDependencies = this.getContextDependencies(); // Should the file get cached?
- let cache = true; // this.fs can be undefined
- // e.g when using the thread-loader
- // fallback to the fs module
- const FS = this.fs || fs;
- const toDepDetails = (dep, mapCallback) => {
- FS.stat(dep, (err, stats) => {
- if (err) {
- mapCallback(err);
- return;
- }
- const mtime = stats.mtime.getTime();
- if (mtime / 1000 >= Math.floor(data.startTime / 1000)) {
- // Don't trust mtime.
- // File was changed while compiling
- // or it could be an inaccurate filesystem.
- cache = false;
- }
- mapCallback(null, {
- path: pathWithCacheContext(options.cacheContext, dep),
- mtime
- });
- });
- };
- async.parallel([cb => async.mapLimit(dependencies, 20, toDepDetails, cb), cb => async.mapLimit(contextDependencies, 20, toDepDetails, cb)], (err, taskResults) => {
- if (err) {
- callback(null, ...args);
- return;
- }
- if (!cache) {
- callback(null, ...args);
- return;
- }
- const [deps, contextDeps] = taskResults;
- writeFn(data.cacheKey, {
- remainingRequest: pathWithCacheContext(options.cacheContext, data.remainingRequest),
- dependencies: deps,
- contextDependencies: contextDeps,
- result: args
- }, () => {
- // ignore errors here
- callback(null, ...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));
- validateOptions(schema, options, {
- name: 'Cache Loader (Pitch)',
- baseDataPath: 'options'
- });
- const {
- cacheContext,
- cacheKey: cacheKeyFn,
- compare: compareFn,
- read: readFn,
- readOnly,
- precision
- } = options;
- const callback = this.async();
- const data = dataInput;
- data.remainingRequest = remainingRequest;
- data.cacheKey = cacheKeyFn(options, data.remainingRequest);
- readFn(data.cacheKey, (readErr, cacheData) => {
- if (readErr) {
- callback();
- return;
- } // 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;
- }
- const FS = this.fs || fs;
- async.each(cacheData.dependencies.concat(cacheData.contextDependencies), (dep, eachCallback) => {
- // 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;
- } // When we are under a readOnly config on cache-loader
- // we don't want to emit any other error than a
- // file stat error
- if (readOnly) {
- eachCallback();
- return;
- }
- const compStats = stats;
- const compDep = contextDep;
- if (precision > 1) {
- ['atime', 'mtime', 'ctime', 'birthtime'].forEach(key => {
- const msKey = `${key}Ms`;
- const ms = roundMs(stats[msKey], precision);
- compStats[msKey] = ms;
- compStats[key] = new Date(ms);
- });
- compDep.mtime = roundMs(dep.mtime, precision);
- } // If the compare function returns false
- // we not read from cache
- if (compareFn(compStats, compDep) !== true) {
- eachCallback(true);
- return;
- }
- eachCallback();
- });
- }, err => {
- if (err) {
- data.startTime = Date.now();
- callback();
- return;
- }
- cacheData.dependencies.forEach(dep => this.addDependency(pathWithCacheContext(cacheContext, dep.path)));
- cacheData.contextDependencies.forEach(dep => this.addContextDependency(pathWithCacheContext(cacheContext, dep.path)));
- callback(null, ...cacheData.result);
- });
- });
- }
- function digest(str) {
- 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);
- if (directories.has(dirname)) {
- // for performance skip creating directory
- fs.writeFile(key, content, 'utf-8', callback);
- } else {
- mkdirp(dirname, mkdirErr => {
- if (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) => {
- if (err) {
- callback(err);
- return;
- }
- try {
- const data = BJSON.parse(content);
- callback(null, data);
- } catch (e) {
- callback(e);
- }
- });
- }
- function cacheKey(options, request) {
- const {
- cacheIdentifier,
- cacheDirectory
- } = options;
- const hash = digest(`${cacheIdentifier}\n${request}`);
- return path.join(cacheDirectory, `${hash}.json`);
- }
- function compare(stats, dep) {
- return stats.mtime.getTime() === dep.mtime;
- }
- const raw = true;
- exports.raw = raw;
|