@roots/bud-compiler 6.21.0 → 6.22.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 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.copiadigital.com/">
73
- <img src="https://cdn.roots.io/app/uploads/copia-digital.svg" alt="Copia Digital" width="200" height="150"/>
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
+ };
@@ -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, Stats, StatsError, Webpack } from '@roots/bud-framework/config';
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: Stats & MultiStats): void;
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 };
@@ -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, CompilerError } from '@roots/bud-support/errors';
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,41 +21,40 @@ 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
- const makeNoticeTitle = (child) => this.app.label !== child.name
50
- ? `${this.app.label} (${child.name})`
51
- : child.name;
52
- this.stats = stats.toJson(statsOptions);
53
- this.app.context.render(this.app.dashboard.render(stats));
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 && this.sourceErrors
59
- ? this.sourceErrors(child.errors)
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)
@@ -71,7 +68,7 @@ class Compiler extends Service {
71
68
  message: stripAnsi(error.message),
72
69
  open: error.file ? pathToFileURL(error.file) : ``,
73
70
  subtitle: error.file ? `Error in ${error.name}` : error.name,
74
- title: makeNoticeTitle(child),
71
+ title: makeNoticeTitle(this.app, child),
75
72
  });
76
73
  error.file && this.app.notifier.openEditor(error.file);
77
74
  }
@@ -80,26 +77,19 @@ class Compiler extends Service {
80
77
  }
81
78
  });
82
79
  }
83
- this.stats.children
84
- ?.filter(child => child.errorsCount === 0)
85
- .forEach(child => {
86
- try {
87
- this.app.notifier.notify({
88
- group: `${this.app.label}-${child.name}`,
89
- message: child.modules
90
- ? `${child.modules.length} modules compiled`
91
- : `Modules compiled successfully`,
92
- open: this.app.server?.publicUrl.href,
93
- subtitle: `Build successful`,
94
- title: makeNoticeTitle(child),
95
- });
96
- this.app.server?.publicUrl.href &&
97
- this.app.context.browser &&
98
- this.app.notifier.openBrowser(this.app.server?.publicUrl.href);
99
- }
100
- catch (error) {
101
- this.logger.error(error);
102
- }
80
+ this.stats.children?.forEach(child => {
81
+ this.app.notifier.notify({
82
+ group: `${this.app.label}-${child.name}`,
83
+ message: child.modules
84
+ ? `${child.modules.length} modules compiled`
85
+ : `Modules compiled successfully`,
86
+ open: this.app.server?.publicUrl.href,
87
+ subtitle: `Build successful`,
88
+ title: makeNoticeTitle(this.app, child),
89
+ });
90
+ this.app.server?.publicUrl.href &&
91
+ this.app.context.browser &&
92
+ this.app.notifier.openBrowser(this.app.server?.publicUrl.href);
103
93
  });
104
94
  }
105
95
  /**
@@ -108,25 +98,16 @@ class Compiler extends Service {
108
98
  async compile(bud) {
109
99
  const config = !bud.hasChildren
110
100
  ? [await bud.build.make()]
111
- : await Promise.all(Object.values(bud.children).map(async (child) => await child.build.make().catch(error => {
112
- throw error;
113
- })));
101
+ : await Promise.all(Object.values(bud.children).map(async (child) => await child.build.make()));
114
102
  this.config = config?.filter(Boolean);
115
103
  if (this.config.length > 1) {
116
104
  this.config.parallelism = Math.max(cpus().length - 1, 1);
117
105
  this.logger.info(`parallel compilations: ${this.config.parallelism}`);
118
106
  }
119
- await bud.hooks.fire(`compiler.before`, bud).catch(error => {
120
- throw error;
121
- });
122
- this.logger.timeEnd(`initialize`);
107
+ await bud.hooks.fire(`compiler.before`, bud);
123
108
  this.instance = this.implementation(this.config);
124
- this.instance.hooks.done.tap(bud.label, (stats) => {
125
- this.onStats(stats);
126
- bud.hooks
127
- .fire(`compiler.done`, bud, this.stats)
128
- .catch(this.app.catch);
129
- });
109
+ this.instance.hooks.done.tap(bud.label, this.onStats);
110
+ this.instance.hooks.done.tap(`${bud.label}-compiler.done`, stats => bud.hooks.fire(`compiler.done`, bud, stats));
130
111
  return this.instance;
131
112
  }
132
113
  /**
@@ -139,72 +120,7 @@ class Compiler extends Service {
139
120
  throw BudError.normalize(error);
140
121
  });
141
122
  }
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
123
  }
205
- __decorate([
206
- bind
207
- ], Compiler.prototype, "onError", null);
208
124
  __decorate([
209
125
  bind
210
126
  ], Compiler.prototype, "onStats", null);
@@ -214,28 +130,4 @@ __decorate([
214
130
  __decorate([
215
131
  bind
216
132
  ], 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
133
  export { Compiler as default };
@@ -0,0 +1,3 @@
1
+ import type { Bud } from '@roots/bud-framework';
2
+ import type { StatsCompilation } from '@roots/bud-framework/config';
3
+ export declare const makeNoticeTitle: (bud: Bud, child: StatsCompilation) => string;
@@ -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.21.0",
3
+ "version": "6.22.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.21.0",
68
+ "@roots/bud-api": "6.22.0",
69
69
  "@skypack/package-check": "0.2.2",
70
- "@types/node": "20.10.5",
71
- "@types/react": "18.2.45"
70
+ "@types/node": "20.12.8"
72
71
  },
73
72
  "dependencies": {
74
- "@roots/bud-dashboard": "6.21.0",
75
- "@roots/bud-framework": "6.21.0",
76
- "@roots/bud-support": "6.21.0",
77
- "react": "18.2.0",
73
+ "@roots/bud-dashboard": "6.22.0",
74
+ "@roots/bud-framework": "6.22.0",
75
+ "@roots/bud-support": "6.22.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,171 @@
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
+ try {
92
+ const error = child.errors?.shift()
93
+ if (!error) return
94
+
95
+ this.app.notifier.notify({
96
+ group: `${this.app.label}-${child.name}`,
97
+ message: stripAnsi(error.message),
98
+ open: error.file ? pathToFileURL(error.file) : ``,
99
+ subtitle: error.file ? `Error in ${error.name}` : error.name,
100
+ title: makeNoticeTitle(this.app, child),
101
+ })
102
+
103
+ error.file && this.app.notifier.openEditor(error.file)
104
+ } catch (error) {
105
+ this.logger.error(error)
106
+ }
107
+ })
108
+ }
109
+
110
+ this.stats.children?.forEach(child => {
111
+ this.app.notifier.notify({
112
+ group: `${this.app.label}-${child.name}`,
113
+ message: child.modules
114
+ ? `${child.modules.length} modules compiled`
115
+ : `Modules compiled successfully`,
116
+ open: this.app.server?.publicUrl.href,
117
+ subtitle: `Build successful`,
118
+ title: makeNoticeTitle(this.app, child),
119
+ })
120
+
121
+ this.app.server?.publicUrl.href &&
122
+ this.app.context.browser &&
123
+ this.app.notifier.openBrowser(this.app.server?.publicUrl.href)
124
+ })
125
+ }
126
+
127
+ /**
128
+ * {@link BudCompiler.compile}
129
+ */
130
+ @bind
131
+ public async compile(bud: Bud): Promise<MultiCompiler> {
132
+ const config = !bud.hasChildren
133
+ ? [await bud.build.make()]
134
+ : await Promise.all(
135
+ Object.values(bud.children).map(
136
+ async (child: Bud) => await child.build.make(),
137
+ ),
138
+ )
139
+
140
+ this.config = config?.filter(Boolean)
141
+
142
+ if (this.config.length > 1) {
143
+ this.config.parallelism = Math.max(cpus().length - 1, 1)
144
+ this.logger.info(`parallel compilations: ${this.config.parallelism}`)
145
+ }
146
+
147
+ await bud.hooks.fire(`compiler.before`, bud)
148
+
149
+ this.instance = this.implementation(this.config)
150
+ this.instance.hooks.done.tap(bud.label, this.onStats)
151
+ this.instance.hooks.done.tap(`${bud.label}-compiler.done`, stats =>
152
+ bud.hooks.fire(`compiler.done`, bud, stats),
153
+ )
154
+
155
+ return this.instance
156
+ }
157
+
158
+ /**
159
+ * {@link Service.register}
160
+ */
161
+ @bind
162
+ public override async register?(bud: Bud): Promise<any> {
163
+ this.implementation = await bud.module
164
+ .import(`@roots/bud-support/webpack`, import.meta.url)
165
+ .catch(error => {
166
+ throw BudError.normalize(error)
167
+ })
168
+ }
169
+ }
170
+
171
+ export {Compiler as default}
@@ -0,0 +1,5 @@
1
+ import type {Bud} from '@roots/bud-framework'
2
+ import type {StatsCompilation} from '@roots/bud-framework/config'
3
+
4
+ export const makeNoticeTitle = (bud: Bud, child: StatsCompilation) =>
5
+ bud.label !== child.name ? `${bud.label} (${child.name})` : child.name
@@ -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}