webpack


前言

写在开篇

说实话,我之前是没有系统学习过webpack的,对webpack技术的使用最多就是创建项目和打包文件

但是这样好吗,当然不好,所以我还是得找时间系统学一下
至于要学成什么样子,这里分几个阶段

  • 入门:灵活运用webpack
  • 进阶:能够扩展webpack
  • 大师:源码级理解打包编译过程

这里我们先要做到入门,后面在不久后的学习实践中一步一步提升

更新日志

2022/01/28
与JavaScript复习笔记一起,并发地更新!

2022/02/07
迂回前进,更新webpack基础使用部分

2022/04/04
迂回前进,更新webpack实战部分

2022/04/10
更新进阶部分

2022/04/11
继续更新进阶部分

2022/04/12
更新完进阶部分,以后如果能够成为webpack大师级程序员那么再继续补充了(大雾)

2022/05/19
补充babel单独使用的一些技巧

2022/05/20
这个日子,这是什么人间疾苦..
补充webpack实战部分的环境隔离相关内容,以及resolve字段的使用

webpack概述

什么是Webpack

前端项目由什么构成——资源:

PNG, JPG, GIF, WEBP, JS, TS,
CSS, Less, Vue, JSX, Sass ……

webpack默认只处理js、json等文件,所以如果要处理图片、css、html等文件的话还需要进行一些配置

历史背景

但是在上个世代(2009以前),如果手动管理资源却发现这里有N个script标签,而且它们之间或者它们和部分代码有前驱后继关系,并且N非常大时,整个项目变得不易维护

出现的问题

  • 浏览器不识别Sass、Less ==> 需要预编译成css ==> 浏览器正确识别

  • 项目中的模块化以及互相之间引用依赖造成文件分散 ==> 需要把各个分散的模块集中打包成大文件,减少HTTP的链接的请求次数

  • 文件体积大 ==> 代码压缩

  • 部分ES6语法有兼容问题 => ES5 ==>浏览器使用

前端工程化

随着时代的发展,越来越多的工具出现,而其中一些工具——诸如webpack,Gulp、rollup.js,browserify,Vite等,而正是这部分工具的出现才有了前端工程化这种概念

我们将其中的webpack定义为

一种前端资源编译、打包工具

webpack能做什么

编译、打包这个说法还是太笼统了,webpack的功能具体如下:

  • 多份资源文件打包成一个Bundle
  • 支持Babel、Eslint、TS、CoffeScript、Less、Sass
  • 支持模块化处理css、图片等资源文件
  • 支持HMR + 开发服务器
  • 支持持续监听、持续构建
  • 支持代码分离
  • 支持Tree-shaking
  • 支持Sourcemap
  • ……..

总的来说,webpack具有三种基本能力:处理依赖、模块化、打包

处理依赖
方便引用第三方模块,让模块共容易复用,避免全局注入导致冲突、避免重复加载或者加载不必要的模块

合并代码
把各个分散的模块集中打包成大文件,减少HTTP请求,优化代码体积

各种插件
内置的babel能把ES6+转化为ES5-

最小规模使用

第一步: 初始化项目

首先先要将项目初始化为一个包

如何初始化为一个包?
如果不太懂的话,建议先学习前置知识:Node.JS基础

第二步:安装依赖

npm i -D webpack webpack-cli
# 上述代码是同时安装多个包,等价于:
# npm install webpack --save-dev
# npm install webpack-cli --save-dev

第三步:开始打包

在终端,到项目根目录下使用:

webpack [入口文件路径] -o [输出路径] --mode=[模式]

示例

webpack ./index.js -o ./dist/index.js --mode=production

效果

image.png

五个核心概念

入口entry
指示以哪个文件为入口起点开始打包,分析内部依赖图

出口output
指示打包后的资源输出到哪里,以及如何命名

加载器loader
让webpack能够去处理非JS文件(webpack自身只能理解JS,loader相当于翻译其他类型)

插件plugins
插件可以用于执行范围更广的任务,比如打包优化和压缩,一直到重新定义环境中的变量(loader可以看做负责翻译的plugin,功能扩展就需要plugins来做)

模式mode
模式指示webpack使用相应模式的配置,一般只用两种:development和production模式

按照上面这个介绍,我们在项目文件根目录下写一个webpack.config.js:

module.exports = {
    
    entry: '',
    
    output: {},
    
    module: { // loader的配置
        rules: []
    },
    
    plugins: [],
    
    mode: ''

}

