快捷搜索: 长连接 前端 源码 pan

webpack5 打包工具 详解

1、webpack是什么

webpack 是一个前端资源加载/打包工具。它将根据模块的依赖关系进行静态分析,然后将这些模块按照指定的规则生成对应的静态资源。

从图中我们可以看出,Webpack 可以将多种静态资源 js、css、less 转换成一个静态文件,减少了页面的请求。

webpack中文文档:

2、webpack初始化配置

首先通过 npm init 命令对项目进行初始化。随后我们在项目当中下载webpack 使用命令:

npm install webpack webpack-cli -g
npm install webpack webpack-cli -D

在进行安装的时候出现了一些问题:

  1. 安装依赖失败:EPERM: operation not permitted, 这是因为在进行设置全局变量的时候可能使用的是管理员下的cmd,这里我们使用管理员打开cmd进行安装即可。
  2. Refusing to install package with name “webpack” under a package 这是因为在项目初始化的时候也是使用webpack作为项目名,导致了重名,我们只需要将pack.json当中的name进行修改掉即可。

在进行打包的时候,抛出了一个错误:webpack : 无法加载文件,是因为在此系统上禁止运行脚本。我们使用管理员权限打开我们的工具(Idea、webstrom、vscode、hbuilder)等等。

  1. 执行:get-ExecutionPolicy,显示Restricted,表示状态是禁止的
  2. 执行:set-ExecutionPolicy RemoteSigned
  3. 再执行get-ExecutionPolicy,就显示RemoteSigned

之后我们直接使用webpack进行打包测试:这里是使用webpack命令:我们的index.js文件就是入口文件,而后面指定的就是打包之后的路径地址。所以我们在src下面新建一个js文件,在js文件下面写入一些内容,之后进行打包:

webpack ./src/js/index.js ./build/ --node=development

进行打包之后我们可以在build下的main.js文件看到我们打包之后的内容:并且我们的webpack进行打包的时候我们可以对js、json文件进行打包处理,而对css/img无法编译通过。在这里我们给index.js文件进行使用js和json,之后进行打包,

/**
 * webpack 起点文件
 * */

/**
 * 运行指令
 * 开发环境
 * webpack ./src/index.js -o ./build --mode=development
 * 将 index.js 做为入口文件进行打包,打包到./build/main.js
 * */

import data from ./data.json
console.log(data)

function add(a, b) {
          
   
	return a + b
}

console.log(add(1, 5))

打包之后会得到main.js文件,得到js文件之后,我们添加一个html文件引入这个js文件,我们运行html文件,查看控制台输出结果。而前面我们都是使用的开发环境进行打包,这里还可以使用生产环境进行打包:

webpack ./src/js/index.js -o build/js/built.js --mode=production

而是用生产环境和开发环境进行打包有什么区别呢?使用生产环境进行打包就是在开发配置功能上多一个功能,压缩代码。

3、webpack 开发环境的基本配置

3.1、webpack配置打包

webpack.config.js 是 webpack 的配置文件。 作用: 指示 webpack 干哪些活(当你运行 webpack 指令时,会加载里面的配置)

我们添加一个webpack.config.js文件,在文件当中我们需要添加webpack的五个要素:也就是entry、output、loader、plugins、mode

/**
 * 使用path的resolve进行拼接绝对路径
 * */

const {
          
   resolve} = require(path)

module.exports={
          
   
    // 入口
    entry:./src/js/index.js,
    // 输出
    output:{
          
   
        // 输出文件名
        filename:main.js,
        // 路径
        path:resolve(__dirname,build)
    },
    // loader
    module:{
          
   
        rules:[]
    },
    plugins:[],
    // 模式 (开发、生产)
    mode:development
}

打包出错:Invalid configuration object. Webpack has been initialized using a configuration object that does not match the API schema. 配置对象无效。已使用与API架构不匹配的配置对象初始化Webpack。

这个的话很简单,原本是rules,却写成了roles,头皮发麻(遇到这个报错仔细检查一下自己是否有写错的地方)。之后我们进行打包,这个时候我们已经可以进行打包成功了,之后我们同样的使用一个html文件,引入打包后的js文件,在浏览器当中查看效果。

3.2、webpack css 样式打包

