Vite-基础知识

2022/2/9 Vite
// 什么是 Vite?
1. 高阶构建工具的封装,基于 Rollup
2. 优点
  1. 使用简单
  2.3. 便于扩展
----------------------------------------------------------------------------------------------
// 和传统构建工具的区别
1. Vite 具有 High Level API,而 Webpack 或 Rollup 具有 low Level API
2. Vite 不包含自己的编译能力,编译能力基于 Rollup
3. 完全基于 ESM 加载方式的开发
4. Webpack 更全面,Rollup 更专一,Vite 更好用
5. Vite 是为项目而生的,不是为构建而生的
6. 减少了很多配置量(都已经集成)
  1. devServer
  2. 各类 loader
  3. build 命令
----------------------------------------------------------------------------------------------
// Vite 的优势
1. 上手非常简单
2. 开发效率极高
3. 社区成本低(兼容 Rollup 插件)
4. 没有复杂晦涩的配置
5. Vite 有自身的插件系统
6. 生态兼容 Rollup 插件
----------------------------------------------------------------------------------------------
// 为什么 Vite 速度快?
1. 传统的构建工具的编译顺序是从 entry 开始把所有的文件打包成一个 Bundle,当文件越多时就会越慢
2. Vite 在启动时只会做一些预编译,即只编译需要用到的文件,没有用到的文件不会去编译,从而提高速度
3. Vite 的入口是 html 文件中引入的 script 脚本,从这个脚本加载开始分析里面需要编译的文件
4. 在开发环境使用了 Esbuild 编译工具,这个工具比传统的构建工具的编译速度要快很多
5. 线上环境使用 Rollup
----------------------------------------------------------------------------------------------
// Vite 创建项目
1. 运行命令:npm init @vitejs/app
2. 可以选择框架如,Vue、React 等,现 Vue 版本使用的是 Vue3
3. 没有提供 ESlint、prettire 等功能,只有基础的目录结构
4. Vite 创建的项目不支持保存一个文件自动做 ESlint 校验,只能手动执行 ESlint 命令
5. Webpack 或 Rollup 的编译入口是一个 js 文件,而 Vite 编译入口是一个 html 文件
   通过执行 html 文件中引入的 script 脚本,这个请求会走到 Vite 的 Server 上
   Vite 再去加载对应的 js 文件进行解析
6. 在安装 Vite 的依赖使用需要使用 yarn 安装,使用 npm 安装可能会报错,具体可查看官网的 issues
7. Vite 创建的项目如果是 Vue 项目,默认是支持 .vue 文件的,原因是使用了 '@vitejs/plugin-vue'
   如果想使用 jsx 语法,需要使用 '@vitejs/plugin-vue-jsx'
   // vite.config.js
   import { defineConfig } from 'vite'
   import vue from '@vitejs/plugin-vue'
   import vueJsx from '@vitejs/plugin-vue-jsx'
   export default defineConfig({
     plugins: [vue(), vueJsx()]
   })
8. Vite 构建 Vue2 项目
  1. 因为 Vite 创建的 Vue 项目是 Vue3 版本而没有 Vue2 版本
  2. 如果需要使用 Vite 创建 Vue2 项目需要使用 'underfin/vite-plugin-vue2'
     在执行 npm init @vitejs/app 后选择 vanilla,然后安装上述的依赖和 Vue2
     自己添加 App.vue 和 main.js
  3. 如果觉得手动安装和添加不方便也可以使用 Vite 官方推荐的 'awesome-vite''awesome-vite' 下有一个 vite-vue2-starter 的 Vue2 项目模板,可以进行 clone
9. Vite 构建 React 项目
  1.19 年底 React 官方在主推 'FastRefresh' 用于替代 Webpack 的 'react-hot-loader'
    1. 解决很多 rhl 无法解决的问题
    2. 速度更快
    3. 支持局部更新
  2. 所以 React 插件名称在 Vite 中叫 '@vitejs/plugin-react-refresh'
  3. 在执行 npm init @vitejs/app 后选择 react
     在创建的项目的 vite.config.js 中就使用了 '@vitejs/plugin-react-refresh' 插件
  4. 在 html 文件里面的 script 脚本会引入 main.jsx 文件,这里要注意,虽然引入的路径后缀是 jsx
     但是 Vite 真正返回的是编译后的文件,而不是直接把 jsx 文件返回,作为浏览器来说并不会关心是
     引入什么路径后缀,只会关心返回的格式是不是标准的,符合 js 的内容,所以 Vite 并不是直接返回 jsx
----------------------------------------------------------------------------------------------
// Vite 项目中使用 CSS
1. 基本的 css 即预处理器都支持
2. 支持 postcss.config.js
3. 支持 css-modules
  1. css 文件命名为:test.module.css
  2. 通过 import classes from '@/styles/test.module.css'
  3. <div class={classes.bgClass}></div>
4. @import('url'),支持 alias,即 @import('@/styles/index.css')
  // vite.config.js
   import { defineConfig } from 'vite'
   import vue from '@vitejs/plugin-vue'
   import vueJsx from '@vitejs/plugin-vue-jsx'
   export default defineConfig({
     plugins: [vue(), vueJsx()],
     alias: {
       '@': '/src'
     }
   })
----------------------------------------------------------------------------------------------
// Vite 项目中使用 TS
1. 只编译 TS,但不校验,如果需要打包时校验需要执行 'tsc --noEmit'
  1. VSCode 会提示,但是 Vite 服务没有报错,也没正常显示
  2. 如果希望在打包时进行校验可以安装 typescript 依赖,
     在 package.json 文件的 scripts 中的 build 命令中修改 'tsc --noEmit && vite build'
  3. 如果是用 Vue + TS 开发则还需要安装 'vue-tsc' 依赖,
     对应的 build 命令修改成 'vue-tsc --noEmit && tsc --noEmit && vite build'
2. 在 tsconfig.json 中的 'compilerOptions' 下新增 'isolatedModules: true' 这个配置
   Vite 对 TS 只做单文件的编译,但是 TS 是可以进行文件关联的,即可以进行模块导入导出
   这个配置就是告诉 TS 不进行模块之间的关联,开启了这个配置就不能做以下的操作
   1. 从其他 TS 文件导入的类型,不能再导出
   2. 不能使用常量 enum,即 const enum,因为在 TS 中会把 'enum' 直接变成一个常量
   3. 不能写没有 'import' 或没有 'export'TS 文件
----------------------------------------------------------------------------------------------
// Vite 项目中的静态文件处理
1. 可以直接导入图片
2. 支持在导入时路径上带参数
  1. url
    1. import test from './test?url'
       console.log(test) // 导入的是这个文件的路径(字符串),而不是这个文件里面的导出对象
  2. raw
    1. import test from './test?raw'
       console.log(test) // 导入的是这个文件里面的内容(字符串),而不是这个文件里面的导出对象
  3. worker
3. 支持直接导入 json 文件,还可以解构
----------------------------------------------------------------------------------------------
// Vite 项目中集成 eslint 和 prettier
1. eslint
 1. 安装 'eslint-config-standard''eslint-plugin-import''eslint-plugin-promise''eslint-plugin-node'
 2..eslintrc.js 中
    module.exports = {
      extends: 'standard'
    }
2. prettier
  1..prettierrc 文件中配置,在 VSCode 开启保存格式化
----------------------------------------------------------------------------------------------
// Vite 项目中的环境变量
1. import.meta.env 获取环境变量
  1. MODE // 当前属于哪个环境,prod 或 dev,string 类型
  2. BASE_URL // 根路径
  3. PROD // 当前是否是 prod 环境,boolean 类型
  4. DEV // 当前是否是 dev 环境,boolean 类型
  5. SSR // 当前是否是服务端渲染环境,boolean 类型