基础配置及其注意事项

上述配置还只是一个空的架子,需要填充一些具体的内容

这里以打包js,css,less为例进行说明,需要补充说明的内容都放到了注释里面

/*
    所有的构建工具都是基于nodejs平台运行的,模块化默认采用commonjs
*/

const {resolve} = require('path')

module.exports = {
    // webpack配置
    entry: './src/index.js',
    output: {
        // 输出文件名
        filename: "built.js",
        // 输出路径
        // __dirname是当前项目(这个包)的绝对路径
        // 这里必须采用绝对路径,否则报错
        path: resolve(__dirname, 'build')
    },
    // loader的配置
    module: {
        rules: [
            // 详细的loader配置
            {
                // 匹配要处理的文件,这里是以css结尾的文件
                test:/\.css$/,
                // 使用哪些loader进行css的处理
                // loader有严格的先后关系,
                // !执行顺序是从下往上 从右往左   
                use: [
                    // 创建style标签,将js中的样式文件插入到其中,添加到head中生效
                    'style-loader',
                    // 将css文件变成commonjs模块加载到js中,内容以字符串形式显示
                    'css-loader'
                ]
            },
            {
                test: /\.less$/,
                use: [
                    'style-loader',
                    'css-loader',
                    'less-loader'
                ]
            }
        ]
    },
    // plugin配置
    plugins: [],
    // 模式
    mode: 'development'
	// 有development和production两种选择
// 前者会保留注释,调试信息等,用于检测是否能够正确运行
// 后者尽可能压缩代码,比如省去函数的运行过程而只保留结果,以及将全部代码压缩到一行
}
// 配置完毕之后,直接在要打包文件所在的目录层级下使用webpack命令就可以直接打包

webpack实战

初学者可以看做是练习题,一开始是题目要求,最后是答案

打包JS和JSON文件

webpack默认情况下是只支持打包JS和JSON文件的

情景描述

这里我们想把这个引用了JSON的JS文件打包到./build/built.js文件夹下面

image.png

答案

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

打包CSS和Less

webpack不能直接识别CSS和Less,这里就需要loader来翻译一下,也需要创建一个webpack.config.js文件进行配置

情景描述

将CSS和Less打包到js中

(build中的html文件为打包后手动创建,此处无需在意)

image.png

方案

这里在webpack.config.js中进行配置
这里需要用到loader,每一个loader都需要手动安装依赖

const {resolve} = require('path')

module.exports = {
    entry: './src/index.js',
    output: {
        filename: "built.js",
        path: resolve(__dirname, 'build')
    },
    module: {
        rules: [
            {
                test:/\.css$/,
                use: [
                    'style-loader',
                    'css-loader'
                ]
            },
            {
                test: /\.less$/,
                use: [
                    'style-loader',
                    'css-loader',
                    'less-loader'
                ]
            }
        ]
    },
    plugins: [],
    mode: 'development'
	
}

自动生成html且自动引入

情景描述

如题

image.png

方案
这里需要用到plugins,配置一个HtmlWebpackPlugin插件,和loader一样,都需要手动安装依赖,但是比loader多了一个用require引入的过程

// loader: 下载 使用
// plugins: 下载 引入 使用

const HtmlWebpackPlugin = require('html-webpack-plugin')
const { resolve } = require('path')

module.exports = {
    entry: './src/index.js',
    output: {
        filename: 'built.js',
        // 必须使用绝对路径,不然webpack会报错
        path: resolve(__dirname, 'build')
    },
    module: {
        rules: [
            {
                test: /\.css$/,
                use: [
                    'style-loader',
                    'css-loader'
                ]
            }
        ]
    },
    plugins: [
        // 复制template指定的html文件。并自动引入打包后输出的其他资源
        // 所以指定的html不能手动引入资源,不然会重复引入报错
        new HtmlWebpackPlugin({
            // 如果这个html文件里面什么都不写的话,那么打包后只有head标签和script标签
            template: './src/index.html',
            filename: 'index.html'
        })
    ],
    mode: 'development'
}

打包图片

注意,这里的图片只包括了jpg,png,gif三种格式,至于webp等其他格式还需要别的loader,不过配置方式大同小异,具体方式读者自行查阅,此处不再赘述

情景描述
需要处理html中img标签和less中的图片url

image.png

方案

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

const { resolve } = require("path");

