├── .gitignore ├── .travis.yml ├── README.md ├── appveyor.yml ├── fixtures ├── .babelrc ├── assets-built-plugin.js ├── cache-blocks-change │ ├── package.json │ ├── src │ │ ├── block.js │ │ ├── entry.js │ │ └── entry.test.js │ └── webpack.config.js ├── cache-multiple-loaders-change │ ├── package.json │ ├── src │ │ ├── entry.js │ │ └── entry.test.js │ └── webpack.config.js ├── config-bail │ ├── package.json │ ├── src │ │ ├── entry.js │ │ ├── entry.test.js │ │ ├── entry2.test.js │ │ ├── entry3.test.js │ │ └── entry4.test.js │ └── webpack.config.js ├── config-testMatch │ ├── package.json │ ├── src │ │ ├── entry.js │ │ ├── entry.test.js │ │ └── entry.test2.js │ └── webpack.config.js ├── flags-bail │ ├── package.json │ ├── src │ │ ├── entry.js │ │ ├── entry.test.js │ │ ├── entry2.test.js │ │ ├── entry3.test.js │ │ └── entry4.test.js │ └── webpack.config.js ├── flags-testMatch │ ├── package.json │ ├── src │ │ ├── entry.js │ │ ├── entry.test.js │ │ └── entry.test2.js │ └── webpack.config.js ├── flags-testNamePattern │ ├── package.json │ ├── src │ │ ├── entry.js │ │ ├── entry1.test.js │ │ └── entry2.test.js │ └── webpack.config.js ├── flags-testPathPattern │ ├── package.json │ ├── src │ │ ├── entry.js │ │ ├── entry1.test.js │ │ └── entry2.test.js │ └── webpack.config.js ├── flags-testRegex │ ├── package.json │ ├── src │ │ ├── entry.js │ │ ├── entry.test.js │ │ └── entry.test2.js │ └── webpack.config.js ├── loader-css │ ├── package.json │ ├── src │ │ ├── entry.css │ │ └── entry.test.js │ └── webpack.config.js ├── module-blocks │ ├── package.json │ ├── src │ │ ├── block.js │ │ ├── entry.js │ │ └── entry.test.js │ └── webpack.config.js ├── module-multiple-loaders-test │ ├── package.json │ ├── src │ │ ├── entry.js │ │ └── entry.test.js │ └── webpack.config.js ├── module-multiple-loaders │ ├── package.json │ ├── src │ │ ├── entry.js │ │ └── entry.test.js │ └── webpack.config.js ├── module-recursive │ ├── package.json │ ├── src │ │ ├── entry.js │ │ ├── entry.test.js │ │ ├── module.js │ │ └── submodule.js │ └── webpack.config.js ├── module-variables │ ├── package.json │ ├── src │ │ ├── entry.js │ │ └── entry.test.js │ └── webpack.config.js ├── test-entries-src-babel │ ├── .babelrc │ ├── package.json │ ├── src │ │ └── entry.test.js │ └── webpack.config.babel.js ├── test-entries-src │ ├── package.json │ ├── src │ │ └── entry.test.js │ └── webpack.config.js ├── utils.js ├── worker.babel.js └── worker.js ├── jest-webpack.js ├── package.json ├── src ├── common-config.test.js ├── common-flags.test.js ├── emit-changed-assets-plugin.js ├── emit-package-plugin.js ├── entry-per-module-plugin.js ├── entry-reference-dependency.js ├── entry-reference-factory.js ├── entry-reference-module.js ├── entry-reference-plugin.js ├── entry-reference-plugin.test.js ├── entry-reference-transform-dependency.js ├── file-hash.js ├── hash.js ├── hash.test.js ├── jest-exec.js ├── jest-webpack-plugin.js ├── jest-webpack.js ├── manifest-plugin.js ├── manifest-plugin.test.js ├── reference-entry-module.js ├── run-jest-when-done-plugin.js ├── shared-data.js ├── test-entries-plugin.js ├── test-entries-plugin.test.js ├── try-require.js └── webpack-loaders.test.js └── webpack.config.babel.js /.gitignore: -------------------------------------------------------------------------------- 1 | webpack-1 2 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | cache: yarn 3 | matrix: 4 | fast_finish: true 5 | include: 6 | - node_js: node 7 | env: WEBPACK_VERSION=4 BABEL_LOADER=7 JEST_VERSION=23 8 | - node_js: 8 9 | env: WEBPACK_VERSION=3 BABEL_LOADER=7 JEST_VERSION=22 10 | - node_js: 6 11 | env: WEBPACK_VERSION=3 BABEL_LOADER=7 JEST_VERSION=22 12 | before_script: 13 | - npm install webpack@$WEBPACK_VERSION babel-loader@$BABEL_LOADER jest@$JEST_VERSION 14 | script: 15 | - npm run build 16 | - npm test 17 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # jest-webpack 2 | 3 | [![Build Status](https://travis-ci.org/mzgoddard/jest-webpack.svg?branch=master)](https://travis-ci.org/mzgoddard/jest-webpack) [![Build status](https://ci.appveyor.com/api/projects/status/g4xvtyepm30hf48i/branch/master?svg=true)](https://ci.appveyor.com/project/mzgoddard/jest-webpack/branch/master) 4 | 5 | A helper tool and webpack plugin to integrate [`jest`](https://facebook.github.io/jest/) and [`webpack`](https://webpack.js.org/). 6 | 7 | Add it to your project as a developer dependency. And run it like `webpack` or `webpack-dev-server` from the command line or as a `package.json` script. 8 | 9 | #### Install 10 | 11 | ```sh 12 | npm install --save-dev jest-webpack 13 | ``` 14 | 15 | or with `yarn` 16 | 17 | ```sh 18 | yarn add -D jest-webpack 19 | ``` 20 | 21 | #### Update package.json 22 | 23 | Add it as a `package.json` script 24 | 25 | ```json 26 | { 27 | "name": "my-package", 28 | "scripts": { 29 | "test": "jest-webpack" 30 | ``` 31 | 32 | #### Run it 33 | 34 | ```sh 35 | npm test 36 | ``` 37 | 38 | Run it with jest options 39 | 40 | ```sh 41 | npm test -- --testPathPattern test-just-this-file 42 | ``` 43 | 44 | ## Status 45 | 46 | `jest-webpack` currently works with a lot of jest options out of the bag since there is no special handling needed and they can just be passed to jest by the tool. You can see what is so far specifically tested so far in https://github.com/mzgoddard/jest-webpack/issues/3. 47 | 48 | ## How it works? 49 | 50 | `jest-webpack` uses a webpack plugin to add related plugins that are responsible for 4 operations. 51 | 52 | 1. The `TestEntriesPlugin` finds test files that jest will operate on and creates entry chunks for them. 53 | 2. The `EntryReferencePlugin` creates additional entries for any other files that are depended on by those test files. These entries return objects pointing to the various transformations of that file. If a css file is depended on by both `css-loader` and `css-loader/locals` for example, both of the outputs of those loaders will be in the same file. Files then depending on those outputs reference the entry chunk and use the exported member for their needed version of the original file. 54 | 3. The `EmitChangedAssetsPlugin` removes entry chunks that already exist in the destination folder. It also includes package.json for the tested project. 55 | 4. The `RunJestWhenDonePlugin` runs jest when webpack is done. It runs jest from the destination folder so jest uses the webpack transformed files. This lets jest determine what files changed and which runs to test again instead of testing all the files again. 56 | 57 | This way of integrating jest and webpack is fairly transparent, requiring little modification to a webpack project. Used along with `source-map-support`, you can also get source maps. 58 | 59 | ## Special Thanks 60 | 61 | Thanks to [Colch](https://github.com/ColCh) for letting me take over development of a jest-webpack integration as the jest-webpack npm package. 62 | -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | # Test against the latest version of this Node.js version 2 | environment: 3 | matrix: 4 | - nodejs_version: "10" 5 | webpack_version: "4" 6 | babel_loader_version: "7" 7 | - nodejs_version: "8" 8 | webpack_version: "3" 9 | babel_loader_version: "7" 10 | 11 | # Install scripts. (runs after repo cloning) 12 | install: 13 | # Get the latest stable version of Node.js or io.js 14 | - ps: Install-Product node $env:nodejs_version 15 | # install modules 16 | - npm install 17 | - npm install webpack@%webpack_version% babel-loader@%babel_loader_version% 18 | 19 | # Post-install test scripts. 20 | test_script: 21 | # Output useful info for debugging. 22 | - node --version 23 | - npm --version 24 | # run tests 25 | - dir node_modules\.bin 26 | - npm test 27 | 28 | # Don't actually build. 29 | build_script: 30 | # Output useful info for debugging. 31 | - node --version 32 | - npm --version 33 | # run a build (node 4/webpack 1 will test with the build) 34 | - npm run build 35 | 36 | branches: 37 | only: 38 | - master 39 | -------------------------------------------------------------------------------- /fixtures/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [["env", {"targets": {"node": 4}}]] 3 | } 4 | -------------------------------------------------------------------------------- /fixtures/assets-built-plugin.js: -------------------------------------------------------------------------------- 1 | var tryRequire = function() { 2 | var attempts = [].slice.call(arguments); 3 | var err; 4 | for (var i = 0; i < attempts.length; i++) { 5 | var fn = attempts[i]; 6 | try { 7 | return fn(); 8 | } 9 | catch (_) {err = _;} 10 | } 11 | throw err; 12 | }; 13 | 14 | var RawSource = tryRequire( 15 | function() { 16 | return require('webpack/node_modules/webpack-sources/lib/RawSource'); 17 | }, 18 | function() { 19 | return require('webpack/node_modules/webpack-core/lib/RawSource'); 20 | }, 21 | function() { 22 | return require('webpack-sources/lib/RawSource'); 23 | }, 24 | function() { 25 | return require('webpack-core/lib/RawSource'); 26 | } 27 | ); 28 | 29 | function AssetsBuiltPlugin() {} 30 | 31 | AssetsBuiltPlugin.prototype.apply = function(compiler) { 32 | compiler.plugin('emit', function(compilation, cb) { 33 | try { 34 | compilation.assets['built.json'] = 35 | new RawSource(JSON.stringify(Object.keys(compilation.assets))); 36 | } 37 | catch (e) { 38 | return cb(e); 39 | } 40 | cb(); 41 | }); 42 | }; 43 | 44 | module.exports = AssetsBuiltPlugin; 45 | -------------------------------------------------------------------------------- /fixtures/cache-blocks-change/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "cache-blocks-change", 3 | "version": "1.0.0", 4 | "jest": { 5 | "testURL": "http://localhost/" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /fixtures/cache-blocks-change/src/block.js: -------------------------------------------------------------------------------- 1 | var fib = function(n) { 2 | return n > 1 ? fib(n - 2) + fib(n - 1) : 1; 3 | }; 4 | 5 | module.exports = fib; 6 | -------------------------------------------------------------------------------- /fixtures/cache-blocks-change/src/entry.js: -------------------------------------------------------------------------------- 1 | var fact2 = function(n) { 2 | return n > 0 ? fact2(n - 1) + n : 0; 3 | }; 4 | 5 | module.exports = fact2; -------------------------------------------------------------------------------- /fixtures/cache-blocks-change/src/entry.test.js: -------------------------------------------------------------------------------- 1 | it('async loads', function() { 2 | return new Promise(resolve => { 3 | require.ensure([], function(require) { 4 | resolve(require('./entry')); 5 | }); 6 | }) 7 | .then(function(entry) { 8 | expect(entry).toBeInstanceOf(Function); 9 | expect(entry(3)).toBe(6); 10 | }); 11 | }); 12 | -------------------------------------------------------------------------------- /fixtures/cache-blocks-change/webpack.config.js: -------------------------------------------------------------------------------- 1 | var join = require('path').join; 2 | // var HardSourceWebpackPlugin = require('hard-source-webpack-plugin'); 3 | 4 | var AssetsBuiltPlugin = require('../assets-built-plugin'); 5 | 6 | module.exports = { 7 | context: __dirname, 8 | entry: './src/entry', 9 | output: { 10 | path: join(__dirname, 'dist'), 11 | filename: '[name].js' 12 | }, 13 | plugins: [ 14 | // new HardSourceWebpackPlugin(), 15 | new AssetsBuiltPlugin(), 16 | ] 17 | }; 18 | -------------------------------------------------------------------------------- /fixtures/cache-multiple-loaders-change/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "cache-multiple-loaders-change", 3 | "version": "1.0.0", 4 | "jest": { 5 | "testURL": "http://localhost/" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /fixtures/cache-multiple-loaders-change/src/entry.js: -------------------------------------------------------------------------------- 1 | var fact = function(n) { 2 | return n > 0 ? fact(n - 1) + n : 0; 3 | }; 4 | 5 | module.exports = fact; 6 | -------------------------------------------------------------------------------- /fixtures/cache-multiple-loaders-change/src/entry.test.js: -------------------------------------------------------------------------------- 1 | var entry = require("./entry"); 2 | var rawEntry = require("raw-loader!./entry"); 3 | 4 | it("loads", function() { 5 | expect(entry).toBeInstanceOf(Function); 6 | expect(entry(3)).toBe(6); 7 | expect(typeof rawEntry).toBe("string"); 8 | expect(rawEntry).toMatch("var fact"); 9 | }); -------------------------------------------------------------------------------- /fixtures/cache-multiple-loaders-change/webpack.config.js: -------------------------------------------------------------------------------- 1 | var join = require('path').join; 2 | // var HardSourceWebpackPlugin = require('hard-source-webpack-plugin'); 3 | 4 | var AssetsBuiltPlugin = require('../assets-built-plugin'); 5 | 6 | module.exports = { 7 | context: __dirname, 8 | entry: './src/entry', 9 | output: { 10 | path: join(__dirname, 'dist'), 11 | filename: '[name].js' 12 | }, 13 | plugins: [ 14 | // new HardSourceWebpackPlugin(), 15 | new AssetsBuiltPlugin() 16 | ] 17 | }; 18 | -------------------------------------------------------------------------------- /fixtures/config-bail/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "config-bail", 3 | "version": "1.0.0", 4 | "jest": { 5 | "bail": true, 6 | "testURL": "http://localhost/" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /fixtures/config-bail/src/entry.js: -------------------------------------------------------------------------------- 1 | var fact = function(n) { 2 | return n > 0 ? fact(n - 1) + n : 0; 3 | }; 4 | 5 | module.exports = fact; 6 | -------------------------------------------------------------------------------- /fixtures/config-bail/src/entry.test.js: -------------------------------------------------------------------------------- 1 | it('fails', () => {throw new Error();}); 2 | 3 | it('passes', () => {}); 4 | -------------------------------------------------------------------------------- /fixtures/config-bail/src/entry2.test.js: -------------------------------------------------------------------------------- 1 | it('fails', () => {throw new Error();}); 2 | 3 | it('passes', () => {}); 4 | -------------------------------------------------------------------------------- /fixtures/config-bail/src/entry3.test.js: -------------------------------------------------------------------------------- 1 | it('fails', () => {throw new Error();}); 2 | 3 | it('passes', () => {}); 4 | -------------------------------------------------------------------------------- /fixtures/config-bail/src/entry4.test.js: -------------------------------------------------------------------------------- 1 | it('fails', () => {throw new Error();}); 2 | 3 | it('passes', () => {}); 4 | -------------------------------------------------------------------------------- /fixtures/config-bail/webpack.config.js: -------------------------------------------------------------------------------- 1 | var join = require('path').join; 2 | // var HardSourceWebpackPlugin = require('hard-source-webpack-plugin'); 3 | 4 | var AssetsBuiltPlugin = require('../assets-built-plugin'); 5 | 6 | module.exports = { 7 | context: __dirname, 8 | entry: './src/entry', 9 | output: { 10 | path: join(__dirname, 'dist'), 11 | filename: '[name].js' 12 | }, 13 | plugins: [ 14 | // new HardSourceWebpackPlugin(), 15 | new AssetsBuiltPlugin() 16 | ] 17 | }; 18 | -------------------------------------------------------------------------------- /fixtures/config-testMatch/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "config-testMatch", 3 | "version": "1.0.0", 4 | "jest": { 5 | "testMatch": ["**/src/*.test{,2}.js"], 6 | "testURL": "http://localhost/" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /fixtures/config-testMatch/src/entry.js: -------------------------------------------------------------------------------- 1 | var fact = function(n) { 2 | return n > 0 ? fact(n - 1) + n : 0; 3 | }; 4 | 5 | module.exports = fact; 6 | -------------------------------------------------------------------------------- /fixtures/config-testMatch/src/entry.test.js: -------------------------------------------------------------------------------- 1 | it('tests .test', () => {}); 2 | -------------------------------------------------------------------------------- /fixtures/config-testMatch/src/entry.test2.js: -------------------------------------------------------------------------------- 1 | it('tests .test2', () => {}); 2 | -------------------------------------------------------------------------------- /fixtures/config-testMatch/webpack.config.js: -------------------------------------------------------------------------------- 1 | var join = require('path').join; 2 | // var HardSourceWebpackPlugin = require('hard-source-webpack-plugin'); 3 | 4 | var AssetsBuiltPlugin = require('../assets-built-plugin'); 5 | 6 | module.exports = { 7 | context: __dirname, 8 | entry: './src/entry', 9 | output: { 10 | path: join(__dirname, 'dist'), 11 | filename: '[name].js' 12 | }, 13 | plugins: [ 14 | // new HardSourceWebpackPlugin(), 15 | new AssetsBuiltPlugin() 16 | ] 17 | }; 18 | -------------------------------------------------------------------------------- /fixtures/flags-bail/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "flags-bail", 3 | "version": "1.0.0", 4 | "jest": { 5 | "testURL": "http://localhost/" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /fixtures/flags-bail/src/entry.js: -------------------------------------------------------------------------------- 1 | var fact = function(n) { 2 | return n > 0 ? fact(n - 1) + n : 0; 3 | }; 4 | 5 | module.exports = fact; 6 | -------------------------------------------------------------------------------- /fixtures/flags-bail/src/entry.test.js: -------------------------------------------------------------------------------- 1 | it('fails', () => {throw new Error();}); 2 | 3 | it('passes', () => {}); 4 | -------------------------------------------------------------------------------- /fixtures/flags-bail/src/entry2.test.js: -------------------------------------------------------------------------------- 1 | it('fails', () => {throw new Error();}); 2 | 3 | it('passes', () => {}); 4 | -------------------------------------------------------------------------------- /fixtures/flags-bail/src/entry3.test.js: -------------------------------------------------------------------------------- 1 | it('fails', () => {throw new Error();}); 2 | 3 | it('passes', () => {}); 4 | -------------------------------------------------------------------------------- /fixtures/flags-bail/src/entry4.test.js: -------------------------------------------------------------------------------- 1 | it('fails', () => {throw new Error();}); 2 | 3 | it('passes', () => {}); 4 | -------------------------------------------------------------------------------- /fixtures/flags-bail/webpack.config.js: -------------------------------------------------------------------------------- 1 | var join = require('path').join; 2 | // var HardSourceWebpackPlugin = require('hard-source-webpack-plugin'); 3 | 4 | var AssetsBuiltPlugin = require('../assets-built-plugin'); 5 | 6 | module.exports = { 7 | context: __dirname, 8 | entry: './src/entry', 9 | output: { 10 | path: join(__dirname, 'dist'), 11 | filename: '[name].js' 12 | }, 13 | plugins: [ 14 | // new HardSourceWebpackPlugin(), 15 | new AssetsBuiltPlugin() 16 | ] 17 | }; 18 | -------------------------------------------------------------------------------- /fixtures/flags-testMatch/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "flags-testMatch", 3 | "version": "1.0.0", 4 | "jest": { 5 | "testURL": "http://localhost/" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /fixtures/flags-testMatch/src/entry.js: -------------------------------------------------------------------------------- 1 | var fact = function(n) { 2 | return n > 0 ? fact(n - 1) + n : 0; 3 | }; 4 | 5 | module.exports = fact; 6 | -------------------------------------------------------------------------------- /fixtures/flags-testMatch/src/entry.test.js: -------------------------------------------------------------------------------- 1 | it('tests .test', () => {}); 2 | -------------------------------------------------------------------------------- /fixtures/flags-testMatch/src/entry.test2.js: -------------------------------------------------------------------------------- 1 | it('tests .test2', () => {}); 2 | -------------------------------------------------------------------------------- /fixtures/flags-testMatch/webpack.config.js: -------------------------------------------------------------------------------- 1 | var join = require('path').join; 2 | // var HardSourceWebpackPlugin = require('hard-source-webpack-plugin'); 3 | 4 | var AssetsBuiltPlugin = require('../assets-built-plugin'); 5 | 6 | module.exports = { 7 | context: __dirname, 8 | entry: './src/entry', 9 | output: { 10 | path: join(__dirname, 'dist'), 11 | filename: '[name].js' 12 | }, 13 | plugins: [ 14 | // new HardSourceWebpackPlugin(), 15 | new AssetsBuiltPlugin() 16 | ] 17 | }; 18 | -------------------------------------------------------------------------------- /fixtures/flags-testNamePattern/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "flags-testNamePattern", 3 | "version": "1.0.0", 4 | "jest": { 5 | "testURL": "http://localhost/" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /fixtures/flags-testNamePattern/src/entry.js: -------------------------------------------------------------------------------- 1 | var fact = function(n) { 2 | return n > 0 ? fact(n - 1) + n : 0; 3 | }; 4 | 5 | module.exports = fact; 6 | -------------------------------------------------------------------------------- /fixtures/flags-testNamePattern/src/entry1.test.js: -------------------------------------------------------------------------------- 1 | it('tests entry1', () => {}); 2 | -------------------------------------------------------------------------------- /fixtures/flags-testNamePattern/src/entry2.test.js: -------------------------------------------------------------------------------- 1 | it('tests entry1', () => {}); 2 | 3 | it('tests entry2', () => {}); 4 | -------------------------------------------------------------------------------- /fixtures/flags-testNamePattern/webpack.config.js: -------------------------------------------------------------------------------- 1 | var join = require('path').join; 2 | // var HardSourceWebpackPlugin = require('hard-source-webpack-plugin'); 3 | 4 | var AssetsBuiltPlugin = require('../assets-built-plugin'); 5 | 6 | module.exports = { 7 | context: __dirname, 8 | entry: './src/entry', 9 | output: { 10 | path: join(__dirname, 'dist'), 11 | filename: '[name].js' 12 | }, 13 | plugins: [ 14 | // new HardSourceWebpackPlugin(), 15 | new AssetsBuiltPlugin() 16 | ] 17 | }; 18 | -------------------------------------------------------------------------------- /fixtures/flags-testPathPattern/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "flags-testPathPattern", 3 | "version": "1.0.0", 4 | "jest": { 5 | "testURL": "http://localhost/" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /fixtures/flags-testPathPattern/src/entry.js: -------------------------------------------------------------------------------- 1 | var fact = function(n) { 2 | return n > 0 ? fact(n - 1) + n : 0; 3 | }; 4 | 5 | module.exports = fact; 6 | -------------------------------------------------------------------------------- /fixtures/flags-testPathPattern/src/entry1.test.js: -------------------------------------------------------------------------------- 1 | it('tests entry1', () => {}); 2 | -------------------------------------------------------------------------------- /fixtures/flags-testPathPattern/src/entry2.test.js: -------------------------------------------------------------------------------- 1 | it('tests entry2', () => {}); 2 | -------------------------------------------------------------------------------- /fixtures/flags-testPathPattern/webpack.config.js: -------------------------------------------------------------------------------- 1 | var join = require('path').join; 2 | // var HardSourceWebpackPlugin = require('hard-source-webpack-plugin'); 3 | 4 | var AssetsBuiltPlugin = require('../assets-built-plugin'); 5 | 6 | module.exports = { 7 | context: __dirname, 8 | entry: './src/entry', 9 | output: { 10 | path: join(__dirname, 'dist'), 11 | filename: '[name].js' 12 | }, 13 | plugins: [ 14 | // new HardSourceWebpackPlugin(), 15 | new AssetsBuiltPlugin() 16 | ] 17 | }; 18 | -------------------------------------------------------------------------------- /fixtures/flags-testRegex/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "flags-testRegex", 3 | "version": "1.0.0", 4 | "jest": { 5 | "testURL": "http://localhost/" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /fixtures/flags-testRegex/src/entry.js: -------------------------------------------------------------------------------- 1 | var fact = function(n) { 2 | return n > 0 ? fact(n - 1) + n : 0; 3 | }; 4 | 5 | module.exports = fact; 6 | -------------------------------------------------------------------------------- /fixtures/flags-testRegex/src/entry.test.js: -------------------------------------------------------------------------------- 1 | it('tests .test', () => {}); 2 | -------------------------------------------------------------------------------- /fixtures/flags-testRegex/src/entry.test2.js: -------------------------------------------------------------------------------- 1 | it('tests .test2', () => {}); 2 | -------------------------------------------------------------------------------- /fixtures/flags-testRegex/webpack.config.js: -------------------------------------------------------------------------------- 1 | var join = require('path').join; 2 | // var HardSourceWebpackPlugin = require('hard-source-webpack-plugin'); 3 | 4 | var AssetsBuiltPlugin = require('../assets-built-plugin'); 5 | 6 | module.exports = { 7 | context: __dirname, 8 | entry: './src/entry', 9 | output: { 10 | path: join(__dirname, 'dist'), 11 | filename: '[name].js' 12 | }, 13 | plugins: [ 14 | // new HardSourceWebpackPlugin(), 15 | new AssetsBuiltPlugin() 16 | ] 17 | }; 18 | -------------------------------------------------------------------------------- /fixtures/loader-css/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "loader-css", 3 | "version": "1.0.0", 4 | "jest": { 5 | "testURL": "http://localhost/" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /fixtures/loader-css/src/entry.css: -------------------------------------------------------------------------------- 1 | .entry { 2 | background: #abcdef; 3 | } 4 | -------------------------------------------------------------------------------- /fixtures/loader-css/src/entry.test.js: -------------------------------------------------------------------------------- 1 | var entry = require('./entry.css'); 2 | var entryCss = require('!!css-loader!./entry.css'); 3 | 4 | it('loads', function() { 5 | expect(entry).toBeTruthy(); 6 | expect('' + entryCss).toContain('.entry'); 7 | }); 8 | -------------------------------------------------------------------------------- /fixtures/loader-css/webpack.config.js: -------------------------------------------------------------------------------- 1 | var join = require('path').join; 2 | // var HardSourceWebpackPlugin = require('hard-source-webpack-plugin'); 3 | var webpackIf = require('webpack-if'); 4 | 5 | var AssetsBuiltPlugin = require('../assets-built-plugin'); 6 | 7 | var webpackVersion = require('webpack/package.json').version; 8 | var ifWebpack1 = webpackIf.ifElse(webpackVersion.startsWith('1')); 9 | 10 | module.exports = webpackIf({ 11 | context: __dirname, 12 | entry: './src/entry', 13 | output: { 14 | path: join(__dirname, 'dist'), 15 | filename: '[name].js' 16 | }, 17 | module: { 18 | loaders: ifWebpack1([ 19 | { 20 | test: /\.css$/, 21 | loaders: ['style-loader', 'css-loader'], 22 | }, 23 | ]), 24 | rules: ifWebpack1(null, [ 25 | { 26 | test: /\.css$/, 27 | use: ['style-loader', 'css-loader'], 28 | }, 29 | ]), 30 | }, 31 | plugins: [ 32 | // new HardSourceWebpackPlugin(), 33 | new AssetsBuiltPlugin() 34 | ] 35 | }); 36 | -------------------------------------------------------------------------------- /fixtures/module-blocks/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "module-blocks", 3 | "version": "1.0.0", 4 | "jest": { 5 | "testURL": "http://localhost/" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /fixtures/module-blocks/src/block.js: -------------------------------------------------------------------------------- 1 | var fib = function(n) { 2 | return n > 1 ? fib(n - 2) + fib(n - 1) : 1; 3 | }; 4 | 5 | module.exports = fib; 6 | -------------------------------------------------------------------------------- /fixtures/module-blocks/src/entry.js: -------------------------------------------------------------------------------- 1 | var fact = function(n) { 2 | return n > 0 ? fact(n - 1) + n : 0; 3 | }; 4 | 5 | module.exports = fact; 6 | -------------------------------------------------------------------------------- /fixtures/module-blocks/src/entry.test.js: -------------------------------------------------------------------------------- 1 | it('async loads', function() { 2 | return new Promise(resolve => { 3 | require.ensure([], function(require) { 4 | resolve(require('./entry')); 5 | }); 6 | }) 7 | .then(function(entry) { 8 | expect(entry).toBeInstanceOf(Function); 9 | expect(entry(3)).toBe(6); 10 | }); 11 | }); 12 | -------------------------------------------------------------------------------- /fixtures/module-blocks/webpack.config.js: -------------------------------------------------------------------------------- 1 | var join = require('path').join; 2 | // var HardSourceWebpackPlugin = require('hard-source-webpack-plugin'); 3 | 4 | var AssetsBuiltPlugin = require('../assets-built-plugin'); 5 | 6 | module.exports = { 7 | context: __dirname, 8 | entry: './src/entry', 9 | output: { 10 | path: join(__dirname, 'dist'), 11 | filename: '[name].js' 12 | }, 13 | plugins: [ 14 | // new HardSourceWebpackPlugin(), 15 | new AssetsBuiltPlugin() 16 | ] 17 | }; 18 | -------------------------------------------------------------------------------- /fixtures/module-multiple-loaders-test/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "module-multiple-loaders-test", 3 | "version": "1.0.0", 4 | "jest": { 5 | "testURL": "http://localhost/" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /fixtures/module-multiple-loaders-test/src/entry.js: -------------------------------------------------------------------------------- 1 | var fact = function(n) { 2 | return n > 0 ? fact(n - 1) + n : 0; 3 | }; 4 | 5 | module.exports = fact; 6 | -------------------------------------------------------------------------------- /fixtures/module-multiple-loaders-test/src/entry.test.js: -------------------------------------------------------------------------------- 1 | var rawTests = require('raw-loader!./entry.test'); 2 | 3 | it('loads', function() { 4 | expect(typeof rawTests).toBe('string'); 5 | expect(rawTests).toMatch('loads'); 6 | }); 7 | -------------------------------------------------------------------------------- /fixtures/module-multiple-loaders-test/webpack.config.js: -------------------------------------------------------------------------------- 1 | var join = require('path').join; 2 | // var HardSourceWebpackPlugin = require('hard-source-webpack-plugin'); 3 | 4 | var AssetsBuiltPlugin = require('../assets-built-plugin'); 5 | 6 | module.exports = { 7 | context: __dirname, 8 | entry: './src/entry', 9 | output: { 10 | path: join(__dirname, 'dist'), 11 | filename: '[name].js' 12 | }, 13 | plugins: [ 14 | // new HardSourceWebpackPlugin(), 15 | new AssetsBuiltPlugin() 16 | ] 17 | }; 18 | -------------------------------------------------------------------------------- /fixtures/module-multiple-loaders/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "module-multiple-loaders", 3 | "version": "1.0.0", 4 | "jest": { 5 | "testURL": "http://localhost/" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /fixtures/module-multiple-loaders/src/entry.js: -------------------------------------------------------------------------------- 1 | var fact = function(n) { 2 | return n > 0 ? fact(n - 1) + n : 0; 3 | }; 4 | 5 | module.exports = fact; 6 | -------------------------------------------------------------------------------- /fixtures/module-multiple-loaders/src/entry.test.js: -------------------------------------------------------------------------------- 1 | var entry = require('./entry'); 2 | var rawEntry = require('raw-loader!./entry'); 3 | 4 | it('loads', function() { 5 | expect(entry).toBeInstanceOf(Function); 6 | expect(entry(3)).toBe(6); 7 | expect(typeof rawEntry).toBe('string'); 8 | expect(rawEntry).toMatch('var fact'); 9 | }); 10 | -------------------------------------------------------------------------------- /fixtures/module-multiple-loaders/webpack.config.js: -------------------------------------------------------------------------------- 1 | var join = require('path').join; 2 | // var HardSourceWebpackPlugin = require('hard-source-webpack-plugin'); 3 | 4 | var AssetsBuiltPlugin = require('../assets-built-plugin'); 5 | 6 | module.exports = { 7 | context: __dirname, 8 | entry: './src/entry', 9 | output: { 10 | path: join(__dirname, 'dist'), 11 | filename: '[name].js' 12 | }, 13 | plugins: [ 14 | // new HardSourceWebpackPlugin(), 15 | new AssetsBuiltPlugin() 16 | ] 17 | }; 18 | -------------------------------------------------------------------------------- /fixtures/module-recursive/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "module-recursive", 3 | "version": "1.0.0", 4 | "jest": { 5 | "testURL": "http://localhost/" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /fixtures/module-recursive/src/entry.js: -------------------------------------------------------------------------------- 1 | var fact = function(n) { 2 | return n > 0 ? fact(n - 1) + n : 0; 3 | }; 4 | 5 | module.exports = fact; 6 | -------------------------------------------------------------------------------- /fixtures/module-recursive/src/entry.test.js: -------------------------------------------------------------------------------- 1 | const submodule = require('./submodule'); 2 | 3 | it('recursive loads', function() { 4 | expect(submodule).toBeTruthy(); 5 | }); 6 | -------------------------------------------------------------------------------- /fixtures/module-recursive/src/module.js: -------------------------------------------------------------------------------- 1 | require('./submodule'); 2 | -------------------------------------------------------------------------------- /fixtures/module-recursive/src/submodule.js: -------------------------------------------------------------------------------- 1 | require('./module'); 2 | -------------------------------------------------------------------------------- /fixtures/module-recursive/webpack.config.js: -------------------------------------------------------------------------------- 1 | var join = require('path').join; 2 | // var HardSourceWebpackPlugin = require('hard-source-webpack-plugin'); 3 | 4 | var AssetsBuiltPlugin = require('../assets-built-plugin'); 5 | 6 | module.exports = { 7 | context: __dirname, 8 | entry: './src/entry', 9 | output: { 10 | path: join(__dirname, 'dist'), 11 | filename: '[name].js' 12 | }, 13 | plugins: [ 14 | // new HardSourceWebpackPlugin(), 15 | new AssetsBuiltPlugin() 16 | ] 17 | }; 18 | -------------------------------------------------------------------------------- /fixtures/module-variables/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "module-variables", 3 | "version": "1.0.0", 4 | "jest": { 5 | "testURL": "http://localhost/" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /fixtures/module-variables/src/entry.js: -------------------------------------------------------------------------------- 1 | module.exports = __resourceQuery; 2 | -------------------------------------------------------------------------------- /fixtures/module-variables/src/entry.test.js: -------------------------------------------------------------------------------- 1 | if (__resourceQuery) { 2 | module.exports = __resourceQuery; 3 | } 4 | else { 5 | it('has variables', function() { 6 | expect(require('./entry?query')).toBe('?query'); 7 | expect(require('./entry?query2')).toBe('?query2'); 8 | expect(require('./entry.test?query3')).toBe('?query3'); 9 | }); 10 | } 11 | -------------------------------------------------------------------------------- /fixtures/module-variables/webpack.config.js: -------------------------------------------------------------------------------- 1 | var join = require('path').join; 2 | // var HardSourceWebpackPlugin = require('hard-source-webpack-plugin'); 3 | 4 | var AssetsBuiltPlugin = require('../assets-built-plugin'); 5 | 6 | module.exports = { 7 | context: __dirname, 8 | entry: './src/entry', 9 | output: { 10 | path: join(__dirname, 'dist'), 11 | filename: '[name].js' 12 | }, 13 | plugins: [ 14 | // new HardSourceWebpackPlugin(), 15 | new AssetsBuiltPlugin() 16 | ] 17 | }; 18 | -------------------------------------------------------------------------------- /fixtures/test-entries-src-babel/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["env"] 3 | } 4 | -------------------------------------------------------------------------------- /fixtures/test-entries-src-babel/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "test-entries-src-babel", 3 | "version": "1.0.0", 4 | "jest": { 5 | "testURL": "http://localhost/" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /fixtures/test-entries-src-babel/src/entry.test.js: -------------------------------------------------------------------------------- 1 | it('passes', () => {}); 2 | -------------------------------------------------------------------------------- /fixtures/test-entries-src-babel/webpack.config.babel.js: -------------------------------------------------------------------------------- 1 | const {join} = require('path'); 2 | // var HardSourceWebpackPlugin = require('hard-source-webpack-plugin'); 3 | 4 | var AssetsBuiltPlugin = require('../assets-built-plugin'); 5 | 6 | module.exports = { 7 | context: __dirname, 8 | entry: './src/entry', 9 | output: { 10 | path: join(__dirname, 'dist'), 11 | filename: '[name].js', 12 | }, 13 | plugins: [ 14 | // new HardSourceWebpackPlugin(), 15 | new AssetsBuiltPlugin() 16 | ] 17 | }; 18 | -------------------------------------------------------------------------------- /fixtures/test-entries-src/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "test-entries-src", 3 | "version": "1.0.0", 4 | "jest": { 5 | "testURL": "http://localhost/" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /fixtures/test-entries-src/src/entry.test.js: -------------------------------------------------------------------------------- 1 | it('passes', function() {}); 2 | -------------------------------------------------------------------------------- /fixtures/test-entries-src/webpack.config.js: -------------------------------------------------------------------------------- 1 | var join = require('path').join; 2 | // var HardSourceWebpackPlugin = require('hard-source-webpack-plugin'); 3 | 4 | var AssetsBuiltPlugin = require('../assets-built-plugin'); 5 | 6 | module.exports = { 7 | context: __dirname, 8 | entry: './src/entry', 9 | output: { 10 | path: join(__dirname, 'dist'), 11 | filename: '[name].js' 12 | }, 13 | plugins: [ 14 | // new HardSourceWebpackPlugin(), 15 | new AssetsBuiltPlugin() 16 | ] 17 | }; 18 | -------------------------------------------------------------------------------- /fixtures/utils.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const {dirname, join, relative, sep} = require('path'); 3 | const {fork} = require('child_process'); 4 | 5 | const findUp = require('find-up'); 6 | const pify = require('pify'); 7 | const regeneratorRuntime = require('regenerator-runtime'); 8 | const _rimraf = require('rimraf'); 9 | 10 | const promisify = fn => (...args) => new Promise((resolve, reject) => { 11 | fn(...Array.from(args).concat((err, value) => { 12 | if (err) {return reject(err);} 13 | resolve(value); 14 | })) 15 | }); 16 | 17 | const readdir = promisify(fs.readdir); 18 | const rimraf = promisify(_rimraf); 19 | const stat = promisify(fs.stat); 20 | 21 | const walkDir = async (root, dir, files = []) => { 22 | let dirItems; 23 | try { 24 | dirItems = await readdir(dir); 25 | } 26 | catch (_) {} 27 | if (dirItems) { 28 | for (let item of dirItems) { 29 | const fullItem = join(dir, item); 30 | const itemStat = await stat(fullItem); 31 | if (itemStat.isDirectory()) { 32 | await walkDir(root, fullItem, files); 33 | } 34 | else { 35 | files.push(relative(root, fullItem)); 36 | } 37 | } 38 | } 39 | return files; 40 | }; 41 | 42 | const concat = pipe => { 43 | return new Promise(resolve => { 44 | let out = ''; 45 | pipe.on('data', data => { 46 | out += data; 47 | }); 48 | pipe.on('end', () => { 49 | resolve(out); 50 | }); 51 | }); 52 | }; 53 | 54 | const _findPaths = fixturePath => { 55 | // These paths need to escape the jest-webpack cache. 56 | const jestWebpackBin = findUp.sync('jest-webpack.js', { 57 | cwd: __dirname, 58 | }); 59 | const fullFixturePath = join(dirname(jestWebpackBin), 'fixtures', fixturePath); 60 | const fullJestWebpackPath = join(fullFixturePath, '.cache/jest-webpack'); 61 | 62 | return { 63 | jestWebpackBin, 64 | fullFixturePath, 65 | fullJestWebpackPath, 66 | }; 67 | }; 68 | 69 | const clean = async result => { 70 | const {fullJestWebpackPath} = _findPaths(result.fixture); 71 | await rimraf(fullJestWebpackPath); 72 | return result; 73 | }; 74 | 75 | const getWorker = (() => { 76 | let isAlive = false; 77 | let worker; 78 | let stdout = ''; 79 | let stderr = ''; 80 | let lastExit = -1; 81 | let workerData = { 82 | popStdout() { 83 | let out = stdout; 84 | stdout = ''; 85 | return out; 86 | }, 87 | popStderr() { 88 | let err = stderr; 89 | stderr = ''; 90 | return err; 91 | }, 92 | exitCode() { 93 | return lastExit; 94 | }, 95 | run(fixture, args) { 96 | return new Promise((resolve, reject) => { 97 | const onExit = code => { 98 | lastExit = code; 99 | resolve({success: false}); 100 | worker.removeListener('message', onMessage); 101 | }; 102 | const onMessage = result => { 103 | lastExit = result.success ? 0 : 1; 104 | resolve(result); 105 | worker.removeListener('exit', onExit); 106 | }; 107 | worker.send({ 108 | id: Math.random().toString(16).substring(2), 109 | fixture, 110 | args, 111 | }); 112 | worker.once('exit', onExit); 113 | worker.once('message', onMessage); 114 | }); 115 | }, 116 | }; 117 | () => require('./worker?__jest_webpack_isEntry'); 118 | () => require('./worker.babel?__jest_webpack_isEntry'); 119 | 120 | return () => { 121 | try { 122 | if (!isAlive) { 123 | try { 124 | worker = fork(join(__dirname, 'worker.babel.js'), { 125 | stdio: ['pipe', 'pipe', 'pipe', 'ipc'], 126 | // stdio: ['inherit', 'inherit', 'inherit', 'ipc'], 127 | }); 128 | worker.stdout.on('data', data => { 129 | stdout += data.toString(); 130 | }); 131 | } 132 | catch (_) { 133 | // Node 4 API 134 | worker = fork(join(__dirname, 'worker.babel.js'), { 135 | silent: true, 136 | // stdio: ['inherit', 'inherit', 'inherit', 'ipc'], 137 | }); 138 | worker.stdout.on('data', data => { 139 | stdout += data.toString(); 140 | }); 141 | } 142 | worker.stderr.on('data', data => { 143 | stderr += data.toString(); 144 | }); 145 | worker.on('exit', () => { 146 | isAlive = false; 147 | }); 148 | isAlive = true; 149 | } 150 | return workerData; 151 | } 152 | catch (err) { 153 | console.error(err.stack || err); 154 | throw err; 155 | } 156 | }; 157 | })(); 158 | 159 | const _runJest = result => { 160 | const {fixture, args} = result; 161 | const {jestWebpackBin, fullFixturePath, fullJestWebpackPath} = 162 | _findPaths(fixture); 163 | 164 | // const child = spawn(process.argv[0], [ 165 | // jestWebpackBin, 166 | // ].concat(args), { 167 | // cwd: fullFixturePath, 168 | // stdio: 'pipe', 169 | // }); 170 | // 171 | // const stdout = concat(child.stdout); 172 | // const stderr = concat(child.stderr); 173 | // const exit = new Promise(resolve => child.on('exit', resolve)); 174 | // // const built = exit 175 | // // .then(() => walkDir(fullJestWebpackPath, fullJestWebpackPath)); 176 | 177 | // console.log('_runJest'); 178 | 179 | const worker = getWorker(); 180 | const job = worker.run(fixture, args); 181 | const stdout = job.then(() => worker.popStdout()); 182 | const stderr = job.then(() => worker.popStderr()); 183 | const exit = job.then(() => worker.exitCode()); 184 | 185 | const built = exit 186 | .then(() => pify(fs.readFile)(join(fullJestWebpackPath, 'built.json'), 'utf8')) 187 | .then(JSON.parse); 188 | 189 | return Promise.all([stdout, stderr, exit, built]) 190 | .catch(err => { 191 | console.error(err.stack || err); 192 | return Promise.all([stdout, stderr]) 193 | .then(([stdout, stderr]) => { 194 | console.error(stdout); 195 | console.error(stderr); 196 | throw err; 197 | }); 198 | }) 199 | .then(([stdout, stderr, exit, built]) => { 200 | return { 201 | fixture, 202 | args, 203 | exit, 204 | built, 205 | stdout, 206 | stderr, 207 | }; 208 | }); 209 | }; 210 | 211 | const willRun = (fixturePath, args = []) => { 212 | return Promise.resolve({ 213 | fixture: fixturePath, 214 | args, 215 | }); 216 | }; 217 | 218 | const run = (fixturePath, args = []) => { 219 | return willRun(fixturePath, args) 220 | .then(clean) 221 | .then(_runJest); 222 | }; 223 | 224 | const runAgain = result => _runJest(result); 225 | 226 | const writeFiles = obj => result => { 227 | const {fullFixturePath} = _findPaths(result.fixture); 228 | for (const key in obj) { 229 | fs.writeFileSync(join(fullFixturePath, key), obj[key].join('\n')); 230 | } 231 | return result; 232 | }; 233 | 234 | const itPasses = result => { 235 | expect(result.exit).toBe(0); 236 | return result; 237 | }; 238 | 239 | const itFails = result => { 240 | expect(result.exit).not.toBe(0); 241 | return result; 242 | } 243 | 244 | const itBuilt = (files) => result => { 245 | files.forEach(file => expect(result.built).toContain(file.replace(/\//g, sep))); 246 | return result; 247 | }; 248 | 249 | const didNotBuild = (files) => result => { 250 | files.forEach(file => expect(result.built).not.toContain(file.replace(/\//g, sep))); 251 | return result; 252 | }; 253 | 254 | const containsOutput = (out) => result => { 255 | expect(result.stderr).toContain(out); 256 | return result; 257 | }; 258 | 259 | const missesOutput = (out) => result => { 260 | expect(result.stderr).not.toContain(out); 261 | return result; 262 | }; 263 | 264 | const itTests = (files) => result => { 265 | files.forEach(file => expect(result.stderr).toMatch(file)); 266 | return result; 267 | }; 268 | 269 | const itSkips = (files) => result => { 270 | files.forEach(file => ( 271 | expect(result.stderr) 272 | .not.toMatch(file) 273 | )); 274 | return result; 275 | }; 276 | 277 | module.exports = { 278 | willRun, 279 | run, 280 | runAgain, 281 | writeFiles, 282 | clean, 283 | itPasses, 284 | itFails, 285 | itBuilt, 286 | didNotBuild, 287 | containsOutput, 288 | missesOutput, 289 | itTests, 290 | itSkips, 291 | }; 292 | -------------------------------------------------------------------------------- /fixtures/worker.babel.js: -------------------------------------------------------------------------------- 1 | var join = require('path').join; 2 | var readFileSync = require('fs').readFileSync; 3 | 4 | var babel = require('babel-core'); 5 | 6 | try { 7 | var code = babel.transform( 8 | readFileSync(join(__dirname, 'worker.js'), 'utf8'), 9 | {presets: [['env', {targets: {node: 4}}]]} 10 | ).code; 11 | } 12 | catch (err) { 13 | console.error(err.stack || err); 14 | throw err; 15 | } 16 | 17 | eval(code); 18 | -------------------------------------------------------------------------------- /fixtures/worker.js: -------------------------------------------------------------------------------- 1 | const {dirname, join} = require('path'); 2 | const {readFileSync, statSync} = require('fs'); 3 | const {runInThisContext} = require('vm'); 4 | 5 | const {transform} = require('babel-core'); 6 | const findUp = require('find-up'); 7 | 8 | const jestWebpackPath = findUp.sync('jest-webpack.js'); 9 | const fixtureRootPath = join(dirname(jestWebpackPath), 'fixtures'); 10 | 11 | const jestWebpack = eval('require')(jestWebpackPath); 12 | 13 | const wrapModule = code => { 14 | return '(function(exports, require, module, __filename, __dirname) {' + 15 | code + 16 | '})'; 17 | }; 18 | 19 | const callModule = (fn, filename) => { 20 | const module = {exports: {}}; 21 | fn(module.exports, Object.assign(modulename => { 22 | if (/\W/.test(modulename[0])) { 23 | return eval('require')(join(dirname(filename), modulename)); 24 | } 25 | return eval('require')(modulename); 26 | }, eval('require')), module, filename, dirname(filename)); 27 | return module.exports; 28 | }; 29 | 30 | const loadFreshConfig = configPath => { 31 | try { 32 | try { 33 | return callModule(runInThisContext( 34 | wrapModule(readFileSync(configPath, 'utf8')), 35 | {filename: configPath} 36 | ), configPath) 37 | } 38 | catch (_) { 39 | return callModule(runInThisContext( 40 | wrapModule(transform(readFileSync(configPath, 'utf8'), {presets: [['env', {targets: {node: 4}}]]}).code), 41 | {filename: configPath} 42 | ), configPath) 43 | } 44 | } 45 | catch (err) { 46 | console.error(err.stack || err); 47 | throw err; 48 | } 49 | }; 50 | 51 | const fixtureWebpackConfigPath = fixture => { 52 | try { 53 | statSync(join(fixtureRootPath, fixture, 'webpack.config.babel.js')); 54 | return join(fixtureRootPath, fixture, 'webpack.config.babel.js'); 55 | } 56 | catch (_) {} 57 | return join(fixtureRootPath, fixture, 'webpack.config.js'); 58 | }; 59 | 60 | const run = () => { 61 | let exitTimeout = -1; 62 | 63 | process.on('message', job => { 64 | clearTimeout(exitTimeout); 65 | const webpackConfig = loadFreshConfig(fixtureWebpackConfigPath(job.fixture)); 66 | webpackConfig.plugins = webpackConfig.plugins || []; 67 | webpackConfig.plugins.push({ 68 | apply(compiler) { 69 | const tapable = require('tapable'); 70 | if (compiler.hooks) { 71 | compiler.hooks.jestWebpackDone = compiler.hooks.jestWebpackDone || 72 | new tapable.SyncHook(['result']); 73 | compiler.hooks.jestWebpackDone.tap('jest-webpack fixtures worker', ({success}) => { 74 | process.send({id: job.id, success}); 75 | exitTimeout = setTimeout(() => process.exit(), 100); 76 | }); 77 | } 78 | else { 79 | compiler.plugin('jest-webpack-done', ({success}) => { 80 | process.send({id: job.id, success}); 81 | exitTimeout = setTimeout(() => process.exit(), 100); 82 | }); 83 | } 84 | }, 85 | }); 86 | process.chdir(join(fixtureRootPath, job.fixture)); 87 | jestWebpack(job.args || [], webpackConfig); 88 | }); 89 | 90 | exitTimeout = setTimeout(() => process.exit(), 5000); 91 | }; 92 | 93 | run(); 94 | -------------------------------------------------------------------------------- /jest-webpack.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | function versionInfo() { 4 | return `jest-webpack ${require('./package.json').version}\n` + 5 | `jest ${require('jest/package.json').version}\n` + 6 | `webpack ${require('webpack/package.json').version}`; 7 | } 8 | 9 | function tryRequire(attempts) { 10 | attempts = Array.from(arguments); 11 | var err; 12 | for (var i = 0; i < attempts.length; i++) { 13 | var fn = attempts[i]; 14 | try { 15 | var exports = fn(); 16 | return exports.default || exports; 17 | } 18 | catch (_) {err = _;} 19 | } 20 | throw err; 21 | } 22 | 23 | function run(argv, webpackConfig) { 24 | var jestArgvPortion, webpackArgvPortion; 25 | var webpackArgIndex = 26 | ~(~argv.lastIndexOf('--') || ~argv.lastIndexOf('--webpack')); 27 | if (webpackArgIndex === -1) { 28 | jestArgvPortion = argv; 29 | webpackArgvPortion = []; 30 | } 31 | else { 32 | jestArgvPortion = argv.slice(0, webpackArgIndex); 33 | webpackArgvPortion = argv.slice(webpackArgIndex); 34 | } 35 | 36 | if (!webpackConfig) { 37 | var webpackYargs = require('yargs/yargs')([]); 38 | tryRequire( 39 | function() {return require('webpack/bin/config-yargs');}, 40 | function() {return require('webpack-cli/bin/config-yargs');} 41 | )(webpackYargs); 42 | var webpackArgv = webpackYargs.parse(webpackArgvPortion); 43 | webpackConfig = tryRequire( 44 | function() {return require('webpack/bin/convert-argv');}, 45 | function() {return require('webpack-cli/bin/convert-argv');} 46 | )( 47 | webpackYargs, webpackArgv 48 | ); 49 | } 50 | 51 | if (typeof webpackConfig.then === 'function') { 52 | webpackConfig.then(function(config) {run(argv, config);}) 53 | .catch(function(err) { 54 | console.error(err.stack || err); 55 | process.exit(); 56 | }); 57 | return; 58 | } 59 | 60 | if (Array.isArray(webpackConfig)) { 61 | console.error( 62 | 'jest-webpack does not support webpack\'s multi-compiler array ' + 63 | 'configuration at this time.' 64 | ); 65 | process.exit(1); 66 | } 67 | 68 | var main; 69 | if (require('webpack/package.json').version.startsWith('1')) { 70 | main = require('./webpack-1/jest-webpack.js'); 71 | } 72 | else { 73 | main = require('./src/jest-webpack.js'); 74 | } 75 | 76 | if (!process.env.NODE_ENV) { 77 | process.env.NODE_ENV = 'test'; 78 | } 79 | 80 | main(argv, webpackConfig); 81 | } 82 | 83 | if (process.mainModule === module) { 84 | const jestWebpackYargs = require('yargs/yargs')(process.argv) 85 | .reset() 86 | .usage(`${versionInfo()}\n\nUsage: jest-webpack [jest options] [--[webpack] [webpack options]]`); 87 | jestWebpackYargs.version(false).parse(); 88 | 89 | run(process.argv.slice(2)); 90 | } 91 | 92 | module.exports = run; 93 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "jest-webpack", 3 | "version": "0.5.1", 4 | "description": "Use webpack loader and plugins you want to build modules into individual files and test them with jest.", 5 | "repository": { 6 | "type": "git", 7 | "url": "https://github.com/mzgoddard/jest-webpack" 8 | }, 9 | "main": "jest-webpack.js", 10 | "bin": { 11 | "jest-webpack": "./jest-webpack.js" 12 | }, 13 | "files": [ 14 | "src", 15 | "webpack-1", 16 | "jest-webpack.js" 17 | ], 18 | "scripts": { 19 | "build": "webpack", 20 | "test": "node ./jest-webpack.js --maxWorkers 1", 21 | "prepublishOnly": "NODE_ENV=production npm run build && npm test" 22 | }, 23 | "keywords": [ 24 | "jest", 25 | "webpack", 26 | "plugin", 27 | "webpack plugin" 28 | ], 29 | "author": { 30 | "name": "Michael \"Z\" Goddard", 31 | "email": "" 32 | }, 33 | "license": "ISC", 34 | "devDependencies": { 35 | "babel": "^6.23.0", 36 | "babel-core": "^6.24.1", 37 | "babel-loader": "^7.0.0 || ^6.0.0", 38 | "babel-preset-env": "^1.5.1", 39 | "babel-preset-jest": "^20.0.3", 40 | "babel-register": "^6.26.0", 41 | "css-loader": "^0.28.4", 42 | "file-loader": "^1.1.11", 43 | "jest": "^23.1.0", 44 | "raw-loader": "^0.5.1", 45 | "rimraf": "^2.6.1", 46 | "source-map-support": "^0.4.15", 47 | "style-loader": "^0.18.2", 48 | "webpack": "^4.12.0", 49 | "webpack-cli": "^3.0.4", 50 | "webpack-if": "^0.1.2" 51 | }, 52 | "peerDependencies": { 53 | "jest": ">=21.0.0", 54 | "webpack": ">=4.0.0", 55 | "webpack-cli": ">=3.0.0" 56 | }, 57 | "dependencies": { 58 | "find-up": "^2.1.0", 59 | "mkdirp": "^0.5.1", 60 | "node-object-hash": "^1.2.0", 61 | "pify": "^3.0.0", 62 | "regenerator-runtime": "^0.10.5", 63 | "webpack-sources": "^1.0.1", 64 | "yargs": "^10.0.3" 65 | }, 66 | "jest": { 67 | "testPathIgnorePatterns": [ 68 | "/node_modules/", 69 | "/fixtures/" 70 | ], 71 | "testURL": "http://localhost/" 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/common-config.test.js: -------------------------------------------------------------------------------- 1 | const {run, itBuilt, itSkips, containsOutput, itPasses, itFails} = require('../fixtures/utils'); 2 | 3 | it('bail', () => { 4 | return run('config-bail') 5 | .then(itBuilt(['src/entry.test.js'])) 6 | .then(containsOutput(' failed, ')) 7 | .then(itFails); 8 | }, 30000); 9 | 10 | it('testMatch', () => { 11 | return run('config-testMatch') 12 | .then(itBuilt(['src/entry.test.js', 'src/entry.test2.js'])) 13 | .then(itPasses); 14 | }, 30000); 15 | -------------------------------------------------------------------------------- /src/common-flags.test.js: -------------------------------------------------------------------------------- 1 | const {run, itBuilt, didNotBuild, itTests, itSkips, containsOutput, itPasses, itFails} = 2 | require('../fixtures/utils'); 3 | 4 | it('--bail', () => { 5 | return run('flags-bail', ['--bail']) 6 | .then(itBuilt(['src/entry.test.js'])) 7 | .then(containsOutput([' failed, '])) 8 | .then(itFails); 9 | }, 30000); 10 | 11 | it('--testMatch', () => { 12 | return run('flags-testMatch', ['--testMatch', '**/src/entry.test{,2}.js']) 13 | .then(itBuilt(['src/entry.test.js', 'src/entry.test2.js'])) 14 | .then(itPasses); 15 | }, 30000); 16 | 17 | it('--testNamePattern', () => { 18 | return run('flags-testNamePattern', ['--testNamePattern', 'entry2', '--testPathPattern', 'entry2']) 19 | .then(itTests(['entry2'])) 20 | .then(itSkips(['entry1'])) 21 | .then(itPasses); 22 | }, 30000); 23 | 24 | it('--testPathPattern', () => { 25 | return run('flags-testPathPattern', ['--testPathPattern', 'entry1.test']) 26 | .then(didNotBuild(['src/entry2.test.js'])) 27 | .then(itTests(['entry1'])) 28 | .then(itSkips(['entry2'])) 29 | .then(itPasses); 30 | }, 30000); 31 | 32 | it('--testRegex', () => { 33 | return run('flags-testRegex', ['--testRegex', '/entry\\.test']) 34 | .then(itBuilt(['src/entry.test.js', 'src/entry.test2.js'])) 35 | .then(itPasses); 36 | }, 30000); 37 | -------------------------------------------------------------------------------- /src/emit-changed-assets-plugin.js: -------------------------------------------------------------------------------- 1 | const {join, relative, resolve} = require('path'); 2 | const {readFileSync} = require('fs'); 3 | 4 | const RawSource = require('webpack-sources/lib/RawSource'); 5 | 6 | const hash = require('./hash'); 7 | 8 | class EmitChangedAssetsPlugin { 9 | apply(compiler) { 10 | const config = compiler.options; 11 | 12 | // Don't emit files that were already written correctly. That'll cause jest 13 | // to run them again. 14 | compiler.plugin('emit', function(compilation, cb) { 15 | try { 16 | Object.keys(compilation.assets).forEach(function(key) { 17 | try { 18 | var keyPath = resolve(config.output.path, key); 19 | var existing = readFileSync(keyPath); 20 | if (hash(existing) === hash(compilation.assets[key].source())) { 21 | delete compilation.assets[key]; 22 | } 23 | } 24 | catch (err) {} 25 | }); 26 | } 27 | catch (err) { 28 | return cb(err); 29 | } 30 | cb(); 31 | }); 32 | } 33 | } 34 | 35 | module.exports = EmitChangedAssetsPlugin; 36 | -------------------------------------------------------------------------------- /src/emit-package-plugin.js: -------------------------------------------------------------------------------- 1 | const {join, relative, resolve} = require('path'); 2 | 3 | const pify = require('pify'); 4 | const RawSource = require('webpack-sources/lib/RawSource'); 5 | 6 | const hash = require('./hash'); 7 | 8 | class EmitPackagePlugin { 9 | apply(compiler) { 10 | const config = compiler.options; 11 | 12 | compiler.plugin('make', (compilation, cb) => { 13 | const inputFileSystem = compilation.inputFileSystem; 14 | const readFile = inputFileSystem.readFile.bind(inputFileSystem); 15 | pify(readFile)(join(config.context, 'package.json')) 16 | .then(src => { 17 | compilation.assets['package.json'] = new RawSource(src); 18 | cb(); 19 | }) 20 | .catch(cb); 21 | }); 22 | } 23 | } 24 | 25 | module.exports = EmitPackagePlugin; 26 | -------------------------------------------------------------------------------- /src/entry-per-module-plugin.js: -------------------------------------------------------------------------------- 1 | const {join, relative} = require('path'); 2 | 3 | const hash = require('./hash'); 4 | 5 | const SingleEntryPlugin = require('webpack/lib/SingleEntryPlugin'); 6 | 7 | class EntryPerModulePlugin { 8 | addEntry(uniqueResource, context, request) { 9 | this.entries[uniqueResource] = [context, request]; 10 | } 11 | 12 | apply(compiler) { 13 | compiler.plugin('compile', () => { 14 | this.entries = {}; 15 | }); 16 | 17 | compiler.plugin('make', (compilation, cb) => { 18 | const context = compiler.options.context; 19 | const modules = {}; 20 | compilation.plugin('build-module', module => { 21 | if (!module.external) {return;} 22 | if (!this.entries[module.request]) {return;} 23 | 24 | const [entryContext, request] = this.entries[module.request]; 25 | const requestParts = request.split('!'); 26 | const loaders = requestParts.slice(0, requestParts.length - 1) 27 | const absoluteResource = join( 28 | entryContext, 29 | requestParts[requestParts.length - 1] 30 | ); 31 | const entry = loaders.concat(absoluteResource).join('!'); 32 | let name = relative(context, module.request); 33 | 34 | if (!name.endsWith('.js')) { 35 | name = name + '.js'; 36 | } 37 | 38 | const dep = SingleEntryPlugin.createDependency(entry, name); 39 | modules[entry] = false; 40 | compilation.addEntry(entryContext, dep, name, (error, module) => { 41 | modules[entry] = true; 42 | module.name = name; 43 | if (Object.values(modules).reduce((carry, m) => carry && m, true)) { 44 | Promise.resolve() 45 | .then(() => cb()); 46 | } 47 | }); 48 | }); 49 | }); 50 | } 51 | } 52 | 53 | module.exports = EntryPerModulePlugin; 54 | -------------------------------------------------------------------------------- /src/entry-reference-dependency.js: -------------------------------------------------------------------------------- 1 | const ModuleDependency = require('webpack/lib/dependencies/ModuleDependency'); 2 | 3 | class EntryReferenceDependency extends ModuleDependency { 4 | constructor(resource) { 5 | super(resource); 6 | } 7 | } 8 | 9 | module.exports = EntryReferenceDependency; 10 | -------------------------------------------------------------------------------- /src/entry-reference-factory.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mzgoddard/jest-webpack/913974b784f988963f2c06833dbca8d274100a9b/src/entry-reference-factory.js -------------------------------------------------------------------------------- /src/entry-reference-module.js: -------------------------------------------------------------------------------- 1 | const {dirname} = require('path'); 2 | 3 | const RawSource = require('webpack-sources/lib/RawSource'); 4 | const Module = require('webpack/lib/Module'); 5 | 6 | const EntryReferenceTransformDependency = require('./entry-reference-transform-dependency'); 7 | 8 | const hash = require('./hash'); 9 | 10 | class EntryReferenceModule extends Module { 11 | constructor(resource) { 12 | super('javascript/auto'); 13 | this.context = dirname(resource); 14 | this.resource = resource; 15 | this.isEntry = false; 16 | this.entryRequest = null; 17 | this.dependencies = []; 18 | this.built = false; 19 | this.cacheable = false; 20 | } 21 | 22 | identifier() { 23 | return `entry reference ${this.resource.split('?')[0]}`; 24 | } 25 | 26 | readableIdentifier(requestShortener) { 27 | return `entry reference ${requestShortener.shorten(this.resource.split('?')[0])}`; 28 | } 29 | 30 | build(options, compilation, resolver, fs, callback) { 31 | this.built = true; 32 | this.buildMeta = {providedExports: []}; 33 | this.buildInfo = { 34 | cacheable: false, 35 | assets: [], 36 | dependencies: this.dependencies, 37 | fileDependencies: new Set, 38 | contextDependencies: new Set 39 | }; 40 | callback(); 41 | } 42 | 43 | source() { 44 | if (this._source) { 45 | return this._source; 46 | } 47 | 48 | const references = []; 49 | const refKeys = {}; 50 | this.dependencies.forEach(dep => { 51 | if (!dep.module) {return;} 52 | if (dep.request.indexOf('!') === -1 && dep.request.indexOf('?') === -1) { 53 | if (refKeys.default) {return;} 54 | refKeys.default = true; 55 | references.push(` default: function() {return __webpack_require__('${dep.module.id}');}`); 56 | } 57 | else { 58 | if (refKeys[hash(dep.request)]) {return;} 59 | refKeys[hash(dep.request)] = true; 60 | references.push(` ${JSON.stringify(hash(dep.request))}: function() {return __webpack_require__('${dep.module.id}');}`); 61 | } 62 | }); 63 | let rawSource = `module.exports = {\n${references.join(',\n')}\n};\n`; 64 | if (this.isEntry) { 65 | const requestHash = ( 66 | this.entryRequest.indexOf('!') === -1 && 67 | this.entryRequest.indexOf('?') === -1 68 | ) ? 69 | 'default' : 70 | hash(this.entryRequest); 71 | rawSource += `module.exports[${JSON.stringify(requestHash)}]();\n`; 72 | } 73 | this._source = new RawSource(rawSource); 74 | return this._source; 75 | } 76 | 77 | size() { 78 | return this.source().size(); 79 | } 80 | 81 | addData(dep) { 82 | const index = this.dependencies.findIndex(_dep => _dep.request === dep.request); 83 | if (index !== -1) { 84 | this.dependencies.splice(index, 1); 85 | } 86 | this.dependencies.push(dep); 87 | } 88 | } 89 | 90 | module.exports = EntryReferenceModule; 91 | -------------------------------------------------------------------------------- /src/entry-reference-plugin.js: -------------------------------------------------------------------------------- 1 | const {dirname, join, relative} = require('path'); 2 | 3 | const SingleEntryDependency = require('webpack/lib/dependencies/SingleEntryDependency'); 4 | const ExternalModule = require('webpack/lib/ExternalModule'); 5 | 6 | const EntryReferenceTransformDependency = require('./entry-reference-transform-dependency'); 7 | const EntryReferenceModule = require('./entry-reference-module'); 8 | const ReferenceEntryModule = require('./reference-entry-module'); 9 | const EntryReferenceDependency = require('./entry-reference-dependency'); 10 | 11 | const NodeTargetPlugin = require('webpack/lib/node/NodeTargetPlugin'); 12 | const NodeTemplatePlugin = require('webpack/lib/node/NodeTemplatePlugin'); 13 | 14 | class EntryReferencePlugin { 15 | constructor(options) { 16 | this.options = options; 17 | } 18 | 19 | apply(compiler) { 20 | const options = this.options || {}; 21 | const exclude = options.exclude || (path => /node_modules/.test(path)); 22 | 23 | let topCompilation; 24 | 25 | compiler.plugin('this-compilation', compilation => { 26 | topCompilation = compilation; 27 | }); 28 | 29 | compiler.plugin('compilation', (compilation, {normalModuleFactory}) => { 30 | compilation.dependencyFactories.set(EntryReferenceDependency, normalModuleFactory); 31 | compilation.dependencyFactories.set(EntryReferenceTransformDependency, normalModuleFactory); 32 | 33 | normalModuleFactory.plugin('before-resolve', (data, callback) => { 34 | if (typeof data.request === 'object') { 35 | data.request = data.rawRequest; 36 | } 37 | callback(null, data); 38 | }); 39 | 40 | normalModuleFactory.plugin('resolver', resolver => (result, callback) => { 41 | const dependency = result.dependency || result.dependencies[0]; 42 | 43 | if (dependency instanceof EntryReferenceDependency) { 44 | return callback(null, options.data.entries[dependency.request]); 45 | } 46 | 47 | resolver(result, (err, data) => { 48 | if (err) { 49 | return callback(err); 50 | } 51 | if (typeof data.source === 'function') { 52 | return options.data.compileFile(data.resource.split('?')[0], () => { 53 | const dep = new EntryReferenceTransformDependency('!!' + data.request); 54 | // dep.userRequest = data.userRequest; 55 | dep.module = data; 56 | options.data.entries[data.resource.split('?')[0]].addData(dep); 57 | callback(err, data); 58 | }); 59 | } 60 | 61 | if (dependency instanceof EntryReferenceTransformDependency) { 62 | return callback(null, data); 63 | } 64 | 65 | if (data.loaders.length === 0 && exclude(data.resource)) { 66 | const compilationDir = dirname(compilation.compiler.name); 67 | const compilerOutput = compiler.options.output.path; 68 | const compilationOutput = join(compilerOutput, compilationDir); 69 | const relativeResource = relative(compilationOutput, data.resource); 70 | return callback(err, new ExternalModule(relativeResource, 'commonjs2')); 71 | } 72 | 73 | options.data.compileModule(data.request, data.resource.split('?')[0], (err, dep) => { 74 | if (err) { 75 | return callback(err); 76 | } 77 | 78 | const shortResource = relative(compilation.compiler.options.context, data.resource.split('?')[0]); 79 | if (compilation.compiler.name === shortResource) { 80 | callback(null, data); 81 | } 82 | else { 83 | callback(null, new ReferenceEntryModule(data, dep)); 84 | } 85 | }, data.resource.split('?')[1] === '__jest_webpack_isEntry'); 86 | }); 87 | }); 88 | 89 | compilation.plugin('seal', () => { 90 | const entries = {}; 91 | const refs = {}; 92 | compilation.modules.forEach(module => { 93 | if (module instanceof EntryReferenceModule) { 94 | entries[module.resource] = module; 95 | if (refs[module.resource]) { 96 | const entryModule = module; 97 | refs[module.resource].map(refModule => { 98 | refModule.isSelfReference = true; 99 | refModule.selfModule = entryModule; 100 | }); 101 | } 102 | 103 | // Ensure modules are in the compilation modules list. 104 | module.dependencies.forEach(dep => { 105 | if (compilation.modules.indexOf(dep.module) === -1) { 106 | dep.module = compilation.modules.find(module => ( 107 | module.identifier() === dep.module.identifier() 108 | )); 109 | } 110 | }); 111 | } 112 | else if (module instanceof ReferenceEntryModule) { 113 | refs[module.resource] = 114 | (refs[module.resource] || []).concat(module); 115 | if (entries[module.resource]) { 116 | const entryModule = entries[module.resource]; 117 | const refModule = module; 118 | refModule.isSelfReference = true; 119 | refModule.selfModule = entryModule; 120 | } 121 | } 122 | }); 123 | }); 124 | }); 125 | } 126 | } 127 | 128 | module.exports = EntryReferencePlugin; 129 | -------------------------------------------------------------------------------- /src/entry-reference-plugin.test.js: -------------------------------------------------------------------------------- 1 | require('source-map-support').install({hookRequire: true}); 2 | 3 | const utils = require('../fixtures/utils'); 4 | 5 | it('builds multiple versions of a dependency', () => { 6 | return utils.run('module-multiple-loaders') 7 | .then(utils.itBuilt(['src/entry.js'])) 8 | .then(utils.itTests(['src/entry.test.js'])) 9 | .then(utils.itPasses); 10 | }, 30000); 11 | 12 | it('builds multiple versions of a test file', () => { 13 | return utils.run('module-multiple-loaders-test') 14 | .then(utils.itBuilt(['src/entry.test.js'])) 15 | .then(utils.itTests(['src/entry.test.js'])) 16 | .then(utils.itPasses); 17 | }, 30000); 18 | 19 | it('builds chunks ensured by a test file', () => { 20 | return utils.run('module-blocks') 21 | .then(utils.itBuilt(['src/entry.test.js'])) 22 | .then(utils.itTests(['src/entry.test.js'])) 23 | .then(utils.itPasses); 24 | }, 30000); 25 | 26 | it('builds variables in a test file', () => { 27 | return utils.run('module-variables') 28 | .then(utils.itBuilt(['src/entry.test.js'])) 29 | .then(utils.itTests(['src/entry.test.js'])) 30 | .then(utils.itPasses); 31 | }, 30000); 32 | -------------------------------------------------------------------------------- /src/entry-reference-transform-dependency.js: -------------------------------------------------------------------------------- 1 | const ModuleDependency = require('webpack/lib/dependencies/ModuleDependency'); 2 | 3 | class EntryReferenceTransformDependency extends ModuleDependency { 4 | constructor(request) { 5 | super(request); 6 | } 7 | } 8 | 9 | module.exports = EntryReferenceTransformDependency; 10 | -------------------------------------------------------------------------------- /src/file-hash.js: -------------------------------------------------------------------------------- 1 | const {basename, join} = require('path'); 2 | const {readdir, readFile, stat} = require('fs'); 3 | 4 | const pify = require('pify'); 5 | const nodeObjectHash = require('node-object-hash'); 6 | 7 | const hash = require('./hash'); 8 | 9 | const fileHash = file => pify(readFile)(file).then(hash); 10 | 11 | const contextHash = (dir, _contextHash = contextHash) => { 12 | return pify(readdir)(dir) 13 | .then(names => { 14 | return Promise.all(names.map(name => { 15 | const fullpath = join(dir, name); 16 | return pify(stat)(fullpath) 17 | .then(stat => { 18 | if (stat.isDirectory()) { 19 | return _contextHash(fullpath); 20 | } 21 | else { 22 | return fileHash(fullpath); 23 | } 24 | }); 25 | })); 26 | }) 27 | .then(hashes => hashes.filter(Boolean)) 28 | .then(hashes => hashes.reduce((carry, value) => hash(carry + value))); 29 | }; 30 | 31 | const depsContextHash = dir => { 32 | return contextHash(dir, dir => ( 33 | basename(dir).startsWith('.') ? 34 | null : 35 | pify(stat)(join(dir, 'package.json')) 36 | .then(() => fileHash(join(dir, 'package.json'))) 37 | .catch(() => contextHash(dir, dir => hash(dir))) 38 | )); 39 | }; 40 | 41 | const configHash = config => { 42 | return nodeObjectHash({sort: false}).hash(config); 43 | }; 44 | 45 | module.exports = { 46 | configHash, 47 | contextHash, 48 | depsContextHash, 49 | fileHash, 50 | }; 51 | -------------------------------------------------------------------------------- /src/hash.js: -------------------------------------------------------------------------------- 1 | const {createHash} = require('crypto'); 2 | 3 | function hash(content) { 4 | return createHash('sha1').update(content).digest().hexSlice(); 5 | } 6 | 7 | module.exports = hash; 8 | -------------------------------------------------------------------------------- /src/hash.test.js: -------------------------------------------------------------------------------- 1 | // require('source-map-support').install({hookRequire: true}); 2 | 3 | const hash = require('./hash'); 4 | 5 | describe('hash', () => { 6 | it('hashes a string', () => { 7 | expect(typeof hash('string')).toBe('string'); 8 | expect(hash('string')).toBe('ecb252044b5ea0f679ee78ec1a12904739e2904d'); 9 | }); 10 | }); 11 | -------------------------------------------------------------------------------- /src/jest-exec.js: -------------------------------------------------------------------------------- 1 | const {spawn} = require('child_process'); 2 | const {statSync} = require('fs'); 3 | 4 | const findUp = require('find-up'); 5 | 6 | const jestExec = (args = [], options = {}) => { 7 | const _jestPath = findUp.sync('node_modules/.bin/jest', { 8 | cwd: __dirname, 9 | }); 10 | const _jestWindowsPath = _jestPath + '.cmd'; 11 | 12 | let jestPath; 13 | try { 14 | statSync(_jestWindowsPath); 15 | jestPath = _jestWindowsPath; 16 | } 17 | catch (_) { 18 | jestPath = _jestPath; 19 | } 20 | 21 | const concat = stream => { 22 | return new Promise(resolve => { 23 | let value = ''; 24 | stream.on('data', data => {value += data;}); 25 | stream.on('end', () => {resolve(value);}); 26 | }); 27 | }; 28 | 29 | return new Promise((resolve, reject) => { 30 | const child = spawn( 31 | jestPath, 32 | args, 33 | Object.assign({}, options, { 34 | env: Object.assign({}, process.env, { 35 | NODE_ENV: 'test', 36 | }, options.env), 37 | stdio: options.stdio || 'inherit', 38 | }) 39 | ); 40 | let stdout, stderr; 41 | if (options.stdio === 'pipe') { 42 | stdout = concat(child.stdout); 43 | stderr = concat(child.stderr); 44 | } 45 | child.on('exit', code => { 46 | Promise.all([stdout, stderr]) 47 | .then(([stdout, stderr]) => { 48 | resolve({ 49 | code, 50 | stdout, 51 | stderr, 52 | }); 53 | }); 54 | }); 55 | }); 56 | }; 57 | 58 | const jestConfig = (args = [], options = {}) => { 59 | return jestExec(args.concat('--showConfig'), {stdio: 'pipe'}) 60 | .then(({stdout}) => stdout) 61 | .then(JSON.parse); 62 | }; 63 | 64 | jestExec.config = jestConfig; 65 | 66 | module.exports = jestExec; 67 | -------------------------------------------------------------------------------- /src/jest-webpack-plugin.js: -------------------------------------------------------------------------------- 1 | const {join} = require('path'); 2 | 3 | // const EmitChangedAssetsPlugin = require('./emit-changed-assets-plugin'); 4 | const EmitPackagePlugin = require('./emit-package-plugin'); 5 | // const EntryPerModulePlugin = require('./entry-per-module-plugin'); 6 | const EntryReferencePlugin = require('./entry-reference-plugin'); 7 | const ManifestPlugin = require('./manifest-plugin'); 8 | const RunJestWhenDonePlugin = require('./run-jest-when-done-plugin'); 9 | const SharedData = require('./shared-data'); 10 | const TestEntriesPlugin = require('./test-entries-plugin'); 11 | 12 | const hash = require('./hash'); 13 | 14 | class JestWebpackPlugin { 15 | constructor(options = {}) { 16 | this.options = options; 17 | } 18 | 19 | // externals: Treat dependencies that match any test in this option as 20 | // external to the chunk being built. Being external they need to be a script 21 | // as their own chunk or not need webpack to handle them. 22 | externals(context, depRequest, cb) { 23 | const request = typeof depRequest === 'string' ? depRequest : depRequest.rawRequest; 24 | const requestParts = request.split('!'); 25 | const resource = requestParts[requestParts.length - 1]; 26 | 27 | if (/^\w/.test(resource)) { 28 | // All other modules are expected to not need webpack work and come from 29 | // node_modules (e.g. react). 30 | cb(null, request, 'commonjs2'); 31 | } 32 | else { 33 | cb(null); 34 | } 35 | } 36 | 37 | apply(compiler) { 38 | const {argv, jestArgv, jestConfig} = this.options; 39 | 40 | compiler.options.entry = {}; 41 | compiler.options.output.path = this.options.path || 42 | join(compiler.options.context, '.cache/jest-webpack'); 43 | compiler.options.output.filename = '[name]'; 44 | compiler.options.output.chunkFilename = '[hash].[id].js'; 45 | // Need an appropriate libraryTarget to get the output from a built module. 46 | compiler.options.output.libraryTarget = 'commonjs2'; 47 | // Jest is going to require files like in node. The output chunks need to 48 | // module.exports their entry module. 49 | compiler.options.target = 'node'; 50 | // compiler.options.externals = this.externals.bind(this); 51 | 52 | const shared = new SharedData(); 53 | 54 | new EmitPackagePlugin().apply(compiler); 55 | // new EmitChangedAssetsPlugin().apply(compiler); 56 | new ManifestPlugin({data: shared}).apply(compiler); 57 | new EntryReferencePlugin({data: shared}).apply(compiler); 58 | // this.entryPerModule = new EntryPerModulePlugin(); 59 | // this.entryPerModule.apply(compiler); 60 | new RunJestWhenDonePlugin({argv, jestArgv}).apply(compiler); 61 | new TestEntriesPlugin({data: shared, jestArgv, jestConfig}).apply(compiler); 62 | } 63 | } 64 | 65 | module.exports = JestWebpackPlugin; 66 | -------------------------------------------------------------------------------- /src/jest-webpack.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const {join, dirname} = require('path'); 3 | 4 | const findUp = require('find-up'); 5 | const webpack = require('webpack'); 6 | 7 | const JestWebpackPlugin = require('./jest-webpack-plugin'); 8 | 9 | // Try to require jest's specific dependencies to mirror its argv and config 10 | // behaviour to try and match how it determines things like test files to 11 | // execute. 12 | const tryRequire = require('./try-require'); 13 | const {readConfig} = tryRequire( 14 | () => require('jest/node_modules/jest-cli/node_modules/jest-config'), 15 | () => require('jest-cli/node_modules/jest-config'), 16 | () => require('jest-config') 17 | ); 18 | const jestYargs = tryRequire( 19 | () => require('jest/node_modules/jest-cli/node_modules/yargs/yargs'), 20 | () => require('jest-cli/node_modules/yargs/yargs'), 21 | () => require('yargs/yargs') 22 | ); 23 | const jestArgs = tryRequire( 24 | () => require('jest/node_modules/jest-cli/build/cli/args'), 25 | () => require('jest-cli/build/cli/args') 26 | ); 27 | 28 | function main(argv, config) { 29 | // Ensure JestWebpackPlugin is active 30 | 31 | const packageRoot = dirname(findUp.sync('package.json', { 32 | cwd: config.context, 33 | })); 34 | 35 | // Echo jest's argv and jest config behaviour 36 | const jestArgv = jestYargs(argv.slice()) 37 | .usage(jestArgs.usage) 38 | .alias('help', 'h') 39 | .options(jestArgs.options) 40 | .epilogue(jestArgs.docs) 41 | .check(jestArgs.check) 42 | .version(false) 43 | .parse(argv.slice()); 44 | 45 | const jestConfig = readConfig(jestArgv, packageRoot); 46 | 47 | // var coverageMode = process.argv 48 | // .reduce(function(carry, opt) {return carry || /^--coverage$/.test(opt);}, false); 49 | 50 | // if (coverageMode) { 51 | // config.babel.plugins = (config.babel.plugins || []).concat('istanbul'); 52 | // } 53 | 54 | config = Object.assign({}, config, { 55 | plugins: config.plugins || [] 56 | }); 57 | config.plugins.push(new JestWebpackPlugin({argv, jestArgv, jestConfig})); 58 | 59 | try { 60 | // If we can resolve JavascriptGenerator assume config takes a mode option. 61 | require.resolve('webpack/lib/JavascriptGenerator'); 62 | // Set mode to 'development' by default. 63 | if (!config.mode) { 64 | config.mode = 'development'; 65 | } 66 | } 67 | catch (error) { 68 | // If we cannot resolve JavascriptGenerator there is no mode option 69 | } 70 | 71 | var compiler = webpack(config); 72 | 73 | // var watchMode = process.argv 74 | // .reduce(function(carry, opt) {return carry || /^--watch/.test(opt);}, false); 75 | 76 | const watchMode = false; 77 | 78 | if (watchMode) { 79 | compiler.watch({}, function(err) { 80 | if (err) { 81 | console.error(err.stack || err); 82 | process.exit(); 83 | } 84 | }); 85 | } 86 | else { 87 | compiler.run(function(err) { 88 | if (err) { 89 | console.error(err.stack || err); 90 | process.exit(); 91 | } 92 | }); 93 | } 94 | } 95 | 96 | // let once = true; 97 | if (process.argv[1] === __filename) { 98 | main(process.argv.slice(2), eval('require')(join(process.cwd(), 'webpack.config.js'))); 99 | } 100 | 101 | module.exports = main; 102 | -------------------------------------------------------------------------------- /src/manifest-plugin.js: -------------------------------------------------------------------------------- 1 | const {join} = require('path'); 2 | 3 | const pify = require('pify'); 4 | const RawSource = require('webpack-sources/lib/RawSource'); 5 | 6 | const hash = require('./hash'); 7 | const ReferenceEntryModule = require('./reference-entry-module'); 8 | const {configHash, contextHash, depsContextHash, fileHash} = 9 | require('./file-hash'); 10 | 11 | const hashMembers = (members, memberHash) => { 12 | return Promise.all(Array.from(members).map(memberHash)) 13 | .then(hashes => hashes.reduce((carry, value) => hash(carry + value), '')); 14 | }; 15 | 16 | class ManifestPlugin { 17 | constructor(options = {}) { 18 | this.options = options; 19 | } 20 | 21 | apply(compiler) { 22 | const options = this.options; 23 | const _configHash = configHash(compiler.options); 24 | 25 | const manifestPath = join(compiler.options.context, '.cache/jest-webpack/manifest.json'); 26 | const depsDirs = [join(compiler.options.context, 'node_modules')]; 27 | const depsHash = Promise.all(depsDirs.map(depsContextHash)) 28 | .catch(() => ['']) 29 | .then(hashes => hashes.reduce((carry, value) => hash(carry + value))); 30 | 31 | compiler.plugin(['watch-run', 'run'], (compiler, cb) => { 32 | const inputFileSystem = compiler.inputFileSystem; 33 | const readFile = inputFileSystem.readFile.bind(inputFileSystem); 34 | const manifest = pify(readFile)(manifestPath) 35 | .then(file => file.toString()) 36 | .catch(() => '{}') 37 | .then(JSON.parse); 38 | Promise.all([manifest, depsHash]) 39 | .then(([manifest, depsHash]) => { 40 | return Promise.all( 41 | Object.keys(manifest) 42 | .filter(resource => !resource.startsWith('__')) 43 | .map(resource => { 44 | return Promise.all([ 45 | pify(readFile)(join(compiler.options.context, '.cache/jest-webpack', resource)) 46 | .then(hash) 47 | .catch(() => ''), 48 | hashMembers(manifest[resource].fileDependencies || manifest[resource].buildInfo.fileDependencies || [], fileHash), 49 | hashMembers(manifest[resource].contextDependencies || manifest[resource].buildInfo.contextDependencies || [], contextHash), 50 | ]) 51 | .then(([hash, fileHash, contextHash]) => { 52 | if ( 53 | manifest[resource].hash === hash && 54 | manifest[resource].fileHash === fileHash && 55 | manifest[resource].contextHash === contextHash 56 | ) { 57 | return { 58 | resource: join(compiler.options.context, resource), 59 | transforms: manifest[resource].transforms, 60 | }; 61 | } 62 | }); 63 | }) 64 | ) 65 | .then(entries => entries.filter(Boolean)) 66 | .then(entries => { 67 | const _manifest = { 68 | __depsHash: manifest.__depsHash, 69 | __configHash: manifest.__configHash, 70 | }; 71 | entries.forEach(entry => { 72 | _manifest[entry.resource] = entry; 73 | }); 74 | return [_manifest, depsHash]; 75 | }); 76 | }) 77 | .then(([manifest, depsHash]) => { 78 | if ( 79 | manifest.__depsHash !== depsHash || 80 | manifest.__configHash !== _configHash 81 | ) { 82 | options.data.setManifest(null); 83 | return cb(); 84 | } 85 | 86 | options.data.setManifest(manifest); 87 | // options.data.setManifest(null); 88 | cb(); 89 | }) 90 | .catch(err => {console.error(err); throw err;}) 91 | .catch(cb); 92 | }); 93 | 94 | compiler.plugin('emit', (compilation, cb) => { 95 | const inputFileSystem = compilation.inputFileSystem; 96 | const readFile = inputFileSystem.readFile.bind(inputFileSystem); 97 | pify(readFile)(manifestPath) 98 | .then(file => file.toString()) 99 | .catch(() => '{}') 100 | .then(JSON.parse) 101 | .then(manifest => { 102 | return Promise.all(compilation.children.map(fileCompilation => { 103 | const source = 104 | fileCompilation.assets[fileCompilation.compiler.name].source(); 105 | return Promise.all([ 106 | Promise.resolve(source) 107 | .then(hash) 108 | .catch(() => ''), 109 | hashMembers(fileCompilation.fileDependencies, fileHash), 110 | hashMembers(fileCompilation.contextDependencies, contextHash), 111 | ]) 112 | .then(([hash, fileHash, contextHash]) => { 113 | // console.log('new version', fileCompilation.compiler.name, hash); 114 | const listDepRequests = block => ( 115 | block.dependencies 116 | .map(dep => dep.module instanceof ReferenceEntryModule ? 117 | dep.module.dep.request : 118 | null) 119 | .filter(Boolean) 120 | ); 121 | const listVarRequests = block => ( 122 | block.variables.map(listDepRequests) 123 | ); 124 | const listBlkRequests = block => ( 125 | listDepRequests(block) 126 | .concat(...listVarRequests(block)) 127 | .concat(...block.blocks.map(listBlkRequests)) 128 | ); 129 | manifest[fileCompilation.compiler.name] = { 130 | fileDependencies: Array.from(fileCompilation.fileDependencies), 131 | contextDependencies: Array.from(fileCompilation.contextDependencies), 132 | hash, 133 | fileHash, 134 | contextHash, 135 | transforms: fileCompilation.modules 136 | .find(module => module.identifier().startsWith("entry reference")) 137 | .dependencies 138 | .reduce((carry, dep) => { 139 | if (!carry.find(_dep => _dep.module.request === dep.module.request)) { 140 | carry.push(dep); 141 | } 142 | return carry; 143 | }, []) 144 | .map(dep => ({ 145 | isEntry: !dep.module.rawRequest.startsWith('!!'), 146 | request: dep.module.request, 147 | rawRequest: dep.module.rawRequest, 148 | dependencies: listBlkRequests(dep.module) 149 | .reduce((carry, dep) => { 150 | if (carry.indexOf(dep) === -1) { 151 | carry.push(dep); 152 | } 153 | return carry; 154 | }, []), 155 | })), 156 | }; 157 | }); 158 | })) 159 | .then(() => depsHash) 160 | .then(depsHash => { 161 | manifest.__configHash = _configHash; 162 | manifest.__depsHash = depsHash; 163 | compilation.assets['manifest.json'] = new RawSource(JSON.stringify(manifest)); 164 | }); 165 | }) 166 | .then(() => cb()) 167 | .catch(cb);; 168 | }); 169 | } 170 | } 171 | 172 | module.exports = ManifestPlugin; 173 | -------------------------------------------------------------------------------- /src/manifest-plugin.test.js: -------------------------------------------------------------------------------- 1 | // require('source-map-support').install({hookRequire: true}); 2 | 3 | const utils = require('../fixtures/utils'); 4 | 5 | const itCaches = (fixture, file) => { 6 | it(`caches "${fixture}"`, () => { 7 | return utils.run(fixture) 8 | .then(utils.itBuilt([file])) 9 | .then(utils.itTests([file])) 10 | .then(utils.itPasses) 11 | .then(utils.runAgain) 12 | .then(utils.didNotBuild([file])) 13 | .then(utils.itTests([file])) 14 | .then(utils.itPasses); 15 | }, 30000); 16 | }; 17 | 18 | const itCachesChange = (fixture, {built, notBuilt, tests, filesA, filesB}) => { 19 | it(`caches and builds changes "${fixture}"`, () => { 20 | return utils.willRun(fixture) 21 | .then(utils.clean) 22 | .then(utils.writeFiles(filesA)) 23 | .then(utils.runAgain) 24 | .then(utils.itBuilt([built, notBuilt].filter(Boolean))) 25 | .then(utils.itTests([tests].filter(Boolean))) 26 | .then(utils.itPasses) 27 | .then(utils.writeFiles(filesB)) 28 | .then(utils.runAgain) 29 | .then(utils.itBuilt([built].filter(Boolean))) 30 | .then(utils.didNotBuild([notBuilt].filter(Boolean))) 31 | .then(utils.itTests([tests].filter(Boolean))) 32 | .then(utils.itPasses); 33 | }, 30000); 34 | }; 35 | 36 | itCaches('module-blocks', 'src/entry.test.js'); 37 | itCaches('module-multiple-loaders', 'src/entry.test.js'); 38 | itCaches('module-multiple-loaders-test', 'src/entry.test.js'); 39 | itCaches('module-recursive', 'src/entry.test.js'); 40 | itCaches('module-variables', 'src/entry.test.js'); 41 | itCaches('test-entries-src', 'src/entry.test.js'); 42 | itCaches('test-entries-src-babel', 'src/entry.test.js'); 43 | 44 | itCachesChange('cache-blocks-change', { 45 | built: 'src/entry.js', 46 | notBuilt: 'src/entry.test.js', 47 | tests: 'src/entry.test.js', 48 | filesA: { 49 | 'src/entry.js': [ 50 | 'var fact = function(n) {', 51 | ' return n > 0 ? fact(n - 1) + n : 0;', 52 | '};', 53 | '', 54 | 'module.exports = fact;', 55 | ], 56 | }, 57 | filesB: { 58 | 'src/entry.js': [ 59 | 'var fact2 = function(n) {', 60 | ' return n > 0 ? fact2(n - 1) + n : 0;', 61 | '};', 62 | '', 63 | 'module.exports = fact2;', 64 | ], 65 | }, 66 | }); 67 | 68 | itCachesChange('cache-multiple-loaders-change', { 69 | built: 'src/entry.js', 70 | tests: 'src/entry.test.js', 71 | filesA: { 72 | 'src/entry.test.js': [ 73 | 'var entry = require("./entry");', 74 | '', 75 | 'it("loads", function() {', 76 | ' expect(entry).toBeInstanceOf(Function);', 77 | ' expect(entry(3)).toBe(6);', 78 | '});', 79 | ], 80 | }, 81 | filesB: { 82 | 'src/entry.test.js': [ 83 | 'var entry = require("./entry");', 84 | 'var rawEntry = require("raw-loader!./entry");', 85 | '', 86 | 'it("loads", function() {', 87 | ' expect(entry).toBeInstanceOf(Function);', 88 | ' expect(entry(3)).toBe(6);', 89 | ' expect(typeof rawEntry).toBe("string");', 90 | ' expect(rawEntry).toMatch("var fact");', 91 | '});', 92 | ], 93 | }, 94 | }); 95 | -------------------------------------------------------------------------------- /src/reference-entry-module.js: -------------------------------------------------------------------------------- 1 | const {dirname, relative} = require('path'); 2 | 3 | const Module = require('webpack/lib/Module'); 4 | const NullDependency = require('webpack/lib/dependencies/NullDependency'); 5 | const RawSource = require('webpack-sources/lib/RawSource'); 6 | 7 | const hash = require('./hash'); 8 | 9 | class ReferenceEntryModule extends Module { 10 | constructor(data, dep) { 11 | super('javascript/auto'); 12 | this.data = data; 13 | this.dep = dep; 14 | this.context = data.context; 15 | this.resource = data.resource; 16 | this.dependencies = []; 17 | this.built = false; 18 | this.cacheable = false; 19 | this.isSelfReference = false; 20 | this.selfModule = null; 21 | } 22 | 23 | get requestHash() { 24 | return (this.data.loaders.length || this.data.resource.indexOf('?') !== -1) ? 25 | hash(this.dep.request) : 26 | 'default'; 27 | } 28 | 29 | identifier() { 30 | return `reference ${this.resource.split('?')[0]}.${this.requestHash}`; 31 | } 32 | 33 | readableIdentifier(requestShortener) { 34 | return `reference ${requestShortener.shorten(this.resource.split('?')[0])}.${this.requestHash}`; 35 | } 36 | 37 | build(options, compilation, resolver, fs, callback) { 38 | this.built = true; 39 | this.buildMeta = { 40 | providedExports: [] 41 | }; 42 | this.buildInfo = { 43 | cacheable: false, 44 | assets: [], 45 | dependencies: this.dependencies, 46 | fileDependencies: new Set, 47 | contextDependencies: new Set 48 | }; 49 | callback(); 50 | } 51 | 52 | source() { 53 | if (!this._source) { 54 | let moduleRequire = `require(${JSON.stringify('./' + relative(this.context, this.resource.split('?')[0]))})`; 55 | if (this.isSelfReference) { 56 | moduleRequire = `__webpack_require__(${this.selfModule.id})`; 57 | this.dependencies.push(new NullDependency()); 58 | } 59 | const moduleIdHash = JSON.stringify(this.requestHash); 60 | this._source = new RawSource( 61 | `module.exports = ${moduleRequire}[${moduleIdHash}]();` 62 | ); 63 | } 64 | return this._source; 65 | } 66 | 67 | size() { 68 | return this.source().size(); 69 | } 70 | } 71 | 72 | module.exports = ReferenceEntryModule; 73 | -------------------------------------------------------------------------------- /src/run-jest-when-done-plugin.js: -------------------------------------------------------------------------------- 1 | const {join} = require('path'); 2 | 3 | const jest = require('jest'); 4 | 5 | const tapable = require('tapable'); 6 | 7 | class RunJestWhenDone { 8 | constructor(options = {}) { 9 | this.options = options; 10 | } 11 | 12 | apply(compiler) { 13 | const {argv, jestArgv} = this.options; 14 | // let cliOnce = false; 15 | 16 | if (compiler.hooks && !compiler.hooks.jestWebpackDone) { 17 | compiler.hooks.jestWebpackDone = new tapable.SyncHook(['result']); 18 | } 19 | 20 | compiler.plugin('done', function() { 21 | const config = compiler.options; 22 | // if (watchMode && cliOnce) { 23 | // return; 24 | // } 25 | // cliOnce = true; 26 | 27 | const wd = join(config.context, '.cache/jest-webpack'); 28 | const oldWd = process.cwd(); 29 | process.chdir(wd); 30 | 31 | const jestDone = result => { 32 | if ( 33 | compiler.hooks && compiler.hooks.jestWebpackDone.taps.length || 34 | !compiler.hooks && compiler._plugins['jest-webpack-done'] 35 | ) { 36 | try { 37 | process.chdir(oldWd); 38 | } 39 | finally { 40 | if (compiler.hooks) { 41 | compiler.hooks.jestWebpackDone.call(result); 42 | } 43 | else { 44 | compiler.applyPlugins('jest-webpack-done', result); 45 | } 46 | } 47 | return true; 48 | } 49 | }; 50 | 51 | const run = () => { 52 | return new Promise((resolve, reject) => { 53 | const promise = jest.runCLI(jestArgv, [wd], (e, result) => { 54 | if (e) {return reject(e);} 55 | return resolve(result); 56 | }); 57 | if (promise) { 58 | promise.then(resolve, reject); 59 | } 60 | }); 61 | }; 62 | 63 | try { 64 | console.log(); 65 | 66 | run() 67 | .then((result) => { 68 | if (!jestDone(result.results)) { 69 | if (!result.results.success) { 70 | process.on('exit', () => process.exit(1)); 71 | } 72 | else if (jestArgv.forceExit) { 73 | process.exit(result.results.success ? 0 : 1); 74 | } 75 | } 76 | }) 77 | .catch(function(e) { 78 | console.error(e.stack || e); 79 | jestDone({success: false}); 80 | process.exit(1); 81 | }); 82 | } 83 | catch (e) { 84 | console.error(e.stack || e); 85 | jestDone({success: false}); 86 | process.exit(1); 87 | } 88 | }); 89 | } 90 | } 91 | 92 | module.exports = RunJestWhenDone; 93 | -------------------------------------------------------------------------------- /src/shared-data.js: -------------------------------------------------------------------------------- 1 | const {basename, dirname, join, relative, sep} = require('path'); 2 | 3 | const NodeTemplatePlugin = require('webpack/lib/node/NodeTemplatePlugin'); 4 | const NodeTargetPlugin = require('webpack/lib/node/NodeTargetPlugin'); 5 | const LibraryTemplatePlugin = require('webpack/lib/LibraryTemplatePlugin'); 6 | 7 | const EntryReferenceModule = require('./entry-reference-module'); 8 | const EntryReferenceDependency = require('./entry-reference-dependency'); 9 | const EntryReferenceTransformDependency = require('./entry-reference-transform-dependency'); 10 | 11 | class SharedData { 12 | constructor() { 13 | this.compilation = null; 14 | this.manifest = null; 15 | this.fulfilledManifest = null; 16 | this.entries = {}; 17 | this.modules = {}; 18 | this.compilations = {}; 19 | this.modulesRunning = 0; 20 | this.modulesCompleted = 0; 21 | this.entriesRunning = 0; 22 | this.entriesCompleted = 0; 23 | this.filesRunning = 0; 24 | this.filesCompleted = 0; 25 | this.fileCallbacks = {}; 26 | this.fileErrors = {}; 27 | this.entryCallbacks = {}; 28 | this.entryErrors = {}; 29 | this.entryCompleted = {}; 30 | this.entryCompletedCallbacks = {}; 31 | this.moduleErrors = {}; 32 | this.onComplete = () => {}; 33 | } 34 | 35 | reset(compilation, onComplete) { 36 | this.compilation = compilation; 37 | this.entries = {}; 38 | this.modules = {}; 39 | this.compilations = {}; 40 | this.modulesRunning = 0; 41 | this.modulesCompleted = 0; 42 | this.entriesRunning = 0; 43 | this.entriesCompleted = 0; 44 | this.filesRunning = 0; 45 | this.filesCompleted = 0; 46 | this.fileCallbacks = {}; 47 | this.fileErrors = {}; 48 | this.entryCallbacks = {}; 49 | this.entryErrors = {}; 50 | this.entryCompleted = {}; 51 | this.entryCompletedCallbacks = {}; 52 | this.moduleErrors = {}; 53 | this.onComplete = onComplete; 54 | } 55 | 56 | setManifest(manifest) { 57 | this.manifest = manifest; 58 | this.fulfilledManifest = {}; 59 | } 60 | 61 | startFile(resource) { 62 | this.filesRunning++; 63 | } 64 | 65 | completeFile(resource, error) { 66 | this.filesCompleted++; 67 | 68 | if ( 69 | this.filesCompleted == this.filesRunning && 70 | this.fulfilledManifest && 71 | this.manifest 72 | ) { 73 | for (const key in this.fulfilledManifest) { 74 | const item = this.fulfilledManifest[key]; 75 | const oldItem = this.manifest[key]; 76 | // item && item.transforms.sort(); 77 | // oldItem && oldItem.transforms.sort(); 78 | if ( 79 | item && oldItem && 80 | item.transforms.length < oldItem.transforms.length 81 | // ( 82 | // item.transforms.length !== oldItem.transforms.length || 83 | // oldItem.transforms.some(t => !item.transforms.find(_t => ( 84 | // _t.request === t.request && 85 | // _t.isEntry === t.isEntry 86 | // ))) 87 | // ) 88 | ) { 89 | // console.log('smaller file', key, item.transforms.length, oldItem.transforms.length); 90 | this.manifest[key] = null; 91 | for (const index in oldItem.transforms) { 92 | // this.compileModule(oldItem.transforms[index], key, () => {}); 93 | } 94 | } 95 | } 96 | } 97 | if (this.filesCompleted == this.filesRunning) { 98 | // this.manifest && 99 | // console.log( 100 | // Object.keys(this.manifest) 101 | // .filter(key => this.manifest[key]), 102 | // this.filesCompleted 103 | // ); 104 | this.onComplete(); 105 | } 106 | } 107 | 108 | startEntry(resource, callback) { 109 | this.entryCallbacks[resource] = callback; 110 | this.entriesRunning++; 111 | } 112 | 113 | completeEntry(resource, error) { 114 | this.entryErrors[resource] = error; 115 | this.entryCompleted[resource] = true; 116 | this.entriesCompleted++; 117 | 118 | if (this.entryCompletedCallbacks[resource]) { 119 | this.entryCompletedCallbacks[resource].forEach(cb => cb()); 120 | } 121 | 122 | this.checkCompletion(); 123 | } 124 | 125 | startModule(request) { 126 | this.modulesRunning++; 127 | } 128 | 129 | completeModule(request, error) { 130 | this.moduleErrors[request] = error; 131 | this.modulesCompleted++; 132 | 133 | this.checkCompletion(); 134 | } 135 | 136 | checkCompletion() { 137 | if ( 138 | this.modulesRunning === this.modulesCompleted && 139 | this.entriesCompleted === this.entriesRunning 140 | ) { 141 | for (let key in this.entryCallbacks) { 142 | this.entryCallbacks[key](this.entryErrors[key]); 143 | } 144 | } 145 | } 146 | 147 | compileFile(resource, callback) { 148 | if (this.entryCompleted[resource]) { 149 | return callback(); 150 | } 151 | 152 | if (!this.entryCompletedCallbacks[resource]) { 153 | this.entryCompletedCallbacks[resource] = []; 154 | } 155 | this.entryCompletedCallbacks[resource].push(callback); 156 | 157 | if (!this.compilations[resource]) { 158 | const shortResource = relative(this.compilation.compiler.options.context, resource); 159 | this.startFile(resource); 160 | 161 | this.entries[resource] = new EntryReferenceModule(resource); 162 | 163 | const _this = this; 164 | 165 | const child = this.compilation.compiler.createChildCompiler(this.compilation, shortResource); 166 | child.records = this.compilation.compiler.records[shortResource][0]; 167 | [ 168 | new NodeTemplatePlugin({ 169 | asyncChunkLoading: false, 170 | }), 171 | new NodeTargetPlugin(), 172 | new LibraryTemplatePlugin(this.compilation.compiler.options.output.library, this.compilation.compiler.options.output.libraryTarget, this.compilation.compiler.options.output.umdNamedDefine, this.compilation.compiler.options.output.auxiliaryComment || ""), 173 | { 174 | apply(compiler) { 175 | compiler.plugin('this-compilation', compilation => { 176 | compilation.plugin('optimize-assets', (assets, cb) => { 177 | Object.keys(assets).forEach(key => { 178 | const newKey = join(dirname(shortResource), basename(key)); 179 | if (newKey === key) {return;} 180 | assets[newKey] = assets[key]; 181 | delete assets[key]; 182 | }); 183 | cb(); 184 | }); 185 | }); 186 | 187 | compiler.plugin('make', (compilation, cb) => { 188 | // Store compilation to add transforms to later. 189 | _this.compilations[resource] = compilation; 190 | 191 | // Use or create a cache for this compilation in the parent cache. 192 | if (compilation.cache) { 193 | if (!compilation.cache[shortResource]) { 194 | compilation.cache[shortResource] = {}; 195 | } 196 | compilation.cache = compilation.cache[shortResource]; 197 | } 198 | 199 | _this.startEntry(resource, cb); 200 | return compilation.addEntry( 201 | // context 202 | dirname(resource), 203 | // dependency 204 | new EntryReferenceDependency(resource), 205 | // compilation name 206 | shortResource, 207 | (err, module) => { 208 | // The callback will be called when all modules have been 209 | // built. 210 | _this.completeEntry(resource, err); 211 | } 212 | ); 213 | }); 214 | }, 215 | }, 216 | ].forEach(plugin => plugin.apply(child)); 217 | child.runAsChild(err => { 218 | this.completeFile(resource, err); 219 | }); 220 | } 221 | } 222 | 223 | compileModule(request, resource, callback, isEntry = false) { 224 | this.modules[request] = true; 225 | 226 | if ( 227 | this.manifest && 228 | this.manifest[resource] && 229 | ( 230 | this.manifest[resource].transforms 231 | .findIndex(transform => ( 232 | transform.request === request && transform.isEntry === isEntry 233 | )) !== -1 234 | ) 235 | ) { 236 | this.fulfilledManifest[resource] = this.fulfilledManifest[resource] || { 237 | transforms: [], 238 | }; 239 | this.fulfilledManifest[resource].transforms.push({ 240 | request, 241 | resource, 242 | isEntry, 243 | }); 244 | 245 | this.manifest[resource].transforms 246 | .find(transform => transform.request === request) 247 | .dependencies.forEach(dep => { 248 | const depSplit = dep.split('!'); 249 | const resource = depSplit[depSplit.length - 1]; 250 | 251 | // Stop modules from recursively looping at this point. 252 | if (!this.modules[dep]) { 253 | this.compileModule(dep, resource.split('?')[0], () => {}, resource.split('?')[1] === '__jest_webpack_isEntry'); 254 | } 255 | }); 256 | 257 | return callback(null, { 258 | request: request, 259 | }); 260 | } 261 | 262 | if (this.manifest && this.manifest[resource]) { 263 | this.manifest[resource] = null; 264 | 265 | if (this.fulfilledManifest && this.fulfilledManifest[resource]) { 266 | this.fulfilledManifest[resource].transforms 267 | .forEach(({request, resource, isEntry}) => { 268 | this.compileModule(request, resource, () => {}, isEntry); 269 | }); 270 | this.fulfilledManifest[resource] = null; 271 | } 272 | } 273 | 274 | this.compileFile(resource, () => { 275 | this.startModule(request); 276 | 277 | const dep = new EntryReferenceTransformDependency( 278 | (isEntry ? '' : '!!') + request 279 | ); 280 | 281 | this.compilations[resource] 282 | ._addModuleChain(dirname(resource), dep, module => { 283 | dep.module = module; 284 | dep.request = module.request; 285 | 286 | this.entries[resource].addData(dep); 287 | if (isEntry) { 288 | this.entries[resource].isEntry = true; 289 | this.entries[resource].entryRequest = module.request; 290 | } 291 | }, (err, module) => { 292 | this.completeModule(request, err); 293 | }); 294 | 295 | callback(null, dep); 296 | }, isEntry); 297 | } 298 | 299 | // compileDependency(dep) { 300 | // 301 | // } 302 | } 303 | 304 | module.exports = SharedData; 305 | -------------------------------------------------------------------------------- /src/test-entries-plugin.js: -------------------------------------------------------------------------------- 1 | const {readFileSync} = require('fs'); 2 | const {resolve, sep} = require('path'); 3 | const mkdirp = require('mkdirp'); 4 | 5 | const SingleEntryDependency = require('webpack/lib/dependencies/SingleEntryDependency'); 6 | const SingleEntryPlugin = require('webpack/lib/SingleEntryPlugin'); 7 | 8 | const NodeTargetPlugin = require('webpack/lib/node/NodeTargetPlugin'); 9 | const NodeTemplatePlugin = require('webpack/lib/node/NodeTemplatePlugin'); 10 | 11 | // Try and require jest's handling for finding source files so we can get the 12 | // test files jest will test once the webpack step is complete. 13 | const tryRequire = require('./try-require'); 14 | const SearchSource = tryRequire( 15 | () => require('jest/node_modules/jest-cli/build/search_source'), 16 | () => require('jest/node_modules/jest-cli/build/SearchSource'), 17 | () => require('jest-cli/build/search_source'), 18 | () => require('jest-cli/build/SearchSource') 19 | ); 20 | const createContext = tryRequire( 21 | () => require('jest/node_modules/jest-cli/build/lib/create_context'), 22 | () => require('jest/node_modules/jest-cli/build/lib/createContext'), 23 | () => require('jest-cli/build/lib/create_context'), 24 | () => require('jest-cli/build/lib/createContext') 25 | ); 26 | const Runtime = tryRequire( 27 | () => require('jest/node_modules/jest-cli/node_modules/jest-runtime'), 28 | () => require('jest-cli/node_modules/jest-runtime'), 29 | () => require('jest-runtime') 30 | ); 31 | // const getTestPathPattern = tryRequire( 32 | // () => require('jest/node_modules/jest-cli/build/lib/getTestPathPattern'), 33 | // () => require('jest-cli/build/lib/getTestPathPattern') 34 | // ); 35 | 36 | const modulePathIgnorePatterns = function(options) { 37 | if (options.jestConfig.config) { 38 | return options.jestConfig.config.modulePathIgnorePatterns; 39 | } 40 | else { 41 | return options.jestConfig.projectConfig.modulePathIgnorePatterns; 42 | } 43 | }; 44 | 45 | const testPathIgnorePatterns = function(options) { 46 | if (options.jestConfig.config) { 47 | return options.jestConfig.config.testPathIgnorePatterns; 48 | } 49 | else { 50 | return options.jestConfig.projectConfig.testPathIgnorePatterns; 51 | } 52 | }; 53 | 54 | const testPathPattern = function(options) { 55 | if (options.jestConfig.config) { 56 | const getTestPathPattern = tryRequire( 57 | () => require('jest/node_modules/jest-cli/build/lib/getTestPathPattern'), 58 | () => require('jest-cli/build/lib/getTestPathPattern') 59 | ); 60 | return getTestPathPattern(options.jestArgv); 61 | } 62 | else { 63 | return options.jestConfig.globalConfig.testPathPattern; 64 | } 65 | }; 66 | 67 | class TestEntriesPlugin { 68 | constructor(options) { 69 | this.options = options; 70 | } 71 | 72 | apply(compiler) { 73 | const options = this.options || {}; 74 | 75 | compiler.plugin("compilation", (compilation, params) => { 76 | const normalModuleFactory = params.normalModuleFactory; 77 | 78 | compilation.dependencyFactories.set(SingleEntryDependency, normalModuleFactory); 79 | }); 80 | 81 | compiler.plugin('make', (compilation, cb) => { 82 | const {jestArgv, jestConfig} = options; 83 | 84 | const ignoreCacheJestWebpack = 85 | '/.cache/jest-webpack/'.replace(/\//g, sep === '\\' ? '\\\\' : sep); 86 | 87 | const configIgnoreJestWebpack = Object.assign( 88 | {}, 89 | jestConfig.projectConfig || jestConfig.config, 90 | { 91 | modulePathIgnorePatterns: 92 | (modulePathIgnorePatterns(options) || []) 93 | .concat([ignoreCacheJestWebpack]), 94 | testPathIgnorePatterns: (testPathIgnorePatterns(options) || []) 95 | .concat([ignoreCacheJestWebpack]), 96 | } 97 | ); 98 | 99 | try { 100 | mkdirp.sync(configIgnoreJestWebpack.cacheDirectory); 101 | } 102 | catch (_) {} 103 | 104 | Runtime.createHasteMap(configIgnoreJestWebpack).build() 105 | .then(hasteMap => createContext(configIgnoreJestWebpack, hasteMap)) 106 | .then(jestContext => { 107 | const searchSource = new SearchSource(jestContext); 108 | return searchSource.findMatchingTests(testPathPattern(options)) 109 | .tests.map(test => test.path); 110 | }) 111 | .then(tests => { 112 | options.data.reset(compilation, () => { 113 | cb(); 114 | }); 115 | 116 | tests.map(function(name) { 117 | const entry = resolve(compiler.options.context, name); 118 | options.data.compileModule(entry, entry, () => {}, true); 119 | }); 120 | 121 | if (options.data.filesRunning === 0) { 122 | cb(); 123 | } 124 | }) 125 | .catch(cb); 126 | }); 127 | } 128 | } 129 | 130 | module.exports = TestEntriesPlugin; 131 | -------------------------------------------------------------------------------- /src/test-entries-plugin.test.js: -------------------------------------------------------------------------------- 1 | require('source-map-support').install({hookRequire: true}); 2 | 3 | const utils = require('../fixtures/utils'); 4 | 5 | it('detects in src', () => { 6 | return utils.run('test-entries-src') 7 | .then(utils.itPasses) 8 | .then(utils.itBuilt(['src/entry.test.js'])) 9 | .then(utils.itTests(['src/entry.test.js'])); 10 | }, 30000); 11 | 12 | it('detects in src - babel', () => { 13 | return utils.run('test-entries-src-babel') 14 | .then(utils.itPasses) 15 | .then(utils.itBuilt(['src/entry.test.js'])) 16 | .then(utils.itTests(['src/entry.test.js'])); 17 | }, 30000); 18 | -------------------------------------------------------------------------------- /src/try-require.js: -------------------------------------------------------------------------------- 1 | module.exports = (...attempts) => { 2 | let err; 3 | for (const fn of attempts) { 4 | try { 5 | const exports = fn(); 6 | return exports.default || exports; 7 | } 8 | catch (_) {err = _;} 9 | } 10 | throw err; 11 | }; 12 | -------------------------------------------------------------------------------- /src/webpack-loaders.test.js: -------------------------------------------------------------------------------- 1 | const {run, itBuilt, didNotBuild, itTests, itSkips, itPasses, itFails} = 2 | require('../fixtures/utils'); 3 | 4 | it('works with style+css loaders', () => { 5 | return run('loader-css') 6 | .then(itBuilt(['src/entry.css', 'src/entry.test.js'])) 7 | .then(itPasses); 8 | }, 30000); 9 | -------------------------------------------------------------------------------- /webpack.config.babel.js: -------------------------------------------------------------------------------- 1 | var {join, resolve} = require('path'); 2 | 3 | // var HardSourceWebpackPlugin = require('hard-source-webpack-plugin'); 4 | var webpack = require('webpack'); 5 | var webpackIf = require('webpack-if'); 6 | 7 | var ProvidePlugin = webpack.ProvidePlugin; 8 | 9 | var root = process.cwd(); 10 | 11 | var startsWith = webpackIf.op((pred, value) => pred.startsWith(String(value))); 12 | 13 | var nodeEnv = () => process.env.NODE_ENV; 14 | var webpackVersion = () => require('webpack/package.json').version; 15 | 16 | var isEnv = webpackIf.is(nodeEnv); 17 | var isWebpackVersion = startsWith(webpackVersion); 18 | 19 | var ifProd = webpackIf.ifElse(isEnv("production")); 20 | var ifWebpack1 = webpackIf.ifElse(isWebpackVersion(1)); 21 | 22 | module.exports = webpackIf({ 23 | // context: Content for entries. 24 | context: root, 25 | entry: { 26 | 'jest-webpack': './src/jest-webpack.js', 27 | }, 28 | output: { 29 | path: join(__dirname, 'webpack-1'), 30 | filename: '[name].js', 31 | libraryTarget: 'commonjs2', 32 | // devtoolModuleFilenameTemplate sets the stored path in the source maps 33 | // sources array. jest will use this to read the original file. 34 | devtoolModuleFilenameTemplate: join(__dirname, '[resource-path]'), 35 | }, 36 | 37 | // devtool: Enable for source maps. Module means lines of the loader output, 38 | // in this case the es5 output from babel. 39 | devtool: 'module-source-map', 40 | 41 | externals: function(context, request, callback) { 42 | if (request.indexOf('!') === -1 && /^\w/.test(request)) { 43 | return callback(null, request, 'commonjs2'); 44 | } 45 | callback(null); 46 | }, 47 | 48 | target: 'node', 49 | 50 | // node: Options for automatic shims for node functionality. 51 | node: { 52 | // __dirname: Set false to disable automatic __dirname shim. 53 | __dirname: false, 54 | 55 | __filename: false, 56 | 57 | process: false, 58 | }, 59 | // module: module configuration. 60 | module: { 61 | // rules: Loaders automatically applied based on test, include, and 62 | // exclude options. 63 | [ifWebpack1('loaders', 'rules')]: [ 64 | // Apply babel-loader to any js file not under node_modules. 65 | { 66 | test: /\.jsx?$/, 67 | exclude: [resolve(root, 'node_modules')], 68 | loader: ifWebpack1( 69 | 'babel-loader?{presets:[["env",{targets:{node:4}}]]}', 70 | 'babel-loader', 71 | ), 72 | // options: Babel-loader configuration. 73 | options: ifWebpack1(null, () => ({ 74 | // babel.presets: Plugin presets babel loader will add on top of those 75 | // specified in babelrc. 76 | 'presets': [['env', {targets: {node: 4}}]], 77 | })), 78 | }, 79 | ], 80 | }, 81 | // plugins: webpack plugins. 82 | plugins: [ 83 | // A webpack cache plugin. A cache is written to the file system and reused 84 | // when possible during later runs for faster builds. 85 | // new HardSourceWebpackPlugin(), 86 | ], 87 | }); 88 | --------------------------------------------------------------------------------