初识webpack
说下起因吧,最近正准备学习一下vite,所以特意补充一下前置知识~
基本概念
我们知道当我们开发完成一个网站后,文件很多,体积很大,网页加载缓慢,应该如何缩小网站代码的体积呢?
webpack
是一个静态模块打包工具,作用是分析 、压缩、打包代码。 webpack中文官网
当webpack处理应用程序时,它会在内部从一个或多个入口点构建一个依赖图,然后将你项目中所需的每一个模块组合成一个或多个
bundles
,它们均为静态资源,勇于展示你的内容。
webpack
会帮我们把图中左边所有文件之间的相互依赖关系进行分析,然后进行压缩,最后打包成静态资源。
- 支持所有类型文件的打包
- 支持less/sass => css
- 支持ES6/7/8 => ES5
- 压缩代码,提高加载速度
开始介绍之前我们必须要先理解一些核心概念:
- 入口(entry)
- 出口(output)
- loader
- 插件(plugin)
入口指的是webpack
应该使用哪个模块开始进行打包,并作为构建其内部依赖图的开始,进入入口文件后,webpack
会找出该文件引入的一些其他文件,也就是我们所说的依赖(直接和间接)然后开始构建依赖关系图。默认会去找./src/index.js
,但是我们可以在项目根目录新建一个webpack.config.js
的文件来改变这个入口文件,例如:
module.exports={
entry:"./path/to/my/entry/file.js"
}
依赖图:每当一个文件依赖另一个文件时,webpack
都会将文件视为直接存在依赖关系。这使得webpack
可以获取非代码资源,如image或web字体等。并会把它们作为依赖提供给应用程序。
出口指的是我们需要告诉webpack
在哪里输出它所创建的bundle,以及如何命名这些文件。默认输出的路径为./dist/main.js
,其他生成文件默认放置在./dist
文件夹中。
可以通过在配置中制定一个output
字段,来配置这些处理过程:
const path=require('path');
module.exports={
entry:'./path/to/my/entry/file.js',
output:{
path:path.resolve(__dirname,'dist'),
filename:'my-first-webpack-bundle.js'
}
}
在上面的示例中,我们通过 output.filename
和 output.path
属性,来告诉 webpack bundle 的名称,以及我们想要 bundle 生成到哪里。
loader
webpack
只能理解JavaScript和JSON文件,这是webpack
开箱可用的自带能力。loader
让webpack
能够去处理其他类型的文件,并将它们转换为有效模块以供引用程序使用,以及被添加到依赖图中。
loader
有两个属性:
- test属性,识别出哪些文件会被转换。
- use属性,定义出在进行转换时,应该使用哪个loader。
const path=require('path');
module.exports={
module:{
rules:[{test:/\.txt$/,use:'raw-loader'}]
}
}
这就告诉 webpack 编译器(compiler) 如下信息:
“嘿,webpack 编译器,当你碰到「在
require()
/import
语句中被解析为 '.txt' 的路径」时,在你对它打包之前,先 use(使用)raw-loader
转换一下。”
插件可以用于执行范围更广的任务,包括:打包优化、资源管理、出入环境变量。想要使用一个插件,你只需要 require()
它,然后把它添加到 plugins
数组中。
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
plugins: [new HtmlWebpackPlugin({ template: './src/index.html' })],
};
在上面的示例中,html-webpack-plugin
为应用程序生成一个 HTML 文件,并自动将生成的所有 bundle 注入到此文件中。
开始打包之前我们先配置一下环境:
首先先在本地创建一个名为webpack-study
的文件夹,在终端中输入npm init -y
或npm init
(前者可以一键生成package.json文件,后者需要手动输入相关信息,一般使用前者就可以),会生成一个包含这个创建项目信息的json文件。
既然要用webpack
打包当然需要先安装了,通过使用npm i webpack webpack-cli -D
命令安装webpack
以及脚手架(也可用yarn),再用vscode将当前文件夹打开:
接下来我们需要在scripts
脚本中配置我们自己的打包命令:
"scripts":{
"build":"webpack" // key可以自定义~
}
打包js文件
当前目录下创建src文件夹、index.js、count文件夹以及count.js文件
在count.js中导出一个求和的函数:
export function sum(a,b){
return a+b;
}
在index.js文件中引入
//index.js(入口文件)
imoprt {sum} from './count/count.js';
console.log(sum(1,2))
执行npm run build
命令进行打包后会在当前目录生成dist文件夹下面的main.js
如果想要修改入口文件和出口文件的话可以在当前目录下新建一个webpack.config.js
文件
const path=require("path");
module.exports={
entry:"./src/main.js",
output:{
path:path.resolve(__dirname,'dist'),
filename:"bundle.js"
}
}
这里深入一下,执行npm run build
命令之后会经历一个这样的流程:
- 先从package.json中找到我们自定义的脚本命令,然后执行后面真正的打包命令
- 有没有进行自定义配置(webpack.config.js文件)没有的话进行默认配置文件,有的话则根据配置文件获取配置参数
- 找到入口文件及其内部的一些依赖文件,先构建出依赖关系图,然后对各个模块文件进行编译
- 最终输出到指定文件
生成html文件
下面我们先来做一个小案例---jquery隔行变色
首先安装一下 npm i jquery
然后我们在入口文件(main.js)中
import $ from 'juqery';
$("ul li:even").css("background","red")
$("ul li:odd").css("background","blue")
最后在当前目录下新建public文件夹下的index.html文件
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<ul>
<li>1</li>
<li>2</li>
<li>3</li>
<li>4</li>
<li>5</li>
<li>6</li>
<li>7</li>
<li>8</li>
<li>9</li>
<li>10</li>
</ul>
</body>
</html>
那怎么让这两个文件产生关联呢?这就是我们接下来要说的html-webpack-plugin
插件
执行npm i html-webpack-plugin -D
命令并在webpack.config.js文件中增加plugins选项:
const path=require("path");
const HtmlWebpackPlugins = require("html-webpack-plugin"); // 引入插件
module.exports={
entry:"./src/main.js",
output:{
path:path.resolve(__dirname,'dist'),
filename:"bundle.js"
},
plugins:[
new HtmlWebpackPlugins({
template:"./public/index.html" // 要生成的html文件
})
]
}
重新执行npm run build
命令后dist文件夹终会出现index.html,打开会发现
打包css文件
我们依然需要先在当前目录下新建css文件夹下的index.css文件,并且执行npm i css-loader style-loader -D
命令,因为webpack并不认识css文件,然后在index.css文件中给所有li标签去除前面的点:
li{
list-style:none;
}
webpack中也需要增对应的loader:
const path=require("path");
const HtmlWebpackPlugins = require("html-webpack-plugin"); // 引入插件
module.exports={
entry:"./src/main.js",
output:{
path:path.resolve(__dirname,'dist'),
filename:"bundle.js"
},
plugins:[
new HtmlWebpackPlugins({
template:"./public/index.html" // 要生成的html文件
})
],
module: {
rules: [
{
// 匹配到以css结尾的文件使用css-loader和style-loader加载
test:/\.css$/i,
use:["style-loader","css-loader"]
},
// 以下为less文件的loader
{
test:/\.less$/i,
use:['style-loader','css-loader','less-loader']
}
],
},
}
里面需要注意的是use:["style-loader","css-loader"] 是这个地方,这里是有书写顺序的,链会逆序执行,第一个loader将其结果(被转换后的资源)传递给下一个loader,css-loader是用来识别css语法的,style-loader会生成style标签并对css进行外链。
打包图片
我们再给li标签设置一个背景图片
li{
list-style:none;
background: url('../../public/xing.png');
}
同时在入口文件main.js文件通过js在页面上添加一张图片
import './css/index.css'
import './less/index1.less'
import img from '../public/superman.jpg'
const dom =document.createElement("img")
dom.src=img
document.body.appendChild(dom)
webpack.config.js文件也需要增加上对图片格式的匹配
const path=require("path");
const HtmlWebpackPlugins = require("html-webpack-plugin"); // 引入插件
module.exports={
entry:"./src/main.js",
output:{
path:path.resolve(__dirname,'dist'),
filename:"bundle.js"
},
plugins:[
new HtmlWebpackPlugins({
template:"./public/index.html" // 要生成的html文件
})
],
module: {
rules: [
{
// 匹配到以css结尾的文件使用css-loader和style-loader加载
test:/\.css$/i,
use:["style-loader","css-loader"]
},
// 以下为less文件的loader
{
test:/\.less$/i,
use:['style-loader','css-loader','less-loader']
},
{
test:/\.png|jpg|gif/,
type:"asset"
}
],
},
}
最后执行npm run build
命令
避免多次发起请求,方便传输,原来是一张图片和css,客户端需要发起两次请求,转为base64相当于把图片合并到了css文件里
为什么webpack只对小的图片进行base64转换呢?因为大的图片在进行完base64转换后体积会增大!
打包font字体图标
webpack默认是会自动打包字体图标的,不需要做任何的配置,然后将其输出到构建目录。
把font文件夹放到public文件夹下:
然后我们在入口文件中引入:
// ...
import '../public/font/iconfont.css'
const div = document.createElement("div");
div.className = "iconfont icon-shouye"
document.body.appendChild(div)
直接运行npm run build
命令会发现:
并且文件名称为了避免打包后文件名称重复造成的覆盖问题使用了hash算法随机生成来保证每个文件的唯一性,随着项目中的字体图标文件越来越多管理起来变得不太方便,为此我们也可以在webpack.config.json文件中进行配置来达到分类的效果:
const path=require("path");
const HtmlWebpackPlugins = require("html-webpack-plugin"); // 引入插件
module.exports={
entry:"./src/main.js",
output:{
path:path.resolve(__dirname,'dist'),
filename:"bundle.js"
},
plugins:[
new HtmlWebpackPlugins({
template:"./public/index.html" // 要生成的html文件
})
],
module: {
rules: [
{
// 匹配到以css结尾的文件使用css-loader和style-loader加载
test:/\.css$/i,
use:["style-loader","css-loader"]
},
// 以下为less文件的loader
{
test:/\.less$/i,
use:['style-loader','css-loader','less-loader']
},
{
test:/\.png|jpg|gif/,
type:"asset",
//此处也可以写generator
// ...
},
{
test:/\.(eot|svg|ttf|woff|woff2)$/i,
type:"asset/resource" //assets/resource表示不转成base64,直接复制到dist
generator:{ // 生成器 自动输出到指定的位置和文件中
// 内置变量 [name] 表示之前叫什么名字就原样输出,也就是原文件名
// [ext]原来的后缀名,包括. 例如.eot
filename:"fonts/[name][ext]"
}
}
],
},
}
重新打包后:
这时候文件名有重复的,我们可以加上一点hash:
...
{
...
filename:"fonts/[name]--[hash:6][ext]" // 表示文件名称跟hash值用--连接,hash值随机生成六位
}
此时:
处理语法降级
现在的浏览器依然不支持一些比较高级的语法像ES6、ES7,但是我们在开发中却经常使用这些高级语法,所以我们需要babel
来进行一个降级。
babel
是一个独立的工具,类似于less,所以我们需要安装babel
(核心包,js编译器,用来分析代码)
、babel-loader(webpack翻译js代码)
的包,还有一个预设包babel-preset-env
(降级规则,转什么语法)
执行npm install -D babel-loader @babel/core @babel/preset-env
命令,然后在webpack.config.js中添加配置:
// ...
module: {
rules: [
{
test: /\.js$/, // 匹配.js的文件
exclude: /(node_modules|bower_components)/, // 排除不对node_modules和bower_components下的文件进行降级,因为这些都是别人已经打包好的,不需要重复进行打包
use: { // 数组:使用默认配置 对象:使用预设
loader: 'babel-loader',
options: {
presets: ['@babel/preset-env']
}
}
}
]
}
开始测试,首先在main.js中写一个箭头函数,注意不要去执行,因为一旦执行webpack为了压缩代码会直接把里面的结果进行打包,这样我们就看不出降级的效果了
// main.js
const fn=()=>{
console.log("我是一个箭头函数")
}
console.log(fn)
执行打包命令后查看dist中的文件
搭建webpack服务器
因为每次我们写完代码都需要重新打包,浪费宝贵的开发时间。当我们遇到比较大的项目时修改代码重新打包时,非常耗时,这是因为重新打包需要:
- 再次找到入口并开始构建依赖关系图
- 从磁盘中读取对应的文件到内存中(比较耗时)
- webpack用配置好的loader和plugins翻译和处理文件(css?js==》css-loader、style-loader)
- 再将处理后的内容写入到磁盘出口位置(比较耗时)
代码再变化,重复上述操作
所以我们可以使用webpack搭建一个本地服务器来实现代码修改后页面就可以自动发生改变,以此来提高我们的开发效率
首先需要先安装npm i webpack-dev-server -D
,然后自定义webpack开发服务器启动命令在package.json里
"scripts": {
"build": "webpack",
"serve":"webpack serve"
},
最后执行npm run serve
就可以直接在浏览器中查看效果
对比上面的四步,执行npm run serve
的时候共做了以下几件事:
- 找到入口并开始构建依赖关系图
- 磁盘读取对应文件到内存中
- webpack用配置好的loader和plugins翻译和处理文件
- 再将处理后的内容放到内存里
- 代码再变化只重新打包变化的代码,自动更新到页面无需手动刷新
module.exports={
// 模式,取值范围:production/development
// webpack把打包分为了两种模式,一种是线上生产环境,另一种是开发环境
// production:生产环境,会对代码进行压缩混淆
// development:开发环境,不会对代码进行压缩混淆,速度快,节省了打包时间
mode:"development" // 上线用production
}
每次修改webpack.config.js文件都需要重新进行启动配置才会生效,再次执行npm run serve
发现已经没有提示了,并且在修改了代码之后页面会立即被更新,这种修改代码后不用刷新浏览器就直接修改页面的技术叫做“代码热更新(Hot Module Replacement)”,就是只打包修改了的部分然后以打补丁的形式替换到浏览器上,开发效率非常高!
下面再说一些webpack-dev-serve的一些其他配置
module.exports={
devServe:{
prot:3000, // 自定义端口号
open:true // 是否自动打开浏览器
}
}
接下来如果项目需要上线的话,需要先修改mode选项为production
环境并且运行npm run build
命令得到dist文件夹,然后把这个文件夹压缩发送给后端部署即可。
end...
转载自:https://juejin.cn/post/7207347342382514236