laravel-mix: extractVueStyles breaks multiple css files processing

Mix:

const mix = require('laravel-mix');

mix.options({
  extractVueStyles: true
});

const stylusOptons = {
  paths: ['node_modules', 'resources/assets/stylus'],
  'include css': true,
  'resolve url': true
};

mix.setPublicPath('./public/assets');

mix.stylus('resources/assets/stylus/home.styl', 'css', stylusOptons);

mix
    .stylus('resources/assets/stylus/vote/main.styl', 'css/vote', stylusOptons)
    .stylus('resources/assets/stylus/vote/alter.styl', 'css/vote', stylusOptons)
    .stylus('resources/assets/stylus/vote/order.styl', 'css/vote', stylusOptons)
    .stylus('resources/assets/stylus/vote/open.styl', 'css/vote', stylusOptons);

mix
    .sourceMaps()
    .js('resources/assets/js/app.js', 'js')
    .js('resources/assets/js/ui.js', 'js')
    .extract(['vue', 'vuex', 'vue-resource'])
    .stylus('resources/assets/stylus/app.styl', 'css', stylusOptons)
    .copy('resources/assets/images', 'public/assets/images', false)
    .copy('resources/assets/fonts', 'public/assets/fonts', false);

Enabling extractVueStyles is leading to situation, when extracted styles are placed in each and every outputted css.

Probably extractVueStyles should accept not a bool, but a path where to extract styles, or extracting should place styles near js file, from what they are extracted (e.g. app.js.css). At current point this simply doesn’t work as expected and breaks everything

About this issue

  • Original URL
  • State: closed
  • Created 7 years ago
  • Reactions: 2
  • Comments: 20 (9 by maintainers)

Most upvoted comments

@JeffreyWay about multiple mix.js() calls — maybe it will be easier to make possible to use several mix-files?

e.g. npm run dev --mix=mymix

In most cases developer will have no need to build several scripts or style sets. And the way mix is built — it is aiming the most common case in most easy way. And the problem described can appear in cases of some specific complex apps. I don’t see an easy solution to this problem instead of two ways:

  1. multiple mix files (webpack.mix.js, app.mix.js, admin.mix.js) — this seems to be the easiest and clear, but will force to call several build commands and will pollute root directory
  2. Introduce some isolated scopes in mix, that will all run from white paper, e.g.
mix.scope((mix) => {
   mix.js('app.js').stylus('app.stylus');
});
mix.scope((mix) => {
   mix.js('admin.js').stylus('admin.stylus');
});
/// etc

This will keep root dir clean, you will still use one command to build, but seems to be harder to implement and also can lead to mix-file overbloat (but not a big deal, I think, can be handled with imports)

Experiencing the same situation when I have two separated apps say, main and admin. Both are using Vue stack and I need to extract the css from two JS entries.

mix.js('resources/js/admin.js', 'public/js')
  .js('resources/js/main.js', 'public/js')

Since the extractVueStyles is expecting a file name for the extraced css and it uses extract-text-webpack-plugin, so I tried to pass a pattern instead of exact filename like this

mix.options({
    extractVueStyles: 'css/[name].css'
})

and I got this

            Asset      Size  Chunks             Chunk Names
css//js/admin.css  53.2 KiB       2  [emitted]  /js/admin
 css//js/main.css  52.3 KiB       3  [emitted]  /js/main
     /js/admin.js  1.65 KiB       2  [emitted]  /js/admin
      /js/main.js  1.61 KiB       3  [emitted]  /js/main
  /js/manifest.js  1.42 KiB       0  [emitted]  /js/manifest
    /js/vendor.js   179 KiB       1  [emitted]  /js/vendor

I wonder if it possible to pass a function or object to extractVueStyles so we could do something like this

mix.options({
  extractVueStyles: {
    filename (getPath) {
      return getPath('[name].css').replace('js/', 'css/')
    }
  }
})

// OR

mix.options({
  extractVueStyles (getPath) {
    return getPath('[name].css').replace('js/', 'css/')
  }
})

Toughts?

@terion-name @JeffreyWay I have the same problem with multiple mix.js file. I also have two app, one is spa for admin, and another app is for users with bootstrap, not a spa. Maybe mix.scope() is the best way to solve this problem.

Now when I build with laravel-mix, both app have to include the vendor.js file. However, the bootstrap app for users isn’t using the vue stack. So if there is a way to split this two app?

The code of my webpack.mix.js

const { mix } = require('laravel-mix');

/*
 |--------------------------------------------------------------------------
 | Mix Asset Management
 |--------------------------------------------------------------------------
 |
 | Mix provides a clean, fluent API for defining some Webpack build steps
 | for your Laravel application. By default, we are compiling the Sass
 | file for the application as well as bundling up all the JS files.
 |
 */

mix.options({
        extractVueStyles: 'css/vue.css'
    })
    .js('resources/assets/js/app.js', 'public/js') // for admin, use the vue stack
    .js('resources/assets/js/main.js', 'public/js') // for public users, dosn't using the vue stack
    .sass('resources/assets/sass/frontend.scss', 'public/css');

if (mix.config.inProduction) {
    mix.extract([
            'vue', 'quill', 'lodash', 'axios',
            'vue-router', 'element-ui', 'vuex'
        ])
        .version();
}

very strange things are going on. give me several minutes, I’ll make a demo app

This is fixed on master now. You can either stick with:

mix.options({
    extractVueStyles: true
});

Or, you can provide an explicit path:

mix.options({
    extractVueStyles: 'public/css/vue-css.css'
});