LOGO OA教程 ERP教程 模切知识交流 PMS教程 CRM教程 开发文档 其他文档  
 
网站管理员

JAVASCRIPT前端框架WEBPACK5从入门到精通

admin
2024年4月3日 11:2 本文热度 89

前言

webpack是什么?

摘自官网的一段话:webpack 是一个用于现代 JavaScript 应用程序的 静态模块打包工具。当 webpack 处理应用程序时,它会在内部从一个或多个入口点构建一个 依赖图(dependency graph),然后将你项目中所需的每一个模块组合成一个或多个 bundles,它们均为静态资源,用于展示你的内容。 官网链接:https://webpack.docschina.org/concepts/

为什么需要打包程序

在日常的开发中,我们会使用框架(React、Vue),ES6 模块化语法,Less/Sass 等 css 预处理器等语法进行开发。这样的代码要想在浏览器运行必须经过编译成浏览器能识别的 JS、Css 等语法,才能运行,所以我们需要打包工具帮我们做完这些事。除此之外,打包工具还能压缩代码、做兼容性处理、提升代码性能等。

正文

概念

在这里我将webpack为了几个要点,也是webpack核心的几个点

entry(入口) 需要打包的入口文件,可以为一个入口,也可以为多个入口(有几个入口就有几个输出),Webpack 本身功能比较少,只能处理 js 资源,一旦遇到 css 等其他资源就会报错output(输出) 可以通过配置 output 选项,告知 webpack 如何向硬盘写入编译文件。注意,即使可以存在多个 entry 起点,但只能指定一个 output 配置。loaders(模块解析器) loader 用于对模块的源代码进行转换。loader 可以使你在 import 或 “load(加载)” 模块时预处理文件。因此,loader 类似于其他构建工具中“任务(task)”,并提供了处理前端构建步骤的得力方式。loader 可以将文件从不同的语言(如 TypeScript)转换为 JavaScript 或将内联图像转换为 data URL。loader 甚至允许你直接在 JavaScript 模块中 import CSS 文件!Plugins(插件) 插件 是 webpack 的 支柱 功能。Webpack 自身也是构建于你在 webpack 配置中用到的 相同的插件系统 之上!插件目的在于解决 loader 无法实现的其他事mode(模式) 指示 Webpack 使用相应模式的配置。 development 开发模式:会将 process.env.NODE_ENV 的值设为 development。启用 NameChunksPlugin 和 NameModulesPlugin。特点是能让代码本地调试运行的环境。 production 生产模式:会将 process.env.NODE_ENV 的值设为 production。启用 FlagDependencyUsagePlugin, FlagIncludedChunksPlugin, ModuleConcatenationPlugin, NoEmitOnErrorsPlugin, OccurrenceOrderPlugin, SideEffectsFlagPlugin 和 UglifyJsPlugin。特点是能让代码优化上线运行的环境。

起步

新建文件,创建一个新的项目

下载我们的依赖

构建项目目录文件

webpack_document # 项目根目录(所有指令必须在这个目录运行)

└── src # 项目源码目录

├── js # js文件目录

│ ├── count.js

│ └── sum.js

├── css # css文件目录

└── index.js # 项目主文件

创建我们的配置文件webpack.config.js

运行指令

npx webpack

观察生成的dist的文件,就是我们打包后的文件了 Webpack 将来都通过 webpack.config.js 文件进行配置,来增强 Webpack 的功能

开发模式介绍

开发模式顾名思义就是我们开发代码时使用的模式。 这个模式下我们主要做两件事:

编译代码,使浏览器能识别运行开发时我们有样式资源、字体图标、图片资源、html 资源等,webpack 默认都不能处理这些资源,所以我们要加载配置来编译这些资源代码质量检查,树立代码规范提前检查代码的一些隐患,让代码运行时能更加健壮。提前检查代码规范和格式,统一团队编码风格,让代码更优雅美观。

处理样式资源

由于webpack只能处理js、json文件,并不能处理css文件,所以我们需要借助相应loader解析器来增强我们的功能,在webpack的官网中,为我们提供了常用的loader,如果不能满足我们的日常需要,也可以到社区中去寻找想要的loader [webpack官网loader]

处理css资源

下载

说明

css-loader:负责将 Css 文件编译成 Webpack 能识别的模块style-loader:会动态创建一个 Style 标签,里面放置 Webpack 中 Css 模块内容 此时样式就会以 Style 标签的形式在页面上生效

配置

使用 在src目录下创建我们的css文件,并在index.js中进行引入,然后进行打包,观察我们的dist文件里面的输出结果 为了方便我们观察效果,我们创建我们静态页面,并引入我们打包后dist文件下面的js文件,文件目录结构如下 页面效果如下:

处理less资源

下载

说明

less-loader:负责将 Less 文件编译成 Css 文件

配置

// path 为 Node.js的核心模块,专门用来处理文件路径


const path = require("path");


module.exports = {

    // 入口


    entry: {

        // 需要一个相对路径


        index: "./src/index.js",

    },


    // 输出


    output: {

        // 需要一个绝对路径


        path: path.resolve(__dirname, "dist"),


        clean: true,


        filename: "index.js",

    },


    // 解析器


    module: {

        rules: [

            {

                test: /\.css$/,


                // loader的执行顺序是从右往左的,所以这里先写style-loader,再写css-loader


                // 如果只使用一个loader的话,可以使用loader属性代替use,如下


                // loader:"style-loader"


                use: ["style-loader", "css-loader"],

            },


            {

                // 正则匹配所有已.less文件结尾的文件


                test: /\.less$/,


                // loader的执行顺序是从右往左的,所以这里先写style-loader,再写css-loader,再写less-loader


                use: ["style-loader", "css-loader", "less-loader"],

            },

        ],

    },


    plugins: [],


    mode: "production",

};

使用 在src目录下,创建我们的less文件夹,并生成index.less文件,写入样式,在index.js,引入我们的less文件,然后在pubilc的index.html文件中,创建对应的box文件,执行npx webpack,观察打包后的结果 页面效果如下:

处理sass/scss资源

下载

说明

sass-loader:负责将 Sass 文件编译成 css 文件sass:sass-loader 依赖 sass 进行编译

配置

使用 在src目录下,创建我们的sass文件夹,并生成index.sass 和 index.scss 文件,写入样式,在index.js,引入我们的sass、scss文件,然后在pubilc的index.html文件中,创建对应的box文件,执行npx webpack,观察打包后的结果 页面效果如下:

处理 Styl 资源

说明

stylus-loader:负责将 Styl 文件编译成 Css 文件

配置

// path 为 Node.js的核心模块,专门用来处理文件路径


const path = require("path");


module.exports = {

    // 入口


    entry: {

        // 需要一个相对路径


        index: "./src/index.js",

    },


    // 输出


    output: {

        // 需要一个绝对路径


        path: path.resolve(__dirname, "dist"),


        clean: true,


        filename: "index.js",

    },


    // 解析器


    module: {

        rules: [

            {

                test: /\.css$/,


                // loader的执行顺序是从右往左的,所以这里先写style-loader,再写css-loader


                // 如果只使用一个loader的话,可以使用loader属性代替use,如下


                // loader:"style-loader"


                use: ["style-loader", "css-loader"],

            },


            {

                // 正则匹配所有已.less文件结尾的文件


                test: /\.less$/,


                // loader的执行顺序是从右往左的,所以这里先写style-loader,再写css-loader


                // 如果只使用一个loader的话,可以使用loader属性代替use,如下


                // loader:"style-loader"


                use: ["style-loader", "css-loader", "less-loader"],

            },


            {

                test: /\.s[ac]ss$/,


                use: ["style-loader", "css-loader", "sass-loader"],

            },


            {

                test: /\.styl$/,


                use: ["style-loader", "css-loader", "stylus-loader"],

            },

        ],

    },


    plugins: [],


    mode: "production",

};

使用

在src目录下,创建我们的styl文件夹,并生成index.styl 和 index.styl 文件,写入样式,在index.js,引入我们的styl文件,然后在pubilc的index.html文件中,创建对应的box文件,执行npx webpack,观察打包后的结果 页面效果如下:

资源模块

以下摘自官网的一段话 资源模块(asset module)是一种模块类型,它允许使用资源文件(字体,图标等)而无需配置额外 loader。 在 webpack 5 之前,通常使用:

raw-loader 将文件导入为字符串

url-loader 将文件作为 data URI 内联到 bundle 中

file-loader 将文件发送到输出目录

资源模块类型(asset module type),通过添加 4 种新的模块类型,来替换所有这些 loader:

asset/resource 发送一个单独的文件并导出 URL。之前通过使用 file-loader 实现。

asset/inline 导出一个资源的 data URI。之前通过使用 url-loader 实现。

asset/source 导出资源的源代码。之前通过使用 raw-loader 实现。

asset 在导出一个 data URI 和发送一个单独的文件之间自动选择。之前通过使用 url-loader,并且配置资源体积限制实现。

当在 webpack 5 中使用旧的 assets loader(如 file-loader/url-loader/raw-loader 等)和 asset 模块时,你可能想停止当前 asset 模块的处理,并再次启动处理,这可能会导致 asset 重复,你可以通过将 asset 模块的类型设置为 ‘javascript/auto’ 来解决。

处理图片资源

配置

使用

在src目录下,创建我们的images文件夹,添加图片文件,笔者这里添加了jpe,png,gif三种格式的图片,分别在不同的样式中进行引入,执行npx webpack,观察打包后的结果

此时如果查看 dist 目录的话,会发现多了三张图片资源

因为 Webpack 会将所有打包好的资源输出到 dist 目录下

为什么样式资源没有呢?

因为经过 style-loader 的处理,样式资源打包到 main.js 里面去了,所以没有额外输出出来

页面效果

图片资源处理优化

将小于某个大小的图片转化成 data URI 形式(Base64 格式)

优点:减少请求数量

缺点:体积变得更大,但是10kb以下的图片,转换为base64格式的情形下,只有增加1-2kb(所以我们出来10kb以下的)

配置

修改输出资源的名称和路径

我们发现打包完成后,图片直接输出到dist的根目录下了,没有规范起来,我们希望输出到dist的static/images目录下,同时js文件输出到dist的static/js目录下,这样就比较规范了 

配置

使用 通过以上配置后,dist文件输出的文件被规范起来了,这个时候需要修改一下我们index.html的引入路径,打开页面也是同样的效果

处理字体图标资源

当我们在项目中使用字体图标的时候,我们也希望打包的时候,将这一部分内容进行打包输出 

配置

// path 为 Node.js的核心模块,专门用来处理文件路径

const path = require("path");

module.exports = {

    // 入口

    entry: {

        // 需要一个相对路径

        index: "./src/index.js",

    },

    // 输出

    output: {

        // 需要一个绝对路径

        path: path.resolve(__dirname, "dist"),

        clean: true,

        filename: "static/js/index.js", // 将 js 文件输出到 static/js 目录中

    },

    // 解析器

    module: {

        rules: [

            {

                test: /\.css$/,

                // loader的执行顺序是从右往左的,所以这里先写style-loader,再写css-loader

                // 如果只使用一个loader的话,可以使用loader属性代替use,如下

                // loader:"style-loader"

                use: ["style-loader", "css-loader"],

            },

            {

                // 正则匹配所有已.less文件结尾的文件

                test: /\.less$/,

                // loader的执行顺序是从右往左的,所以这里先写style-loader,再写css-loader

                // 如果只使用一个loader的话,可以使用loader属性代替use,如下

                // loader:"style-loader"

                use: ["style-loader", "css-loader", "less-loader"],

            },

            {

                test: /\.s[ac]ss$/,

                use: ["style-loader", "css-loader", "sass-loader"],

            },

            {

                test: /\.styl$/,

                use: ["style-loader", "css-loader", "stylus-loader"],

            },

            {

                test: /\.(png|jpe?g|gif|webp)$/,

                type: "asset",

                // 类似于 module.generator,你可以用 module.parser 在一个地方配置所有解析器的选项。

                parser: {

                    // 如果一个模块源码大小小于 maxSize,那么模块会被作为一个 Base64 编码的字符串注入到包中, 否则模块文件会被生成到输出的目标目录中。

                    dataUrlCondition: {

                        maxSize: 10 * 1024, // 小于10kb的图片会被base64处理

                    },

                },

                // 可以使用 module.generator 在一个地方配置所有生成器的选项

                generator: {

                    // 将图片文件输出到 static/imgs 目录中

                    // 将图片文件命名 [hash:8][ext][query]

                    // [hash:8]: hash值取8位

                    // [ext]: 使用之前的文件扩展名

                    // [query]: 添加之前的query参数

                    filename: "static/imgs/[hash:8][ext][query]",

                },

            },

            {

                test: /\.(ttf|woff2?|svg)$/,

                // 发送一个单独的文件并导出 URL

                type: "asset/resource",

                generator: {

                    filename: "static/media/[hash:8][ext][query]",

                },

            },

        ],

    },

    plugins: [],

    mode: "production",

};

使用 在src目录下,创建我们的fonts文件夹,在阿里巴巴适量库中下载字体图标(笔者这里为了演示,使用的是font-class的形式),然后引入到fonts文件夹中,在index.js,然后在pubilc的index.html文件中,创建对应的span标签,添加class类名,执行npx webpack,观察打包后的结果 页面效果

处理其他资源

开发中可能还存在一些其他资源,如音视频等,我们也一起处理了

就是在处理字体图标资源基础上增加其他文件类型,统一处理即可

配置

js资源处理

这里有同学可能会问,js 资源 Webpack 不能已经处理了吗,为什么我们还要处理呢? 原因是 Webpack 对 js 处理是有限的,只能编译 js 中 ES 模块化语法,不能编译其他语法,导致 js 不能在 IE 等浏览器运行,所以我们希望做一些兼容性处理。 其次开发中,团队对代码格式是有严格要求的,我们不能由肉眼去检测代码格式,需要使用专业的工具来检测。 针对 js 兼容性处理,我们使用 Babel 来完成 针对代码格式,我们使用 Eslint 来完成 我们先完成 Eslint,检测代码格式无误后,在由 Babel 做代码兼容性处理

ESLint

介绍 简介:可组装的 JavaScript 和 JSX 检查工具。 这句话意思就是:它是用来检测 js 和 jsx 语法的工具,可以配置各项功能 我们使用 Eslint,关键是写 Eslint 配置文件,里面写上各种 rules 规则,将来运行 Eslint 时就会以写的规则对代码进行检查 配置文件 配置文件由很多种写法:

.eslintrc.*:新建文件,位于项目根目录

.eslintrc

.eslintrc.js

.eslintrc.json

区别在于配置格式不一样

package.json 中 eslintConfig:不需要创建文件,在原有文件基础上写

ESLint 会查找和自动读取它们,所以以上配置文件只需要存在一个即可

具体配置 我们以.eslintrc.js为例子进行配置 具体eslint的配置参考官网和规则文档,这里给出相关链接 ESlint规则 ESlint官网

下载

npm i eslint-webpack-plugin eslint -D

在webpack中使用

vsocde安装eslint插件 安装eslint插件后,配置需要忽略检查的文件

// .eslintignore

# 忽略dist目录下所有文件

dist

Babel

介绍 JavaScript 编译器。 主要用于将 ES6 语法编写的代码转换为向后兼容的 JavaScript 语法,以便能够运行在当前和旧版本的浏览器或其他环境中

配置文件

配置文件由很多种写法:

babel.config.*:新建文件,位于项目根目录

babel.config.js

babel.config.json

.babelrc.*:新建文件,位于项目根目录

.babelrc

.babelrc.js

.babelrc.json

package.json 中 babel:不需要创建文件,在原有文件基础上写

Babel 会查找和自动读取它们,所以以上配置文件只需要存在一个即可

具体配置 我们以babel.config.js为例子

// babel.config.js

module.exports = {

// 预设

presets: [],

};

presets 预设

简单理解:就是一组 Babel 插件, 扩展 Babel 功能

@babel/preset-env: 一个智能预设,允许您使用最新的 JavaScript。

@babel/preset-react:一个用来编译 React jsx 语法的预设

@babel/preset-typescript:一个用来编译 TypeScript 语法的预设

下载

npm i babel-loader @babel/core @babel/preset-env -D

配置

// path 为 Node.js的核心模块,专门用来处理文件路径

const path = require("path");

const ESLintWebpackPlugin = require("eslint-webpack-plugin");

module.exports = {

    // 入口

    entry: {

        // 需要一个相对路径

        index: "./src/index.js",

    },

    // 输出

    output: {

        // 需要一个绝对路径

        path: path.resolve(__dirname, "dist"),

        clean: true,

        filename: "static/js/index.js", // 将 js 文件输出到 static/js 目录中

    },

    // 解析器

    module: {

        rules: [

            {

                test: /\.css$/,

                // loader的执行顺序是从右往左的,所以这里先写style-loader,再写css-loader

                // 如果只使用一个loader的话,可以使用loader属性代替use,如下

                // loader:"style-loader"

                use: ["style-loader", "css-loader"],

            },

            {

                // 正则匹配所有已.less文件结尾的文件

                test: /\.less$/,

                // loader的执行顺序是从右往左的,所以这里先写style-loader,再写css-loader

                // 如果只使用一个loader的话,可以使用loader属性代替use,如下

                // loader:"style-loader"

                use: ["style-loader", "css-loader", "less-loader"],

            },

            {

                test: /\.s[ac]ss$/,

                use: ["style-loader", "css-loader", "sass-loader"],

            },

            {

                test: /\.styl$/,

                use: ["style-loader", "css-loader", "stylus-loader"],

            },

            {

                test: /\.(png|jpe?g|gif|webp)$/,

                type: "asset",

                // 类似于 module.generator,你可以用 module.parser 在一个地方配置所有解析器的选项。

                parser: {

                    // 如果一个模块源码大小小于 maxSize,那么模块会被作为一个 Base64 编码的字符串注入到包中, 否则模块文件会被生成到输出的目标目录中。

                    dataUrlCondition: {

                        maxSize: 10 * 1024, // 小于10kb的图片会被base64处理

                    },

                },

                // 可以使用 module.generator 在一个地方配置所有生成器的选项

                generator: {

                    // 将图片文件输出到 static/imgs 目录中

                    // 将图片文件命名 [hash:8][ext][query]

                    // [hash:8]: hash值取8位

                    // [ext]: 使用之前的文件扩展名

                    // [query]: 添加之前的query参数

                    filename: "static/imgs/[hash:8][ext][query]",

                },

            },

            {

                test: /\.(ttf|woff2?|svg)$/,

                // 发送一个单独的文件并导出 URL

                type: "asset/resource",

                generator: {

                    filename: "static/media/[hash:8][ext][query]",

                },

            },

            {

                test: /\.js$/,

                exclude: /node_modules/, // 排除node_modules代码不编译

                loader: "babel-loader",

            },

        ],

    },

    plugins: [

        new ESLintWebpackPlugin({

            // 指定检查文件的根目录

            context: path.resolve(__dirname, "src"),

        }),

    ],

    mode: "production",

};

HTML资源处理

在实际的开发工作中,我们希望使用一个html模块,然后将我们打包后的js文件自动引入到html模版中,这样我们开发的时候就不用手动引入或者修改打包后的文件了

插件

下载

npm i html-webpack-plugin -D

配置

css资源处理

Css 文件目前被打包到 js 文件中,当 js 文件加载时,会创建一个 style 标签来生成样式 这样对于网站来说,会出现闪屏现象,用户体验不好 我们应该是单独的 Css 文件,通过 link 标签加载性能才好

提取css为单独文件

下载插件

npm i mini-css-extract-plugin -D

配置

css兼容性处理

下载

npm i postcss-loader postcss postcss-preset-env -D

配置

配置需要兼容的浏览器列表

// package.json

"browserslist": [

"last 2 version",

"> 1%",

"not dead"

]

}

css压缩

下载

npm i css-minimizer-webpack-plugin -D

配置

// path 为 Node.js的核心模块,专门用来处理文件路径

const path = require("path");

const ESLintWebpackPlugin = require("eslint-webpack-plugin");

const HtmlWebpackPlugin = require("html-webpack-plugin");

const MiniCssExtractPlugin = require("mini-css-extract-plugin");

const CssMinimizerPlugin = require("css-minimizer-webpack-plugin");

module.exports = {

    // 入口

    entry: {

        // 需要一个相对路径

        index: "./src/index.js",

    },

    // 输出

    output: {

        // 需要一个绝对路径

        path: path.resolve(__dirname, "dist"),

        clean: true,

        filename: "static/js/index.js", // 将 js 文件输出到 static/js 目录中

    },

    // 解析器

    module: {

        rules: [

            {

                test: /\.css$/,

                // loader的执行顺序是从右往左的,所以这里先写style-loader,再写css-loader

                // 如果只使用一个loader的话,可以使用loader属性代替use,如下

                // loader:"style-loader"

                use: [

                    MiniCssExtractPlugin.loader,

                    "css-loader",

                    {

                        loader: "postcss-loader",

                        options: {

                            postcssOptions: {

                                plugins: [

                                    "postcss-preset-env", // 能解决大多数样式兼容性问题

                                ],

                            },

                        },

                    },

                ],

            },

            {

                // 正则匹配所有已.less文件结尾的文件

                test: /\.less$/,

                // loader的执行顺序是从右往左的,所以这里先写style-loader,再写css-loader

                // 如果只使用一个loader的话,可以使用loader属性代替use,如下

                // loader:"style-loader"

                use: [

                    MiniCssExtractPlugin.loader,

                    "css-loader",

                    {

                        loader: "postcss-loader",

                        options: {

                            postcssOptions: {

                                plugins: [

                                    "postcss-preset-env", // 能解决大多数样式兼容性问题

                                ],

                            },

                        },

                    },

                    "less-loader",

                ],

            },

            {

                test: /\.s[ac]ss$/,

                use: [

                    MiniCssExtractPlugin.loader,

                    "css-loader",

                    {

                        loader: "postcss-loader",

                        options: {

                            postcssOptions: {

                                plugins: [

                                    "postcss-preset-env", // 能解决大多数样式兼容性问题

                                ],

                            },

                        },

                    },

                    "sass-loader",

                ],

            },

            {

                test: /\.styl$/,

                use: [

                    MiniCssExtractPlugin.loader,

                    "css-loader",

                    {

                        loader: "postcss-loader",

                        options: {

                            postcssOptions: {

                                plugins: [

                                    "postcss-preset-env", // 能解决大多数样式兼容性问题

                                ],

                            },

                        },

                    },

                    "stylus-loader",

                ],

            },

            {

                test: /\.(png|jpe?g|gif|webp)$/,

                type: "asset",

                // 类似于 module.generator,你可以用 module.parser 在一个地方配置所有解析器的选项。

                parser: {

                    // 如果一个模块源码大小小于 maxSize,那么模块会被作为一个 Base64 编码的字符串注入到包中, 否则模块文件会被生成到输出的目标目录中。

                    dataUrlCondition: {

                        maxSize: 10 * 1024, // 小于10kb的图片会被base64处理

                    },

                },

                // 可以使用 module.generator 在一个地方配置所有生成器的选项

                generator: {

                    // 将图片文件输出到 static/imgs 目录中

                    // 将图片文件命名 [hash:8][ext][query]

                    // [hash:8]: hash值取8位

                    // [ext]: 使用之前的文件扩展名

                    // [query]: 添加之前的query参数

                    filename: "static/imgs/[hash:8][ext][query]",

                },

            },

            {

                test: /\.(ttf|woff2?|svg)$/,

                // 发送一个单独的文件并导出 URL

                type: "asset/resource",

                generator: {

                    filename: "static/media/[hash:8][ext][query]",

                },

            },

            {

                test: /\.js$/,

                exclude: /node_modules/, // 排除node_modules代码不编译

                loader: "babel-loader",

            },

        ],

    },

    plugins: [

        new ESLintWebpackPlugin({

            // 指定检查文件的根目录

            context: path.resolve(__dirname, "src"),

        }),

        new HtmlWebpackPlugin({

            // 以 public/index.html 为模板创建文件

            // 新的html文件有两个特点:1. 内容和源文件一致 2. 自动引入打包生成的js等资源

            template: path.resolve(__dirname, "public/index.html"),

        }),

        // 提取css成单独文件

        new MiniCssExtractPlugin({

            // 定义输出文件名和目录

            filename: "static/css/index.css",

        }),

        new CssMinimizerPlugin(),

    ],

    mode: "production",

};

合并样式处理配置

开发服务器&自动化

在我们的日常开发中我们希望我改了代码后,会自动更新我们更改后的内容,而不用每次打包后才能看到效果,所以webpack提供了一个devServer的配置 下载

npm i webpack-dev-server -D

配置

// path 为 Node.js的核心模块,专门用来处理文件路径

const path = require('path')

const ESLintWebpackPlugin = require("eslint-webpack-plugin");

const HtmlWebpackPlugin = require("html-webpack-plugin");

module.exports = {

// 入口

entry: {

// 需要一个相对路径

index: './src/index.js'

},

// 输出

output: {

// 需要一个绝对路径

path: path.resolve(__dirname, 'dist'),

clean: true,

filename: "static/js/index.js", // 将 js 文件输出到 static/js 目录中

},

// 解析器

module: {

rules: [

{

test: /\.css$/,

// loader的执行顺序是从右往左的,所以这里先写style-loader,再写css-loader

// 如果只使用一个loader的话,可以使用loader属性代替use,如下

// loader:"style-loader"

use: ["style-loader", "css-loader"],

},

{

// 正则匹配所有已.less文件结尾的文件

test: /\.less$/,

// loader的执行顺序是从右往左的,所以这里先写style-loader,再写css-loader

// 如果只使用一个loader的话,可以使用loader属性代替use,如下

// loader:"style-loader"

use: ["style-loader", "css-loader", "less-loader"],

},

{

test: /\.s[ac]ss$/,

use: ["style-loader", "css-loader", "sass-loader"],

},

{

test: /\.styl$/,

use: ["style-loader", "css-loader", "stylus-loader"],

},

{

test: /\.(png|jpe?g|gif|webp)$/,

type: "asset",

// 类似于 module.generator,你可以用 module.parser 在一个地方配置所有解析器的选项。

parser: {

// 如果一个模块源码大小小于 maxSize,那么模块会被作为一个 Base64 编码的字符串注入到包中, 否则模块文件会被生成到输出的目标目录中。

dataUrlCondition: {

maxSize: 10 * 1024 // 小于10kb的图片会被base64处理

}

},

// 可以使用 module.generator 在一个地方配置所有生成器的选项

generator: {

// 将图片文件输出到 static/imgs 目录中

// 将图片文件命名 [hash:8][ext][query]

// [hash:8]: hash值取8位

// [ext]: 使用之前的文件扩展名

// [query]: 添加之前的query参数

filename: "static/imgs/[hash:8][ext][query]",

},

},

{

test: /\.(ttf|woff2?|svg)$/,

// 发送一个单独的文件并导出 URL

type: "asset/resource",

generator: {

filename: "static/media/[hash:8][ext][query]",

},

},

{

test: /\.js$/,

exclude: /node_modules/, // 排除node_modules代码不编译

loader: "babel-loader",

},

]

},

plugins: [

new ESLintWebpackPlugin({

// 指定检查文件的根目录

context: path.resolve(__dirname, "src"),

}),

new HtmlWebpackPlugin({

// 以 public/index.html 为模板创建文件

// 新的html文件有两个特点:1. 内容和源文件一致 2. 自动引入打包生成的js等资源

template: path.resolve(__dirname, "public/index.html"),

})

],

// 开发服务器

devServer: {

host: "localhost", // 启动服务器域名

port: "3000", // 启动服务器端口号

open: true, // 是否自动打开浏览器

},

mode: 'production'

}

运行

npx webpack serve

并且当你使用开发服务器时,所有代码都会在内存中编译打包,并不会输出到 dist 目录下。 开发时我们只关心代码能运行,有效果即可,至于代码被编译成什么样子,我们并不需要知道。

开发模式与生产模式

在我们日常开发中,我们总是希望我们开发完成后,今天一系列打包优化,性能提升到最好,所以webpack这里也给我们提供了使用不同的配置文件进行配置

开发模式

适用于dev环境,配合devServe配置,进行开发,并自动实现代码更新,所有代码都会在内存中编译打包,并不会输出到 dist 目录下。

生产模式

生产模式是开发完成代码后,我们需要得到代码将来部署上线。 这个模式下我们主要对代码进行优化,让其运行性能更好。 优化主要从两个角度出发:

优化代码运行性能优化代码打包速度

使用

我们分别准备两个配置文件来放不同的配置,分别对应开发模式和生产模式

目录配置

├── webpack-document (项目根目录)

├── config (Webpack配置文件目录)

│ ├── webpack.dev.js(开发模式配置文件)

│ └── webpack.prod.js(生产模式配置文件)

├── node_modules (下载包存放目录)

├── src (项目源码目录,除了html其他都在src里面)

│ └── 略

├── public (项目html文件)

│ └── index.html

├── .eslintrc.js(Eslint配置文件)

├── babel.config.js(Babel配置文件)

└── package.json (包的依赖管理配置文件)

生产不同的webpack配置,并做里面的绝对路径进行相应的修改

webpack.dev.js配置

// path 为 Node.js的核心模块,专门用来处理文件路径

const path = require('path')

const ESLintWebpackPlugin = require("eslint-webpack-plugin");

const HtmlWebpackPlugin = require("html-webpack-plugin");

const MiniCssExtractPlugin = require("mini-css-extract-plugin");

const CssMinimizerPlugin = require("css-minimizer-webpack-plugin");

// 获取处理样式的Loaders

const getStyleLoaders = (preProcessor) => {

return [

MiniCssExtractPlugin.loader,

"css-loader",

{

loader: "postcss-loader",

options: {

postcssOptions: {

plugins: [

"postcss-preset-env", // 能解决大多数样式兼容性问题

],

},

},

},

preProcessor,

].filter(Boolean);

};

module.exports = {

// 入口

entry: {

// 需要一个相对路径

index: './src/index.js'

},

// 输出

output: {

path: undefined, // 开发模式没有输出,不需要指定输出目录

filename: "static/js/main.js", // 将 js 文件输出到 static/js 目录中

// clean: true, // 开发模式没有输出,不需要清空输出结果

},

// Performance 这些选项可以控制 webpack 如何通知「资源(asset)和入口起点超过指定文件限制」。

performance: {

hints: false

},

// 解析器

module: {

rules: [

{

test: /\.css$/,

// loader的执行顺序是从右往左的,所以这里先写style-loader,再写css-loader

// 如果只使用一个loader的话,可以使用loader属性代替use,如下

// loader:"style-loader"

use: getStyleLoaders(),

},

{

// 正则匹配所有已.less文件结尾的文件

test: /\.less$/,

// loader的执行顺序是从右往左的,所以这里先写style-loader,再写css-loader

// 如果只使用一个loader的话,可以使用loader属性代替use,如下

// loader:"style-loader"

use: getStyleLoaders("less-loader"),

},

{

test: /\.s[ac]ss$/,

use: getStyleLoaders("sass-loader"),

},

{

test: /\.styl$/,

use: getStyleLoaders("stylus-loader"),

},

{

test: /\.(png|jpe?g|gif|webp)$/,

type: "asset",

// 类似于 module.generator,你可以用 module.parser 在一个地方配置所有解析器的选项。

parser: {

// 如果一个模块源码大小小于 maxSize,那么模块会被作为一个 Base64 编码的字符串注入到包中, 否则模块文件会被生成到输出的目标目录中。

dataUrlCondition: {

maxSize: 10 * 1024 // 小于10kb的图片会被base64处理

}

},

// 可以使用 module.generator 在一个地方配置所有生成器的选项

generator: {

// 将图片文件输出到 static/imgs 目录中

// 将图片文件命名 [hash:8][ext][query]

// [hash:8]: hash值取8位

// [ext]: 使用之前的文件扩展名

// [query]: 添加之前的query参数

filename: "static/imgs/[hash:8][ext][query]",

},

},

{

test: /\.(ttf|woff2?|svg)$/,

// 发送一个单独的文件并导出 URL

type: "asset/resource",

generator: {

filename: "static/media/[hash:8][ext][query]",

},

},

{

test: /\.js$/,

exclude: /node_modules/, // 排除node_modules代码不编译

loader: "babel-loader",

},

]

},

plugins: [

new ESLintWebpackPlugin({

// 指定检查文件的根目录

context: path.resolve(__dirname, "../src"),

}),

new HtmlWebpackPlugin({

// 以 public/index.html 为模板创建文件

// 新的html文件有两个特点:1. 内容和源文件一致 2. 自动引入打包生成的js等资源

template: path.resolve(__dirname, "../public/index.html"),

}),

// 提取css成单独文件

new MiniCssExtractPlugin({

// 定义输出文件名和目录

filename: "static/css/index.css",

}),

new CssMinimizerPlugin()

],

// 开发服务器

devServer: {

host: "localhost", // 启动服务器域名

port: "3000", // 启动服务器端口号

open: true, // 是否自动打开浏览器

},

mode: 'development'

}

webpack.prod.js配置

// path 为 Node.js的核心模块,专门用来处理文件路径

const path = require('path')

const ESLintWebpackPlugin = require("eslint-webpack-plugin");

const HtmlWebpackPlugin = require("html-webpack-plugin");

const MiniCssExtractPlugin = require("mini-css-extract-plugin");

const CssMinimizerPlugin = require("css-minimizer-webpack-plugin");

// 获取处理样式的Loaders

const getStyleLoaders = (preProcessor) => {

return [

MiniCssExtractPlugin.loader,

"css-loader",

{

loader: "postcss-loader",

options: {

postcssOptions: {

plugins: [

"postcss-preset-env", // 能解决大多数样式兼容性问题

],

},

},

},

preProcessor,

].filter(Boolean);

};

module.exports = {

// 入口

entry: {

// 需要一个相对路径

index: './src/index.js'

},

// 输出

output: {

// 需要一个绝对路径

path: path.resolve(__dirname, '../dist'),

clean: true,

filename: "static/js/index.js", // 将 js 文件输出到 static/js 目录中

},

// Performance 这些选项可以控制 webpack 如何通知「资源(asset)和入口起点超过指定文件限制」。

performance: {

hints: false

},

// 解析器

module: {

rules: [

{

test: /\.css$/,

// loader的执行顺序是从右往左的,所以这里先写style-loader,再写css-loader

// 如果只使用一个loader的话,可以使用loader属性代替use,如下

// loader:"style-loader"

use: getStyleLoaders(),

},

{

// 正则匹配所有已.less文件结尾的文件

test: /\.less$/,

// loader的执行顺序是从右往左的,所以这里先写style-loader,再写css-loader

// 如果只使用一个loader的话,可以使用loader属性代替use,如下

// loader:"style-loader"

use: getStyleLoaders("less-loader"),

},

{

test: /\.s[ac]ss$/,

use: getStyleLoaders("sass-loader"),

},

{

test: /\.styl$/,

use: getStyleLoaders("stylus-loader"),

},

{

test: /\.(png|jpe?g|gif|webp)$/,

type: "asset",

// 类似于 module.generator,你可以用 module.parser 在一个地方配置所有解析器的选项。

parser: {

// 如果一个模块源码大小小于 maxSize,那么模块会被作为一个 Base64 编码的字符串注入到包中, 否则模块文件会被生成到输出的目标目录中。

dataUrlCondition: {

maxSize: 10 * 1024 // 小于10kb的图片会被base64处理

}

},

// 可以使用 module.generator 在一个地方配置所有生成器的选项

generator: {

// 将图片文件输出到 static/imgs 目录中

// 将图片文件命名 [hash:8][ext][query]

// [hash:8]: hash值取8位

// [ext]: 使用之前的文件扩展名

// [query]: 添加之前的query参数

filename: "static/imgs/[hash:8][ext][query]",

},

},

{

test: /\.(ttf|woff2?|svg)$/,

// 发送一个单独的文件并导出 URL

type: "asset/resource",

generator: {

filename: "static/media/[hash:8][ext][query]",

},

},

{

test: /\.js$/,

exclude: /node_modules/, // 排除node_modules代码不编译

loader: "babel-loader",

},

]

},

plugins: [

new ESLintWebpackPlugin({

// 指定检查文件的根目录

context: path.resolve(__dirname, "../src"),

}),

new HtmlWebpackPlugin({

// 以 public/index.html 为模板创建文件

// 新的html文件有两个特点:1. 内容和源文件一致 2. 自动引入打包生成的js等资源

template: path.resolve(__dirname, "../public/index.html"),

}),

// 提取css成单独文件

new MiniCssExtractPlugin({

// 定义输出文件名和目录

filename: "static/css/index.css",

}),

new CssMinimizerPlugin()

],

// 开发服务器

// devServer: {

// host: "localhost", // 启动服务器域名

// port: "3000", // 启动服务器端口号

// open: true, // 是否自动打开浏览器

// },

mode: 'production'

}

package.json配置

{

"name": "webpack_document",

"version": "1.0.0",

"description": "",

"main": "index.js",

// 为了方便运行不同模式的指令,我们将指令定义在 package.json 中 scripts 里面

"scripts": {

"start": "npx webpack serve --config ./config/webpack.dev.js",

"dev": "npx webpack serve --config ./config/webpack.dev.js",

"build": "npx webpack --config ./config/webpack.prod.js",

"test": "echo \"Error: no test specified\" && exit 1"

},

"author": "",

"license": "ISC",

"devDependencies": {

"@babel/core": "^7.20.12",

"@babel/preset-env": "^7.20.2",

"babel-loader": "^9.1.2",

"css-loader": "^6.7.3",

"eslint": "^8.33.0",

"eslint-webpack-plugin": "^3.2.0",

"html-webpack-plugin": "^5.5.0",

"less-loader": "^11.1.0",

"sass": "^1.58.0",

"sass-loader": "^13.2.0",

"style-loader": "^3.3.1",

"stylus-loader": "^7.1.0",

"webpack": "^5.75.0",

"webpack-cli": "^5.0.1",

"webpack-dev-server": "^4.11.1"

}

}

开发模式:npm start 或 npm run dev 生产模式:npm run build

基础总结

如果你学习到这里,恭喜你现在已经是一个webpack小能手了

两种开发模式

开发模式:代码能编译自动化运行

生产模式:代码编译优化输出

Webpack 基本功能

开发模式:可以编译 ES Module 语法

生产模式:可以编译 ES Module 语法,压缩 js 代码

Webpack 配置文件

5 个核心概念

entry

output

loader

plugins

mode

devServer 配置

Webpack 脚本指令用法

webpack 直接打包输出

webpack serve 启动开发服务器,内存编译打包没有输出

高级

提升开发体验之sourceMap

在开发过程中,我们运行的代码是经过webpack编译后的代码,如果代码出错了,我们想看到代码的映射为我们自己的代码,这个时候就需要sourceMap 了

介绍

SourceMap(源代码映射)是一个用来生成源代码与构建后代码一一映射的文件的方案。

它会生成一个 xxx.map 文件,里面包含源代码和构建后代码每一行、每一列的映射关系。当构建后代码出错了,会通过 xxx.map 文件,从构建后代码出错位置找到映射后源代码出错位置,从而让浏览器提示源代码文件出错位置,帮助我们更快的找到错误根源。

使用

通过查看Webpack DevTool 文档文档可知,SourceMap 的值有很多种情况. 但实际开发时我们只需要关注两种情况即可:

开发模式:cheap-module-source-map

优点:打包编译速度快,只包含行映射

缺点:没有列映射

module.exports = {

// 其他省略

mode: "development",

devtool: "cheap-module-source-map",

};

生产模式:source-map

优点:包含行/列映射

缺点:打包编译速度更慢

module.exports = {

// 其他省略

mode: "production",

devtool: "source-map",

};

提升打包构建速度

介绍

开发时我们修改了其中一个模块代码,Webpack 默认会将所有模块全部重新打包编译,速度很慢。 所以我们需要做到修改某个模块代码,就只有这个模块代码需要重新打包编译,其他模块不变,这样打包速度就能很快。

HotModuleReplacement(HMR/热模块替换):在程序运行中,替换、添加或删除模块,而无需重新加载整个页面。

使用

module.exports = {

// 其他省略

devServer: {

host: "localhost", // 启动服务器域名

port: "3000", // 启动服务器端口号

open: true, // 是否自动打开浏览器

hot: true, // 开启HMR功能(只能用于开发环境,生产环境不需要了)

},

};

此时 css 样式经过 style-loader 处理,已经具备 HMR 功能了。 但是 js 还不行。 JS 配置需要手动进行属性需要进行热模块使用的功能

// 判断是否支持HMR功能

if (module.hot) {

module.hot.accept("./js/sum");

}

这样如果我们自己搭建脚手架,每个引入的文件都需要进行这样更新一次,这里我们推荐使用社区的loader进行解决,比如:vue-loader, react-hot-loader。

使用oneof对loader进行处理

介绍

打包时每个文件都会经过所有 loader 处理,虽然因为 test 正则原因实际没有处理上,但是都要过一遍。比较慢。

规则数组,当规则匹配时,只使用第一个匹配规则。

使用

我们的生产模式和开发模式都需要配置,这里笔者展示开发模式的配置

// path 为 Node.js的核心模块,专门用来处理文件路径

const path = require('path')

const ESLintWebpackPlugin = require("eslint-webpack-plugin");

const HtmlWebpackPlugin = require("html-webpack-plugin");

const MiniCssExtractPlugin = require("mini-css-extract-plugin");

const CssMinimizerPlugin = require("css-minimizer-webpack-plugin");

// 获取处理样式的Loaders

const getStyleLoaders = (preProcessor) => {

return [

MiniCssExtractPlugin.loader,

"css-loader",

{

loader: "postcss-loader",

options: {

postcssOptions: {

plugins: [

"postcss-preset-env", // 能解决大多数样式兼容性问题

],

},

},

},

preProcessor,

].filter(Boolean);

};

module.exports = {

// 入口

entry: {

// 需要一个相对路径

index: './src/index.js'

},

// 输出

output: {

path: undefined, // 开发模式没有输出,不需要指定输出目录

filename: "static/js/main.js", // 将 js 文件输出到 static/js 目录中

// clean: true, // 开发模式没有输出,不需要清空输出结果

},

// Performance 这些选项可以控制 webpack 如何通知「资源(asset)和入口起点超过指定文件限制」。

performance: {

hints: false

},

devtool: "cheap-module-source-map",

// 解析器

module: {

rules: [

{

// 每个文件只能被其中的一个loader处理

oneOf: [

{

test: /\.css$/,

// loader的执行顺序是从右往左的,所以这里先写style-loader,再写css-loader

// 如果只使用一个loader的话,可以使用loader属性代替use,如下

// loader:"style-loader"

use: getStyleLoaders(),

},

{

// 正则匹配所有已.less文件结尾的文件

test: /\.less$/,

// loader的执行顺序是从右往左的,所以这里先写style-loader,再写css-loader

// 如果只使用一个loader的话,可以使用loader属性代替use,如下

// loader:"style-loader"

use: getStyleLoaders("less-loader"),

},

{

test: /\.s[ac]ss$/,

use: getStyleLoaders("sass-loader"),

},

{

test: /\.styl$/,

use: getStyleLoaders("stylus-loader"),

},

{

test: /\.(png|jpe?g|gif|webp)$/,

type: "asset",

// 类似于 module.generator,你可以用 module.parser 在一个地方配置所有解析器的选项。

parser: {

// 如果一个模块源码大小小于 maxSize,那么模块会被作为一个 Base64 编码的字符串注入到包中, 否则模块文件会被生成到输出的目标目录中。

dataUrlCondition: {

maxSize: 10 * 1024 // 小于10kb的图片会被base64处理

}

},

// 可以使用 module.generator 在一个地方配置所有生成器的选项

generator: {

// 将图片文件输出到 static/imgs 目录中

// 将图片文件命名 [hash:8][ext][query]

// [hash:8]: hash值取8位

// [ext]: 使用之前的文件扩展名

// [query]: 添加之前的query参数

filename: "static/imgs/[hash:8][ext][query]",

},

},

{

test: /\.(ttf|woff2?|svg)$/,

// 发送一个单独的文件并导出 URL

type: "asset/resource",

generator: {

filename: "static/media/[hash:8][ext][query]",

},

},

{

test: /\.js$/,

exclude: /node_modules/, // 排除node_modules代码不编译

loader: "babel-loader",

},

]

}

]

},

plugins: [

new ESLintWebpackPlugin({

// 指定检查文件的根目录

context: path.resolve(__dirname, "../src"),

}),

new HtmlWebpackPlugin({

// 以 public/index.html 为模板创建文件

// 新的html文件有两个特点:1. 内容和源文件一致 2. 自动引入打包生成的js等资源

template: path.resolve(__dirname, "../public/index.html"),

}),

// 提取css成单独文件

new MiniCssExtractPlugin({

// 定义输出文件名和目录

filename: "static/css/index.css",

}),

new CssMinimizerPlugin()

],

// 开发服务器

devServer: {

host: "localhost", // 启动服务器域名

port: "3000", // 启动服务器端口号

open: true, // 是否自动打开浏览器

hot: true, // 开启HMR功能(只能用于开发环境,生产环境不需要了)

},

mode: 'development'

}

Include/Exclude

介绍

开发时我们需要使用第三方的库或插件,所有文件都下载到 node_modules 中了。而这些文件是不需要编译可以直接使用的。

所以我们在对 js 文件处理时,要排除 node_modules 下面的文件。(只针对js文件进行处理,只有babel和eslint在进行工作)

注意:以下两个配置只能写一种

include

包含,只处理 xxx 文件

exclude

排除,除了 xxx 文件以外其他文件都处理

配置

// path 为 Node.js的核心模块,专门用来处理文件路径

const path = require('path')

const ESLintWebpackPlugin = require("eslint-webpack-plugin");

const HtmlWebpackPlugin = require("html-webpack-plugin");

const MiniCssExtractPlugin = require("mini-css-extract-plugin");

const CssMinimizerPlugin = require("css-minimizer-webpack-plugin");

// 获取处理样式的Loaders

const getStyleLoaders = (preProcessor) => {

return [

MiniCssExtractPlugin.loader,

"css-loader",

{

loader: "postcss-loader",

options: {

postcssOptions: {

plugins: [

"postcss-preset-env", // 能解决大多数样式兼容性问题

],

},

},

},

preProcessor,

].filter(Boolean);

};

module.exports = {

// 入口

entry: {

// 需要一个相对路径

index: './src/index.js'

},

// 输出

output: {

// 需要一个绝对路径

path: path.resolve(__dirname, '../dist'),

clean: true,

filename: "static/js/index.js", // 将 js 文件输出到 static/js 目录中

},

// Performance 这些选项可以控制 webpack 如何通知「资源(asset)和入口起点超过指定文件限制」。

performance: {

hints: false

},

devtool: "source-map",

// 解析器

module: {

rules: [

{

oneOf: [

{

test: /\.css$/,

// loader的执行顺序是从右往左的,所以这里先写style-loader,再写css-loader

// 如果只使用一个loader的话,可以使用loader属性代替use,如下

// loader:"style-loader"

use: getStyleLoaders(),

},

{

// 正则匹配所有已.less文件结尾的文件

test: /\.less$/,

// loader的执行顺序是从右往左的,所以这里先写style-loader,再写css-loader

// 如果只使用一个loader的话,可以使用loader属性代替use,如下

// loader:"style-loader"

use: getStyleLoaders("less-loader"),

},

{

test: /\.s[ac]ss$/,

use: getStyleLoaders("sass-loader"),

},

{

test: /\.styl$/,

use: getStyleLoaders("stylus-loader"),

},

{

test: /\.(png|jpe?g|gif|webp)$/,

type: "asset",

// 类似于 module.generator,你可以用 module.parser 在一个地方配置所有解析器的选项。

parser: {

// 如果一个模块源码大小小于 maxSize,那么模块会被作为一个 Base64 编码的字符串注入到包中, 否则模块文件会被生成到输出的目标目录中。

dataUrlCondition: {

maxSize: 10 * 1024 // 小于10kb的图片会被base64处理

}

},

// 可以使用 module.generator 在一个地方配置所有生成器的选项

generator: {

// 将图片文件输出到 static/imgs 目录中

// 将图片文件命名 [hash:8][ext][query]

// [hash:8]: hash值取8位

// [ext]: 使用之前的文件扩展名

// [query]: 添加之前的query参数

filename: "static/imgs/[hash:8][ext][query]",

},

},

{

test: /\.(ttf|woff2?|svg)$/,

// 发送一个单独的文件并导出 URL

type: "asset/resource",

generator: {

filename: "static/media/[hash:8][ext][query]",

},

},

{

test: /\.js$/,

// exclude: /node_modules/, // 排除node_modules代码不编译

include: path.resolve(__dirname, "../src"), // 也可以用包含

loader: "babel-loader",

},

]

}

]

},

plugins: [

new ESLintWebpackPlugin({

// 指定检查文件的根目录

context: path.resolve(__dirname, "../src"),

exclude: "node_modules", // 默认值

}),

new HtmlWebpackPlugin({

// 以 public/index.html 为模板创建文件

// 新的html文件有两个特点:1. 内容和源文件一致 2. 自动引入打包生成的js等资源

template: path.resolve(__dirname, "../public/index.html"),

}),

// 提取css成单独文件

new MiniCssExtractPlugin({

// 定义输出文件名和目录

filename: "static/css/index.css",

}),

new CssMinimizerPlugin()

],

// 开发服务器

// devServer: {

// host: "localhost", // 启动服务器域名

// port: "3000", // 启动服务器端口号

// open: true, // 是否自动打开浏览器

// },

mode: 'production'

}

cache缓存

介绍

每次打包时 js 文件都要经过 Eslint 检查 和 Babel 编译,速度比较慢。

我们可以缓存之前的 Eslint 检查 和 Babel 编译结果,这样第二次打包时速度就会更快了。

对 Eslint 检查 和 Babel 编译结果进行缓存。

配置

我们的生产模式和开发模式都需要配置,这里笔者展示开发模式的配置

// path 为 Node.js的核心模块,专门用来处理文件路径

const path = require('path')

const ESLintWebpackPlugin = require("eslint-webpack-plugin");

const HtmlWebpackPlugin = require("html-webpack-plugin");

const MiniCssExtractPlugin = require("mini-css-extract-plugin");

const CssMinimizerPlugin = require("css-minimizer-webpack-plugin");

// 获取处理样式的Loaders

const getStyleLoaders = (preProcessor) => {

return [

MiniCssExtractPlugin.loader,

"css-loader",

{

loader: "postcss-loader",

options: {

postcssOptions: {

plugins: [

"postcss-preset-env", // 能解决大多数样式兼容性问题

],

},

},

},

preProcessor,

].filter(Boolean);

};

module.exports = {

// 入口

entry: {

// 需要一个相对路径

index: './src/index.js'

},

// 输出

output: {

path: undefined, // 开发模式没有输出,不需要指定输出目录

filename: "static/js/main.js", // 将 js 文件输出到 static/js 目录中

// clean: true, // 开发模式没有输出,不需要清空输出结果

},

// Performance 这些选项可以控制 webpack 如何通知「资源(asset)和入口起点超过指定文件限制」。

performance: {

hints: false

},

devtool: "cheap-module-source-map",

// 解析器

module: {

rules: [

{

// 每个文件只能被其中的一个loader处理

oneOf: [

{

test: /\.css$/,

// loader的执行顺序是从右往左的,所以这里先写style-loader,再写css-loader

// 如果只使用一个loader的话,可以使用loader属性代替use,如下

// loader:"style-loader"

use: getStyleLoaders(),

},

{

// 正则匹配所有已.less文件结尾的文件

test: /\.less$/,

// loader的执行顺序是从右往左的,所以这里先写style-loader,再写css-loader

// 如果只使用一个loader的话,可以使用loader属性代替use,如下

// loader:"style-loader"

use: getStyleLoaders("less-loader"),

},

{

test: /\.s[ac]ss$/,

use: getStyleLoaders("sass-loader"),

},

{

test: /\.styl$/,

use: getStyleLoaders("stylus-loader"),

},

{

test: /\.(png|jpe?g|gif|webp)$/,

type: "asset",

// 类似于 module.generator,你可以用 module.parser 在一个地方配置所有解析器的选项。

parser: {

// 如果一个模块源码大小小于 maxSize,那么模块会被作为一个 Base64 编码的字符串注入到包中, 否则模块文件会被生成到输出的目标目录中。

dataUrlCondition: {

maxSize: 10 * 1024 // 小于10kb的图片会被base64处理

}

},

// 可以使用 module.generator 在一个地方配置所有生成器的选项

generator: {

// 将图片文件输出到 static/imgs 目录中

// 将图片文件命名 [hash:8][ext][query]

// [hash:8]: hash值取8位

// [ext]: 使用之前的文件扩展名

// [query]: 添加之前的query参数

filename: "static/imgs/[hash:8][ext][query]",

},

},

{

test: /\.(ttf|woff2?|svg)$/,

// 发送一个单独的文件并导出 URL

type: "asset/resource",

generator: {

filename: "static/media/[hash:8][ext][query]",

},

},

{

test: /\.js$/,

// exclude: /node_modules/, // 排除node_modules代码不编译

include: path.resolve(__dirname, "../src"), // 也可以用包含

loader: "babel-loader",

options: {

cacheDirectory: true, // 开启babel编译缓存

cacheCompression: false, // 缓存文件不要压缩

},

},

]

}

]

},

plugins: [

new ESLintWebpackPlugin({

// 指定检查文件的根目录

context: path.resolve(__dirname, "../src"),

exclude: "node_modules", // 默认值

cache: true, // 开启缓存

// 缓存目录

cacheLocation: path.resolve(

__dirname,

"../node_modules/.cache/.eslintcache"

),

}),

new HtmlWebpackPlugin({

// 以 public/index.html 为模板创建文件

// 新的html文件有两个特点:1. 内容和源文件一致 2. 自动引入打包生成的js等资源

template: path.resolve(__dirname, "../public/index.html"),

}),

// 提取css成单独文件

new MiniCssExtractPlugin({

// 定义输出文件名和目录

filename: "static/css/index.css",

}),

new CssMinimizerPlugin()

],

// 开发服务器

devServer: {

host: "localhost", // 启动服务器域名

port: "3000", // 启动服务器端口号

open: true, // 是否自动打开浏览器

hot: true, // 开启HMR功能(只能用于开发环境,生产环境不需要了)

},

mode: 'development'

}

Thead多进程打包

介绍

当项目越来越庞大时,打包速度越来越慢,甚至于需要一个下午才能打包出来代码。这个速度是比较慢的。

我们想要继续提升打包速度,其实就是要提升 js 的打包速度,因为其他文件都比较少。

而对 js 文件处理主要就是 eslint 、babel、Terser 三个工具,所以我们要提升它们的运行速度。

我们可以开启多进程同时处理 js 文件,这样速度就比之前的单进程打包更快了。

多进程打包:开启电脑的多个进程同时干一件事,速度更快。

需要注意:请仅在特别耗时的操作中使用,因为每个进程启动就有大约为 600ms 左右开销。

使用

下载

npm i thread-loader -D

获取cpu核数

// nodejs核心模块,直接使用

const os = require("os");

// cpu核数

const threads = os.cpus().length;

配置 我们的生产模式和开发模式都需要配置,这里笔者展示开发模式的配置

// path 为 Node.js的核心模块,专门用来处理文件路径

const path = require('path')

const os = require("os");

const ESLintWebpackPlugin = require("eslint-webpack-plugin");

const HtmlWebpackPlugin = require("html-webpack-plugin");

const MiniCssExtractPlugin = require("mini-css-extract-plugin");

const CssMinimizerPlugin = require("css-minimizer-webpack-plugin");

const TerserPlugin = require("terser-webpack-plugin");

// cpu核数

const threads = os.cpus().length;

// 获取处理样式的Loaders

const getStyleLoaders = (preProcessor) => {

return [

MiniCssExtractPlugin.loader,

"css-loader",

{

loader: "postcss-loader",

options: {

postcssOptions: {

plugins: [

"postcss-preset-env", // 能解决大多数样式兼容性问题

],

},

},

},

preProcessor,

].filter(Boolean);

};

module.exports = {

// 入口

entry: {

// 需要一个相对路径

index: './src/index.js'

},

// 输出

output: {

path: undefined, // 开发模式没有输出,不需要指定输出目录

filename: "static/js/main.js", // 将 js 文件输出到 static/js 目录中

// clean: true, // 开发模式没有输出,不需要清空输出结果

},

// Performance 这些选项可以控制 webpack 如何通知「资源(asset)和入口起点超过指定文件限制」。

performance: {

hints: false

},

devtool: "cheap-module-source-map",

// 解析器

module: {

rules: [

{

// 每个文件只能被其中的一个loader处理

oneOf: [

{

test: /\.css$/,

// loader的执行顺序是从右往左的,所以这里先写style-loader,再写css-loader

// 如果只使用一个loader的话,可以使用loader属性代替use,如下

// loader:"style-loader"

use: getStyleLoaders(),

},

{

// 正则匹配所有已.less文件结尾的文件

test: /\.less$/,

// loader的执行顺序是从右往左的,所以这里先写style-loader,再写css-loader

// 如果只使用一个loader的话,可以使用loader属性代替use,如下

// loader:"style-loader"

use: getStyleLoaders("less-loader"),

},

{

test: /\.s[ac]ss$/,

use: getStyleLoaders("sass-loader"),

},

{

test: /\.styl$/,

use: getStyleLoaders("stylus-loader"),

},

{

test: /\.(png|jpe?g|gif|webp)$/,

type: "asset",

// 类似于 module.generator,你可以用 module.parser 在一个地方配置所有解析器的选项。

parser: {

// 如果一个模块源码大小小于 maxSize,那么模块会被作为一个 Base64 编码的字符串注入到包中, 否则模块文件会被生成到输出的目标目录中。

dataUrlCondition: {

maxSize: 10 * 1024 // 小于10kb的图片会被base64处理

}

},

// 可以使用 module.generator 在一个地方配置所有生成器的选项

generator: {

// 将图片文件输出到 static/imgs 目录中

// 将图片文件命名 [hash:8][ext][query]

// [hash:8]: hash值取8位

// [ext]: 使用之前的文件扩展名

// [query]: 添加之前的query参数

filename: "static/imgs/[hash:8][ext][query]",

},

},

{

test: /\.(ttf|woff2?|svg)$/,

// 发送一个单独的文件并导出 URL

type: "asset/resource",

generator: {

filename: "static/media/[hash:8][ext][query]",

},

},

{

test: /\.js$/,

// exclude: /node_modules/, // 排除node_modules代码不编译

include: path.resolve(__dirname, "../src"), // 也可以用包含

use: [

{

loader: "thread-loader", // 开启多进程

options: {

workers: threads, // 数量

},

},

{

loader: "babel-loader",

options: {

cacheDirectory: true, // 开启babel编译缓存

},

},

],

options: {

cacheDirectory: true, // 开启babel编译缓存

cacheCompression: false, // 缓存文件不要压缩

},

},

]

}

]

},

plugins: [

new ESLintWebpackPlugin({

// 指定检查文件的根目录

context: path.resolve(__dirname, "../src"),

exclude: "node_modules", // 默认值

cache: true, // 开启缓存

// 缓存目录

cacheLocation: path.resolve(

__dirname,

"../node_modules/.cache/.eslintcache"

),

threads, // 开启多进程

}),

new HtmlWebpackPlugin({

// 以 public/index.html 为模板创建文件

// 新的html文件有两个特点:1. 内容和源文件一致 2. 自动引入打包生成的js等资源

template: path.resolve(__dirname, "../public/index.html"),

}),

// 提取css成单独文件

new MiniCssExtractPlugin({

// 定义输出文件名和目录

filename: "static/css/index.css",

}),

// new CssMinimizerPlugin()

],

// 推荐将压缩操作放在这里

optimization: {

minimize: true,

minimizer: [

// css压缩也可以写到optimization.minimizer里面,效果一样的

new CssMinimizerPlugin(),

// 当生产模式会默认开启TerserPlugin,但是我们需要进行其他配置,就要重新写了

new TerserPlugin({

parallel: threads // 开启多进程

})

],

},

// 开发服务器

devServer: {

host: "localhost", // 启动服务器域名

port: "3000", // 启动服务器端口号

open: true, // 是否自动打开浏览器

hot: true, // 开启HMR功能(只能用于开发环境,生产环境不需要了)

},

mode: 'development'

}

减少代码体积 Tree Shaking

介绍

开发时我们定义了一些工具函数库,或者引用第三方工具函数库或组件库。

如果没有特殊处理的话我们打包时会引入整个库,但是实际上可能我们可能只用上极小部分的功能。

这样将整个库都打包进来,体积就太大了。

Tree Shaking 是一个术语,通常用于描述移除 JavaScript 中的没有使用上的代码。

注意:它依赖 ES Module

使用

Webpack 已经默认开启了这个功能,无需其他配置。

减少babel文件生成的体积

介绍

Babel 为编译的每个文件都插入了辅助代码,使代码体积过大!

Babel 对一些公共方法使用了非常小的辅助代码,比如 _extend。默认情况下会被添加到每一个需要它的文件中。

你可以将这些辅助代码作为一个独立模块,来避免重复引入。

@babel/plugin-transform-runtime: 禁用了 Babel 自动对每个文件的 runtime 注入,而是引入

@babel/plugin-transform-runtime 并且使所有辅助代码从这里引用。

使用

下载

npm i @babel/plugin-transform-runtime -D

配置 我们的生产模式和开发模式都需要配置,这里笔者展示开发模式的配置

// path 为 Node.js的核心模块,专门用来处理文件路径

const path = require('path')

const os = require("os");

const ESLintWebpackPlugin = require("eslint-webpack-plugin");

const HtmlWebpackPlugin = require("html-webpack-plugin");

const MiniCssExtractPlugin = require("mini-css-extract-plugin");

const CssMinimizerPlugin = require("css-minimizer-webpack-plugin");

const TerserPlugin = require("terser-webpack-plugin");

// cpu核数

const threads = os.cpus().length;

// 获取处理样式的Loaders

const getStyleLoaders = (preProcessor) => {

return [

MiniCssExtractPlugin.loader,

"css-loader",

{

loader: "postcss-loader",

options: {

postcssOptions: {

plugins: [

"postcss-preset-env", // 能解决大多数样式兼容性问题

],

},

},

},

preProcessor,

].filter(Boolean);

};

module.exports = {

// 入口

entry: {

// 需要一个相对路径

index: './src/index.js'

},

// 输出

output: {

path: undefined, // 开发模式没有输出,不需要指定输出目录

filename: "static/js/main.js", // 将 js 文件输出到 static/js 目录中

// clean: true, // 开发模式没有输出,不需要清空输出结果

},

// Performance 这些选项可以控制 webpack 如何通知「资源(asset)和入口起点超过指定文件限制」。

performance: {

hints: false

},

devtool: "cheap-module-source-map",

// 解析器

module: {

rules: [

{

// 每个文件只能被其中的一个loader处理

oneOf: [

{

test: /\.css$/,

// loader的执行顺序是从右往左的,所以这里先写style-loader,再写css-loader

// 如果只使用一个loader的话,可以使用loader属性代替use,如下

// loader:"style-loader"

use: getStyleLoaders(),

},

{

// 正则匹配所有已.less文件结尾的文件

test: /\.less$/,

// loader的执行顺序是从右往左的,所以这里先写style-loader,再写css-loader

// 如果只使用一个loader的话,可以使用loader属性代替use,如下

// loader:"style-loader"

use: getStyleLoaders("less-loader"),

},

{

test: /\.s[ac]ss$/,

use: getStyleLoaders("sass-loader"),

},

{

test: /\.styl$/,

use: getStyleLoaders("stylus-loader"),

},

{

test: /\.(png|jpe?g|gif|webp)$/,

type: "asset",

// 类似于 module.generator,你可以用 module.parser 在一个地方配置所有解析器的选项。

parser: {

// 如果一个模块源码大小小于 maxSize,那么模块会被作为一个 Base64 编码的字符串注入到包中, 否则模块文件会被生成到输出的目标目录中。

dataUrlCondition: {

maxSize: 10 * 1024 // 小于10kb的图片会被base64处理

}

},

// 可以使用 module.generator 在一个地方配置所有生成器的选项

generator: {

// 将图片文件输出到 static/imgs 目录中

// 将图片文件命名 [hash:8][ext][query]

// [hash:8]: hash值取8位

// [ext]: 使用之前的文件扩展名

// [query]: 添加之前的query参数

filename: "static/imgs/[hash:8][ext][query]",

},

},

{

test: /\.(ttf|woff2?|svg)$/,

// 发送一个单独的文件并导出 URL

type: "asset/resource",

generator: {

filename: "static/media/[hash:8][ext][query]",

},

},

{

test: /\.js$/,

// exclude: /node_modules/, // 排除node_modules代码不编译

include: path.resolve(__dirname, "../src"), // 也可以用包含

use: [

{

loader: "thread-loader", // 开启多进程

options: {

workers: threads, // 数量

},

},

{

loader: "babel-loader",

options: {

cacheDirectory: true, // 开启babel编译缓存

cacheCompression: false, // 缓存文件不要压缩

plugins: ["@babel/plugin-transform-runtime"], // 减少代码体积

},

},

],

},

]

}

]

},

plugins: [

new ESLintWebpackPlugin({

// 指定检查文件的根目录

context: path.resolve(__dirname, "../src"),

exclude: "node_modules", // 默认值

cache: true, // 开启缓存

// 缓存目录

cacheLocation: path.resolve(

__dirname,

"../node_modules/.cache/.eslintcache"

),

threads, // 开启多进程

}),

new HtmlWebpackPlugin({

// 以 public/index.html 为模板创建文件

// 新的html文件有两个特点:1. 内容和源文件一致 2. 自动引入打包生成的js等资源

template: path.resolve(__dirname, "../public/index.html"),

}),

// 提取css成单独文件

new MiniCssExtractPlugin({

// 定义输出文件名和目录

filename: "static/css/index.css",

}),

// new CssMinimizerPlugin()

],

// 推荐将压缩操作放在这里

optimization: {

minimize: true,

minimizer: [

// css压缩也可以写到optimization.minimizer里面,效果一样的

new CssMinimizerPlugin(),

// 当生产模式会默认开启TerserPlugin,但是我们需要进行其他配置,就要重新写了

new TerserPlugin({

parallel: threads // 开启多进程

})

],

},

// 开发服务器

devServer: {

host: "localhost", // 启动服务器域名

port: "3000", // 启动服务器端口号

open: true, // 是否自动打开浏览器

hot: true, // 开启HMR功能(只能用于开发环境,生产环境不需要了)

},

mode: 'development'

}

Image Minimizer 图片压缩

介绍

开发如果项目中引用了较多图片,那么图片体积会比较大,将来请求速度比较慢。

我们可以对图片进行压缩,减少图片体积。

注意:如果项目中图片都是在线链接,那么就不需要了。本地项目静态图片才需要进行压缩。

image-minimizer-webpack-plugin: 用来压缩图片的插件

使用

下载

npm i image-minimizer-webpack-plugin imagemin -D

还有剩下包需要下载,有两种模式:(如果npm下载不下来,使用cnpm下载)

无损压缩

npm install imagemin-gifsicle imagemin-jpegtran imagemin-optipng imagemin-svgo -D

有损压缩

npm install imagemin-gifsicle imagemin-mozjpeg imagemin-pngquant imagemin-svgo -D

配置 以无损压缩为例子,(生产模式与开发模式都需要配置)

// path 为 Node.js的核心模块,专门用来处理文件路径

const path = require('path')

const os = require("os");

const ESLintWebpackPlugin = require("eslint-webpack-plugin");

const HtmlWebpackPlugin = require("html-webpack-plugin");

const MiniCssExtractPlugin = require("mini-css-extract-plugin");

const CssMinimizerPlugin = require("css-minimizer-webpack-plugin");

const TerserPlugin = require("terser-webpack-plugin");

const ImageMinimizerPlugin = require("image-minimizer-webpack-plugin");

// cpu核数

const threads = os.cpus().length;

// 获取处理样式的Loaders

const getStyleLoaders = (preProcessor) => {

return [

MiniCssExtractPlugin.loader,

"css-loader",

{

loader: "postcss-loader",

options: {

postcssOptions: {

plugins: [

"postcss-preset-env", // 能解决大多数样式兼容性问题

],

},

},

},

preProcessor,

].filter(Boolean);

};

module.exports = {

// 入口

entry: {

// 需要一个相对路径

index: './src/index.js'

},

// 输出

output: {

path: undefined, // 开发模式没有输出,不需要指定输出目录

filename: "static/js/main.js", // 将 js 文件输出到 static/js 目录中

// clean: true, // 开发模式没有输出,不需要清空输出结果

},

// Performance 这些选项可以控制 webpack 如何通知「资源(asset)和入口起点超过指定文件限制」。

performance: {

hints: false

},

devtool: "cheap-module-source-map",

// 解析器

module: {

rules: [

{

// 每个文件只能被其中的一个loader处理

oneOf: [

{

test: /\.css$/,

// loader的执行顺序是从右往左的,所以这里先写style-loader,再写css-loader

// 如果只使用一个loader的话,可以使用loader属性代替use,如下

// loader:"style-loader"

use: getStyleLoaders(),

},

{

// 正则匹配所有已.less文件结尾的文件

test: /\.less$/,

// loader的执行顺序是从右往左的,所以这里先写style-loader,再写css-loader

// 如果只使用一个loader的话,可以使用loader属性代替use,如下

// loader:"style-loader"

use: getStyleLoaders("less-loader"),

},

{

test: /\.s[ac]ss$/,

use: getStyleLoaders("sass-loader"),

},

{

test: /\.styl$/,

use: getStyleLoaders("stylus-loader"),

},

{

test: /\.(png|jpe?g|gif|webp)$/,

type: "asset",

// 类似于 module.generator,你可以用 module.parser 在一个地方配置所有解析器的选项。

parser: {

// 如果一个模块源码大小小于 maxSize,那么模块会被作为一个 Base64 编码的字符串注入到包中, 否则模块文件会被生成到输出的目标目录中。

dataUrlCondition: {

maxSize: 10 * 1024 // 小于10kb的图片会被base64处理

}

},

// 可以使用 module.generator 在一个地方配置所有生成器的选项

generator: {

// 将图片文件输出到 static/imgs 目录中

// 将图片文件命名 [hash:8][ext][query]

// [hash:8]: hash值取8位

// [ext]: 使用之前的文件扩展名

// [query]: 添加之前的query参数

filename: "static/imgs/[hash:8][ext][query]",

},

},

{

test: /\.(ttf|woff2?|svg)$/,

// 发送一个单独的文件并导出 URL

type: "asset/resource",

generator: {

filename: "static/media/[hash:8][ext][query]",

},

},

{

test: /\.js$/,

// exclude: /node_modules/, // 排除node_modules代码不编译

include: path.resolve(__dirname, "../src"), // 也可以用包含

use: [

{

loader: "thread-loader", // 开启多进程

options: {

workers: threads, // 数量

},

},

{

loader: "babel-loader",

options: {

cacheDirectory: true, // 开启babel编译缓存

cacheCompression: false, // 缓存文件不要压缩

plugins: ["@babel/plugin-transform-runtime"], // 减少代码体积

},

},

],

},

]

}

]

},

plugins: [

new ESLintWebpackPlugin({

// 指定检查文件的根目录

context: path.resolve(__dirname, "../src"),

exclude: "node_modules", // 默认值

cache: true, // 开启缓存

// 缓存目录

cacheLocation: path.resolve(

__dirname,

"../node_modules/.cache/.eslintcache"

),

threads, // 开启多进程

}),

new HtmlWebpackPlugin({

// 以 public/index.html 为模板创建文件

// 新的html文件有两个特点:1. 内容和源文件一致 2. 自动引入打包生成的js等资源

template: path.resolve(__dirname, "../public/index.html"),

}),

// 提取css成单独文件

new MiniCssExtractPlugin({

// 定义输出文件名和目录

filename: "static/css/index.css",

}),

// new CssMinimizerPlugin()

],

// 推荐将压缩操作放在这里

optimization: {

minimize: true,

minimizer: [

// css压缩也可以写到optimization.minimizer里面,效果一样的

new CssMinimizerPlugin(),

// 当生产模式会默认开启TerserPlugin,但是我们需要进行其他配置,就要重新写了

new TerserPlugin({

parallel: threads // 开启多进程

}),

// 压缩图片

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",

},

},

],

},

],

],

},

},

}),

],

},

// 开发服务器

devServer: {

host: "localhost", // 启动服务器域名

port: "3000", // 启动服务器端口号

open: true, // 是否自动打开浏览器

hot: true, // 开启HMR功能(只能用于开发环境,生产环境不需要了)

},

mode: 'development'

}

优化代码运行性能

Code Split

介绍

打包代码时会将所有 js 文件打包到一个文件中,体积太大了。我们如果只要渲染首页,就应该只加载首页的 js 文件,其他文件不应该加载。

所以我们需要将打包生成的文件进行代码分割,生成多个 js 文件,渲染哪个页面就只加载某个 js 文件,这样加载的资源就少,速度就更快。

代码分割(Code Split)主要做了两件事:

分割文件:将打包生成的文件进行分割,生成多个 js 文件。

按需加载:需要哪个文件就加载哪个文件。

使用

单页面配置

如果我们使用了动态导入的语法,那么在eslint里面需要支持一下 下载

npm i eslint-plugin-import -D

配置

// .eslintrc.js

module.exports = {

// 继承 Eslint 规则

extends: ["eslint:recommended"],

env: {

node: true, // 启用node中全局变量

browser: true, // 启用浏览器中全局变量

},

plugins: ["import"], // 解决动态导入import语法报错问题 --> 实际使用eslint-plugin-import的规则解决的

parserOptions: {

ecmaVersion: 6,

sourceType: "module",

},

rules: {

"no-var": 2, // 不能使用 var 定义变量

},

};

Code Split统一命名

我们的配置中对于输出文件的配置有很多,比如入口文件,图片文件,字体图标文件、css文件、动态引入文件等等,我们希望统一写在一个地方 配置 这里以生产模式为例子

// path 为 Node.js的核心模块,专门用来处理文件路径

const path = require('path')

const os = require("os");

const ESLintWebpackPlugin = require("eslint-webpack-plugin");

const HtmlWebpackPlugin = require("html-webpack-plugin");

const MiniCssExtractPlugin = require("mini-css-extract-plugin");

const CssMinimizerPlugin = require("css-minimizer-webpack-plugin");

const TerserPlugin = require("terser-webpack-plugin");

const ImageMinimizerPlugin = require("image-minimizer-webpack-plugin");

// cpu核数

const threads = os.cpus().length;

// 获取处理样式的Loaders

const getStyleLoaders = (preProcessor) => {

return [

MiniCssExtractPlugin.loader,

"css-loader",

{

loader: "postcss-loader",

options: {

postcssOptions: {

plugins: [

"postcss-preset-env", // 能解决大多数样式兼容性问题

],

},

},

},

preProcessor,

].filter(Boolean);

};

module.exports = {

// 入口

entry: {

// 需要一个相对路径

index: './src/index.js'

},

// 输出

output: {

// 需要一个绝对路径

path: path.resolve(__dirname, '../dist'),

clean: true,

// chunk的name是啥呢? 比如: entry中xxx: "./src/xxx.js", name就是xxx。注意是前面的xxx,和文件名无关。

// 为什么需要这样命名呢?如果还是之前写法main.js,那么打包生成两个js文件都会叫做main.js会发生覆盖。(实际上会直接报错的)

filename: "static/js/[name].js", // 入口文件打包输出资源命名方式

chunkFilename: "static/js/[name].chunk.js", // 动态导入输出资源命名方式

assetModuleFilename: "static/media/[name].[hash][ext]", // 图片、字体等资源命名方式(注意用hash)

},

// Performance 这些选项可以控制 webpack 如何通知「资源(asset)和入口起点超过指定文件限制」。

performance: {

hints: false

},

devtool: "source-map",

// 解析器

module: {

rules: [

{

oneOf: [

{

test: /\.css$/,

// loader的执行顺序是从右往左的,所以这里先写style-loader,再写css-loader

// 如果只使用一个loader的话,可以使用loader属性代替use,如下

// loader:"style-loader"

use: getStyleLoaders(),

},

{

// 正则匹配所有已.less文件结尾的文件

test: /\.less$/,

// loader的执行顺序是从右往左的,所以这里先写style-loader,再写css-loader

// 如果只使用一个loader的话,可以使用loader属性代替use,如下

// loader:"style-loader"

use: getStyleLoaders("less-loader"),

},

{

test: /\.s[ac]ss$/,

use: getStyleLoaders("sass-loader"),

},

{

test: /\.styl$/,

use: getStyleLoaders("stylus-loader"),

},

{

test: /\.(png|jpe?g|gif|webp)$/,

type: "asset",

// 类似于 module.generator,你可以用 module.parser 在一个地方配置所有解析器的选项。

parser: {

// 如果一个模块源码大小小于 maxSize,那么模块会被作为一个 Base64 编码的字符串注入到包中, 否则模块文件会被生成到输出的目标目录中。

dataUrlCondition: {

maxSize: 10 * 1024 // 小于10kb的图片会被base64处理

}

},

// 可以使用 module.generator 在一个地方配置所有生成器的选项

// generator: {

// // 将图片文件输出到 static/imgs 目录中

// // 将图片文件命名 [hash:8][ext][query]

// // [hash:8]: hash值取8位

// // [ext]: 使用之前的文件扩展名

// // [query]: 添加之前的query参数

// filename: "static/imgs/[hash:8][ext][query]",

// },

},

{

test: /\.(ttf|woff2?|svg)$/,

// 发送一个单独的文件并导出 URL

type: "asset/resource",

// generator: {

// filename: "static/media/[hash:8][ext][query]",

// },

},

{

test: /\.js$/,

// exclude: /node_modules/, // 排除node_modules代码不编译

include: path.resolve(__dirname, "../src"), // 也可以用包含

use: [

{

loader: "thread-loader", // 开启多进程

options: {

workers: threads, // 数量

},

},

{

loader: "babel-loader",

options: {

cacheDirectory: true, // 开启babel编译缓存

cacheCompression: false, // 缓存文件不要压缩

plugins: ["@babel/plugin-transform-runtime"], // 减少代码体积

},

},

],

},

]

}

]

},

plugins: [

new ESLintWebpackPlugin({

// 指定检查文件的根目录

context: path.resolve(__dirname, "../src"),

exclude: "node_modules", // 默认值

cache: true, // 开启缓存

// 缓存目录

cacheLocation: path.resolve(

__dirname,

"../node_modules/.cache/.eslintcache"

),

threads, // 开启多进程

}),

new HtmlWebpackPlugin({

// 以 public/index.html 为模板创建文件

// 新的html文件有两个特点:1. 内容和源文件一致 2. 自动引入打包生成的js等资源

template: path.resolve(__dirname, "../public/index.html"),

}),

// 提取css成单独文件

new MiniCssExtractPlugin({

// 定义输出文件名和目录

filename: "static/css/[name].css",

chunkFilename: "static/css/[name].chunk.css",

}),

// new CssMinimizerPlugin()

],

// 推荐将压缩操作放在这里

optimization: {

minimize: true,

minimizer: [

// css压缩也可以写到optimization.minimizer里面,效果一样的

new CssMinimizerPlugin(),

// 当生产模式会默认开启TerserPlugin,但是我们需要进行其他配置,就要重新写了

new TerserPlugin({

parallel: threads // 开启多进程

}),

// 压缩图片

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",

},

},

],

},

],

],

},

},

}),

],

splitChunks: {

chunks: "all"

}

},

// 开发服务器

// devServer: {

// host: "localhost", // 启动服务器域名

// port: "3000", // 启动服务器端口号

// open: true, // 是否自动打开浏览器

// },

mode: 'production'

}

Preload / Prefetch 预处理

介绍

我们前面已经做了代码分割,同时会使用 import 动态导入语法来进行代码按需加载(我们也叫懒加载,比如路由懒加载就是这样实现的)。

但是加载速度还不够好,比如:是用户点击按钮时才加载这个资源的,如果资源体积很大,那么用户会感觉到明显卡顿效果。

我们想在浏览器空闲时间,加载后续需要使用的资源。我们就需要用上 Preload 或 Prefetch 技术

Preload:告诉浏览器立即加载资源。

Prefetch:告诉浏览器在空闲时才开始加载资源。(开发中这个用的最多)

它们共同点:

都只会加载资源,并不执行。

都有缓存。

它们区别:

Preload加载优先级高,Prefetch加载优先级低。

Preload只能加载当前页面需要使用的资源,Prefetch可以加载当前页面资源,也可以加载下一个页面需要使用的资源。

总结:

当前页面优先级高的资源用 Preload 加载。

下一个页面需要使用的资源用 Prefetch 加载。

它们的问题:兼容性较差。

我们可以去 Can I Use 网站查询 API 的兼容性问题。

Preload 相对于 Prefetch 兼容性好一点。

使用

下载

npm i @vue/preload-webpack-plugin -D

配置 以生产模式为例子

// path 为 Node.js的核心模块,专门用来处理文件路径

const path = require('path')

const os = require("os");

const ESLintWebpackPlugin = require("eslint-webpack-plugin");

const HtmlWebpackPlugin = require("html-webpack-plugin");

const MiniCssExtractPlugin = require("mini-css-extract-plugin");

const CssMinimizerPlugin = require("css-minimizer-webpack-plugin");

const TerserPlugin = require("terser-webpack-plugin");

const ImageMinimizerPlugin = require("image-minimizer-webpack-plugin");

const PreloadWebpackPlugin = require("@vue/preload-webpack-plugin");

// cpu核数

const threads = os.cpus().length;

// 获取处理样式的Loaders

const getStyleLoaders = (preProcessor) => {

return [

MiniCssExtractPlugin.loader,

"css-loader",

{

loader: "postcss-loader",

options: {

postcssOptions: {

plugins: [

"postcss-preset-env", // 能解决大多数样式兼容性问题

],

},

},

},

preProcessor,

].filter(Boolean);

};

module.exports = {

// 入口

entry: {

// 需要一个相对路径

index: './src/index.js'

},

// 输出

output: {

// 需要一个绝对路径

path: path.resolve(__dirname, '../dist'),

clean: true,

// chunk的name是啥呢? 比如: entry中xxx: "./src/xxx.js", name就是xxx。注意是前面的xxx,和文件名无关。

// 为什么需要这样命名呢?如果还是之前写法main.js,那么打包生成两个js文件都会叫做main.js会发生覆盖。(实际上会直接报错的)

filename: "static/js/[name].js", // 入口文件打包输出资源命名方式

chunkFilename: "static/js/[name].chunk.js", // 动态导入输出资源命名方式

assetModuleFilename: "static/media/[name].[hash][ext]", // 图片、字体等资源命名方式(注意用hash)

},

// Performance 这些选项可以控制 webpack 如何通知「资源(asset)和入口起点超过指定文件限制」。

performance: {

hints: false

},

devtool: "source-map",

// 解析器

module: {

rules: [

{

oneOf: [

{

test: /\.css$/,

// loader的执行顺序是从右往左的,所以这里先写style-loader,再写css-loader

// 如果只使用一个loader的话,可以使用loader属性代替use,如下

// loader:"style-loader"

use: getStyleLoaders(),

},

{

// 正则匹配所有已.less文件结尾的文件

test: /\.less$/,

// loader的执行顺序是从右往左的,所以这里先写style-loader,再写css-loader

// 如果只使用一个loader的话,可以使用loader属性代替use,如下

// loader:"style-loader"

use: getStyleLoaders("less-loader"),

},

{

test: /\.s[ac]ss$/,

use: getStyleLoaders("sass-loader"),

},

{

test: /\.styl$/,

use: getStyleLoaders("stylus-loader"),

},

{

test: /\.(png|jpe?g|gif|webp)$/,

type: "asset",

// 类似于 module.generator,你可以用 module.parser 在一个地方配置所有解析器的选项。

parser: {

// 如果一个模块源码大小小于 maxSize,那么模块会被作为一个 Base64 编码的字符串注入到包中, 否则模块文件会被生成到输出的目标目录中。

dataUrlCondition: {

maxSize: 10 * 1024 // 小于10kb的图片会被base64处理

}

},

// 可以使用 module.generator 在一个地方配置所有生成器的选项

// generator: {

// // 将图片文件输出到 static/imgs 目录中

// // 将图片文件命名 [hash:8][ext][query]

// // [hash:8]: hash值取8位

// // [ext]: 使用之前的文件扩展名

// // [query]: 添加之前的query参数

// filename: "static/imgs/[hash:8][ext][query]",

// },

},

{

test: /\.(ttf|woff2?|svg)$/,

// 发送一个单独的文件并导出 URL

type: "asset/resource",

// generator: {

// filename: "static/media/[hash:8][ext][query]",

// },

},

{

test: /\.js$/,

// exclude: /node_modules/, // 排除node_modules代码不编译

include: path.resolve(__dirname, "../src"), // 也可以用包含

use: [

{

loader: "thread-loader", // 开启多进程

options: {

workers: threads, // 数量

},

},

{

loader: "babel-loader",

options: {

cacheDirectory: true, // 开启babel编译缓存

cacheCompression: false, // 缓存文件不要压缩

plugins: ["@babel/plugin-transform-runtime"], // 减少代码体积

},

},

],

},

]

}

]

},

plugins: [

new ESLintWebpackPlugin({

// 指定检查文件的根目录

context: path.resolve(__dirname, "../src"),

exclude: "node_modules", // 默认值

cache: true, // 开启缓存

// 缓存目录

cacheLocation: path.resolve(

__dirname,

"../node_modules/.cache/.eslintcache"

),

threads, // 开启多进程

}),

new HtmlWebpackPlugin({

// 以 public/index.html 为模板创建文件

// 新的html文件有两个特点:1. 内容和源文件一致 2. 自动引入打包生成的js等资源

template: path.resolve(__dirname, "../public/index.html"),

}),

// 提取css成单独文件

new MiniCssExtractPlugin({

// 定义输出文件名和目录

filename: "static/css/[name].css",

chunkFilename: "static/css/[name].chunk.css",

}),

// new CssMinimizerPlugin()

new PreloadWebpackPlugin({

rel: "preload", // preload兼容性更好

as: "script",

// rel: 'prefetch' // prefetch兼容性更差

}),

],

// 推荐将压缩操作放在这里

optimization: {

minimize: true,

minimizer: [

// css压缩也可以写到optimization.minimizer里面,效果一样的

new CssMinimizerPlugin(),

// 当生产模式会默认开启TerserPlugin,但是我们需要进行其他配置,就要重新写了

new TerserPlugin({

parallel: threads // 开启多进程

}),

// 压缩图片

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",

},

},

],

},

],

],

},

},

}),

],

splitChunks: {

chunks: "all"

}

},

// 开发服务器

// devServer: {

// host: "localhost", // 启动服务器域名

// port: "3000", // 启动服务器端口号

// open: true, // 是否自动打开浏览器

// },

mode: 'production'

}

Network Cache

介绍

将来开发时我们对静态资源会使用缓存来优化,这样浏览器第二次请求资源就能读取缓存了,速度很快。

但是这样的话就会有一个问题, 因为前后输出的文件名是一样的,都叫 main.js,一旦将来发布新版本,因为文件名没有变化导致浏览器会直接读取缓存,不会加载新资源,项目也就没法更新了。

所以我们从文件名入手,确保更新前后文件名不一样,这样就可以做缓存了。

它们都会生成一个唯一的 hash 值。

fullhash(webpack4 是 hash)

每次修改任何一个文件,所有文件名的 hash 至都将改变。所以一旦修改了任何一个文件,整个项目的文件缓存都将失效。

chunkhash

根据不同的入口文件(Entry)进行依赖文件解析、构建对应的 chunk,生成对应的哈希值。我们 js 和 css 是同一个引入,会共享一个 hash 值。

contenthash

根据文件内容生成 hash 值,只有文件内容变化了,hash 值才会变化。所有文件 hash 值是独享且不同的。

存在问题与解决方案

问题: 当我们修改 math.js 文件再重新打包的时候,因为 contenthash 原因,math.js 文件 hash 值发生了变化(这是正常的)。

但是 main.js 文件的 hash 值也发生了变化,这会导致 main.js 的缓存失效。明明我们只修改 math.js, 为什么 main.js 也会变身变化呢?

原因:

更新前:math.xxx.js, main.js 引用的 math.xxx.js 更新后:math.yyy.js, main.js 引用的 math.yyy.js, 文件名发生了变化,间接导致 main.js 也发生了变化 解决:

将 hash 值单独保管在一个 runtime 文件中。

我们最终输出三个文件:main、math、runtime。当 math 文件发送变化,变化的是 math 和 runtime 文件,main 不变。

runtime 文件只保存文件的 hash 值和它们与文件关系,整个文件体积就比较小,所以变化重新请求的代价也小。

使用

// 提取runtime文件

runtimeChunk: {

name: (entrypoint) => `runtime~${entrypoint.name}`, // runtime文件命名规则

},

以生产模式为例子

// path 为 Node.js的核心模块,专门用来处理文件路径

const path = require('path')

const os = require("os");

const ESLintWebpackPlugin = require("eslint-webpack-plugin");

const HtmlWebpackPlugin = require("html-webpack-plugin");

const MiniCssExtractPlugin = require("mini-css-extract-plugin");

const CssMinimizerPlugin = require("css-minimizer-webpack-plugin");

const TerserPlugin = require("terser-webpack-plugin");

const ImageMinimizerPlugin = require("image-minimizer-webpack-plugin");

const PreloadWebpackPlugin = require("@vue/preload-webpack-plugin");

// cpu核数

const threads = os.cpus().length;

// 获取处理样式的Loaders

const getStyleLoaders = (preProcessor) => {

return [

MiniCssExtractPlugin.loader,

"css-loader",

{

loader: "postcss-loader",

options: {

postcssOptions: {

plugins: [

"postcss-preset-env", // 能解决大多数样式兼容性问题

],

},

},

},

preProcessor,

].filter(Boolean);

};

module.exports = {

// 入口

entry: {

// 需要一个相对路径

index: './src/index.js'

},

// 输出

output: {

// 需要一个绝对路径

path: path.resolve(__dirname, '../dist'),

clean: true,

// chunk的name是啥呢? 比如: entry中xxx: "./src/xxx.js", name就是xxx。注意是前面的xxx,和文件名无关。

// 为什么需要这样命名呢?如果还是之前写法main.js,那么打包生成两个js文件都会叫做main.js会发生覆盖。(实际上会直接报错的)

// [contenthash:8]使用contenthash,取8位长度

filename: "static/js/[name].[contenthash:8].js", // 入口文件打包输出资源命名方式

chunkFilename: "static/js/[name].[contenthash:8].chunk.js", // 动态导入输出资源命名方式

assetModuleFilename: "static/media/[name].[hash][ext]", // 图片、字体等资源命名方式(注意用hash)

},

// Performance 这些选项可以控制 webpack 如何通知「资源(asset)和入口起点超过指定文件限制」。

performance: {

hints: false

},

devtool: "source-map",

// 解析器

module: {

rules: [

{

oneOf: [

{

test: /\.css$/,

// loader的执行顺序是从右往左的,所以这里先写style-loader,再写css-loader

// 如果只使用一个loader的话,可以使用loader属性代替use,如下

// loader:"style-loader"

use: getStyleLoaders(),

},

{

// 正则匹配所有已.less文件结尾的文件

test: /\.less$/,

// loader的执行顺序是从右往左的,所以这里先写style-loader,再写css-loader

// 如果只使用一个loader的话,可以使用loader属性代替use,如下

// loader:"style-loader"

use: getStyleLoaders("less-loader"),

},

{

test: /\.s[ac]ss$/,

use: getStyleLoaders("sass-loader"),

},

{

test: /\.styl$/,

use: getStyleLoaders("stylus-loader"),

},

{

test: /\.(png|jpe?g|gif|webp)$/,

type: "asset",

// 类似于 module.generator,你可以用 module.parser 在一个地方配置所有解析器的选项。

parser: {

// 如果一个模块源码大小小于 maxSize,那么模块会被作为一个 Base64 编码的字符串注入到包中, 否则模块文件会被生成到输出的目标目录中。

dataUrlCondition: {

maxSize: 10 * 1024 // 小于10kb的图片会被base64处理

}

},

// 可以使用 module.generator 在一个地方配置所有生成器的选项

// generator: {

// // 将图片文件输出到 static/imgs 目录中

// // 将图片文件命名 [hash:8][ext][query]

// // [hash:8]: hash值取8位

// // [ext]: 使用之前的文件扩展名

// // [query]: 添加之前的query参数

// filename: "static/imgs/[hash:8][ext][query]",

// },

},

{

test: /\.(ttf|woff2?|svg)$/,

// 发送一个单独的文件并导出 URL

type: "asset/resource",

// generator: {

// filename: "static/media/[hash:8][ext][query]",

// },

},

{

test: /\.js$/,

// exclude: /node_modules/, // 排除node_modules代码不编译

include: path.resolve(__dirname, "../src"), // 也可以用包含

use: [

{

loader: "thread-loader", // 开启多进程

options: {

workers: threads, // 数量

},

},

{

loader: "babel-loader",

options: {

cacheDirectory: true, // 开启babel编译缓存

cacheCompression: false, // 缓存文件不要压缩

plugins: ["@babel/plugin-transform-runtime"], // 减少代码体积

},

},

],

},

]

}

]

},

plugins: [

new ESLintWebpackPlugin({

// 指定检查文件的根目录

context: path.resolve(__dirname, "../src"),

exclude: "node_modules", // 默认值

cache: true, // 开启缓存

// 缓存目录

cacheLocation: path.resolve(

__dirname,

"../node_modules/.cache/.eslintcache"

),

threads, // 开启多进程

}),

new HtmlWebpackPlugin({

// 以 public/index.html 为模板创建文件

// 新的html文件有两个特点:1. 内容和源文件一致 2. 自动引入打包生成的js等资源

template: path.resolve(__dirname, "../public/index.html"),

}),

// 提取css成单独文件

new MiniCssExtractPlugin({

// 定义输出文件名和目录

filename: "static/css/[name].[contenthash:8].css",

chunkFilename: "static/css/[name].[contenthash:8].chunk.css",

}),

// new CssMinimizerPlugin()

new PreloadWebpackPlugin({

rel: "preload", // preload兼容性更好

as: "script",

// rel: 'prefetch' // prefetch兼容性更差

}),

],

// 推荐将压缩操作放在这里

optimization: {

minimize: true,

minimizer: [

// css压缩也可以写到optimization.minimizer里面,效果一样的

new CssMinimizerPlugin(),

// 当生产模式会默认开启TerserPlugin,但是我们需要进行其他配置,就要重新写了

new TerserPlugin({

parallel: threads // 开启多进程

}),

// 压缩图片

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",

},

},

],

},

],

],

},

},

}),

],

