背景
相信很多童鞋在面试的时候经常会被问到"自己有没有手动配置过webpack", "webpack基础配置"等问题。通过本篇文章带你了解常用的webpack基础配置(此文章基于react技术栈)。
一、依赖包安装
1. webpack相关依赖包
- webpack
webpack核心包 本文依赖版本4.x - webpack-cli
webpack命令行工具, 依赖于webpack核心包 - webpack-dev-server
开发环境代码live reloading(实时重新加载)与hot module replacement(热模块替换) - webpack-merge
开发与生产环境合并webpack基础配置
npm install webpack webpack-cli webpack-dev-server webpack-merge -D
2. babel相关依赖包
- @babel/core
把js代码分析成ast,方便各个插件分析语法进行相应的处理 - @babel/preset-env
无需管理目标环境需要的语法转换或浏览器polyfill,就可以使用最新的js语法 - @babel/preset-react
提供对react的支持, 例如支持JSX语法格式 - @babel/plugin-proposal-class-properties
- core-js
支持es6+新特性, 例如Promise、Set、Iterator - babel-loader
解析js
npm install @babel/core @babel/preset-env @babel/preset-react @babel/plugin-proposal-class-properties core-js@3 babel-loader -D
从 Babel v7 开始,所有针对标准提案阶段的功能所编写的预设(stage preset)都已被弃用,官方已经移除了 @babel/preset-stage-x
3. 样式处理相关依赖包(此处拿less举例)
- autoprefixer
针对不同的浏览器在css属性上添加前缀 - css-loader
- postcss
- postcss-loader
css解析成js可以操作的ast,并调用插件处理ast得到结果 - less
- less-loader
npm install autoprefixer css-loader postcss postcss-loader less less-loader -D
- 可以是用cssnext替代autoprefixer。
- cssnext 插件允许开发人员在当前的项目中使用 CSS 将来版本中可能会加入的新特性。
- cssnext 负责把这些新特性转译成当前浏览器中可以使用的语法。从实现角度来说,cssnext 是一系列与 CSS 将来版本相关的 PostCSS 插件的组合。比如,cssnext 中已经包含了对 Autoprefixer 的使用,因此使用了 cssnext 就不再需要使用 Autoprefixer。
4. 文件(包含图片、视频、音频、字体文件)处理相关依赖包
- file-loader
- url-loader
npm install file-loader url-loader -D
- file-loader 返回的是图片的url
- url-loader可以通过limit属性对图片分情况处理,当图片小于limit(单位:byte)大小时转base64,大于limit时调用file-loader对图片进行处理。
5. 常用plugins相关依赖包(重要)
- clean-webpack-plugin
清空打包文件 - copy-webpack-plugin
将某些静态资源复制到特定文件夹下 - html-webpack-plugin
创建一个html文件, 并把webpack打包后的静态文件自动插入到此文件中 - mini-css-extract-plugin
将css提取到单独的文件中 - optimize-css-assets-webpack-plugin
压缩css - terser-webpack-plugin
压缩js
npm install clean-webpack-plugin copy-webpack-plugin html-webpack-plugin mini-css-extract-plugin optimize-css-assets-webpack-plugin terser-webpack-plugin -D
- html-webpack-plugin可配置多次, 常见于多入口打包
- mini-css-extract-plugin用来替换extract-text-webpack-plugin, 与 extract-text-webpack-plugin 相比优势在于: (1) 异步加载 (2) 没有重复的编译(性能)(3) 配置更简洁 (4) 特别针对 CSS 开发
- terser-webpack-plugin用来替换uglifyjs-webpack-plugin, 因为uglifyjs不支持es6语法
二、webpack配置
0. 前置路径配置文件
添加config.js配置文件
const path = require('path');
const resolve = (dir) => path.resolve(__dirname, dir);
const config = {
// publicPath(客户端所访问的上线资源地址)指定的路径会被作为前缀添加到所有的url上, 例如html文件中的link标签,script标签、img标签
pubicPath: '/',
template: resolve('../public/index.html'),
entry: resolve('../src/index.js'),
// 打包文件放置位置
path: resolve('../dist'),
};
module.exports = config;
1. 基础配置
添加webpack.config.js基础配置文件, 文件内容如下:
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const config = require('../config');
const webpackConfig = {
devtool: false, // 此选项控制是否生成,以及如何生成 source map
resolve: {
alias: {
demo: path.join(__dirname, '../src/components'),
},
// 尝试按顺序解析文件后缀名, 如果有多个文件有相同的名字,但后缀名不同,webpack 会解析列在数组首位的后缀的文件 并跳过其余的后缀。
extensions: ['*', '.js', '.jsx', '.less', '.css'],
},
// 入口配置
entry: {
bundle: config.entry,
vendor: ['react', 'react-dom', 'react-router-dom'],
},
// 输出配置
output: {
filename: '[name]-[hash:8].js',
// chunkFilename: '[name]-[chunkhash:8].js',
path: config.path,
},
module: {
// 解析器配置
rules: [
{
test: /\.(js|jsx)$/,
use: {
loader: 'babel-loader',
options: {
presets: [
[
'@babel/preset-env',
{
targets: {
// 大于相关浏览器版本无需用到 preset-env
edge: '17',
firefox: '60',
chrome: '67',
safari: '11.1',
},
corejs: '3', // 声明corejs版本, 主要提供对es6+新语法、新特性的支持
// 根据代码逻辑中用到的 ES6+语法进行方法的导入,而不是全部导入
useBuiltIns: 'usage', // useBuiltIns就是是否开启自动支持 polyfill,它能自动给每个文件添加其需要的poly-fill。
},
],
'@babel/preset-react',
],
plugins: ['@babel/proposal-class-properties'], // 解决Support for the experimental syntax 'classProperties' isn't currently enabled
},
},
exclude: /node_modules/,
},
{
test: /\.(css|less)$/,
use: [
{
loader: MiniCssExtractPlugin.loader,
options: {
// 只在开发模式中启用热更新
hmr: process.env.NODE_ENV === 'development',
// 如果模块热更新不起作用,重新加载全部样式
reloadAll: true,
},
},
'css-loader',
{
loader: 'postcss-loader',
options: {
postcssOptions: {
ident: 'postcss', // 说明options里面插件的使用是针对于谁的,我们这里是针对于postcss的
plugins: [
// 这里的插件只是这对于postcss
require('autoprefixer')(), // 引入添加前缀的插件,第二个空括号是将该插件执行
],
},
},
},
'less-loader',
],
},
{
test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
use: [
{
loader: 'url-loader',
options: {
limit: 100, // 不超过100byte, 则转换成base64位
name: 'assets/img/[name].[ext]', // 图片输出路径
},
},
],
},
{
test: /\.(mp4|webm|ogg|mp3|wav|flac|aac)(\?.*)?$/,
use: [
{
loader: 'url-loader',
options: {
limit: 10000,
name: 'assets/blob/[name].[ext]', // 音视频输出路径
},
},
],
},
{
test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/,
use: [
{
loader: 'url-loader',
options: {
limit: 10000,
name: 'assets/font/[name].[ext]', // 字体输出路径
},
},
],
},
],
},
// 插件配置
plugins: [
new HtmlWebpackPlugin({
template: config.template,
inject: true, // 注入选项 有四个值 true,body(script标签位于body底部),head,false(不插入js文件)
filename: 'index.html',
minify: {
// 压缩html
removeComments: true, // 去除注释
collapseWhitespace: true, // 去除空格
},
}),
new MiniCssExtractPlugin({
filename: process.env.NODE_ENV === 'development' ? 'assets/style.css' : 'assets/style.[hash:8].css', // 配置样式文件输出路径
}),
],
};
module.exports = webpackConfig;
- 在不进行任何配置的情况下,@babel/preset-env 所包含的插件将支持所有最新的JS特性(ES2015,ES2016等,不包含 stage 阶段),将其转换成ES5代码。例如,代码中使用了还都处在在 stage 阶段新特新Optional Chaining(可选链操作符)或者Nullish coalescing Operator(空位合并操作符),那么只配置 @babel/preset-env,转换时会抛出错误,需要另外安装相应的插件。
- babel7.4以后官方以不推荐使用@babel/polyfill,用core-js@3代替
- MiniCssExtractPlugin插件配置filename时, 区分了开发与生产环境的样式文件名称, 原因是在开发环境中, 如果不固定样式文件名称, 会出现修改样式不能自动更新的问题
- url-loader支持两种图片引入方式:
1.import img from './image.png'
2.css样式引入, 例如backgorund: url('./image.png')
2. 基于基础配置添加dev环境配置文件
const webpack = require('webpack');
const merge = require('webpack-merge');
// const CopyWebpackPlugin = require('copy-webpack-plugin');
const baseConfig = require('./webpack.config');
const config = require('../config');
const devConfig = merge(baseConfig, {
mode: 'development',
devtool: 'inline-source-map',
devServer: {
hot: true, // hotOnly 修改内容后command+s后,页面并不会刷新,而需要手动进行刷新
inline: true,
disableHostCheck: true,
contentBase: config.path,
compress: true,
host: 'localhost',
port: 8080,
overlay: true,
publicPath: config.pubicPath,
proxy: {
'/api': {
target: 'http://xxx.meituan.com',
changeOrigin: true,
},
},
},
output: {
publicPath: '/',
},
plugins: [
new webpack.HotModuleReplacementPlugin()
// new CopyWebpackPlugin([
// {
// from: path.resolve(__dirname, '../static'),
// to: config.staticSubDirectory,
// ignore: ['.*'],
// },
// ]),
],
});
module.exports = devConfig;
- hotOnly 修改内容后command+s后,页面并不会刷新,而需要手动进行刷新
- mode为development时可省略NamedModulesPlugin、NamedChunksPlugin、DefinePlugin配置
- 在开发环境中肯定少不了使用webpack-dev-server进行热更新, 通过如上配置大家可以发现热更新已经生效, 但是每次修改会重新加载整个页面, 并不会局部更新, 解决方案为在入口处添加如下代码(
非常重要):if (module.hot) { module.hot.accept(App, () => { //App 代表需要热更新的dom render(App); }); }
wepback目前已经发布了5.x版本, 但是本人尝试了5.x版本的热更新不起作用, 还未找到解决方案
3. 基于基础配置添加prod环境配置文件
const merge = require('webpack-merge');
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
const TerserWebpackPlugin = require('terser-webpack-plugin');
const OptimizeCssAssetsPlugin = require('optimize-css-assets-webpack-plugin');
// const { BundleAnalyzerPlugin } = require('webpack-bundle-analyzer');
const baseConfig = require('./webpack.config');
const config = require('../config');
module.exports = merge(baseConfig, {
mode: 'production',
output: {
publicPath: config.pubicPath,
},
plugins: [
new CleanWebpackPlugin(),
// new BundleAnalyzerPlugin(), // 打包结束后会启动一个服务在浏览器查看打包的大小以及包内容
],
optimization: {
minimizer: [
// js压缩
new TerserWebpackPlugin({
parallel: true,
exclude: /\/node_modules/,
extractComments: false, // 这个选项如果为true 会生成一个xxx.js.LICENSE.txt文件 存储特定格式的注释
terserOptions: {
warnings: false,
compress: {
unused: true,
drop_debugger: true, // 删除debugger
drop_console: true, // 删除console
},
},
}),
// css压缩
new OptimizeCssAssetsPlugin({
cssProcessorOptions: { safe: true, discardComments: { removeAll: true } },
}),
],
},
});
- mode为production时可省略NoEmitOnErrorsPlugin、DefinePlugin、TerserPlugin、ModuleConcatenationPlugin配置
启动命令
"scripts": {
"start": "NODE_ENV=development webpack-dev-server --inline --config webpack.dev.js",
"build": "NODE_ENV=production webpack --config webpack.prod.js",
}