2. 在 build 打包完成后的代码中是不存在 import.meta.env 的代码,Vite 会执行替换成对应的对象或值
3. 在根目录上可以新增 .env 文件,在里面可以自定义环境变量,key 需要以 VITE_ 开头
   如 VITE_NAME=chenjie,通过 import.meta.env.VITE_NAME 获取
4. 在根目录上的 .env 文件命名可以支持环境和模块
   1. 环境:.env.development、.env.production
   2. 模式:.env.test,这种需要在脚本命令中传入 mode 属性,如 'dev': 'vite --mode test'
5. 如果使用了 TS,需要在 tsconfig.json 中 'compilerOptions' 下新增 'types: ['vite/client']' 得到语法提示
   可以在 'vite-env.d.ts' 中声明自定义的环境变量类型,就可以在获取时得到语法提示
   interface ImportMetaEnv {
     VITE_NAME: string
   }
----------------------------------------------------------------------------------------------
// Vite 项目中的 HMR
1. import.meta.hot 获取热更新模块
2. 使用 Vite 创建的普通项目如何使用 hot?
  // main.js
  export function render() {
    document.querySelector('#app').innerHTML(`
      <h1>hello</h1>
    `)
  }
  render()
  // 使用 vite build 后 import.meta.hot 是 undefined
  if (import.meta.hot) {
    import.meta.hot.accept((newModule) => {
      // 接收到的 newModule 中会有这个 main.js 中的函数,即 render()
      newModule.render()
    })
  }
3. HMR 原理:通过 WebSocket 实现,当某个文件发生变化,会向浏览器推送消息,浏览器重新请求文件实现更新
----------------------------------------------------------------------------------------------
// Vite 项目中的 glob-import 批量导入
1. 通过 import.meta.glob('./url/*') 可以异步批量导入 url 下的所有导出文件
   但是导入的是引用函数,并未真正导入,需要自己手动执行导入的函数
   const globMoudules = import.meta.glob('./url/*')
   Object.entries(globMoudules).forEach(([k, v]) => {
     v().then(m => console.log(m))
   })
2. 通过 import.meta.globEager('./url/*') 可以同步批量导入 url 下的所有导出文件
   和 1 的区别就是不需要自己手动执行引用函数,同步导入,
3. 在导入的 url 中可以写对应的正则去匹配,不止 * 这个功能
4. 这是 Vite 提供的功能,而不是 ESM,注意只能在 Vite 项目中使用,其原理是使用了 'fast-glob' 这个库
----------------------------------------------------------------------------------------------
// Vite 项目中的预编译
1. Vite 在启动时会把非 ESM 的文件转换成 ESM 文件
2. 如果遇到使用模块时提示没有通过 ESM 导出,则需要在 vite.config.js 中的 optimizeDepas 下进行配置
  // vite.config.js
  import { defineConfig } from 'vite'
  import vue from '@vitejs/plugin-vue'
  export default defineConfig({
    plugins: [vue()],
    optimizeDeps: {
      include: ['moduleNmae']
    }
  })
3. Vite 对于像 lodash 这种库会编译优化成一个文件,lodash 库中的文件很多个
   如果不编译成一个文件,会导致当引入 lodash 时,在浏览器上会一下子请求很多个
   lodash 中的各种方法文件
