一文讲清楚webpack和vite原理

一、前言
每次用vite创建项目秒建好,用vue-cli创建了一个项目,却有点久,那为什么用 vite 比 webpack 要快呢,这篇文章带你梳理清楚它们的原理及不同之处!文章有一点长,看完绝对有收获!

二、webpack基本使用
webpack 的出现主要是解决浏览器里的 javascript 没有一个很好的方式去引入其它的文件这个问题的。话说肯定有小伙伴不记得 webpack 打包是咋使用的(清楚的话可以跳过这一小节),那么我以一个小 demo 来实现一下:

  1. 搭建基本目录结构
    我们在vue项目中初始化后全局安装 webpack 和 webpack-cli :
yarn add webpack webpack-cli -g

创建vue所需的目录文件,以及webpack配置文件。

目录结构如下:

在这里插入图片描述

  1. webpack.config.js配置文件编写
    不清楚webpack配置项的朋友可以进官方文档瞅一眼:webpack 中文文档

看完之后,我们知道webpack主要包含的几个概念就开始编写配置文件了!

(1)打包main.js
代码如下:

const path = require('path')module.exports = {mode: 'development',  //设置开发模式entry: path.resolve(__dirname, './src/main.js'),   //打包入口output: {   //打包到哪里去path: path.resolve(__dirname, 'dist'),filename: 'js/[name].js',  //默认文件名main.js}
}

为了方便我们运行,我们去package.json中配置命令,只需yarn dev就能运行了:

"dev": "webpack server --progress --config ./webpack.config.js"

运行后我们发现根目录多出了一个dist文件夹,我们进到main.js中查看发现打包成功了!

(2)打包index.html
问题❓:我们知道vue项目中是有一个index.html文件的,我们如果要打包这个html文件咋办呢?
我们就需要借助plugin插件去扩展webpack的能力,去装它:

yarn add html-webpack-plugin -D

引入并使用它:

const HtmlWebpackPlugin = require('html-webpack-plugin')plugins: [new HtmlWebpackPlugin({template: path.resolve(__dirname, 'index.html'),    //需要被打包的htmlfilename: 'index.html',  //文件打包名title: '手动搭建vue' //html传进去的变量}),]

index.html 代码如下:

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title><%= htmlWebpackPlugin.options.title%></title>
</head>
<body><div id="app"></div>
</body>
</html>

好啦,我们再次运行打包命令,发现dist目录下多出index.html文件,打包成功!

(3)打包vue文件
首先,我们需要去安装vue的源码:

yarn add vue

新建一个App.vue:

<template><div>vue项目测试</div>
</template><script setup>
</script><style lang="css" scoped>
</style>

main.js中写入:

import { createApp } from 'vue'
import App from './App.vue'const app = createApp(App)
app.mount('#app')

我们再去打包,发现报错了,根据提示,我们可以推断webpack是不能处理且不能编译.vue后缀的文件的,这就需要引入loader及vue编译插件了!装它!

yarn add vue-loader@next
yarn add vue-template-compiler -D

继续在配置文件中引入并使用:

const { VueLoaderPlugin } = require('vue-loader')module: {rules: [{test: /\.vue$/,  //.vue后缀的文件use: ['vue-loader']  //启用vue-loader}]},plugins: [new VueLoaderPlugin()]

再次打包,打包成功!我们可以测试一下,用live server运行打包后的index.html看看,会发现写在vue中的文字在页面成功展示!

(4)打包css
那么我们如果要在vue中写css样式呢?显然webpack是识别不了的,还得loader来帮忙:

yarn add css-loader style-loader -D

配置文件中加入新的一条css规则:

module: {rules: [{test: /\.css$/,  //.css后缀的文件use: ['style-loader', 'css-loader']}]}

去vue文件中把字体样式改为红色后,打包并测试一下,成功!

(5)配置babel
为了防止 webpack 识别不了高版本的 js 代码,我们去装 babel :

yarn add @babel/core @babel/cdpreset-env babel-loader -D

webpack.config.js 配置文件添加新的一条 js 规则:

module: {rules: [{test: /\.js$/,  //.js后缀的文件exclude: /node_modules/, //不包含node_modulesuse: ['babel-loader']}]}

babel.config.js 配置文件代码如下:

module.exports={presets:[['@babel/preset-env',{'targets':{'browsers':['last 2 versions']}}]]
}
  1. webpack热重载
    热重载它是webpack的一个超级nice的插件,让你不用每次都去执行打包命令,装它:
yarn add webpack-dev-server -D

之后,我们去webpack.config.js中配置:

devServer: { static:{directory: path.resolve(__dirname, './dist')},port:8080,  //端口hot: true, //自动打包host:'localhost', open:true //自动跳到浏览器}

此时还需要将package.json中的命令改改:

"dev": "webpack server --progress --config ./webpack.config.js"

我们使用yarn dev再次运行,熟悉的一幕来了!自动跳转到浏览器且将vue文件的内容展示在页面上,修改vue内容也会自动打包!

三、webpack打包原理
实现一个webpack的思路主要有三步:

  • 读取入口文件内容(使用 fs )
  • 分析入口文件,递归的方式去读取模块所依赖的文件并且生成AST语法树

1.安装 @babel/parser 转AST树)

2.根据AST语法树,生成浏览器可以运行的代码(遍历AST树)

a.安装 @babel/traverse 做依赖收集b.安装 @babel/core 和 @babel/preset-env 让es6转es5

我们去新建一个目录,结构如下(其中 add.js 和 minus.js 定义了两个值相加减的函数并将其抛出,index.js中引入这两个函数并打印结果,代码就不附上了,比较简单):

在这里插入图片描述

bundle.js 是我们用来打造 webpack 的文件,代码如下:
const fs = require('fs');
const path = require('path');
const parser = require('@babel/parser')
const traverse = require('@babel/traverse').default
const babel = require('@babel/core')const getModuleInfo = (file) => {//1 读文件const body = fs.readFileSync(file, 'utf8');  //读到路径下的文件内容//2 分析文件转AST树const ast = parser.parse(body, {   //body为需要解析的代码sourceType: 'module' //以es6的模块化语法解析})// console.log(ast.program.body);  //[{},{},{}...]//3 依赖收集 const deps = {}traverse(ast, { //遍历astImportDeclaration({ node }) {  //把import类型的对象找出来const dirname = path.dirname(file)  //拿到index.js所在文件夹路径const abspath = './' + path.posix.join(dirname, node.source.value) //add.js文件的绝对路径deps[node.source.value] = abspath //key:'./add.js'  value:'xxx/add.js'}})//4.把ast->codeconst { code } = babel.transformFromAst(ast, null, {presets: ['@babel/preset-env']})console.log(code);const moduleInfo = { file, deps, code }return moduleInfo
}//5. 递归获取所有依赖
const parseModules = (file) => {const entry = getModuleInfo(file)const temp = [entry]const depsGraph = {}for (let i = 0; i < temp.length; i++) {const deps = temp[i].deps //{ './add.js': './src/add.js', './minus.js': './src/minus.js' }if (deps) {for (const key in deps) {if (deps.hasOwnProperty(key)) {temp.push(getModuleInfo(deps[key]))}}}}temp.forEach(moduleInfo => {depsGraph[moduleInfo.file] = {deps: moduleInfo.deps,code: moduleInfo.code}})// console.log(temp);return depsGraph
}//打包
const bundle = (file) => {const depsGraph = JSON.stringify(parseModules(file));//手写一个require 借助evalreturn `(function(grash) {function require(file) {function absRequire(relPath) {return require(grash[file].deps[relPath])}var exports = {};(function(require, code) {eval(code)})(absRequire, grash[file].code)return exports}require('${file}')})(${depsGraph})`}const result=bundle('./src/index.js')
fs.mkdirSync('./dist')
fs.writeFileSync('./dist/bundle.js', result)

我们使用node去运行这个文件,去到 html 页面上,发现控制台能输出加减法对应的结果,说明打包成功!

但是,webpack有一个缺点,如果在这个文件中需要改动一点点再保存,webpack的热重载又会重新自动打包一次,这对于大型项目是极不友好的,这时间估计等的花都要谢了。那么vite出现了!

四、vite工作原理
我们知道,当声明一个 script 标签类型为 module 时,浏览器会对其内部的 import 引用发起 HTTP 请求获取模块内容。那么,vite 会劫持这些请求并进行相应处理。因为浏览器只会对用到的模块发送http请求,所以vite不用对项目中所有文件都打包,而是按需加载,大大减少了AST树的生成和代码转换,降低服务启动的时间和项目复杂度的耦合,提升了开发者的体验。

  1. 需要解决的问题
    那么,要打包一个vue项目,它的入口文件是main.js,浏览器会遇到三个问题:
import { createApp } from 'vue' //浏览器无法识别vue路径
import App from './App.vue' //浏览器无法解析.vue文件
import './index.css' //index.css不是一个合法的js文件,因为import只能引入js文件const app = createApp(App)
app.mount('#app')

知道怎么解决这几个问题,我们就能打造一个vite了!

  1. 打造vite
    我们使用 koa 去搭建一个本地服务让其可以运行,新建一个 server.js 文件用来打造 vite ,代码如下:
//用node启一个服务
const Koa = require('koa');
const app = new Koa()
const fs = require('fs')
const path = require('path')
const compilerDom = require('@vue/compiler-dom')  //引入vue源码  能识别template中的代码
const compilerSfc = require('@vue/compiler-sfc')  // 能识别script中的代码function rewriteImport(content) {return content.replace(/ from ['|"]([^'"]+)['|"]/g, (s0, s1) => {//若以 ./  ../  / 开头的相对路径console.log(s0, s1);if (s1[0] !== '.' && s1[0] !== '/') {   //'vuereturn ` from '/@modules/${s1}'`   //去http://localhost:5173/@modules/vue} else {return s0}})
}app.use((ctx) => {const { request: { url, query } } = ctxif (url === '/') {//读index.htmlctx.type = 'text/html'  //设置类型let content = fs.readFileSync('./index.html', 'utf8')  //读文件// console.log(content);ctx.body = content//content输出给前端}else if (url.endsWith('.js')) {  //js文件  /src/main.jsconst p = path.resolve(__dirname, url.slice(1))  //   src/main.js  拿到文件的绝对路径ctx.type = 'application/javascript'const content = fs.readFileSync(p, 'utf8')ctx.body = rewriteImport(content)}else if (url.startsWith('/@modules')) { //  '/@modules/vue'const prefix = path.resolve(__dirname, 'node_modules', url.replace('/@modules/', ''))  // 'vue'const module = require(prefix + '/package.json').module //读取package.json中的module字段   拿到vue的模块源码地址const p = path.resolve(prefix, module)  // 拿到vue的模块源码的终极地址const ret = fs.readFileSync(p, 'utf8')  //读取文件ctx.type = 'application/javascript'ctx.body = rewriteImport(ret)  //递归 防止vue源码又用到了其它模块}else if (url.indexOf('.vue') > -1) {const p = path.resolve(__dirname, url.split('?')[0].slice(1)) // src/App.vueconst { descriptor } = compilerSfc.parse(fs.readFileSync(p, 'utf8'))console.log(descriptor);if (!query.type) { // 返回.vue文件的js部分ctx.type = 'application/javascript'ctx.body = `${rewriteImport(descriptor.script.content.replace('export default ', 'const __script = '))}import { render as __render } from "${url}?type=template"__script.render = __renderexport default __script`} else if (query.type === 'template') { // 返回.vue文件的html部分const template = descriptor.templateconst render = compilerDom.compile(template.content, {mode: 'module'}).codectx.type = 'application/javascript'ctx.body = rewriteImport(render)}}else if (url.endsWith('.css')) {const p = path.resolve(__dirname, url.slice(1))const file = fs.readFileSync(p, 'utf8')const content = `const css="${file.replace(/\n/g, '')}"let link=document.createElement('style')link.setAttribute('type','text/css')document.head.appendChild(link)link.innerHTML = cssexport default css`ctx.type = "application/javascript"ctx.body = content}
})app.listen(5173, () => {console.log('listening on port 5173');
})
  1. vite热更新
    那么,vite 的热更新怎么实现呢?

我们可以使用chokidar库来监听某个文件夹的变更,只要监听到有文件变更,就用websocket通知浏览器重新发一个请求,浏览器就会在代码每次变更之后立刻重新请求这份资源。

(1) 安装chokidar库:

yarn add chokidar -D

(2) 之后去新建一个文件夹chokidar,在其中新建 handleHMRUpdate.js 用于实现监听:

const chokidar = require('chokidar');export function watch() {const watcher = chokidar.watch('../src', {ignored: ['**/node_modules/**', '**/.git/**'],  //不监听哪些文件ignorePermissionErrors: true,disableGlobbing: true})return watcher
}export function handleHMRupdate(opts) {   //创建websocket连接 客户端不给服务端发请求,服务端可以通过websocket来发数据const { file, ws } = optsconst shortFile = getShortName(file, appRoot)const timestamp = Date.now()let updates;if (shortFile.endsWith('.css')) {  //css文件的热更新updates = [{type: 'js-update',timestamp,path: `${shortFile}`,acceptPath: `${shortFile}`}]}ws.send({type: 'update',updates})
}

五、小结
webpack可以说是把所有模块的依赖关系打包成一个大文件,速度比较慢。
Vite 利用现代浏览器中原生 ES 模块的特性,将开发和构建过程拆分为更小的单位,通过浏览器运行时发送的http请求实现了来实现文件的按需加载。快速的冷启动和实时的模块热更新。Vite 提供了更高效的开发体验和更快的开发环境速度。
vite开发环境使用 esbuild 做esm 转换,不做打包处理,生产用rollup作为打包工具。虽然vite也有一些小瑕疵(首屏,懒加载),不过和webpack相比体验感确实提升了不少。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/bicheng/65590.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Vue3源码解读--方向篇

vue3文档地址、GitHub项目地址&#xff1a; https://cn.vuejs.org/v2/api/ https://github1s.com/vuejs/vue-next/tree/3.2 二、如何本地调试vue3源码 很多时候我们都是在本地调试vue3的源码&#xff0c;然后沿着调用链&#xff0c;一步一步的去梳理vue3的源码。 把vue3项目拉到…

工业大数据分析算法实战-day20

文章目录 day20设备对象维度建模方法维度专家规则驱动的方法&#xff1a;AI-FIT-PM过程模型少量样本驱动的方法数据驱动的方法 软件维度 day20 今天是第20天&#xff0c;昨日是对第九章节行业知识沉淀中知识工程、维度模型、模式要素模型进行讲解&#xff0c;尤其是维度模型中…

k8s的可观测性

文章目录 1. 健康状态监测&#xff08;Health Check&#xff09;1.1 健康检查的原理1.2 健康检查的配置示例1.3 健康状态监测工具 2. 资源使用监控&#xff08;Resource Usage Monitoring&#xff09;2.1 资源使用监控的原理2.2 资源使用监控的配置示例2.3 资源使用监控工具 3.…

InstructGPT:基于人类反馈训练语言模型遵从指令的能力

大家读完觉得有意义记得关注和点赞&#xff01;&#xff01;&#xff01; 大模型进化树&#xff0c;可以看到 InstructGPT 所处的年代和位置。来自 大语言模型&#xff08;LLM&#xff09;综述与实用指南&#xff08;Amazon&#xff0c;2023&#xff09; 目录 摘要 1 引言 …

自定义VUE指定,实现鼠标悬停显示提示面板,离开元素或面板后面板消失

参考文档&#xff1a;https://www.bilibili.com/opus/500023412612186477 // 自定义"v-tooltip"指令,实现鼠标悬浮显示文本 Vue.directive(tooltip, {bind(element, binding) {const tooltipText binding.value;const tooltip document.createElement(div);toolti…

如何进行年度工作回顾?

发生了什么事&#xff1f; 什么事情进展顺利 &#xff1f; 什么事情进展不顺利&#xff1f; 如何适应未来&#xff1f; 年度回顾的定义&#xff1a;这是一种战略工具&#xff0c;能帮助人们清晰看到过去一年对业务、职业或个人生活的影响&#xff0c;可用于明确关键事件、找出问…

Centos 7 二进制安装时序数据库TDengine_我和国产时序数据库的第一次亲密接触

一、前言 之前在搞监控时&#xff0c;曾学习和测试过InfluxDB数据库&#xff0c;第一次接触时序数据库&#xff0c;也深深感受到了时序数据库的块&#xff0c;最近在墨天轮上看到对国产库时序数据库&#xff08;Time Series Database&#xff09;的介绍&#xff0c;特别是看了涛…

Design Compiler:两种工作模式(线负载模式和拓扑模式)

相关阅读 Design Compilerhttps://blog.csdn.net/weixin_45791458/category_12738116.html?spm1001.2014.3001.5482 Design Compiler可以以线负载模式或拓扑模式启动&#xff0c;必须选择其中一个模式。在拓扑模式下还可使用多模式和UPF模式&#xff1a;多模式允许在多种工作…

【2024年-6月-14日-开源社区openEuler实践记录】探索 test - tools:高效测试的开源宝库

开篇引言 大家好&#xff0c;我是 fzr123&#xff0c;在软件开发领域深耕多年&#xff0c;一直致力于探索各种提升效率的工具与技术。今天&#xff0c;我将为大家深入介绍一款在测试领域极具价值的开源项目——test - tools&#xff0c;它为开发者们提供了一系列强大的测试功能…

Java实现拍卖系统详解

一、项目背景与需求分析 随着互联网技术的飞速发展&#xff0c;电子商务领域不断拓展新的业务模式&#xff0c;在线拍卖系统应运而生并逐渐成为一种重要的商业交易方式。在当今数字化的时代&#xff0c;人们越来越倾向于通过网络平台进行各类交易活动&#xff0c;在线拍卖系统…

真的一行代码没写,使用cursor智能代码编辑器,通过问答的方式,我构建了一个微信小程序

文章目录 1 待办123 产品介绍1.1 使用说明1.2 产品亮点1.3 应用场景 2 零基础实战教程&#xff1a;零代码开发微信小程序2.1 微信公众号注册小程序2.2 下载安装微信开发者工具2.3 下载安装cursor2.4 使用cursor零代码构建微信小程序 3 总结 使用cursor已经有一段时间了&#xf…

element-plus在Vue3中开发相关知识

报错&#xff1a;error.mjs:20 ElementPlusError: [ElForm] model is required for resetFields to work. 原因&#xff1a;el-form使用v-model没有把内容绑定上&#xff0c;需要使用 :model 才可以校验 将&#xff1a; <el-form label-width"auto" class"…

Python爬虫实战(保姆级登网页信息爬取教程)

此blog为爬虫实战教学&#xff0c;代码已附上&#xff0c;可以复制运行。若要直接看实战代码翻到博客后半部分。 本文使用selenium库进行爬虫&#xff0c;实现爬取数据操作&#xff0c;此库是通过模仿用户的操作进行对页面的处理。了解了这个思维模式&#xff0c;可以对代码进…

PyTorch快速入门教程【小土堆】之DataLoader的使用

视频地址DataLoader的使用_哔哩哔哩_bilibili dataset数据集&#xff0c;相当于一副扑克&#xff0c;dataloader数据加载器相当于我们的手&#xff0c;选择摸几张牌&#xff0c;怎么摸牌 import torchvision# 准备的测试数据集 from torch.utils.data import DataLoader from …

# 光速上手 - JPA 原生 sql DTO 投影

前言 使用 JPA 时&#xff0c;我们一般通过 Entity 进行实体类映射&#xff0c;从数据库中查询出对象。然而&#xff0c;在实际开发中&#xff0c;有时需要自定义查询结果并将其直接映射到 DTO&#xff0c;而不是实体类。这种需求可以通过 JPA 原生 SQL 查询和 DTO 投影 来实现…

ThinkPHP 8开发环境安装

【图书介绍】《ThinkPHP 8高效构建Web应用》-CSDN博客 《ThinkPHP 8高效构建Web应用 夏磊 编程与应用开发丛书 清华大学出版社》【摘要 书评 试读】- 京东图书 1. 安装PHP8 Windows系统用户可以前往https://windows.php.net/downloads/releases/archives/下载PHP 8.0版本&am…

npm 切换镜像源

设置镜像源 npm config set registry https://mirrors.huaweicloud.com/repository/npm/ npm 官方原始镜像网址是&#xff1a;https://registry.npmjs.org/ 淘宝 NPM 镜像&#xff1a;https://registry.npm.taobao.org 阿里云 NPM 镜像&#xff1a;https://npm.aliyun.com 腾…

VUE前端实现防抖节流 Lodash

方法一&#xff1a;采用Lodash工具库 Lodash 是一个一致性、模块化、高性能的 JavaScript 实用工具库。 &#xff08;1&#xff09;采用终端导入Lodash库 $ npm i -g npm $ npm i --save lodash &#xff08;2&#xff09;应用 示例&#xff1a;搜索框输入防抖 在这个示例…

pikachu靶场搭建详细步骤

一、靶场下载 点我去下载 二、靶场安装 需要的环境&#xff1a; mysqlApaches&#xff08;直接使用小皮面板Phpstudy&#xff1a;https://www.xp.cn/&#xff09;&#xff0c;启动他们 设置网站&#xff0c;把靶场的路径对应过来 对应数据库的信息 由于没有核对数据库的信…

Excel for Finance 06 `STOCKHISTORY` 函数

Excel 中的 STOCKHISTORY 函数是一个强大的工具&#xff0c;专为获取股票或金融市场历史数据而设计。它允许用户从特定日期范围内提取股票的历史价格、交易量和其他相关数据&#xff0c;并将这些数据动态地插入工作表中进行分析。 语法&#xff1a; STOCKHISTORY(stock, start…