Webpack详解

642 阅读5分钟

下载webpack

  1. 全局下载webpackwebpack-cli

    cnpm install webpack webpack-cli -g
    
    // 使用webpack -v 查看版本号
    
  • 项目内部下载webpack(为了解决webpack版本兼容)

    打开项目文件夹使用命令

    cnpm i webpack webpack-cli -D
    

    因为在项目内部使用使用webpack -v 查看版本号会默认去全局目录查找,所以要使用npx webpck -v查看本地版本号

    npx webpck -v
    

    注意:

    • npx命令会帮助我们去node_moudles去查找文件

    • webpack-cli可以帮助我们正确在命令行里运行webpack命令

    拓展:

    • 【npm uninstall xxx】删除xxx模块;
    • 【npm uninstall -g xxx】删除全局模块xxx;
  1. 项目内部下载固定版本号的webpack
    • 首先用npm info webpack 可以查看到webpack存在的版本
    • cnpm i webpack@4.16.5 webpack-cli -D

简单的webpck.config.js

webpck.config.js

as we konw webpack打包需要一个配置文件 来告诉它需要如何完成打包 这个文件就是webpck.config.js

const path = require('path')

module.exports = {
    // mode设置为production会自动压缩文件 设置为 development则不会压缩
    mode: 'production',
	entry: './src/index.js',
	output: {
		filename: 'bundle.js',
		path: path.resolve(__dirname, 'dist')
	}
}

运行命令开始打包

npx webpack

简化npx webpack命令

  • 使用npm run script来简化npx webpack命令

  • 在packjson.js中修改script字段

    "script": {
        // 会默认去本地查找webpack依赖
    	"bundle": "webpack"
    }
    
  • 以后运行 npx webpack就可以直接使用

    npm run bundle
    

loader是什么

因为webpack不能识别非js结尾的文件,所以就需要借助loader来处理

loader就是一个打包方案,告诉你打包什么样的文件做什么样的处理

添加图片打包功能

需要下载file-loader

cnpm i file-loader -D
const path = require('path')

module.exports = {
    // mode设置为production会自动压缩文件 设置为 development则不会压缩
    mode: 'production',
	entry: './src/index.js',
    module: {
        // 配置图片打包
        rules: [{
            test: /\.jpg$/,
            use: {
                loader: 'file-loader'
            }
        }]
    }
	output: {
		filename: 'bundle.js',
		path: path.resolve(__dirname, 'dist')
	}
}

使用loader打包静态资源(图片篇)

placeholder 占位符

    module: {
        // 配置图片打包
        rules: [{
            test: /\.(jpg|png|gif)$/,
            use: {
                loader: 'file-loader'
                option: {
                	// placeholder 占位符
                	name: '[name]_[hash].[ext]',
                	// 打包目录
                	outputPath: 'images/'
                }
            }
        }]
    }

url-loader

url-loader是通过把图片改为base64放在里JS文件里

url-loader相比file-loader多了一个limit配置项

**注意:**如果图片过大则会影响JS加载时间,所以需要对图片的大小做限制

    module: {
        // 配置图片打包
        rules: [{
            test: /\.(jpg|png|gif)$/,
            use: {
                loader: 'url-loader'
                option: {
                	// placeholder 占位符
                	name: '[name]_[hash].[ext]',
                	// 打包目录
                	outputPath: 'images/',
                	// 大小限制
                	limit: 10240
                }
            }
        }]
    }

使用loader打包静态资源(样式篇)

打包普通css文件

简单使用

    module: {
        // 配置图片打包
        rules: [{
            test: /\.(jpg|png|gif)$/,
            use: {
                loader: 'url-loader'
                option: {
                	// placeholder 占位符
                	name: '[name]_[hash].[ext]',
                	// 打包目录
                	outputPath: 'images/',
                	// 大小限制
                	limit: 10240
                }
            }
        }, {
            test: /\.css$/,
            use: ['style-loader', 'css-loader']
        }]
    }
  • css-loader会把css文件整合到一个css文件
  • style-loader会把css文件插入到style标签里去

处理scss文件

    module: {
        // 配置图片打包
        rules: [{
            test: /\.(jpg|png|gif)$/,
            use: {
                loader: 'url-loader'
                option: {
                	// placeholder 占位符
                	name: '[name]_[hash].[ext]',
                	// 打包目录
                	outputPath: 'images/',
                	// 大小限制
                	limit: 10240
                }
            }
        }, {
            test: /\.scss$/,
            use: ['style-loader', 'css-loader', 'scss-loader']
        }]
    }

**注意:**loader的执行顺序是从下到上,从右往左执行的

自动添加css前缀

  1. 使用postcss-loader

        module: {
            // 配置图片打包
            rules: [{
                test: /\.(jpg|png|gif)$/,
                use: {
                    loader: 'url-loader'
                    option: {
                    	// placeholder 占位符
                    	name: '[name]_[hash].[ext]',
                    	// 打包目录
                    	outputPath: 'images/',
                    	// 大小限制
                    	limit: 10240
                    }
                }
            }, {
                test: /\.scss$/,
                use: ['style-loader', 'css-loader', 'scss-loader', 'postcss-loader']
            }]
        }
    
  2. 创建postcss.config.js文件

    module.exports = {
    	plugins: [
    		require('autoprefixer')
    	]
    }
    

css-loader常用配置项

importLoaders,modules

    module: {
        {
            test: /\.scss$/,
            use: [
                'style-loader', 
                // css-loader常用配置项
                {
                	loader: 'css-loader',
                	options: {
                		// 通过@import引入的css也要去走前两个loader(scss-loader和postcss-loader)
                		importLoaders: 2,
                		// 模块化引入css 对应的js文件不引入css则不应用css样式
                		modules: true
                	}
                }, 
                'scss-loader', 
                'postcss-loader'
		   ]
        }]
    }
  • importLoaders: 2 // 过@import引入的css也要去走前两个loader(scss-loader和postcss-loader)
  • modules:true // 模块化引入css 对应的js文件不引入css则不应用css样式

打包字体文件

同样使用file-loader

{
    test: /\.(eot|ttf|svg)$/,
    use: {
        loader: 'file-loader'
    }
}

使用plugins让打包更便捷

plugins可以在webpack运行到某个时刻的时候,帮你做一些事情

htmlWebpackPlugin

htmlWebpackPlugin会在打包结束后,自动生成一个html文件,并把打包生成的js自动引入到这个html文件中

添加template会按照模板来生成html

cnpm i html-webpack-plugin -D

const path = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')

module.exports = {
    // mode设置为production会自动压缩文件 设置为 development则不会压缩
    mode: 'production',
	entry: './src/index.js',
    module: {
        // 配置图片打包
        rules: [{
            test: /\.jpg$/,
            use: {
                loader: 'file-loader'
            }
        }]
    },
    plugins: [
        new HtmlWebpackPlugin({
            template: 'src/index.htlm'
        })
    ]
	output: {
		filename: 'bundle.js',
		path: path.resolve(__dirname, 'dist')
	}
}

cleanWebpackPlugin

每次打包会清空dist目录下的文件

cnpm i clean-webpack-plugin -D

const path = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const CleanWebpackPlugin = require('clean-webpack-plugin')

module.exports = {
    mode: 'production',
	entry: './src/index.js',
    module: {
        rules: [{
            test: /\.jpg$/,
            use: {
                loader: 'file-loader'
            }
        }]
    },
    plugins: [
        // 配置html模板
        new HtmlWebpackPlugin({
            template: 'src/index.htlm'
        }),
        // 每次打包会清空dist目录下的文件
        new CleanWebpackPlugin(['dist'])
    ]
	output: {
		filename: 'bundle.js',
		path: path.resolve(__dirname, 'dist')
	}
}

