入门webpack

172 阅读7分钟

webpack.jpg

背景

用过Vue的朋友都知道,Vue使用的构建工具(或者叫打包工具)是webpack,那么webpack到底是什么,为什么他能够称为前端构建工具的老大哥呢。这段时间学习了webpack,也通过这篇文章记录以及分享一下自己的心得。

概念

webpack 是一个用于现代 JavaScript 应用程序的 静态模块打包工具
一、什么是前端构建工具
1、浏览器不认识css预处理器,所以需要一个工具将css预处理器转为浏览器所认识的css。
2、浏览器不认识ES6的一些高级语法,所以也需要一个工具将ES6转为ES5。
3、还有其他一些高级语法、高级写法等通通需要转化为浏览器所认识的语法和写法。
统筹上述这些工具,整合到一起,就形成了前端构建工具。
webpack之前有gulp,现在的Vite也是非常的热门。但是也是无法撼动webpack老大哥地位

注:为了方便,后续写代码的文件目录和下图一样(是不是非常像vue-cli)

1648599520(1).jpg

五大核心概念

入口 entry

作用:指示webpack从哪个文件为起点文件开始解析,分析构建内部依赖图
写法:字符串写法、数组写法、对象写法

// 字符串写法也叫做单入口写法,指定一个文件为入口文件
// 打包形成一个chunk,输出一个bundle,此时chunk的名称默认叫做main
entry: "./src/main.js"
-----------------------------------------------------------------
// 数组写法也叫做多入口写法,指定多个文件为入口文件
// 打包形成一个chunk,输出一个bundle
entry: [ "./src/main.js", "./src/test.js" ]
-----------------------------------------------------------------
// 对象写法是最灵活的写法,有几个入口文件就会形成几个chunk,输出几个bundle。形式是 key + value
entry: {
    main: "./src/main.js",
    test: "./src/test.js"
}

输出 output

作用:指示webpack在哪里输出打包后的bundle,以及如何对这些文件进行命名。默认dist
写法:对象写法

output: {
    filename: "[name].js",  // 指定文件输出的名称,也可以加上目录
    path: resolve(__dirname, "build"), // 输出的目录,这里是输出到顶层文件夹下的build文件夹
    publicPath: "/", // 所有输出资源引入的公共路径前缀  imgs/a.jpg ---> /imgs/a.jpg
    chunkFilename: "[name]_chunk.js", // 非初始文件的chunk名称
    library: { // 结合dll使用,将全局库暴露出去
      name: "[name]",
      type: "this"
    }
}

loader

作用:处理webpack不认识的图片、css预处理器、ES6高级语法等 写法:全局配置、rule规则数组

const commonCssLoader = [
    "style-loader", // 创建一个style标签,将js中的css文件插入到head中生效
    "css-loader", // 将css文件编译成common.js模块插入到JS中,内容是css样式字符串
    {
        loader: "postcss-laoder", // 用于处理css兼容性
        options: {
          postcssOptions: {
            plugins: [ "postcss-preset-env" ]
          }
        }
    }
]
module: {
    rules: [
        {
            test: /\.scss$/,
            use: [ ...commonCssLoader, "sass-loader" ] // sass-loader用于处理sass、scss资源
        },
        {
            test: /\.css$/,
            use: [ ...commonCssLoader ]
        },
        {
            test: /\.(jpg|png|gif)$/,
            // 这个type是webpack5更改的配置,以前是用file-loader、url-loader
            // asset/resource:将资源分割成单独文件,输出URL,之前是file-loader
            // asset/inline:将资源导出为dataURL(url(data:))的形式,之前的 url-loader的功能
            // asset/source:将资源导出为源码(source code). 之前的 raw-loader 功能
            // asset:自动选择导出为URL还是dataUrl,默认是8KB
            type: "asset",
            generator: {
                filename: "image/[name][ext][query]" // 输出到 image文件夹下,以名字、后缀、参数
            },
            parser: {
                dataUrlCondition: {
                    maxSize: 10 * 1024 // 小于 10MB 使用base64编码
                    // base64的优点:减少请求的次数,降低服务器的压力
                    // base64的缺点:会使代码体积增大,base64通常是很长的
                }
            }
        },
        {
            test: /\.js$/,
            use: {
                loader: "babel-loader", // babel-loader用于处理JS兼容性
                options: {
                    presets: [
                      "@babel/preset-env"
                    ]
                }
            }
        },
        {
            test: /\.(svg|ttf|woff)$/,
            asset: "asset/resource",
            generator: {
                filename: "image/[name][ext]"
            },
        },
        {
            test: /\.html$/,
            loader: "html-loader" // 将 HTML 导出为字符串。当编译器需要时,将压缩 HTML 字符串
        }
    ]
}

