webpack学习

一、前端构建工具简介

1.1 什么是构建工具

  • 定义:自动化处理代码的开发工具
  • 主要功能:
    • 打包:合并多个模块为少量文件(减少请求)
    • 编译:转换新语法(eg:es6->es5)(解决兼容性问题)
    • 优化:压缩代码,删除未用代码
    • 拓展:支持sass/less,ts等

1.2 常见的构建工具

  • 常见工具:
    • webpack:静态模块打包工具(主流)
    • vite:基于ESM的轻量开发服务器
    • rollup:库项目打包(适合sdk开发)
    • parcel:零配置打包(适合简单项目)

二、Webpack基础内容

2.1 webpack介绍

  • 定义:静态资源打包工具(将浏览器不识别的语法转换为浏览器识别的语法)
  • webpack本身功能有限
    • 开发模式:仅能编译js中的ES Module语法
    • 生产模式:编译js中的ES Module语法 + js代码压缩

2.2 开始使用

  • 安装
# 初始化项目
npm init -y
# webpack:核心语法,webpack-cli:命令行
npm install webpack webpack-cli --save-dev
  • 基础使用示例
// index.js - 浏览器之前不支持es module
import count from './js/count'
import sum from './js/sum'
console.log(count(8,1))
console.log(sum(8,1))
  • 打包
npx webpack ./src/index.js --mode=development # 开发模式打包
npx webpack ./src/index.js --mode=production # 生产模式打包

2.3 基本配置

const path = require('path');
module.exports = {
    // 1.打包入口文件
    entry: './src/index.js', // 相对路径
    // 2.文件的输出目录
    output: {
        path: path.resolve(__dirname, 'dist'), // 绝对路径。__dirname:当前文件夹的文件目录
        filename: 'main.js', // 文件名
    },
    // 3.loader加载器(处理非JS资源,如:css,media媒体资源等)
    module: {
        rules:[
            // loader的配置
        ]
    },
    // 4.插件(拓展功能,如:优化,资源管理)
    plugins: [],
    // 5.模式
    mode: 'development'
}

2.4 处理样式,图片等资源

  • 前提:浏览器不支持直接在JS模块中使用import来加载CSS
import './style/index.css'
// import './style/index.less'
// import './style/index.scss'
// 报错:Uncaught SyntaxError: Cannot use import statement outside a module
  • 解决:使用loader解决(css文件,图片,字体图标库等)
// 安装:npm install --save-dev css-loader style-loader
// 按需安装:npm install --save-dev css-loader style-loader less less-loader
// 按需安装:npm install --save-dev css-loader style-loader sass-loader sass
module: {
    rules: [
        {
            test: /\.css$/i,
            use: ['style-loader', 'css-loader'],
        },
        {
            test: /\.less$/i,
            use: [
                'style-loader',
                'css-loader',
                'less-loader',
            ],
        },
        {
            test: /\.s[ac]ss$/i,
            // 执行顺序从下到上
            use: [
                'style-loader', // 将JS字符串生成为style节点
                'css-loader', // 将CSS转化成CommonJS模块
                'sass-loader', // 将Sass编译成CSS
            ],
        },
        {
            test: /\.(png|jpe?g|gif|webp|svg)/,
            type: 'asset',
            parser: {
                dataUrlCondition: {
                    // 小于10kb的图片转base64
                    // 优点:减少请求数量。缺点:体积会更大
                    maxSize: 10 * 1024 // 10kb
                }
            },
            generator: {
                // [hash:10]取hash前10位 ext:后缀 query:参数
                filename: "static/images/[hash:10][ext][query]"
            }
        },
        {
            test: /\.(ttf|woff2?|mp3|mp4|avi)$/i,
            type: 'asset/resource',
            generator: {
                filename: 'static/media/[hash:10][ext][query]'
            },
        },
    ]
},

2.5 处理js资源(Eslint,Babel)

2.5.1 介绍

  • 前提:webpack只能编译js中es模块化语法,不能编译其他语法
  • 方法:
    • Eslint:检测代码格式
    • Babel:针对js兼容性处理(es6 -> es5)

2.5.2 Eslint 官网

  • 安装
# 注意后面版本写法有所不同,最新版本只支持eslint.config.js
npm install eslint@7.32.0 eslint-webpack-plugin@3.1.1 --save-dev
npx eslint . # 检测ESLint是否正确加载配置文件
  • 使用
// 1.webpack.config.js
const ESLintPlugin = require('eslint-webpack-plugin');
module.exports = {
  plugins: [new ESLintPlugin({
    // 指定检测文件
    context: path.resolve(__dirname, 'src')
  })],
};
// 2.新建.eslintrc.js文件(新版本只支持eslintrc.config.js文件,需重新配置)
module.exports = {
    // 继承Eslint官方的规则(vue/react有各自的规则)
    extends: ["eslint:recommended"],
    env: {
        node: true, // 启动node中全局变量(console)
        browser: true // 启动浏览器中全局变量(window)
    },
    // 语法环境
    parserOptions: {
        ecmaVersion: 6, // es6
        sourceType: 'module', // es module
    },
    rules: {
        "no-var": 2, // 禁止使用var定义变量
    },
}
// 3.配置.eslintignore
dist

2.5.3 Babel

  • 安装
npm install -D babel-loader @babel/core @babel/preset-env
  • 使用
// webpack.config.js
module: {
    rules: [
        {
            test: /\.js$/,
            exclude: /node_modules/, // 排除node_module中的js
            use: {
                loader: 'babel-loader',
                // options: {
                //     presets: ['@babel/preset-env'],
                // },
            },
        },
    ]
}
// 新建babel.config.js
module.exports = {
    // 只能预设:能编译es6语法
     presets: ['@babel/preset-env'],
}

2.6 处理html资源(HtmlWebpackPlugin)

  • 作用:使用HtmlWebpackPlugin生成HTML文件
  • 使用:
// 1.安装
npm install --save-dev html-webpack-plugin
// 2.webpack.config.js中使用
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
    plugins: [
        new HtmlWebpackPlugin({
            // 以public/index.html为模板
            // 结构和模板一致,自动引入打包输出的资源(自动引入入口文件)
            template: path.resolve(__dirname, 'public/index.html')
        })
    ],
}

2.7 开发服务器及自动化

  • 步骤
// 1.安装
npm install --save-dev webpack-dev-server
// 2.配置开发服务器
devServer: {
    host: 'localhost', // 启动服务器域名
    port: '3000',   // 端口号
    open: true, // 是否自动打开浏览器
},
// 3.启动
npx webpack serve
  • 注意:开发服务器没有输出

2.8 区分模式

  • 开发模式
    • 编译代码,使浏览器能识别运行
    • 代码质量检查,树立代码规范
  • 生产模式
    • 优化代码运行性能
    • 优化代码打包速度
  • 新建config文件夹
    • webpack.dev.js
      • 修改文件中绝对路径(相对路径不用修改)
      • 启动:npx webpack serve –config ./config/webpack.dev.js
    • webpack.prod.js
      • 修改文件中绝对路径(相对路径不用修改)
      • 打包:npx webpack –config ./config/webpack.prod.js
  • 配置package.json
 "scripts": {
    "start": "npm run dev",
    "dev": "webpack serve --config ./config/webpack.dev.js",
    "build": "webpack --config ./config/webpack.prod.js"
  },

2.9 css处理 - 生产配置

2.9.1 抽离css为单独文件(阻止闪屏)

  • 前提:css文件会被打包到js中,当js文件加载时,会创建一个style标签生成样式
    • css-loader:把css打包到js中
    • style-loader:创建style标签
  • 缺点:会出现闪屏现象,用户体验不好
  • 解决:提取单独css文件,通过link标签加载
  • 使用
// 1.安装
npm install --save-dev mini-css-extract-plugin
// 2.引入
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
// 3.使用
plugins: [
    new MiniCssExtractPlugin({
        filename: "static/css/main.css"
    })
],
rules: [
    {
        test: /\.css$/i,
        use: [
            MiniCssExtractPlugin.loader, // 抽离css为单独文件
            'css-loader'
        ],
    },
]

2.9.2 css兼容性处理(如:flex)

  • 步骤
// 1.安装
npm i postcss-loader postcss postcss-preset-env -D
// 2.配置
{
    test: /\.s[ac]ss$/i,
    // 执行顺序从下到上
    use: [
        MiniCssExtractPlugin.loader, // 抽离css为单独文件
        'css-loader', // 将CSS转化成CommonJS模块
        {
            loader: 'postcss-loader',
            options: {
            postcssOptions: {
                plugins: [
                    "postcss-preset-env" // 解决大多数样式兼容性问题
                ]
            }
            }
        },
        'sass-loader', // 将Sass编译成CSS
    ],
},
// 3.测试(package.json)
"browserslist": [
    "ie >= 8"
]
// 注意:正常浏览器兼容性
"browserslist": [
    "last 2 version",
    "> 1%",
    "not dead"
]

2.9.3 css压缩(生产环境js和html默认压缩)

  • 步骤
