diff --git a/sources/@roots/bud-build/src/config/experiments.ts b/sources/@roots/bud-build/src/config/experiments.ts index 0547424c3e..23db1d3e6a 100644 --- a/sources/@roots/bud-build/src/config/experiments.ts +++ b/sources/@roots/bud-build/src/config/experiments.ts @@ -6,6 +6,7 @@ export const experiments: Factory<`experiments`> = async ({ }) => hooks.filter(`build.experiments`, { backCompat: false, + cacheUnaffected: true, lazyCompilation: isDevelopment ? {entries: false, imports: true} : false, diff --git a/sources/@roots/bud-build/src/config/optimization.ts b/sources/@roots/bud-build/src/config/optimization.ts index 31858c26cb..10b0d4b56b 100644 --- a/sources/@roots/bud-build/src/config/optimization.ts +++ b/sources/@roots/bud-build/src/config/optimization.ts @@ -31,7 +31,7 @@ export const optimization: Factory<`optimization`> = async ({ ), removeEmptyChunks: filter( `build.optimization.removeEmptyChunks`, - isProduction, + false, ), runtimeChunk: filter(`build.optimization.runtimeChunk`, `single`), sideEffects: filter(`build.optimization.sideEffects`, isProduction), diff --git a/sources/@roots/bud-build/src/config/output/index.ts b/sources/@roots/bud-build/src/config/output/index.ts index 6441e9de34..250d9f6194 100644 --- a/sources/@roots/bud-build/src/config/output/index.ts +++ b/sources/@roots/bud-build/src/config/output/index.ts @@ -24,7 +24,11 @@ export const output: Factory<`output`> = async ({ iife: filter(`build.output.iife`, undefined), module: filter(`build.output.module`, false), path: filter(`build.output.path`, path(`@dist`)), - pathinfo: filter(`build.output.pathinfo`, true), + /** + * Path info is not necessary unless the user + * really knows what's going on. + */ + pathinfo: filter(`build.output.pathinfo`, false), publicPath: filter(`build.output.publicPath`, `auto`), scriptType: filter( `build.output.scriptType`, diff --git a/sources/@roots/bud-compiler/src/service.tsx b/sources/@roots/bud-compiler/src/service.tsx index 9ec02be234..3e27722544 100644 --- a/sources/@roots/bud-compiler/src/service.tsx +++ b/sources/@roots/bud-compiler/src/service.tsx @@ -23,6 +23,8 @@ import {bind} from '@roots/bud-support/decorators/bind' import {BudError, type BudErrorClass} from '@roots/bud-support/errors' import {duration} from '@roots/bud-support/human-readable' import {render} from '@roots/bud-support/ink' +import isNull from '@roots/bud-support/lodash/isNull' +import isString from '@roots/bud-support/lodash/isString' import stripAnsi from '@roots/bud-support/strip-ansi' import webpack from '@roots/bud-support/webpack' @@ -210,63 +212,71 @@ export class Compiler extends Service implements BudCompiler { if (!errors || !errors.length) return [] try { - const parseError = ( - error: StatsError, - ): ErrorWithSourceFile | StatsError => { - let file: SourceFile[`file`] | undefined - - const moduleIdent = error.moduleId ?? error.moduleName - - /** - * In a perfect world webpack plugins would use the - * `nameForCondition` property to identify the module. - */ - let module = this.compilationStats.children - .flatMap(child => child?.modules) - .find( - module => - module?.id === moduleIdent || module?.name === moduleIdent, - ) - - /** - * If the module is not found, we try to parse the error message - */ - if (!moduleIdent) { - const stylelintExtracted = error.message.match( - /file:\/\/(.*)\x07(.*)\x1B]8;;/, - ) - - if (stylelintExtracted?.[1]) { - module = { - name: stylelintExtracted[2] ?? stylelintExtracted[1], - nameForCondition: stylelintExtracted[1], - } + return errors + ?.map((error: StatsError): ErrorWithSourceFile | StatsError => { + let file: SourceFile[`file`] | undefined + let module: undefined | Webpack.StatsModule + + const ident = error.moduleId ?? error.moduleName + + /** + * In a perfect world webpack plugins would use the + * `nameForCondition` property to identify the module. + */ + if (ident) { + module = this.compilationStats.children + .flatMap(child => child?.modules) + .find(module => [module?.id, module?.name].includes(ident)) } - } - /** - * If the module is still not found, we return the error as-is - */ - if (!module) return error - - /** - * We'll prefer the `nameForCondition` property if it exists, - * otherwise we'll use the `name` property. - */ - if (module.nameForCondition) { - file = module.nameForCondition - } else if (module.name) { - file = this.app.path(`@src`, module.name) - } + /** + * If the module is not found, we try to parse the error message + */ + if (!ident && error.message?.includes(`[stylelint]`)) { + // try to get the origin of the stylelint error, + // which is contained in the second line of the error message + const unparsedOrigin = error.message?.split(`\n`)?.[1] + + // if the origin is not a string or too long, we return the error as-is + if (!isString(unparsedOrigin) || unparsedOrigin.length > 100) + return error + + // extract absolute path and context relative name of module + const styleError = unparsedOrigin.match( + /file:\/\/(.*)\x07(.*)\x1B]8;;/, + ) + if (isNull(styleError)) return error + + // get parts of matched error + const [, file, name] = styleError + // return enriched error + return {...error, file, name, nameForCondition: file} + } - return !file - ? {...error, name: module.name ?? error.name} - : {...error, file, name: module.name ?? error.name} - } + /** + * If the module is still not found, we return the error as-is + */ + if (!module) return error + + /** + * We'll prefer the `nameForCondition` property if it exists, + * otherwise we'll use the `name` property. + */ + if (module.nameForCondition) { + file = module.nameForCondition + } else if (module.name) { + file = this.app.path(`@src`, module.name) + } - return errors?.map(parseError).filter(Boolean) + const name = module.name ?? error.name ?? `error` + return {...error, file, name} + }) + .filter(Boolean) } catch (error) { - this.logger.warn(`error parsing errors`, error) + this.logger.warn( + `Problem parsing errors. This probably won't break anything but please report it: https://github.com/roots/bud/issues/new`, + error, + ) return errors } } @@ -281,16 +291,12 @@ const statsOptions = { cachedAssets: true, cachedModules: true, entrypoints: true, - errorDetails: false, errors: true, errorsCount: true, - errorStack: false, hash: true, modules: true, name: true, outputPath: true, - reasons: false, - runtime: true, timings: true, warnings: true, warningsCount: true, diff --git a/sources/@roots/bud-framework/src/notifier.ts b/sources/@roots/bud-framework/src/notifier.ts index daffaf8c18..7dfa33cbdd 100644 --- a/sources/@roots/bud-framework/src/notifier.ts +++ b/sources/@roots/bud-framework/src/notifier.ts @@ -12,6 +12,7 @@ import isEmpty from '@roots/bud-support/lodash/isEmpty' import isString from '@roots/bud-support/lodash/isString' import logger from '@roots/bud-support/logger' import {open, openEditor} from '@roots/bud-support/open' +import chalk from 'chalk' const notifierPath = resolve( dirname(fileURLToPath(import.meta.url)), @@ -173,7 +174,6 @@ export class Notifier { */ public openEditor(input: Array | string) { if (!this.openEditorEnabled) return - if (!input || isEmpty(input)) return logger.scope(`notifier`, `openEditor`).log(`input received`, input) @@ -193,21 +193,23 @@ export class Notifier { * True if editor opening is enabled */ public get openEditorEnabled(): boolean { - if (!this.editor) { + const enabled = + this.app.context.editor === true || // if true, fall back to default behavior + typeof this.app.context.editor === `string` // if string, opens in that editor + + if (enabled && !this.editor) { logger .scope(`notifier`, `editor check`) .warn( - `Editor not set.`, - `\n\nYou should set an editor using any of the following env vars:\n`, - `\n - BUD_EDITOR (bud specific; preferred)`, - `\n - VISUAL (unix standard)`, - `\n - EDITOR (unix standard)`, - `\n\nAlternatively, use the --editor flag.`, + chalk.magenta(`\n\nEditor not defined.`), + `\n\nYou should set an editor using any of the following ENV variables:\n`, + `\n - ${chalk.blue(`BUD_EDITOR`)} (bud specific; preferred)`, + `\n - ${chalk.blue(`VISUAL`)} (unix standard)`, + `\n - ${chalk.blue(`EDITOR`)} (unix standard)`, + `\n\nAlternatively, use the ${chalk.blue(`--editor`)} flag.`, ) } - return ( - this.app.context.editor === true || // if true, fall back to default behavior - typeof this.app.context.editor === `string` // if string, opens in that editor - ) + + return enabled } } diff --git a/yarn.lock b/yarn.lock index 02bb89a199..88d5b53bca 100644 --- a/yarn.lock +++ b/yarn.lock @@ -15302,9 +15302,9 @@ __metadata: linkType: hard "caniuse-lite@npm:^1.0.0, caniuse-lite@npm:^1.0.30001464, caniuse-lite@npm:^1.0.30001503": - version: 1.0.30001516 - resolution: "caniuse-lite@npm:1.0.30001516" - checksum: 044adf3493b734a356a2922445a30095a0f6de6b9194695cdf74deafe7bef658e85858a31177762c2813f6e1ed2722d832d59eee0ecb2151e93a611ee18cb21f + version: 1.0.30001517 + resolution: "caniuse-lite@npm:1.0.30001517" + checksum: e4e87436ae1c4408cf4438aac22902b31eb03f3f5bad7f33bc518d12ffb35f3fd9395ccf7efc608ee046f90ce324ec6f7f26f8a8172b8c43c26a06ecee612a29 languageName: node linkType: hard