Last active
June 29, 2017 07:26
-
-
Save a-ignatov-parc/2804f7f20a9e328e6719ae621d5cefb3 to your computer and use it in GitHub Desktop.
Jest integration with webpack
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// jest/module-transformer.js | |
'use strict'; | |
const fs = require('fs'); | |
const path = require('path'); | |
const crypto = require('crypto'); | |
const { execSync } = require('child_process'); | |
const hashFiles = require('hash-files'); | |
const { resolveFromRoot } = require('../utils/webpack'); | |
const resolveDependencies = require('./resolve-dependencies'); | |
const webpackPath = require.resolve('webpack/bin/webpack'); | |
const webpackConfigPath = require.resolve('./webpack.config'); | |
const rootPath = resolveFromRoot('.'); | |
/** | |
* Чтоб небыло рейс-кондишена на запись файлов при сборке модулей из разных процессов | |
* делаем для каждого процесса уникальную директорию для артефактов на основе его pid. | |
*/ | |
const outputPath = resolveFromRoot('./tmp/jest-webpack-artifacts/' + process.pid); | |
const entityRegex = /\.spec\.js$/; | |
/** | |
* Резолвим все зависимости вебпаковского конфига чтоб можно было посчитать хеш от всего | |
* что может повлиять на результат сборки модуля. | |
*/ | |
const webpackFiles = resolveDependencies(webpackConfigPath); | |
const webpackSignature = hashFiles.sync({ | |
files: webpackFiles, | |
algorithm: 'md5', | |
noGlob: true, | |
}); | |
module.exports = { | |
process(source, filepath) { | |
const entity = path.relative(rootPath, filepath); | |
const entityName = entity.replace(entityRegex, ''); | |
/** | |
* Так как jest может резолвить зависимости только синхронно (https://github.com/facebook/jest/issues/2711), | |
* то приходится прибегнуть к трюку с запуском сборки асинхронного вебпака | |
* с помощью `execSync` процесса ноды и последующим считыванием артефакта сборки. | |
*/ | |
try { | |
execSync(`NODE_ENV=test ${webpackPath} --config ${webpackConfigPath} --output-path ${outputPath} ${entityName}=${filepath}`, { | |
/** | |
* - stdin (stdio[0]) родителя пайпим в stdin потомка. | |
* - stdout (stdio[1]) потомка никуда не выводим. Нет смысла выводить | |
* логирование вебпака в основную консоль, только лишний мусор. | |
* - stderr (stdio[2]) потомка пайпим в stderr родителя. | |
* | |
* Подробнее тут: https://nodejs.org/api/child_process.html#child_process_options_stdio | |
*/ | |
stdio: ['pipe', 'ignore', 'pipe'], | |
}); | |
} catch (error) { | |
/** | |
* С ошибкой в данном месте ничего не нужно делать так как `execSync` | |
* сконфигурирован через `stdio`, чтоб все ошибки пайпились в родительский | |
* процесс. При этом в jest есть проблема что если не указать try..catch, | |
* то ошибка не будут отображена. | |
*/ | |
} | |
return fs.readFileSync(path.resolve(outputPath, `${entityName}.js`), 'utf-8'); | |
}, | |
getCacheKey(source, filepath) { | |
return crypto | |
.createHash('md5') | |
.update(webpackSignature + source, 'utf8') | |
.digest('hex'); | |
}, | |
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
{ | |
"jest": { | |
"transform": { | |
".*": "<rootDir>/jest/module-transformer.js" | |
}, | |
"moduleNameMapper": { | |
"^jest/(.*)": "<rootDir>/jest/$1" | |
}, | |
"testEnvironment": "node" | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// jest/resolve-dependencies.js | |
'use strict'; | |
const path = require('path'); | |
const precinct = require('precinct'); | |
module.exports = function resolveDependencies(filePath) { | |
const fileDir = path.dirname(filePath); | |
return precinct | |
.paperwork(filePath, {includeCore: false}) | |
.map(moduleImport => path.resolve(fileDir, moduleImport)) | |
.map(modulePath => { | |
try { | |
return require.resolve(modulePath); | |
} catch(error) { | |
return null; | |
} | |
}) | |
.filter(Boolean) | |
.reduce((result, modulePath) => { | |
return result.concat(modulePath, resolveDependencies(modulePath)); | |
}, []) | |
.filter((modulePath, i, list) => list.indexOf(modulePath) === i); | |
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// jest/webpack.config.js | |
'use strict'; | |
const config = require('../site/webpack.config'); | |
const { resolveFromRoot } = require('../utils/webpack'); | |
const rootPath = resolveFromRoot('.'); | |
// Нас интересует только серверный конфиг. | |
const [serverConfig] = config.filter(compilation => compilation.target === 'node'); | |
serverConfig.externals = [ | |
function ResolveModules(context, request, callback) { | |
const parts = request.split('/'); | |
if (serverConfig.resolve.alias[parts[0]]) { | |
parts[0] = serverConfig.resolve.alias[parts[0]]; | |
return callback(null, parts.join('/')); | |
} | |
if (context !== rootPath) { | |
return callback(null, 'commonjs ' + request); | |
} | |
callback(); | |
}, | |
]; | |
// Сбрасываем все параметры. Они у нас будут приходить из аргументов запуска вебпака. | |
serverConfig.entry = null; | |
// Изменяем путь куда будет собираться бандл, чтоб он ни в коем случае | |
// не попал в артефакты. | |
serverConfig.output.path = resolveFromRoot('./tmp'); | |
module.exports = serverConfig; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment