-
Notifications
You must be signed in to change notification settings - Fork 618
/
Copy pathTesting.ts
147 lines (131 loc) · 4.91 KB
/
Testing.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.
// See LICENSE in the project root for license information.
import { createFsFromVolume, Volume, type IFs } from 'memfs';
import path from 'path';
import type { StatsCompilation as WebpackStatsCompilation } from 'webpack';
import webpackMerge from 'webpack-merge';
import type { MultiStats, Stats, Configuration, Compiler, StatsError, OutputFileSystem } from 'webpack';
/**
* @public
* This function generates a webpack compiler with default configuration and the output filesystem mapped to
* a memory filesystem. This is useful for testing webpack plugins/loaders where we do not need to write to disk (which can be costly).
* @param entry - The entry point for the webpack compiler
* @param additionalConfig - Any additional configuration that should be merged with the default configuration
* @param memFs - The memory filesystem to use for the output filesystem. Use this option if you want to _inspect_, analyze, or read the output
* files generated by the webpack compiler. If you do not need to do this, you can omit this parameter and the output files.
*
* @returns - A webpack compiler with the output filesystem mapped to a memory filesystem
*
* @example
* ```typescript
* import Testing from '@rushstack/webpack-plugin-utilities';
*
* describe('MyPlugin', () => {
* it('should run', async () => {
* const stats = await Testing.getTestingWebpackCompiler(
* `./src/index.ts`,
* );
*
* expect(stats).toBeDefined();
* });
* });
* ```
*
* @remarks
* If you want to be able to read, analyze, access the files written to the memory filesystem,
* you can pass in a memory filesystem instance to the `memFs` parameter.
*
* @example
* ```typescript
* import Testing from '@rushstack/webpack-plugin-utilities';
* import { createFsFromVolume, Volume, IFs } from 'memfs';
* import path from 'path';
*
* describe('MyPlugin', () => {
* it('should run', async () => {
* const virtualFileSystem: IFs = createFsFromVolume(new Volume());
* const stats = await Testing.getTestingWebpackCompiler(
* `./src/index.ts`,
* {},
* virtualFileSystem
* );
*
* expect(stats).toBeDefined();
* expect(virtualFileSystem.existsSync(path.join(__dirname, 'dist', 'index.js'))).toBe(true);
* });
* });
* ```
*/
export async function getTestingWebpackCompilerAsync(
entry: string,
additionalConfig: Configuration = {},
memFs: IFs = createFsFromVolume(new Volume())
): Promise<(Stats | MultiStats) | undefined> {
let webpackModule: typeof import('webpack');
try {
webpackModule = (await import('webpack')).default;
} catch (e) {
throw new Error(
'Unable to load module "webpack". The @rushstack/webpack-plugin-utilities package declares "webpack" as ' +
'an optional peer dependency, but a function was invoked on it that requires webpack. Make sure ' +
`the peer dependency on "webpack" is fulfilled. Inner error: ${e}`
);
}
const compilerOptions: Configuration = webpackMerge(_defaultWebpackConfig(entry), additionalConfig);
const compiler: Compiler = webpackModule(compilerOptions);
// The memFs Volume satisfies the interface contract, but the types aren't happy due to strict null checks
const outputFileSystem: OutputFileSystem = memFs as unknown as OutputFileSystem;
outputFileSystem.join = path.join.bind(path);
compiler.outputFileSystem = outputFileSystem;
return new Promise((resolve, reject) => {
compiler.run((err, stats) => {
compiler.close(() => {
if (err) {
return reject(err);
}
_processAndHandleStatsErrorsAndWarnings(stats, reject);
resolve(stats);
});
});
});
}
function _processAndHandleStatsErrorsAndWarnings(
stats: Stats | MultiStats | undefined,
reject: (reason: unknown) => void
): void {
if (stats?.hasErrors() || stats?.hasWarnings()) {
const serializedStats: WebpackStatsCompilation[] = [stats?.toJson('errors-warnings')];
const errors: StatsError[] = [];
const warnings: StatsError[] = [];
for (const compilationStats of serializedStats) {
if (compilationStats.warnings) {
for (const warning of compilationStats.warnings) {
warnings.push(warning);
}
}
if (compilationStats.errors) {
for (const error of compilationStats.errors) {
errors.push(error);
}
}
if (compilationStats.children) {
for (const child of compilationStats.children) {
serializedStats.push(child);
}
}
}
reject([...errors, ...warnings]);
}
}
function _defaultWebpackConfig(entry: string = './src'): Configuration {
return {
// We don't want to have eval source maps, nor minification
// so we set mode to 'none' to disable both. Default is 'production'
mode: 'none',
context: __dirname,
entry,
output: {
filename: 'test-bundle.js'
}
};
}