webpack-contrib / postcss-loader

PostCSS loader for webpack
MIT License
2.86k stars 209 forks source link

[Question] Output multiple css files and cssnext integration #200

Closed yotov closed 7 years ago

yotov commented 7 years ago

Hello guys.

I I'm trying to migrate css processing to webpack and I have a problem with cssnext. Let me describe the project structure:

src/
| -- app.js
| -- theme1.css
| -- theme2.css
| -- css/
| -- | -- index.css
| -- | -- component1.css
| -- | -- component2.css
webpack.config.js

Every component has separate css file in css/ folder and all components are imported in index.css. The colors in components file are defined with css variables. Here is an example of component1.css file:

.component {
    background: var(--background-color);
}

And all variables are defined in theme files which also include the css/index.css file:

:root {
    --background-color: black;
}

@import 'css/index.css';

I define theme1 and theme2 as entry points to output two separate files in dist folder. This is the webpack config:

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

module.exports = {
    entry: {
        'app': 'app.js',
        'theme1': 'theme1.css',
        'theme2': 'theme2.css'
    },
    output: {
        filename: '[name].js',
        path: path.resolve(__dirname, 'dist')
    },
    plugins: [
        new ExtractTextPlugin({
            filename: 'css/[name].css',
            allChunks: true
        }),
    ],
    resolve: {
        modules: [
             path.resolve(__dirname, 'src'),
            'node_modules'
        ],
        extensions: ['.js']
    },
    module: {
        rules: [
            {
                test: /\.css/,
                use: ExtractTextPlugin.extract({
                    fallback: 'style-loader',
                    use: [
                        {
                            loader: 'css-loader'
                        },
                        {
                            loader: 'postcss-loader',
                            options: {
                                plugins: function (ctx) {
                                    console.log(ctx.resourcePath);
                                    return [
                                        require('postcss-cssnext')({ browsers: ['last 50 versions'] })
                                    ];
                                }
                            }
                        }
                    ],
                })
            }
        ]
    }
};

After running webpack the output structure is:

dist/
| -- app.js
| -- theme1.js
| -- theme2.js
| -- css/
| -- | -- theme1.css
| -- | -- theme2.css
webpack.config.js

First I'm not sure why I have theme1.js and theme2.js files next to app.js file. The only way to export two separates css files with ExtractTextPlugin was to declare them as entry points but this cause two additional js files.

The next strange behaviour is that the plugin function is executed two times per file. I added console.log(ctx.resourcePath); to plugins function and this is the output:

...\webpack2\src\theme2.css
...\webpack2\src\theme1.css
...\webpack2\src\theme1.css
...\webpack2\src\theme2.css

But the plugin function is executed once when I set allChunks to true for ExtractTextPlugin ( and I'm not sure why and is there any other effect ).

And the final problem is that dist/css/theme1.css and dist/css/theme2.css files contains css variables. I try to debug what's going on and I found that the postcss-cssnext run only for source of the entry files ( files with the definitions of variables and import for other components). I'm not sure is it possible to run postcss-loader on a bundle produced from css-loader?

Thank you in advance.

michael-ciniawsky commented 7 years ago

.css files can not be used as entry points, webpack is a JS Module Bundler and HTML, CSS, Assets require a loader to work correctly.

postcss-cssnext has a plugin for CSS Custom Properties included, you can save your variables in a JSON file etc and add them via postcss-cssnext, if you use a postcss.confg.js file autoreloading them works out-of-the-box :)

postcss.config.js

const vars = {...}

module.exports = (ctx) => ({
 plugins: [
   require('postcss-cssnext')({
      variables: vars,
      browsers: '...'
   })
 ]
})
|– src
|   |– entry.js
|   |– entry.css 
|   |– components
|       |– component
|           |– index.css
|           |– index.js
|
|– postcss.config.js
|– webpack.config.js

entry.js

import styles from './entry.css'
...

entry.css

@import 'normalize.css';
@import 'bootstrap.css';

.entry {}

./components/component/index.js

import styles from './index.css'

./components/component/index.css

.component1 {}

extracted.css

/* Order Critical  [Global] CSS */
@import 'normalize.css';
@import 'bootstrap.css';

.entry {}

/* Order Non-Critical Components [Local] CSS */
.component1 {}
.component2 {}
.componentN {}
...

webpack walks the require()/import' s (that's the only 'order' you will get 😛 ), in terms of CSS usage think of require()/import as an @import and try to keep component CSS always relative ./ to the component JS to avoid 90% of ExtractTextPlugin Order Issues.

yotov commented 7 years ago

Thank you for the comment. I'm not sure how do you output two extracted.css files with different set of variables.

ben-pr-p commented 7 years ago

@IlianYotov did you ever figure it out?

yotov commented 7 years ago

Nope

ben-pr-p commented 7 years ago

As a work around, you can leave the CSS variables in the CSS, and include a Javascript polyfill (what I'm doing). Or use gulp