UNPKG

orionsoft-react-scripts

Version:

Orionsoft Configuration and scripts for Create React App.

305 lines (255 loc) 8.08 kB
/** * 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;