vite介绍
什么是 Vite
借用作者的原话:
Vite,一个基于浏览器原生 ES imports 的开发服务器。利用浏览器去解析 imports,在服务器端按需编译返回,完全跳过了打包这个概念,服务器随起随用。同时不仅有 Vue 文件支持,还搞定了热更新,而且热更新的速度不会随着模块增多而变慢。针对生产环境则可以把同一份代码用 rollup 打包。虽然现在还比较粗糙,但这个方向我觉得是有潜力的,做得好可以彻底解决改一行代码等半天热更新的问题。
注意到两个点:
- 一个是 Vite 主要对应的场景是开发模式,原理是拦截浏览器发出的 ES imports 请求并做相应处理。(生产模式是用 rollup 打包)
- 一个是 Vite 在开发模式下不需要打包,只需要编译浏览器发出的 HTTP 请求对应的文件即可,所以热更新速度很快。
因此,要实现上述目标,需要要求项目里只使用原生 ES imports,如果使用了 require 将失效,所以要用它完全替代掉 Webpack 就目前来说还是不太现实的。上面也说了,生产模式下的打包不是 Vite 自身提供的,因此生产模式下如果你想要用 Webpack 打包也依然是可以的。从这个角度来说,Vite 可能更像是替代了 webpack-dev-server 的一个东西。
modules 模块
Vite 的实现离不开现代浏览器原生支持的模块功能。如下:
<script type="module">
import { a } from './a.js'
</script>
当声明一个 script
标签类型为 module
时,浏览器将对其内部的 import
引用发起 HTTP
请求获取模块内容。比如上述,浏览器将发起一个对 HOST/a.js
的 HTTP 请求,获取到内容之后再执行。
Vite 劫持了这些请求,并在后端进行相应的处理(比如将 Vue 文件拆分成 template
、style
、script
三个部分),然后再返回给浏览器。
由于浏览器只会对用到的模块发起 HTTP 请求,所以 Vite 没必要对项目里所有的文件先打包后返回,而是只编译浏览器发起 HTTP 请求的模块即可。这里是不是有点按需加载的味道?
编译和打包的区别
看到这里,可能有些朋友不免有些疑问,编译和打包有什么区别?为什么 Vite 号称「热更新的速度不会随着模块增多而变慢」?
简单举个例子,有三个文件 a.js
、b.js
、c.js
// a.js
const a = () => { ... }
export { a } // b.js
const b = () => { ... }
export { b }
// c.js
import { a } from './a'
import { b } from './b' const c = () => {
return a() + b()
} export { c }
如果以 c 文件为入口,那么打包就会变成如下(结果进行了简化处理):(假定打包文件名为 bundle.js
)
// bundle.js
const a = () => { ... }
const b = () => { ... }
const c = () => {
return a() + b()
} export { c }
值得注意的是,打包也需要有编译的步骤。
Webpack 的热更新原理简单来说就是,一旦发生某个依赖(比如上面的 a.js
)改变,就将这个依赖所处的 module
的更新,并将新的 module
发送给浏览器重新执行。由于我们只打了一个 bundle.js
,所以热更新的话也会重新打这个 bundle.js
。试想如果依赖越来越多,就算只修改一个文件,理论上热更新的速度也会越来越慢。
而如果是像 Vite 这种只编译不打包会是什么情况呢?
只是编译的话,最终产出的依然是 a.js
、b.js
、c.js
三个文件,只有编译耗时。由于入口是 c.js
,浏览器解析到 import { a } from './a'
时,会发起 HTTP 请求 a.js
(b 同理),就算不用打包,也可以加载到所需要的代码,因此省去了合并代码的时间。
在热更新的时候,如果 a
发生了改变,只需要更新 a
以及用到 a
的 c
。由于 b
没有发生改变,所以 Vite 无需重新编译 b
,可以从缓存中直接拿编译的结果。这样一来,修改一个文件 a
,只会重新编译这个文件 a
以及浏览器当前用到这个文件 a
的文件,而其余文件都无需重新编译。所以理论上热更新的速度不会随着文件增加而变慢。
当然这样做有没有不好的地方?有,初始化的时候如果浏览器请求的模块过多,也会带来初始化的性能问题。不过如果你能遇到初始化过慢的这个问题,相信热更新的速度会弥补很多。当然我相信以后尤大也会解决这个问题。
Vite 运行 Web 应用的实现
上面说了这么多的铺垫,可能还不够直观,我们可以先跑一个 Vite 项目来实际看看。
按照官网的说明,可以输入如下命令(<project-name>
为自己想要的目录名即可)
$ npx create-vite-app <project-name>
$ cd <project-name>
$ npm install
$ npm run dev
如果一切都正常你将在 localhost:3000
(Vite 的服务器起的端口) 看到这个界面:
并得到如下的代码结构:
.
├── App.vue // 页面的主要逻辑
├── index.html // 默认打开的页面以及 Vue 组件挂载
├── node_modules
└── package.json
拦截 HTTP 请求
接下来开始说一下 Vite 实现的核心——拦截浏览器对模块的请求并返回处理后的结果。
我们知道,由于是在 localhost:3000
打开的网页,所以浏览器发起的第一个请求自然是请求 localhost:3000/
,这个请求发送到 Vite 后端之后经过静态资源服务器的处理,会进而请求到 /index.html
,此时 Vite 就开始对这个请求做拦截和处理了。
首先,index.html
里的源码是这样的:
<div id="app"></div>
<script type="module">
import { createApp } from 'vue'
import App from './App.vue' createApp(App).mount('#app')
</script>
但是在浏览器里它是这样的:
注意到什么不同了吗?是的, import { createApp } from 'vue'
换成了 import { createApp } from '/@modules/vue
。
这里就不得不说浏览器对 import
的模块发起请求时的一些局限了,平时我们写代码,如果不是引用相对路径的模块,而是引用 node_modules
的模块,都是直接 import xxx from 'xxx'
,由 Webpack 等工具来帮我们找这个模块的具体路径。但是浏览器不知道你项目里有 node_modules
,它只能通过相对路径去寻找模块。
因此 Vite 在拦截的请求里,对直接引用 node_modules
的模块都做了路径的替换,换成了 /@modules/
并返回回去。而后浏览器收到后,会发起对 /@modules/xxx
的请求,然后被 Vite 再次拦截,并由 Vite 内部去访问真正的模块,并将得到的内容再次做同样的处理后,返回给浏览器。
imports 替换
普通 JS import 替换
上面说的这步替换来自 src/node/serverPluginModuleRewrite.ts
:
// 只取关键代码:
// Vite 使用 Koa 作为内置的服务器
// 如果请求的路径是 /index.html
if (ctx.path === '/index.html') {
// ...
const html = await readBody(ctx.body)
ctx.body = html.replace(
/(<script\b[^>]*>)([\s\S]*?)<\/script>/gm, // 正则匹配
(_, openTag, script) => {
// also inject __DEV__ flag
const devFlag = hasInjectedDevFlag ? `` : devInjectionCode
hasInjectedDevFlag = true
// 替换 html 的 import 路径
return `${devFlag}${openTag}${rewriteImports(
script,
'/index.html',
resolver
)}</script>`
}
)
// ...
}
如果并没有在 script
标签内部直接写 import
,而是用 src
的形式引用的话如下:
<script type="module" src="/main.js"></script>
那么就会在浏览器发起对 main.js
请求的时候进行处理:
// 只取关键代码:
if (
ctx.response.is('js') &&
// ...
) {
// ...
const content = await readBody(ctx.body)
await initLexer
// 重写 js 文件里的 import
ctx.body = rewriteImports(
content,
ctx.url.replace(/(&|\?)t=\d+/, ''),
resolver,
ctx.query.t
)
// 写入缓存,之后可以从缓存中直接读取
rewriteCache.set(content, ctx.body)
}
替换逻辑 rewriteImports
就不展开了,用的是 es-module-lexer
来进行的语法分析获取 imports
数组,然后再做的替换。
*.vue 文件的替换
如果 import
的是 .vue
文件,将会做更进一步的替换:
原本的 App.vue
文件长这样:
<template>
<h1>Hello Vite + Vue 3!</h1>
<p>Edit ./App.vue to test hot module replacement (HMR).</p>
<p>
<span>Count is: {{ count }}</span>
<button @click="count++">increment</button>
</p>
</template> <script>
export default {
data: () => ({ count: 0 }),
}
</script> <style scoped>
h1 {
color: #4fc08d;
} h1, p {
font-family: Arial, Helvetica, sans-serif;
}
</style>
替换后长这样:
// localhost:3000/App.vue
import { updateStyle } from "/@hmr" // 抽出 script 逻辑
const __script = {
data: () => ({ count: 0 }),
} // 将 style 拆分成 /App.vue?type=style 请求,由浏览器继续发起请求获取样式
updateStyle("c44b8200-0", "/App.vue?type=style&index=0&t=1588490870523")
__script.__scopeId = "data-v-c44b8200" // 样式的 scopeId // 将 template 拆分成 /App.vue?type=template 请求,由浏览器继续发起请求获取 render function
import { render as __render } from "/App.vue?type=template&t=1588490870523&t=1588490870523"
__script.render = __render // render 方法挂载,用于 createApp 时渲染
__script.__hmrId = "/App.vue" // 记录 HMR 的 id,用于热更新
__script.__file = "/XXX/web/vite-test/App.vue" // 记录文件的原始的路径,后续热更新能用到
export default __script
这样就把原本一个 .vue
的文件拆成了三个请求(分别对应 script
、style
和template
) ,浏览器会先收到包含 script
逻辑的 App.vue
的响应,然后解析到 template
和 style
的路径后,会再次发起 HTTP 请求来请求对应的资源,此时 Vite 对其拦截并再次处理后返回相应的内容。
如下:
不得不说这个思路是非常巧妙的。
这一步的拆分来自 src/node/serverPluginVue.ts
,核心逻辑是根据 URL 的 query 参数来做不同的处理(简化分析如下):
// 如果没有 query 的 type,比如直接请求的 /App.vue
if (!query.type) {
ctx.type = 'js'
ctx.body = compileSFCMain(descriptor, filePath, publicPath) // 编译 App.vue,编译成上面说的带有 script 内容,以及 template 和 style 链接的形式。
return etagCacheCheck(ctx) // ETAG 缓存检测相关逻辑
} // 如果 query 的 type 是 template,比如 /App.vue?type=template&xxx
if (query.type === 'template') {
ctx.type = 'js'
ctx.body = compileSFCTemplate( // 编译 template 生成 render function
// ...
)
return etagCacheCheck(ctx)
} // 如果 query 的 type 是 style,比如 /App.vue?type=style&xxx
if (query.type === 'style') {
const index = Number(query.index)
const styleBlock = descriptor.styles[index]
const result = await compileSFCStyle( // 编译 style
// ...
)
if (query.module != null) { // 如果是 css module
ctx.type = 'js'
ctx.body = `export default ${JSON.stringify(result.modules)}`
} else { // 正常 css
ctx.type = 'css'
ctx.body = result.code
}
}
@modules/* 路径解析
上面只涉及到了替换的逻辑,解析的逻辑来自 src/node/serverPluginModuleResolve.ts
。这一步就相对简单了,核心逻辑就是去 node_modules
里找有没有对应的模块,有的话就返回,没有的话就报 404:(省略了很多逻辑,比如对 web_modules
的处理、缓存的处理等)
// ...
try {
const file = resolve(root, id) // id 是模块的名字,比如 axios
return serve(id, file, 'node_modules') // 从 node_modules 中找到真正的模块内容并返回
} catch (e) {
console.error(
chalk.red(`[vite] Error while resolving node_modules with id "${id}":`)
)
console.error(e)
ctx.status = 404 // 如果没找到就 404
}
Vite 热更新的实现
上面已经说完了 Vite 是如何运行一个 Web 应用的,包括如何拦截请求、替换内容、返回处理后的结果。接下来说一下 Vite 热更新的实现,同样实现的非常巧妙。
我们知道,如果要实现热更新,那么就需要浏览器和服务器建立某种通信机制,这样浏览器才能收到通知进行热更新。Vite 的是通过 WebSocket
来实现的热更新通信。
客户端
客户端的代码在 src/client/client.ts
,主要是创建 WebSocket
客户端,监听来自服务端的 HMR 消息推送。
Vite 的 WS 客户端目前监听这几种消息:
connected
: WebSocket 连接成功vue-reload
: Vue 组件重新加载(当你修改了 script 里的内容时)vue-rerender
: Vue 组件重新渲染(当你修改了 template 里的内容时)style-update
: 样式更新style-remove
: 样式移除js-update
: js 文件更新full-reload
: fallback 机制,网页重刷新
其中针对 Vue 组件本身的一些更新,都可以直接调用 HMRRuntime
提供的方法,非常方便。其余的更新逻辑,基本上都是利用了 timestamp
刷新缓存重新执行的方法来达到更新的目的。
核心逻辑如下,我感觉非常清晰明了:
import { HMRRuntime } from 'vue' // 来自 Vue3.0 的 HMRRuntime console.log('[vite] connecting...') declare var __VUE_HMR_RUNTIME__: HMRRuntime const socket = new WebSocket(`ws://${location.host}`) // Listen for messages
socket.addEventListener('message', ({ data }) => {
const { type, path, id, index, timestamp, customData } = JSON.parse(data)
switch (type) {
case 'connected':
console.log(`[vite] connected.`)
break
case 'vue-reload':
import(`${path}?t=${timestamp}`).then((m) => {
__VUE_HMR_RUNTIME__.reload(path, m.default)
console.log(`[vite] ${path} reloaded.`) // 调用 HMRRUNTIME 的方法更新
})
break
case 'vue-rerender':
import(`${path}?type=template&t=${timestamp}`).then((m) => {
__VUE_HMR_RUNTIME__.rerender(path, m.render)
console.log(`[vite] ${path} template updated.`) // 调用 HMRRUNTIME 的方法更新
})
break
case 'style-update':
updateStyle(id, `${path}?type=style&index=${index}&t=${timestamp}`) // 重新加载 style 的 URL
console.log(
`[vite] ${path} style${index > 0 ? `#${index}` : ``} updated.`
)
break
case 'style-remove':
const link = document.getElementById(`vite-css-${id}`)
if (link) {
document.head.removeChild(link) // 删除 style
}
break
case 'js-update':
const update = jsUpdateMap.get(path)
if (update) {
update(timestamp) // 用新的时间戳加载并执行 js,达到更新的目的
console.log(`[vite]: js module reloaded: `, path)
} else {
console.error(
`[vite] got js update notification but no client callback was registered. Something is wrong.`
)
}
break
case 'custom':
const cbs = customUpdateMap.get(id)
if (cbs) {
cbs.forEach((cb) => cb(customData))
}
break
case 'full-reload':
location.reload()
}
})
服务端
服务端的实现位于 src/node/serverPluginHmr.ts
。核心是监听项目文件的变更,然后根据不同文件类型(目前只有 vue
和 js
)来做不同的处理:
watcher.on('change', async (file) => {
const timestamp = Date.now() // 更新时间戳
if (file.endsWith('.vue')) {
handleVueReload(file, timestamp)
} else if (file.endsWith('.js')) {
handleJSReload(file, timestamp)
}
})
对于 Vue
文件的热更新而言,主要是重新编译 Vue
文件,检测 template
、script
、style
的改动,如果有改动就通过 WS 服务端发起对应的热更新请求。
简单的源码分析如下:
async function handleVueReload(
file: string,
timestamp: number = Date.now(),
content?: string
) {
const publicPath = resolver.fileToRequest(file) // 获取文件的路径
const cacheEntry = vueCache.get(file) // 获取缓存里的内容 debugHmr(`busting Vue cache for ${file}`)
vueCache.del(file) // 发生变动了因此之前的缓存可以删除 const descriptor = await parseSFC(root, file, content) // 编译 Vue 文件 const prevDescriptor = cacheEntry && cacheEntry.descriptor // 获取前一次的缓存 if (!prevDescriptor) {
// 这个文件之前从未被访问过(本次是第一次访问),也就没必要热更新
return
} // 设置两个标志位,用于判断是需要 reload 还是 rerender
let needReload = false
let needRerender = false // 如果 script 部分不同则需要 reload
if (!isEqual(descriptor.script, prevDescriptor.script)) {
needReload = true
} // 如果 template 部分不同则需要 rerender
if (!isEqual(descriptor.template, prevDescriptor.template)) {
needRerender = true
} const styleId = hash_sum(publicPath)
// 获取之前的 style 以及下一次(或者说热更新)的 style
const prevStyles = prevDescriptor.styles || []
const nextStyles = descriptor.styles || [] // 如果不需要 reload,则查看是否需要更新 style
if (!needReload) {
nextStyles.forEach((_, i) => {
if (!prevStyles[i] || !isEqual(prevStyles[i], nextStyles[i])) {
send({
type: 'style-update',
path: publicPath,
index: i,
id: `${styleId}-${i}`,
timestamp
})
}
})
} // 如果 style 标签及内容删掉了,则需要发送 `style-remove` 的通知
prevStyles.slice(nextStyles.length).forEach((_, i) => {
send({
type: 'style-remove',
path: publicPath,
id: `${styleId}-${i + nextStyles.length}`,
timestamp
})
}) // 如果需要 reload 发送 `vue-reload` 通知
if (needReload) {
send({
type: 'vue-reload',
path: publicPath,
timestamp
})
} else if (needRerender) {
// 否则发送 `vue-rerender` 通知
send({
type: 'vue-rerender',
path: publicPath,
timestamp
})
}
}
对于热更新 js
文件而言,会递归地查找引用这个文件的 importer
。比如是某个 Vue
文件所引用了这个 js
,就会被查找出来。假如最终发现找不到引用者,则会返回 hasDeadEnd: true
。
const vueImporters = new Set<string>() // 查找并存放需要热更新的 Vue 文件
const jsHotImporters = new Set<string>() // 查找并存放需要热更新的 js 文件
const hasDeadEnd = walkImportChain(
publicPath,
importers,
vueImporters,
jsHotImporters
)
如果 hasDeadEnd
为 true
,则直接发送 full-reload
。如果 vueImporters
或 jsHotImporters
里查找到需要热更新的文件,则发起热更新通知:
if (hasDeadEnd) {
send({
type: 'full-reload',
timestamp
})
} else {
vueImporters.forEach((vueImporter) => {
send({
type: 'vue-reload',
path: vueImporter,
timestamp
})
})
jsHotImporters.forEach((jsImporter) => {
send({
type: 'js-update',
path: jsImporter,
timestamp
})
})
}
客户端逻辑的注入
写到这里,还有一个问题是,我们在自己的代码里并没有引入 HRM
的 client
代码,Vite 是如何把 client
代码注入的呢?
回到上面的一张图,Vite 重写 App.vue
文件的内容并返回时:
注意这张图里的代码区第一句话 import { updateStyle } from '/@hmr'
,并且在左侧请求列表中也有一个对 @hmr
文件的请求。这个请求是啥呢?
可以发现,这个请求就是上面说的客户端逻辑的 client.ts
的内容。
在 src/node/serverPluginHmr.ts
里,有针对 @hmr
文件的解析处理:
export const hmrClientFilePath = path.resolve(__dirname, './client.js')
export const hmrClientId = '@hmr'
export const hmrClientPublicPath = `/${hmrClientId}` app.use(async (ctx, next) => {
if (ctx.path !== hmrClientPublicPath) { // 请求路径如果不是 @hmr 就跳过
return next()
}
debugHmr('serving hmr client')
ctx.type = 'js'
await cachedRead(ctx, hmrClientFilePath) // 返回 client.js 的内容
})
至此,热更新的整体流程已经解析完毕。
vite修改项目端口方法:根目录新建vite.config.js,配置server.port,文档:https://vitejs.dev/config/#server-port
vite与webpack区别
webpack会先打包,然后启动开发服务器,请求服务器时直接给予打包结果。
而vite是直接启动开发服务器,请求哪个模块再对该模块进行实时编译。
由于现代浏览器本身就支持ES Module,会自动向依赖的Module发出请求。vite充分利用这一点,将开发环境下的模块文件,就作为浏览器要执行的文件,而不是像webpack那样进行打包合并。
由于vite在启动的时候不需要打包,也就意味着不需要分析模块的依赖、不需要编译,因此启动速度非常快。当浏览器请求某个模块时,再根据需要对模块内容进行编译。这种按需动态编译的方式,极大的缩减了编译时间,项目越复杂、模块越多,vite的优势越明显。
在HMR(热更新)方面,当改动了一个模块后,仅需让浏览器重新请求该模块即可,不像webpack那样需要把该模块的相关依赖模块全部编译一次,效率更高。
当需要打包到生产环境时,vite使用传统的rollup(也可以自己手动安装webpack来)进行打包,因此,vite的主要优势在开发阶段。另外,由于vite利用的是ES Module,因此在代码中(除了vite.config.js里面,这里是node的执行环境)不可以使用CommonJS。
介绍ES Module
顾名思义这是ES提供的模块化规则,主要有
import
、exports
、export default
,这三个方法的具体功能和用法可以查看es6官方文档【module的语法】,我这里主要说明一些注意的知识点。
1. import 异步导入模块,在js解析阶段进行
2. import() 按需导入模块,可在函数中进行,缓解加载缓慢的问题
3. export {} 导出数据,此处的 {} 不是对象,而是一种约定的符号,用于接收数据,所以不能用es的语法糖
4. 在js引擎中,这里有一个模块环境记录的处理,bind导出的字段,类似于: const name = name(name为导出的字段); 如果在导出文件中,后续异步修改了name,在导入文件中拿到的name是最新的name值,就是因为export的内部结构;
既然已经有了 Webpack,尤雨溪为啥再整一个 Vite呢?
webpack 无法避免的问题:
- 本地开发环境webpack也是需要先打包,然后服务器运行的是打包后的文件,所以代码量很大的项目就会有启服务很慢的现象,
- 热更新:Webpack 的热更新会以当前修改的文件为入口重新 build 打包,所有涉及到的依赖也都会被重新加载一次。虽然webpack 也采用的是局部热更新并且是有缓存机制的,但是还是需要重新打包所以很大的代码项目是真的有卡顿的现象(亲身经历,例如集成很多子平台的大型项目)
具有了快速冷启动、按需编译、模块热更新的 Vite
Vite 通过在一开始将应用中的模块区分为 依赖 和 源码 两类,改进了开发服务器启动时间。
- 依赖预构建:依赖 大多为在开发时不会变动的纯 JavaScript。一些较大的依赖(例如有上百个模块的组件库)处理的代价也很高。依赖也通常会存在多种模块化格式(例如 ESM 或者 CommonJS)。Vite 将会使用 esbuild 预构建依赖。Esbuild 使用 Go 编写,并且比以 JavaScript 编写的打包器预构建依赖快 10-100 倍。参考文章:zhuanlan.zhihu.com/p/379164359
这个过程有两个目的:
CommonJS 和 UMD 兼容性: 开发阶段中,Vite 的开发服务器将所有代码视为原生 ES 模块
Vite 将有许多内部模块的 ESM 依赖关系转换为单个模块,以提高后续页面加载性能。
快速冷启动:只启动一台静态页面的服务器,对文件代码不打包,服务器会根据客户端的请求加载不同的模块处理(利用的是浏览器对esMoudle的原生支持),所以节省了webpack 那一套打包转化封装的逻辑。所以大型项目不会再出现热更新卡顿,起服务慢的情况(理论上,尚未找到合适项目实践)
与其它非打包解决方案比较
按需编译、模块热更新:采用立即编译当前修改文件的办法。同时 vite 还会使用缓存机制( http 缓存 => vite 内置缓存 )是基于缓存的热更新。
文件缓存:Vite 会将预构建的依赖缓存到
node_modules/.vite
。它根据几个源来决定是否需要重新运行预构建步骤:package.json
中的dependencies
列表, package-lock等浏览器缓存:解析后的依赖请求会以 HTTP 头
max-age=31536000,immutable
强缓存,以提高在开发时的页面重载性能。一旦被缓存,这些请求将永远不会再到达开发服务器
后续有兴趣,继续深入,可以了解一下js 模块化方案,commonJs等。
ES6
之前,JS一直没有自己的模块体系
,这一点对于大型项目的开发很不友好,所以社区出现了CommonJS
和AMD
(本人不熟悉),CommonJS
主要是用于服务器(Node
),AMD
主要是用于浏览器
。
但是ES6引入了ESM
,到此,JS终于有了自己的模块体系
,基本上可以完全取代
CJS和AMD。
以上。
vite介绍的更多相关文章
- 【记录】如何造一个vite插件(1)
在看文章前,先做个定位,这不是一篇纯粹的技术性文章,可以把它理解成一个叙述文章,记录我开发插件的过程. 开始前简单的吹个牛 vue2 也写了很多年了,多人合作始终避不开用到别人的组件.关键是有些组件没 ...
- 使用Vite快速构建Vue3+ts+pinia脚手架
一.前言 vue3的快速更新,很多IT发展快的地区在22开始都已经提上日程,小编所在的青岛好像最近才有点风波.vue3的人才在青岛还是比较稀缺的哈,纯属小编自己的看法,可能小编是个井底之蛙!! vue ...
- webpack 项目接入Vite的通用方案介绍(上)
愿景 希望通过本文,能给读者提供一个存/增量项目接入Vite的点子,起抛砖引玉的作用,减少这方面能力的建设成本 在阐述过程中同时也会逐渐完善webpack-vite-serve这个工具 读者可直接fo ...
- vite + ts 快速搭建 vue3 项目 以及介绍相关特性
博客地址:https://ainyi.com/98 Vue3.0,One Piece 接下来得抽空好好学习了 vite 尤大在 Vue 3.0 beta 直播中推荐了 vite 的工具,强调:针对Vu ...
- 蒲公英 · JELLY技术周刊 Vol.12 尤雨溪新作 Vite, 你会支持么?
「蒲公英」期刊,每周更新,我们专注于挖掘「基础技术.工程化.跨端框架技术.图形编程.服务端开发.桌面开发.人工智能」等多个大方向的业界热点,并加以专业的解读:不仅如此,我们还精选凹凸技术文章,向大家呈 ...
- 简单介绍下各种 JavaScript 解析器
作者:沧海 各种js解析器是前端工程化的基石,可以说如果没有它,很多工程化都无法正常执行,我们每天用到的babel.webpack.eslint.TypeScript背后都需要一套对应的js解析器,今 ...
- 如何在 Vite 中使用 Element UI + Vue 3
在上篇文章<2021新年 Vue3.0 + Element UI 尝鲜小记>里,我们尝试使用了 Vue CLI 创建 Vue 3 + Element UI 的项目,而 Vue CLI 实际 ...
- Vue3教程:Vue 3 + Element Plus + Vite 2 的后台管理系统开源啦
之前发布过一篇文章<Vue3教程:开发一个 Vue 3 + element-plus 的后台管理系统>,文中提到会开发并开源一个 Vue 3 + Element Plus 的项目供大家练手 ...
- vite 动态 import 引入打包报错解决方案
关注公众号: 微信搜索 前端工具人 ; 收货更多的干货 原文链接: 自己掘金文章 https://juejin.cn/post/6951557699079569422/ 关注公众号: 微信搜索 前端工 ...
- antd+react项目迁移vite的解决方案
antd+react+webpack往往是以react技术栈为主的前端项目的标准组合,三者都有成熟的生态和稳定的表现,但随着前端圈的技术不断革新,号称下一代构建平台vite2的发布,webpack似乎 ...
随机推荐
- 【Mybatis】学习
Mybatis 学习 环境搭建 pom.xml <!--log4j--> <dependency> <groupId>org.slf4j</groupId&g ...
- keycloak~AbstractJsonUserAttributeMapper的作用
AbstractJsonUserAttributeMapper 它是一个抽象类,用来更新条件更新用户属性(user_attribute)的信息,我们在实现自己的mapper时,需要关注3个方法,下面分 ...
- PlayWright(二十)- Pytest之conftest文件
1.介绍与使用场景 conftest.py 这个是什么呢? 顾名思义,他就是一个文件,那这个文件是干什么用的呢? 在我们上文中,用了fixture函数是直接在用例的文件里定义的,那不能我们所有 ...
- struct 结构体分析
struct分析 1.无成员的空结构体size为 1byte 2.通过/zp可以调整对齐值,默认是8字节 //设编译对齐设定值为Zp //设成员变量的类型为 member type //设成员变量在结 ...
- (转)[Android测试] Android Studio+Appium+Java+Windows 自动化测试之二:Appium环境安装搭建
一.需要下载安装的东西 1. 文件下载 网上也有挺多安装教程的,这里我提供我的安装方法.Win10 64位.一些文件我在后面打包. 2016.9.12号本人安装记录. SDK: 由于我是搞Androi ...
- dBeaver操作iotdb并实现导入和导出
1.windows下操作iotdb,现在官网下载相关的iotdb包 官网地址:https://archive.apache.org/dist/iotdb/ 一般建议下载 -all的 2.打开db ...
- 11、Mybatis之逆向工程
11.1.正向与逆向工程概述 正向工程:先创建Java实体类,由框架负责根据实体类生成数据库表:例如Hibernate是支持正向工程的. 逆向工程:先创建数据库表,由框架负责根据数据库表,反向生成Ja ...
- typora使用教程&高级用法&Markdown
typora使用教程&高级用法&Markdown typora介绍 哇啦哇啦哇啦哇,,,,,,,,,,,,, 提示:小白看不懂的话,建议哔哩哔哩搜索"遇见狂神说", ...
- Web应用防火墙--规则防护
一.什么是Web应用防火墙? Web应用防火墙对网站.APP的业务流量安全及合规性保护,对业务流量的识别恶意特征提取.分析识别出恶意流量并进行处理, 将正常安全的流量回源到业务服务器, 保护网站核心业 ...
- 工作中常用的一些Git骚操作,一般人我不告诉他。
一.Git提交代码 1 git pull 从服务器上拉取代码 2 git status 查看文件的状态 3 git add . 添加所有文件到暂存区 4 git commit -m "提交的 ...