插件 plugin

作用:loader可以用于转换某些类型的模块,而插件可以用于处理范围更广的任务,包括打包优化、资源管理、注入环境变量等
写法:只需要安装依赖,然后引入该插件,最后在plugins的数组里new一个实例对象即可

const HtmlWebpackPlugin = require("html-webpack-plugin"); // 默认会创建一个空的html文件,引入打包输出的所有资源
const MiniCssExtractPlugin = require("mini-css-extract-plugin"); // 提取css成单独文件
const CssMinimizerWebpackPlugin = require("css-minimizer-webpack-plugin"); // 压缩css
const EslintWebpackPlugin = require("eslint-webpack-plugin"); // 启动eslint
// vue2.0使用的vue-loader和模板解析vue-template-compiler
const VueLoaderPlugin = require("./node_modules/vue-loader/lib/plugin");
const CopyWebpackPlugin = require("copy-webpack-plugin"); // 用于处理不想被webpack编译的文件,也叫静态复制

module: {
    rules: [
        {
            test: /\.css$/,
            use: [ MiniCssExtractPlugin.loader, "css-loader" ]
        },
        {
            test: /\.vue$/,
            loader: "vue-loader"
        }
    ]
}

plugins: [
    new HtmlWebpackPlugin({
        template: "./public/index.html",
        minify: {
            collapseWhitespace: true, // 折叠空格
            removeComments: true  // 移除注释
        }
    }),
    // 使用mini-css-extract-plugin的时候内置了loader,用于代替style-loader,提取JS中的css文件
    new MiniCssExtractPlugin({
        filename: "css/build.css" // 将文件输出到css文件夹下以build.css命名
    }),
    new EslintWebpackPlugin({
        fix: true, // 自动修复不合理的写法
        extensions: [ "js", "json" ], // 需要检查的扩展名
        exclude: "node_modules",  // 排除检查node_modules
    }),
    new VueLoaderPlugin(),
    new CopyWebpackPlugin({
      patterns: [
        { from: resolve(__dirname, "public/static"), to: resolve(__dirname, "build") }  
        // 这里我是排除了public文件夹的static文件夹,这里面的文件不会被编译
      ]
    })
]
optimization: { // 新的配置项,优化
    minimizer: [
      new CssMinimizerWebpackPlugin() // 生产环境使用
    ],
    minimize: true, // 开发环境使用,启动css优化
},

模式 mode

目的:webpack的模式,有开发模式development和生产模式production
development用于本地调试代码,方便调试
production用于上线生产,需要考虑兼容性、代码优化、性能优化等

其他的一些常用配置

// webpack-dev-serve用于快速开发应用程序,也就是本地启一个服务
// 需要安装 webpack-dev-server      webpack5运行指令 npx webpack serve
devServer: {
    static: path.resolve(__dirname, "build"), // 从build文件夹下读取资源
    open: true, // 自动打开浏览器
    hot: true, // 启动HMR热更新
    port: 9527, // 端口
    compress: true,  // 启动gzip压缩
    proxy: { // 代理
        "/api": {
            target: "http://localhost:9527", // 代理的地址
            pathRewrite: { "^/api": "" }, // 重写路径
            changeOrigin: true
        }
    }
},
resolve: {
    alias: { // 用于起别名
        "@": path.resolve(__dirname, "src") // 这样就可以使用 @/main.js取到src下的main.js
    },
    module: ["node_module"] // 告诉webpack去哪找资源
}

webpack优化配置

HMR启动热模块替换

为什么会存在HMR:webpack启动devServer的时候默认修改一个模块,所有模块都会重新编译打包,导致打包构建速度变慢。
作用:一个模块发生变化,只重新打包这一个模块的,而非打包所有

devServer: {
    static: path.resolve(__dirname, "build"), // 从build文件夹下读取资源
    hot: true, // 启动HMR热更新
}

样式文件:因为style-loader内置了HMR,默认开启HMR
.vue文件:vue-loader也内置了HMR
js文件:默认不使用HMR功能,因此需要配置如下代码

if(module.hot) {
  module.hot.accept("代码路径", function() {
    console.log("我被更新了")
  })
}

