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

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.
@@ -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}