Having trouble compiling sass / scss after adding vuetify

Hello everyone,

I have been trying to build a Sage theme using Sage 10 + Bud 6.12.3, Vue 3 and Vuetify. So far I’ve managed to get past most of the issues that i’ve encountered so far, but the one error that I’ve had no success rectifying is that when i add my app.scss file to the entry (or import it using app.js, I get the following error:

│ Module parse failed: Unexpected token (1:5)
│ You may need an appropriate loader to handle this file type, currently no loaders are configured to process this file.
See https://webpack.js.org/concepts#loaders
│ > body {
│ |     background-color: #{var(--theme-color-background)};

Here is my bud.config.js file:

/**
 * Compiler configuration
 *
 * @see {@link https://roots.io/docs/sage sage documentation}
 * @see {@link https://bud.js.org/guides/configure bud.js configuration guide}
 *
 * @param {import('@roots/bud').Bud} app
 */
import { VuetifyPlugin } from 'webpack-plugin-vuetify'
import { readdirSync, statSync } from 'fs'

export default async (app) => {
  /**
   * Add some babel presets
   */
  app.babel.setPreset('@vue/cli-plugin-babel/preset')
  /**
   * Application assets & entrypoints
   *
   * @see {@link https://bud.js.org/docs/bud.entry}
   * @see {@link https://bud.js.org/docs/bud.assets}
   */
  app
    .entry('app', ['@scripts/app', '@styles/app'])
    .entry('editor', ['@scripts/editor', '@styles/editor'])
    .assets(['images']);

  app.postcss.setPlugin('postcss-scss')
  /**
   * Static assets, especially those used as defaults
   */
  app.alias('@static', app.path('@src/static'))
  const files = readdirSync(app.path('@src/static'))
  files.forEach(file => {
    const budPath = `static/${file}`
    const path = app.path(`@src/static/${file}`)
    const stats = statSync(path)
    if (stats.isFile()) {
      app.copyFile(budPath)
    } else if (stats.isDirectory()) {
      app.copyDirectory(budPath)
    }
  })

  /**
   * Add Vuetify
   * 
   * @see {@link https://bud.js.org/docs/bud.use#usage}
   * @see {@link https://www.npmjs.com/package/webpack-plugin-vuetify}
   * @see {@link https://discourse.roots.io/t/fontsource-error-unexpected-character/22629/2}
   */
  app.compilePaths([app.path('@modules/vuetify'), app.path('@modules/@mdi/font')])
  app.hooks.action(`build.before`, async app => {
    app.build.rules.css.use.push('sass')
    app.build.rules.cssModule.use.push('sass')
    app.build.rules.sass.test = /\.s[ac]ss$/i
  })
  app.config( existing => {
    existing.plugins.push(
      new VuetifyPlugin({
        autoImport: true,
        styles: 'sass',
      })
    )
    return existing
  })
  app.sass.importGlobal([
    'vuetify/styles',
    'vuetify/lib/components/VApp/VApp',
    'vuetify/lib/components/VMain/VMain',
    'vuetify/lib/components/VGrid/VGrid',
    'vuetify/lib/components/VCard/VCard',
    'vuetify/lib/components/VAvatar/VAvatar',
    'vuetify/lib/components/VIcon/VIcon',
    'vuetify/lib/components/VDivider/VDivider',
  ])

  /**
   * Set public path
   *
   * @see {@link https://bud.js.org/docs/bud.setPublicPath}
   */
  app.setPublicPath('/app/themes/sage/public/');

  /**
   * Vue Confiugration
   * 
   * @see {@link https://budjs.org/extensions/bud-vue}
   */
  app.vue.set('runtimeOnly', false);

  /**
   * Development server settings
   *
   * @see {@link https://bud.js.org/docs/bud.setUrl}
   * @see {@link https://bud.js.org/docs/bud.setProxyUrl}
   * @see {@link https://bud.js.org/docs/bud.watch}
   */
  app
    .setUrl('http://localhost:3000')
    .setProxyUrl('http://web.test')
    .watch(['resources/views', 'app']);

  /**
   * Generate WordPress `theme.json`
   *
   * @note This overwrites `theme.json` on every build.
   *
   * @see {@link https://bud.js.org/extensions/sage/theme.json}
   * @see {@link https://developer.wordpress.org/block-editor/how-to-guides/themes/theme-json}
   */
  app.wpjson
    .set('settings.color.custom', false)
    .set('settings.color.customDuotone', false)
    .set('settings.color.customGradient', false)
    .set('settings.color.defaultDuotone', false)
    .set('settings.color.defaultGradients', false)
    .set('settings.color.defaultPalette', false)
    .set('settings.color.duotone', [])
    .set('settings.custom.spacing', {})
    .set('settings.custom.typography.font-size', {})
    .set('settings.custom.typography.line-height', {})
    .set('settings.spacing.padding', true)
    .set('settings.spacing.units', ['px', '%', 'em', 'rem', 'vw', 'vh'])
    .set('settings.typography.customFontSize', false)
    .enable();
};

Here is my package.json:

{
  "name": "sage",
  "private": true,
  "browserslist": [
    "extends @roots/browserslist-config"
  ],
  "engines": {
    "node": ">=16.0.0"
  },
  "type": "module",
  "scripts": {
    "dev": "bud dev",
    "build": "bud build",
    "watch": "nodemon --exec \"bud build --notify\"",
    "translate": "yarn translate:pot && yarn translate:update",
    "translate:pot": "wp i18n make-pot . ./resources/lang/sage.pot --include=\"app,resources\"",
    "translate:update": "wp i18n update-po ./resources/lang/sage.pot ./resources/lang/*.po",
    "translate:compile": "yarn translate:mo && yarn translate:js",
    "translate:js": "wp i18n make-json ./resources/lang --pretty-print",
    "translate:mo": "wp i18n make-mo ./resources/lang ./resources/lang",
    "populate:google:fonts": "node ./bin/populateGoogleFonts.cjs"
  },
  "devDependencies": {
    "@mdi/font": "^7.2.96",
    "@roots/bud": "^6.12.3",
    "@roots/bud-babel": "^6.12.3",
    "@roots/bud-imagemin": "^6.12.3",
    "@roots/bud-postcss": "^6.12.3",
    "@roots/bud-sass": "^6.12.3",
    "@roots/bud-vue": "^6.12.3",
    "@roots/sage": "^6.12.3",
    "@vue/cli-plugin-babel": "^5.0.8",
    "@vue/compiler-sfc": "^3.3.4",
    "@vue/runtime-dom": "^3.3.4",
    "axios": "^1.4.0",
    "caniuse-lite": "^1.0.30001489",
    "deepmerge": "^4.3.1",
    "nodemon": "^2.0.22",
    "postcss-scss": "^4.0.6",
    "sass": "^1.62.1",
    "sass-loader": "^13.3.0",
    "vue": "^3.3.4",
    "vuetify": "^3.3.1",
    "webpack-plugin-vuetify": "^2.0.1"
  },
  "nodemonConfig": {
    "watch": [
      "resources/**/*",
      "bud.config.js",
      "package.json"
    ],
    "ext": "js,scss,json,vue"
  }
}

Message continues in the comment below due to character limit limitations.

Here’s the result of running util.inspect on app.build.rules:
(I’ve replaced the real directory on my computer with <root> for privacy reasons)

{
  toml: Rule {
    _app: [Function (anonymous)],
    type: 'json',
    include: [ [Function (anonymous)] ],
    test: [Function (anonymous)],
    parser: [Function (anonymous)]
  },
  xml: Rule {
    _app: [Function (anonymous)],
    include: [ [Function (anonymous)] ],
    test: [Function (anonymous)],
    use: [ 'xml' ]
  },
  csv: Rule {
    _app: [Function (anonymous)],
    include: [
      '<root>'
    ],
    test: [Function (anonymous)],
    use: [ 'csv' ]
  },
  yml: Rule {
    _app: [Function (anonymous)],
    test: [Function (anonymous)],
    include: [ [Function (anonymous)] ],
    use: [ 'yml' ]
  },
  html: Rule {
    _app: [Function (anonymous)],
    include: [ [Function (anonymous)] ],
    test: /\.(html?)$/,
    use: [ 'html' ]
  },
  json: Rule {
    _app: [Function (anonymous)],
    type: 'json',
    include: [ [Function (anonymous)] ],
    test: /\.json$/,
    parser: [Function (anonymous)]
  },
  font: Rule {
    _app: [Function (anonymous)],
    type: 'asset',
    test: /\.(ttf|otf|eot|woff2?|ico)$/,
    include: [ [Function (anonymous)] ]
  },
  svg: Rule {
    _app: [Function (anonymous)],
    test: /\.svg$/,
    include: [ [Function (anonymous)] ],
    type: 'asset/resource'
  },
  webp: Rule {
    _app: [Function (anonymous)],
    test: /\.webp$/,
    include: [ [Function (anonymous)] ],
    type: 'asset/resource'
  },
  image: Rule {
    _app: [Function (anonymous)],
    test: /\.(png|jpe?g|gif|webp)$/,
    include: [ [Function (anonymous)] ],
    type: 'asset/resource'
  },
  inlineFont: Rule {
    _app: [Function (anonymous)],
    test: /\.(ttf|otf|eot|woff2?|ico)$/,
    include: [ [Function (anonymous)] ],
    resourceQuery: /inline/,
    type: 'asset/inline'
  },
  inlineSvg: Rule {
    _app: [Function (anonymous)],
    test: /\.svg$/,
    include: [ [Function (anonymous)] ],
    resourceQuery: /inline/,
    generator: [Function (anonymous)],
    type: 'asset/inline'
  },
  inlineImage: Rule {
    _app: [Function (anonymous)],
    test: /\.(png|jpe?g|gif|webp)$/,
    include: [ [Function (anonymous)] ],
    type: 'asset/inline',
    resourceQuery: /inline/
  },
  cssModule: Rule {
    _app: [Function (anonymous)],
    test: /\.module\.css$/,
    include: [ [Function (anonymous)] ],
    use: [ 'precss', 'cssModule', 'postcss', 'sass' ]
  },
  css: Rule {
    _app: [Function (anonymous)],
    test: /(?!.*\.module)\.css$/,
    include: [ [Function (anonymous)] ],
    use: [ 'precss', 'css', 'postcss', 'sass' ]
  },
  js: Rule {
    _app: [Function (anonymous)],
    test: /\.(mjs|jsx?)$/,
    include: [ [Function (anonymous)] ],
    use: [
      Item {
        _app: [Function (anonymous)],
        loader: 'babel',
        ident: 'babel',
        options: [Function: options]
      }
    ]
  },
  sass: Rule {
    _app: [Function (anonymous)],
    test: /\.s[ac]ss$/i,
    use: [
      Item {
        _app: [Function (anonymous)],
        ident: 'minicss',
        loader: 'minicss',
        options: [Function (anonymous)]
      },
      Item {
        _app: [Function (anonymous)],
        ident: 'css',
        loader: 'css',
        options: [Function (anonymous)]
      },
      Item {
        _app: [Function (anonymous)],
        loader: 'postcss',
        ident: 'postcss',
        options: [Function: options]
      },
      Item {
        _app: [Function (anonymous)],
        loader: 'resolveUrl',
        ident: 'resolveUrl',
        options: [Function: options]
      },
      Item {
        _app: [Function (anonymous)],
        ident: 'sass',
        loader: 'sass-loader',
        options: [Function: options]
      }
    ],
    include: [ [Function (anonymous)] ],
    exclude: undefined,
    type: undefined,
    parser: [Function (anonymous)],
    generator: [Function (anonymous)]
  }
}

Message continues below due to character limit limitations.

And here is the result of running util.inspect on the config object passed to the callback of app.config (app.config(c => console.log(util.inspect(c, false, 20, false))):
(I’ve replaced the absolute path to the root of the theme with <root> for privacy reasons)

{
  bail: true,
  cache: {
    name: 'webpack/production',
    type: 'filesystem',
    store: 'pack',
    allowCollectingMemory: true,
    buildDependencies: {
      bud: [
        '<root>/package.json',
        '<root>/bud.config.js'
      ]
    },
    cacheDirectory:
'/Users/<redacted>/Library/Caches/bud-nodejs/pSfYjEU80AODRN7b21L8/FV6zE0=/sage/cache',
    idleTimeout: 10000,
    idleTimeoutForInitialStore: 0,
    profile: false,
    version: 'uZ3yt0OPjkLe0ZEKn4Ylw3BGkFY='
  },
  context: '<root>/resources',
  dependencies: [],
  devtool: false,
  entry: {
    app: { import: [ '@scripts/app', '@styles/app' ] },
    editor: { import: [ '@scripts/editor', '@styles/editor' ] }
  },
  externalsType: 'var',
  infrastructureLogging: {
    console: {
      log: [Function: log],
      warn: [Function: warn],
      dir: [Function: dir],
      time: [Function: time],
      timeEnd: [Function: timeEnd],
      timeLog: [Function: timeLog],
      trace: [Function: trace],
      assert: [Function: assert],
      clear: [Function: clear],
      count: [Function: count],
      countReset: [Function: countReset],
      group: [Function: group],
      groupEnd: [Function: groupEnd],
      table: [Function: table],
      debug: [Function: debug],
      info: [Function: info],
      dirxml: [Function: dirxml],
      error: [Function: error],
      groupCollapsed: [Function: groupCollapsed],
      Console: [Function: Console],
      profile: [Function: profile],
      profileEnd: [Function: profileEnd],
      timeStamp: [Function: timeStamp],
      context: [Function: context]
    },
    level: 'log'
  },
  mode: 'production',
  module: {
    ... // see next comment due to character limitations
  },
  output: {
    assetModuleFilename: '[path][name].[contenthash:6][ext]',
    chunkFilename: 'js/dynamic/[name].[contenthash:6].chunk.js',
    clean: true,
    environment: undefined,
    filename: 'js/[name].[contenthash:6].js',
    module: false,
    path: '<root>/public',
    pathinfo: undefined,
    publicPath: '/app/themes/sage/public/',
    scriptType: false,
    uniqueName: '@roots/bud/sage/sage'
  },
  parallelism: 70,
  performance: { hints: false },
  recordsPath: '/Users/<redacted>/Library/Caches/bud-nodejs/pSfYjEU80AODRN7b21L8/FV6zE0
=/sage/modules.json',
  resolveLoader: { alias: {} },
  snapshot: {
    managedPaths: [
      '<root>/node_modules'
    ]
  },
  stats: {
    all: false,
    assets: true,
    children: false,
    entrypoints: true,
    errors: true,
    errorsCount: true,
    hash: true,
    outputPath: true,
    modules: true,
    timings: true,
    warnings: true,
    warningsCount: true
  },
  target: 'browserslist:<root>/package.json',
  plugins: [
    MiniCssExtractPlugin {
      _sortedModulesCache: WeakMap { <items unknown> },
      options: {
        filename: 'css/[name].[contenthash:6].css',
        ignoreOrder: false,
        experimentalUseImportModule: undefined,
        runtime: true,
        chunkFilename: 'css/[name].[contenthash:6].css'
      },
      runtimeOptions: {
        insert: undefined,
        linkType: 'text/css',
        attributes: undefined
      }
    },
    FixStyleOnlyEntrypoints {},
    CleanWebpackPlugin {
      dangerouslyAllowCleanPatternsOutsideProject: false,
      dry: false,
      verbose: false,
      cleanStaleWebpackAssets: true,
      protectWebpackAssets: true,
      cleanAfterEveryBuildPatterns: [],
      cleanOnceBeforeBuildPatterns: [ '**/*' ],
      currentAssets: [],
      initialClean: false,
      outputPath: '',
      apply: [Function: bound apply],
      handleInitial: [Function: bound handleInitial],
      handleDone: [Function: bound handleDone],
      removeFiles: [Function: bound removeFiles]
    },
    CopyPlugin {
      patterns: [
        {
          from: 'images/**/*',
          to: '[path][name].[contenthash:6][ext]',
          context: '<root>/resources',
          noErrorOnMissing: true,
          globOptions: { dot: false }
        },
        {
          from: 'static/default-icon.png',
          to: 'static/[path][name].[contenthash:6][ext]',
          context: '<root>/resources'
        },
        {
          from: 'static/default-logo.png',
          to: 'static/[path][name].[contenthash:6][ext]',
          context: '<root>/resources'
        }
      ],
      options: {}
    },
    WebpackManifestPlugin {
      options: {
        assetHookStage: Infinity,
        basePath: '',
        fileName: 'manifest.json',
        filter: null,
        generate: [Function (anonymous)],
        map: null,
        publicPath: '',
        removeKeyHash: /([a-f0-9]{16,32}\.?)/gi,
        seed: undefined,
        serialize: [Function: serialize],
        sort: null,
        transformExtensions: /^(gz|map)$/i,
        useEntryKeys: false,
        useLegacyEmit: false,
        writeToFileEmit: true
      }
    },
    EntrypointsWebpackPlugin {
      options: { emitHtml: false, publicPath: '' },
      plugin: { name: 'EntrypointsManifestPlugin', stage: Infinity },
      name: 'entrypoints.json'
    },
    BladeWebpackPlugin { options: {} },
    ThemeJsonWebpackPlugin {
      options: {
        path: '<root>/theme.json',
        settings: {
          color: {
            custom: false,
            customGradient: false,
            customDuotone: false,
            defaultDuotone: false,
            defaultGradients: false,
            defaultPalette: false,
            duotone: []
          },
          custom: {
            spacing: {},
            typography: { 'font-size': {}, 'line-height': {} }
          },
          spacing: {
            padding: true,
            units: [ 'px', '%', 'em', 'rem', 'vw', 'vh' ]
          },
          typography: { customFontSize: false, dropCap: false }
        }
      }
    },
    WordPressDependenciesWebpackPlugin {
      plugin: { name: 'WordPressDependenciesWebpackPlugin', stage: Infinity },
      manifest: {},
      usedDependencies: {},
      fileName: 'wordpress.json'
    },
    WordPressExternals {
      name: 'WordPressExternalsWebpackPlugin',
      stage: Infinity,
      externals: ExternalsPlugin {
        type: 'window',
        externals: [Function: externals]
      }
    },
    MergedManifestWebpackPlugin {
      plugin: { name: 'MergedManifestPlugin' },
      file: 'entrypoints.json',
      entrypointsName: 'entrypoints.json',
      wordpressName: 'wordpress.json'
    },
    VuetifyPlugin {
      options: { autoImport: true, styles: 'sass', stylesTimeout: 10000 }
    }
  ],
  resolve: {
    alias: {
      '@src': '<root>/resources',
      '@fonts': '<root>/resources/fonts',
      '@images': '<root>/resources/images',
      '@scripts': '<root>/resources/scripts',
      '@styles': '<root>/resources/styles',
      '@views': '<root>/resources/views',
      '@static': '<root>/resources/static',
      vue: 'vue/dist/vue.esm-bundler.js'
    },
    extensions: [
      '.mjs',  '.js',
      '.jsx',  '.css',
      '.json', '.wasm',
      '.yml',  '.yaml',
      '.xml',  '.toml',
      '.csv',  '.scss',
      '.sass', '.blade.php',
      '.vue'
    ],
    modules: [ 'resources', 'node_modules' ]
  }
}

Message (and contents of module) in next comment due to character restrictions

module: {
  noParse: undefined,
    rules: [
      {
        test: /\.(mjs|jsx?)$/,
        include: [
          '<root>/resources'
        ],
        parser: { requireEnsure: false }
      },
      {
        oneOf: [
          {
            test: /\.toml$/,
            type: 'json',
            parser: { parse: [Function: parse] },
            include: [
              '<root>/node_modules/vuetify',
              '<root>/node_modules/@mdi/font'
            ]
          },
          {
            test: /\.xml$/,
            use: [
              {
                loader:
'<root>/node_modules/@roots/bud-support/lib/xml-loader/index.cjs',
                ident: 'xml'
              }
            ],
            include: [
              '<root>/node_modules/vuetify',
              '<root>/node_modules/@mdi/font'
            ]
          },
          {
            test: /\.(csv|tsv)$/,
            use: [
              {
                loader:
'<root>/node_modules/@roots/bud-support/lib/csv-loader/index.cjs',
                ident: 'csv'
              }
            ],
            include: [
              '<root>/node_modules/vuetify',
              '<root>/node_modules/@mdi/font'
            ]
          },
          {
            test: /\.ya?ml$/,
            use: [
              {
                loader:
'<root>/node_modules/@roots/bud-support/lib/yml-loader/index.cjs',
                ident: 'yml'
              }
            ],
            include: [
              '<root>/node_modules/vuetify',
              '<root>/node_modules/@mdi/font'
            ]
          },
          {
            test: /\.(html?)$/,
            use: [
              {
                loader:
'<root>/node_modules/@roots/bud-support/lib/html-loader/index.cjs',
                ident: 'html'
              }
            ],
            include: [
              '<root>/node_modules/vuetify',
              '<root>/node_modules/@mdi/font'
            ]
          },
          {
            test: /\.json$/,
            type: 'json',
            parser: { parse: undefined },
            include: [
              '<root>/node_modules/vuetify',
              '<root>/node_modules/@mdi/font'
            ]
          },
          {
            test: /\.(ttf|otf|eot|woff2?|ico)$/,
            type: 'asset',
            include: [
              '<root>/node_modules/vuetify',
              '<root>/node_modules/@mdi/font'
            ]
          },
          {
            test: /\.svg$/,
            type: 'asset/resource',
            include: [
              '<root>/node_modules/vuetify',
              '<root>/node_modules/@mdi/font'
            ]
          },
          {
            test: /\.webp$/,
            type: 'asset/resource',
            include: [
              '<root>/node_modules/vuetify',
              '<root>/node_modules/@mdi/font'
            ]
          },
          {
            test: /\.(png|jpe?g|gif|webp)$/,
            type: 'asset/resource',
            include: [
              '<root>/node_modules/vuetify',
              '<root>/node_modules/@mdi/font'
            ]
          },
          {
            test: /\.(ttf|otf|eot|woff2?|ico)$/,
            type: 'asset/inline',
            resourceQuery: /inline/,
            include: [
              '<root>/node_modules/vuetify',
              '<root>/node_modules/@mdi/font'
            ]
          },
          {
            test: /\.svg$/,
            type: 'asset/inline',
            generator: { dataUrl: [Function: dataUrl] },
            resourceQuery: /inline/,
            include: [
              '<root>/node_modules/vuetify',
              '<root>/node_modules/@mdi/font'
            ]
          },
          {
            test: /\.(png|jpe?g|gif|webp)$/,
            type: 'asset/inline',
            resourceQuery: /inline/,
            include: [
              '<root>/node_modules/vuetify',
              '<root>/node_modules/@mdi/font'
            ]
          },
          {
            test: /\.module\.css$/,
            use: [
              {
                loader:
'<root>/node_modules/mini-css-extract-plugin/dist/loader.js',
                options: { esModule: false },
                ident: 'minicss'
              },
              {
                loader:
'<root>/node_modules/@roots/bud-support/lib/css-loader/index.cjs',
                options: { importLoaders: 2, modules: true, sourceMap: false },
                ident: 'cssModule'
              },
              {
                loader: '<root>/node_modules/postcss-loader/dist/cjs.js',
                options: {
                  postcssOptions: {
                    config: false,
                    syntax: 'postcss-scss',
                    parser: undefined,
                    plugins: [
                      '<root>/node_modules/postcss-import/index.js',
                      '<root>/node_modules/postcss-nested/index.js',
                      [ 'postcss-scss' ],
                      [
                        '<root>/node_modules/postcss-preset-env/dist/index.cjs',
                        {
                          stage: 1,
                          features: { 'focus-within-pseudo-class': false }
                        }
                      ]
                    ]
                  },
                  sourceMap: undefined
                },
                ident: 'postcss'
              },
              {
                loader: '<root>/node_modules/sass-loader/dist/cjs.js',
                options: {
                  implementation: {
                    load: [Function (anonymous)],
                    compile: [Function: sass.compile],
                    compileString: [Function: sass.compileString],
                    compileAsync: [Function: sass.compileAsync],
                    compileStringAsync: [Function: sass.compileStringAsync],
                    Value: [Function: Value0],
                    SassBoolean: [Function: sass.SassBoolean],
                    SassArgumentList: [Function: sass.SassArgumentList],
                    SassColor: [Function: sass.SassColor],
                    SassFunction: [Function: sass.SassFunction],
                    SassList: [Function: sass.SassList],
                    SassMap: [Function: sass.SassMap],
                    SassNumber: [Function: sass.SassNumber],
                    SassString: [Function: sass.SassString],
                    sassNull: null,
                    sassTrue: true,
                    sassFalse: false,
                    Exception: [class sass.Exception extends Error],
                    Logger: {
                      silent: {
                        warn: [Function: sass.Logger.silent.warn],
                        debug: [Function: sass.Logger.silent.debug]
                      }
                    },
                    info: 'dart-sass\t1.62.1\t(Sass Compiler)\t[Dart]\n' +
                      'dart2js\t2.19.6\t(Dart Compiler)\t[Dart]',
                    render: [Function: sass.render],
                    renderSync: [Function: sass.renderSync],
                    types: {
                      Boolean: [Function: sass.types.Boolean] {
                        TRUE: true,
                        FALSE: false
                      },
                      Color: [Function: sass.types.Color],
                      List: [Function: sass.types.List],
                      Map: [Function: sass.types.Map],
                      Null: [Function: sass.types.Null] { NULL: null },
                      Number: [Function: sass.types.Number],
                      String: [Function: sass.types.String],
                      Error: [Function: Error] { stackTraceLimit: 10 }
                    },
                    NULL: null,
                    TRUE: true,
                    FALSE: false,
                    cli_pkg_main_0_: <ref *1> [Function (anonymous)] {
                      '___dart__$dart_dartClosure_ZxYxX_0_': _wrapMain_closure0
{
                        main: StaticClosure {
                          '$initialize': [Function: StaticClosure],
                          constructor: [Function: static_tear_off],
                          '$_name': 'main0',
                          '$_target': [Function: main0],
                          '$static_name': 'main0',
                          '$signature': 599,
                          'call$1': [Function: main0],
                          'call*': [Function: main0],
                          '$requiredArgCount': 1,
                          '$defaultValues': null
                        },
                        '$dart_jsFunction': [Circular *1]
                      }
                    }
                  },
                  sourceMap: true,
                  additionalData: '@import "vuetify/styles";\n' +
                    '@import "vuetify/lib/components/VApp/VApp";\n' +
                    '@import "vuetify/lib/components/VMain/VMain";\n' +
                    '@import "vuetify/lib/components/VGrid/VGrid";\n' +
                    '@import "vuetify/lib/components/VCard/VCard";\n' +
                    '@import "vuetify/lib/components/VAvatar/VAvatar";\n' +
                    '@import "vuetify/lib/components/VIcon/VIcon";\n' +
                    '@import "vuetify/lib/components/VDivider/VDivider";'
                },
                ident: 'sass'
              }
            ],
            include: [
              '<root>/node_modules/vuetify',
              '<root>/node_modules/@mdi/font'
            ]
          },
          {
            test: /(?!.*\.module)\.css$/,
            use: [
              {
                loader: '<root>/node_modules/vue-style-loader/index.js',
                ident: 'vue-style'
              },
              {
                loader:
'<root>/node_modules/mini-css-extract-plugin/dist/loader.js',
                options: { esModule: false },
                ident: 'minicss'
              },
              {
                loader:
'<root>/node_modules/@roots/bud-support/lib/css-loader/index.cjs',
                options: { importLoaders: 3, modules: false, sourceMap: false },
                ident: 'css'
              },
              {
                loader: '<root>/node_modules/postcss-loader/dist/cjs.js',
                options: {
                  postcssOptions: {
                    config: false,
                    syntax: 'postcss-scss',
                    parser: undefined,
                    plugins: [
                      '<root>/node_modules/postcss-import/index.js',
                      '<root>/node_modules/postcss-nested/index.js',
                      [ 'postcss-scss' ],
                      [
                        '<root>/node_modules/postcss-preset-env/dist/index.cjs',
                        {
                          stage: 1,
                          features: { 'focus-within-pseudo-class': false }
                        }
                      ]
                    ]
                  },
                  sourceMap: undefined
                },
                ident: 'postcss'
              },
              {
                loader: '<root>/node_modules/sass-loader/dist/cjs.js',
                options: {
                  implementation: {
                    load: [Function (anonymous)],
                    compile: [Function: sass.compile],
                    compileString: [Function: sass.compileString],
                    compileAsync: [Function: sass.compileAsync],
                    compileStringAsync: [Function: sass.compileStringAsync],
                    Value: [Function: Value0],
                    SassBoolean: [Function: sass.SassBoolean],
                    SassArgumentList: [Function: sass.SassArgumentList],
                    SassColor: [Function: sass.SassColor],
                    SassFunction: [Function: sass.SassFunction],
                    SassList: [Function: sass.SassList],
                    SassMap: [Function: sass.SassMap],
                    SassNumber: [Function: sass.SassNumber],
                    SassString: [Function: sass.SassString],
                    sassNull: null,
                    sassTrue: true,
                    sassFalse: false,
                    Exception: [class sass.Exception extends Error],
                    Logger: {
                      silent: {
                        warn: [Function: sass.Logger.silent.warn],
                        debug: [Function: sass.Logger.silent.debug]
                      }
                    },
                    info: 'dart-sass\t1.62.1\t(Sass Compiler)\t[Dart]\n' +
                      'dart2js\t2.19.6\t(Dart Compiler)\t[Dart]',
                    render: [Function: sass.render],
                    renderSync: [Function: sass.renderSync],
                    types: {
                      Boolean: [Function: sass.types.Boolean] {
                        TRUE: true,
                        FALSE: false
                      },
                      Color: [Function: sass.types.Color],
                      List: [Function: sass.types.List],
                      Map: [Function: sass.types.Map],
                      Null: [Function: sass.types.Null] { NULL: null },
                      Number: [Function: sass.types.Number],
                      String: [Function: sass.types.String],
                      Error: [Function: Error] { stackTraceLimit: 10 }
                    },
                    NULL: null,
                    TRUE: true,
                    FALSE: false,
                    cli_pkg_main_0_: <ref *1> [Function (anonymous)] {
                      '___dart__$dart_dartClosure_ZxYxX_0_': _wrapMain_closure0
{
                        main: StaticClosure {
                          '$initialize': [Function: StaticClosure],
                          constructor: [Function: static_tear_off],
                          '$_name': 'main0',
                          '$_target': [Function: main0],
                          '$static_name': 'main0',
                          '$signature': 599,
                          'call$1': [Function: main0],
                          'call*': [Function: main0],
                          '$requiredArgCount': 1,
                          '$defaultValues': null
                        },
                        '$dart_jsFunction': [Circular *1]
                      }
                    }
                  },
                  sourceMap: true,
                  additionalData: '@import "vuetify/styles";\n' +
                    '@import "vuetify/lib/components/VApp/VApp";\n' +
                    '@import "vuetify/lib/components/VMain/VMain";\n' +
                    '@import "vuetify/lib/components/VGrid/VGrid";\n' +
                    '@import "vuetify/lib/components/VCard/VCard";\n' +
                    '@import "vuetify/lib/components/VAvatar/VAvatar";\n' +
                    '@import "vuetify/lib/components/VIcon/VIcon";\n' +
                    '@import "vuetify/lib/components/VDivider/VDivider";'
                },
                ident: 'sass'
              }
            ],
            include: [
              '<root>/node_modules/vuetify',
              '<root>/node_modules/@mdi/font'
            ]
          },
          {
            test: /\.(mjs|jsx?)$/,
            use: [
              {
                loader: '<root>/node_modules/babel-loader/lib/index.js',
                options: {
                  cacheIdentifier: 'babel',
                  cacheDirectory: '/Users/jak/Library/Caches/bud-nodejs/pSfYjEU8
0AODRN7b21L8/FV6zE0=/sage/cache/babel',
                  configFile: false,
                  presets: [
                    [
                      '<root>/node_modules/@babel/preset-env/lib/index.js'
                    ],
                    [
                      '<root>/node_modules/@babel/preset-react/lib/index.js'
                    ],
                    [ '@vue/cli-plugin-babel/preset' ]
                  ],
                  plugins: [
                    [

'<root>/node_modules/@babel/plugin-transform-runtime/lib/index.js',
                      { helpers: false }
                    ]
                  ],
                  env: { development: { compact: false } },
                  root: '<root>',
                  targets: [ 'extends @roots/browserslist-config' ]
                },
                ident: 'babel'
              },
              {
                loader:
'<root>/node_modules/@roots/wordpress-hmr/lib/loader.cjs',
                options: { notify: true },
                ident: '@roots/wordpress-hmr/loader'
              }
            ],
            include: [
              '<root>/node_modules/vuetify',
              '<root>/node_modules/@mdi/font'
            ]
          },
          {
            test: /\.s[ac]ss$/i,
            use: [
              {
                loader: '<root>/node_modules/vue-style-loader/index.js',
                ident: 'vue-style'
              },
              {
                loader:
'<root>/node_modules/mini-css-extract-plugin/dist/loader.js',
                options: { esModule: false },
                ident: 'minicss'
              },
              {
                loader:
'<root>/node_modules/@roots/bud-support/lib/css-loader/index.cjs',
                options: { importLoaders: 3, modules: false, sourceMap: false },
                ident: 'css'
              },
              {
                loader: '<root>/node_modules/postcss-loader/dist/cjs.js',
                options: {
                  postcssOptions: {
                    config: false,
                    syntax: 'postcss-scss',
                    parser: undefined,
                    plugins: [
                      '<root>/node_modules/postcss-import/index.js',
                      '<root>/node_modules/postcss-nested/index.js',
                      [ 'postcss-scss' ],
                      [
                        '<root>/node_modules/postcss-preset-env/dist/index.cjs',
                        {
                          stage: 1,
                          features: { 'focus-within-pseudo-class': false }
                        }
                      ]
                    ]
                  },
                  sourceMap: undefined
                },
                ident: 'postcss'
              },
              {
                loader: '<root>/node_modules/resolve-url-loader/index.js',
                options: {
                  root: '<root>/resources',
                  sourceMap: true
                },
                ident: 'resolveUrl'
              },
              {
                loader: '<root>/node_modules/sass-loader/dist/cjs.js',
                options: {
                  implementation: {
                    load: [Function (anonymous)],
                    compile: [Function: sass.compile],
                    compileString: [Function: sass.compileString],
                    compileAsync: [Function: sass.compileAsync],
                    compileStringAsync: [Function: sass.compileStringAsync],
                    Value: [Function: Value0],
                    SassBoolean: [Function: sass.SassBoolean],
                    SassArgumentList: [Function: sass.SassArgumentList],
                    SassColor: [Function: sass.SassColor],
                    SassFunction: [Function: sass.SassFunction],
                    SassList: [Function: sass.SassList],
                    SassMap: [Function: sass.SassMap],
                    SassNumber: [Function: sass.SassNumber],
                    SassString: [Function: sass.SassString],
                    sassNull: null,
                    sassTrue: true,
                    sassFalse: false,
                    Exception: [class sass.Exception extends Error],
                    Logger: {
                      silent: {
                        warn: [Function: sass.Logger.silent.warn],
                        debug: [Function: sass.Logger.silent.debug]
                      }
                    },
                    info: 'dart-sass\t1.62.1\t(Sass Compiler)\t[Dart]\n' +
                      'dart2js\t2.19.6\t(Dart Compiler)\t[Dart]',
                    render: [Function: sass.render],
                    renderSync: [Function: sass.renderSync],
                    types: {
                      Boolean: [Function: sass.types.Boolean] {
                        TRUE: true,
                        FALSE: false
                      },
                      Color: [Function: sass.types.Color],
                      List: [Function: sass.types.List],
                      Map: [Function: sass.types.Map],
                      Null: [Function: sass.types.Null] { NULL: null },
                      Number: [Function: sass.types.Number],
                      String: [Function: sass.types.String],
                      Error: [Function: Error] { stackTraceLimit: 10 }
                    },
                    NULL: null,
                    TRUE: true,
                    FALSE: false,
                    cli_pkg_main_0_: <ref *1> [Function (anonymous)] {
                      '___dart__$dart_dartClosure_ZxYxX_0_': _wrapMain_closure0
{
                        main: StaticClosure {
                          '$initialize': [Function: StaticClosure],
                          constructor: [Function: static_tear_off],
                          '$_name': 'main0',
                          '$_target': [Function: main0],
                          '$static_name': 'main0',
                          '$signature': 599,
                          'call$1': [Function: main0],
                          'call*': [Function: main0],
                          '$requiredArgCount': 1,
                          '$defaultValues': null
                        },
                        '$dart_jsFunction': [Circular *1]
                      }
                    }
                  },
                  sourceMap: true,
                  additionalData: '@import "vuetify/styles";\n' +
                    '@import "vuetify/lib/components/VApp/VApp";\n' +
                    '@import "vuetify/lib/components/VMain/VMain";\n' +
                    '@import "vuetify/lib/components/VGrid/VGrid";\n' +
                    '@import "vuetify/lib/components/VCard/VCard";\n' +
                    '@import "vuetify/lib/components/VAvatar/VAvatar";\n' +
                    '@import "vuetify/lib/components/VIcon/VIcon";\n' +
                    '@import "vuetify/lib/components/VDivider/VDivider";'
                },
                ident: 'sass'
              }
            ],
            include: [
              '<root>/node_modules/vuetify',
              '<root>/node_modules/@mdi/font'
            ]
          }
        ]
      }
    ],
    unsafeCache: false
  },
  name: 'sage',
  node: false,
  optimization: {
    emitOnErrors: false,
    minimize: true,
    minimizer: [
      ImageMinimizerPlugin {
        options: {
          minimizer: {
            implementation: [Function: sharpMinify],
            options: { encodeOptions: {} }
          },
          generator: undefined,
          severityError: undefined,
          exclude: undefined,
          include: undefined,
          loader: true,
          concurrency: undefined,
          test: /\.(png|jpe?g|gif|webp)$/,
          deleteOriginalAssets: true
        }
      },
      ImageMinimizerPlugin {
        options: {
          minimizer: undefined,
          generator: [
            {
              preset: 'webp',
              filename: '[path]generated.[name]@[width]x[height][ext]',
              implementation: [Function: sharpGenerate],
              options: { encodeOptions: { webp: {} } }
            }
          ],
          severityError: undefined,
          exclude: undefined,
          include: undefined,
          loader: true,
          concurrency: undefined,
          test: /\.(jpe?g|png|gif|tif|webp|svg|avif|jxl)$/i,
          deleteOriginalAssets: true
        }
      },
      ImageMinimizerPlugin {
        options: {
          minimizer: {
            implementation: [AsyncFunction: svgoMinify],
            options: { encodeOptions: {} }
          },
          generator: undefined,
          severityError: undefined,
          exclude: undefined,
          include: undefined,
          loader: true,
          concurrency: undefined,
          test: /\.svg$/,
          deleteOriginalAssets: true
        }
      },
      CssMinimizerPlugin {
        options: {
          test: /\.css(\?.*)?$/i,
          warningsFilter: [Function: warningsFilter],
          parallel: true,
          include: undefined,
          exclude: undefined,
          minimizer: {
            implementation: [AsyncFunction: cssnanoMinify],
            options: {
              preset: [ 'default', { discardComments: { removeAll: true } } ]
            }
          }
        }
      },
      TerserPlugin {
        options: {
          test: /\.[cm]?js(\?.*)?$/i,
          extractComments: false,
          parallel: true,
          include: undefined,
          exclude: undefined,
          minimizer: {
            implementation: [AsyncFunction: terserMinify] {
              getMinimizerVersion: [Function (anonymous)]
            },
            options: {
              compress: {
                drop_console: false,
                drop_debugger: true,
                defaults: true,
                unused: true
              },
              format: { ascii_only: true, comments: false },
              mangle: { safari10: true }
            }
          }
        }
      }
    ],
    moduleIds: 'named',
    removeEmptyChunks: false,
    runtimeChunk: 'single',
    splitChunks: {
      chunks: 'all',
      automaticNameDelimiter: '/',
      minSize: 0,
      cacheGroups: {
        vendor: {
          idHint: 'vendor',
          filename: 'js/bundle/vendor/[name].js',
          test: /[\\/]node_modules[\\/]/,
          priority: -20
        }
      }
    },
    usedExports: true
  }

I’ve tried selectively removing loaders to see if there was an issue where the wrong loader was loading the @styles/app.scss file, but that did not work. I’ve tried changing to using sass instead of scss (both as a file extension and as the content of the file) but that did not work.

The build passes successfully when i remove the file from the build process (remove it from the entry + remove any imports to it).

I am not sure how to continue debugging at this point and I would appreciate some assistance debugging further to resolve the issue.

Something that I have not managed to do which I think would be helpful is if i could see which module threw the error. I have not figured out how to change the output from the --debug flag to include it.

Thanks for reading!

OK, so I’ve had a little inspiration and discovered that one of the javascript parsers is trying to read the app.scss file.

I tested this by changing the contents of app.scss to var test = '', which passed the build. I’m now digging through the rules to see what module is reading the app.scss file which isn’t supposed to be.

I’ve tried setting:

app.hooks.action(`build.before`, async app => {
  app.build.rules.js.setExclude([
    app.path('@styles/app.scss'),
  ])    
})

however that has had no effect.

I’ve also manually added the exclusion to every rule except for sass:

app.hooks.action(`build.before`, async app => {
  Object.keys(app.build.rules).map(key => {
    if ('sass' !== key) {
      app.build.rules[key].setExclude([
        app.path('@styles/app.scss'),
      ])
    }
  })    
})

This also has had no effect.

I have also tried to manually exclude the file directly on all webpack rules which do not contain scss in their regex:

app.config( existing => {
  existing.plugins.unshift(
    new VuetifyPlugin({
      autoImport: true,
    })
  )
  existing.module.rules.map((rule, index) => {
    if (rule.test) {
      const test = 'function' === typeof rule.test ? rule.test().toString() : rule.test.toString()
      if (!test.includes('scss')) {
        existing.module.rules[index].exclude = [
          app.path('@styles/app.scss'),
        ]
      }
    }
    if (rule.oneOf) {
      rule.oneOf.map((sr, si) => {
        const test = 'function' === typeof sr.test ? sr.test().toString() : sr.test.toString()
        if (!test.includes('scss')) {
          existing.module.rules[index].oneOf[si].exclude = [
            app.path('@styles/app.scss'),
          ]
        }
      })
    }
  })
  return existing
})

This also has had no effect. I now think that the issue is related to the ordering of the plugins used by the scss / sass rule. I’m going to run some tests to see if i can shed some light there.

OK! after finally specifically adding the app.scss file to the sass loader:

app.config( existing => {
  existing.plugins.unshift(
    new VuetifyPlugin({
      autoImport: true,
    })
  )
  existing.module.rules.map((rule, index) => {
    if (rule.test) {
      const test = 'function' === typeof rule.test ? rule.test().toString() : rule.test.toString()
      if (!test.includes('scss')) {
        existing.module.rules[index].exclude = [
          app.path('@styles/app.scss'),
        ]
      } else {
        if (Array.isArray(existing.module.rules[index].include)) {
          existing.module.rules[index].include.push(app.path('@styles'))
        } else {
          existing.module.rules[index].include = [app.path('@styles')]
        }
      }
    }
    if (rule.oneOf) {
      rule.oneOf.map((sr, si) => {
        const test = 'function' === typeof sr.test ? sr.test().toString() : sr.test.toString()
        if (!test.includes('scss')) {
          existing.module.rules[index].oneOf[si].exclude = [
            app.path('@styles/app.scss'),
          ]
        } else {
          if (Array.isArray(existing.module.rules[index].oneOf[si].include)) {
            existing.module.rules[index].oneOf[si].include.push(app.path('@styles'))
          } else {
            existing.module.rules[index].oneOf[si].include = [app.path('@styles')]
          }
        }
      })
    }
  })
  return existing
})

I finally am getting some errors which appear to be related to one of the loaders being used by the sass module. I’m going to try to disable & test them individually to see which one is causing the problem. I suspect that the issue is related to vue-style-loader being included in the “use” chain for sass/scss files.

OK, i can now confirm that the issue was related to vue-style-loader being added to the module for sass in webpack. Once that was filtered out, the build worked correctly / as expected (after a little cleanup of other things that were disabled / edited / changed for debugging.)

Here is the updated bud.config.js file:

/**
 * Compiler configuration
 *
 * @see {@link https://roots.io/docs/sage sage documentation}
 * @see {@link https://bud.js.org/guides/configure bud.js configuration guide}
 *
 * @param {import('@roots/bud').Bud} app
 */
import { VuetifyPlugin } from 'webpack-plugin-vuetify'
import { readdirSync, statSync } from 'fs'

export default async (app) => {
  /**
   * Add some babel presets
   */
  // app.babel.setPreset('@vue/cli-plugin-babel/preset')
  /**
   * Application assets & entrypoints
   *
   * @see {@link https://bud.js.org/docs/bud.entry}
   * @see {@link https://bud.js.org/docs/bud.assets}
   */
  app
    .entry('app', ['@scripts/app', '@styles/app', '@styles/style.scss'])
    .entry('editor', ['@scripts/editor', '@styles/editor', '@styles/style.scss'])
    .assets(['images']);

  app.postcss.setPlugin('postcss-scss')
  /**
   * Static assets, especially those used as defaults
   */
  app.alias('@static', app.path('@src/static'))
  const files = readdirSync(app.path('@src/static'))
  files.forEach(file => {
    const budPath = `static/${file}`
    const path = app.path(`@src/static/${file}`)
    const stats = statSync(path)
    if (stats.isFile()) {
      app.copyFile(budPath)
    } else if (stats.isDirectory()) {
      app.copyDirectory(budPath)
    }
  })

  /**
   * Add Vuetify
   * 
   * @see {@link https://bud.js.org/docs/bud.use#usage}
   * @see {@link https://www.npmjs.com/package/webpack-plugin-vuetify}
   * @see {@link https://discourse.roots.io/t/fontsource-error-unexpected-character/22629/2}
   */
  app.compilePaths([app.path('@modules/vuetify'), app.path('@modules/@mdi/font')])
  app.config( existing => {
    existing.plugins.unshift(
      new VuetifyPlugin({
        autoImport: true,
      })
    )
    existing.module.rules.map((rule, index) => {
      if (rule.test) {
        const test = 'function' === typeof rule.test ? rule.test().toString() : rule.test.toString()
        if (!test.includes('scss')) {
        } else {
          if (Array.isArray(existing.module.rules[index].include)) {
            existing.module.rules[index].include.push(app.path('@styles/style.scss'))
          } else {
            existing.module.rules[index].include = [
              app.path('@styles/style.scss'),
            ]
          }
          existing.module.rules[index].use = existing.module.rules[index].use.filter(item => !(item.ident && item.ident === 'vue-style')) 
        }
      }
      if (rule.oneOf) {
        rule.oneOf.map((sr, si) => {
          const test = 'function' === typeof sr.test ? sr.test().toString() : sr.test.toString()
          if (!test.includes('scss')) {
          } else {
            if (Array.isArray(existing.module.rules[index].oneOf[si].include)) {
              existing.module.rules[index].oneOf[si].include.push(app.path('@styles/style.scss'))
            } else {
              existing.module.rules[index].oneOf[si].include = [
                app.path('@styles/style.scss'),
              ]
            }
            existing.module.rules[index].oneOf[si].use = existing.module.rules[index].oneOf[si].use.filter(item => !(item.ident && item.ident === 'vue-style'))
          }
        })
      }
    })
    return existing
  })
  app.sass.importGlobal([
    'vuetify/styles',
    'vuetify/lib/components/VApp/VApp',
    'vuetify/lib/components/VMain/VMain',
    'vuetify/lib/components/VGrid/VGrid',
    'vuetify/lib/components/VCard/VCard',
    'vuetify/lib/components/VAvatar/VAvatar',
    'vuetify/lib/components/VIcon/VIcon',
    'vuetify/lib/components/VDivider/VDivider',
  ])

  /**
   * Set public path
   *
   * @see {@link https://bud.js.org/docs/bud.setPublicPath}
   */
  app.setPublicPath('/app/themes/sage/public/');

  /**
   * Vue Confiugration
   * 
   * @see {@link https://budjs.org/extensions/bud-vue}
   */
  app.vue.set('runtimeOnly', false);

  /**
   * Development server settings
   *
   * @see {@link https://bud.js.org/docs/bud.setUrl}
   * @see {@link https://bud.js.org/docs/bud.setProxyUrl}
   * @see {@link https://bud.js.org/docs/bud.watch}
   */
  app
    .setUrl('http://localhost:3000')
    .setProxyUrl('http://web.test')
    .watch(['resources/views', 'app']);

  /**
   * Generate WordPress `theme.json`
   *
   * @note This overwrites `theme.json` on every build.
   *
   * @see {@link https://bud.js.org/extensions/sage/theme.json}
   * @see {@link https://developer.wordpress.org/block-editor/how-to-guides/themes/theme-json}
   */
  app.wpjson
    .set('settings.color.custom', false)
    .set('settings.color.customDuotone', false)
    .set('settings.color.customGradient', false)
    .set('settings.color.defaultDuotone', false)
    .set('settings.color.defaultGradients', false)
    .set('settings.color.defaultPalette', false)
    .set('settings.color.duotone', [])
    .set('settings.custom.spacing', {})
    .set('settings.custom.typography.font-size', {})
    .set('settings.custom.typography.line-height', {})
    .set('settings.spacing.padding', true)
    .set('settings.spacing.units', ['px', '%', 'em', 'rem', 'vw', 'vh'])
    .set('settings.typography.customFontSize', false)
    .enable();
};
2 Likes