----------------------------------------------------------------------------------------------
// Vite 集成到非 Node 服务的项目中
1. 在一些老项目中,前端页面是由服务端渲染的,而如果想在这种老项目中集成 Vite 开发
   可以在服务端的模板引擎中引入对应的 Vite 启动的服务,从而渲染由 Vite 开发的前端项目
   正常这种情况,前端代码会放在服务端代码的目录下
  1. 开发环境
    // test.pug 服务端的模板引擎
    html
      head
        title=title // title 是变量
      body
        h1=message // message 是变量
        div(id="app")
        script(src="http://localhost:3001/@vite/client" type="module") // @vite/client 是 Vite 服务
        script(src="http://localhost:3001/src/main.js" type="module") // main.js
  2. 正式环境,在 vite.config.js 下需要新增 build 配置,在打包后的 dist 目录下会新增 manifest.json 文件
     然后服务端再去读取这个文件下打包好的文件路径传入模板中渲染
    // vite.config.js
    import { defineConfig } from 'vite'
    import vue from '@vitejs/plugin-vue'
    export default defineConfig({
      plugins: [vue()],
      build: {
        manifest: true
      }
    })
    // test.pug 服务端的模板引擎
    html
      head
        title=title // title 是变量
      body
        h1=message // message 是变量
        div(id="app")
        // vendor 和 index 是变量,这里是服务端从 dist 目录下的 manifest.json 文件中读取的打包好的文件路径
        // vendor 相当于开发环境的 @vite/client,而 index 相当于开发环境的 main.js
        script(src=vendor)
        script(src=index)
----------------------------------------------------------------------------------------------
// Vite 集成到 Node 服务的项目中
1. 在前端项目中可以创建一个 server.js 文件,里面可以引入 express 和 vite,相当于启动了 vite 的 devServer
   createViteServer 传入的 server 参数下的 middlewareMode 是 'html',运行 node server.js
  // server.js
  const express = require('express')
  const app = express()
  const { createServer: createViteServer } = require('vite')
  createViteServer({
    server: { middlewareMode: 'html' }
  }).then(vite => {
    app.use(viet.middlewares)
    app.listen(4000)
  })
2. ssr
  1. 开发环境(react),运行 node server.js
    // server-entry.jsx
    import ReactDOMServer from 'react-dom/server'
    import { StaticRouter } from 'react-router-dom'
    import { App } from './App'
    export function render(url, context) {
      return ReactDOMServer.renderToString(
        <StaticRouter location={url} context={context}>
        <App />
        </StaticRouter>
      )
    }
    // server.js
    const express = require('express')
    const app = express()
    const { createServer: createViteServer } = require('vite')
    createViteServer({
      server: { middlewareMode: 'ssr' }
    }).then(vite => {
      app.use(viet.middlewares)
      app.get('*', async (req, res) => {
        let template = fs.readFileSync('index.html', 'utf-8')
        template = await vite.transformIndexHtml(req.url, template)
        const { render } = await vite.ssrLoadModule('/src/server-entry.jsx')
        const html = render(req.url)
        const responseHtml = template.replace('在 html 中自定义内容占位,用于插入 html', html)
        res.set('content-type', 'text/html').send(responseHtml)
      })
      app.listen(4000)
    })
  2. 生产环境(React),修改 package.json 的 build 命令,执行 npm run build
    // package.json
    "build": "npm run build:client && npm run build:server"
    "build:client": "vite build --outDir dist/client"
    "build:server": "vite build --outDir dist/server --ssr src/server-entry.jsx"
    // server-entry.jsx
    import ReactDOMServer from 'react-dom/server'
    import { StaticRouter } from 'react-router-dom'
    import { App } from './App'
    export function render(url, context) {
      return ReactDOMServer.renderToString(
        <StaticRouter location={url} context={context}>
        <App />
        </StaticRouter>
      )
    }
    // server.js
    const express = require('express')
    const app = express()
    app.user(express.static('dist/client'))
    const template = fs.readFileSync('dist/client/index.html')
    app.get('*', (req, res) => {
      const render = require('./dist/server/server-entery').render
      const context = {} // 这个和框架有关系,如 Vue 和 React 中处理不一样
      const html = render(req.url, context)
      if (context.url) {
        res.redirect(301, context.url)
        return
      }
      const responseHtml = template.replace('在 html 中自定义内容占位,用于插入 html', html)
    })
    app.listen(4000)
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
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
Last Updated: 2024/6/11 14:20:38
    飘向北方
    那吾克热-NW,尤长靖