json返回页面读取data里的值都是object_【一】尤大神都说Vite香,让我来手把手分析Vite原理...

fc020675113ef83919a121a1f944d48b.gif戳蓝字"前端优选"关注我们哦

一.什么是Vite?

法语Vite(轻量,轻快)vite 是一个基于 Vue3单文件组件的非打包开发服务器,它做到了本地快速开发启动、实现按需编译、不再等待整个应用编译完成的功能作用。

对于Vite的描述:针对Vue单页面组件的无打包开发服务器,可以直接在浏览器运行请求的vue文件。

面向现代浏览器,Vite基于原生模块系统 ESModule 实现了按需编译,而在webpack的开发环境却很慢,是因为其开发时需要将进行的编译放到内存中,打包所有文件。

Vite有如此多的优点,那么它是如何实现的呢?

二.Vite的实现原理

我们先来总结下Vite的实现原理:

  • Vite在浏览器端使用的是 export import 方式导入和导出的模块;
  • vite同时实现了按需加载;
  • Vite高度依赖module script特性。

实现过程如下:

  • koa 中间件中获取请求 body;
  • 通过 es-module-lexer 解析资源 ast 并拿到 import 内容;
  • 判断 import 的资源是否是 npm 模块;
  • 返回处理后的资源路径:"vue" => "/@modules/vue"

    1cf318263eb002e965159047f0ab2718.png

将要处理的template,script,style等所需依赖以http请求的形式、通过query参数的形式区分,并加载SFC(vue单文件)文件各个模块内容。

接下来将自己手写一个Vite来实现相同的功能:

三.手把手实现Vite

1.安装依赖

实现Vite的环境需要es-module-lexerkoakoa-staticmagic-string模块搭建:

npm install es-module-lexer koa koa-static magic-string

这些模块的功能是:

  • koakoa-staticvite内部使用的服务框架;
  • es-module-lexer 用于分析ES6import语法;
  • magic-string 用来实现重写字符串内容。

2.基本结构搭建

Vite需要搭建一个koa服务:

const Koa = require('koa');
function createServer() {
    const app = new Koa();
    const root = process.cwd();
    // 构建上下文对象
    const context = {
        app,
        root
    }
    app.use((ctx, next) => {
        // 扩展ctx属性
        Object.assign(ctx, context);
        return next();
    });
    const resolvedPlugins = [

    ];
    // 依次注册所有插件
    resolvedPlugins.forEach(plugin => plugin(context));
    return app;
}
createServer().listen(4000);

3.Koa静态服务配置

用于处理项目中的静态资源:

const {serveStaticPlugin} = require('./serverPluginServeStatic');
const resolvedPlugins = [
 serveStaticPlugin
];
const path = require('path');
function serveStaticPlugin({app,root}){
    // 以当前根目录作为静态目录
    app.use(require('koa-static')(root));
    // 以public目录作为根目录
    app.use(require('koa-static')(path.join(root,'public')))
}
exports.serveStaticPlugin = serveStaticPlugin;

目的是让当前目录下的文件和public目录下的文件可以直接被访问

4.重写模块路径

const {moduleRewritePlugin} = require('./serverPluginModuleRewrite');
const resolvedPlugins = [
    moduleRewritePlugin,
    serveStaticPlugin
];
const { readBody } = require("./utils");
const { parse } = require('es-module-lexer');
const MagicString = require('magic-string');
function rewriteImports(source) {
    let imports = parse(source)[0];
    const magicString = new MagicString(source);
    if (imports.length) {
        for (let i = 0; i             const { s, e } = imports[i];
            let id = source.substring(s, e);
            if (/^[^\/\.]/.test(id)) {
                id = `/@modules/${id}`;
                // 修改路径增加 /@modules 前缀
                magicString.overwrite(s, e, id);
            }
        }
    }
    return magicString.toString();
}
function moduleRewritePlugin({ app, root }) {
    app.use(async (ctx, next) => {
        await next();
        // 对类型是js的文件进行拦截
        if (ctx.body && ctx.response.is('js')) {
            // 读取文件中的内容
            const content = await readBody(ctx.body);
            // 重写import中无法识别的路径
            const r = rewriteImports(content);
            ctx.body = r;
        }
    });
}
exports.moduleRewritePlugin = moduleRewritePlugin;

js文件中的 import 语法进行路径的重写,改写后的路径会再次向服务器拦截请求

读取文件内容:

const { Readable } = require('stream')
async function readBody(stream) {
    if (stream instanceof Readable) { // 
        return new Promise((resolve, reject) => {
            let res = '';
            stream
                .on('data', (chunk) => res += chunk)
                .on('end', () => resolve(res));
        })
    }else{
        return stream.toString()
    }
}
exports.readBody = readBody

5.解析 /@modules 文件

const {moduleResolvePlugin} = require('./serverPluginModuleResolve');
const resolvedPlugins = [
    moduleRewritePlugin,
    moduleResolvePlugin,
    serveStaticPlugin
];
const fs = require('fs').promises;
const path = require('path');
const { resolve } = require('path');
const moduleRE = /^\/@modules\//; 
const {resolveVue} = require('./utils')
function moduleResolvePlugin({ app, root }) {
    const vueResolved = resolveVue(root)
    app.use(async (ctx, next) => {
        // 对 /@modules 开头的路径进行映射
        if(!moduleRE.test(ctx.path)){ 
            return next();
        }
        // 去掉 /@modules/路径
        const id = ctx.path.replace(moduleRE,'');
        ctx.type = 'js';
        const content = await fs.readFile(vueResolved[id],'utf8');
        ctx.body = content
    });
}
exports.moduleResolvePlugin = moduleResolvePlugin;

将/@modules 开头的路径解析成对应的真实文件,并返回给浏览器

const path = require('path');
function resolveVue(root) {
    const compilerPkgPath = path.resolve(root, 'node_modules', '@vue/compiler-sfc/package.json');
    const compilerPkg = require(compilerPkgPath);
    // 编译模块的路径  node中编译
    const compilerPath = path.join(path.dirname(compilerPkgPath), compilerPkg.main);
    const resolvePath = (name) => path.resolve(root, 'node_modules', `@vue/${name}/dist/${name}.esm-bundler.js`);
    // dom运行
    const runtimeDomPath = resolvePath('runtime-dom')
    // 核心运行
    const runtimeCorePath = resolvePath('runtime-core')
    // 响应式模块
    const reactivityPath = resolvePath('reactivity')
    // 共享模块
    const sharedPath = resolvePath('shared')
    return {
        vue: runtimeDomPath,
        '@vue/runtime-dom': runtimeDomPath,
        '@vue/runtime-core': runtimeCorePath,
        '@vue/reactivity': reactivityPath,
        '@vue/shared': sharedPath,
        compiler: compilerPath,
    }
}

编译的模块使用commonjs规范,其他文件均使用es6模块

6.处理process的问题

浏览器中并没有process变量,所以我们需要在html中注入process变量

