目录:
1.我想手写一个字
2.手写的字怎么写
3.我用手写的字
4.手写这个字怎么读
5.我想手写一下
6.手写字怎么弄
7.手写的一怎么写
8.我想用手写怎么写
9.我想手写写字
10.手写的文字
1.我想手写一个字
之前写过几篇 Vite 的文章,对 Vite 的概念也有一定的理解了,但理解归理解,仍然觉得很虚,也不知怎么的,这几个概念突然就变成一个这么强大的工具于是,我决定自己手写一遍 Vite,这样才有实在感,而且为了往往要考虑兼容各种情况,源码往往会非常复杂,不利于理解。
2.手写的字怎么写
那么这时候,手写一遍,去掉这些兼容逻辑、边界判断等,只关注核心逻辑,就能进一步地加深理解本文是这个系列的第一篇文章,在本篇文章中,我们先不关注 Vite 的架构,因为我们得先有个东西出来,对于很多人来说,。
3.我用手写的字
空谈架构是不行的因此,我们首先把 Vite 开发环境的部分功能模仿出来:实现 Vite Dev Server,并能够对请求的 ts 文件做编译下篇文章,我们再来讲述,如何给这个手写的 Vite 加入架构相关的内容。
4.手写这个字怎么读
本文用到的仓库存放在该 GitHub 仓库,感兴趣的可以自行下载项目约定我们既然要手写 Vite,那当然要有一个 my-vite 的项目,我们还需要一个调试 Vite 的前端页面项目我打算把手写 Vite,做成一个系列,代码都放到一个仓库中,因此我。
5.我想手写一下
使用monorepo来管理这些项目这里做如下的目录约定:└─packages └─ 1. my-vite-xxx ├─playground └─ 2. my-vite-xxx ├─playground └─ ……。
6.手写字怎么弄
所有版本的手写 Vite 项目都放在 packages 中每个手写 Vite 项目中,会有一个 playground 文件夹用来存放调试用的前端页面项目本文的用到的例子为 1.my-vite-simple-server
7.手写的一怎么写
以及该文件夹里面的 playground调试用的页面项目在手写 Vite 之前,我们构造一个极其简单的前端页面,用最简单的项目来说明 Vite 的核心流程index.html 文件:
8.我想用手写怎么写
Title
9.我想手写写字
main.js 文件// playground/src/main.js import{subModule
10.手写的文字
}from./sub-module.js;constapp=document.getElementById(app);if(app){app.innerText=Hello World;}subModule
(app);sub-module.js 文件:// playground/src/sub-module.js exportfunctionsubModule(app){console.log(this is a subModule
);app.innerHTML+=
this is a subModule;}如何运行 Vite 命令当我们使用 Vite 时,在 package.json 使用如下命令,即可在开发环境运行 Vite:{“scripts”:{“dev”:”vite”,},}要实现 Vite 命令,说实话有点复杂,我们要给 my-vite 做一个 bin 脚本,另外我们用 TS 写的代码,还得将代码编译成 JS,Vite 还没写就整这么多无关的东西,这多不好鸭。
那我们换个思路, package.json 改成这样:{“scripts”:{“dev”:”esno ../vite.ts”,},}我们直接用esno运行一个 TS 脚本,这样即不需要做一个 bin 脚本,也不需要编译 ts 代码,这对我们理解核心逻辑是有帮助的。
我们就把 vite.ts 当做是运行了 vite 命令,然后我们在vite.ts脚本中写 Vite 命令实际执行的内容即可开启一个 ServerVite 在开发环境下,会创建一个 Server,那我们首先也来创建一个 Server。
创建 Server 用 connect 包(Vite 也是使用它创建 Server),它是一个可扩展的 HTTP 服务器框架,使用方式如下:// /src/node/server/index.ts import
connectfromconnect;importhttpfromhttp;exportasyncfunctioncreateServer(){constapp=connect();// 每次请求会经过该中间件的处理
app.use(function(_,res){// 响应请求 res.end(Hello from Connect!\n);});http.createServer(app).listen(3000);
console.log(open http://localhost:3000/);}我们在 vite.ts 进行调用:// vite.ts import{createServer}from./src/node/server
;createServer();然后在 playground 中运行:pnpmrundev#openhttp://localhost:3000/ 打开 http://localhost:3000/ 效果如下:
如果 Network 中有多余的请求,可能是浏览器插件导致的,可以使用无痕模式进行调试在这个例子中,无论请求的链接是什么,都会返回 Hello from Connect,因为中间件始终返回同样的内容我们这里再稍微介绍一下 Connect 中间件的机制,已经知道的同学也可以跳过。
中间件机制connect 的中间件机制,可以用如下图表示:
当一个请求发送到 server 时,会经过一个个的中间件,中间件本质是一个回调函数,每次请求都会执行回调connect 的中间件机制有如下特点:每个中间件可以分别对请求进行处理,并进行响应每个中间件可以。
只处理特定的事情,其他事情交给其他中间件处理可以调用 next 函数,将请求传递给下一个中间件如果不调用,则之后的中间件都不会被执行想要实现 Vite Dev Server 的行为,其实就是实现对应能力的中间件。
为了先把页面给展示出来,我们先实现文件服务的中间件实现文件服务中间件这里我们直接借助 sirv 这个包,它是一个非常轻量级中间件,用于处理对静态资源的请求// /src/node/server/middlewares/static.ts 。
import{NextHandleFunction}fromconnect;importsirvfromsirv;exportfunctionstaticMiddleware():NextHandleFunction
{constserveFromRoot=sirv(./,{dev:true});returnasync(req,res,next)=>{serveFromRoot(req,res,next);};}使用中间件的方式:
// vite.ts app.use(staticMiddleware());然后重新执行 vite.ts,**重启 Server **(由于我们的 Server 没有做热更新机制,每次修改必须手动重启 Server,代码才会生效),访问
http://localhost:3000,就能显示出页面了。
这其实就是个平平无奇的文件服务,根据请求的访问路径,读取文件因为浏览器能直接执行 js 的代码,因此能正确展示页面如果我们把 JS 改成 TS Title
。– +
main.ts:import{subModule}from./sub-module.ts;constapp=document.getElementById(app);app!.innerText
=Hello World;subModule(app!);sub-module.ts:exportfunctionsubModule(app: HTMLElement){console.log(this is a subModule
);app.innerHTML+=
this is a subModule;}这下子页面就出不来了:因为浏览器无法识别 TS 的语法,自然就报错了当然这是预期之内的因为 vite 会在请求中对 TS 进行编译,而我们这里并没有处理那我们接下来把这个能力补上TS 编译中间件先来写一个中间件的基本结构:// /src/node/server/middlewares/transform.ts
exportfunctiontransformMiddleware():NextHandleFunction{returnasyncfunctionviteTransformMiddleware(req
,res,next){if(req.method!==GET){returnnext();}consturl: string=cleanUrl(req.url!);if(isTsRequest(url)
){// 编译 TS 代码 constresult=awaitdoTransform(url);// 设置 header,告诉浏览器把这个请求的响应值,当做 js 运行 res.setHeader(Content-Type
,application/javascript);// 响应请求 returnres.end(result.code);}next();};}只处理 GET 和 TS 的请求,其他的交给下一个中间件处理。
该中间应该放到文件服务中间件之前,因为 TS 的请求,需要进行转换,不应该再走到文件服务了,转换完成后,直接由该中间件进行响应由于不走文件服务中间件,我们应该自行实现 TS 文件的读取接下来我们来实现
doTransform 函数:import{transform}fromesbuild;importpathfrompath;import{readFile}fromfs-extra;exportasync
functiondoTransform(url: string){constfile=url.startsWith(/)?.+url : url;// 读取文件 constrawCode=awaitreadFile
(file,utf-8);const{code,map}=awaittransform(rawCode,{target:esnext,format:esm,sourcemap: true,loader:
ts,});return{code,map,};}主要流程如下:读取文件转换代码访问页面,效果如下:
从图中可以看出,TS 已经被转换成 JS 了由于 TS 文件被转换了,接下来我们再补一下 sourcemapexport function transformMiddleware( ): NextHandleFunction { return async function viteTransformMiddleware(req, res, next) { if (req.method !== GET) { return next(); } const url: string = cleanUrl(req.url!); if ( isTsRequest(url) ) { // 编译 TS 代码 const result = await doTransform(url); 。
+ const code = getCodeWithSourcemap(result.code, result.map); // 设置 header,告诉浏览器把这个请求的响应值,当做 js 运行 res.setHeader(Content-Type, application/javascript); // 响应请求
– return res.end(result.code); + return res.end(code); } next(); }; } getCodeWithSourcemap
的实现如下:// 生成 sourcemap 的 data url exportfunctiongenSourceMapUrl(map: string):string{return`data:application/json;base64,
${Buffer.from(map).toString(base64)}`;}// 将 sourcemap 拼接到代码末尾 exportfunctiongetCodeWithSourcemap(code
: string,map: string):string{code+=`\n//# sourceMappingURL=${genSourceMapUrl(map)}`;returncode;}主要流程如下:
将 esbuild 转换时生成的 map,用 base64 编码后,拼接成 data url。关注 data url 可以看 MDN把 sourcemap 字符串,拼接到代码末尾效果如下:
可以看出,打断点时,能映射到源码TSX/JSX 编译由于 esbuild 也能直接处理 tsx、jsx 等语法,我们只需要稍微修改一下 doTransform ,就能用于 tsx、jsx 的转换export function transformMiddleware( ): NextHandleFunction { return async function viteTransformMiddleware(req, res, next) { if (req.method !== GET) { return next(); } const url: string = cleanUrl(req.url!); if ( 。
– isTsRequest(url) + isJsRequest(url) ) { // 编译 TS 代码 const result = await doTransform(url); const code = getCodeWithSourcemap(result.code, result.map); // 设置 header,告诉浏览器把这个请求的响应值,当做 js 运行 res.setHeader(Content-Type, application/javascript); // 响应请求 return res.end(code); } next(); }; }
isJSRequest 实现如下:constknownJsSrcRE=/\.((j|t)sx?)$/;exportconstisJSRequest=(url: string):boolean=>{return
knownJsSrcRE.test(url);};doTransform 也需要做相应的修改import { transform } from esbuild; import path from path; import { readFile } from fs-extra; export async function doTransform(url: string) {
+ const extname = path.extname(url).slice(1); const file = url.startsWith(/) ? . + url : url; // 读取文件 const rawCode = await readFile(file, utf-8); const { code, map } = await transform(rawCode, { target: esnext, format: esm, sourcemap: true,
– loader: ts, + loader: extname as js | ts | jsx | tsx, }); return { code, map, }; }
那么我们来尝试一下使用 tsx首先先从 CDN 引入 React Title
+ +
+新增 tsx 模块// react-component.tsx exportfunctionReactComponent(){return(
>);}引入 tsx 模块import { subModule } from ./sub-module.ts; + import {ReactComponent} from ./react-component.tsx;
const app = document.getElementById(app); app!.innerText = Hello World; subModule(app!); + const comp = ReactComponent();
+ const root = ReactDOM.createRoot( + document.getElementById(react-root) + ); + root.render(comp);
重启 Server,效果如下:
可以看到,tsx 已经被正确编译,React 组件被渲染出来了处理 CSS 的引入为了演示 CSS 的相关处理,我们先造一些 CSS 文件style.css@import”./style-imported.css”。
;body{font-size:24px;font-weight:700;}style-imported.cssbody{color:#2196f3;}加入 @import 是为了测试 import style
我们在 index.html 引入,先看看效果:
看完效果,我们得把 html 中的引入删掉,我们要在 js 中引入,并使这种引入方式能够正常生效import { subModule } from ./sub-module.ts; import {ReactComponent} from ./react-component.tsx; 。
+ import ./style.css; const app = document.getElementById(app); app!.innerText = Hello World; subModule(app!); const comp = ReactComponent(); const root = ReactDOM.createRoot( document.getElementById(react-root) ); root.render(comp);
众所周知,js 中直接引入 css,是不行的会得到以下错误:Failedtoloadmodulescript:ExpectedaJavaScriptmodulescriptbuttheserverresponded。
withaMIMEtype of”text/css”.StrictMIMEtype checkingisenforcedformodulescriptsperHTMLspec.意思是,用 JS import 的 style.css 请求,它的响应值不是 JS,但浏览器期望它是 JS,这样它才能执行。
那么我们将 CSS 转换成 JS 即可,因此我们需要一个 CSS 转换的中间件CSS 中间件同样的,我们先写一个中间件的基本结构:import{NextHandleFunction}fromconnect。
;import{isCSSRequest}from../../utils;exportfunctioncssMiddleware():NextHandleFunction{returnasyncfunction
viteTransformMiddleware(req,res,next){if(req.method!==GET){returnnext();}consturl: string=req.url!;if
(isCSSRequest(url)){// CSS 文件的读取和转换 res.setHeader(Content-Type,application/javascript);returnres.end(
/* 转换后的代码 */);}next();};}接下来补充一下,文件的读取:constfile=url.startsWith(/)?.+url : url;constrawCode=awaitreadFile
(file,utf-8);那如何将 CSS 转换成 JS 模块,让它能够作为 ES6 module 引入呢?其实很简单,用 JS 将 CSS 的内容,插入到页面即可constfile=url.startsWith
(/)?.+url : url;constrawCode=awaitreadFile(file,utf-8);res.setHeader(Content-Type,application/javascript
);returnres.end(` var style = document.createElement(style) style.setAttribute(type, text/css) style.innerHTML = \`
${rawCode} \` document.head.appendChild(style) `);创建一个 style 标签,内容为 CSS 的文本,然后加入到 document这样就能把 CSS 当做 JS 模块引入了。
我们来看看效果:
样式渲染出来了,但又没有完全出来style-imported.css 的字体颜色样式没有渲染出来可以看出有 style-imported.css 的请求是失败的,而看看我们写的 Server,也报错了,错误为找不到文件。
因为没有错误处理,整个 Server 直接崩了,进程退出。我们来看看是什么原因导致的。
可以看出,使用 style-imported.css 的 src 路径没有了,导致读取文件的时候,读取文件的目录不对,找不到 style-imported.css为什么直接在 html 中引入 CSS 文件正常,用 JS 引入却会发生问题?
要理解这个,就要理解 CSS 的相对 url 的行为,在 MDN 中的描述如下:相对地址相对于 CSS 样式表的 URL(而不是网页的 URL)在 html 引入的 CSS 中,样式表的 URL 为 src/style.css
,则 ./style-imported.css 解析为 src/style-imported.css 而作为 JS 模块引入的 CSS,是通过document.head.appendChild(style)。
加入到页面的,不存在 URL,因此不能正确解析相对路径那这个问题该如何处理?两个思路:在请求响应前,将 @import 的 url 修改为相对于项目根目录的路径将 @import 的内容,通过打包,内联到一个 CSS 文件。
方案一看起来简单,但实际上需要兼容的情况还是比较多的方案二目前其实已经有成熟的方案了,使用 PostCSS 处理即可在 Vite 内部,实际上是使用了方案二;最终的实现代码如下:import{NextHandleFunction。
}fromconnect;import{cleanUrl,isCSSRequest}from../../utils;import{readFile}fromfs-extra;importpostcssfrom
postcss;importatImportfrompostcss-import;exportfunctioncssMiddleware():NextHandleFunction{returnasync
functionviteTransformMiddleware(req,res,next){if(req.method!==GET){returnnext();}consturl: string=cleanUrl
(req.url!);if(isCSSRequest(url)){constfile=url.startsWith(/)?.+url : url;constrawCode=awaitreadFile(file
,utf-8);// 使用 PostCSS 进行处理 constpostcssResult=awaitpostcss([atImport()]).process(rawCode,{from:file,to
:file});res.setHeader(Content-Type,application/javascript);returnres.end(` var style = document.createElement(style)
style.setAttribute(type, text/css) style.innerHTML = \`${postcssResult.css} \` document.head.appendChild(style)
`);}next();};}效果如下:
可以看到,@import 的内容,已经被内联到了 style.css 中总结在该文章中,我们首先构造了一个用于调试的项目,然后用一种巧妙的方式,通过 esno直接运行vite.ts脚本, 替代了 vite
命令的实现,简化了我们的实现成本,不需要编译 TS,同时也减少了大家的理解成本然后我们开始写 Server 的内容,写了如何启动一个 Server,并简单的介绍了 Connect 的中间件的机制接下来,使用 。
sirv 搭建了一个文件服务,把页面展示出来了然后我们分别对 TS 和 CSS 进行了处理对于 TS,我们用 esbuild 进行编译,同时 esbuild 也支持 TSX/JSX 的转换,因此也对此进行兼容,并做了一个小 Demo 进行展示。
对于 CSS,我们先用 PostCSS 进行转换,然后将转换后的代码,处理成 JS 模块,通过创建style标签并插入到document的方式,将 style 注入到页面中这样就能够在 JS 代码中对 CSS 文件进行 import。
至此,我们第一版的 my-vite 就完成了,但其实这距离 Vite,还有非常大的一段距离,我们这次写的 my-vite,只是一个普普通通的服务,只是实现了看起来跟 Vite 差不多功能的一个东西,里面的逻辑都是写死的,一点扩展性都没有
,如果要新增能力,就得修改 my-vite 的代码Vite 之所以强大,除了它自身实现的优秀能力外,很大程度是因为其插件式的架构提供设计,提供了极大的可扩展性,可通过插件,对 Vite 能力进行扩展,而不需要对 Vite 自身代码进行修改,例如: 。
@vite/plugin-vue 插件,通过使用该插件,就能够获取到 Vue 文件的编译能力因此,本篇文章的 my-vite ,只是把一些能力做出来了,但是毫无架构可言下篇文章,将会在这个的基础上,逐步地加入一些架构的内容,敬请期待。
。最后如果这篇文章对您有所帮助,请帮忙点个赞 ,您的鼓励是我创作路上的最大的动力。
2. 分享目的仅供大家学习和交流,您必须在下载后24小时内删除!
3. 不得使用于非法商业用途,不得违反国家法律。否则后果自负!
4. 本站提供的源码、模板、插件等等其他资源,都不包含技术服务请大家谅解!
5. 如有链接无法下载、失效或广告,请联系管理员处理!
6. 本站资源售价只是赞助,收取费用仅维持本站的日常运营所需!
7. 如遇到加密压缩包,请使用WINRAR解压,如遇到无法解压的请联系管理员!
8. 精力有限,不少源码未能详细测试(解密),不能分辨部分源码是病毒还是误报,所以没有进行任何修改,大家使用前请进行甄别
丞旭猿论坛
暂无评论内容