module.exports = {
    entry: './src/index.js',
    output: {
        filename: 'js/built.js',
        path: resolve(__dirname, 'build')
    },
    module: {
        rules: [
            {
                test: /\.less$/,
                use: [
                    'style-loader',
                    'css-loader',
                    'less-loader'
                ] 
            },
            {
                test: /\.(jpg|png|gif)$/,
               
                // 这里需要安装 url-loader 和 file-loader,
                // url-loader会讲图片编为base64
                // file-loader可以加载本地图片
                // 仅有一个loader则不需要使用use
                loader: 'url-loader',
                options: {
                    // 如果图片大小小于8kb,那么就会被处理为base64
                    limit: 8 * 1024,
                    // url-loader默认用es6模块去解析,
                    // html-loader引入图片是commonjs模块
                    // 为了防止冲突,这里统一用commonjs
                    esModule: false,
                    // 随机生成的文件名太长了,下面是指定命名:
				  // image文件夹下,hash值的前10位.扩展名
                    name: 'image/[hash:10].[ext]'
                }
            },
           
            {
                test: /\.html$/,
               // 处理html中的img标签,
			//负责引入img从而能够被url-loader进行处理
                loader: 'html-loader',
                options: {
                    // webpack4 只需要在url-loader配置,
                    // webpack5还需要在这里配置
                    esModule: false,
                }
            }
        ]
    },
    
    plugins: [
        new HtmlWebpackPlugin({
            filename: 'index.html',
            template: './src/index.html'
        })
    ],
    mode: "production"
}

剥离、合并、压缩及兼容处理CSS

情景描述
基于上一个情景,这里需要把less和css全部打包到一个单独的css文件中,并且这个css会自动兼容大部分浏览器(加上私有前缀等操作),最后被自动引入html中

方案
这里的配置有一点繁琐,首先需要在package.json中加上新的字段:

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

这里就是说,
开发环境下,需要兼容的浏览器及其版本(chrome,firefox,safari的最新版);

生产环境下,需要兼容的浏览器及其范围(兼容99.9%以上的属性,没有停运的浏览器)

注意,这里的生产环境和开发环境不是webpack决定的,而是node决定的!
意思是,它不取决于webpack的mode字段,而是由下面这条语句进行配置:

process.env.NODE_ENV = 'development'

知道了这些之后,我们直接看看webpack.config.js该如何配置

const HtmlWebpackPlugin = require("html-webpack-plugin");
const MiniCssExtractPlugin = require ('mini-css-extract-plugin')
// 提取,合并css文件
const CssMinimizerPlugin = require('css-minimizer-webpack-plugin')
// 压缩css文件内容

const { resolve } = require("path");

// 设置node的环境变量,
// 这样就会去package.json中找到browserlist中的development
 
process.env.NODE_ENV = 'development'

module.exports = {
    entry: './src/index.js',
    output: {
        filename: 'js/built.js',
        path: resolve(__dirname, 'build')
    },
    module: {
        rules: [
            {
                test: /\.css$/,
                use: [
                    // style-loader是从JS中插入到style标签,
                    // 这里我们把CSS单独提取出来所以不用它
                    // 'style-loader',
                    MiniCssExtractPlugin.loader,
                    'css-loader'
                ]
            },
            {
                test: /\.less$/,
                use: [
 
                    {
                        loader: MiniCssExtractPlugin.loader,
                        options: {}  
                    },
                    'css-loader',
                    // postcss会去package.json里面找browserslist,并据此调整css的兼容性样式(比如加上浏览器的私有前缀等等)
                    {
                        loader: 'postcss-loader',
                        options: {
                            postcssOptions: {
                                plugins: [['postcss-preset-env',{}]]
                            }
                        }
                    },
                    'less-loader'
                ] 
            },
            {
                test: /\.(jpg|png|gif)$/,
                loader: 'url-loader',
                options: {
                    limit: 8 * 1024,
                    esModule: false,
                    name: 'image/[hash:10].[ext]'
                }
            },

            {
                test: /\.html$/,
                loader: 'html-loader',
                options: {
                    esModule: false,
                }
            }
        ]
    },
    
    plugins: [
        new HtmlWebpackPlugin({
            filename: 'index.html',
            template: './src/index.html'
        }),
        new MiniCssExtractPlugin({
            // 整合到一个CSS中,自动处理冲突
            filename: './css/index.css'
        }),
        // 下面这个是为了把单独剥离出来的CSS文件压缩成一行
        // 如果不装这个的话,纵使是生产环境,也只有html和js是一行的状态
        new CssMinimizerPlugin()
    ],
    mode: "production"
}

处理Vue代码

注意不是处理Vue-Cli

不再赘述具体代码

loader
vue2.x ===> vue-loader
vue3.x ===> vue-loader@nex

plugins
VueLoaderPlugin

如上配置还不够,直接build会报错,不过根据报错内容补全缺失的包就好了

环境隔离

情景描述

我们知道有生产环境和开发环境,并且为了应对一些更加特殊的场景,还需要有其他的环境,这时候,只有一套webpack打包配置文件就显得力不从心的

方案

通常会建立一个configs文件夹,然后在里面配置多个文件针对不同的场景

然后安装webpack-merge

npm i webpack-merge -D

进行一个配置整合即可(具体使用方式还没尝试过,后面补上)

webpack进阶

概要

HMR
resolve
optimization
dll
eslint
babel
pwa
loader
plugin
devtool
tree shaking
code split
caching
lazy loading
library(待补充)
shimming(待补充)

HMR

概念

当我们修改一行代码,整个项目就得重新转译打包,效率十分低下,这时候就得考虑HMR了

Hot Module Replacement,即热模块替换

这里需要知道,和五个核心字段平级的内容中有一个watch字段,可以通过设置watch:true自动重新打包,但是这样的自动更新依旧是打包整个项目,不能算是HMR(watch:true还有另一种写法就是在package.json中的scripts中配置webpack (略) --watch 来达到同样的效果)

永远不要再生产模式下启用HMR

devServer

vscode插件live-server能够自动刷新页面实现类似的功能(热重载)

但是我们希望能够用webpack来实现,所以我们需要安装webpack-dev-server

npm install webpack-dev-server -D
webpack serve # 默认跑8080
# 由于webpack的版本兼容问题,这里也可能不能运行

这个服务器是基于Node的express框架的

和直接使用webpack打包出的静态页面不同,这里不会生成任何文件,这是因为文件操作效率较低,所以webpack-dev-server选择将编译的结果放到内存中,再从内存中读取。

如果你想要基于其他的框架,那么可以这么做:

npm install webpack-dev-middleware express # 基于express

然后在node的express后端文件中引入这个包,并使用app.use()来启用这个引入的包

此后便可以使用这个包来处理文件,返回express的中间件

具体实现有一点繁琐,此处不再赘述

另外,我们常在Vue项目中配置proxy,这个其实就是webpack的devServer的功能之一,解决跨域的原理是因为可以调用本地的服务器,绕开浏览器的安全限制

使用HMR

这里主要是学习webpack5,需要注意其HMR配置方式与旧版本有所冲突

之前说的live-server,watch,(直接使用)webpack-dev-server这些,都是刷新整个页面;

HRM是需要在webpack-dev-server上使用的

module.exports = {
	entry: ''
    // 略
    devServer: {
    	hot:true
	}
}

HRM运行机制(图片来自网络)

1.webpack监听到文件的变化,进行新的编译和打包,而且以简单的js对象保存在内存中。web

2.webpack与devServer进行交互,告诉webpack要保存代码到内存中。json

3.devServer对文件的监控,而且经过配置来告诉devServer是刷新仍是进行热更新。浏览器

4.devServer经过sock.js和浏览器创建websock长连接,把webpack编译代码各阶段信息告诉浏览器(包括更新模块的hash),就是经过这一个hash来进行更新的。服务器

5.webpack经过devServer的配置和传递给他的信息(也就是第二部监听的信息)来决定是否进行浏览器刷新或者热更新。

6.webpack的HotModuleReplacement经过JsonpMainTemplate.runtime 向 server 端发送 Ajax 请求,服务端返回一个 json,该 json 包含了全部要更新的模块的hash值,获取到更新列表后,该模块再次经过jsonp请求,获取到最新的模块代码。这就是上图中 七、八、9 步骤。webpack-dev-server

10.HotModulePlugin将会对新旧模块进行对比,决定是否更新模块,在决定更新模块后,检查模块之间的依赖关系,更新模块的同时更新模块间的依赖引用。jsonp

11.当 HMR 失败后,回退到 live reload 操做,刷新浏览器。

source-map

一种源代码到构建后代码映射技术

如果构建后的代码出错了,通过映射可以追踪到源代码的错误

使用source-map

