Problems removing Mix from a Laravel project with Vue

I want to remove Laravel Mix from my project in order to adopt new features or releases quicker, without having to use Mix’s syntax or plugins or to put everything inside the webpackConfig method.

I have created a Webpack configuration that works for styles and assets, but I’m not able to make the vue-loader import the styles from vue single file components in a static .html file: if I use the html-webpack-plugin everything runs correctly, but if I include the .js bundle in a static .html file the styles do not load. I want it to work in a static .html file in order to simulate the behavior of a blade file in php.

Here is a simplified version of my setup:

webpack.config.js

const path = require('path');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
const { VueLoaderPlugin } = require("vue-loader");
const htmlWebpackPlugin = require("html-webpack-plugin");

module.exports = {
    entry: {
        main: "./src/main.js",
    },
    output: {
        filename: 'js/[name].js',
        path: path.resolve(__dirname, 'dist')
    },
    resolve: {
        extensions: ['.js', '.vue'],
        alias: {
            "vue$": "vue/dist/vue.common.js"
        }
    },
    module: {
        rules: [
            {
                test: /.((c|sa|sc)ss)$/i,
                use: [
                'vue-style-loader',
                MiniCssExtractPlugin.loader,
                'css-loader', 'postcss-loader', 'sass-loader'
            ],
            },
            {
                test: /.js$/,
                exclude: /node_modules/,
                use: ['babel-loader'],
            },
            {
                test: /.vue$/,
                loader: 'vue-loader'
            },
        ],  
    },
    plugins: [
        new MiniCssExtractPlugin({
            filename: 'css/[name].css',
        }),
        new htmlWebpackPlugin({
            template: path.resolve(__dirname, "public", "index.html")
        }),
        new CleanWebpackPlugin({
            cleanStaleWebpackAssets: false,
            verbose: true
        }),
        new VueLoaderPlugin()
    ],
};

main.js

import Vue from "vue";
import App from "./App.vue";

new Vue({
    render: (h) => h(App),
}).$mount("#app");

App.vue

<template>
  <div id="app">
    <div class="nav">
        Test nav
    </div>
  </div>
</template>

<style lang="scss">
.nav {
    padding: 30px 0 100px 0;
    font-family: sans-serif;
}
</style>

index.html

(used to generate another index.html with html-webpack-plugin)

๐Ÿ‘‰ with this one everything works

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <meta http-equiv="X-UA-Compatible" content="ie=edge" />
    <title>Vue app</title>
</head>

<body>
    <div id="app"></div>
    <!-- built files will be auto injected -->
</body>

</html>

index.html (static file)

๐Ÿ‘‰ this one doesn’t load the .css files

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Test</title>
</head>

<body>
    <div id="app"></div>
    <script src="dist/js/main.js"></script>
</body>

</html>

What I’ve tried

  • Not using html-webpack-plugin during the build (nothing changed)
  • Using a different alias for $vue: (vue$: "vue/dist/vue.runtime.esm.js") (nothing changed)
  • Not using mini-css-extract-plugin (the css wouldn’t load even with the html-webpack-plugin)

Also in Laravel Mix the styles are loaded inside <style> tags and not in separate .css files, but this is not an issue (both are fine for me).

Answer

By looking at this answer to the SO question How to migrate from laravel mix to pure Webpack? I’ve printed out the configuration of a new Laravel Mix project with minimal set-up.

Mix.listen('configReady', function (config) {
  RegExp.prototype.toJSON = RegExp.prototype.toString;
  console.log(JSON.stringify(config));
});

I figured out that Laravel Mix uses two different sets of loaders between css files coming from Vue and all the other css/scss files.

I didn’t copy their configuration because it involves lots of duplication, but luckily I was able to figure out a quick setup that does the same job:

// [...]
module: {
  rules: [
    {
      test: /.vue.css$/, // only vue css
      use: [
        'style-loader',
        'css-loader',
        'postcss-loader',
        'sass-loader'
      ]
    },
    {
      test: /.((c|sa|sc)ss)$/i,
      exclude(modulePath) {
        // exclude vue css
        return modulePath.includes('.vue');
      },
      use: [
        {
          loader: MiniCssExtractPlugin.loader,
          options: {
            publicPath: '../',
          },
        },
        'css-loader', 'postcss-loader', 'sass-loader'
      ]
    },
    // [...]
  ]
}

I’m not sure that this is the best way to load css in Vue applications, but at least now I can replicate the same situation I have in the original application.

Edit 01/28/21

When working with scss files you need to update the first test like this:

test: /.vue.((c|sa|sc)ss)$/i, // only vue css