Webpack CSS 输出总是被缩小

Webpack CSS Output Is Always Minified

对于我的特定项目,我需要控制 CSS 的缩小,并且只缩小某些文件。我非常接近使用 OptimizeCSSAssetsPlugin 的工作解决方案,我使用 assetNameRegExp 选项来选择我想要缩小的 CSS 文件。

我现在花了一段时间试图弄清楚为什么我的所有其他 CSS 文件都仍然 被缩小了。结果是 sass-loader 总是 在生产模式下缩小你的 CSS 无论你是否愿意。

这是我的完整 webpack.config.js

const CopyWebpackPlugin = require('copy-webpack-plugin');
const FixStyleOnlyEntriesPlugin = require('webpack-fix-style-only-entries');
const FractalWebpackPlugin = require('fractal-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin');
const path = require('path');
const PrettierPlugin = require('prettier-webpack-plugin');
const StyleLintPlugin = require('stylelint-webpack-plugin');
const TerserPlugin = require('terser-webpack-plugin');

module.exports = (env, argv) => {
    /**
     * Determine if is production mode from the command executed
     */
    const isProduction = argv.mode === 'production';

    /**
     * Common paths
     */
    const paths = {
        src: 'src',
        dev: 'public',
        prod: 'public'
    };

    /**
     * Generate the settings for webpack depending on if it is
     * development or production mode.
     */
    const settings = {
        mode: isProduction ? 'production' : 'development',
        outputDir: isProduction ? paths.prod : paths.dev,
        fractal: {
            mode: isProduction ? 'build' : 'server',
            sync: isProduction ? false : true
        }
    };

    return {
        // Mode is set by --mode property in command
        mode: settings.mode,

        /**
         * 3 entries:
         *      designSystem: This is Design System UI specific CSS
         *      website: This is website & component specific CSS
         *      app: This is the website & component specific JS
         */
        entry: {
            /**
             * Main website and Design System CSS files
             */
            designSystem: path.resolve(__dirname, `./${paths.src}/theme/scss/theme.scss`),
            website: path.resolve(__dirname, `./${paths.src}/scss/styles.scss`),

            /**
             * Specific enteries for all comonents to generate a CSS file specific to that component
             */
            headings: path.resolve(__dirname, `./${paths.src}/patterns/01-branding/03-typography/02-headings/headings.scss`),
            paragraphs: path.resolve(__dirname, `./${paths.src}/patterns/01-branding/03-typography/03-paragraphs/paragraphs.scss`),
            inlineElements: path.resolve(__dirname, `./${paths.src}/patterns/01-branding/03-typography/04-inline-elements/inline-elements.scss`),
            ordered: path.resolve(__dirname, `./${paths.src}/patterns/01-branding/03-typography/05-lists/ordered/ordered.scss`),
            unordered: path.resolve(__dirname, `./${paths.src}/patterns/01-branding/03-typography/05-lists/unordered/unordered.scss`),
            images: path.resolve(__dirname, `./${paths.src}/patterns/01-branding/06-images/images.scss`),
            spacers: path.resolve(__dirname, `./${paths.src}/patterns/02-ui-components/01-layout/02-spacers/spacers.scss`),
            primaryButton: path.resolve(__dirname, `./${paths.src}/patterns/02-ui-components/02-buttons/primary-button/primary-button.scss`),
            secondaryButton: path.resolve(__dirname, `./${paths.src}/patterns/02-ui-components/02-buttons/secondary-button/secondary-button.scss`),
            tertiaryButton: path.resolve(__dirname, `./${paths.src}/patterns/02-ui-components/02-buttons/tertiary-button/tertiary-button.scss`),
            checkboxes: path.resolve(__dirname, `./${paths.src}/patterns/02-ui-components/03-form-elements/checkboxes/checkboxes.scss`),
            inputs: path.resolve(__dirname, `./${paths.src}/patterns/02-ui-components/03-form-elements/inputs/inputs.scss`),
            labels: path.resolve(__dirname, `./${paths.src}/patterns/02-ui-components/03-form-elements/labels/labels.scss`),
            radios: path.resolve(__dirname, `./${paths.src}/patterns/02-ui-components/03-form-elements/radios/radios.scss`),
            selects: path.resolve(__dirname, `./${paths.src}/patterns/02-ui-components/03-form-elements/selects/selects.scss`),
            textareas: path.resolve(__dirname, `./${paths.src}/patterns/02-ui-components/03-form-elements/textareas/textareas.scss`),
            footer: path.resolve(__dirname, `./${paths.src}/patterns/02-ui-components/04-footer/footer.scss`),
            navigation: path.resolve(__dirname, `./${paths.src}/patterns/02-ui-components/05-navigation/navigation.scss`),
            informationPanel: path.resolve(__dirname, `./${paths.src}/patterns/02-ui-components/06-information-panel/information-panel.scss`),
            informationPill: path.resolve(__dirname, `./${paths.src}/patterns/02-ui-components/07-information-pill/information-pill.scss`),
            modal: path.resolve(__dirname, `./${paths.src}/patterns/02-ui-components/08-modal/modal.scss`),

            /**
             * Main website and Design System JS files
             */
            app: [
                'regenerator-runtime/runtime',
                'core-js/modules/es6.array.from',
                'core-js/modules/es6.array.for-each',
                'core-js/modules/es6.object.assign',
                'core-js/modules/es6.promise',
                path.resolve(__dirname, `./${paths.src}/js/app.js`)
            ]
        },

        /**
         * JS output goes into the scripts folder and depending on mode will
         * either go into the public or the dist folder with it's chunks
         */
        output: {
            path: path.resolve(__dirname, `./${settings.outputDir}`),
            filename: 'scripts/[name].js',
            chunkFilename: 'scripts/[name].chunk.js'
        },

        module: {
            rules: [
                {
                    parser: {
                        amd: false
                    }
                },
                {
                    /**
                     * Load JS files with Babel Loader and set to transpile code to work
                     * in IE10 and above.
                     */
                    test: /\.(js)$/,
                    exclude: /node_modules/,
                    use: [
                        {
                            loader: 'babel-loader',
                            options: {
                                configFile: './babel.config.js',
                                presets: [
                                    [
                                        '@babel/preset-env',
                                        {
                                            useBuiltIns: 'entry',
                                            corejs: '^3.1.4',
                                            targets: {
                                                browsers: ['defaults, ie >= 10']
                                            }
                                        }
                                    ]
                                ]
                            }
                        },
                        {
                            loader: 'eslint-loader',
                            options: {
                                configFile: '.eslintrc.json'
                            }
                        }
                    ]
                },
                {
                    /**
                     * Load SASS files with 2 loaders
                     *      PostCSS: This converts the SCSS to CSS, adds in polyfills for flexbox,
                     *               auto prefixes and adds in normalise CSS.
                     *      SASS Loader: This generates source maps for CSS.
                     */
                    test: /\.(scss|sass)$/,
                    use: [
                        {
                            loader: MiniCssExtractPlugin.loader
                        },
                        {
                            loader: 'css-loader',
                            options: {
                                sourceMap: true
                            }
                        },
                        {
                            loader: 'postcss-loader',
                            options: {
                                plugins: () => [
                                    require('postcss-flexbugs-fixes'),
                                    require('postcss-preset-env')({
                                        autoprefixer: {
                                            flexbox: 'no-2009'
                                        },
                                        stage: 3
                                    }),
                                    require('autoprefixer')()
                                ],
                                sourceMap: true,
                                minimize: false
                            }
                        },
                        {
                            loader: 'sass-loader',
                            options: {
                                sourceMap: true,
                                minimize: false,
                                outputStyle: 'uncompressed'
                            }
                        }
                    ]
                },
                {
                    /**
                     * This looks for all images and uses the File Loader to move them to
                     * the output directory. It excludes the fonts directory so there is no
                     * duplication of SVG files
                     */
                    test: /\.(png|jpg|jpeg|gif|svg)$/,
                    exclude: /fonts/,
                    use: [
                        {
                            loader: 'file-loader',
                            options: {
                                name: '[folder]/[name].[ext]',
                                outputPath: '/images'
                            }
                        }
                    ]
                },
                {
                    /**
                     * This looks for all font files and uses the File Loader to
                     * move hem to the output directory. It excludes the images directory
                     * so there is no duplication of SVG files
                     */
                    test: /\.(woff|woff2|eot|ttf|otf|svg)$/,
                    exclude: /images/,
                    use: [
                        {
                            loader: 'file-loader',
                            options: {
                                name: '[folder]/[name].[ext]',
                                outputPath: '/fonts'
                            }
                        }
                    ]
                }
            ]
        },

        plugins: [
            /**
             * This prevents webpack from generating a JS file for SCSS entries
             */
            new FixStyleOnlyEntriesPlugin(),
            /**
             * Runs SASS linting
             */
            new StyleLintPlugin({
                configFile: '.stylelintrc.json',
                context: 'src',
                files: '**/*.scss',
                failOnError: false,
                quiet: false,
                emitErrors: true
            }),
            /**
             * This outputs SCSS entires into CSS files and thier chunks
             */
            new MiniCssExtractPlugin({
                filename: 'style/[name].css',
                chunkFilename: 'style/[name].chunk.css'
            }),
            /**
             * Runs Fractal in either server mode for dev and build mode for
             * production.
             */
            new FractalWebpackPlugin({
                mode: settings.fractal.mode,
                sync: settings.fractal.sync
            }),
            /**
             * Copies images over to the output directory
             */
            new CopyWebpackPlugin([
                {
                    from: path.resolve(__dirname, `./${paths.src}/images`),
                    to: 'images'
                }
            ]),
            // new PrettierPlugin()
        ],

        /**
         * This only runs when in production mode and will minify JS and CSS
         */
        optimization: {
            minimize: true,
            minimizer: [
                new OptimizeCSSAssetsPlugin({
                    assetNameRegExp: /style\/(website|designSystem).css/,
                    cssProcessor: require('cssnano')
                }),
                new TerserPlugin({
                    include: /\/js/,
                    exclude: /\/scss/
                })
            ]
        },

        /**
         * Generates source maps
         */
        devtool: 'source-maps'
    };
};

我终于弄清楚了我的问题并想 post 答案,这样如果以后有人遇到这个问题,他们可以解决它。

不管我的问题是什么,一开始我实际上并不知道是哪个加载器导致了这个问题,在做了一些研究之后我最初认为 css-loader 是罪魁祸首。我深入研究了代码,发现 css-loader 中没有缩小。下一个要研究的加载器是 sass-loader,经过大量研究我最终发现 sass-loader 正在做缩小。查看 sass-loader 文档,我似乎没有找到任何关于缩小或如何停止它的信息。经过大量谷歌搜索后,我最终找到了记录很差的选项 outputStyle.

据我所知,outputStyle 有 3 个选项:

outputStyle: 'compressed'
outputStyle: 'uncompressed'
outputStyle: 'expanded'

这是我的魔法选项,而 sass-loader 似乎没有注意到 webpack.config.js 中的 minimize: false 它会听取 outputStyle 选项。这将关闭所有 CSS 文件的缩小。然后,这允许 OptimizeCSSAssetsPlugin 发挥作用并缩小您需要的文件。

这里如果新sass-loader代码:

{
    loader: 'sass-loader',
    options: {
        sourceMap: true,
        minimize: false,
        outputStyle: 'expanded'
    }
}

这是完整的webpack.config.js

const CopyWebpackPlugin = require('copy-webpack-plugin');
const FixStyleOnlyEntriesPlugin = require('webpack-fix-style-only-entries');
const FractalWebpackPlugin = require('fractal-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin');
const path = require('path');
const PrettierPlugin = require('prettier-webpack-plugin');
const StyleLintPlugin = require('stylelint-webpack-plugin');
const TerserPlugin = require('terser-webpack-plugin');

module.exports = (env, argv) => {
    /**
     * Determine if is production mode from the command executed
     */
    const isProduction = argv.mode === 'production';

    /**
     * Common paths
     */
    const paths = {
        src: 'src',
        dev: 'public',
        prod: 'public'
    };

    /**
     * Generate the settings for webpack depending on if it is
     * development or production mode.
     */
    const settings = {
        mode: isProduction ? 'production' : 'development',
        outputDir: isProduction ? paths.prod : paths.dev,
        fractal: {
            mode: isProduction ? 'build' : 'server',
            sync: isProduction ? false : true
        }
    };

    return {
        // Mode is set by --mode property in command
        mode: settings.mode,

        /**
         * 3 entries:
         *      designSystem: This is Design System UI specific CSS
         *      website: This is website & component specific CSS
         *      app: This is the website & component specific JS
         */
        entry: {
            /**
             * Main website and Design System CSS files
             */
            designSystem: path.resolve(__dirname, `./${paths.src}/theme/scss/theme.scss`),
            website: path.resolve(__dirname, `./${paths.src}/scss/styles.scss`),

            /**
             * Specific enteries for all comonents to generate a CSS file specific to that component
             */
            headings: path.resolve(__dirname, `./${paths.src}/patterns/01-branding/03-typography/02-headings/headings.scss`),
            paragraphs: path.resolve(__dirname, `./${paths.src}/patterns/01-branding/03-typography/03-paragraphs/paragraphs.scss`),
            inlineElements: path.resolve(__dirname, `./${paths.src}/patterns/01-branding/03-typography/04-inline-elements/inline-elements.scss`),
            ordered: path.resolve(__dirname, `./${paths.src}/patterns/01-branding/03-typography/05-lists/ordered/ordered.scss`),
            unordered: path.resolve(__dirname, `./${paths.src}/patterns/01-branding/03-typography/05-lists/unordered/unordered.scss`),
            images: path.resolve(__dirname, `./${paths.src}/patterns/01-branding/06-images/images.scss`),
            spacers: path.resolve(__dirname, `./${paths.src}/patterns/02-ui-components/01-layout/02-spacers/spacers.scss`),
            primaryButton: path.resolve(__dirname, `./${paths.src}/patterns/02-ui-components/02-buttons/primary-button/primary-button.scss`),
            secondaryButton: path.resolve(__dirname, `./${paths.src}/patterns/02-ui-components/02-buttons/secondary-button/secondary-button.scss`),
            tertiaryButton: path.resolve(__dirname, `./${paths.src}/patterns/02-ui-components/02-buttons/tertiary-button/tertiary-button.scss`),
            checkboxes: path.resolve(__dirname, `./${paths.src}/patterns/02-ui-components/03-form-elements/checkboxes/checkboxes.scss`),
            inputs: path.resolve(__dirname, `./${paths.src}/patterns/02-ui-components/03-form-elements/inputs/inputs.scss`),
            labels: path.resolve(__dirname, `./${paths.src}/patterns/02-ui-components/03-form-elements/labels/labels.scss`),
            radios: path.resolve(__dirname, `./${paths.src}/patterns/02-ui-components/03-form-elements/radios/radios.scss`),
            selects: path.resolve(__dirname, `./${paths.src}/patterns/02-ui-components/03-form-elements/selects/selects.scss`),
            textareas: path.resolve(__dirname, `./${paths.src}/patterns/02-ui-components/03-form-elements/textareas/textareas.scss`),
            footer: path.resolve(__dirname, `./${paths.src}/patterns/02-ui-components/04-footer/footer.scss`),
            navigation: path.resolve(__dirname, `./${paths.src}/patterns/02-ui-components/05-navigation/navigation.scss`),
            informationPanel: path.resolve(__dirname, `./${paths.src}/patterns/02-ui-components/06-information-panel/information-panel.scss`),
            informationPill: path.resolve(__dirname, `./${paths.src}/patterns/02-ui-components/07-information-pill/information-pill.scss`),
            modal: path.resolve(__dirname, `./${paths.src}/patterns/02-ui-components/08-modal/modal.scss`),

            /**
             * Main website and Design System JS files
             */
            app: [
                'regenerator-runtime/runtime',
                'core-js/modules/es6.array.from',
                'core-js/modules/es6.array.for-each',
                'core-js/modules/es6.object.assign',
                'core-js/modules/es6.promise',
                path.resolve(__dirname, `./${paths.src}/js/app.js`)
            ]
        },

        /**
         * JS output goes into the scripts folder and depending on mode will
         * either go into the public or the dist folder with it's chunks
         */
        output: {
            path: path.resolve(__dirname, `./${settings.outputDir}`),
            filename: 'scripts/[name].js',
            chunkFilename: 'scripts/[name].chunk.js'
        },

        module: {
            rules: [
                {
                    parser: {
                        amd: false
                    }
                },
                {
                    /**
                     * Load JS files with Babel Loader and set to transpile code to work
                     * in IE10 and above.
                     */
                    test: /\.(js)$/,
                    exclude: /node_modules/,
                    use: [
                        {
                            loader: 'babel-loader',
                            options: {
                                configFile: './babel.config.js',
                                presets: [
                                    [
                                        '@babel/preset-env',
                                        {
                                            useBuiltIns: 'entry',
                                            corejs: '^3.1.4',
                                            targets: {
                                                browsers: ['defaults, ie >= 10']
                                            }
                                        }
                                    ]
                                ]
                            }
                        },
                        {
                            loader: 'eslint-loader',
                            options: {
                                configFile: '.eslintrc.json'
                            }
                        }
                    ]
                },
                {
                    /**
                     * Load SASS files with 2 loaders
                     *      PostCSS: This converts the SCSS to CSS, adds in polyfills for flexbox,
                     *               auto prefixes and adds in normalise CSS.
                     *      SASS Loader: This generates source maps for CSS.
                     */
                    test: /\.(scss|sass)$/,
                    use: [
                        {
                            loader: MiniCssExtractPlugin.loader
                        },
                        {
                            loader: 'css-loader',
                            options: {
                                sourceMap: true
                            }
                        },
                        {
                            loader: 'postcss-loader',
                            options: {
                                plugins: () => [
                                    require('postcss-flexbugs-fixes'),
                                    require('postcss-preset-env')({
                                        autoprefixer: {
                                            flexbox: 'no-2009'
                                        },
                                        stage: 3
                                    }),
                                    require('autoprefixer')()
                                ],
                                sourceMap: true,
                                minimize: false
                            }
                        },
                        {
                            loader: 'sass-loader',
                            options: {
                                sourceMap: true,
                                minimize: false,
                                outputStyle: 'expanded'
                            }
                        }
                    ]
                },
                {
                    /**
                     * This looks for all images and uses the File Loader to move them to
                     * the output directory. It excludes the fonts directory so there is no
                     * duplication of SVG files
                     */
                    test: /\.(png|jpg|jpeg|gif|svg)$/,
                    exclude: /fonts/,
                    use: [
                        {
                            loader: 'file-loader',
                            options: {
                                name: '[folder]/[name].[ext]',
                                outputPath: '/images'
                            }
                        }
                    ]
                },
                {
                    /**
                     * This looks for all font files and uses the File Loader to
                     * move hem to the output directory. It excludes the images directory
                     * so there is no duplication of SVG files
                     */
                    test: /\.(woff|woff2|eot|ttf|otf|svg)$/,
                    exclude: /images/,
                    use: [
                        {
                            loader: 'file-loader',
                            options: {
                                name: '[folder]/[name].[ext]',
                                outputPath: '/fonts'
                            }
                        }
                    ]
                }
            ]
        },

        plugins: [
            /**
             * This prevents webpack from generating a JS file for SCSS entries
             */
            new FixStyleOnlyEntriesPlugin(),
            /**
             * Runs SASS linting
             */
            new StyleLintPlugin({
                configFile: '.stylelintrc.json',
                context: 'src',
                files: '**/*.scss',
                failOnError: false,
                quiet: false,
                emitErrors: true
            }),
            /**
             * This outputs SCSS entires into CSS files and thier chunks
             */
            new MiniCssExtractPlugin({
                filename: 'style/[name].css',
                chunkFilename: 'style/[name].chunk.css'
            }),
            /**
             * Runs Fractal in either server mode for dev and build mode for
             * production.
             */
            new FractalWebpackPlugin({
                mode: settings.fractal.mode,
                sync: settings.fractal.sync
            }),
            /**
             * Copies images over to the output directory
             */
            new CopyWebpackPlugin([
                {
                    from: path.resolve(__dirname, `./${paths.src}/images`),
                    to: 'images'
                }
            ]),
            // new PrettierPlugin()
        ],

        /**
         * This only runs when in production mode and will minify JS and CSS
         */
        optimization: {
            minimize: true,
            minimizer: [
                new OptimizeCSSAssetsPlugin({
                    assetNameRegExp: /style\/(website|designSystem).css/,
                    cssProcessor: require('cssnano')
                }),
                new TerserPlugin({
                    include: /\/js/,
                    exclude: /\/scss/
                })
            ]
        },

        /**
         * Generates source maps
         */
        devtool: 'source-maps'
    };
};