我们首先添加一个css文件,文件内容先随便添加一点css样式进去,之后我们在入口文件当中进行引入样式文件。样式文件引入之后,直接执行webpack打包命令会报错,这是因为css文件打包我们还需要添加其他的配置才行。

我们进行css样式打包,我们需要安装css-loader和style-loader。直接执行命令进行安装即可。css-loader是允许在js中import一个css文件,会将css文件当成一个模块引入到js文件中。而style-loader能够在需要载入的html中创建一个标签,标签里的内容就是CSS内容。

npm install style-loader --save-dev
npm install css-loader --save-dev

之后我们在webpack.config.js文件当中添加loader和rules打包规则:

rules:[{
          
   
            // 匹配哪些文件
            test:/.css$/,
            // 使用哪些loader进行处理
            use:[
                // 从下到上进行执行
                style-loader,
                css-loader
            ]
        }]

添加好之后我们就可以对css样式文件进行打包啦。同样的我们这里不单单是存在css文件,有些时候我们还需要对less或者sass文件进行打包,这有如何处理呢?,我们只需要在rules当中添加less或者sass的打包规则即可。

{
          
   
	test: /.less$/,
    use: [
		style-loader,
		css-loader,
		less-loader
	    ]
}

之后我们再进行打包即可,当然了,我们这里还需要下载less-loader和less的依赖,直接执行命令进行安装就好。

npm install less --save-dev
npm install less-loader --save-dev

3.3、webpack Html资源打包

首先下载所需依赖:

npm i -D html-webpack-plugin

之后我们在配置文件当中引入这个组件,并且进行配置使用。这个插件默认会创建一个html文件,引入打包生成的所有资源。

const HtmlWebpackPlugin = require(html-webpack-plugin)

    plugins: [
        // 1、默认的html文件
        // new HtmlWebpackPlugin(),
        // 2、自定义内容
        // new HtmlWebpackPlugin({
          
   
        //     title:"this is title"
        // })
        // 3、使用模板
         new HtmlWebpackPlugin({
          
   
            template:./src/index.html
        })
    ],

我们可以看一下打包生成的html文件,如下,可以看到我们打包生成对应的js文件main.js会在html文件当中进行引入。

3.4、webpack 图片资源打包

首先下载所需依赖:

npm install url-loader --save-dev
npm install file-loader --save-dev

在这里我们可以不用配置任何相关图片资源的打包方式进行打包,在webpack5之后默认会对图片文件进行打包,但是我们在使用图片的时候,还是需要对图片进行处理的,比方说哪些图片就不进行打包了之间转成base64,而哪些需要进行打包,转成base64格式,可以减少请求的数量,降低服务器压力,但是他的缺点就是文件体积会变大,请求速度会变慢。

这里对小于100kb的图片文件进行转码处理,可以看到这张图片就不会被打包出来,进行访问的时候也会通过base64格式进行访问获取.

而当我们直接在html文件当中通过image标签进行使用图片,进行打包之后会发现图片的路径地址不会指向打包之后的地址,这个如何处理呢?首先我们引入html-loader.

npm install html-loader --save-dev

添加配置:再次进行打包,可以发现image标签的指向地址页替换成打包之后的图片地址了。

// 处理html文件当中的图片
      {
          
   
        test: /.html$/,
        loader: html-loader
      }

3.5、webpack 其他资源打包

这里说到的其他资源指的就是除了前端常用的css、js、html这种,我们这里已图标为例进行打包,首先在iconfont上面下载一些图标的css和对应的字体等文件下来,我们进行导入css文件,

import icon from ../other/iconfont.css

导入后在html文件当中通过class类名进行指定即可使用:

<span class="iconfont icon-zhangdan"></span>
    <span class="iconfont icon-youxi"></span>
    <span class="iconfont icon-yuanbao"></span>
    <span class="iconfont icon-shoujichongzhi"></span>

而对应的webpack打包配置可以使用:

// 处理其他资源
      {
          
   
        exclude: /.(css|js|html|less)$/,
        loader: file-loader,
        options:{
          
   
          name:[name].[ext],
          esModule:false,
        },
        type: javascript/auto,
      }

4、devServer 热更新

在这里webpack提供了一个实时更新代码进行打包的工具,这里我们首先安装依赖:

npm i webpack-dev-server -D

