New Aurelia and Webpack project fails to load required css file

Hi,
I created a new aurelia / webpack project skeleton and when i want to load a scss file i’m getting the following error:

Error: Failed loading required CSS file: app.scss
    at fixupCSSUrls (aurelia-templating-resources.js?fcf2:formatted:1579)

In my webpack.config.js i have defined the sass-loader for scss files.
In my app.html i use the following to load the scss file:

 <require from="./app.scss"></require>

app.scss contents:

body {
    background-color:red; 
}

From what i could see in the webpack.config.js everything should be fine.
Am i missing something that gives the error?
Webpack.config.js contents:

const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const CopyWebpackPlugin = require('copy-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const DuplicatePackageCheckerPlugin = require('duplicate-package-checker-webpack-plugin');
const project = require('./aurelia_project/aurelia.json');
const { AureliaPlugin, ModuleDependenciesPlugin } = require('aurelia-webpack-plugin');
const { ProvidePlugin } = require('webpack');
const { BundleAnalyzerPlugin } = require('webpack-bundle-analyzer');
const { CleanWebpackPlugin } = require('clean-webpack-plugin');

// config helpers:
const ensureArray = (config) => config && (Array.isArray(config) ? config : [config]) || [];
const when = (condition, config, negativeConfig) =>
  condition ? ensureArray(config) : ensureArray(negativeConfig);

// primary config:
const title = 'Aurelia Skeleton';
const outDir = path.resolve(__dirname, project.platform.output);
const srcDir = path.resolve(__dirname, 'src');
const nodeModulesDir = path.resolve(__dirname, 'node_modules');
const baseUrl = '/';
const baseUrlProduction = './';

const cssRules = [
  { loader: 'css-loader' },
  {
    loader: 'postcss-loader',
    options: { plugins: () => [
      require('autoprefixer')(),
      require('cssnano')()
    ] }
  }
];

const sassRules = [
  {
    loader: "sass-loader",
    options: {
      sassOptions: {
        includePaths: ['node_modules']
      }
    }
  }
];

module.exports = ({ production } = {}, {extractCss, analyze, tests, hmr, port, host } = {}) => ({
  resolve: {
    extensions: ['.ts', '.js'],
    modules: [srcDir, 'node_modules'],

    alias: {
      // https://github.com/aurelia/dialog/issues/387
      // Uncomment next line if you had trouble to run aurelia-dialog on IE11
      // 'aurelia-dialog': path.resolve(__dirname, 'node_modules/aurelia-dialog/dist/umd/aurelia-dialog.js'),

      // https://github.com/aurelia/binding/issues/702
      // Enforce single aurelia-binding, to avoid v1/v2 duplication due to
      // out-of-date dependencies on 3rd party aurelia plugins
      'aurelia-binding': path.resolve(__dirname, 'node_modules/aurelia-binding'),      
    }
  },
  entry: {
    app: [
      // Uncomment next line if you need to support IE11
      // 'promise-polyfill/src/polyfill',
      'aurelia-bootstrapper',
      'jquery',
      'oidc-client',
    ]
  },
  mode: production ? 'production' : 'development',
  output: {
    path: outDir,
    publicPath: production ? baseUrlProduction : baseUrl,
    filename: production ? '[name].[contenthash].js' : '[name].[hash].bundle.js',
    sourceMapFilename: production ? '[name].[contenthash].map' : '[name].[hash].bundle.map',
    chunkFilename: production ? '[name].[contenthash].js' : '[name].[hash].chunk.js'
  },
  optimization: {
    runtimeChunk: 'single',
    splitChunks: {
      cacheGroups: {
        vendor: {
          test: /[\\/]node_modules[\\/]/,
          name: 'vendors',
          chunks: 'all',          
          
        },
      },
    },
  },
  performance: { hints: false },
  devServer: {
    contentBase: outDir,
    // serve index.html for all 404 (required for push-state)
    historyApiFallback: true,
    hot: hmr || project.platform.hmr,
    port: port || project.platform.port,
    host: host
  },
  devtool: production ? 'nosources-source-map' : 'cheap-module-eval-source-map',
  module: {
    rules: [
      // CSS required in JS/TS files should use the style-loader that auto-injects it into the website
      // only when the issuer is a .js/.ts file, so the loaders are not applied inside html templates
      {
        test: /\.scss$/i,
        issuer: [{ not: [{ test: /\.html$/i }] }],
        use: extractCss ? [{
          loader: MiniCssExtractPlugin.loader
        }, ...cssRules, ...sassRules
        ] : ['style-loader', ...cssRules, ...sassRules]
      },
      {
        test: /\.scss$/i,
        issuer: [{ test: /\.html$/i }],
        // CSS required in templates cannot be extracted safely
        // because Aurelia would try to require it again in runtime
        use:  ['style-loader', ...cssRules, ...sassRules]
      },
      {
        test: /\.html$/i,
        loader: 'html-loader',
        options:
        {
          attributes: false,
        }
      },
      { test: /\.ts$/, loader: "ts-loader" },
      // embed small images and fonts as Data Urls and larger ones as files:
      { test: /\.(png|gif|jpg|cur)$/i, loader: 'url-loader', options: { limit: 8192 } },
      { test: /\.woff2(\?v=[0-9]\.[0-9]\.[0-9])?$/i, loader: 'url-loader', options: { limit: 10000, mimetype: 'application/font-woff2' } },
      { test: /\.woff(\?v=[0-9]\.[0-9]\.[0-9])?$/i, loader: 'url-loader', options: { limit: 10000, mimetype: 'application/font-woff' } },
      // load these fonts normally, as files:
      { test: /\.(ttf|eot|svg|otf)(\?v=[0-9]\.[0-9]\.[0-9])?$/i, loader: 'file-loader' },
      { test: /environment\.json$/i, use: [
        {loader: "app-settings-loader", options: {env: production ? 'production' : 'development' }},
      ]},
      ...when(tests, {
        test: /\.[jt]s$/i, loader: 'istanbul-instrumenter-loader',
        include: srcDir, exclude: [/\.(spec|test)\.[jt]s$/i],
        enforce: 'post', options: { esModules: true },
      }),
      {
        // Exposes jQuery for use outside Webpack build
        test: require.resolve('jquery'),
        loader: 'expose-loader',
        options: {
          exposes: ['$', 'jQuery'],
        },
      },
      {
        // Exposes oidc-client for use outside Webpack build
        test: require.resolve('oidc-client'),
        use: [{
          loader: 'expose-loader',
          options: {
            exposes: ['Oidc'],
          },
        }]
      }
    ]
  },
  plugins: [
    ...when(!tests, new DuplicatePackageCheckerPlugin()),
    new AureliaPlugin(),
    new ProvidePlugin({      
      $: 'jquery',
      jQuery: 'jquery',
      'window.jQuery': 'jquery',
      Oidc: 'Oidc',
    }),
    new ModuleDependenciesPlugin({
      'aurelia-testing': ['./compile-spy', './view-spy']
    }),
    new HtmlWebpackPlugin({
      template: 'index.ejs',
      filename: 'index.html',
      inject: 'head',
      minify: production ? {
        removeComments: true,
        collapseWhitespace: true
      } : undefined,
      metadata: production ? {
        // available in index.ejs //
        title, baseUrlProduction
      } : {
        // available in index.ejs //
        title, baseUrl
      }
    }),
    new HtmlWebpackPlugin({
      template: 'static/callback.html',
      filename: 'callback.html',
      inject: 'head',      
      minify: production ? {
        removeComments: true,
        collapseWhitespace: true
      } : undefined,
      metadata: production ? {
        // available in index.ejs //
        title, baseUrlProduction
      } : {
          // available in index.ejs //
          title, baseUrl
        }
    }),
    // ref: https://webpack.js.org/plugins/mini-css-extract-plugin/
    ...when(extractCss, new MiniCssExtractPlugin({ // updated to match the naming conventions for the js files
      filename: production ? 'css/[name].[contenthash].bundle.css' : 'css/[name].[hash].bundle.css',
      chunkFilename: production ? 'css/[name].[contenthash].chunk.css' : 'css/[name].[hash].chunk.css'
    })),
    new CopyWebpackPlugin({
      patterns: [
        { from: 'static/callback.js', to: 'callback.js' },
        { from: 'static/favicon.ico', to: 'favicon.ico' },
        { from: production ? 'appsettings.json' : 'appsettings.development.json', to: 'appsettings.json' },
      ],
   }), 
    ...when(analyze, new BundleAnalyzerPlugin()),
    /**
     * Note that the usage of following plugin cleans the webpack output directory before build.
     * In case you want to generate any file in the output path as a part of pre-build step, this plugin will likely
     * remove those before the webpack build. In that case consider disabling the plugin, and instead use something like
     * `del` (https://www.npmjs.com/package/del), or `rimraf` (https://www.npmjs.com/package/rimraf).
     */ 
    new CleanWebpackPlugin()
  ]
});

Package.json contents(if helpfull):

{
  "name": "identity-client-skeleton",
  "description": "An Aurelia client application.",
  "version": "0.1.0",
  "repository": {
    "type": "???",
    "url": "???"
  },
  "license": "MIT",
  "dependencies": {
    "@babel/plugin-syntax-dynamic-import": "^7.8.3",
    "@progress/kendo-ui": "^2020.2.617",
    "@types/bootstrap": "^4.5.0",
    "@types/jquery": "^3.5.1",
    "@types/kendo-ui": "^2020.2.0",
    "acorn": "^8.0.1",
    "aurelia-animator-css": "^1.0.4",
    "aurelia-bootstrapper": "^2.3.3",
    "aurelia-dialog": "^2.0.0",
    "aurelia-http-client": "^1.3.1",
    "aurelia-i18n": "^3.1.1",
    "aurelia-kendoui-bridge": "^1.10.0",
    "aurelia-open-id-connect": "^2.0.4",
    "aurelia-polyfills": "^1.3.4",
    "aurelia-templating-resources": "^1.13.0",
    "aurelia-templating-router": "^1.4.0",
    "aurelia-validation": "^1.6.0",
    "babel": "^6.23.0",
    "babel-loader": "^8.1.0",
    "bluebird": "^3.7.2",
    "braces": "^3.0.2",
    "bootstrap": "^4.5.2",
    "filesize": "^6.1.0",
    "i18next-xhr-backend": "^3.2.2",
    "jquery": "^3.5.1",
    "kendo-ui-core": "^2020.2.617",
    "minimist": "^1.2.5",
    "moment": "^2.27.0",
    "oidc-client": "^1.10.1",
    "popper": "^1.0.1",
    "popper.js": "^1.16.1",
    "swiper": "^6.1.1"
  },
  "peerDependencies": {},
  "devDependencies": {
    "@babel/core": "^7.11.1",
    "@types/bluebird": "^3.5.32",
    "@types/lodash": "^4.14.159",
    "@types/node": "^14.0.27",
    "@types/webpack": "^4.41.21",
    "@types/webpack-env": "^1.15.2",
    "app-settings-loader": "^1.0.6",
    "aurelia-cli": "^1.3.1",
    "aurelia-loader-nodejs": "^1.1.0",
    "aurelia-pal-nodejs": "^2.0.0",
    "aurelia-testing": "^1.0.0",
    "aurelia-tools": "^2.0.0",
    "aurelia-webpack-plugin": "^4.0.0",
    "autoprefixer": "^9.8.6",
    "clean-webpack-plugin": "^3.0.0",
    "copy-webpack-plugin": "^6.0.3",
    "css-loader": "^4.2.1",
    "cssnano": "^4.1.10",
    "del": "^5.1.0",
    "duplicate-package-checker-webpack-plugin": "^3.0.0",
    "expose-loader": "^1.0.0",
    "file-loader": "^6.0.0",
    "gulp": "^4.0.2",
    "gulp-rename": "^2.0.0",
    "html-loader": "^1.1.0",
    "html-webpack-plugin": "^4.3.0",
    "istanbul-instrumenter-loader": "^3.0.1",
    "json-loader": "^0.5.7",
    "mini-css-extract-plugin": "^0.10.0",
    "minimatch": "^3.0.4",
    "node-sass": "^4.14.1",
    "nps": "^5.10.0",
    "nps-utils": "^1.7.0",
    "opn": "^6.0.0",
    "plugin-error": "^1.0.1",
    "postcss-loader": "^3.0.0",
    "promise-polyfill": "^8.1.3",
    "sass": "^1.26.10",
    "sass-loader": "^9.0.3",
    "style-loader": "^1.2.1",
    "through2": "^4.0.2",
    "tree-kill": "^1.2.2",
    "ts-loader": "^8.0.2",
    "ts-node": "^8.10.2",
    "typescript": "^3.9.7",
    "url-loader": "^4.1.0",
    "vinyl-fs": "^3.0.3",
    "webpack": "^4.44.1",
    "webpack-bundle-analyzer": "latest",
    "webpack-cli": "^3.3.12",
    "webpack-dev-server": "^3.11.0"
  },
  "browserslist": [
    "defaults"
  ],
  "engines": {
    "node": ">= 12.0.0"
  },
  "scripts": {
    "build": "webpack --env.production --extractCss",
    "start": "webpack-dev-server --extractCss",
    "build:dev": "webpack --extractCss",
    "analyze": "webpack --env.production --analyze"
  },
  "main": "dist/app.bundle.js"
}

1 Like

I guess you started with css, and manually added scss to your project.
The au new has custom option to let you select SASS which will provide you correct webpack config.

For your existing project, remove style-loader for scss loaded from html file (issuer: /\.html?$/i)

{
        test: /\.scss$/,
        use: [...cssRules, ...sassRules],
        issuer: /\.html?$/i
      },
3 Likes

I had the same issue recently, I believe its related to a newer version of css-loader. Try passing the below option to your css-loader rule.

{ loader: 'css-loader', options: { esModule: false } }
4 Likes

Thx for pointing it out!
BTW, the latest aurelia-cli skeleton got the fix.

3 Likes

@huochunpeng and @jbockle
Thank you both for the help!

I needed both your suggestions(adding the options to css-loader, and removing style-loader from the html issuer) to fix the issue.

Thnx again!

2 Likes

By the way, is there an “easy” way to upgrade an existing Aurelia project to the newest NPM package versions and the latest corresponding configuration settings using the (latest) CLI?

Currently, when I want to upgrade my existing projects, I just create a new (custom) dummy project using the newest CLI and then use a compare tool to inspect and update the changes in the projects’ configuration files. I also used this strategy to determine how to add SCSS support to my project which didn’t initially have SCSS support.

(Well, I think that manually comparing changes is the safest way to do so, especially since I have also added some additional changes to the project configuration files myself afterwards. But I guessed it doesn’t hurt to ask for possible alternative ways. ;))

1 Like

@Bart you could try using the npm package “npm-check-updates” or ncu for short. It will check your package.json with available versions and show a list of packages that can be upgraded. The package can upgrade the package.json for you. Then you would have to do a npm install to install the newer versions.
The next thing would be to check what breaks and where you need to change things in your webpack.config.ts to get everything working again.

This would be the only way i know of. Not sure if anyone else knows a better way.

4 Likes