配置方式很简单,就是参数有点多

module.exports = {
	entry: '',
    // 略
    source-map: inline-source-map
    // 参数的格式是[inline-|hidden-|eval-][nosources-][cheap-[module-]]source-map
}

source-map是通过base64编码来映射的
下面是几种常见的参数:

source-mao 外部
包含 错误信息 和 错误在源代码的位置
inline-source-map 内联
包含 错误信息 和 错误在源代码的位置
嵌入到构建后的js文件中
hidden-source-map 外部
包含 错误信息
单独作为一个文件
eval-source-map 内联
但是追加到相应的每个文件标识符后
包含 错误信息 和 错误在源代码的位置
nosources-source-map 外部
包含错误代码准确信息
cheap-source-map 外部
包含错误信息 和 错误在源代码的位置(只能精确到行)
cheap-module-source-map 外部
包含错误信息 和 错误在源代码的位置(只能精确到行)

参数性能比较

速度

eval>inline>cheap>…
eval-cheap-source-map
eval-source-map

调试友好

source-map
cheap-module-source-map
cheap-source-map

所以结论是:

调试最友好:eval-source-map(脚手架默认)
速度最快:eval-cheap-module-source-map

oneOf

使用oneOf

正常打包流程,是所有loader都会被走一遍,这样其实没有必要,所以我们可以用oneOf来处理一下,最多执行一次loader

module.exports = {
    // 略
    module: {
        rules: [
            {
               oneOf: [ 
              // 这里面的东西仅会执行一个,不能有两个配置处理同一个类型文件
                   {
                       test: /\.css$/,
                       enforce: 'pre'	 // 优先级更高
                       // 略   
                   },
                   {
                       test: /\.less$/,
                       	 // 略   
				  }
               ]
            }
        ]
    }
}

babel

概念

babel是将ES6+等代码转义为ES5代码的工具,相当于一个编译器,和Webpack通常配合使用

babel是能够单独使用的,并不能简单地被认为是webpack的一个内置工具

babel负责转义,webpack负责处理文件

单独安装babel可以尝试

npm i @babel/core -D
# babel的核心代码
npm i @babel/cli -g
# 可以在命令行中运行的依赖,就是说要脱离webpack独立使用的话必须安装的

像这种以@开头的库,都是放在Monorepo中的

之后使用如下两条语句之一进行转义输出

# 方式一
npx babel test.js --out-dir dist
# 方式二
 npx babel test.js -d dist

babel原理图示意

babel也是博大精深的,这里主要是学习webpack,不再赘述

注意,之后说的都是webpack的内置babel,不是单独使用

使用babel缓存

和HMR有类似之处,比如100个模块,1个变了其他99个也得一起重新编译,
由于HMR是基于开发环境下的webpack-dev-server的,
所以生产环境下是用不了HMR的,这就得利用缓存来实现类似的功能

module.exports = {
    // 略
    module: {
        rules: [
            {
               	{
                   test: /\.css$/,
                   cacheDirectory: true // 开启babel缓存,下次构建就会读取缓存就更快了
                       // 略   
            	}  
            }
        ]
    }
}

这里读取缓存可能会有些问题
浏览器缓存是只有响应头才能设置的,前端这里设置的是本地缓存。
本地缓存中更新的内容大概是被浏览器缓存缓存的旧内容覆盖而没有呈现,所以提出给文件名做一些处理:文件名+id,这样处理后浏览器缓存中就没有这个更新的文件,自然会从本地读取

缓存策略

基于文件名+id的可行性,我们需要思考如何找到一个唯一的id值

hash

每次webpack构建时会生成一个唯一的hash值
因为js和css会同时使用一个hash值,所以会导致所有缓存失效

chunkhash

根据chunk生成的hash值,如果打包来源来源于同一个chunk,那么hash就是一样

chunk是代码块的意思,可以理解为模块的封装单元

contenthash

根据文件内容生成hash,不同文件hash值一定不同

上述三种hash的使用方式都是类似的:

filename: 'index.[hash:10].html'

tree shaking

概念

这个tree要怎么理解呢,可以看看webpack的logo图,再想想webpack的入口文件这些,一切似乎就有了一个轮廓了——入口文件的代码就是根节点,引入的其他文件就是子节点,如是递归,就形成了一棵树

那么shaking是什么呢,就是一个比喻,通过摇晃这个树,抖掉书上没用的枯叶——没用到的代码

