@roots/bud-compiler 6.21.0 → 6.23.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -5
- package/lib/service/formatError.d.ts +3 -0
- package/lib/service/formatError.js +54 -0
- package/lib/service/index.d.ts +2 -8
- package/lib/service/index.js +56 -169
- package/lib/service/makeNoticeTitle.d.ts +3 -0
- package/lib/service/makeNoticeTitle.js +1 -0
- package/package.json +6 -8
- package/src/service/formatError.ts +78 -0
- package/src/service/index.ts +167 -0
- package/src/service/makeNoticeTitle.ts +5 -0
- package/src/service/index.tsx +0 -300
package/README.md
CHANGED
@@ -69,9 +69,6 @@ However, the amount of effort needed to maintain and develop new features and pr
|
|
69
69
|
<a href="https://worksitesafety.ca/careers/">
|
70
70
|
<img src="https://cdn.roots.io/app/uploads/worksite-safety.svg" alt="Worksite Safety" width="200" height="150"/>
|
71
71
|
</a>
|
72
|
-
<a href="https://www.
|
73
|
-
<img src="https://cdn.roots.io/app/uploads/
|
74
|
-
</a>
|
75
|
-
<a href="https://www.freave.com/">
|
76
|
-
<img src="https://cdn.roots.io/app/uploads/freave.svg" alt="Freave" width="200" height="150"/>
|
72
|
+
<a href="https://www.itineris.co.uk/">
|
73
|
+
<img src="https://cdn.roots.io/app/uploads/itineris.svg" alt="Itineris" width="200" height="150"/>
|
77
74
|
</a>
|
@@ -0,0 +1,3 @@
|
|
1
|
+
import type { StatsCompilation, StatsError } from '@roots/bud-framework/config';
|
2
|
+
import type { ErrorWithSourceFile } from '@roots/bud-support/open';
|
3
|
+
export declare const makeErrorFormatter: (stats: StatsCompilation) => (error: StatsError) => ErrorWithSourceFile | StatsError;
|
@@ -0,0 +1,54 @@
|
|
1
|
+
import isNull from '@roots/bud-support/isNull';
|
2
|
+
import isString from '@roots/bud-support/isString';
|
3
|
+
import logger from '@roots/bud-support/logger';
|
4
|
+
export const makeErrorFormatter = (stats) => (error) => {
|
5
|
+
try {
|
6
|
+
let file;
|
7
|
+
let module;
|
8
|
+
const ident = error.moduleId ?? error.moduleName;
|
9
|
+
/**
|
10
|
+
* In a perfect world webpack plugins would use the
|
11
|
+
* {@link StatsError.nameForCondition} property to identify the module.
|
12
|
+
*/
|
13
|
+
if (ident && stats?.children) {
|
14
|
+
module = stats.children
|
15
|
+
.flatMap(child => child?.modules)
|
16
|
+
.find(module => [module?.id, module?.name].includes(ident));
|
17
|
+
}
|
18
|
+
/**
|
19
|
+
* If the module is not found, we try to parse the error message
|
20
|
+
*/
|
21
|
+
if (!ident && error.message?.includes(`[stylelint]`)) {
|
22
|
+
// try to get the origin of the stylelint error,
|
23
|
+
// which is contained in the second line of the error message
|
24
|
+
const unparsedOrigin = error.message?.split(`\n`)?.[1];
|
25
|
+
// if the origin is not a string or too long, we return the error as-is
|
26
|
+
if (!isString(unparsedOrigin) || unparsedOrigin.length > 100)
|
27
|
+
return error;
|
28
|
+
// extract absolute path and context relative name of module
|
29
|
+
const styleError = unparsedOrigin.match(/file:\/\/(.*)\x07(.*)\x1B]8;;/);
|
30
|
+
if (isNull(styleError))
|
31
|
+
return error;
|
32
|
+
// get parts of matched error
|
33
|
+
const [, file, name] = styleError;
|
34
|
+
// return enriched error
|
35
|
+
return { ...error, file, name, nameForCondition: file };
|
36
|
+
}
|
37
|
+
/**
|
38
|
+
* If the module is still not found, we return the error as-is
|
39
|
+
*/
|
40
|
+
if (!module)
|
41
|
+
return error;
|
42
|
+
/**
|
43
|
+
* We'll prefer the `nameForCondition` property if it exists,
|
44
|
+
* otherwise we'll use the `name` property.
|
45
|
+
*/
|
46
|
+
file = module.nameForCondition ?? module.name;
|
47
|
+
const name = module.name ?? error.name ?? `error`;
|
48
|
+
return { ...error, file, name };
|
49
|
+
}
|
50
|
+
catch (formatError) {
|
51
|
+
logger.warn(`Problem parsing errors. This probably won't break anything but please report it: https://github.com/roots/bud/issues/new`, formatError);
|
52
|
+
return error;
|
53
|
+
}
|
54
|
+
};
|
package/lib/service/index.d.ts
CHANGED
@@ -1,7 +1,6 @@
|
|
1
1
|
import type { Bud } from '@roots/bud-framework';
|
2
2
|
import type { Compiler as BudCompiler } from '@roots/bud-framework';
|
3
|
-
import type { MultiCompiler, MultiStats,
|
4
|
-
import type { ErrorWithSourceFile } from '@roots/bud-support/open';
|
3
|
+
import type { MultiCompiler, MultiStats, Webpack } from '@roots/bud-framework/config';
|
5
4
|
import { Service } from '@roots/bud-framework/service';
|
6
5
|
/**
|
7
6
|
* {@link BudCompiler} implementation
|
@@ -23,14 +22,10 @@ declare class Compiler extends Service implements BudCompiler {
|
|
23
22
|
* {@link BudCompiler.stats}
|
24
23
|
*/
|
25
24
|
stats: BudCompiler[`stats`];
|
26
|
-
/**
|
27
|
-
* {@link BudCompiler.onError}
|
28
|
-
*/
|
29
|
-
onError(error: Error | undefined): void;
|
30
25
|
/**
|
31
26
|
* {@link BudCompiler.onStats}
|
32
27
|
*/
|
33
|
-
onStats(stats:
|
28
|
+
onStats(stats: MultiStats): void;
|
34
29
|
/**
|
35
30
|
* {@link BudCompiler.compile}
|
36
31
|
*/
|
@@ -39,6 +34,5 @@ declare class Compiler extends Service implements BudCompiler {
|
|
39
34
|
* {@link Service.register}
|
40
35
|
*/
|
41
36
|
register?(bud: Bud): Promise<any>;
|
42
|
-
sourceErrors?(errors: Array<StatsError> | undefined): Array<ErrorWithSourceFile | StatsError>;
|
43
37
|
}
|
44
38
|
export { Compiler as default };
|
package/lib/service/index.js
CHANGED
@@ -1,16 +1,14 @@
|
|
1
1
|
import { __decorate } from "tslib";
|
2
|
-
import { jsx as _jsx } from "@roots/bud-support/jsx-runtime";
|
3
2
|
import { cpus } from 'node:os';
|
4
3
|
import process from 'node:process';
|
5
4
|
import { pathToFileURL } from 'node:url';
|
6
|
-
import { Display as DisplayError } from '@roots/bud-dashboard/components/error';
|
7
5
|
import { Service } from '@roots/bud-framework/service';
|
8
6
|
import { bind } from '@roots/bud-support/decorators/bind';
|
9
|
-
import { BudError
|
10
|
-
import isNull from '@roots/bud-support/isNull';
|
7
|
+
import { BudError } from '@roots/bud-support/errors';
|
11
8
|
import isNumber from '@roots/bud-support/isNumber';
|
12
|
-
import isString from '@roots/bud-support/isString';
|
13
9
|
import stripAnsi from '@roots/bud-support/strip-ansi';
|
10
|
+
import { makeErrorFormatter } from './formatError.js';
|
11
|
+
import { makeNoticeTitle } from './makeNoticeTitle.js';
|
14
12
|
/**
|
15
13
|
* {@link BudCompiler} implementation
|
16
14
|
*/
|
@@ -23,83 +21,70 @@ class Compiler extends Service {
|
|
23
21
|
* {@link BudCompiler.implementation}
|
24
22
|
*/
|
25
23
|
implementation;
|
26
|
-
/**
|
27
|
-
* {@link BudCompiler.onError}
|
28
|
-
*/
|
29
|
-
onError(error) {
|
30
|
-
process.exitCode = 1;
|
31
|
-
if (!error)
|
32
|
-
return;
|
33
|
-
this.app.server?.appliedMiddleware?.hot?.publish({ error });
|
34
|
-
const normalized = CompilerError.normalize(error, {
|
35
|
-
thrownBy: import.meta.url,
|
36
|
-
});
|
37
|
-
normalized.details = undefined;
|
38
|
-
this.app.notifier?.notify({
|
39
|
-
group: this.app.label,
|
40
|
-
message: normalized.message,
|
41
|
-
subtitle: normalized.name,
|
42
|
-
});
|
43
|
-
this.app.context.render(_jsx(DisplayError, { error: error }));
|
44
|
-
}
|
45
24
|
/**
|
46
25
|
* {@link BudCompiler.onStats}
|
47
26
|
*/
|
48
27
|
onStats(stats) {
|
49
|
-
|
50
|
-
|
51
|
-
:
|
52
|
-
|
53
|
-
|
28
|
+
this.stats = stats.toJson({
|
29
|
+
all: false,
|
30
|
+
children: {
|
31
|
+
all: false,
|
32
|
+
assets: true,
|
33
|
+
cached: true,
|
34
|
+
cachedAssets: true,
|
35
|
+
cachedModules: true,
|
36
|
+
entrypoints: true,
|
37
|
+
errorDetails: false,
|
38
|
+
errors: true,
|
39
|
+
errorsCount: true,
|
40
|
+
hash: true,
|
41
|
+
modules: true,
|
42
|
+
name: true,
|
43
|
+
outputPath: true,
|
44
|
+
timings: true,
|
45
|
+
warnings: true,
|
46
|
+
warningsCount: true,
|
47
|
+
},
|
48
|
+
});
|
49
|
+
this.app.dashboard.render(stats);
|
54
50
|
if (stats.hasErrors()) {
|
55
51
|
process.exitCode = 1;
|
52
|
+
const format = makeErrorFormatter(this.stats);
|
56
53
|
this.stats.children = this.stats.children?.map(child => ({
|
57
54
|
...child,
|
58
|
-
errors: child.errors
|
59
|
-
?
|
60
|
-
: child.errors ?? [],
|
55
|
+
errors: (child.errors
|
56
|
+
? child.errors?.map(format).filter(Boolean)
|
57
|
+
: child.errors) ?? [],
|
61
58
|
}));
|
62
59
|
this.stats.children
|
63
60
|
?.filter(child => isNumber(child.errorsCount) && child.errorsCount > 0)
|
64
61
|
.forEach(child => {
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
return;
|
69
|
-
this.app.notifier.notify({
|
70
|
-
group: `${this.app.label}-${child.name}`,
|
71
|
-
message: stripAnsi(error.message),
|
72
|
-
open: error.file ? pathToFileURL(error.file) : ``,
|
73
|
-
subtitle: error.file ? `Error in ${error.name}` : error.name,
|
74
|
-
title: makeNoticeTitle(child),
|
75
|
-
});
|
76
|
-
error.file && this.app.notifier.openEditor(error.file);
|
77
|
-
}
|
78
|
-
catch (error) {
|
79
|
-
this.logger.error(error);
|
80
|
-
}
|
81
|
-
});
|
82
|
-
}
|
83
|
-
this.stats.children
|
84
|
-
?.filter(child => child.errorsCount === 0)
|
85
|
-
.forEach(child => {
|
86
|
-
try {
|
62
|
+
const error = child.errors?.shift();
|
63
|
+
if (!error)
|
64
|
+
return;
|
87
65
|
this.app.notifier.notify({
|
88
66
|
group: `${this.app.label}-${child.name}`,
|
89
|
-
message:
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
subtitle: `Build successful`,
|
94
|
-
title: makeNoticeTitle(child),
|
67
|
+
message: stripAnsi(error.message),
|
68
|
+
open: error.file ? pathToFileURL(error.file) : ``,
|
69
|
+
subtitle: error.file ? `Error in ${error.name}` : error.name,
|
70
|
+
title: makeNoticeTitle(this.app, child),
|
95
71
|
});
|
96
|
-
this.app.
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
this.
|
102
|
-
|
72
|
+
error.file && this.app.notifier.openEditor(error.file);
|
73
|
+
});
|
74
|
+
}
|
75
|
+
this.stats.children?.forEach(child => {
|
76
|
+
this.app.notifier.notify({
|
77
|
+
group: `${this.app.label}-${child.name}`,
|
78
|
+
message: child.modules
|
79
|
+
? `${child.modules.length} modules compiled`
|
80
|
+
: `Modules compiled successfully`,
|
81
|
+
open: this.app.server?.publicUrl.href,
|
82
|
+
subtitle: `Build successful`,
|
83
|
+
title: makeNoticeTitle(this.app, child),
|
84
|
+
});
|
85
|
+
this.app.server?.publicUrl.href &&
|
86
|
+
this.app.context.browser &&
|
87
|
+
this.app.notifier.openBrowser(this.app.server?.publicUrl.href);
|
103
88
|
});
|
104
89
|
}
|
105
90
|
/**
|
@@ -108,25 +93,16 @@ class Compiler extends Service {
|
|
108
93
|
async compile(bud) {
|
109
94
|
const config = !bud.hasChildren
|
110
95
|
? [await bud.build.make()]
|
111
|
-
: await Promise.all(Object.values(bud.children).map(async (child) => await child.build.make()
|
112
|
-
throw error;
|
113
|
-
})));
|
96
|
+
: await Promise.all(Object.values(bud.children).map(async (child) => await child.build.make()));
|
114
97
|
this.config = config?.filter(Boolean);
|
115
98
|
if (this.config.length > 1) {
|
116
99
|
this.config.parallelism = Math.max(cpus().length - 1, 1);
|
117
100
|
this.logger.info(`parallel compilations: ${this.config.parallelism}`);
|
118
101
|
}
|
119
|
-
await bud.hooks.fire(`compiler.before`, bud)
|
120
|
-
throw error;
|
121
|
-
});
|
122
|
-
this.logger.timeEnd(`initialize`);
|
102
|
+
await bud.hooks.fire(`compiler.before`, bud);
|
123
103
|
this.instance = this.implementation(this.config);
|
124
|
-
this.instance.hooks.done.tap(bud.label,
|
125
|
-
|
126
|
-
bud.hooks
|
127
|
-
.fire(`compiler.done`, bud, this.stats)
|
128
|
-
.catch(this.app.catch);
|
129
|
-
});
|
104
|
+
this.instance.hooks.done.tap(bud.label, this.onStats);
|
105
|
+
this.instance.hooks.done.tap(`${bud.label}-compiler.done`, stats => bud.hooks.fire(`compiler.done`, bud, stats));
|
130
106
|
return this.instance;
|
131
107
|
}
|
132
108
|
/**
|
@@ -139,72 +115,7 @@ class Compiler extends Service {
|
|
139
115
|
throw BudError.normalize(error);
|
140
116
|
});
|
141
117
|
}
|
142
|
-
sourceErrors(errors) {
|
143
|
-
if (!errors || !errors.length)
|
144
|
-
return [];
|
145
|
-
try {
|
146
|
-
return errors
|
147
|
-
?.map((error) => {
|
148
|
-
let file;
|
149
|
-
let module;
|
150
|
-
const ident = error.moduleId ?? error.moduleName;
|
151
|
-
/**
|
152
|
-
* In a perfect world webpack plugins would use the
|
153
|
-
* `nameForCondition` property to identify the module.
|
154
|
-
*/
|
155
|
-
if (ident && this.stats?.children) {
|
156
|
-
module = this.stats.children
|
157
|
-
.flatMap(child => child?.modules)
|
158
|
-
.find(module => [module?.id, module?.name].includes(ident));
|
159
|
-
}
|
160
|
-
/**
|
161
|
-
* If the module is not found, we try to parse the error message
|
162
|
-
*/
|
163
|
-
if (!ident && error.message?.includes(`[stylelint]`)) {
|
164
|
-
// try to get the origin of the stylelint error,
|
165
|
-
// which is contained in the second line of the error message
|
166
|
-
const unparsedOrigin = error.message?.split(`\n`)?.[1];
|
167
|
-
// if the origin is not a string or too long, we return the error as-is
|
168
|
-
if (!isString(unparsedOrigin) || unparsedOrigin.length > 100)
|
169
|
-
return error;
|
170
|
-
// extract absolute path and context relative name of module
|
171
|
-
const styleError = unparsedOrigin.match(/file:\/\/(.*)\x07(.*)\x1B]8;;/);
|
172
|
-
if (isNull(styleError))
|
173
|
-
return error;
|
174
|
-
// get parts of matched error
|
175
|
-
const [, file, name] = styleError;
|
176
|
-
// return enriched error
|
177
|
-
return { ...error, file, name, nameForCondition: file };
|
178
|
-
}
|
179
|
-
/**
|
180
|
-
* If the module is still not found, we return the error as-is
|
181
|
-
*/
|
182
|
-
if (!module)
|
183
|
-
return error;
|
184
|
-
/**
|
185
|
-
* We'll prefer the `nameForCondition` property if it exists,
|
186
|
-
* otherwise we'll use the `name` property.
|
187
|
-
*/
|
188
|
-
if (module.nameForCondition) {
|
189
|
-
file = module.nameForCondition;
|
190
|
-
}
|
191
|
-
else if (module.name) {
|
192
|
-
file = this.app.path(`@src`, module.name);
|
193
|
-
}
|
194
|
-
const name = module.name ?? error.name ?? `error`;
|
195
|
-
return { ...error, file, name };
|
196
|
-
})
|
197
|
-
.filter(Boolean);
|
198
|
-
}
|
199
|
-
catch (error) {
|
200
|
-
this.logger.warn(`Problem parsing errors. This probably won't break anything but please report it: https://github.com/roots/bud/issues/new`, error);
|
201
|
-
return errors;
|
202
|
-
}
|
203
|
-
}
|
204
118
|
}
|
205
|
-
__decorate([
|
206
|
-
bind
|
207
|
-
], Compiler.prototype, "onError", null);
|
208
119
|
__decorate([
|
209
120
|
bind
|
210
121
|
], Compiler.prototype, "onStats", null);
|
@@ -214,28 +125,4 @@ __decorate([
|
|
214
125
|
__decorate([
|
215
126
|
bind
|
216
127
|
], Compiler.prototype, "register", null);
|
217
|
-
__decorate([
|
218
|
-
bind
|
219
|
-
], Compiler.prototype, "sourceErrors", null);
|
220
|
-
const statsOptions = {
|
221
|
-
all: false,
|
222
|
-
children: {
|
223
|
-
all: false,
|
224
|
-
assets: true,
|
225
|
-
cached: true,
|
226
|
-
cachedAssets: true,
|
227
|
-
cachedModules: true,
|
228
|
-
entrypoints: true,
|
229
|
-
errorDetails: false,
|
230
|
-
errors: true,
|
231
|
-
errorsCount: true,
|
232
|
-
hash: true,
|
233
|
-
modules: true,
|
234
|
-
name: true,
|
235
|
-
outputPath: true,
|
236
|
-
timings: true,
|
237
|
-
warnings: true,
|
238
|
-
warningsCount: true,
|
239
|
-
},
|
240
|
-
};
|
241
128
|
export { Compiler as default };
|
@@ -0,0 +1 @@
|
|
1
|
+
export const makeNoticeTitle = (bud, child) => bud.label !== child.name ? `${bud.label} (${child.name})` : child.name;
|
package/package.json
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
{
|
2
2
|
"name": "@roots/bud-compiler",
|
3
|
-
"version": "6.
|
3
|
+
"version": "6.23.0",
|
4
4
|
"description": "Compilation handler",
|
5
5
|
"engines": {
|
6
6
|
"node": ">=16"
|
@@ -65,16 +65,14 @@
|
|
65
65
|
"types": "./lib/index.d.ts",
|
66
66
|
"module": "./lib/index.js",
|
67
67
|
"devDependencies": {
|
68
|
-
"@roots/bud-api": "6.
|
68
|
+
"@roots/bud-api": "6.23.0",
|
69
69
|
"@skypack/package-check": "0.2.2",
|
70
|
-
"@types/node": "20.
|
71
|
-
"@types/react": "18.2.45"
|
70
|
+
"@types/node": "20.12.8"
|
72
71
|
},
|
73
72
|
"dependencies": {
|
74
|
-
"@roots/bud-dashboard": "6.
|
75
|
-
"@roots/bud-framework": "6.
|
76
|
-
"@roots/bud-support": "6.
|
77
|
-
"react": "18.2.0",
|
73
|
+
"@roots/bud-dashboard": "6.23.0",
|
74
|
+
"@roots/bud-framework": "6.23.0",
|
75
|
+
"@roots/bud-support": "6.23.0",
|
78
76
|
"tslib": "2.6.2"
|
79
77
|
},
|
80
78
|
"volta": {
|
@@ -0,0 +1,78 @@
|
|
1
|
+
import type {
|
2
|
+
StatsCompilation,
|
3
|
+
StatsError,
|
4
|
+
StatsModule,
|
5
|
+
} from '@roots/bud-framework/config'
|
6
|
+
import type {
|
7
|
+
ErrorWithSourceFile,
|
8
|
+
SourceFile,
|
9
|
+
} from '@roots/bud-support/open'
|
10
|
+
|
11
|
+
import isNull from '@roots/bud-support/isNull'
|
12
|
+
import isString from '@roots/bud-support/isString'
|
13
|
+
import logger from '@roots/bud-support/logger'
|
14
|
+
|
15
|
+
export const makeErrorFormatter =
|
16
|
+
(stats: StatsCompilation) =>
|
17
|
+
(error: StatsError): ErrorWithSourceFile | StatsError => {
|
18
|
+
try {
|
19
|
+
let file: SourceFile[`file`] | undefined
|
20
|
+
let module: StatsModule | undefined
|
21
|
+
|
22
|
+
const ident = error.moduleId ?? error.moduleName
|
23
|
+
|
24
|
+
/**
|
25
|
+
* In a perfect world webpack plugins would use the
|
26
|
+
* {@link StatsError.nameForCondition} property to identify the module.
|
27
|
+
*/
|
28
|
+
if (ident && stats?.children) {
|
29
|
+
module = stats.children
|
30
|
+
.flatMap(child => child?.modules)
|
31
|
+
.find(module => [module?.id, module?.name].includes(ident))
|
32
|
+
}
|
33
|
+
|
34
|
+
/**
|
35
|
+
* If the module is not found, we try to parse the error message
|
36
|
+
*/
|
37
|
+
if (!ident && error.message?.includes(`[stylelint]`)) {
|
38
|
+
// try to get the origin of the stylelint error,
|
39
|
+
// which is contained in the second line of the error message
|
40
|
+
const unparsedOrigin = error.message?.split(`\n`)?.[1]
|
41
|
+
|
42
|
+
// if the origin is not a string or too long, we return the error as-is
|
43
|
+
if (!isString(unparsedOrigin) || unparsedOrigin.length > 100)
|
44
|
+
return error
|
45
|
+
|
46
|
+
// extract absolute path and context relative name of module
|
47
|
+
const styleError = unparsedOrigin.match(
|
48
|
+
/file:\/\/(.*)\x07(.*)\x1B]8;;/,
|
49
|
+
)
|
50
|
+
if (isNull(styleError)) return error
|
51
|
+
|
52
|
+
// get parts of matched error
|
53
|
+
const [, file, name] = styleError
|
54
|
+
// return enriched error
|
55
|
+
return {...error, file, name, nameForCondition: file}
|
56
|
+
}
|
57
|
+
|
58
|
+
/**
|
59
|
+
* If the module is still not found, we return the error as-is
|
60
|
+
*/
|
61
|
+
if (!module) return error
|
62
|
+
|
63
|
+
/**
|
64
|
+
* We'll prefer the `nameForCondition` property if it exists,
|
65
|
+
* otherwise we'll use the `name` property.
|
66
|
+
*/
|
67
|
+
file = module.nameForCondition ?? module.name
|
68
|
+
|
69
|
+
const name = module.name ?? error.name ?? `error`
|
70
|
+
return {...error, file, name}
|
71
|
+
} catch (formatError) {
|
72
|
+
logger.warn(
|
73
|
+
`Problem parsing errors. This probably won't break anything but please report it: https://github.com/roots/bud/issues/new`,
|
74
|
+
formatError,
|
75
|
+
)
|
76
|
+
return error
|
77
|
+
}
|
78
|
+
}
|
@@ -0,0 +1,167 @@
|
|
1
|
+
import type {Bud} from '@roots/bud-framework'
|
2
|
+
import type {Compiler as BudCompiler} from '@roots/bud-framework'
|
3
|
+
import type {
|
4
|
+
MultiCompiler,
|
5
|
+
MultiStats,
|
6
|
+
Webpack,
|
7
|
+
} from '@roots/bud-framework/config'
|
8
|
+
|
9
|
+
import {cpus} from 'node:os'
|
10
|
+
import process from 'node:process'
|
11
|
+
import {pathToFileURL} from 'node:url'
|
12
|
+
|
13
|
+
import {Service} from '@roots/bud-framework/service'
|
14
|
+
import {bind} from '@roots/bud-support/decorators/bind'
|
15
|
+
import {BudError} from '@roots/bud-support/errors'
|
16
|
+
import isNumber from '@roots/bud-support/isNumber'
|
17
|
+
import stripAnsi from '@roots/bud-support/strip-ansi'
|
18
|
+
|
19
|
+
import {makeErrorFormatter} from './formatError.js'
|
20
|
+
import {makeNoticeTitle} from './makeNoticeTitle.js'
|
21
|
+
|
22
|
+
/**
|
23
|
+
* {@link BudCompiler} implementation
|
24
|
+
*/
|
25
|
+
class Compiler extends Service implements BudCompiler {
|
26
|
+
/**
|
27
|
+
* {@link BudCompiler.config}
|
28
|
+
*/
|
29
|
+
public config: BudCompiler[`config`] = []
|
30
|
+
|
31
|
+
/**
|
32
|
+
* {@link BudCompiler.implementation}
|
33
|
+
*/
|
34
|
+
public implementation: BudCompiler[`implementation`] & typeof Webpack
|
35
|
+
|
36
|
+
/**
|
37
|
+
* {@link BudCompiler.instance}
|
38
|
+
*/
|
39
|
+
public declare instance: BudCompiler[`instance`]
|
40
|
+
|
41
|
+
/**
|
42
|
+
* {@link BudCompiler.stats}
|
43
|
+
*/
|
44
|
+
public declare stats: BudCompiler[`stats`]
|
45
|
+
|
46
|
+
/**
|
47
|
+
* {@link BudCompiler.onStats}
|
48
|
+
*/
|
49
|
+
@bind
|
50
|
+
public onStats(stats: MultiStats) {
|
51
|
+
this.stats = stats.toJson({
|
52
|
+
all: false,
|
53
|
+
children: {
|
54
|
+
all: false,
|
55
|
+
assets: true,
|
56
|
+
cached: true,
|
57
|
+
cachedAssets: true,
|
58
|
+
cachedModules: true,
|
59
|
+
entrypoints: true,
|
60
|
+
errorDetails: false,
|
61
|
+
errors: true,
|
62
|
+
errorsCount: true,
|
63
|
+
hash: true,
|
64
|
+
modules: true,
|
65
|
+
name: true,
|
66
|
+
outputPath: true,
|
67
|
+
timings: true,
|
68
|
+
warnings: true,
|
69
|
+
warningsCount: true,
|
70
|
+
},
|
71
|
+
})
|
72
|
+
this.app.dashboard.render(stats)
|
73
|
+
|
74
|
+
if (stats.hasErrors()) {
|
75
|
+
process.exitCode = 1
|
76
|
+
|
77
|
+
const format = makeErrorFormatter(this.stats)
|
78
|
+
this.stats.children = this.stats.children?.map(child => ({
|
79
|
+
...child,
|
80
|
+
errors:
|
81
|
+
(child.errors
|
82
|
+
? child.errors?.map(format).filter(Boolean)
|
83
|
+
: child.errors) ?? [],
|
84
|
+
}))
|
85
|
+
|
86
|
+
this.stats.children
|
87
|
+
?.filter(
|
88
|
+
child => isNumber(child.errorsCount) && child.errorsCount > 0,
|
89
|
+
)
|
90
|
+
.forEach(child => {
|
91
|
+
const error = child.errors?.shift()
|
92
|
+
if (!error) return
|
93
|
+
|
94
|
+
this.app.notifier.notify({
|
95
|
+
group: `${this.app.label}-${child.name}`,
|
96
|
+
message: stripAnsi(error.message),
|
97
|
+
open: error.file ? pathToFileURL(error.file) : ``,
|
98
|
+
subtitle: error.file ? `Error in ${error.name}` : error.name,
|
99
|
+
title: makeNoticeTitle(this.app, child),
|
100
|
+
})
|
101
|
+
|
102
|
+
error.file && this.app.notifier.openEditor(error.file)
|
103
|
+
})
|
104
|
+
}
|
105
|
+
|
106
|
+
this.stats.children?.forEach(child => {
|
107
|
+
this.app.notifier.notify({
|
108
|
+
group: `${this.app.label}-${child.name}`,
|
109
|
+
message: child.modules
|
110
|
+
? `${child.modules.length} modules compiled`
|
111
|
+
: `Modules compiled successfully`,
|
112
|
+
open: this.app.server?.publicUrl.href,
|
113
|
+
subtitle: `Build successful`,
|
114
|
+
title: makeNoticeTitle(this.app, child),
|
115
|
+
})
|
116
|
+
|
117
|
+
this.app.server?.publicUrl.href &&
|
118
|
+
this.app.context.browser &&
|
119
|
+
this.app.notifier.openBrowser(this.app.server?.publicUrl.href)
|
120
|
+
})
|
121
|
+
}
|
122
|
+
|
123
|
+
/**
|
124
|
+
* {@link BudCompiler.compile}
|
125
|
+
*/
|
126
|
+
@bind
|
127
|
+
public async compile(bud: Bud): Promise<MultiCompiler> {
|
128
|
+
const config = !bud.hasChildren
|
129
|
+
? [await bud.build.make()]
|
130
|
+
: await Promise.all(
|
131
|
+
Object.values(bud.children).map(
|
132
|
+
async (child: Bud) => await child.build.make(),
|
133
|
+
),
|
134
|
+
)
|
135
|
+
|
136
|
+
this.config = config?.filter(Boolean)
|
137
|
+
|
138
|
+
if (this.config.length > 1) {
|
139
|
+
this.config.parallelism = Math.max(cpus().length - 1, 1)
|
140
|
+
this.logger.info(`parallel compilations: ${this.config.parallelism}`)
|
141
|
+
}
|
142
|
+
|
143
|
+
await bud.hooks.fire(`compiler.before`, bud)
|
144
|
+
|
145
|
+
this.instance = this.implementation(this.config)
|
146
|
+
this.instance.hooks.done.tap(bud.label, this.onStats)
|
147
|
+
this.instance.hooks.done.tap(`${bud.label}-compiler.done`, stats =>
|
148
|
+
bud.hooks.fire(`compiler.done`, bud, stats),
|
149
|
+
)
|
150
|
+
|
151
|
+
return this.instance
|
152
|
+
}
|
153
|
+
|
154
|
+
/**
|
155
|
+
* {@link Service.register}
|
156
|
+
*/
|
157
|
+
@bind
|
158
|
+
public override async register?(bud: Bud): Promise<any> {
|
159
|
+
this.implementation = await bud.module
|
160
|
+
.import(`@roots/bud-support/webpack`, import.meta.url)
|
161
|
+
.catch(error => {
|
162
|
+
throw BudError.normalize(error)
|
163
|
+
})
|
164
|
+
}
|
165
|
+
}
|
166
|
+
|
167
|
+
export {Compiler as default}
|
package/src/service/index.tsx
DELETED
@@ -1,300 +0,0 @@
|
|
1
|
-
import type {Bud} from '@roots/bud-framework'
|
2
|
-
import type {Compiler as BudCompiler} from '@roots/bud-framework'
|
3
|
-
import type {
|
4
|
-
MultiCompiler,
|
5
|
-
MultiStats,
|
6
|
-
Stats,
|
7
|
-
StatsCompilation,
|
8
|
-
StatsError,
|
9
|
-
Webpack,
|
10
|
-
} from '@roots/bud-framework/config'
|
11
|
-
import type {
|
12
|
-
ErrorWithSourceFile,
|
13
|
-
SourceFile,
|
14
|
-
} from '@roots/bud-support/open'
|
15
|
-
|
16
|
-
import {cpus} from 'node:os'
|
17
|
-
import process from 'node:process'
|
18
|
-
import {pathToFileURL} from 'node:url'
|
19
|
-
|
20
|
-
import {Display as DisplayError} from '@roots/bud-dashboard/components/error'
|
21
|
-
import {Service} from '@roots/bud-framework/service'
|
22
|
-
import {bind} from '@roots/bud-support/decorators/bind'
|
23
|
-
import {BudError, CompilerError} from '@roots/bud-support/errors'
|
24
|
-
import isNull from '@roots/bud-support/isNull'
|
25
|
-
import isNumber from '@roots/bud-support/isNumber'
|
26
|
-
import isString from '@roots/bud-support/isString'
|
27
|
-
import stripAnsi from '@roots/bud-support/strip-ansi'
|
28
|
-
|
29
|
-
/**
|
30
|
-
* {@link BudCompiler} implementation
|
31
|
-
*/
|
32
|
-
class Compiler extends Service implements BudCompiler {
|
33
|
-
/**
|
34
|
-
* {@link BudCompiler.config}
|
35
|
-
*/
|
36
|
-
public config: BudCompiler[`config`] = []
|
37
|
-
|
38
|
-
/**
|
39
|
-
* {@link BudCompiler.implementation}
|
40
|
-
*/
|
41
|
-
public implementation: BudCompiler[`implementation`] & typeof Webpack
|
42
|
-
|
43
|
-
/**
|
44
|
-
* {@link BudCompiler.instance}
|
45
|
-
*/
|
46
|
-
public declare instance: BudCompiler[`instance`]
|
47
|
-
|
48
|
-
/**
|
49
|
-
* {@link BudCompiler.stats}
|
50
|
-
*/
|
51
|
-
public declare stats: BudCompiler[`stats`]
|
52
|
-
|
53
|
-
/**
|
54
|
-
* {@link BudCompiler.onError}
|
55
|
-
*/
|
56
|
-
@bind
|
57
|
-
public onError(error: Error | undefined) {
|
58
|
-
process.exitCode = 1
|
59
|
-
if (!error) return
|
60
|
-
|
61
|
-
this.app.server?.appliedMiddleware?.hot?.publish({error})
|
62
|
-
|
63
|
-
const normalized = CompilerError.normalize(error, {
|
64
|
-
thrownBy: import.meta.url,
|
65
|
-
})
|
66
|
-
|
67
|
-
normalized.details = undefined
|
68
|
-
|
69
|
-
this.app.notifier?.notify({
|
70
|
-
group: this.app.label,
|
71
|
-
message: normalized.message,
|
72
|
-
subtitle: normalized.name,
|
73
|
-
})
|
74
|
-
|
75
|
-
this.app.context.render(<DisplayError error={error} />)
|
76
|
-
}
|
77
|
-
|
78
|
-
/**
|
79
|
-
* {@link BudCompiler.onStats}
|
80
|
-
*/
|
81
|
-
@bind
|
82
|
-
public onStats(stats: Stats & MultiStats) {
|
83
|
-
const makeNoticeTitle = (child: StatsCompilation) =>
|
84
|
-
this.app.label !== child.name
|
85
|
-
? `${this.app.label} (${child.name})`
|
86
|
-
: child.name
|
87
|
-
|
88
|
-
this.stats = stats.toJson(statsOptions)
|
89
|
-
this.app.context.render(this.app.dashboard.render(stats))
|
90
|
-
|
91
|
-
if (stats.hasErrors()) {
|
92
|
-
process.exitCode = 1
|
93
|
-
|
94
|
-
this.stats.children = this.stats.children?.map(child => ({
|
95
|
-
...child,
|
96
|
-
errors:
|
97
|
-
child.errors && this.sourceErrors
|
98
|
-
? this.sourceErrors(child.errors)
|
99
|
-
: child.errors ?? [],
|
100
|
-
}))
|
101
|
-
|
102
|
-
this.stats.children
|
103
|
-
?.filter(
|
104
|
-
child => isNumber(child.errorsCount) && child.errorsCount > 0,
|
105
|
-
)
|
106
|
-
.forEach(child => {
|
107
|
-
try {
|
108
|
-
const error = child.errors?.shift()
|
109
|
-
if (!error) return
|
110
|
-
|
111
|
-
this.app.notifier.notify({
|
112
|
-
group: `${this.app.label}-${child.name}`,
|
113
|
-
message: stripAnsi(error.message),
|
114
|
-
open: error.file ? pathToFileURL(error.file) : ``,
|
115
|
-
subtitle: error.file ? `Error in ${error.name}` : error.name,
|
116
|
-
title: makeNoticeTitle(child),
|
117
|
-
})
|
118
|
-
|
119
|
-
error.file && this.app.notifier.openEditor(error.file)
|
120
|
-
} catch (error) {
|
121
|
-
this.logger.error(error)
|
122
|
-
}
|
123
|
-
})
|
124
|
-
}
|
125
|
-
|
126
|
-
this.stats.children
|
127
|
-
?.filter(child => child.errorsCount === 0)
|
128
|
-
.forEach(child => {
|
129
|
-
try {
|
130
|
-
this.app.notifier.notify({
|
131
|
-
group: `${this.app.label}-${child.name}`,
|
132
|
-
message: child.modules
|
133
|
-
? `${child.modules.length} modules compiled`
|
134
|
-
: `Modules compiled successfully`,
|
135
|
-
open: this.app.server?.publicUrl.href,
|
136
|
-
subtitle: `Build successful`,
|
137
|
-
title: makeNoticeTitle(child),
|
138
|
-
})
|
139
|
-
|
140
|
-
this.app.server?.publicUrl.href &&
|
141
|
-
this.app.context.browser &&
|
142
|
-
this.app.notifier.openBrowser(this.app.server?.publicUrl.href)
|
143
|
-
} catch (error) {
|
144
|
-
this.logger.error(error)
|
145
|
-
}
|
146
|
-
})
|
147
|
-
}
|
148
|
-
/**
|
149
|
-
* {@link BudCompiler.compile}
|
150
|
-
*/
|
151
|
-
@bind
|
152
|
-
public async compile(bud: Bud): Promise<MultiCompiler> {
|
153
|
-
const config = !bud.hasChildren
|
154
|
-
? [await bud.build.make()]
|
155
|
-
: await Promise.all(
|
156
|
-
Object.values(bud.children).map(
|
157
|
-
async (child: Bud) =>
|
158
|
-
await child.build.make().catch(error => {
|
159
|
-
throw error
|
160
|
-
}),
|
161
|
-
),
|
162
|
-
)
|
163
|
-
|
164
|
-
this.config = config?.filter(Boolean)
|
165
|
-
|
166
|
-
if (this.config.length > 1) {
|
167
|
-
this.config.parallelism = Math.max(cpus().length - 1, 1)
|
168
|
-
this.logger.info(`parallel compilations: ${this.config.parallelism}`)
|
169
|
-
}
|
170
|
-
|
171
|
-
await bud.hooks.fire(`compiler.before`, bud).catch(error => {
|
172
|
-
throw error
|
173
|
-
})
|
174
|
-
|
175
|
-
this.logger.timeEnd(`initialize`)
|
176
|
-
|
177
|
-
this.instance = this.implementation(this.config)
|
178
|
-
|
179
|
-
this.instance.hooks.done.tap(bud.label, (stats: any) => {
|
180
|
-
this.onStats(stats)
|
181
|
-
bud.hooks
|
182
|
-
.fire(`compiler.done`, bud, this.stats)
|
183
|
-
.catch(this.app.catch)
|
184
|
-
})
|
185
|
-
|
186
|
-
return this.instance
|
187
|
-
}
|
188
|
-
|
189
|
-
/**
|
190
|
-
* {@link Service.register}
|
191
|
-
*/
|
192
|
-
@bind
|
193
|
-
public override async register?(bud: Bud): Promise<any> {
|
194
|
-
this.implementation = await bud.module
|
195
|
-
.import(`@roots/bud-support/webpack`, import.meta.url)
|
196
|
-
.catch(error => {
|
197
|
-
throw BudError.normalize(error)
|
198
|
-
})
|
199
|
-
}
|
200
|
-
|
201
|
-
@bind
|
202
|
-
public sourceErrors?(
|
203
|
-
errors: Array<StatsError> | undefined,
|
204
|
-
): Array<ErrorWithSourceFile | StatsError> {
|
205
|
-
if (!errors || !errors.length) return []
|
206
|
-
|
207
|
-
try {
|
208
|
-
return errors
|
209
|
-
?.map((error: StatsError): ErrorWithSourceFile | StatsError => {
|
210
|
-
let file: SourceFile[`file`] | undefined
|
211
|
-
let module: undefined | Webpack.StatsModule
|
212
|
-
|
213
|
-
const ident = error.moduleId ?? error.moduleName
|
214
|
-
|
215
|
-
/**
|
216
|
-
* In a perfect world webpack plugins would use the
|
217
|
-
* `nameForCondition` property to identify the module.
|
218
|
-
*/
|
219
|
-
if (ident && this.stats?.children) {
|
220
|
-
module = this.stats.children
|
221
|
-
.flatMap(child => child?.modules)
|
222
|
-
.find(module => [module?.id, module?.name].includes(ident))
|
223
|
-
}
|
224
|
-
|
225
|
-
/**
|
226
|
-
* If the module is not found, we try to parse the error message
|
227
|
-
*/
|
228
|
-
if (!ident && error.message?.includes(`[stylelint]`)) {
|
229
|
-
// try to get the origin of the stylelint error,
|
230
|
-
// which is contained in the second line of the error message
|
231
|
-
const unparsedOrigin = error.message?.split(`\n`)?.[1]
|
232
|
-
|
233
|
-
// if the origin is not a string or too long, we return the error as-is
|
234
|
-
if (!isString(unparsedOrigin) || unparsedOrigin.length > 100)
|
235
|
-
return error
|
236
|
-
|
237
|
-
// extract absolute path and context relative name of module
|
238
|
-
const styleError = unparsedOrigin.match(
|
239
|
-
/file:\/\/(.*)\x07(.*)\x1B]8;;/,
|
240
|
-
)
|
241
|
-
if (isNull(styleError)) return error
|
242
|
-
|
243
|
-
// get parts of matched error
|
244
|
-
const [, file, name] = styleError
|
245
|
-
// return enriched error
|
246
|
-
return {...error, file, name, nameForCondition: file}
|
247
|
-
}
|
248
|
-
|
249
|
-
/**
|
250
|
-
* If the module is still not found, we return the error as-is
|
251
|
-
*/
|
252
|
-
if (!module) return error
|
253
|
-
|
254
|
-
/**
|
255
|
-
* We'll prefer the `nameForCondition` property if it exists,
|
256
|
-
* otherwise we'll use the `name` property.
|
257
|
-
*/
|
258
|
-
if (module.nameForCondition) {
|
259
|
-
file = module.nameForCondition
|
260
|
-
} else if (module.name) {
|
261
|
-
file = this.app.path(`@src`, module.name)
|
262
|
-
}
|
263
|
-
|
264
|
-
const name = module.name ?? error.name ?? `error`
|
265
|
-
return {...error, file, name}
|
266
|
-
})
|
267
|
-
.filter(Boolean)
|
268
|
-
} catch (error) {
|
269
|
-
this.logger.warn(
|
270
|
-
`Problem parsing errors. This probably won't break anything but please report it: https://github.com/roots/bud/issues/new`,
|
271
|
-
error,
|
272
|
-
)
|
273
|
-
return errors
|
274
|
-
}
|
275
|
-
}
|
276
|
-
}
|
277
|
-
|
278
|
-
const statsOptions = {
|
279
|
-
all: false,
|
280
|
-
children: {
|
281
|
-
all: false,
|
282
|
-
assets: true,
|
283
|
-
cached: true,
|
284
|
-
cachedAssets: true,
|
285
|
-
cachedModules: true,
|
286
|
-
entrypoints: true,
|
287
|
-
errorDetails: false,
|
288
|
-
errors: true,
|
289
|
-
errorsCount: true,
|
290
|
-
hash: true,
|
291
|
-
modules: true,
|
292
|
-
name: true,
|
293
|
-
outputPath: true,
|
294
|
-
timings: true,
|
295
|
-
warnings: true,
|
296
|
-
warningsCount: true,
|
297
|
-
},
|
298
|
-
}
|
299
|
-
|
300
|
-
export {Compiler as default}
|