// 1.安装
npm install css-minimizer-webpack-plugin --save-dev
// 2.引入
const CssMinimizerPlugin = require('css-minimizer-webpack-plugin');
// 3.使用
optimization: {
    minimizer: [
        new CssMinimizerPlugin(),
    ],
},

三、Webpack高级内容

3.1 提升开发体验

  • 前提:开发环境js文件打包后错误信息不明显
  • SourceMap:源代码与构建代码的映射
devtool: "cheap-module-source-map" // 开发环境-只包含行映射
devtool: "source-map" // 生产环境-包含行列映射,打包速度会变慢

3.2 提升打包速度

  • HMR/热模块替换 - 开发环境下打包编译速度更快
// css支持热更新
devServer: {
    host: 'localhost', // 启动服务器域名
    port: '3000',   // 端口号
    open: true, // 是否自动打开浏览器
    hot: true // 热更新
},
// js不支持热更新,自行搭建需写以西代码(vue-loader支持)
if(module.hot) {
    // 判断是否支持热模块替换功能
    module.hot.accept('./js/sum')
    // ...有多少js文件写多少
}
  • OneOf - 让文件只被一个loader配置处理
module: {
    rules: [
        {
            oneOf: [
                // ...
            ]
        }
    ]
}
  • Include/Exclude(一般处理eslint和babel)
{
    test: /\.js$/,
    exclude: /node_modules/, // 排除node_module中的js
    use: {
        loader: 'babel-loader',
        // options: {
        //     presets: ['@babel/preset-env'],
        // },
    },
},
  • Cache
    • babel缓存(第二次只会对修改的文件编译)
    {
        test: /\.js$/,
        exclude: /node_modules/, // 排除node_module中的js
        use: {
            loader: 'babel-loader',
            options: {
                // presets: ['@babel/preset-env'],
                cacheDirectory: true, // 开启babel缓存
                cacheCompression: false // 关闭缓存压缩
            },
        },
    },
    
    • eslint缓存(第二次只会对修改的文件检测)
    new ESLintPlugin({
        // 指定检测文件
        context: path.resolve(__dirname, '../src'),
        exclude: "node_modules", // 排除node_module中的js
        cache: true, // 开启缓存
        cacheLocation: path.resolve(__dirname, '../node_modules/.cache/eslintcache') 
    }),
    
  • Thead - 多线程打包 - 每个进程的启动需要600ms左右开销
// 1.下载
npm i thread-loader -D
// 2.引入
const os = require('os');
const threads = os.cpus().length; // 获取cpu核数
// 3.使用
// babel
{
    test: /\.js$/,
    exclude: /node_modules/, // 排除node_module中的js
    use: [
        {
            loader: 'thread-loader', // 开启多进程
            options: {
                works: threads, // 进程数量
            }
        },
        {
            loader: 'babel-loader',
            options: {
                // presets: ['@babel/preset-env'],
                cacheDirectory: true, // 开启babel缓存
                cacheCompression: false // 关闭缓存压缩
            },
        }
    ],
},
// eslint
 new ESLintPlugin({
    // 指定检测文件
    context: path.resolve(__dirname, '../src'),
    exclude: "node_modules", // 排除node_module中的js
    cache: true, // 开启缓存
    cacheLocation: path.resolve(__dirname, '../node_modules/.cache/eslintcache'),
    threads, // 开启多进程和进程数量
}),
// js(开发环境不需要)
const TerserWebpackPlugin = require('terser-webpack-plugin'); 

optimization: {
    minimizer: [
        new CssMinimizerPlugin(),
        // 压缩js,生产环境使用,添加配置需要重新引入
        new TerserWebpackPlugin({
            parallel: threads, // 开启多进程和进程数量
        })
    ],
},

3.3 减少代码体积

  • Tree Shaking - 没使用的功能不会打包
    • 依赖es Module
    • 生产默认开启
  • 减少Babel代码体积 - 复用Babel辅助代码
// 1.安装
npm i @babel/plugin-transform-runtime -D
// 2.使用
{
    loader: 'babel-loader',
    options: {
        // presets: ['@babel/preset-env'],
        cacheDirectory: true, // 开启babel缓存
        cacheCompression: false, // 关闭缓存压缩
        plugins: ['@babel/plugin-transform-runtime'], // 减少代码体积
    },
}
  • 压缩图片