速成就是树摇,但似乎也没有更官方的翻译了..
主要是针对JS代码,通过import和export确定树的结构

这个性质是默认启用的,所以直接打包即可

代码的副作用

「副作用」的定义是,在导入时会执行特殊行为的代码,而不是仅仅暴露一个 export 或多个 export。

如果在package.json中配置sideEffects:false,那么将把所有代码都视为没有副作用,都将进行tree shaking过滤

也可以配置为形如sideEffects:["*.css"]来指定哪些文件可以视为没有副作用,限制tree shaking的过滤范围

code split

概念

将打包输出的一个文件输出为多个文件(切片),加载时就可以实现按需加载、并行加载等等

使用code split

有三种常用的代码分离方法:

  • 入口起点:使用 entry 配置手动地分离代码。
  • 防止重复:使用 CommonsChunkPlugin 去重和分离 chunk。
  • 动态导入:通过模块的内联函数调用来分离代码。

入口起点

有几个入口就有几个打包的文件

module.exports = {
    entry: {
		main: './src/index.js',
    	test: './src/test.js'
    },
    output: {
    	filename: 'js/[name].[contenthash:10].js',
        path: resolve(__dirname, 'build')
	},
     plugins: [
    	new HTMLWebpackPlugin({})
  	],
}

防止重复

这事基于多入口的,在此基础上做出了改进

如果a.js,b.js都引用了c.js,那么以ab为入口打包的话生成的两个包都会包含c.js,导致重复打包,这样就需要设置optimization

  1. 会将node_modules中的代码单独打包到一个chunk中,供其他文件引用
  2. 自动分析多入口chunk的公共文件,如果有则会提取出作为一个单独的chunk
module.exports = {
    entry: {
        index: './src/index.js',
    	test: './src/test.js'
    }
    // 略
    optimization: {
		splitChunks: {
            chunks: 'all'
        }
    }
}

动态导入

有猜测说这可能是用了ES11的新特性——但实际上并不是这样的,webpack5的这个写法比ES11更早出现

在JS文件中

import ('./xxx').then((res) => {
    console.log('动态导入成功')
    console.log(res) // es6的模块,导入成功后会被单独打包
}).catch((err) => {
    console.log('动态导入失败')
})

懒加载和预加载

懒加载就是要用的时候才加载,而预加载也并非直接加载——而是先等其他资源加载,在浏览器相对空闲的时候才进行加载

预加载更多时候是根据需求决定是否加载,如果被调用了,那么就提前加载,而不是像直接引入那样不管三七二十一就加载

魔法注释

这里必须先要提一下这个前置的知识点

java有个东西叫注解…没错…魔法注释就是那个东西…

我特么…魔法注释….谁翻译啊喂…这有点粉红少女心啊…

使用懒加载和预加载

懒加载

比如可以在点击事件的回调中写一个

// import中的那个不是注释,而是

import( './xxx').then(( {someFunc} ) => {
	someFunc()
    console.log('懒加载')
})

就和Vue路由懒加载是一个样子的

这里就一定会有仔细思考的读者发问了——那这不是每点击一次都会加载吗,这样下来性能不会更低吗?

这个问题确实是存在的,但是如果是一个大型项目,首页加载的时间非常长的时候我们就可以考虑懒加载,其造成的额外开销与之相比可以是微不足道的。

实际操作测试了一下,懒加载只有第一次引入的时候发起了http请求,不会发起第二次

猜测可能是放到了缓存中

至于这如何使用魔法注释:

魔法注释

预加载

开启预加载就更简单了,直接魔——法——注——释!!!

预加载

预加载的兼容性相对较差,在移动端使用可能与预期不符(当然IE就更不用说了)

PWA

概念

Progressive Web App 渐进式网络开发应用程序
具体表现是,能够在离线状态下加载大部分资源

在一些侧重于展示的页面就可以考虑这个技术

要使用PWA技术,我们通常通过workbox来实现,webpack中可以使用work-webpack-plugin

使用PWA

首先先要在这里配置一个插件

plugins: [
	new WorkboxWebpackPlugin.GenerateSW({
        /*
        	生成一个serviceworker配置文件
        	1. 帮助serviceworker快速启动
        	2. 删除旧的serviceworker
        */
		clientClaim: true,
		skipWaiting: true
	}) 
]

然后再到需要做PWA处理的JS文件中:

