webpacksassbootstrap-4extract-text-plugin

Webpack bootstrap ExtractTextPlugin SCSS - Module Parse Failed Unexpected character '@'


I'm attempting to output a css file from bootstrap 4.0.0 beta scss using webpack. I've successfully handled scss with an inline style injection as per: https://getbootstrap.com/docs/4.0/getting-started/webpack/ . There are issues related to having the style injected into the head of the document.

here's my package.json:

{
  "name": "testproject",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "build": "webpack"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "@types/knockout": "^3.4.46",
    "bootstrap": "^4.0.0-beta",
    "css-loader": "^0.28.7",
    "extract-text-webpack-plugin": "^3.0.1",
    "html-webpack-plugin": "^2.30.1",
    "jquery": "^3.2.1",
    "knockout": "^3.4.2",
    "node-sass": "^4.5.3",
    "popper.js": "^1.12.5",
    "postcss-loader": "^2.0.8",
    "precss": "^2.0.0",
    "sass-loader": "^6.0.6",
    "style-loader": "^0.19.0",
    "ts-loader": "^3.0.2",
    "typescript": "^2.5.3",
    "url-loader": "^0.6.2",
    "webpack": "^3.7.1",
    "webpack-notifier": "^1.5.0"
  },
  "-vs-binding": {
    "BeforeBuild": [
      "build"
    ]
  },
  "dependencies": {}
}

and my webpack.config.js (updated):

var path = require('path');
var webpack = require('webpack');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const ExtractTextPlugin = require('extract-text-webpack-plugin');

const extractScss = new ExtractTextPlugin({
    filename: 'dist/[name].[hash].css',
    allChunks: true,
});

module.exports = {
    entry: {
        site: [
            './wwwroot/js/site.js',
            './Script/testkoviewmodel.ts'],
    },
    output: {
        publicPath: "/dist/",
        path: path.join(__dirname, '/wwwroot/dist/'),
        filename: '[name].[hash].build.js',
    },
    plugins: [
        extractScss,
        new webpack.optimize.UglifyJsPlugin({
            minimize: true,
            compress: {
                warnings: false
            },
        }),
        new webpack.ProvidePlugin({
            $: 'jquery',
            jQuery: 'jquery',
            'window.jQuery': 'jquery',
            Popper: ['popper.js', 'default'],
        }),
        new HtmlWebpackPlugin({
            inject: false,
            template: 'Views/Shared/_LayoutTemplate.cshtml',
            filename: '../../Views/Shared/_Layout.cshtml',
        }),        
    ],
    devtool: 'source-map',
    module: {
        rules: [
            {
                test: /\.tsx?$/,
                loader: 'ts-loader',
                exclude: /node_modules/,
            },
            { test: /\.css$/, loader: "style-loader!css-loader" },
            {
                test: /\.scss$/,
                use: extractScss.extract({
                    fallback: 'style-loader',
                    use: ['css-loader', 'sass-loader']
                }),
                exclude: /node_modules/,
            },
            { test: /\.eot(\?v=\d+\.\d+\.\d+)?$/, loader: "file" },
            { test: /\.(woff|woff2)$/, loader: "url?prefix=font/&limit=5000" },
            { test: /\.ttf(\?v=\d+\.\d+\.\d+)?$/, loader: "url?limit=10000&mimetype=application/octet-stream" },
            { test: /\.svg(\?v=\d+\.\d+\.\d+)?$/, loader: "url?limit=10000&mimetype=image/svg+xml" },
        ]
    },
    resolve: {
        extensions: ['.ts', '.tsx', '.js', '.scss'],
    }
};

I've tried all manner of modifications to the webpack config as per many many posts of people all having the same or related issue. So far I've obviously been unsuccessful.

**EDIT !important : The entire problem is the exclude properties should be QUOTED as they are not regex but strings in this case. Thanks to BudHead!


Solution

  • Assuming you are importing bootstrap 4 like so in your entry js:

    import 'bootstrap/scss/bootstrap.scss';
    

    ...try the following plugin setup:

    const extractScss = new ExtractTextPlugin({
      filename: 'assets/css/[name].[contenthash].css',
      allChunks: true
    });
    

    ... add the above to your plugins array:

    plugins: [extractScss]
    

    ... and then in your rules:

    {
      test: /\.scss$/,
      use: extractScss.extract({
        fallback: 'style-loader',
        //resolve-url-loader may be chained before sass-loader if necessary
        use: ['css-loader', 'sass-loader']
      })
    }
    

    So it's important to construct your ExtracTextPlugin, here I have mine set to the

    const extractScss = new ExtractTextPlugin(...)
    

    That way later on in your rules configuration you are passing the same object. In your code above you are using the imported instance of ExtractTextPlugin not the new one you created in your plugins array (which has no options set).

    You'll probably have to adjust the filename in the plugin options, mine is set to go to something like assets/css/main.awef32fq23faf23aasdf.css