@roots/bud-compiler 2024.6.4-7 → 2024.6.5-7

Sign up to get free protection for your applications and to get access to all the features.
@@ -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, StatsError, 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: MultiStats & Stats): void;
28
+ onStats(stats: MultiStats): void;
34
29
  /**
35
30
  * {@link BudCompiler.compile}
36
31
  */
@@ -39,6 +34,8 @@ 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>;
37
+ formatErrors(errors: Array<StatsError> | undefined): Array<({
38
+ file: string;
39
+ } & Error) | StatsError>;
43
40
  }
44
41
  export { Compiler as default };
@@ -1,17 +1,14 @@
1
1
  import { __decorate } from "tslib";
2
- import { jsxDEV as _jsxDEV } from "@roots/bud-support/jsx-dev-runtime";
3
- const _jsxFileName = "/home/runner/work/bud/bud/sources/@roots/bud-compiler/src/service/index.tsx";
4
2
  import { cpus } from 'node:os';
5
3
  import process from 'node:process';
6
4
  import { pathToFileURL } from 'node:url';
7
- import { Display as DisplayError } from '@roots/bud-dashboard/components/error';
8
5
  import { Service } from '@roots/bud-framework/service';
9
6
  import { bind } from '@roots/bud-support/decorators/bind';
10
- import { BudError, CompilerError } from '@roots/bud-support/errors';
11
- import isNull from '@roots/bud-support/isNull';
7
+ import { BudError } from '@roots/bud-support/errors';
12
8
  import isNumber from '@roots/bud-support/isNumber';
13
- import isString from '@roots/bud-support/isString';
14
9
  import stripAnsi from '@roots/bud-support/strip-ansi';
10
+ import { makeErrorFormatter } from './formatError.js';
11
+ import { makeNoticeTitle } from './makeNoticeTitle.js';
15
12
  /**
16
13
  * {@link BudCompiler} implementation
17
14
  */
@@ -24,40 +21,18 @@ class Compiler extends Service {
24
21
  * {@link BudCompiler.implementation}
25
22
  */
26
23
  implementation;
27
- /**
28
- * {@link BudCompiler.onError}
29
- */
30
- onError(error) {
31
- process.exitCode = 1;
32
- if (!error)
33
- return;
34
- this.app.server?.appliedMiddleware?.hot?.publish({ error });
35
- const normalized = CompilerError.normalize(error, {
36
- thrownBy: import.meta.url,
37
- });
38
- normalized.details = undefined;
39
- this.app.notifier?.notify({
40
- group: this.app.label,
41
- message: normalized.message,
42
- subtitle: normalized.name,
43
- });
44
- this.app.context.render(_jsxDEV(DisplayError, { error: error }, void 0, false, { fileName: _jsxFileName, lineNumber: 75, columnNumber: 29 }, this));
45
- }
46
24
  /**
47
25
  * {@link BudCompiler.onStats}
48
26
  */
49
27
  onStats(stats) {
50
- const makeNoticeTitle = (child) => this.app.label !== child.name
51
- ? `${this.app.label} (${child.name})`
52
- : child.name;
53
28
  this.stats = stats.toJson(statsOptions);
54
29
  this.app.context.render(this.app.dashboard.render(stats));
55
30
  if (stats.hasErrors()) {
56
31
  process.exitCode = 1;
57
32
  this.stats.children = this.stats.children?.map(child => ({
58
33
  ...child,
59
- errors: child.errors && this.sourceErrors
60
- ? this.sourceErrors(child.errors)
34
+ errors: child.errors
35
+ ? this.formatErrors(child.errors)
61
36
  : child.errors ?? [],
62
37
  }));
63
38
  this.stats.children
@@ -72,7 +47,7 @@ class Compiler extends Service {
72
47
  message: stripAnsi(error.message),
73
48
  open: error.file ? pathToFileURL(error.file) : ``,
74
49
  subtitle: error.file ? `Error in ${error.name}` : error.name,
75
- title: makeNoticeTitle(child),
50
+ title: makeNoticeTitle(this.app, child),
76
51
  });
77
52
  error.file && this.app.notifier.openEditor(error.file);
78
53
  }
@@ -81,26 +56,19 @@ class Compiler extends Service {
81
56
  }
82
57
  });
83
58
  }
84
- this.stats.children
85
- ?.filter(child => child.errorsCount === 0)
86
- .forEach(child => {
87
- try {
88
- this.app.notifier.notify({
89
- group: `${this.app.label}-${child.name}`,
90
- message: child.modules
91
- ? `${child.modules.length} modules compiled`
92
- : `Modules compiled successfully`,
93
- open: this.app.server?.publicUrl.href,
94
- subtitle: `Build successful`,
95
- title: makeNoticeTitle(child),
96
- });
97
- this.app.server?.publicUrl.href &&
98
- this.app.context.browser &&
99
- this.app.notifier.openBrowser(this.app.server?.publicUrl.href);
100
- }
101
- catch (error) {
102
- this.logger.error(error);
103
- }
59
+ this.stats.children?.forEach(child => {
60
+ this.app.notifier.notify({
61
+ group: `${this.app.label}-${child.name}`,
62
+ message: child.modules
63
+ ? `${child.modules.length} modules compiled`
64
+ : `Modules compiled successfully`,
65
+ open: this.app.server?.publicUrl.href,
66
+ subtitle: `Build successful`,
67
+ title: makeNoticeTitle(this.app, child),
68
+ });
69
+ this.app.server?.publicUrl.href &&
70
+ this.app.context.browser &&
71
+ this.app.notifier.openBrowser(this.app.server?.publicUrl.href);
104
72
  });
105
73
  }
106
74
  /**
@@ -109,9 +77,7 @@ class Compiler extends Service {
109
77
  async compile(bud) {
110
78
  const config = !bud.hasChildren
111
79
  ? [await bud.build.make()]
112
- : await Promise.all(Object.values(bud.children).map(async (child) => await child.build.make().catch(error => {
113
- throw error;
114
- })));
80
+ : await Promise.all(Object.values(bud.children).map(async (child) => await child.build.make()));
115
81
  this.config = config?.filter(Boolean);
116
82
  if (this.config.length > 1) {
117
83
  this.config.parallelism = Math.max(cpus().length - 1, 1);
@@ -120,14 +86,9 @@ class Compiler extends Service {
120
86
  await bud.hooks.fire(`compiler.before`, bud).catch(error => {
121
87
  throw error;
122
88
  });
123
- this.logger.timeEnd(`initialize`);
124
89
  this.instance = this.implementation(this.config);
125
- this.instance.hooks.done.tap(bud.label, (stats) => {
126
- this.onStats(stats);
127
- bud.hooks
128
- .fire(`compiler.done`, bud, this.stats)
129
- .catch(this.app.catch);
130
- });
90
+ this.instance.hooks.done.tap(bud.label, this.onStats);
91
+ this.instance.hooks.done.tap(`${bud.label}-compiler.done`, stats => bud.hooks.fire(`compiler.done`, bud, stats));
131
92
  return this.instance;
132
93
  }
133
94
  /**
@@ -140,72 +101,10 @@ class Compiler extends Service {
140
101
  throw BudError.normalize(error);
141
102
  });
142
103
  }
143
- sourceErrors(errors) {
144
- if (!errors || !errors.length)
145
- return [];
146
- try {
147
- return errors
148
- ?.map((error) => {
149
- let file;
150
- let module;
151
- const ident = error.moduleId ?? error.moduleName;
152
- /**
153
- * In a perfect world webpack plugins would use the
154
- * `nameForCondition` property to identify the module.
155
- */
156
- if (ident && this.stats?.children) {
157
- module = this.stats.children
158
- .flatMap(child => child?.modules)
159
- .find(module => [module?.id, module?.name].includes(ident));
160
- }
161
- /**
162
- * If the module is not found, we try to parse the error message
163
- */
164
- if (!ident && error.message?.includes(`[stylelint]`)) {
165
- // try to get the origin of the stylelint error,
166
- // which is contained in the second line of the error message
167
- const unparsedOrigin = error.message?.split(`\n`)?.[1];
168
- // if the origin is not a string or too long, we return the error as-is
169
- if (!isString(unparsedOrigin) || unparsedOrigin.length > 100)
170
- return error;
171
- // extract absolute path and context relative name of module
172
- const styleError = unparsedOrigin.match(/file:\/\/(.*)\x07(.*)\x1B]8;;/);
173
- if (isNull(styleError))
174
- return error;
175
- // get parts of matched error
176
- const [, file, name] = styleError;
177
- // return enriched error
178
- return { ...error, file, name, nameForCondition: file };
179
- }
180
- /**
181
- * If the module is still not found, we return the error as-is
182
- */
183
- if (!module)
184
- return error;
185
- /**
186
- * We'll prefer the `nameForCondition` property if it exists,
187
- * otherwise we'll use the `name` property.
188
- */
189
- if (module.nameForCondition) {
190
- file = module.nameForCondition;
191
- }
192
- else if (module.name) {
193
- file = this.app.path(`@src`, module.name);
194
- }
195
- const name = module.name ?? error.name ?? `error`;
196
- return { ...error, file, name };
197
- })
198
- .filter(Boolean);
199
- }
200
- catch (error) {
201
- this.logger.warn(`Problem parsing errors. This probably won't break anything but please report it: https://github.com/roots/bud/issues/new`, error);
202
- return errors;
203
- }
104
+ formatErrors(errors) {
105
+ return (errors?.map(makeErrorFormatter(this.stats)).filter(Boolean) ?? []);
204
106
  }
205
107
  }
206
- __decorate([
207
- bind
208
- ], Compiler.prototype, "onError", null);
209
108
  __decorate([
210
109
  bind
211
110
  ], Compiler.prototype, "onStats", null);
@@ -217,7 +116,7 @@ __decorate([
217
116
  ], Compiler.prototype, "register", null);
218
117
  __decorate([
219
118
  bind
220
- ], Compiler.prototype, "sourceErrors", null);
119
+ ], Compiler.prototype, "formatErrors", null);
221
120
  const statsOptions = {
222
121
  all: false,
223
122
  children: {
@@ -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": "2024.6.4-7",
3
+ "version": "2024.6.5-7",
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": "2024.6.4-7",
68
+ "@roots/bud-api": "2024.6.5-7",
69
69
  "@skypack/package-check": "0.2.2",
70
- "@types/node": "20.12.8",
71
- "@types/react": "18.3.1"
70
+ "@types/node": "20.12.8"
72
71
  },
73
72
  "dependencies": {
74
- "@roots/bud-dashboard": "2024.6.4-7",
75
- "@roots/bud-framework": "2024.6.4-7",
76
- "@roots/bud-support": "2024.6.4-7",
77
- "react": "18.3.1",
73
+ "@roots/bud-dashboard": "2024.6.5-7",
74
+ "@roots/bud-framework": "2024.6.5-7",
75
+ "@roots/bud-support": "2024.6.5-7",
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,183 @@
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
+ StatsError,
7
+ Webpack,
8
+ } from '@roots/bud-framework/config'
9
+
10
+ import {cpus} from 'node:os'
11
+ import process from 'node:process'
12
+ import {pathToFileURL} from 'node:url'
13
+
14
+ import {Service} from '@roots/bud-framework/service'
15
+ import {bind} from '@roots/bud-support/decorators/bind'
16
+ import {BudError} from '@roots/bud-support/errors'
17
+ import isNumber from '@roots/bud-support/isNumber'
18
+ import stripAnsi from '@roots/bud-support/strip-ansi'
19
+
20
+ import {makeErrorFormatter} from './formatError.js'
21
+ import {makeNoticeTitle} from './makeNoticeTitle.js'
22
+
23
+ /**
24
+ * {@link BudCompiler} implementation
25
+ */
26
+ class Compiler extends Service implements BudCompiler {
27
+ /**
28
+ * {@link BudCompiler.config}
29
+ */
30
+ public config: BudCompiler[`config`] = []
31
+
32
+ /**
33
+ * {@link BudCompiler.implementation}
34
+ */
35
+ public implementation: BudCompiler[`implementation`] & typeof Webpack
36
+
37
+ /**
38
+ * {@link BudCompiler.instance}
39
+ */
40
+ public declare instance: BudCompiler[`instance`]
41
+
42
+ /**
43
+ * {@link BudCompiler.stats}
44
+ */
45
+ public declare stats: BudCompiler[`stats`]
46
+
47
+ /**
48
+ * {@link BudCompiler.onStats}
49
+ */
50
+ @bind
51
+ public onStats(stats: MultiStats) {
52
+ this.stats = stats.toJson(statsOptions)
53
+ this.app.context.render(this.app.dashboard.render(stats))
54
+
55
+ if (stats.hasErrors()) {
56
+ process.exitCode = 1
57
+
58
+ this.stats.children = this.stats.children?.map(child => ({
59
+ ...child,
60
+ errors: child.errors
61
+ ? this.formatErrors(child.errors)
62
+ : child.errors ?? [],
63
+ }))
64
+
65
+ this.stats.children
66
+ ?.filter(
67
+ child => isNumber(child.errorsCount) && child.errorsCount > 0,
68
+ )
69
+ .forEach(child => {
70
+ try {
71
+ const error = child.errors?.shift()
72
+ if (!error) return
73
+
74
+ this.app.notifier.notify({
75
+ group: `${this.app.label}-${child.name}`,
76
+ message: stripAnsi(error.message),
77
+ open: error.file ? pathToFileURL(error.file) : ``,
78
+ subtitle: error.file ? `Error in ${error.name}` : error.name,
79
+ title: makeNoticeTitle(this.app, child),
80
+ })
81
+
82
+ error.file && this.app.notifier.openEditor(error.file)
83
+ } catch (error) {
84
+ this.logger.error(error)
85
+ }
86
+ })
87
+ }
88
+
89
+ this.stats.children?.forEach(child => {
90
+ this.app.notifier.notify({
91
+ group: `${this.app.label}-${child.name}`,
92
+ message: child.modules
93
+ ? `${child.modules.length} modules compiled`
94
+ : `Modules compiled successfully`,
95
+ open: this.app.server?.publicUrl.href,
96
+ subtitle: `Build successful`,
97
+ title: makeNoticeTitle(this.app, child),
98
+ })
99
+
100
+ this.app.server?.publicUrl.href &&
101
+ this.app.context.browser &&
102
+ this.app.notifier.openBrowser(this.app.server?.publicUrl.href)
103
+ })
104
+ }
105
+
106
+ /**
107
+ * {@link BudCompiler.compile}
108
+ */
109
+ @bind
110
+ public async compile(bud: Bud): Promise<MultiCompiler> {
111
+ const config = !bud.hasChildren
112
+ ? [await bud.build.make()]
113
+ : await Promise.all(
114
+ Object.values(bud.children).map(
115
+ async (child: Bud) => await child.build.make(),
116
+ ),
117
+ )
118
+
119
+ this.config = config?.filter(Boolean)
120
+
121
+ if (this.config.length > 1) {
122
+ this.config.parallelism = Math.max(cpus().length - 1, 1)
123
+ this.logger.info(`parallel compilations: ${this.config.parallelism}`)
124
+ }
125
+
126
+ await bud.hooks.fire(`compiler.before`, bud).catch(error => {
127
+ throw error
128
+ })
129
+
130
+ this.instance = this.implementation(this.config)
131
+ this.instance.hooks.done.tap(bud.label, this.onStats)
132
+ this.instance.hooks.done.tap(`${bud.label}-compiler.done`, stats =>
133
+ bud.hooks.fire(`compiler.done`, bud, stats),
134
+ )
135
+
136
+ return this.instance
137
+ }
138
+
139
+ /**
140
+ * {@link Service.register}
141
+ */
142
+ @bind
143
+ public override async register?(bud: Bud): Promise<any> {
144
+ this.implementation = await bud.module
145
+ .import(`@roots/bud-support/webpack`, import.meta.url)
146
+ .catch(error => {
147
+ throw BudError.normalize(error)
148
+ })
149
+ }
150
+
151
+ @bind
152
+ public formatErrors(
153
+ errors: Array<StatsError> | undefined,
154
+ ): Array<({file: string} & Error) | StatsError> {
155
+ return (
156
+ errors?.map(makeErrorFormatter(this.stats)).filter(Boolean) ?? []
157
+ )
158
+ }
159
+ }
160
+
161
+ const statsOptions = {
162
+ all: false,
163
+ children: {
164
+ all: false,
165
+ assets: true,
166
+ cached: true,
167
+ cachedAssets: true,
168
+ cachedModules: true,
169
+ entrypoints: true,
170
+ errorDetails: false,
171
+ errors: true,
172
+ errorsCount: true,
173
+ hash: true,
174
+ modules: true,
175
+ name: true,
176
+ outputPath: true,
177
+ timings: true,
178
+ warnings: true,
179
+ warningsCount: true,
180
+ },
181
+ }
182
+
183
+ 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: MultiStats & Stats) {
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}