source-map

目的:提供一种构建后代码映射到源代码的技术,通过映射关系追踪源代码的错误 使用:新增配置项 devtool

module.export = {
    devtool: "eval-source-map"
}
// source-map有很多类型,分为内敛外联,需要根据不同场合进行筛选
// [inline-|eval-][cheap-[module-]]source-map
-   inline:内联,一个chunk生成一个总的source-map
-   eval:内联,每一个文件生成一个source-map
-   cheap:外部,报错位置只能精确到行。
-   cheap-module:显示第三方库的source-map
开发环境选择:eval-source-map 调试友好、速度快(vue-cli也是使用的这个)
生产环境选择:nosources-source-map 隐藏源代码

oneof

目的:用于一个文件只匹配一个loader

rules:[ 
    { 
        test: /\.js$/, 
        exclude: /node_modules/, 
        loader: "eslint-loader", 
    }, 
    { 
        // 以下loader一种文件只会匹配一个 
        oneOf: [ 
        // 不能有两个配置处理同一种类型文件,如果有,另外一个规则要放到外面。 
            { 
                test: /\.js$/, 
                exclude: /node_modules/, 
                use: [ 
                    { 
                        loader: "babel-loader" 
                    }
                ]
             }, 
             { 
                 test: /\.css$/, 
                 use: [ "style-loader", "css-loader", ], 
             } 
        ]
    }
]

缓存

目的:加快打包构建速度,未经修改的文件直接从缓存中读取

module.exports = {
    output: {
        // 文件缓存有三种,hash值缓存,chunkhash值缓存,contenthash值缓存
        // hash值缓存:利用打包后生成的hash值进行缓存
        // 问题:因为js和css同时使用一个hash值,如果重新打包则导致所有缓存失效
        // chunkhash缓存:根据chunk生成的hash值,如果打包来自同一个chunk,则hash值一样
        // 问题:js和css的hash值还是一样,因为js引入了css,所以隶属于一个chunk
        // contenthash:根据文件内容生成hash,不同文件hash值一定不一样
        filename: "js/build[contenthash].js",
        path: resolve(__dirname, "build")
    },
    module: {
        rules: [
            {
                test: /\.js$/,
                use: {
                    loader: "babel-loader",
                    options: {
                        presets: [
                            "@babel/preset-env"
                        ],
                        cacheDirectory: true  // 开启bable缓存
                    }
                }
            }
        ]
    }
}

tree-shaking

概念:树摇,去除无用的代码 使用:模式mode设置为production开启tree-shaking 在package.json文件中配置 sideEffects: false

code split

目的:代码分割,按需加载

optimization: {
    splitChunks: {  // 将node——modules中代码单独打包成一个chunk输出
        chunks: "all", // 自动分析多入口chunk是否有公共代码,只打包一次公共代码
        minSize
    }
}

懒加载和预加载

目的:为了防止浏览器加载过大资源时出现假死现象
懒加载:使用import语法
预加载:使用import语法,里面加载 webpackPrefetch: true

document.getElementById("btn").addEventListener("click", () => {
  import ("/*webpackPrefetch: true */./test").then(({ mul }) => {
    console.log(mul(4, 5));
  }).catch(err => {
    console.log(err);
  })
})

PWA

目的:渐进式网络开发程序,用于程序离线也能访问

// 需要安装workbox-webpack-plugin
const WorkboxWebpackPlugin = require("workbox-webpack-plugin");
plugins: [
    new workboxWebpackPlugin.GenerateSW({
        clientsClaim: true, // 帮助serviceworker快速启动
        skipWaiting: true // 删除旧的serviceworker
    })
]

多进程打包

目的:开启多个线程,加快打包速度。这个只有在编译时间过长时使用,因为本身开启这个会占用相当大的资源。

{
    test: /\.js$/,
    use: [
        {
            loader: "thread-loader",
            options: {
                workers: 2  // 2个线程
            }
        },
        {
            loader: "babel-loader",
            options: {
                presets: [
                    "@babel/preset-env"
                ],
            cacheDirectory: true  // 设置bable缓存
            }
        }
    ]
}

externals

目的:防止某些import包打包到bundle

module.exports = {
    externals: {
        jquery: "jQuery" // 忽略后记得手动引入cdn链接
    }
}

结尾

当然还有各种形形色色的loader、插件等,我最近也在使用webpack去搭建一个完整的vue项目。最后来一句:webpack牛逼