Entry与Output的基础配置

多入口配置

在配置Entry的时候可以配置多入口,与之相应的Output也需要修改可以输出多个js文件

此时可以使用**[name]**占位符

const path = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const CleanWebpackPlugin = require('clean-webpack-plugin')

module.exports = {
    mode: 'production',
    entry: {
        main: './src/index.js',
        sub: './src/index.js'
    },
    module: {
        rules: [{
            test: /\.jpg$/,
            use: {
                loader: 'file-loader'
            }
        }]
    },
    plugins: [
        new HtmlWebpackPlugin({
            template: 'src/index.htlm'
        }),
        new CleanWebpackPlugin(['dist'])
    ]
	output: {
    	// 使用**[name]**占位符 对应entry输出的文件名
		filename: '[name].js',
		path: path.resolve(__dirname, 'dist')
	}
}

公共前缀publicPath

可以自动添加cdn前缀

const path = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const CleanWebpackPlugin = require('clean-webpack-plugin')

module.exports = {
    mode: 'production',
    entry: {
        main: './src/index.js',
        sub: './src/index.js'
    },
    module: {
        rules: [{
            test: /\.jpg$/,
            use: {
                loader: 'file-loader'
            }
        }]
    },
    plugins: [
        new HtmlWebpackPlugin({
            template: 'src/index.htlm'
        }),
        new CleanWebpackPlugin(['dist'])
    ]
	output: {
    	// 使用publicPath 添加公共前缀
    	publicPath: 'http://cdn.com.cn'
    	// 使用**[name]**占位符 对应entry输出的文件名
		filename: '[name].js',
		path: path.resolve(__dirname, 'dist')
	}
}

SourceMap配置

sourceMap它是一个映射关系,它知道打包后的文件下有语法错误,通过SourceMap的映射,能够对应到开发文件下的具体出错位置

**注意:mode: 'production'**时devtoll会自动开启

选择一种 source map 格式来增强调试过程。不同的值会明显影响到构建(build)和重新构建(rebuild)的速度。

devtool 构建速度 重新构建速度 生产环境 品质(quality)
(none) +++ +++ yes 打包后的代码
eval +++ +++ no 生成后的代码
cheap-eval-source-map + ++ no 转换过的代码(仅限行)
cheap-module-eval-source-map o ++ no 原始源代码(仅限行)
eval-source-map -- + no 原始源代码
cheap-source-map + o no 转换过的代码(仅限行)
cheap-module-source-map o - no 原始源代码(仅限行)
inline-cheap-source-map + o no 转换过的代码(仅限行)
inline-cheap-module-source-map o - no 原始源代码(仅限行)
source-map -- -- yes 原始源代码
inline-source-map -- -- no 原始源代码
hidden-source-map -- -- yes 原始源代码
nosources-source-map -- -- yes 无源代码内容

实例

const path = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const CleanWebpackPlugin = require('clean-webpack-plugin')

module.exports = {
    mode: 'development',
    // 使用SourceMap
    // development devtool: 'cheap-module-eval-source-map'
    // production devtool: 'cheap-module-source-map'
    devtool: 'cheap-module-eval-source-map',
    entry: {
        main: './src/index.js',
        sub: './src/index.js'
    },
    module: {
        rules: [{
            test: /\.jpg$/,
            use: {
                loader: 'file-loader'
            }
        }]
    },
    plugins: [
        new HtmlWebpackPlugin({
            template: 'src/index.htlm'
        }),
        new CleanWebpackPlugin(['dist'])
    ]
	output: {
    	publicPath: 'http://cdn.com.cn'
		filename: '[name].js',
		path: path.resolve(__dirname, 'dist')
	}
}

注意:

// development devtool: 'cheap-module-eval-source-map'
// production devtool: 'cheap-module-source-map'

使用WebpackDevServer提升开发效率

使用watch

使用watch监听文件变化自动重新打包