// 这里是处理兼容性问题
if ('serviceworker' in navigator ) {
    window.addEventListener('load', () => {
        navigator.serviceworker.register('/service-work.js').then(() => {
			console.log('注册成功')
        }).catch(() => {
            console.log('注册失败')
        })
    })
}

注意PWA仅在服务器环境下生效

如果一切顺利,那么在页面的控制台的Application一栏中的Service Workers和Cache Storage中就能找到相关的数据了,

只要成功加载过,那么之后就算断线了也还能继续访问大部分内容

多进程打包

概念

利用thread-loader来做多——多什么?
thread难道不是线程吗?

就当我以为是学习资料出错的时候,去官网考证了一下确实是多进程打包,但是每个进程又是单线程的

反正…就叫多进程打包吧..

使用多进程打包

开启多进程的方式就是在use数组中的最前面一个位置填上thread-loader,和前面的其他loader的配置都差不多,这里唯一需要注意的就是,虽然开启多进程打包之后效率会提高,但是这个打开的过程是要开销大量资源的(据说开启一个就得耗费600ms左右——虽然数据可能并不准确,但是也足以反映其开销大了)

externals

概念

就是对于指定内容,不做打包处理

使用externals

使用方式也非常简单,只需要在五个核心字段平级的位置加上externals

module.exports = {
    entry:{
        
    },
    // 略
    externals:{
        // [打包后的文件名] : [要打包的npm包名]
        jquery: 'jquery'
    }
}

什么场景下不用打包呢?

没错就是CDN引入的时候,如果不配置externals那CDN引入的内容就会作为依赖而打包——但是这样就失去了CDN引入的意义

这里才知道CDN是分布式静态网络的意思…分布式存储静态资源减少服务器的压力…高级啊(我是废物

resolve

概念

个人理解是指定webpack字段相关文件的查找位置,并且能够辅助判断引入文件的路径是一个文件还是文件夹

使用resolve

module.exports = {
	entry: '',
   	output: {},
    devServer:{},
    resolve: {
        // 指定module中配置的内容优先去node_modules中找
        module: [node_modules]// 指定将这些扩展名作为文件解析
        extensions: ['.js', '.html''.json', '.vue']
    	// 设置宏
    	alias: {
    		"@": path.resolve(__dirname, './src')
		}
    },
    module:{},
    plugins:[]
}

dll

概念

读作dio(手动狗头),动态链接库

对于多个库会打包成一个chunk——主要是针对于node_modules的打包,多个依赖打包成一个实在是太大了

使用dll会能够实现node_modules的单独打包

使用dll

module.exports = {
    entry: {
		// 最终打包生成的 [name] --> jquery    
        // ['jquery']指的就是要打包的库是jquery
        jquery: ['jquery']
    },
    output: {
	    filename: '[name].js',
        path: resolve(__dirname, 'dll'),
        library: '[name]_[hash]' // 打包的库里面向外暴露出去的内容的名称
    },
    // 略
    plugins: [
        // 会生成一个manifest.json,提供和jquery的映射
        new webpack.DllPlugin({
           // name: '[name]_[hash]',
           // path: resolve(__dirname, 'dll/manifest.json') // 输出文件的路径
        	
            // 指定不参与打包的库,同时使用时的名称也得变
            manifest: resolve(__dirname, 'dll/manifest.json')
        }),
        // 将某个文件打包输出,并在html中自动引入
        new AddAssetHtmlWebpackPlugin({})
    ]
}

和externals的区别

externals
不用打包依赖,后面手动改为CDN引入

dll
先将依赖单独打包,后面直接引用就好了(不使用dll则是将js一股脑塞到一个文件)

可以配合code spilt将依赖拆分为任意个数的包

总结

开发环境下

优化打包构建速度

  • HMR

优化代码调试

  • source-map

提升开发效率

  • resolve

生产环境下

优化打包构建速度

  • oneOf
  • babel缓存
  • 多进程打包
  • externals
  • dll

优化代码运行的性能

  • 缓存(hash-chunkhash-contenthash)

  • tree shaking(默认启动)

  • code spilt

  • 懒加载/预加载

  • PWA(service worker + cache)

后记

终于到了这里哈哈。

但是webpack的学习仍未结束,之后还要继续努力。

大师级的打包人不是一朝一夕就能练成的。

不过这周还是赶紧把项目完结,把JS的笔记写一写,剩下的时间用来复习吧。


文章作者: Serio
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 Serio !
  目录