随着 vue3.0 的公布,为 vue.js 带来了如下变动:
- 组合式 api
- 响应式优化
- 编译优化
- 源码体积优化
变动
组合式 api
在 vue3.0 之前的版本中应用申明式 api,尽管语法上清晰简略,然而带来了问题,即:
- 实现某个性能的代码散布在申明的各个变量中,导致实现多个性能的代码糅合在一起,不利于代码复用,尤其是开发大型项目。
- 尽管提供了 mixin 用于代码复用,然而也会带来问题,如命名抵触等。
组合式 api 就能够解决这些问题,能够通过一系列 api 将实现某个性能的代码放到一起,从而能够实现代码复用。
如下例子中,页面会捕获用户鼠标地位,能够将捕获鼠标地位的代码提取到一个公共办法中,当须要应用的时候,间接引入代码即可:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<div id="app">
<div>
X: {{position.x}} <br />
Y: {{position.y}}
</div>
</div>
<script type="module">
import {createApp, reactive, onMounted, onUnmounted} from './node_modules/vue/dist/vue.esm-browser.js'
// 共用的监控鼠标地位办法
function useMousePosition() {
const position = reactive({
x: 0,
y: 0
})
const handler = (e) => {
position.x = e.pageX
position.y = e.pageY
}
onMounted(() => {window.addEventListener('mousemove', handler)
})
onUnmounted(() => {window.removeEventListener('mousemove', handler)
})
return {position}
}
createApp({setup() {
// 在须要应用的 vue 组件中引入即可
return {...useMousePosition()
}
}
}).mount('#app')
</script>
</body>
</html>
响应式优化
家喻户晓,vue2.0 中应用 Object.defineProperty 增加数据监听,然而其存在如下问题:
- 初始化的时候会深度遍历对象属性,为每一个属性增加监听。
- 无奈监听动静增加,删除属性。
- 无奈监听数组的通过.length 和 [0] 的扭转。
而 3.0 中,应用 Proxy 对象重写响应式零碎,解决了上述问题,从而晋升了响应式零碎性能。
编译优化
在 vue 我的项目编译的时候,会将 template 编译成 render 函数,在编译原理的文章中说过,vue2.0 会标记动态根节点,在虚构 dom 的 diff 过程中会跳过动态根节点的比照,从而优化编译速度。
而在 vue3.0 中又增加了如下内容用于优化编译过程:
- 动态晋升。
- Patch Flag。
- 缓存事件处理函数。
- Fragments(片段)。
在上面具体阐明过程中会应用 https://vue-next-template-explorer.netlify.app 这个站点用于查看 template 编译后的 render 函数。
应用的 template 案例如下:
<div id="app">
<div>
static root
<div>static node</div>
</div>
<div>static node</div>
<div :id="id">{{count}}</div>
<button @click="handler">button</button>
</div>
该例子中蕴含动态节点,动态根节点,属性绑定,插值表达式和绑定事件。
动态晋升
当没有启用动态晋升的时候,template 被编译成的 render 函数如下:
其中,红色框标出的是动态节点内容。
当启用动态晋升后,编译后果如下:
能够看出,原来 render 函数中的动态节点都被提取到 render 函数的里面,这样就导致,动态节点只有在初始化的时候被编译一次,后续发生变化后,动态节点会复用初始化时编译的后果,从而晋升编译性能。
patch flag
在编译的后果中能够看出,在编译生成的动静节点中,_createVNode 办法最初会蕴含一个数字,如 9,此数字就是 patch flag,9 代表的是该节点的 text 和 props 中均存在动静绑定的内容:
而如果咱们去掉模版中的:id=”id” 属性绑定,其编译后果如下:
此时 flag 变为 1,示意该节点只有 text 被动静绑定,那么在虚构 Dom 的 patch 过程中,就能够跳过节点比照,间接比对其蕴含的 text 内容,从而晋升编译速度。
缓存事件处理函数
在模版中绑定了 click 事件,其编译后果如下:
此时,默认认为该节点属性中绑定了某个动态数据,所以当事件处理函数发生变化的时候,会登程页面的更新。
如果开启事件缓存,其编译后果如下:
在初始化编译的时候,其外部用一个函数包裹了绑定事件并增加到缓存中,当后续变动的时候,会间接从缓存中读取相应的处理函数,此时放慢了编译速度,同时,因为新生成了一个函数包裹事件处理函数,不论处理函数是否发生变化,包裹得函数是不会变动的,此时也就不会因为处理函数的变动导致页面更新。
fragments
在 vue2.0 中,template 模版只能存在一个根节点,而 3.0 中通过 fragments 实现了能够存在多个同级根节点。
在例子中,只存在一个根节点 div,其编译后的后果如下:
能够看出,生成的 render 办法中是应用_createBlock 函数来解决根节点,从而将 template 转换为一个树形数据。
当咱们删除例子中的根节点之后,其编译的后果如下:
此时,_createBlock 函数传入_Fragment 内置组件作为根节点。
源码体积优化
vue3.0 中通过如下两个方面优化了源码体积:
- 移除了一些不罕用的 api:inline-template, filter 等。
- 更好的 tree-shaking,除了根底代码之外,如各种 api 都是按需加载的。
响应式原理
vue3.0 将响应式局部独自提取成一个包 reactivity,其蕴含了所有响应式相干的代码,能够独自应用:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<script type="module">
import {reactive, effect} from './node_modules/@vue/reactivity/dist/reactivity.esm-browser.js'
// 创立响应式数据
const product = reactive({
name: 'iPhone',
price: 5000,
count: 3
})
let total = 0
// 增加变动监听
effect(() => {total = product.price * product.count})
console.log(total)
product.price = 4000
console.log(total)
product.count = 1
console.log(total)
</script>
</body>
</html>
波及到两个要害函数:
- reactive:将对象转换为响应式数据。
- effect:注册监听变动函数,当 product 的 price 和 count 属性发生变化的时候会登程该函数执行。
后续会通过模仿 reactivity 包的实现来阐明 vue3.0 中响应式实现外围原理。
reactive
在 reactive 函数中,将为对象增加 Proxy 代理,并在 get 办法中增加监听,在 set 和 delete 办法中触发变动。
const isObject = val => val !== null && typeof val === 'object'
const convert = target => isObject(target) ? reactive(target) : target
const hasOwnProperty = Object.prototype.hasOwnProperty
const hasOwn = (target, key) => hasOwnProperty.call(target, key)
export function reactive(target) {
// 当 target 不是对象的时候,间接返回 target
if (!isObject(target)) return target
const handler = {get(target, key, receiver) {
// 收集依赖
const result = Reflect.get(target, key, receiver)
// 对象的某个属性有可能还是对象,此时须要为该属性增加响应式
return convert(result)
},
set(target, key, value, receiver) {const oldValue = Reflect.get(target, key, receiver)
let result = true
if (oldValue !== value) {result = Reflect.set(target, key, value, receiver)
// 触发更新
}
return result
},
deleteProperty(target, key) {const hadKey = hasOwn(target, key)
const result = Reflect.deleteProperty(target, key)
if (hadKey && result) {
// 触发更新
trigger(target, key)
}
return result
}
}
return new Proxy(target, handler)
}
effect
effect 函数用于为数据变动增加响应操作。整个响应式收集过程可用下图示意:
整个依赖收集过程会波及三个数据:
- targetMap: WeakMap,示意一个响应式对象对应一个 depsMap。
- depsMap:Map,示意响应式对象上的属性 key 对应一个 dep。
- dep:Set,示意一系列响应操作。
实现 effect:
let activeEffect = null
export function effect (callback) {
activeEffect = callback
callback() // 拜访响应式对象属性,去收集依赖
activeEffect = null
}
其外部首先会将响应函数存储到全局对象 activeEffect 中,而后调用响应函数,因为响应函数中存在相似 production.price 调用,此时会触发 get 办法,在 get 办法中就能够将全局的 activeEffect 保留到相应的 dep 汇合中。
track
track 办法用于收集依赖:
let targetMap = new WeakMap()
export function track (target, key) {if (!activeEffect) return
let depsMap = targetMap.get(target)
if (!depsMap) {targetMap.set(target, (depsMap = new Map()))
}
let dep = depsMap.get(key)
if (!dep) {depsMap.set(key, (dep = new Set()))
}
dep.add(activeEffect)
}
此办法就是依据 target 获取 depsMap,而后依据 key 获取相应 dep,再而后将全局的 activeEffect 对象增加到 dep 汇合中。
定义实现 track 办法后,须要批改 reactive 办法收集依赖局部:
// 收集依赖
track(target, key)
trigger
trigger 函数用于触发更新:
export function trigger (target, key) {const depsMap = targetMap.get(target)
if (!depsMap) return
const dep = depsMap.get(key)
if (dep) {
dep.forEach(effect => {effect()
})
}
}
此办法就是依据 target 和 key 找到相应的 dep,而后循环调用 dep 数组中的响应办法。
定义实现 trigger 函数后,须要批改 reactive 函数中 get,delete 办法中触发更新局部:
// 触发更新
trigger(target, key)
通过上述几个函数,就能够实现根本的 vue3.0 响应过程。
ref
ref 函数和 reactive 函数相似,只不过其是将根本数据类型包装成响应式对象:
export function ref (raw) {
// 判断 raw 是否是 ref 创立的对象,如果是的话间接返回
if (isObject(raw) && raw.__v_isRef) {return}
let value = convert(raw)
const r = {
__v_isRef: true,
get value () {track(r, 'value')
return value
},
set value (newValue) {if (newValue !== value) {
raw = newValue
value = convert(raw)
trigger(r, 'value')
}
}
}
return r
}
在 ref 函数中,次要是依据传入的 raw 生成一个响应式对象,该对象蕴含一个 value 属性,在 value 属性的 get 和 set 办法中收集依赖和触发更新。
toRefs
在应用 reactive 函数将对象转换为响应式对象后,如果间接通过解构的形式获取其外部属性,那么获取到的数据并不是响应式的:
如:
// 创立响应式数据
const product = reactive({
name: 'iPhone',
price: 5000,
count: 3
})
const {price} = product
上例中,通过解构获取到的 price 并不是响应式的。
此时,能够通过 toRefs 办法解决响应式对象,将其每个属性都变成可解构的响应式对象。
export function toRefs (proxy) {const ret = proxy instanceof Array ? new Array(proxy.length) : {}
for (const key in proxy) {ret[key] = toProxyRef(proxy, key)
}
return ret
}
function toProxyRef (proxy, key) {
const r = {
__v_isRef: true,
get value () {return proxy[key]
},
set value (newValue) {proxy[key] = newValue
}
}
return r
}
此办法比较简单,就是遍历响应式数据的属性,而后依据属性创立一个新的对象,对象内所有属性都是响应式对象(和 ref 相似,蕴含 value 属性,在 get 和 set 办法中调用原来的 proxy 对象,原来的 proxy 对象中蕴含依赖收集和触发更新)。
computed
computed 函数用于创立计算对象,该对象也是响应式的。
export function computed (getter) {const result = ref()
effect(() => (result.value = getter()))
return result
}
computed 函数外部比较简单,就是调用 ref 办法创立一个新的响应式对象,而后调用 effect 办法增加响应监听,最初再返回 ref 对象。
vite 原理
目前,vue 我的项目应用 webpack 进行打包,然而 webpack 打包存在一个问题,就是开发阶段也是须要打包构建而后能力在浏览器上看到页面,这样会导致首次启动等待时间较长。
vite 是随着 vue3.0 一起公布的一个 vue 构建工具,因为其采纳浏览器原生反对的 ES Module 个性,在开发阶段跳过编译,能间接在浏览器端运行,所以其存在以下特点:
- 疾速冷启动,不必构建后再启动。
- 天生反对更好的模块热更新体验。
- 按需编译,如 vue 单文件组件,只有在加载的时候才会在服务端进行构建。
- 开箱即用,其依赖的 js 包非常少。
Vite 创立我的项目
- 增加全局依赖
npm install -g create-vite-app
- 创立我的项目
create-vite-app project-name
- 启动我的项目
npm run dev
通过简略的几步,我的项目就被创立好并且能够应用了。
模仿 Vite 实现
模仿 vite 须要首先明确 vite 的实现原理,即在浏览器端通过 module 形式加载我的项目下的 main.js 文件,但遇到 vue 单文件、css、图片等 ES Module 不辨认的资源时,再在服务器端进行编译转换为浏览器能够辨认的 js 资源。
创立空的 js 包
- 创立我的项目文件夹
- 增加 package.json
npm init –y
- 增加 bin 配置,指向我的项目目录下的 index.js 文件
- 在 index.js 文件中增加 node 程序启动头
增加动态服务器
在 vite 中是应用 koa 来创立服务器的,咱们在这个我的项目中也用它,装置依赖:
npm i -D koa koa-send
批改 index.js
const Koa = require('koa')
const send = require('koa-send')
const app = new Koa()
// 动态文件服务器
app.use(async (ctx, next) => {await send(ctx, ctx.path, {root: process.cwd(), index: 'index.html'})
// 继续执行后续的中间件
await next()})
app.listen(3000)
console.log('Server running @ http://localhost:3000')
批改实现后执行
npm link
关上 vite 创立的我的项目,在我的项目中执行:
my-vite
我的项目名称是 my-vite,能够依据本人的我的项目名去执行命令。
此时命令行中会提醒,动态服务器启动胜利,关上浏览器,也能够看到可能失常获取资源:
只不过此时页面没有失常显示,这个是因为控制台报错了:
这个报错的起因是因为在我的项目的 main.js 中引入了其余包:
import {createApp} from ‘vue’
这个援用在浏览器端无奈辨认,所以下一步须要解决第三方模块援用。
第三方模块援用
浏览器只能辨认 ’/’ 或者 ’./’ 援用的 js 文件,然而咱们的我的项目中存在如:
import {createApp} from ‘vue’
这样的大量援用,这些援用的第三方模块 ES Module 是无奈辨认的,因而须要在服务端对这样的门路进行解决:
Vite 的解决逻辑是将第三方的援用后面加上 ’/@modules’,如:
import {createApp} from ‘vue’
批改为
import {createApp} from ‘/@modules/vue’
批改咱们的代码:
#!/usr/bin/env node
const Koa = require('koa')
const send = require('koa-send')
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)
})
// 动态文件服务器
app.use(async (ctx, next) => {await send(ctx, ctx.path, { root: process.cwd(), index: 'index.html' })
// 继续执行后续的中间件
await next()})
// 批改第三方文件门路
app.use(async (ctx, next) => {
// 如果是 js 文件
if (ctx.type === 'application/javascript') {const contents = await streamToString(ctx.body)
// 为第三方援用后面加上 /@module
ctx.body = contents
.replace(/(from\s+['"])(?![\.\/])/g,'$1/@modules/')
}
next()})
app.listen(3000)
console.log('Server running @ http://localhost:3000')
此时再次启动之后,会发现:
浏览器会去服务端申请:http://localhost:3000/@modules/vue
这个地址在服务端是不存在的,因而须要手动解决这个申请,解决的逻辑就是当门路中蕴含 @/modules 时,服务端截取第三方包的名称,而后去 node_modules 文件夹下获取第三方包,读取 package.json 中 module 属性指向的文件,而后将文件返回给浏览器:
#!/usr/bin/env node
const Koa = require('koa')
const send = require('koa-send')
const path = require('path')
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)
})
app.use(async (ctx, next) => {
// 如果申请的门路蕴含 '/@modules/',那么就将 path 批改成 node_modules 文件夹下指定包的门路
// 而后动态文件处理器会读取文件并返回
if (ctx.path.startsWith('/@modules/')) {const moduleName = ctx.path.substr(10)
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()})
// 动态文件服务器
app.use(async (ctx, next) => {await send(ctx, ctx.path, { root: process.cwd(), index: 'index.html' })
// 继续执行后续的中间件
await next()})
// 批改第三方文件门路
app.use(async (ctx, next) => {
// 如果是 js 文件
if (ctx.type === 'application/javascript') {const contents = await streamToString(ctx.body)
// 为第三方援用后面加上 /@module
ctx.body = contents
.replace(/(from\s+['"])(?![\.\/])/g,'$1/@modules/')
}
await next()})
app.listen(3000)
console.log('Server running @ http://localhost:3000')
批改实现后,重新启动,此时能失常获取第三方包,而且第三方外部援用的其余第三方包也能被失常解决:
此时,第三方包的解决曾经实现,然而咱们还是不能看到页面失常显示,是因为浏览器无奈辨认获取到的单文件组件和 css 文件。
解决 css 文件
在 main.js 中引入了 css 文件:
这个文件尽管可能被浏览器正确下载,然而,ES Module 无奈辨认 import 的资源,此时须要对 css 进行解决。
Vite 解决 css 文件和解决第三方资源的逻辑类似,就是将 css 的援用改为:
而后在服务端解决,返回可能被辨认的 js 文件,js 文件中就是将读取到的 css 内容插入到以后 html 中。
#!/usr/bin/env node
const Koa = require('koa')
const send = require('koa-send')
const path = require('path')
const {Readable} = require('stream')
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
}
// 解决 @modules
app.use(async (ctx, next) => {
// 如果申请的门路蕴含 '/@modules/',那么就将 path 批改成 node_modules 文件夹下指定包的门路
// 而后动态文件处理器会读取文件并返回
if (ctx.path.startsWith('/@modules/')) {const moduleName = ctx.path.substr(10)
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()})
// 动态文件服务器
app.use(async (ctx, next) => {await send(ctx, ctx.path, { root: process.cwd(), index: 'index.html' })
// 继续执行后续的中间件
await next()})
// 解决?import
app.use(async (ctx, next) => {if (ctx.query.import === '') {const contents = await streamToString(ctx.body)
let code = 'const content ='
code += '`' + contents + '`\n'
code += `
const style = document.createElement('style');
style.setAttribute('type', 'text/css');
style.innerHTML = content;
document.head.appendChild(style);`
ctx.type = 'application/javascript'
ctx.body = stringToStream(code)
}
await next()})
// 批改第三方文件门路
app.use(async (ctx, next) => {
// 如果是 js 文件
if (ctx.type === 'application/javascript') {const contents = await streamToString(ctx.body)
// 为第三方援用后面加上 /@module
ctx.body = contents
.replace(/(from\s+['"])(?![\.\/])/g,'$1/@modules/')
// 解决 css 门路
.replace(/(import\s+['"].*\.css)/g,'$1?import')
}
await next()})
app.listen(3000)
console.log('Server running @ http://localhost:3000')
重新启动站点
此时 css 资源可能被正确处理。
解决单文件组件
ES Module 并不能辨认.vue 结尾的资源,此时须要在服务端进行解决,解决逻辑就是解析单文件组件的 js 代码和 template 模板,将 template 模板转换为 render 函数,而后返回给浏览器解决后的 js 文件。
解析单文件组件须要应用 @vue/compiler-sfc,因而增加依赖:
npm i -D @vue/compiler-sfc
- 解析单文件组件,获取 js 局部
判断以后申请是否是单文件组件,如果是,那么应用 compiler-sfc 进行解析,获取其中的 js 局部,而后返回一个 js 文件,js 内容格局如下:
const __script = '解析到的 js 内容'
import {render as __render} from "单文件门路?type=template"
__script.render = __render
export default __script
此时 js 内容中蕴含一个模板地址援用,这个地址在后续进行解决。
解决逻辑如下:
#!/usr/bin/env node
const Koa = require('koa')
const send = require('koa-send')
const path = require('path')
const {Readable} = require('stream')
const compilerSFC = require('@vue/compiler-sfc')
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
}
// 解决 @modules
app.use(async (ctx, next) => {
// 如果申请的门路蕴含 '/@modules/',那么就将 path 批改成 node_modules 文件夹下指定包的门路
// 而后动态文件处理器会读取文件并返回
if (ctx.path.startsWith('/@modules/')) {const moduleName = ctx.path.substr(10)
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()})
// 动态文件服务器
app.use(async (ctx, next) => {await send(ctx, ctx.path, { root: process.cwd(), index: 'index.html' })
// 继续执行后续的中间件
await next()})
// 解决?import
app.use(async (ctx, next) => {if (ctx.query.import === '') {const contents = await streamToString(ctx.body)
let code = 'const content ='
code += '`' + contents + '`\n'
code += `
const style = document.createElement('style');
style.setAttribute('type', 'text/css');
style.innerHTML = content;
document.head.appendChild(style);`
ctx.type = 'application/javascript'
ctx.body = stringToStream(code)
}
await next()})
// 解决单文件组件
app.use(async (ctx, next) => {if (ctx.path.endsWith('.vue')) {const contents = await streamToString(ctx.body)
const {descriptor} = compilerSFC.parse(contents)
let code
if (!ctx.query.type) {
code = descriptor.script.content
// console.log(code)
code = code.replace(/export\s+default\s+/g, 'const __script =')
// 将 template 模板转换为另一个 import 申请,"/src/App.vue?type=template"
code += `
import {render as __render} from "${ctx.path}?type=template"
__script.render = __render
export default __script
`
}
ctx.type = 'application/javascript'
ctx.body = stringToStream(code)
}
await next()})
// 批改第三方文件门路
app.use(async (ctx, next) => {
// 如果是 js 文件
if (ctx.type === 'application/javascript') {const contents = await streamToString(ctx.body)
// 为第三方援用后面加上 /@module
ctx.body = contents
.replace(/(from\s+['"])(?![\.\/])/g,'$1/@modules/')
// 解决 css 门路
.replace(/(import\s+['"].*\.css)/g,'$1?import')
}
await next()})
app.listen(3000)
console.log('Server running @ http://localhost:3000')
重新启动站点:
- 解决模板
因为在解析 js 的时候,将模板独自作为一个门路引入,此门路返回的是一个蕴含导出 render 函数的 js 文件,所以须要增加逻辑对模板门路进行解决:
在解决单文件组件 js 逻辑代码的中间件中持续批改,当申请单文件组件的 query 参数中的 type=template 时,就应用 compiler-sfc 解析单文件组件,获取 render 函数,而后返回给浏览器:
app.use(async (ctx, next) => {if (ctx.path.endsWith('.vue')) {const contents = await streamToString(ctx.body)
const {descriptor} = compilerSFC.parse(contents)
let code
if (!ctx.query.type) {
code = descriptor.script.content
// console.log(code)
code = code.replace(/export\s+default\s+/g, 'const __script =')
// 将 template 模板转换为另一个 import 申请,"/src/App.vue?type=template"
code += `
import {render as __render} from "${ctx.path}?type=template"
__script.render = __render
export default __script
`
} else if (ctx.query.type === 'template') {const templateRender = compilerSFC.compileTemplate({ source: descriptor.template.content})
code = templateRender.code
}
ctx.type = 'application/javascript'
ctx.body = stringToStream(code)
}
await next()})
重新启动:
此时模板也可能失常解决。
解决图片资源
自定义 vite 写到这里,咱们解决了第三方资源,单文件组件和 css 资源,然而页面还是不能失常显示,这是因为在单文件组件中援用了图片资源
通过 import 形式引入的图片,浏览器是不能辨认的,此时须要将图片资源的 import 援用去掉,间接通过 src 援用图片门路。
大抵逻辑如下:
- 首先须要通过正则匹配:
import _imports_0 from ‘./assets/logo.png’
- 获取_imports_0 和图片门路。
- 将_createNode 参数中的 src 值替换为图片门路
- 删除图片的 import 援用。
// 解决单文件组件
app.use(async (ctx, next) => {if (ctx.path.endsWith('.vue')) {const contents = await streamToString(ctx.body)
const {descriptor} = compilerSFC.parse(contents)
let code
if (!ctx.query.type) {
code = descriptor.script.content
// console.log(code)
code = code.replace(/export\s+default\s+/g, 'const __script =')
// 将 template 模板转换为另一个 import 申请,"/src/App.vue?type=template"
code += `
import {render as __render} from "${ctx.path}?type=template"
__script.render = __render
export default __script
`
} else if (ctx.query.type === 'template') {const templateRender = compilerSFC.compileTemplate({ source: descriptor.template.content})
code = templateRender.code
// 解决图片
const imgs = code.match(/import.*from\s+['"].*\.png'/g)
if (imgs) {
imgs.forEach(item => {code = code.replace(item, '')
const strs = item.split('from')
const key = strs[0].replace(/import\s+/g, '')
code = code.replace(key.trim(), strs[1].replace('./', '/src/'))
})
}
}
ctx.type = 'application/javascript'
ctx.body = stringToStream(code)
}
await next()})
此时图片资源可能被失常加载,
然而页面还是报错:
这个谬误是因为在代码中应用了环境变量:
所以还须要解决环境变量。
解决环境变量
环境变量的解决很简略,只须要将 js 内容中的环境变量进行替换即可:
// 批改第三方文件门路
app.use(async (ctx, next) => {
// 如果是 js 文件
if (ctx.type === 'application/javascript') {const contents = await streamToString(ctx.body)
// 为第三方援用后面加上 /@module
ctx.body = contents
.replace(/(from\s+['"])(?![\.\/])/g,'$1/@modules/')
// 解决 css 门路
.replace(/(import\s+['"].*\.css)/g,'$1?import')
// 替换环境变量
.replace(/process\.env\.NODE_ENV/g, '"development"')
}
await next()})
解决实现后,从新关上站点,能够发现我的项目可能失常显示并失常工作: