MiniCssExtractPlugin

MiniCssExtractPlugin他是一个可以将css代码从打包结果当中提取出来的插件,那通过这个插件我们就可以时间css模块的按需加载。

他的使用也非常简单,我们回到项目当中,我们先安装一下这个插件。

yarn add mini-css-extract-plugin

我们打开webpack的配置文件,这里我们需要先导入这个插件的模块, 那导入过后我们就可以将这个插件添加到配置对象的plugins数组当中。

那这样的话MiniCssExtractPlugin他在工作时就会自动提取我们代码当中的css到一个单独文件当中。

那除此以外呢,目前我们所使用的样式模块,他是先交给css-loader去解析,然后再交给style-loader去处理。这里的style-loader他的作用就是将我们样式代码通过style标签的方式注入到页面当中,从而使样式可以工作。

那使用MiniCssExtractPlugin的话,我们的样式就会单独存放到文件当中也就不需要style标签,而是直接通过link的方式去引入。所以说这里我们就不再需要style-loader了。

取而代之我们所使用的是MiniCssExtractPlugin当中所提供的一个loader,来去实现我们样式文件通过link标签的方式去注入。

const { CleanWebpackPlugin } = require('clean-webpack-plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
module.exports = allModes.map(item => {
    return {
        mode: 'none',
        entry: {
            main: './src/index.js',
        },
        output: {
            filename: `[name].bundle.js`,
        },
        module: {
            rules: [
                {
                    test: /\.css$/,
                    use: [
                        MiniCssExtractPlugin.loader,
                        // 'style-loader',
                        'css-loader'
                    ]
                }
            ]
        },
        plugins: [
            new CleanWebpackPlugin(),
            new HtmlWebpackPlugin({
                template: './src/index.html',
                filename: `index.html`
            }),
            new MiniCssExtractPlugin()
        ]
    }
})

完成以后我们重新打包,完成过后我们就可以在dist目录中看到提取出来的样式文件了。

不过这里我们需要注意一点,如果说样式文件他的体积不是很大的话,那提取他到单个文件当中,那效果可能会适得其反。

我个人的经验是如果说css文件超过了150kb左右才需要考虑是否将他提取到单独文件当中。

否则的话,其实css嵌入到代码当中他减少了一次请求,效果可能会更好。

OptimizeCssAssetsWebpackPlugin

使用了MiniCssExtractPlugin过后样式文件就可以被提取到单独的css文件当中了,但是这里同样会有一个小问题。

我们回到命令行,这里我们尝试以生产模式去运行打包。

yarn webpack --mode production

那按照之前的了解,在生产模式下webpack会自动去压缩输出的结果。但是我们这里打开输出的样式文件会发现我们样式文件根本没有任何的变化。

那这是因为,webpack内置的压缩插件,他紧紧是针对于js文件的压缩,那对于其他资源文件压缩,都需要额外的插件去支持。

webpack官方推荐了一个optimize-css-assets-webpack-plugin, 我们可以使用这个插件来去压缩我们的样式文件。

那我们先来安装一下这个插件。

yarn add optimize-css-assets-webpack-plugin

回到配置文件当中,我们需要先导入这个插件,导入完成过后呢,我们去把这个插件添加到配置对象的plugins数组当中。

const { CleanWebpackPlugin } = require('clean-webpack-plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const OptimizeCssAssetsWebpackPlugin = require('optimize-css-assets-webpack-plugin');
module.exports = allModes.map(item => {
    return {
        mode: 'none',
        entry: {
            main: './src/index.js',
        },
        output: {
            filename: `[name].bundle.js`,
        },
        module: {
            rules: [
                {
                    test: /\.css$/,
                    use: [
                        MiniCssExtractPlugin.loader,
                        // 'style-loader',
                        'css-loader'
                    ]
                }
            ]
        },
        plugins: [
            new CleanWebpackPlugin(),
            new HtmlWebpackPlugin({
                template: './src/index.html',
                filename: `index.html`
            }),
            new MiniCssExtractPlugin(),
            new OptimizeCssAssetsWebpackPlugin()
        ]
    }
})

我们再次使用生产模式打包, 那这次打包完成过后呢,我们的样式文件就以压缩文件的格式去输出了。

不过这里还有一个额外的小点,可能大家在官方文档当中会发现文档当中这个插件并不是配置在plugins数组当中的。而是添加到了optimization属性当中的minimize属性当中。

那这是为什么呢?其实也非常简单,如果说我们把这个插件配置到plugins数组当中,那这个插件在任何情况下都会正常工作。而配置在minimize数组当中的话,那只会在minimize这样一个特性开启时才会工作。

所以说webpack建议,像这种压缩类的插件我们应该配置到minimize数组当中,以便于我们可以通过minimize这个选项去统一控制。

那这里我们尝试把这个插件移至到我们的optimization属性当中的minimize数组当中。

const { CleanWebpackPlugin } = require('clean-webpack-plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const OptimizeCssAssetsWebpackPlugin = require('optimize-css-assets-webpack-plugin');
module.exports = allModes.map(item => {
    return {
        mode: 'none',
        entry: {
            main: './src/index.js',
        },
        output: {
            filename: `[name].bundle.js`,
        },
        optimization: {
            minimize: [
                new OptimizeCssAssetsWebpackPlugin()
            ]
        },
        module: {
            rules: [
                {
                    test: /\.css$/,
                    use: [
                        MiniCssExtractPlugin.loader,
                        // 'style-loader',
                        'css-loader'
                    ]
                }
            ]
        },
        plugins: [
            new CleanWebpackPlugin(),
            new HtmlWebpackPlugin({
                template: './src/index.html',
                filename: `index.html`
            }),
            new MiniCssExtractPlugin(),
            new OptimizeCssAssetsWebpackPlugin()
        ]
    }
})

重新打包,那此时如果说我们没有开启压缩这个功能的话,那这个插件他就不会工作。反之如果说我们以生产模式打包,那么minimize属性就会自动开启,那这个压缩插件就会自动工作。我们的样式文件也就会被压缩。

但是这么配置也有一个小小的缺点,我们可以看一下输出的js文件,那这时候你会发现,原本可以自动压缩的js,这次确不能自动压缩了。

那这是因为我们这里设置了minimize这个数组,webpack认为我们如果配置了这个数组,那就是要去自定义所使用的的压缩器插件。那内部的js压缩器就会被覆盖掉,所以说我们这里需要再手动的把他添加回来。

那内置的js压缩插件叫做terser-webpack-plugin,我们需要安装这个模块,

yarn add terser-webpack-plugin --dev

安装完成过后我们再来把这个插件手动的去添加到minimize这个数组当中

const { CleanWebpackPlugin } = require('clean-webpack-plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const OptimizeCssAssetsWebpackPlugin = require('optimize-css-assets-webpack-plugin');
const TerserWebpackPlugin = require('terser-webpack-plugin');
module.exports = allModes.map(item => {
    return {
        mode: 'none',
        entry: {
            main: './src/index.js',
        },
        output: {
            filename: `[name].bundle.js`,
        },
        optimization: {
            minimize: [
                new OptimizeCssAssetsWebpackPlugin(),
                new TerserWebpackPlugin()
            ]
        },
        module: {
            rules: [
                {
                    test: /\.css$/,
                    use: [
                        MiniCssExtractPlugin.loader,
                        // 'style-loader',
                        'css-loader'
                    ]
                }
            ]
        },
        plugins: [
            new CleanWebpackPlugin(),
            new HtmlWebpackPlugin({
                template: './src/index.html',
                filename: `index.html`
            }),
            new MiniCssExtractPlugin(),
            new OptimizeCssAssetsWebpackPlugin()
        ]
    }
})

那这样的话,如果我们再以生产模式运行打包,然后js文件,css文件都可以正常被压缩了。那如果说我们以普通模式打包,也就是不开启压缩的话,那他也就不会以压缩的形式输出了。

输出文件名 Hash

一般我们去部署前端的资源文件时,都会启用服务器的静态资源缓存,那这样的话对应用户的浏览器而言,他就可以缓存住我们应用当中的静态资源。那后续就不再需要请求服务器,得到这些静态资源文件了。

那这样,整体我们应用的响应速度就有一个大幅度的提升。

不过呢,开启静态资源的客户端缓存,他也会有一些小小的问题,那如果说我们在缓存策略中我们的缓存失效时间设置的过短的话,那效果就不是特别明显。

那如果说我们把过期时间设置的比较长,那一但说我们在这个过程中应用发生了更新重新部署过后又没有办法及时更新到客户端。

那为了解决这样一个问题我们建议在生产模式下,我们需要给输出的文件名当中添加Hash值,那这样的话,一旦我们的资源文件,发生改变,那我们的文件名称也可以跟着一起去变化。

那对于客户端而言,全新的文件名就是全新的请求,那也就没有缓存的问题,那这样的话我们就可以把服务端缓存策略的缓存时间设置的非常长,也就不用担心文件更新过后的问题。

webpack中的filename属性和绝大多数插件的filename属性,都支持通过占位符的方式来去为文件名设置hash,不过这里支持三种hash效果各不相同。那这里我们来分别尝试一下。

首先就是最普通的hash我们可以通过[hash]然后去拿到,那这个hash它实际上是整个项目级别的,也就是说一旦项目当中有任何一个地方发生改动,那我们这一次打包过程当中的hash值都会发生变化。

const { CleanWebpackPlugin } = require('clean-webpack-plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const OptimizeCssAssetsWebpackPlugin = require('optimize-css-assets-webpack-plugin');
const TerserWebpackPlugin = require('terser-webpack-plugin');
module.exports = allModes.map(item => {
    return {
        mode: 'none',
        entry: {
            main: './src/index.js',
        },
        output: {
            filename: `[name]-[hash].bundle.js`,
        },
        optimization: {
            minimize: [
                new OptimizeCssAssetsWebpackPlugin(),
                new TerserWebpackPlugin()
            ]
        },
        module: {
            rules: [
                {
                    test: /\.css$/,
                    use: [
                        MiniCssExtractPlugin.loader,
                        // 'style-loader',
                        'css-loader'
                    ]
                }
            ]
        },
        plugins: [
            new CleanWebpackPlugin(),
            new HtmlWebpackPlugin({
                template: './src/index.html',
                filename: `index.html`
            }),
            new MiniCssExtractPlugin({
                filename: '[name]-[hash].bundle.css'
            }),
        ]
    }
})

我们这里可以在任意一个代码当中做下修改,然后尝试重新打包,那此时你就会发现,我们的hash值全部发生改变了。

其次呢是chunkhash,那这个hash是chunk级别的,也就是在我们打包过程中,只要是同一路的打包,那chunkhash他都是相同的。

const { CleanWebpackPlugin } = require('clean-webpack-plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const OptimizeCssAssetsWebpackPlugin = require('optimize-css-assets-webpack-plugin');
const TerserWebpackPlugin = require('terser-webpack-plugin');
module.exports = allModes.map(item => {
    return {
        mode: 'none',
        entry: {
            main: './src/index.js',
        },
        output: {
            filename: `[name]-[chunkhash].bundle.js`,
        },
        optimization: {
            minimize: [
                new OptimizeCssAssetsWebpackPlugin(),
                new TerserWebpackPlugin()
            ]
        },
        module: {
            rules: [
                {
                    test: /\.css$/,
                    use: [
                        MiniCssExtractPlugin.loader,
                        // 'style-loader',
                        'css-loader'
                    ]
                }
            ]
        },
        plugins: [
            new CleanWebpackPlugin(),
            new HtmlWebpackPlugin({
                template: './src/index.html',
                filename: `index.html`
            }),
            new MiniCssExtractPlugin({
                filename: '[name]-[chunkhash].bundle.css'
            }),
        ]
    }
})

那我们这里虽然只配置了一个打包入口index,但是呢在我们的代码当中通过动态导入的方式又分别形成了两路chunk分别是posts和album。

那样式文件是从这个代码当中单独提取出来的,所以说他并不是单独的chunk,所以我们这所看到的结果呢就是main和posts还是album他们三者chunkhash各不相同。

而我们css和所对应的js文件,他们二者的chunkhash是完全一样的。因为他们是同一路。

那这里我们现在index当中尝试做一些修改,然后重新打包。那这时候你会发现,只有main.bundle的文件名发生了变化,其他的文件都没有变。

然后我们再尝试在posts.js文件中做一些修改,那此次我们posts所输出的js和css都会发生变化,因为我们刚刚说过了,他们是属于同一个chunk。

至于main.bundle他也会发生变化的原因是因为我们posts所生成的这个js文件和css文件他的文件名发生变化,那我们再入口文件中去引入他们的路径也会发生变化。所以说mian.chunk他算是一个被动的改变。

那相比于普通的hash,chunkhash的控制要更精确一点。

最后还有一个contenthash,那这个hash他其实是文件级别的hash,他其实是再根据输出文件的内容生成的hash值。也就是说只要是不同的文件,他就有不同的hash值。

const { CleanWebpackPlugin } = require('clean-webpack-plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const OptimizeCssAssetsWebpackPlugin = require('optimize-css-assets-webpack-plugin');
const TerserWebpackPlugin = require('terser-webpack-plugin');
module.exports = allModes.map(item => {
    return {
        mode: 'none',
        entry: {
            main: './src/index.js',
        },
        output: {
            filename: `[name]-[contenthash].bundle.js`,
        },
        optimization: {
            minimize: [
                new OptimizeCssAssetsWebpackPlugin(),
                new TerserWebpackPlugin()
            ]
        },
        module: {
            rules: [
                {
                    test: /\.css$/,
                    use: [
                        MiniCssExtractPlugin.loader,
                        // 'style-loader',
                        'css-loader'
                    ]
                }
            ]
        },
        plugins: [
            new CleanWebpackPlugin(),
            new HtmlWebpackPlugin({
                template: './src/index.html',
                filename: `index.html`
            }),
            new MiniCssExtractPlugin({
                filename: '[name]-[contenthash].bundle.css'
            }),
        ]
    }
})

那这里我们先尝试在index当中做一些修改,然后重新打包,那此时同样只有main.bundle文件名发生了变化。

然后我们再去修改posts.css,那这时候你会发现,posts所对应的样式文件的文件名就发生了变化,而main.bundle同样也是因为路径的原因才被动更新。

那相比于前两者,contenthash应该是解决缓存问题最好的方式了,因为他精确的定位到了文件级别的hash,那只有当这个文件发生了变化才有可能去更新掉他的这个文件名,那这个实际上是最适合我们去解决缓存问题的。

那另外如果说你觉得我们这个20位长度的hash太长的话,那webpack还允许我们去指定hash的长度,我们可以在占位符里面通过冒号跟一个数组([:8])的方式来去指定我们hash的长度,我们这里设置长度为8。

new MiniCssExtractPlugin({
    filename: '[name]-[contenthash:8].bundle.css'
})

那总的来说我个人觉得如果说是控制缓存的话,8位的contenthash应该是最好的选择了。