const {htmlRewritePlugin} = require('./serverPluginHtml');
const resolvedPlugins = [
    htmlRewritePlugin,
    moduleRewritePlugin,
    moduleResolvePlugin,
    serveStaticPlugin
];
const { readBody } = require("./utils");
function htmlRewritePlugin({root,app}){
    const devInjection = `
    `
    app.use(async(ctx,next)=>{
        await next();
        if(ctx.response.is('html')){
            const html = await readBody(ctx.body);
            ctx.body = html.replace(//,`$&${devInjection}`)
        }
    })
}
exports.htmlRewritePlugin = htmlRewritePlugin

html的head标签中注入脚本

7.处理.vue后缀文件

const {vuePlugin} = require('./serverPluginVue')
const resolvedPlugins = [
    htmlRewritePlugin,
    moduleRewritePlugin,
    moduleResolvePlugin,
    vuePlugin,
    serveStaticPlugin
];
const path = require('path');
const fs = require('fs').promises;
const { resolveVue } = require('./utils');
const defaultExportRE = /((?:^|\n|;)\s*)export default/

function vuePlugin({ app, root }) {
    app.use(async (ctx, next) => {
        if (!ctx.path.endsWith('.vue')) {
            return next();
        }
        // vue文件处理
        const filePath = path.join(root, ctx.path);
        const content = await fs.readFile(filePath, 'utf8');
        // 获取文件内容
        let { parse, compileTemplate } = require(resolveVue(root).compiler);
        let { descriptor } = parse(content); // 解析文件内容
        if (!ctx.query.type) {
            let code = ``;
            if (descriptor.script) {
                let content = descriptor.script.content;
                let replaced = content.replace(defaultExportRE, '$1const __script =');
                code += replaced;
            }
            if (descriptor.template) {
                const templateRequest = ctx.path + `?type=template`
                code += `\nimport { render as __render } from ${JSON.stringify(
                    templateRequest
                )}`;
                code += `\n__script.render = __render`
            }
            ctx.type = 'js'
            code += `\nexport default __script`;
            ctx.body = code;
        }
        if (ctx.query.type == 'template') {
            ctx.type = 'js';
            let content = descriptor.template.content;
            const { code } = compileTemplate({ source: content });
            ctx.body = code;
        }
    })
}
exports.vuePlugin = vuePlugin;

在后端将.vue文件进行解析成如下结果

import {reactive} from '/@modules/vue';
const __script = {
  setup() {
    let state = reactive({count:0});
    function click(){
      state.count+= 1
    }
    return {
      state,
      click
    }
  }
}
import { render as __render } from "/src/App.vue?type=template"
__script.render = __render
export default __script
import { toDisplayString as _toDisplayString, createVNode as _createVNode, Fragment as _Fragment, openBlock as _openBlock, createBlock as _createBlock } from "/@modules/vue"

export function render(_ctx, _cache) {
  return (_openBlock(), _createBlock(_Fragment, null, [
    _createVNode("div", null, "计数器:" + _toDisplayString(_ctx.state.count), 1 /* TEXT */),
    _createVNode("button", {
      onClick: _cache[1] || (_cache[1] = $event => (_ctx.click($event)))
    }, "+")
  ], 64 /* STABLE_FRAGMENT */))
}

解析后的结果可以直接在createApp方法中进行使用

8.小结

到这里,基本的一个Vite就实现了。总结一下就是:通过Koa服务,实现了按需读取文件,省掉了打包步骤,以此来提升项目启动速度,这中间包含了一系列的处理,诸如解析代码内容、静态文件读取、浏览器新特性实践等等。

其实Vite的内容远不止于此,这里我们实现了非打包开发服务器,那它是如何做到热更新的呢,下次将手把手实现Vite热更新原理~

81eb420ecd5809dd2111f875e1882795.png

历史好文推荐:

1、Vue3之——和Vite不得不说的事                        

2、大厂面试算法之斐波那契数列                        

3、2020字节跳动面试题一面解析                          

927304518c9bdc01ac155ce3280ef894.gif

d846cc05cbf77442c00c76715d65ec63.png

点个在看,大家都看 429853deeae61b1877d961abea8e050d.gif

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

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

相关文章

item name=android:width,Android:形状中的笔划创建笔划宽度的边距

我创建了一个矩形形状&#xff0c;以便将其用作列表项背景。 我的问题是笔划不遵循视图边框但是让笔划宽度为/-。这是我的形状的xml&#xff1a;<?xml version"1.0" encoding"utf-8"?>android:innerRadiusRatio"1"android:shape"re…

shell脚本报错:[: =: unary operator expected

shell脚本报错&#xff1a;"[: : unary operator expected" 在匹配字符串相等时&#xff0c;我用了类似这样的语句&#xff1a; if [ $STATUS "OK" ]; then echo "OK" fi 在运行时出现了 [: : unary operator expected 的错误&#xff…

discuz 标签详解

Discuz 模板标签说明 Discuz! 的模板采用近似 PHP 表达式的语法&#xff0c;基本都是可识别的HTML&#xff0c;但涉及到变量和动态内容时&#xff0c;基本形式下: <!-{ 代码内容 }-> 逻辑元素包围符&#xff0c;该符号用于包含条件和循环元素 条件判断 <!-{if …

ppp在linux下的编译和安装

我的编程环境是vmware fedora9, ARM目标板为ATMEL 9G45&#xff0c;LINUX内核2.6.30&#xff0c; GPRS模块为TELIT公司的GC864-DUAL-V2&#xff0c;使用3线制串口连接&#xff08;TXD RXD GND&#xff09; 1.修改2.6.30内核选项 &#xff0c;make menuconfigDevice drivers -…

计算机图形学在线作业,电子科技16秋《计算机图形学》在线作业3 辅导资料

16秋《计算机图形学》在线作业3一、单选题(共 10 道试题&#xff0c;共 50 分。)1. 在下列有关曲线和曲面概念的叙述语句中&#xff0c;错误的论述为____。. 实体模型和曲面造型是系统中常用的主要造型方法&#xff0c;曲面造型是用参数曲面描述来表示一个复杂的物体. 在曲线和…

eins

模拟9 T1 &#xff08;COGS上也有&#xff0c;链接http://218.28.19.228/cogs/problem/problem.php?pid1426&#xff09; 题目描述 f0 0&#xff0c;f1 1&#xff0c; fn fn-1 fn-2&#xff08;i > 2&#xff09;&#xff0c;求fn mod p 分析 反正就是矩阵乘法吧&#…

V210 UART 整体流程

2.6内核以后&#xff0c;多数驱动都是以平台总线的方式编写&#xff0c;因此对于这种类型的驱动&#xff0c;实际就是要分成两个流程来分析 平台总线设备的构造&#xff0c;平台总线驱动的构造。 下面先分析平台总线设备的构造&#xff0c;平台总线设备里是硬件相关的信息&am…

安徽大学计算机考研学硕2019初试单科线,安徽大学2019年考研复试分数线已公布...

2019考研国家线及各大院校复试分数线已公布&#xff01;考生们自从得知考研成绩后都在忐忑的等待着。下面中公考研小编整理了“安徽大学2019年考研复试分数线已公布”相关内容&#xff0c;希望能对2019考研考生们有所帮助。点击查看&#xff1a;2019考研国家线一、第一志愿报考…

1 jquery对checkbox的简单操作

//全选和全不选 votefunction selectAll(){ if($(":checkbox").prop(checked)){ //$(":checkbox").removeAttr(checked);//attr不兼容了jquery1.6以上 $(":checkbox").prop(checked,false); }else{ $(":checkbox").prop(c…

e记法 python 底数_备战python二级

明天考试去&#xff0c;滚吧提醒与分值&#xff1a;1*40&#xff08;选择&#xff09;5*3&#xff08;填空&#xff09;101520比如今年的一个题目是要求随机抽一个手机品牌&#xff0c;这道题目的关键点在于你要使用seed()函数覆盖原来的给定的种子seed(1)&#xff0c;因为要求…

V210 UART TX 流程

1. 虽然V210的uart驱动是平台总线设备驱动模型&#xff0c;但实际上他还是以字符设备驱动存在&#xff0c;那么分析他的发送流程&#xff0c; 首先找到他的file_operations的write函数 drivers/char/tty_io.c tty_write(struct file *file, const char __user *buf, size_t cou…

浙江省计算机二级办公软件高级应用分值,浙江计算机二级高级办公软件word题分值是多少...

计算机文化基础试题集(浙江省计算机办公室软件等级考试悬赏分&#xff1a;10 - 离问题结束还有 12 天 23 小时一、选择题((1)&#xff5e;(30)每小题1分&#xff0c;(31)&#xff5e;(55)每小题2分&#xff0c;共80分)下列各题 A) 、B)、C)、D)四个选项中&#xff0c;只有一个选…

MVC中JSON字符长度超出限制的异常处理

异常信息如下&#xff1a; 使用 JSON JavaScriptSerializer 进行序列化或反序列化时出错。字符串的长度超过了为 maxJsonLength 属性设置的值。这个异常是在执行MVC中的JsonResult的时抛出的&#xff0c;根据异常的Message得知是序列化的字符串超出了maxJsonLength的限制。并得…

cookie 百科_cookie是什么

很多朋友并不了解cookie是什么&#xff0c;Cookie&#xff0c;有时也用其复数形式 Cookies&#xff0c;指某些网站为了辨别用户身份、进行 session 跟踪而储存在用户本地终端上的数据(通常经过加密)。定义于 RFC2109 和 2965 中的都已废弃&#xff0c;最新取代的规范是 RFC6265…

pppd 源码修改1

1. pppd拨号成功后&#xff0c;会将解析到的dns服务器IP地址&#xff0c;写入/etc/ppp/resolv.conf 这样的话&#xff0c;gethostbyname_r并不会识别&#xff0c;并且&#xff0c;如果有启动两路pppd的话&#xff0c;后面一路会将resolv.conf文件重写。 因此&#xff0c;这块代…

学校计算机专业春联大全带横批,对联大全带横批_春节对联大全_新年春联合集...

上联&#xff1a;旧岁又添几个喜 下联&#xff1a;新年更上一层楼 横批&#xff1a;辞旧迎新上联&#xff1a;绿竹别其三分景 下联&#xff1a;红梅正报万家春 横批&#xff1a;春回大地上联&#xff1a;民安国泰逢盛世 下联&#xff1a;风调雨顺颂华年 横批&#xff1a;民泰国…

2014腾讯实习生招聘软件开发类附加题

附加题&#xff1a; 31. 一个珠宝商甲要鉴定41克以下的宝石&#xff08;40克及以下的任意重量&#xff09;&#xff0c;商甲只带一个天平和四个砝码&#xff0c;请问带哪四个砝码&#xff1f; 32. 一道有关utf-8编码的题。给了个例子&#xff1a;“我”的Unicode码是xxxx, 其ut…

location.href属于重定向还是转发_servlet2 单元测试、转发、重定向

解决服务端接收数据乱码问题。服务器默认采用 ISO8859-1 编码响应内容。// 1req.setCharacterEncoding("utf-8");// 2 byte[] bytes req.getParameter("username").getBytes("iso-8859-1"); System.out.println("username:" n…

如何在CSDN博客中的所贴的代码进行【代码块】显示

笔者最近很喜欢在csdn发一些技术博客&#xff0c;可是看了别人的博客&#xff0c;有代码的地方总是可以显示出代码块&#xff0c;而自己贴上去的代码总是没有。刚开始还以为CSDN博客里面的编辑功能有&#xff0c;可是找来找去都没有找到。后来才发现原来需要自己在源码上进行修…

学安全工程用不用计算机,上重点大学的末流专业,不如上普通大学的重点专业,你赞成吗?...

上重点大学的末流专业&#xff0c;不如上普通大学的重点专业&#xff0c;你赞成吗&#xff1f;首先&#xff0c;我对这个说法不赞成&#xff0c;这个说法是错误的。可以说&#xff1a;基本上说的是对的也是错的。说对的&#xff0c;是这个思路是对的&#xff0c;说错&#xff0…