"script": {
    // 使用watch监听文件变化自动重新打包
	"watch": "webpack --watch"
}

使用WebpackDevServer

  1. 下载webpack-dev-server

    cnpm i webpack-dev-server -D
    
    
  2. 对webpck.config.js进行修改

    const path = require('path')
    const HtmlWebpackPlugin = require('html-webpack-plugin')
    const CleanWebpackPlugin = require('clean-webpack-plugin')
    
    module.exports = {
        mode: 'development',
        devtool: 'cheap-module-eval-source-map',
        entry: {
            main: './src/index.js',
            sub: './src/index.js'
        },
        devServer: {
            // contentBase 自动去dist目录寻找文件
    		contentBase: './dist',
            // open 自动打开里篮球
            open: true,
            // proxy 会配置跨越
            proxy: {
                '/api': 'http://localhost:3000'
            }
            // port 设置打开的端口号
            port: 8080
        },
        module: {
            rules: [{
                test: /\.jpg$/,
                use: {
                    loader: 'file-loader'
                }
            }]
        },
        plugins: [
            new HtmlWebpackPlugin({
                template: 'src/index.htlm'
            }),
            new CleanWebpackPlugin(['dist'])
        ]
    	output: {
        	publicPath: 'http://cdn.com.cn'
    		filename: '[name].js',
    		path: path.resolve(__dirname, 'dist')
    	}
    }
    
    
  3. 改script命令

    "script": {
        // 使用watch监听文件变化自动重新打包
    	"start": "webpack-dev-server "
    }
    
    

