orionsoft-react-scripts
Version:
Orionsoft Configuration and scripts for Create React App.
305 lines (255 loc) • 8.08 kB
JavaScript
/**
* Copyright (c) 2014-present, Facebook, Inc. All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
*
*/
'use strict';
const createDirectory = require('jest-util').createDirectory;
const crypto = require('crypto');
const fileExists = require('jest-file-exists');
const fs = require('graceful-fs');
const getCacheFilePath = require('jest-haste-map').getCacheFilePath;
const path = require('path');
const shouldInstrument = require('./shouldInstrument');
const stableStringify = require('json-stable-stringify');
const vm = require('vm');
const VERSION = require('../package.json').version;
const EVAL_RESULT_VARIABLE = 'Object.<anonymous>';
const cache = new Map();
const configToJsonMap = new Map();
// Cache regular expressions to test whether the file needs to be preprocessed
const ignoreCache = new WeakMap();
const removeFile = path => {
try {
fs.unlinkSync(path);
} catch (e) {}
};
const getCacheKey = (
fileData,
filePath,
config,
instrument) =>
{
if (!configToJsonMap.has(config)) {
// We only need this set of config options that can likely influence
// cached output instead of all config options.
configToJsonMap.set(config, stableStringify({
cacheDirectory: config.cacheDirectory,
collectCoverage: config.collectCoverage,
collectCoverageFrom: config.collectCoverageFrom,
collectCoverageOnlyFrom: config.collectCoverageOnlyFrom,
coveragePathIgnorePatterns: config.coveragePathIgnorePatterns,
haste: config.haste,
mocksPattern: config.mocksPattern,
moduleFileExtensions: config.moduleFileExtensions,
moduleNameMapper: config.moduleNameMapper,
preprocessorIgnorePatterns: config.preprocessorIgnorePatterns,
rootDir: config.rootDir,
testPathDirs: config.testPathDirs,
testRegex: config.testRegex }));
}
const confStr = configToJsonMap.get(config) || '';
const preprocessor = getPreprocessor(config);
if (preprocessor && typeof preprocessor.getCacheKey === 'function') {
return preprocessor.getCacheKey(fileData, filePath, confStr, { instrument });
} else {
return crypto.createHash('md5').
update(fileData).
update(confStr).
update(instrument ? 'instrument' : '').
digest('hex');
}
};
const writeCacheFile = (cachePath, fileData) => {
try {
fs.writeFileSync(cachePath, fileData, 'utf8');
} catch (e) {
e.message = 'jest: failed to cache transform results in: ' + cachePath;
removeFile(cachePath);
throw e;
}
};
const wrap = content => '({"' +
EVAL_RESULT_VARIABLE +
'":function(module,exports,require,__dirname,__filename,global,jest){' +
content +
'\n}});';
const readCacheFile = (filePath, cachePath) => {
if (!fileExists(cachePath)) {
return null;
}
let fileData;
try {
fileData = fs.readFileSync(cachePath, 'utf8');
} catch (e) {
e.message = 'jest: failed to read cache file: ' + cachePath;
removeFile(cachePath);
throw e;
}
if (fileData == null) {
// We must have somehow created the file but failed to write to it,
// let's delete it and retry.
removeFile(cachePath);
}
return fileData;
};
const getScriptCacheKey = (filename, config, instrument) => {
const mtime = fs.statSync(filename).mtime;
return filename + '_' + mtime.getTime() + (
instrument ? '_instrumented' : '');
};
const shouldPreprocess = (filename, config) => {
if (!ignoreCache.has(config)) {
if (!config.preprocessorIgnorePatterns) {
ignoreCache.set(config, null);
} else {
ignoreCache.set(
config,
new RegExp(config.preprocessorIgnorePatterns.join('|')));
}
}
const ignoreRegexp = ignoreCache.get(config);
const isIgnored = ignoreRegexp ? ignoreRegexp.test(filename) : false;
return !!config.scriptPreprocessor && (
!config.preprocessorIgnorePatterns.length ||
!isIgnored);
};
const getFileCachePath = (
filename,
config,
content,
instrument) =>
{
const baseCacheDir = getCacheFilePath(
config.cacheDirectory,
'jest-transform-cache-' + config.name,
VERSION);
const cacheKey = getCacheKey(content, filename, config, instrument);
// Create sub folders based on the cacheKey to avoid creating one
// directory with many files.
const cacheDir = path.join(baseCacheDir, cacheKey[0] + cacheKey[1]);
const cachePath = path.join(
cacheDir,
path.basename(filename, path.extname(filename)) + '_' + cacheKey);
createDirectory(cacheDir);
return cachePath;
};
const preprocessorCache = new WeakMap();
const getPreprocessor = config => {
if (preprocessorCache.has(config)) {
return preprocessorCache.get(config);
} else {
let preprocessor;
if (!config.scriptPreprocessor) {
preprocessor = null;
} else {
// $FlowFixMe
preprocessor = require(config.scriptPreprocessor);
if (typeof preprocessor.process !== 'function') {
throw new TypeError(
'Jest: a preprocessor must export a `process` function.');
}
}
preprocessorCache.set(config, preprocessor);
return preprocessor;
}
};
const stripShebang = content => {
// If the file data starts with a shebang remove it. Leaves the empty line
// to keep stack trace line numbers correct.
if (content.startsWith('#!')) {
return content.replace(/^#!.*/, '');
} else {
return content;
}
};
const instrumentFile = (
content,
filename,
config) =>
{
// NOTE: Keeping these requires inside this function reduces a single run
// time by 2sec if not running in `--coverage` mode
const babel = require('babel-core');
const babelPluginIstanbul = require('babel-plugin-istanbul').default;
return babel.transform(content, {
filename,
auxiliaryCommentBefore: ' istanbul ignore next ',
plugins: [
[
babelPluginIstanbul,
{
exclude: [],
cwd: config.rootDir }]],
retainLines: true,
babelrc: false }).
code;
};
const transformSource = (
filename,
config,
content,
instrument) =>
{
const preprocessor = getPreprocessor(config);
const cacheFilePath = getFileCachePath(filename, config, content, instrument);
// Ignore cache if `config.cache` is set (--no-cache)
let result = config.cache ? readCacheFile(filename, cacheFilePath) : null;
if (result) {
return result;
}
result = content;
if (preprocessor && shouldPreprocess(filename, config)) {
result = preprocessor.process(result, filename, config, { instrument });
}
// That means that the preprocessor has a custom instrumentation
// logic and will handle it based on `config.collectCoverage` option
const preprocessorWillInstrument = preprocessor && preprocessor.canInstrument;
if (!preprocessorWillInstrument && instrument) {
result = instrumentFile(result, filename, config);
}
writeCacheFile(cacheFilePath, result);
return result;
};
const transformAndBuildScript = (
filename,
config,
options,
instrument) =>
{
const isInternalModule = !!(options && options.isInternalModule);
const content = stripShebang(fs.readFileSync(filename, 'utf8'));
let wrappedResult;
if (
!isInternalModule && (
shouldPreprocess(filename, config) || instrument))
{
wrappedResult =
wrap(transformSource(filename, config, content, instrument));
} else {
wrappedResult = wrap(content);
}
return new vm.Script(wrappedResult, { displayErrors: true, filename });
};
module.exports = (
filename,
config,
options) =>
{
const instrument = shouldInstrument(filename, config);
const scriptCacheKey = getScriptCacheKey(filename, config, instrument);
let script = cache.get(scriptCacheKey);
if (script) {
return script;
} else {
script = transformAndBuildScript(filename, config, options, instrument);
cache.set(scriptCacheKey, script);
return script;
}
};
module.exports.EVAL_RESULT_VARIABLE = EVAL_RESULT_VARIABLE;
module.exports.transformSource = transformSource;