splitChunks: {

chunks: "all"

},

// 提取runtime文件

runtimeChunk: {

name: (entrypoint) => `runtime~${entrypoint.name}`, // runtime文件命名规则

},

},

// 开发服务器

// devServer: {

// host: "localhost", // 启动服务器域名

// port: "3000", // 启动服务器端口号

// open: true, // 是否自动打开浏览器

// },

mode: 'production'

}

Core-js彻底解决js的兼容问题

介绍

过去我们使用 babel 对 js 代码进行了兼容性处理,其中使用@babel/preset-env 智能预设来处理兼容性问题。

它能将 ES6 的一些语法进行编译转换,比如箭头函数、点点点运算符等。但是如果是 async 函数、promise 对象、数组的一些方法(includes)等,它没办法处理。

所以此时我们 js 代码仍然存在兼容性问题,一旦遇到低版本浏览器会直接报错。所以我们想要将 js 兼容性问题彻底解决

core-js 是专门用来做 ES6 以及以上 API 的 polyfill。

polyfill翻译过来叫做垫片/补丁。就是用社区上提供的一段代码,让我们在不兼容某些新特性的浏览器上,使用该新特性。

使用

下载

npm i core-js

三种使用方式

手动全部引入

import "core-js";

这样引入会将所有兼容性代码全部引入,体积太大了。我们只想引入 promise 的 polyfill。

手动按需引入

import "core-js/es/promise";

只引入打包 promise 的 polyfill,打包体积更小。但是将来如果还想使用其他语法,我需要手动引入库很麻烦。

自动按需引入 修改babel.config.js的智能预设,帮助我们进行core.js的按需加载

// babel.config.js

module.exports = {

// 智能预设:能够编译ES6语法

presets: [

[

"@babel/preset-env",

// 按需加载core-js的polyfill

{ useBuiltIns: "usage", corejs: { version: "3", proposals: true } },

],

],

};

此时就会自动根据我们代码中使用的语法,来按需加载相应的 polyfill 了。

PWA

介绍

开发 Web App 项目,项目一旦处于网络离线情况,就没法访问了。

我们希望给项目提供离线体验。

渐进式网络应用程序(progressive web application - PWA):

是一种可以提供类似于 native app(原生应用程序) 体验的 Web App 的技术。

其中最重要的是,在 离线(offline) 时应用程序能够继续运行功能。

内部通过 Service Workers 技术实现的。

使用

下载

npm i workbox-webpack-plugin -D

配置

// path 为 Node.js的核心模块,专门用来处理文件路径

const path = require('path')

const os = require("os");

const ESLintWebpackPlugin = require("eslint-webpack-plugin");

const HtmlWebpackPlugin = require("html-webpack-plugin");

const MiniCssExtractPlugin = require("mini-css-extract-plugin");

const CssMinimizerPlugin = require("css-minimizer-webpack-plugin");

const TerserPlugin = require("terser-webpack-plugin");

const ImageMinimizerPlugin = require("image-minimizer-webpack-plugin");

const PreloadWebpackPlugin = require("@vue/preload-webpack-plugin");

const WorkboxPlugin = require("workbox-webpack-plugin");

// cpu核数

const threads = os.cpus().length;

// 获取处理样式的Loaders

const getStyleLoaders = (preProcessor) => {

return [

MiniCssExtractPlugin.loader,

"css-loader",

{

loader: "postcss-loader",

options: {

postcssOptions: {

plugins: [

"postcss-preset-env", // 能解决大多数样式兼容性问题

],

},

},

},

preProcessor,

].filter(Boolean);

};

module.exports = {

// 入口

entry: {

// 需要一个相对路径

index: './src/index.js'

},

// 输出

output: {

// 需要一个绝对路径

path: path.resolve(__dirname, '../dist'),

clean: true,

// chunk的name是啥呢? 比如: entry中xxx: "./src/xxx.js", name就是xxx。注意是前面的xxx,和文件名无关。

// 为什么需要这样命名呢?如果还是之前写法main.js,那么打包生成两个js文件都会叫做main.js会发生覆盖。(实际上会直接报错的)

// [contenthash:8]使用contenthash,取8位长度

filename: "static/js/[name].[contenthash:8].js", // 入口文件打包输出资源命名方式

chunkFilename: "static/js/[name].[contenthash:8].chunk.js", // 动态导入输出资源命名方式

assetModuleFilename: "static/media/[name].[hash][ext]", // 图片、字体等资源命名方式(注意用hash)

},

// Performance 这些选项可以控制 webpack 如何通知「资源(asset)和入口起点超过指定文件限制」。

performance: {

hints: false

},

devtool: "source-map",

// 解析器

module: {

rules: [

{

oneOf: [

{

test: /\.css$/,

// loader的执行顺序是从右往左的,所以这里先写style-loader,再写css-loader

// 如果只使用一个loader的话,可以使用loader属性代替use,如下

// loader:"style-loader"

use: getStyleLoaders(),

},

{

// 正则匹配所有已.less文件结尾的文件

test: /\.less$/,

// loader的执行顺序是从右往左的,所以这里先写style-loader,再写css-loader

// 如果只使用一个loader的话,可以使用loader属性代替use,如下

// loader:"style-loader"

use: getStyleLoaders("less-loader"),

},

{

test: /\.s[ac]ss$/,

use: getStyleLoaders("sass-loader"),

},

{

test: /\.styl$/,

use: getStyleLoaders("stylus-loader"),

},

{

test: /\.(png|jpe?g|gif|webp)$/,

type: "asset",

// 类似于 module.generator,你可以用 module.parser 在一个地方配置所有解析器的选项。

parser: {

// 如果一个模块源码大小小于 maxSize,那么模块会被作为一个 Base64 编码的字符串注入到包中, 否则模块文件会被生成到输出的目标目录中。

dataUrlCondition: {

maxSize: 10 * 1024 // 小于10kb的图片会被base64处理

}

},

// 可以使用 module.generator 在一个地方配置所有生成器的选项

// generator: {

// // 将图片文件输出到 static/imgs 目录中

// // 将图片文件命名 [hash:8][ext][query]

// // [hash:8]: hash值取8位

// // [ext]: 使用之前的文件扩展名

// // [query]: 添加之前的query参数

// filename: "static/imgs/[hash:8][ext][query]",

// },

},

{

test: /\.(ttf|woff2?|svg)$/,

// 发送一个单独的文件并导出 URL

type: "asset/resource",

// generator: {

// filename: "static/media/[hash:8][ext][query]",

// },

},

{

test: /\.js$/,

// exclude: /node_modules/, // 排除node_modules代码不编译

include: path.resolve(__dirname, "../src"), // 也可以用包含

use: [

{

loader: "thread-loader", // 开启多进程

options: {

workers: threads, // 数量

},

},

{

loader: "babel-loader",

options: {

cacheDirectory: true, // 开启babel编译缓存

cacheCompression: false, // 缓存文件不要压缩

plugins: ["@babel/plugin-transform-runtime"], // 减少代码体积

},

},

],

},

]

}

]

},

plugins: [

new ESLintWebpackPlugin({

// 指定检查文件的根目录

context: path.resolve(__dirname, "../src"),

exclude: "node_modules", // 默认值

cache: true, // 开启缓存

// 缓存目录

cacheLocation: path.resolve(

__dirname,

"../node_modules/.cache/.eslintcache"

),

threads, // 开启多进程

}),

new HtmlWebpackPlugin({

// 以 public/index.html 为模板创建文件

// 新的html文件有两个特点:1. 内容和源文件一致 2. 自动引入打包生成的js等资源

template: path.resolve(__dirname, "../public/index.html"),

}),

// 提取css成单独文件

new MiniCssExtractPlugin({

// 定义输出文件名和目录

filename: "static/css/[name].[contenthash:8].css",

chunkFilename: "static/css/[name].[contenthash:8].chunk.css",

}),

// new CssMinimizerPlugin()

new PreloadWebpackPlugin({

rel: "preload", // preload兼容性更好

as: "script",

// rel: 'prefetch' // prefetch兼容性更差

}),

new WorkboxPlugin.GenerateSW({

// 这些选项帮助快速启用 ServiceWorkers

// 不允许遗留任何“旧的” ServiceWorkers

clientsClaim: true,

skipWaiting: true,

}),

],

// 推荐将压缩操作放在这里

optimization: {

minimize: true,

minimizer: [

// css压缩也可以写到optimization.minimizer里面,效果一样的

new CssMinimizerPlugin(),

// 当生产模式会默认开启TerserPlugin,但是我们需要进行其他配置,就要重新写了

new TerserPlugin({

parallel: threads // 开启多进程

}),

// 压缩图片

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",

},

},

],

},

],

],

},

},

}),

],

splitChunks: {

chunks: "all"

},

// 提取runtime文件

runtimeChunk: {

name: (entrypoint) => `runtime~${entrypoint.name}`, // runtime文件命名规则

},

},

// 开发服务器

// devServer: {

// host: "localhost", // 启动服务器域名

// port: "3000", // 启动服务器端口号

// open: true, // 是否自动打开浏览器

// },

mode: 'production'

}

问题与解决

此时如果直接通过 VSCode 访问打包后页面,在浏览器控制台会发现 SW registration failed。

因为我们打开的访问路径是:http://127.0.0.1:5500/dist/index.html。此时页面会去请求 service-worker.js 文件,请求路径是:http://127.0.0.1:5500/service-worker.js,这样找不到会 404。

实际 service-worker.js 文件路径是:http://127.0.0.1:5500/dist/service-worker.js。

解决

下载包 npm i serve -g

serve 也是用来启动开发服务器来部署代码查看效果的。

运行指令 serve dist

此时通过 serve 启动的服务器我们 service-worker 就能注册成功了。

高级总结

我们从 4 个角度对 webpack 和代码进行了优化:

提升开发体验

使用 Source Map 让开发或上线时代码报错能有更加准确的错误提示。提升 webpack 提升打包构建速度使用 HotModuleReplacement 让开发时只重新编译打包更新变化了的代码,不变的代码使用缓存,从而使更新速度更快。使用 OneOf 让资源文件一旦被某个 loader 处理了,就不会继续遍历了,打包速度更快。使用 Include/Exclude 排除或只检测某些文件,处理的文件更少,速度更快。使用 Cache 对 eslint 和 babel 处理的结果进行缓存,让第二次打包速度更快。使用 Thead 多进程处理 eslint 和 babel 任务,速度更快。(需要注意的是,进程启动通信都有开销的,要在比较多代码处理时使用才有效果)

减少代码体积

使用 Tree Shaking 剔除了没有使用的多余代码,让代码体积更小。使用 @babel/plugin-transform-runtime 插件对 babel 进行处理,让辅助代码从中引入,而不是每个文件都生成辅助代码,从而体积更小。使用 Image Minimizer 对项目中图片进行压缩,体积更小,请求速度更快。(需要注意的是,如果项目中图片都是在线链接,那么就不需要了。本地项目静态图片才需要进行压缩。)

优化代码运行性能

使用 Code Split 对代码进行分割成多个 js 文件,从而使单个文件体积更小,并行加载 js 速度更快。并通过 import 动态导入语法进行按需加载,从而达到需要使用时才加载该资源,不用时不加载资源。使用 Preload / Prefetch 对代码进行提前加载,等未来需要使用时就能直接使用,从而用户体验更好。使用 Network Cache 能对输出资源文件进行更好的命名,将来好做缓存,从而用户体验更好。使用 Core-js 对 js 进行兼容性处理,让我们代码能运行在低版本浏览器。使用 PWA 能让代码离线也能访问,从而提升用户体验。

如果你学习到这了,那么恭喜你,你已经可以搞定面试官了!!!


该文章在 2024/4/3 11:52:50 编辑过
关键字查询
相关文章
正在查询...
点晴ERP是一款针对中小制造业的专业生产管理软件系统,系统成熟度和易用性得到了国内大量中小企业的青睐。
点晴PMS码头管理系统主要针对港口码头集装箱与散货日常运作、调度、堆场、车队、财务费用、相关报表等业务管理,结合码头的业务特点,围绕调度、堆场作业而开发的。集技术的先进性、管理的有效性于一体,是物流码头及其他港口类企业的高效ERP管理信息系统。
点晴WMS仓储管理系统提供了货物产品管理,销售管理,采购管理,仓储管理,仓库管理,保质期管理,货位管理,库位管理,生产管理,WMS管理系统,标签打印,条形码,二维码管理,批号管理软件。
点晴免费OA是一款软件和通用服务都免费,不限功能、不限时间、不限用户的免费OA协同办公管理系统。
Copyright 2010-2024 ClickSun All Rights Reserved