// 1.安装
npm install image-minimizer-webpack-plugin imagemin -D
npm install imagemin-gifsicle imagemin-jpegtran imagemin-optipng imagemin-svgo --save-dev // 无损压缩
npm install imagemin-gifsicle imagemin-mozjpeg imagemin-pngquant imagemin-svgo --save-dev // 有损压缩
// 2.引入
const ImageMinimizerPlugin = require('image-minimizer-webpack-plugin');
// 3.无损压缩使用(注意:jpegtran.exe的安装及使用环境)
new ImageMinimizerPlugin({
    minimizer: {
        implementation: ImageMinimizerPlugin.imageminGenerate,
        options: {
            plugins: [
                ['gifsicle', { interlaced: true }],
                ['jpegtran', { progressive: true }],
                ['optipng', { optimizationLevel: 5 }],
                [
                    'svgo',
                    {
                        plugins: [
                            "preset-default",
                            "prefixIds",
                            {
                                name: "sortAttrs",
                                params: {
                                    xmlnsOrder: "alphabetical",
                                }
                            }
                        ]
                    },
                ],
            ],
        }
    }
}),
// 4.有损压缩 - 安装固定版本
npm i image-minimizer-webpack-plugin@3.8.3 imagemin@8.0.1 
imagemin-mozjpeg@9.0.0 imagemin-pngquant@9.0.0 
imagemin-gifsicle@7.0.0 imagemin-svgo@10.0.1 --save-dev
// 使用
new ImageMinimizerPlugin({
    minimizer: {
        implementation: ImageMinimizerPlugin.imageminMinify,
        options: {
            plugins: [
                // JPEG 压缩(有损)
                ['imagemin-mozjpeg', {
                    quality: 85,
                    progressive: true
                }],
                // PNG 压缩(有损)
                ['imagemin-pngquant', {
                    quality: [0.65, 0.9],
                    speed: 4
                }],
                // GIF 压缩
                ['imagemin-gifsicle', {
                    interlaced: true
                }],
                // SVG 压缩
                ['imagemin-svgo', {
                    plugins: [
                        { name: 'removeViewBox', active: false },
                        { name: 'cleanupIDs', active: false }
                    ]
                }]
            ]
        }
    },
    severityError: 'warning' // 错误降级为警告
})

3.4 优化代码运行性能

3.4.1 Code Split (提升首屏加载速度)

  • 作用:
    • 分割文件:将打包生成的文件进行分割,生成多个js文件
    • 按需加载:需要哪个文件就加载哪个文件
  • 多入口,多输出
module.exports = {
    entry: {
        app: './src/index.js',
        main: './src/main.js',
    },
    output: {
        path: path.resolve(__dirname, 'dist'),
        filename: '[name].js', // [name]以文件名命名
        clean: true, // 打包前清空path目录下文件
    },
    mode: 'production',
}
  • 提取公共引用模块
    • 场景
      • 多个js文件同时使用import引入同一模块
      • npm安装到node_modules中的文件的使用
    • 使用
    optimization: { 
        splitChunks: {
            chunks: 'all', // 对所有模块进行分割
            // cacheGroups: { // 修改配置
            //     default: {
            //         minSize: 20000, // 打包最小体积文件
            //         minChunks: 2, // 至少被引用的次数
            //     }
            // }
        }
    }
    