依赖安装之后我们在配置文件当中加入配置:

devServer: {
          
   
    contentBase: resolve(__dirname, build),
    compress: true,
    port: 3000
  }

而后我们通过npx webpack-dev-server指令进行启动这个项目,

启动报错:[webpack-cli] Invalid options object. Dev Server has been initialized using an options object that does not match the API schema.

webpack版本原因,不支持这个contentBase属性,配置调整为:

devServer: {
          
   
    // contentBase: resolve(__dirname, build),
    static: resolve(__dirname, build),
    compress: true,
    port: 3000
  }

5、webpack 生产环境的基本配置

5.1、将css提取为单独文件

首先我们安装开发环境的webpack配置进行构建一个配置文件,在前面我们使用开发环境对css文件进行打包,可以看到并没有css文件输出,这是因为使用了style-loader会将css样式转成link标签的格式进行引入,对应的css就被打包到了js文件当中,而我们想将css文件单独进行打包输出该如何做呢?

首先我们导入依赖:

npm install --save-dev mini-css-extract-plugin

这是一个插件,对于前面使用到的HtmlWebpackPlugin的使用是一样的,首先在配置文件当中导入,之后进行实例化。这里我们可以直接new创建,也可以传递参数,下面代码表示的是css文件单独打包提取之后的路径。

const MiniCssExtractPlugin = require(mini-css-extract-plugin);
    plugins: [
        // new MiniCssExtractPlugin()
        new MiniCssExtractPlugin({
          
   
            filename:css/built.css
        })
    ],

并且,在前面对css打包使用的style-loader也不能使用了,改成这个插件的loader进行打包即可。

rules: [{
          
   
            // 匹配哪些文件
            test: /.css$/,
            // 使用哪些loader进行处理
            use: [
                // 从下到上进行执行
                // "style-loader",
                MiniCssExtractPlugin.loader,
                "css-loader",
            ],
        }]

5.2、css兼容性问题处理

对于css样式在不同的浏览器不同的版本下会存在一些兼容性的问题,

npm install --save-dev postcss-loader postcss-preset-env

修改配置:

rules: [{
          
   
            // 匹配哪些文件
            test: /.css$/,
            // 使用哪些loader进行处理
            use: [
                // 从下到上进行执行
                // "style-loader",
                MiniCssExtractPlugin.loader,
                "css-loader",
                {
          
   
                    loader: postcss-loader,
                    options: {
          
   
                        postcssOptions: {
          
   
                            plugins: [[postcss-preset-env,{
          
   }]]
                        }
                    }
                }
            ],
        }]

往下我们在package.json文件当中可以对开发环境、生产环境下的浏览器兼容进行配置。

"browserslist": {
          
   
    "development": [
      "last 1 chrome version",
      "last 1 firefox version",
      "last 1 safari version"
    ],
    "production": [
      ">0.2%",
      "not dead",
      "not op_mini all"
    ]
  }

在这里我们进行打包,默认会使用生产环境下的对应兼容性处理,而要使用开发环境下的兼容处理,我们只需要指定node的环境变量,如下代码:

process.env.NODE_ENV = development

我们使用生产环境进行兼容性处理,在css当中对应flex定位等样式会存在兼容性问题,我们在css当中加入:

display: flex;
    backface-visibility: hidden;

而后进行打包,可以看到对该样式做了兼容性处理

display: flex;
    -webkit-backface-visibility: hidden;
            backface-visibility: hidden;

5.3、css压缩

在对css样式代码进行压缩的时候我们需要使用到一个插件,先对插件进行安装:

npm install --save-dev optimize-css-assets-webpack-plugin

而对于插件的使用基本上都是大同小异,首先将插件引入,引入之后在plugins里面直接new创建对象即可。

const OptimizeCssAssetsWebpackPlugin = require(optimize-css-assets-webpack-plugin )

plugins: [
	new OptimizeCssAssetsWebpackPlugin()
]

5.4、Eslint语法检查

首先我们安装eslint相关依赖:

npm install --save-dev eslint-loader eslint eslint-config-airbnb-base eslint-plugin-import

5.5、JS兼容性处理

在使用babel可以对js的代码进行降级也就是对后续的高级的js代码进行降级处理,方便对低版本的浏览器做兼容,首先我们引入babel相关包。

npm install --save-dev babel-loader @babel/core @babel/preset-env @babel/polyfill core-js

这个时候我们添加js代码,可以添加一个const或者箭头函数,对应的我们在配置当中加入配置:这个时候进行编译可以看到在编译后的js代码当中都进行了降级处理。

{
          
   
        test: /.js$/,
        exclude: /node_modules/,
        loader: "babel-loader",
        options: {
          
   
            presets: [@babel/preset-env]
        }
      }

但是对于一些高阶的函数对象等是无法进行兼容的,我们在js代码当中加入一个promise对象,拿到这个对象我们使用上面的配置进行编译打包。看到promise对象是不会被转义的,这个时候我们添加一个依赖。

npm install --save-dev @babel/polyfill

而后直接在js代码当中导入这个polyfill的js库。

import @babel/polyfill

这样的解决方式是全部的浏览器都进行了兼容处理,所以通过该方式打包之后的js代码也会格外的大,那么我们还可以进行部分适配兼容,添加core-js依赖。

npm install --save-dev core-js

之后我们修改配置如下:这样就做到了对部分浏览器兼容,也大大的减小了打包后的js文件的大小

{
          
   
        test: /.js$/,
        exclude: /node_modules/,
        loader: "babel-loader",
        options: {
          
   
          // 预设:指示 babel 做怎么样的兼容性处理
          presets: [
            [
              "@babel/preset-env",
              {
          
   
                // 按需加载
                useBuiltIns: "usage",
                // 指定 core-js 版本
                corejs: {
          
   
                  version: 3,
                },
                // 指定兼容性做到哪个版本浏览器
                targets: {
          
   
                  chrome: "60",
                  firefox: "60",
                  ie: "9",
                  safari: "10",
                  edge: "17",
                },
              },
            ],
          ],
        },

5.6、JS压缩

在生产环境下会默认对js代码进行压缩,所以我们只需要切换mode选项为生产环境即可。

mode:production

5.7、html压缩

这个我们只需要在前面设置html模板的插件来进行压缩即可,修改对应配置:

plugins: [
        new HtmlWebpackPlugin({
          
   
            template: "./src/html/index-pro.html",
            minify: {
          
   
            	// 去掉空格、换行
                collapseWhitespace: true,
                // 去掉注释
                removeComments: true
            }
        }),
    ],

6、性能优化

6.1、HMR代码热更新

在进行热更新启动项目的时候,当修改一个文件的代码,会对整个项目下的全部代码进行打包,这大大的降低了开发调试的效率,所以就有了HMR单独更新修改的文件,热更新的配置,在原有的基础上加上hot属性:

devServer: {
          
   
    // contentBase: resolve(__dirname, build),
    static: resolve(__dirname, build),
    compress: true,
    port: 3000,
    hot: true
  }

这个时候由于是使用style-loader来进行处理css样式的,该loader已经实现了HMR功能,所以这个时候当我们修改样式文件之后,对应的js代码并不会进行重新加载, log.js:16 [HMR] - ./src/css/image.css log.js:24 [HMR] - ./node_modules/css-loader/dist/cjs.js!./src/css/image.css 其次对于html文件来说,我们并不需要对其进行热更新,在开发当中我们往往会打包一个主页面也就是对应的html文件,而当html文件修改后该页面必然要进行更新,其对应的js等代码也需要进行更新,在这里我们只需要在打包的入口上加上这个html文件即可。

entry: ["./src/js/index.js", "./src/html/index.html"],

6.2、Source-map

当我们在终端中运行npm run dev时,如果代码出现问题报错时, 我们通过控制台看到的错误是在代码压缩后的,而不是源代码的错误。当在源代码中故意写错console,然后运行后报错,但是这个报错行数显示的是编译后的代码,所以就会出现错误行数不匹配的结果,所以我们引出Source Map概念。

Source Map就是一个信息文件,里面存储着位置信息,也就是说Source Map文件中存储着压缩、混淆后的代码,所对应的转换前的位置,有了它,代码出错的时候,除错工具将直接显示原始代码的错误行数,而不是转换后的代码,能够极大的方便后期的调试。

Source-map的使用只需要在配置文件当中加入:

devtool: source-map

6.3、oneOf

正常来讲,所有文件在执行的时候,都要将loader中的rules过一遍,如果符合,就被对应loader处理,不符合则直接过。这样对性能不好,为了解决这个问题,使用ondeof。

作用:提升构建速度,避免每个文件都被所有loader过一遍,因为任何一个文件,构建过程中,在遇到第一个与之对应的loader后,不会再往下进行。oneOf里面的loader只匹配一个。不能有两个配置处理同一种类型的文件,比方说两个Loader,一个eslint,一个babel,他们都处理Js文件,那只会第一个生效,第二个不起作用。

oneof的格式是一个数组,数组当中放loader的配置。

6.4、缓存

6.4.1、babel缓存

代码里面最多的就是js文件,标签结构或者样式文件较少(即使多也没办法处理) babel可以对我们写的代码进行编译处理,处理成浏览器可以识别的语法,即JS的兼容性处理。 比如有100个js模块,其中有一个JS模块有变化修改,其他99个模块没有,那么不可能因为这一个JS 模块变化了,就把全部100个JS模块重新打包。 这个时候开启babel缓存,先把100个JS模块缓存,第二次构建时,没有变化的JS模块就直接启用缓存。

并且在这个时候我们使用nodejs构建一个服务器向外暴露端口来进行访问我们打包之后的文件,并且将文件进行缓存。

const express = require(express)

const app = express()

app.use(express.static(build, {
          
   
    maxAge: 1000 * 3600
}))

app.listen(3000)

之后直接通过node进行启动这个服务,访问3000端口,可以看到在刷新第二次的时候会从缓存当中获取css和js文件。这样就大幅度的减少了对服务器的请求,加快了页面的响应速度

6.4.2、文件资源缓存

但是这样也会存在一个问题,当我们的js和css代码发生改变之后并不会访问服务器获取最新的代码进行渲染,这又该如何处理呢?

这个时候我们只需要对修改后的文件的文件名进行处理即可,每次打包之后的文件名使用hash来进行命名, 这样请求的文件不一样就不会从缓存当中获取了。

/**
 * 以下为js输出地址、css同理
*/
    output: {
          
   
        filename: "main.[hash:10].js",
    },

但是这样的话,每当重新打包后生成的文件名都不一样,都走不了缓存,而当只改了一个文件,重新打包后所有文件都会被进行重新获取,就大大的浪费了时间跟资源。所以就引入了contenthash:根据文件的内容生成hash值,不同文件hash值一定不一样

6.5、tree shaking

基于 ES6 的静态引用,tree shaking 通过扫描所有 ES6 的 export,找出被 import 的内容并添加到最终代码中。 webpack 的实现是把所有 import 标记为有使用/无使用两种,在后续压缩时进行区别处理。简单来说就是去除没有使用的代码,如下我们新加一个js文件,采用es6进行向外暴露

export function add(x, y) {
          
   
    return x + y
}

export function reduce(x, y) {
          
   
    return x - y
}

而后当我们在入口js当中只引用了一个方法,这个时候再进行打包,就会去除掉未使用的方法,如下

// 入口文件代码
import {
          
   
    add
} from "./tree-shaking";
console.log(add(1, 2));

// 打包后代码
!function(){
          
   "use strict";console.log(3)}();

6.6、代码分割 code split

webpack会将所有依赖的文件打包到一个文件中,即bundle.js文件,当一个项目慢慢变得复杂的时候会导致这个bundle.js文件越来越大,浏览器加载的速度也会越来越慢,可以使用代码分割来将不同代码单独打包成不同文件。

  1. 改为多文件入口
  2. 通过optimization将公共代码单独打包

6.6.1、多入口配置:

添加配置:这样就会对多个js文件就会进行分别打包,

entry: {
          
   
        indexPro: "./src/js/index-pro.js",
        indexProTest: "./src/js/index-pro-test.js"
    },

6.6.2、公共代码单独打包

首先对js代码引入一个第三方库axios;这样进行打包

import {
          
   
    reduce
} from "./tree-shaking";
import axios from axios
console.log(reduce(1, 2));
console.log(axios)

这里进行打包之后axios的源码也会被打包到这个js文件当中,这就需要对这种进行抽离出来,我们只需加上配置

optimization: {
          
   
        splitChunks: {
          
   
            chunks: all
        }
    },

6.7、懒加载和预加载

6.7.1、懒加载

懒加载:当代码进行使用的时候才开始加载对应资源:

如下:首先在html当中定义一个按钮,之后在入口文件当中给按钮绑定一个单击事件,这种情况下,当页面初始化加载的时候并不会加载dom.js这个文件资源,只有当按钮点击之后才会加载dom.js文件。

console.log("index-pro loading...")

document.getElementById(btn).onclick = function () {
          
   
    import( /*webpackChunkName:dom*/ ./dom).then((data) => {
          
   
        console.log(data)
    })
}

6.7.2、预加载

预加载:会在使用之前加载js等文件资源,正常加载是同时并发的加载对应的资源,而预加载会在使用的资源加载完毕之后,在浏览器空闲的时候再加载后续资源。但是也有缺陷:浏览器兼容性

document.getElementById(btn).onclick = function () {
          
   
    import( /*webpackChunkName:dom,webpackPrefetch:true*/ ./dom).then((data) => {
          
   
        console.log(data)
    })
}

6.8、PWA

PWA::渐进式网络开发应用程序(离线可访问)

在进行使用的时候需要下载依赖:

npm install --save-dev workbox-webpack-plugin

而后在配置文件当中的插件下加入配置:

new WorkboxWebpackPlugin.GenerateSW({
          
   
            clientsClaim: true,
            skipWaiting: true
        })

最后只需要在入口文件当中进行判断要不要使用这个PWA技术即可:

if (serviceWorker in navigator) {
          
   
    window.addEventListener(load, () => {
          
   
        navigator.serviceWorker.register(./service-worker.js).then(() => {
          
   
            console.log("success")
        }).catch(() => {
          
   
            console.log("error")
        })
    })
}

同样的,打包之后的代码需要运行在服务器之内,使用前面提到的server.js那个文件,通过express库构建一个服务器。最后我们访问暴露的端口,并且切换到offinline模式下或者切断网络,进行测试:

6.9、多进程打包

首先下载多进程打包依赖

npm install --save-dev thread-loader

对于多进程打包,只需要将对应的loader加在要打包的loader上即可。之后进行webpack打包就会变为多进程打包

6.10、externals

externals:防止将某些 import 的包(package)打包到 bundle 中,而是在运行时(runtime)再去从外部获取这些扩展依赖(external dependencies)。

这里在js代码当中直接导入axios库,这样进行直接打包,第三方库就会被打包到对应的那个js文件当中,这样显然是不友好的,所以我们可以在这里排除对axios的打包

externals: {
          
   
        // 拒绝 axios 被打包进来 
        axios: axios
    }

而在入口html文件当中加上axios的cdn进行外连接过来即可

<script src="https://unpkg.com/axios/dist/axios.min.js"></script>

6.11、DLL

    什么是DLL DLL全称是动态链接库(Dynamic Link Library),是为软件在Windows中实现共享函数库的一种实现方式; 那么webpack中也有内置DLL的功能,它指的是可以将可以共享,并且不经常改变的代码,抽取成一个共享的库; 这个库在之后编译的过程中,会被引入到其他项目的代码中,减少的打包的时间;

首先对于ddl对第三方库进行打包,指定哪些第三方库进行额外打包

// 第三方库打包
    entry: {
          
   
        indexPro: "./src/js/index-pro.js",
        axios: [axios]
    },
    // 输出
    output: {
          
   
        // dll 打包
        filename: [name].js,
        path: resolve(__dirname, dll),
        library: [name]_[hash]
    },

并且指定打包后的关系

// 打包生成一个映射关系
        new webpack.DllPlugin({
          
   
            name: [name]_[hash],
            path: resolve(__dirname, dll/mainifest.json),
        }),

这样之后就可以看到会单独给axios打一个包。但是这样打包之后的文件如何使用呢?这个时候就需要新增依赖。

npm i add-asset-html-webpack-plugin -D

用于指定第三方库打包后的存放位置

const AddAssetHtmlWebpackPlugin = require(add-asset-html-webpack-plugin);

new AddAssetHtmlWebpackPlugin({
          
   
            filepath: resolve(__dirname, dll/axios.js)
        })
经验分享 程序员 微信小程序 职场和发展