碎碎念:
原先笔者也一直没有真正去了解过webpack
,觉得会敲项目就好了,只知道它是打包工具,但为什么?有什么好处?基层工事决定上层建筑。要深入了解它的原理不容易,所以今天决定开始学一下webpack
,一下是一些个人的理解
前言:
webpack
有什么作用?
官方解释是:会从内部一个或多个入口点构建一个依赖图,将项目中所需的每一个模块变成静态资源,用于展示项目内容
那为什么非得把项目内容打包成静态资源?
将项目资源打包成静态资源实际上是为了将项目中各种文件打包成数量较少的静态资源文件,方便部署到服务器,对页面的加载速度提升、减少网络请求次数等都有好处
怎么理解使用webpack
打包项目可以"减少网络请求次数"?
一个前端项目中,可能包含数十个JS
文件、样式文件、图片文件等其他文件,如果不使用webpack
打包,每个文件都要发送一个网络请求获取,这样就会导致网络请求次数增加(注意: 此处所说的"请求"并不是AJAX
请求,而是页面资源请求)
但是,如果使用webpack
打包,这些资源文件会被整合成一个或几个较大的静态资源文件中,这样就只需要进行这几个页面资源请求,而不是原先的数十个。大大减少了网络请求的次数,从而提高了页面加载的速度
非得使用webpack
(打包工具)不可吗?
答案是肯定的。前面几个问题是从webpack
带来的好处出发的,下面从它的功能性出发
这里参考了一下这位老哥的文章:Webpack5 系列(一):基础篇
当一个前端工程变大的时候,我们不可能将所有的模板代码、样式代码、逻辑代码都堆叠在一次,这时候就需要分模块
你可能回想,我不模块化不就行了,那我就可以不使用webpack了。想法不错(doge)但是不行,这样的话代码维护性差、复用性差、且开发效率底下(本来分模块的时候开发得就够屎了,再不分模块不就是屎上加屎嘛hh)
其实如果你进行过Vue
或者React
的开发,应该对这个不陌生,这两个框架所提倡的组件化开发其实思想和分模块如出一辙,一个组件看成是一个单独的模块,里面有模板、样式、逻辑
回归正题,现在已经确定了要使用模块化,但还没解决为什么非得使用webpack的问题。这主要是因为使用ES6的模块化语法(即import
、export
导入导出语法),浏览器不支持,所以webpack
就应运而生
webpack
可以将模块化语法的代码打包成浏览器可直接运行的静态资源文件,虽然打包后的静态资源文件可能才一个或几个,但webpack
会处理各模块之间的依赖关系,使得浏览器能够正确加载和执行这些模块化代码
实际应用中资源请求的矛盾点:
前面在"怎么理解使用webpack打包可以'减少网络请求次数'"讲到:webpack可以将项目整合成一个或几个资源文件,这样在请求页面资源时也就只用请求几个
可是当你随意打开任何一个网站或应用PC
端,检查网络请求部分,似乎页面资源请求有好多个,不是说webpack
打包后能将项目整合成几个静态资源文件吗?这不是自相矛盾吗?
以Edge
浏览器首页为例,单首页部分的JS
请求就有这么多个
主要有几方面的原因:
- 框架的选择:当前所浏览的页面可能是基于
Vue
等其他框架开发的,这些框架通常会引入多个JS
和CSS
文件,以及一些图像、字体资源 - 模块化开发:当项目代码分割成多个模块,每个模块就有一个或几个文件,那势必造成总的资源请求变多
模块化开发虽然带来了代码的维护性和可读性等方面的提升,但随着也造成了页面资源请求变多。但是,可以使用webpack
等构建工具进行打包优化解决这个问题(有种绕了一大圈,回到起点的感觉hh)
- 懒加载、
CDN
加速等其他原因
CDN
加速是指将资源分发到位于全球各地的服务器上,当用户访问时,会从最近的CDN
服务器上获取。但是用户在加载页面时可能会从多个不同的CDN
服务器上获取资源,所以也变向导致资源请求数量增多
至此,就大概讲了为什么要是有webpack
的原因以及它能够带来的好处,说白了就是:减少请求次数、加快加载速度、方便部署、将模块化语法翻译成浏览器看得懂的样子
webpack核心概念:
在项目根路径新建webpack.config.js
1. 入口文件
即webpack
构建时从哪个模块开始找起。进入这个入口后,webpack
找到和这个入口直接和间接依赖的,然后接着向下找,如此深入,直至找完
ini
module.exports = {
entry: './src/index.js',
};
2. 输出
即webpack
打包完后静态资源文件放哪个文件夹下,以及文件的名字。在指明存放路径时需要使用Node.js
提供的path
库
lua
const path = require('path');
module.exports = {
entry: './src/index.js',
output: {
path: path.resolve(__dirname, 'dist'), // 存放在dist文件夹下
filename: 'dist.js', // 默认是main.js
},
};
3. loader
webpack
只能理解JavaScript
和JSON
文件。但是一个项目中肯定有其他如.css
、.html
、.ts
等文件,而loader
就是让webpack
能够去处理这些原先它理解不了的文件,并将它们转换为有效模块,以供应用程序使用,以及被添加到依赖图中
使用步骤:
- 装包(装对应的
loader
)
比如对.css
使用css-loader
,对.ts
使用ts-loader
- 配置(采用内联写法,官方推荐)
javascript
module.exports = {
module: {
rules: [
{ test: /.css$/, use: 'css-loader' },
{ test: /.ts$/, use: 'ts-loader' },
],
},
};
内联写法中,rules
数组中表示配置各种loader
,每个对象表示配置一个loader
;第一个参数test为正则表达式,第二个表示使用哪一个loader
意为:对所有的css文件,使用css-loader进行转译;对所有的ts文件,使用ts-loader进行转译
正则表达式在使用时注意不需要为它添加引号
4. 模式(mode)
可选值:production | development | none
通过设置mode
,告知webpack
使用响应模式的内置优化
当然,这一项你也可以不配置,那就默认为production
5. 插件(plugin)
插件可以执行范围更广的任务,包括:打包优化、资源管理、注入环境变量...
使用一个插件,只需要require()
它,并把它添加到plugins
数组中。多数插件可以通过option
自定义(如下面的例子)。可以在一个配置文件中因为不同目的而多次使用同一插件,这是要通过new
操作符创建一个插件实例
ini
const HtmlWebpackPlugin = require('html-webpack-plugin');
const webpack = require('webpack'); // 用于访问内置插件
module.exports = {
plugins: [
new HtmlWebpackPlugin({ template: './src/index.html' })
],
};
压缩打包后的JS代码:
- 安装插件
npm i --dev terser-webpack-plugin
- 引入并配置
ini
const TerserPlugin = require('terser-webpack-plugin');
module.exports = {
optimization: {
minimize: true,
minimizer: [new TerserPlugin()],
},
};
避免浏览器缓存JS文件:
由于现在每次生成的文件名都是dist.js
(前面在输出时配置了文件名为这个),所以浏览器会对这个文件进行缓存,进而不更新。为了避免浏览器进行缓存,可以给文件名添加一段随机的字符,每次更新后都生成新的随机字符。所以在webpack.config.js
中配置输出即可
lua
module.exports = {
output: {
filename: "[name].[contenthash].js",
path: path.resolve(__dirname, "dist"),
},
}
[name]
的写法也可以换成一个写死的文件名,中括号的写法的话会在每次打包时自动替换上前面输出时配置的文件名contenthash
会在每次打包时随机生成一行字符串,这样的话,每次打包生成的文件名就不一样了,浏览器也就不会进行缓存
当然它也就会导致每次在保存生就多生成一些文件
配置别名路径:
就常见的就是把@
配置成指向src
。这一点在Vue2
和Vue3
应该体验不深,毕竟不需要我们自个儿配置,因为Vue2
和Vue3
的脚手架工具(Vue CLI、Vite
)默认就帮我们配置好的。但如果是一个由CRA
创建的React
项目,常常就需要配置别名路径
貌似使用Vite创建的React+TS项目也需要手动配置,应该没记错
java
module.exports = {
resolve: {
alias: {
'@': resolve('src')
}
}
}