Skip to content

Commit f228401

Browse files
sudo-suhasnovemberborn
authored andcommitted
Improve webpack recipe (#1385)
Discuss various options and trade-offs in how to use webpack builds as a way to run AVA tests.
1 parent 7c62b35 commit f228401

File tree

1 file changed

+235
-7
lines changed

1 file changed

+235
-7
lines changed

docs/recipes/precompiling-with-webpack.md

+235-7
Original file line numberDiff line numberDiff line change
@@ -2,30 +2,258 @@
22

33
Translations: [Français](https://github.com/avajs/ava-docs/blob/master/fr_FR/docs/recipes/precompiling-with-webpack.md)
44

5-
The AVA [readme](https://github.com/avajs/ava#transpiling-imported-modules) mentions precompiling your imported modules as an alternative to runtime compilation, but it doesn't explain how. This recipe shows how to do this using webpack. (This example uses webpack 2.0)
5+
The AVA [readme](https://github.com/avajs/ava#transpiling-imported-modules) mentions precompiling your imported modules as an alternative to runtime compilation, but it doesn't explain how. This recipe discusses several approaches using webpack v2. Multiple approaches are discussed as each has its own pros and cons. You should select the approach that best fits your use case. See the original discussion [here](https://github.com/avajs/ava/pull/1385).
6+
7+
- [Single test file](#single-test-file)
8+
- [Multiple test files](#multiple-test-files)
9+
10+
### Single test file
11+
This is the simplest use case. You might need this if you are [using aliases](https://github.com/avajs/ava/issues/1011).
612

713
###### webpack.config.js
814

915
```js
16+
const path = require('path');
1017
const nodeExternals = require('webpack-node-externals');
1118

1219
module.exports = {
13-
entry: ['src/tests.js'],
20+
entry: ['test.js'],
1421
target: 'node',
1522
output: {
16-
path: '_build',
17-
filename: 'tests.js'
23+
path: path.resolve(__dirname, '_build'),
24+
filename: 'test.js'
1825
},
1926
externals: [nodeExternals()],
2027
module: {
2128
rules: [{
22-
test: /\.(js|jsx)$/,
23-
use: 'babel-loader'
29+
test: /\.(js|jsx)$/,
30+
use: 'babel-loader',
31+
options: { cacheDirectory: true }
2432
}]
2533
}
2634
};
2735
```
2836

2937
The important bits are `target: 'node'`, which ignores Node.js-specific `require`s (e.g. `fs`, `path`, etc.) and `externals: nodeModules` which prevents webpack from trying to bundle external Node.js modules which it may choke on.
3038

31-
You can now run `$ ava _build/tests.js` to run the tests contained in this output.
39+
You can now run `$ ava _build/test.js` to run the tests contained in this output.
40+
41+
### Multiple test files
42+
Things are a little more complicated with multiple test files. We recommend [using babel-register](babelrc.md) until the performance penalty becomes too great.
43+
44+
The possible approaches are:
45+
46+
- [Refer precompiled source in tests](#refer-precompiled-source-in-tests)
47+
- [Single entry file](#single-entry-file)
48+
- [Multiple entry files](#multiple-entry-files)
49+
- [Test against precompiled sources](#test-against-precompiled-sources)
50+
51+
#### Refer precompiled source in tests
52+
Source files can be compiled to `_src` folder and referenced in tests. While this is less than elegant, it performs well and the workflow can be optimized with [`babel-cli` watch mode](https://babeljs.io/docs/usage/cli/#babel).
53+
54+
```js
55+
// Before
56+
import fresh from '../src';
57+
// After
58+
import fresh from '../_src';
59+
```
60+
61+
#### Single entry file
62+
Multiple test files can be compiled into a single file. This may have the best performance but that does come at a cost. All tests will be in the same file, which can make it harder to know which test has failed, since AVA can't show the file name the test was originally in. You'll also lose [process isolation](https://github.com/avajs/ava#process-isolation).
63+
64+
###### webpack.config.js
65+
66+
[Related stackoverflow answer](http://stackoverflow.com/questions/32874025/how-to-add-wildcard-mapping-in-entry-of-webpack/34545812#34545812)
67+
68+
```js
69+
const path = require('path');
70+
const glob = require('glob');
71+
72+
const nodeExternals = require('webpack-node-externals');
73+
74+
module.exports = {
75+
target: 'node',
76+
entry: glob.sync('./test/**/*.js'),
77+
output: {
78+
path: path.resolve(__dirname, '_build'),
79+
filename: 'tests.js'
80+
},
81+
externals: [nodeExternals()],
82+
module: {
83+
rules: [{
84+
test: /\.(js|jsx)$/,
85+
use: {
86+
loader: 'babel-loader',
87+
options: { cacheDirectory: true }
88+
}
89+
}]
90+
}
91+
};
92+
```
93+
94+
<details>
95+
<summary>Error report comparison</summary>
96+
97+
```
98+
# Before
99+
aggregations-test » cardinality-agg » sets precision_threshold option
100+
E:\Projects\repos\elastic-builder\test\_macros.js:167
101+
102+
166: const expected = getExpected(keyName, recursiveToJSON(propValue));
103+
167: t.deepEqual(value, expected);
104+
168: }
105+
106+
Difference:
107+
108+
Object {
109+
my_agg: Object {
110+
cardinality: Object {
111+
- precision_threshol: 5000,
112+
+ precision_threshold: 5000,
113+
},
114+
},
115+
}
116+
117+
# After
118+
sets precision_threshold option
119+
E:\Projects\repos\elastic-builder\_build\tests.js:106
120+
121+
105: column: 21
122+
106: }
123+
107: },
124+
125+
Difference:
126+
127+
Object {
128+
my_agg: Object {
129+
cardinality: Object {
130+
- precision_threshol: 5000,
131+
+ precision_threshold: 5000,
132+
},
133+
},
134+
}
135+
136+
```
137+
138+
</details>
139+
140+
#### Multiple entry files
141+
We can ask webpack to generate multiple entry files. This helps retain file names so that error reports are easy to interpret. But each entry file gets it's own copy of the source files. This results in considerably larger file sizes. This can [perform quite poorly](https://github.com/avajs/ava/pull/1385#issuecomment-304684047) on the first execution.
142+
143+
###### webpack.config.js
144+
145+
```js
146+
const path = require('path');
147+
const glob = require('glob');
148+
149+
const nodeExternals = require('webpack-node-externals');
150+
151+
const entryObj = glob.sync('./test/**/*.js')
152+
.reduce((acc, file) => {
153+
acc[path.basename(file, path.extname(file))] = file;
154+
return acc;
155+
}, {}); // empty object as initial value
156+
157+
module.exports = {
158+
target: 'node',
159+
entry: entryObj,
160+
output: {
161+
path: path.resolve(__dirname, '_build'),
162+
filename: '[name].js'
163+
},
164+
externals: [nodeExternals()],
165+
module: {
166+
rules: [{
167+
test: /\.(js|jsx)$/,
168+
use: {
169+
loader: 'babel-loader',
170+
options: { cacheDirectory: true }
171+
}
172+
}]
173+
}
174+
};
175+
```
176+
177+
#### Test against precompiled sources
178+
This is the most complicated to setup but performs quite well and also retains file names. In this approach, we use the `babel-cli` to compile the source files but preserve file structure. Require paths in tests are rewritten when compiling them in webpack. The following example is for a specific file structure. Depending on how your source and test files are organised, you might need to make changes.
179+
180+
File structure:
181+
```
182+
├───src
183+
│ ├───my-pkg-fldr
184+
│ │ ├───my-module.js
185+
│ │ └───index.js
186+
│ └───index.js
187+
└───test
188+
├───my-pkg-fldr
189+
│ └───my-module.test.js
190+
└───index.test.js
191+
192+
# Generated file structure
193+
├───_src
194+
│ ├───my-pkg-fldr
195+
│ │ ├───my-module.js
196+
│ │ └───index.js
197+
│ └───index.js
198+
└───_build
199+
├───my-module.test.js
200+
└───index.test.js
201+
```
202+
203+
npm scripts:
204+
```js
205+
{
206+
"scripts": {
207+
"precompile-src": "cross-env NODE_ENV=test babel src --out-dir _src",
208+
"precompile-tests": "cross-env NODE_ENV=test webpack --config webpack.config.test.js",
209+
"pretest": "npm run precompile-src && npm run precompile-tests",
210+
"test": "cross-env NODE_ENV=test nyc --cache ava _build --concurrency 3"
211+
}
212+
}
213+
```
214+
215+
###### webpack.config.js
216+
217+
Webpack Externals Docs - https://webpack.js.org/configuration/externals/#function
218+
219+
```js
220+
const path = require('path');
221+
const glob = require('glob');
222+
223+
const nodeExternals = require('webpack-node-externals');
224+
225+
const entryObj = glob.sync('./test/**/*.js')
226+
.reduce((acc, file) => {
227+
acc[path.basename(file, path.extname(file))] = file;
228+
return acc;
229+
}, {}); // empty object as initial value
230+
231+
232+
module.exports = {
233+
target: 'node',
234+
entry: entryObj,
235+
output: {
236+
path: path.resolve(__dirname, '_build'),
237+
filename: '[name].js'
238+
},
239+
externals: [
240+
nodeExternals(),
241+
// Rewrite the require paths to use _src
242+
(context, request, callback) => {
243+
// This is a little messy because tests are not output in original file structure
244+
// test/index.test.js -> _build/index.test.js
245+
// => ../src -> ../_src
246+
// test/my-pkg-fldr/my-module.test.js -> _build/my-module.test.js
247+
// => ../../src -> ../_src
248+
if (request.includes('/src')) {
249+
const requestReqwrite = request
250+
.replace('/src', '/_src')
251+
.replace('../../_src', '../_src');
252+
return callback(null, `commonjs ${requestReqwrite}`);
253+
}
254+
callback();
255+
}
256+
]
257+
};
258+
```
259+

0 commit comments

Comments
 (0)