* 3.4.3 按需引入(动态导入)
```js
// utils/sum.js
export function sum(...args) {
    return args.reduce((acc, cur, index, arr) => acc + cur, 0)
}
// index.js 在需要使用的时候加载
// 注意:上方export default 下方需 { default }
document.getElementById('btn').onclick = function() {
    import(/* webpackChunkName: "sum" */ './utils/sum').then(({ sum }) => {
        alert(sum(1,2,3,4));
    })
}

 output: {
    path: path.resolve(__dirname, 'dist'),
    filename: '[name].js', // [name]以文件名命名 // 入口chunk
    chunkFilename: '[name].[contenthash:8].chunk.js', // 非入口chunk	
    clean: true, // 打包前清空path目录下文件
},

3.4.2 Preload / Prefetch(提前加载资源,不执行)

  • Preload:告诉浏览器立即加载资源
    • 优先级高
    • 只能加载当前页面使用的资源
  • Prefetch:告诉浏览器空闲时才开始加载资源
    • 优先级低
    • 也可加载其他页面使用的资源
  • 使用
// 1.安装
npm install --save-dev @vue/preload-webpack-plugin
// 2.引入
const PreloadWebpackPlugin = require('@vue/preload-webpack-plugin');
// 3.使用
 new PreloadWebpackPlugin({
    rel: 'preload', // 'prefetch'
    as: 'script'
  })
//  4.结果 - 动态引入的文件会缓存加载
<script defer="defer" src="static/js/main.js"></script>
<link href="static/js/sum.9eef1040.chunk.js" rel="preload" as="script">
  • 注意:兼容性问题

3.4.3 Network Cache

  • 作用:通过文件名哈希管理缓存,确保内容变更时浏览器获取新资源,同时最大化利用未变更资源的缓存
  • 问题:若模块A被模块B引用,A的哈希变化会导致B的哈希也变化
  • 解决:runtimeChunk - 将模块映射关系(文件名 → 哈希)提取到独立的 runtime 文件中
optimization: {
    runtimeChunk: { name: (entry) => `runtime-${entry.name}.js` }
} 
  • 总结:合理组合 contenthash 与 runtimeChunk,可显著提升应用加载速度

3.4.4 Core.js

  • 作用:专门用于解决ES6以及以上API的兼容性问题(如:async函数,promise对象等)
  • 步骤:
// 1.下载
npm i core=js
// 2.main.js 引入
import 'core-js'; // 全部引入
import 'core-js/es/promise' // 手动按需引入
// babel 按需引入 babel.config.js
module.exports = {
    // 只能预设:能编译es6语法
    presets: [
        [
            '@babel/preset-env',
            { 
                useBuiltIns: 'usage', // 按需加载自动引入
                corejs: 3
            }
        ]
    ],  
}

3.4.5 PWA(离线访问体验-兼容性问题)

  • 原理:内部通过Service Workers技术实现
  • 使用
// 1.安装
npm install workbox-webpack-plugin --save-dev
// 2.引入
const WorkboxPlugin = require('workbox-webpack-plugin');
// 3.webpack.config.js使用
new WorkboxPlugin.GenerateSW({
    // 这些选项帮助快速启用 ServiceWorkers
    // 不允许遗留任何“旧的” ServiceWorkers
    clientsClaim: true,
    skipWaiting: true,
}),
// 4.main.js中注册 Service Worker(兼容性问题)
if ('serviceWorker' in navigator) {
   window.addEventListener('load', () => {
     navigator.serviceWorker.register('/service-worker.js').then(registration => {
       console.log('SW registered: ', registration);
     }).catch(registrationError => {
       console.log('SW registration failed: ', registrationError);
     });
   });
}
  • 验证:
npm i serve -g // 可部署静态资源服务器
serve dist

四、webpack总结

  • 核心:模块打包和资源整合。
  • 优化策略:
    • 构建速度优化
      • 缓存(babel缓存,eslint缓存)
      • 多线程
      • 范围限制(exclude: /node_modules/)
    • 产出性能优化
      • 用Tree Shaking删未用代码(生产模式 + ES Module + 无副作用声明)
      • 代码分割
        • 按需加载:将代码拆分为多个chunk,减少首屏加载资源体积
        • 并行加载:浏览器可同时加载多个chunk,加速资源获取
        • 缓存优化:分离高频变更的业务代码与稳定的第三方库
      • 资源压缩(js/css/图片)
      • 优化缓存(contenthash + runtimeChunk/抽离运行时代码,防止contenthash变化)
  • 注意:runtimeChunk:抽离Webpack的运行时代码,避免业务代码改动导致所有文件缓存(contenthash)失效

五、相关概念解读

5.1 webpack 核心命令汇总

npx webpack # 默认打包 - 入口src/index.js,输出dist/main.js
npx webpack --watch # 监听文件变化自动重新打包
npx webpack serve # 启动开发服务器

npx webpack ./src/index.js --mode=development # 开发模式打包
npx webpack ./src/index.js --mode=production # 生产模式打包

5.2 npx 和 npm run区别

* npx
    * 定义:运行本地或全局安装的命令行工具
    * 示例:npx webpack (执行本地 node_modules/.bin/webpack)
* npm run 
    * 定义:执行package.json中定义的脚本
    * 示例:npm run serve

5.3 现代浏览器已经支持ES模块

// 加上type="module"才能支持ES Modules
// 注意:加上type="module",如果以`file://`打开的方式会报跨域
<script type="module" src="../src/index.js"></script>

5.4 [hash:10] 与 [contenthash:10] 的区别与作用

  • [hash:10](项目级哈希)
    • 所有文件共享同一哈希值 - 截取前10位
    • 修改一个文件会导致所有缓存失效
  • [contenthash:10](内容级哈希)
    • 文件间哈希独立(截取前10位)
    • 根据单个文件内容计算哈希,仅当文件内容变化时哈希值才改变

5.5 字体图标库的使用

  • 下载
  • 使用:
    • 只需要.css .ttf woff woff2四个文件
    • 修改.css文件路径
    • 使用<span class="iconfont icon-xx"></span>
×

喜欢就点赞,疼爱就打赏