使用middleware自己搭建devServer服务器

  1. 更改script命令

    "script": {
        // 使用middleware监听文件变化自动重新打包
    	"middleware": "node server.js"
    }
    
    
  2. 下载插件

    cnpm install express webpack-dev-middleware -D
    
    
  3. 创建server.js

    const express = require('express')
    const webpack = require('webpack')
    const webpackDevMiddleware = require('webpack-dev-middleware')
    // 引入配置文件
    const config  = require('./webpack.config.js')
    // webpack编译器
    const complier = webpack(config)
    
    const app = express()
    // 使用webpackDevMiddleware中间件
    app.use(webpackDevMiddleware(complier, {
        publicePath: config.output.publicePath
    })
    
    app.listen(3000, () => {
    	console.log('server is running')
    })
    
    

Hot Module Replacement(热模块更新)

模块热替换(HMR - hot module replacement)功能会在应用程序运行过程中,替换、添加或删除 模块,而无需重新加载整个页面。主要是通过以下几种方式,来显著加快开发速度:

  • 保留在完全重新加载页面期间丢失的应用程序状态。
  • 只更新变更内容,以节省宝贵的开发时间。
  • 在源代码中对 CSS/JS 进行修改,会立刻在浏览器中进行更新,这几乎相当于在浏览器 devtools 直接更改样式。
  1. 启用 webpack 的 模块热替换 功能:

    const webpack = require('webpack')
    module.exports = {
      //...
      devServer: {
        hot: true,
        // 在构建失败时不需要刷新页面作为回退。
        hotOnly: true
      },
      //...
      plugins: [
          new webpack.HotModuleReplacementPlugin()
      ]
    };
    
    

**注意:**对于css文件cssloader已经默认为我们编写下面一段代码了会进行模块热替换 但是对应JS文件,需要手动去添加代码

import number from './number'

if (module.hot) {
	module.hot.accept('./number', () => {
		number()
	})
}

但是在vue与react中也帮我们完成了这些代码

使用Babel处理ES6语法

@babel/polyfill(常用于写业务代码)

  1. 下载

    cnpm i babel-loader @babel/core @babel/preset-env @babel/polyfill -D 
    
    

    注意:

    • @babel/core是babel-loader的核心包

    • @babel/polyfill里面放的是把es6转到es5的方法 需要引入到对应的JS文件但是使用 **useBuiltIns: 'usage'**后会自动导入@babel/polyfill,不需要手动引入

    • @babel/preset-env是配置插件

  2. 增加规则

    webpack.config.js

    const webpack = require('webpack')
    module.exports = {
      //...
      module: {
          rules: [
              {
                  test: /\.js$/,
                  // 排除node_modules文件夹
                  exclude: /node_modules/,
                  loader: "babel-loader",
                  // options配置参数
                  options: {
                      // presets: ["@babel/preset-env"]
                      presets: [["@babel/preset-env", {
                          // 打包之后会运行在chrome67版本以上的环境下
                          targets: {
                            chrome: "67",
                          },
                          // 按需引入@babel/polyfill把es6转到es5的方法 
                          useBuiltIns: 'usage'
                      }]]
                  }
              }
          ]
      }
    };
    
    

@babel/plugin-transform-runtime(常用于写库代码)

注入时以闭包的形式,不污染全局变量

  1. 下载

    npm install --save-dev @babel/plugin-transform-runtime
    npm install --save @babel/runtime
    npm install --save @babel/runtime-corejs2
    
    
    corejs option Install command
    false npm install --save @babel/runtime
    2 npm install --save @babel/runtime-corejs2
    3 npm install --save @babel/runtime-corejs3
  2. 使用配置

    const webpack = require('webpack')
    module.exports = {
      //...
      module: {
          rules: [
              {
                  test: /\.js$/,
                  // 排除node_modules文件夹
                  exclude: /node_modules/,
                  loader: "babel-loader",
                  // options配置参数
                  options: {
    				"plugins": [
                        [
                          "@babel/plugin-transform-runtime",
                          {
                            "absoluteRuntime": false,
                            "corejs": 2,
                            "helpers": true,
                            "regenerator": true,
                            "useESModules": false,
                            "version": "7.0.0-beta.0"
                          }
                        ]
                      ]
                  }
              }
          ]
      }
    };
    
    

把options配置参数提取到.babelrc文件下

创建.babelrc文件

把options配置参数剪切到.babelrc文件

{
    "plugins": [
        [
          "@babel/plugin-transform-runtime",
          {
            "absoluteRuntime": false,
            "corejs": 2,
            "helpers": true,
            "regenerator": true,
            "useESModules": false,
            "version": "7.0.0-beta.0"
          }
        ]
      ]
}

实现对React框架代码打包

  1. 下载

    npm install --save-dev @babel/preset-react
    
    
  2. 配置babel-loader(在.babelrc文件下配置)

    {
      "presets": [
    	["@babel/preset-env", {
            target: {
                chrom: "67",
            },
            useBuiltIns: "usage"
        }],
        [
          "@babel/preset-react",
          {
            "pragma": "dom", // default pragma is React.createElement (only in classic runtime)
            "pragmaFrag": "DomFrag", // default is React.Fragment (only in classic runtime)
            "throwIfNamespace": false, // defaults to true
            "runtime": "classic" // defaults to classic
            // "importSource": "custom-jsx-library" // defaults to react (only in automatic runtime)
          }
        ]
      ]
    }
    
    

Tree Shaking (ES模块按需引入)

mode为development时默认没有Tree Shaking

  1. 在webpack.config.js中修改

    const webpack = require('webpack')
    module.exports = {
      mode: "development",
      //...
      optimization: {
          usedExports: true
      }
    };
    
    
  2. 在package.json中修改

    添加sideEffects字段可以把一些不需要按需引入的模块排除

    {
    	//...
    	"sideEffects": ["@babel/polyfill"]
    }
    
    

    如果没有需要特殊处理的模块,可以把sideEffects设置为false

Code Splitting(代码分割)

同步代码分割

main.js被拆成loadsh.js(1MB),main.js(1MB)

当业务逻辑发生变化时,只要加载main.js即可(1MB)

webpack.config.js

{
	//...
    // 只需再optimization里面做配置即可
	optimization: {
        splitChunks: {
            chunks: 'all'
        }
    }
}

异步代码自动分割(import)

当代码内部有异步加载的时候,无需做任何配置,webpack会自动帮你分割到另一个文件

SplitChunksPlugin规则

做代码分割时,默认会有一个SplitChunksPlugin 配置及规则

SplitChunksPlugin 默认配置及规则

module.exports = {
  //...
  optimization: {
    splitChunks: {
      chunks: 'async',
      minSize: 30000,
      maxSize: 0,
      minChunks: 1,
      maxAsyncRequests: 5,
      maxInitialRequests: 3,
      automaticNameDelimiter: '~',
      name: true,
      cacheGroups: {
        vendors: {
          test: /[\\/]node_modules[\\/]/,
          priority: -10
        },
        default: {
          minChunks: 2,
          priority: -20,
          reuseExistingChunk: true
        }
      }
    }
  }
};

  • 新代码块可以被共享引用,或这些模块都是来自node_modules
  • 新产出的vendor-chunk的大小得大于30kb
  • 按需加载的代码块(vendor-chunk)并行请求的数量小于或等于5个
  • 初始加载的代码块,并行请求的数量小于或者等于3个

CSS文件代码分割(MiniCssExtractPlugin)

默认情况下webpack会把引入的css文件编译成JS打包,如果我们想把css分离出来单独打包我们就需要借助插件MiniCssExtractPlugin来实现(不支持模块热更新)一般在线上环境中使用

  1. 下载

    npm install --save-dev mini-css-extract-plugin
    
    
  2. 修改webpack.config.js

    const MiniCssExtractPlugin = require("mini-css-extract-plugin");
    module.exports = {
      plugins: [
        new MiniCssExtractPlugin({
          // Options similar to the same options in webpackOptions.output
          // both options are optional
          filename: "[name].css",
          chunkFilename: "[id].css"
        })
      ],
      module: {
        rules: [
          {
            test: /\.css$/,
            use: [
              {
                loader: MiniCssExtractPlugin.loader,
                options: {
                  // you can specify a publicPath here
                  // by default it use publicPath in webpackOptions.output
                  publicPath: '../'
                }
              },
              "css-loader"
            ]
          }
        ]
      }
    }
    
    
  3. 可以会与Tree Shaking冲突,导致未使用的css文件不参与打包,需在package.json中排除CSS文件的模块化按需加载

    package.json

    {
    	//...
    	"sideEffects": ["*.css"]
    }
    
    

Css代码压缩

  1. 下载

    npm i optimize-css-assets-webpack-plugin -D
    
    
  2. 使用

    const MiniCssExtractPlugin = require("mini-css-extract-plugin");
    const OptimizeCSSAssetsPlugin = require("optimize-css-assets-webpack-plugin");
    module.exports = {
      optimization: {
        minimizer: [
          new OptimizeCSSAssetsPlugin({})
        ]
      },
      plugins: [
        new MiniCssExtractPlugin({
          filename: "[name].css",
          chunkFilename: "[id].css"
        })
      ],
      module: {
        rules: [
          {
            test: /\.css$/,
            use: [
              MiniCssExtractPlugin.loader,
              "css-loader"
            ]
          }
        ]
      }
    }
    
    

Dev和prod区别打包

  • 创建两个webpack.config.js文件webpack.dev.jswebpack.prod.js

  • 修改script命令

    "script": {
    	"dev": "webpack-dev-server --config webpack.dev.js",
    	"build": "webpack --config webpack.prod.js"
    }
    
    
  • 可以把dev和prod共用的代码提取出来到webpack.common.js中去(使用webpack-merge进行合并)

    webpack.common.js

    const path = require('path');
    const HtmlWebpackPlugin = require('html-webpack-plugin');
    const CleanWebpackPlugin = require('clean-webpack-plugin');
    
    module.exports = {
    	entry: {
    		main: './src/index.js',
    	},
    	module: {
    		rules: [{ 
    			test: /\.js$/, 
    			exclude: /node_modules/,
    			use: [{
    				loader: 'babel-loader'
    			}]
    		}, {
    			test: /\.(jpg|png|gif)$/,
    			use: {
    				loader: 'url-loader',
    				options: {
    					name: '[name]_[hash].[ext]',
    					outputPath: 'images/',
    					limit: 10240
    				}
    			} 
    		}, {
    			test: /\.(eot|ttf|svg)$/,
    			use: {
    				loader: 'file-loader'
    			} 
    		}]
    	},
    	plugins: [
    		new HtmlWebpackPlugin({
    			template: 'src/index.html'
    		}), 
    		new CleanWebpackPlugin(['dist'], {
    			root: path.resolve(__dirname, '../')
    		})
    	],
    	optimization: {
    		runtimeChunk: {
    			name: 'runtime'
    		},
    		usedExports: true,
    		splitChunks: {
          chunks: 'all',
          cacheGroups: {
          	vendors: {
          		test: /[\\/]node_modules[\\/]/,
          		priority: -10,
          		name: 'vendors',
          	}
          }
        }
    	},
    	performance: false,
    	output: {
    		path: path.resolve(__dirname, '../dist')
    	}
    }
    
    

    webpack.dev.js

    const webpack = require('webpack');
    const merge = require('webpack-merge');
    const commonConfig = require('./webpack.common.js');
    
    const devConfig = {
    	mode: 'development',
    	devtool: 'cheap-module-eval-source-map',
    	devServer: {
    		contentBase: './dist',
    		open: true,
    		port: 8080,
    		hot: true
    	},
    	module: {
    		rules: [{
    			test: /\.scss$/,
    			use: [
    				'style-loader', 
    				{
    					loader: 'css-loader',
    					options: {
    						importLoaders: 2
    					}
    				},
    				'sass-loader',
    				'postcss-loader'
    			]
    		}, {
    			test: /\.css$/,
    			use: [
    				'style-loader',
    				'css-loader',
    				'postcss-loader'
    			]
    		}]
    	},
    	plugins: [
    		new webpack.HotModuleReplacementPlugin()
    	],
    	output: {
    		filename: '[name].js',
    		chunkFilename: '[name].js',
    	}
    }
    
    module.exports = merge(commonConfig, devConfig);
    
    

    webpack.prod.js

    const MiniCssExtractPlugin = require("mini-css-extract-plugin");
    const OptimizeCSSAssetsPlugin = require("optimize-css-assets-webpack-plugin");
    const WorkboxPlugin = require('workbox-webpack-plugin');
    const merge = require('webpack-merge');
    const commonConfig = require('./webpack.common.js');
    
    const prodConfig = {
    	mode: 'production',
    	devtool: 'cheap-module-source-map',
    	module: {
    		rules:[{
    			test: /\.scss$/,
    			use: [
    				MiniCssExtractPlugin.loader, 
    				{
    					loader: 'css-loader',
    					options: {
    						importLoaders: 2
    					}
    				},
    				'sass-loader',
    				'postcss-loader'
    			]
    		}, {
    			test: /\.css$/,
    			use: [
    				MiniCssExtractPlugin.loader,
    				'css-loader',
    				'postcss-loader'
    			]
    		}]
    	},
    	optimization: {
    		minimizer: [new OptimizeCSSAssetsPlugin({})]
    	},
    	plugins: [
    		new MiniCssExtractPlugin({
    			filename: '[name].css',
    			chunkFilename: '[name].chunk.css'
    		}),
    		new WorkboxPlugin.GenerateSW({
    			clientsClaim: true,
    			skipWaiting: true
    		})
    	],
    	output: {
    		filename: '[name].[contenthash].js',
    		chunkFilename: '[name].[contenthash].js'
    	}
    }
    
    module.exports = merge(commonConfig, prodConfig);