@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 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,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
- 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)
64
61
  .forEach(child => {
65
- try {
66
- const error = child.errors?.shift();
67
- if (!error)
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: 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),
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.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
- }
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().catch(error => {
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).catch(error => {
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, (stats) => {
125
- this.onStats(stats);
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,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.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.21.0",
68
+ "@roots/bud-api": "6.23.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.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}
@@ -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}