vite起步
wǎng luò shí huāng 2021-05-31
vite
命令行
只是认识vite而已
- 笔记参考线上笔记:https://akaileyouyou.gitee.io/jg-vue/vue-analyse/vite.html
- 笔记可以参考这里:https://github.com/okbug/ajunge-docs/blob/5bb67154eb95535b5ef1d1221a500d7aa7be5509/zf/jiagouke2-vue/18.vite-app/index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<link rel="icon" href="/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Vite App</title>
</head>
<body>
<div id="app"></div>
<!-- 表示是一个es6模块 内部可以直接加载 main.js文件 import语法会发送请求 -->
<script type="module" src="/src/main.js"></script>
<!--
1. 通过服务端返回渲染 ,会将首页返还给你,首页加载了main.js
2. es6Module 不支持直接导入第三方模块,路径必须是/ 或者./开头,才会发请求
3. 增加/@modules 表 示他是一个第三方模块 返回会前端
4. 前端会扫描当前有import 语法 会再次发送请求 遇到@module 会将node_modules文件返还给你
5. 后端将.vue文件进行处理,返回给前端(分为3部分返回给前端) :
我们在network的请求里,可以看见请求每个.vue文件时,文件里对用的代码,如下:
第一个文件:import一个文件,路径包含:xxxxxx.vue?type=template,这个文件会返回一个render函数,就是把vue模板编译为render函数。
第二个文件:import一个文件,路径包含:xxxxxx.vue?type=template,这个是vue组件里的样式
当前请求的这个文件内容包含:export default {} ,这个是vue组件本身导出的东西。
这就把vue文件分成了3部分解析。
6. 会再次发送请求。因为上面的文件里有import语句,所以对应的再次发送对应的请求,去拿到css文件和组件模板发育对应的render函数。
7. 热更新靠的是websocket
-->
</body>
</html>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
# 2021-06-09拉勾公开课:vite原理与实践
- 作者笔记:https://juejin.cn/post/6972082127965945869
- 代码和课件:https://gitee.com/wangluoshihuang/lagou/tree/master/%E7%9B%B4%E6%92%AD/2021-06-11-vite2.0%E5%8E%9F%E7%90%86%E4%B8%8E%E5%AE%9E%E8%B7%B5
- npm init @vitejs/app
- 开始选择官方为我们提供的模板,开始选择我们需要使用的模板
- 还可以参考这里 (opens new window)直接跳过选择模板。
- 使用上面的命令还可以指定社区为我们提供的很多模板。
- https://github.com/vitejs/awesome-vite#templates 针对不同技术栈
- 开始选择官方为我们提供的模板,开始选择我们需要使用的模板
- npm run serve可以开启一个本地服务,以供查看打包之后的文件有无错误。
- npx vire --help:可以查看vite还有哪些命令可用。
- vite.config.js的配置方式和rollup一样。
- vite里使用社区的脚手架开发vue2
- 使用命令即可:npx degit matt-auckland/vite-vue2-starter myvuevite(项目名字)
- 参考:https://github.com/matt-auckland/vite-vue2-starter
- 使用 degit
- 老项目怎么在vite下运行
脚手架和打包工具的区别:脚手架是项目的基本结构和基本代码;打包工具是转换代码。
第一步:老项目代码已经有了;所以我们安装vite,而不是安装@vitejs/app这个脚手架。
第二步:添加vite.config.js;这个文件里使用相应的插件去解析相应的文件
- 参考:https://gitee.com/wangluoshihuang/lagou/blob/master/%E7%9B%B4%E6%92%AD/2021-06-11-vite2.0%E5%8E%9F%E7%90%86%E4%B8%8E%E5%AE%9E%E8%B7%B5/myvue2vite/vite.config.js
第三步:添加vite入口文件
- 参考:https://gitee.com/wangluoshihuang/lagou/blob/master/%E7%9B%B4%E6%92%AD/2021-06-11-vite2.0%E5%8E%9F%E7%90%86%E4%B8%8E%E5%AE%9E%E8%B7%B5/myvue2vite/index.html
第四步:添加启动命令vite
- 参考:https://gitee.com/wangluoshihuang/lagou/blob/master/%E7%9B%B4%E6%92%AD/2021-06-11-vite2.0%E5%8E%9F%E7%90%86%E4%B8%8E%E5%AE%9E%E8%B7%B5/myvue2vite/package.json#L5
这也只是跑起来了。打包的话可能我们还用到了webpack的某些插件,但是在vite里却没有相似的插件。
# vite插件
- vite插件基于rollup,所以rollup插件可以直接在vite里使用。vite插件只是在rollup插件基础之上加了自己的钩子函数。
- 插件导出的一定是一个函数。
- 插件必须有一个返回值,返回值是一个对象,对象必须有个属性name,这个就是插件的名字。
- 还需要实现resolveId、load两个函数,只是rollup插件里的规范。 参数id是模块的名称。
- 开发的插件:https://gitee.com/wangluoshihuang/lagou/blob/master/%E7%9B%B4%E6%92%AD/2021-06-11-vite2.0%E5%8E%9F%E7%90%86%E4%B8%8E%E5%AE%9E%E8%B7%B5/vue3/plugins/vite-plugin-vFile.js
- 这里 (opens new window)我们引入my-vfiles模块时插件里的参数id就是这个名字。插件导出的内容就这一行 (opens new window)的msg值。
Rollup打包工具的使用(超详细,超基础,附代码截图超简单):https://juejin.cn/post/6844904058394771470#heading-38
# 自己实现一个vite功能的工具
- vite源码在:https://github.com/vitejs/vite/tree/main/packages/vite
- 命令行工具创建的几个步骤:
- 第一步:package.json里的name字段就是我们在命令行使用的命令名字
- 第二步:package.json里的bin字段,就是我们执行命令时对应执行代码的路径
- 第3步:写具体js代码
- 第4步:使用 npm link命令把命令加入到全局命令行去。此时在任何地方都可以执行这个命令。
- 创建过程:
- 服务器拿到index.html页面,找到里面的入口文件,然后解析入口文件及其里面引入的文件,返回。
- 实现过程如下:
#!/usr/bin/env node
const path = require('path')
const { Readable } = require('stream')
const Koa = require('koa')
const send = require('koa-send')
const compilerSFC = require('@vue/compiler-sfc')
/**
* 1.最终开发是一个基于node的命令行工具,在index.js头部配置运行node的位置`#!/usr/bin/env node`
* 2.导入koa和koa-send
* 3.开启静态文件服务器
* 4.返回当前目录下的index.html静态页面
* 5.监听端口,测试静态服务器,在终端执行npm link将当前项目链接到npm的安装目录中
* 6.打开基于vue3开发的项目,在终端输入vite-cli,开启静态服务器
* 7.创建中间件,处理第三方模块路径为/@modules/xxx
* 8.在处理静态文件之前,创建中间件,当请求时判断请求中是否有/@modules/xxx,如果有去node_modules中加载对应模块,交给处理静态文件中间件去处理
* 9.处理单文件组件,第一次请求将单文件组件编译成一个对象,第二次请求编译单文件组件的模板返回render函数,并挂载到render方法上
* 10.当请求到单文件组件,并读取完成后,接着要对单文件组件进行编译,并且将编译后的结果返回给浏览器,所以是处理完静态文件之后,并且单文件组件可能会加载第三方模块,并且是加载第三方模块之前1,2之间
* 11.使用@vue/compiler-sfc对单文件组件进行编译,将编译后的结果拼凑成vite生成文件样式,并转换为流的方式发送给浏览器
* 12.加载css模块错误会阻塞后续代码执行,导致请求中断,注释图片和css模块的加载,此时重新启动,第一次编译单文件组件为组件的项目对象并返回给浏览器成功
* 13.处理第二次请求,将单文件组件模板编译为render函数
* 14.将process.env.NODE_ENV处理为development
* 15.处理样式、图片等资源
*/
const app = new Koa()
const streamToString = (stream) =>
new Promise((resolve, reject) => {
const chunks = []
stream.on('data', (chunk) => chunks.push(chunk))
stream.on('end', () => resolve(Buffer.concat(chunks).toString('utf-8')))
stream.on('error', reject)
})
const stringToStream = (text) => {
const stream = new Readable()
stream.push(text)
stream.push(null)
return stream
}
// 3. 加载第三方模块
app.use(async (ctx, next) => {
// ctx.path --> /@modules/vue
if (ctx.path.startsWith('/@modules/')) {
const moduleName = ctx.path.substr(10)
// 需要先找到模块的package.json,在获取package.json中module值,就是ESModule的入口文件
const pkgPath = path.join(
process.cwd(),
'node_modules',
moduleName,
'package.json'
)
const pkg = require(pkgPath)
ctx.path = path.join('/node_modules', moduleName, pkg.module)
}
await next()
})
// 1. 静态文件服务器
app.use(async (ctx, next) => {
// 返回当前目录下的index.html
await send(ctx, ctx.path, { root: process.cwd(), index: 'index.html' })
// 执行下一个中间件
await next()
})
// 4. 处理单文件组件
app.use(async (ctx, next) => {
if (ctx.path.endsWith('.vue')) {
const contents = await streamToString(ctx.body)
// 单文件组件的描述对象descriptor
const { descriptor } = compilerSFC.parse(contents) // 使用compiler-sfc模块解析.vue组件
let code
// 如果当前请求不带type字段 说明是第一次请求
if (!ctx.query.type) {
code = descriptor.script.content
// console.log(code)
// import HelloWorld from './components/HelloWorld.vue'
// export default {
// name: 'App',
// components: {
// HelloWorld
// }
// }
code = code.replace(/export\s+default\s+/g, 'const __script = ')
code += `
import { render as __render } from "${ctx.path}?type=template"
__script.render = __render
export default __script
`
// 处理第二次请求 编译模板compileTemplate
} else if (ctx.query.type === 'template') {
const templateRender = compilerSFC.compileTemplate({
source: descriptor.template.content,
})
code = templateRender.code
}
// 设置'application/javascript'请求头
ctx.type = 'application/javascript' // 不管返回的文件名字是什么,浏览器只关心返回文件响应头的type是什么类型。只要这里的type类型是正确的那么浏览器就会正确的解析。
// 需要将code转换为只读流发送给浏览器
ctx.body = stringToStream(code)
}
// 经过下一中间件处理 将加载第三方模块路径进行修改
await next()
})
// 2. 修改第三方模块的路径
app.use(async (ctx, next) => {
// 判断当前文件是否是javascript
if (ctx.type === 'application/javascript') {
// ctx.body是流,需要转换为字符串
const contents = await streamToString(ctx.body)
// 匹配第三方模块 替换为@modules/xxx
// import vue from 'vue'
// import App from './App.vue'
ctx.body = contents
.replace(/(from\s+['"])(?![\.\/])/g, '$1/@modules/')
.replace(/process\.env\.NODE_ENV/g, '"development"')
}
})
app.listen(3000)
console.log('Server running @ http://localhost:3000')
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
所以vite为什么快?他只做了两件事:读取代码;修改代码的加载路径。