当前位置: 首页 > news >正文

网站建设企划书张家界网站建设

网站建设企划书,张家界网站建设,WordPress多站點支付插件,无锡企业推广前言 本文总结了一些关于 Vite 的工作原理,以及一些实现细节。 本节对应的 demo 可以在这里找到。 什么是 Vite Vite 是一个基于浏览器原生 ES imports 的开发服务器。利用浏览器去解析 imports,在服务器端按需编译返回,完全跳过了打包这个…

前言

本文总结了一些关于 Vite 的工作原理,以及一些实现细节。

本节对应的 demo 可以在这里找到。

什么是 Vite

Vite 是一个基于浏览器原生 ES imports 的开发服务器。利用浏览器去解析 imports,在服务器端按需编译返回,完全跳过了打包这个概念,服务器随起随用。

实现步骤

  • 项目搭建
  • 实现 cli
  • 起静态服务器, nodemon 监听文件修改,执行 vite 命令
  • 处理 index.html
  • 处理 js,处理 node_modules 的引入
  • 中间件拆分
  • 处理 react 文件

项目结构

├── _example
├── cli
│ └── index.js
├── src
│ └── index.js

_example 通过 npx create-vite-app 创建的 vite 项目, 用于和 mini-vite 对比

npx create-vite _example --template react

实现 cli

新建 cli/index.js

#! /usr/bin/env node
console.log("mini-vite!");

mini-vite/package.json

{"bin": "cli/index.js"
}

通过 yarn link 将 cli 链接到全局

#  _demo/mini-vite目录
yarn link

在 _example 中 link

#  _demo/mini-vite/_example目录
yarn link mini-vite

在 package.json 中添加命令

{"scripts": {"dev:mini-vite": "mini-vite"}
}

跑下 dev:mini-vite 命令,可以看到控制台已经打印出 mini-vite!

起静态服务器

依赖安装

yarn add koa koa-static

在 src 目录下新建 index.js

// src/index.js
const Koa = require("koa");
const KoaStatic = require("koa-static");const app = new Koa();// 执行命令时的路径
const rootPath = process.cwd();
app.use(KoaStatic(rootPath));app.listen(8000, () => {console.log("mini-vite server启动成功!");
});

同时,在_example 中的 package.json 中添加命令

{"scripts": {"dev:mini-vite": "nodemon -w ../ --exec mini-vite","mini-vite": "mini-vite"}
}

并安装 nodemon

# mini-vite/_example
yarn add nodemon -D

执行,可以看到控制台打印出 mini-vite server 启动成功!同时在浏览器中打开 http://localhost:8000/ 可以看到项目已经跑起来了。(这里的端口号是 8000,是因为 create-vite-app 默认的端口号是 3000,所以这里我们用 8000)

同时修改 index.js 也可以看到 terminal 中打印出修改成功。

处理 jsx

现在我们已经可以返回静态文件了,但是在返回 index.html 中后,浏览器随即发起了 src/main.jsx 的请求

<script type="module" src="/src/main.jsx"></script>

然后就报错了,因为浏览器无法解析 jsx 文件,所以我们需要对.jsx 进行处理,将 src/main.jsx 改为 src/main.js

main.jsx:1 Failed to load module script: Expected a JavaScript module script but the server responded with a MIME type of “text/jsx”. Strict MIME type checking is enforced for module scripts per HTML spec.

首先是 jsx 的转换

在 mini-vite 目录安装依赖:

# mini-vite
yarn add  @babel/core @babel/plugin-transform-react-jsx

添加 transformJsx 函数

function transformJsx(jsxCode) {const babel = require("@babel/core");const options = {// presets: ['@babel/preset-env'], // 注意这里不要使用 @babel/preset-env,因为它会将所有的代码都转换成 ES5,包括importplugins: [["@babel/plugin-transform-react-jsx",{pragma: "React.createElement",pragmaFrag: "React.Fragment",},],],};const { code } = babel.transform(jsxCode, options);return code;
}

修改 src/index.js 的代码, 进行了一点重构, 添加中间件的机制

// index.js
const Koa = require("koa");
const KoaStatic = require("koa-static");function createServer() {const app = new Koa();const context = {app,rootPath: process.cwd(),};const resolvePlugins = [moduleRewirePlugin, serverStaticPlugin];resolvePlugins.forEach((plugin) => plugin(context));
}createServer();function serverStaticPlugin({ app, rootPath }) {app.use(KoaStatic(rootPath));app.use(KoaStatic(rootPath, "/public"));app.listen(8000, () => {console.log("mini-vite server启动成功!");});
}function moduleRewirePlugin({ app, context }) {app.use(async (ctx, next) => {await next();if (ctx.body && ctx.response.is("jsx")) {// 初始的 ctx.body 是一个 Readable 流,需要转换成字符串const jsxCode = await readBody(ctx.body);// 通过babel转换jsx代码const transformedCode = transformJsx(jsxCode);ctx.type = "application/javascript";ctx.body = transformedCode;}});
}function transformJsx(jsxCode) {const babel = require("@babel/core");const options = {// presets: ['@babel/preset-env'], // 注意这里不要使用 @babel/preset-env,因为它会将所有的代码都转换成 ES5,包括importplugins: [["@babel/plugin-transform-react-jsx",{pragma: "React.createElement",pragmaFrag: "React.Fragment",},],],};const { code } = babel.transform(jsxCode, options);return code;
}function readBody(stream) {return new Promise((resolve, reject) => {if (!stream.readable) {resolve(stream);} else {let res = "";stream.on("data", (data) => {res += data;});stream.on("end", () => {resolve(res);});stream.on("error", (err) => {reject(err);});}});
}

可以看到此时浏览器已经成功请求到了 main.js, 并且我们的 jsx 语法也被转换成了 React.createElement

但此时浏览器报错了

Uncaught TypeError: Failed to resolve module specifier “react”. Relative references must start with either “/”, “./”, or “…/”.

原因是我们在 main.js 中引入了 react,但是浏览器无法解析 node_modules 中的模块,所以我们需要对 node_modules 中的模块进行处理。

处理 node_modules

添加自定义的 babel 插件

module.exports = function ({ types: t }) {return {visitor: {ImportDeclaration(path, state) {const { node } = path;const id = node.source.value;// 简化场景: 不是以 / . 开头的,都是第三方模块,不考虑alias等其他情况if (/^[^\/\.]/.test(id)) {node.source = t.stringLiteral("/@modules/" + id);}},},};
};

服务端做对应的处理

const customAliasPlugin = require("./babel-plugin-custom-alias");const regex = /^\/@modules\//;
function moduleResolvePlugin({ app, context }) {app.use(async (ctx, next) => {if (!regex.test(ctx.path)) {return next();}const id = ctx.path.replace(regex, "");console.log("id", id);const mapping = {// 从package.json中读取esm读出来的字段,这里只是简化了一下,正常应该从package.json中读取esm导出react: path.resolve(process.cwd(), "node_modules/react/index.js"),"react-dom/client": path.resolve(process.cwd(),"node_modules/react-dom/client.js"),};ctx.type = "application/javascript";const content = fs.readFileSync(mapping[id], "utf-8");ctx.body = content;});
}

上述操作遇到一个问题就是,react 没有提供 esm 的版本!

看了下 React 官方的 package.json 的 export 字段

{"exports": {".": {"react-server": "./react.shared-subset.js","default": "./index.js"},"./package.json": "./package.json","./jsx-runtime": "./jsx-runtime.js","./jsx-dev-runtime": "./jsx-dev-runtime.js"}
}

找到对应的 index.js

"use strict";if (process.env.NODE_ENV === "production") {module.exports = require("./cjs/react.production.min.js");
} else {module.exports = require("./cjs/react.development.js");
}

这里也提到了:https://segmentfault.com/q/1010000043780457

两种方案

    1. 找一个有 esm 的版本,比如 https://github.com/esm-bundle/react
    1. 还是原来的包,但是需要在服务端做一些处理,将 cjs 的包转换成 esm 的包

看看 vite-plugin-react 是如何这个问题的, 还是回到浏览器,查看正常 vite 打包出来的文件

import __vite__cjsImport0_react_jsxDevRuntime from "/node_modules/.vite/deps/react_jsx-dev-runtime.js?v=78b1e259";
const jsxDEV = __vite__cjsImport0_react_jsxDevRuntime["jsxDEV"];
import __vite__cjsImport1_react from "/node_modules/.vite/deps/react.js?v=78b1e259";
const React = __vite__cjsImport1_react.__esModule? __vite__cjsImport1_react.default: __vite__cjsImport1_react;
import __vite__cjsImport2_reactDom_client from "/node_modules/.vite/deps/react-dom_client.js?v=78b1e259";
const ReactDOM = __vite__cjsImport2_reactDom_client.__esModule? __vite__cjsImport2_reactDom_client.default: __vite__cjsImport2_reactDom_client;
import App from "/src/App.jsx";
// ReactDOM.createRoot(document.getElementById("root")).render

看了下 node_modules/.vite/deps/react.js,确实是把代码 copy 了一份,然后把 cjs 的包转换成了 esm 的包,这个过程在 vite 中称为 optimizeDeps

处理 commonJS

vite 内部用了 esbuild 去处理,这里我们就不用 esbuild 了,直接用 babel 去处理

yarn add @babel/core babel-plugin-transform-commonjs

🚧🚧🚧: 注意,这里有两个包

  1. @babel/plugin-transform-modules-commonjs: 将 esm 转换成 cjs
  2. @babel/plugin-transform-commonjs: 将 cjs 转换成 esm

我们在启动服务的时候,添加一个 setupDevDepsAssets 的过程

setupDevDepsAssets(process.cwd());
/*** 依赖预构建,将react, react-dom, scheduler等第三方库转换成ES Module, 写入开发临时文件夹* @param {*} rootPath*/
function setupDevDepsAssets(rootPath) {//查看node_modules/.mini-viteconst tempDevDir = path.resolve(rootPath, "node_modules", ".mini-vite");if (!fs.existsSync(tempDevDir)) {fs.mkdirSync(tempDevDir);}// 将项目中的 react, react-dom, scheduler 等第三方库转换成 ES Module,写入到 node_modules/.mini-vite 目录下// 这里只是简化,实际上要从index.html中开始递归查找依赖,然后再转换const mapping = {react: {sourcePath: path.resolve(rootPath,"node_modules/react/cjs/react.development.js"),targetPath: path.resolve(tempDevDir, "react.js"),},"react-dom/client": {sourcePath: path.resolve(rootPath,"node_modules/react-dom/cjs/react-dom.development.js"),targetPath: path.resolve(tempDevDir, "react-dom.js"),},scheduler: {sourcePath: path.resolve(rootPath,"node_modules/scheduler/cjs/scheduler.development.js"),targetPath: path.resolve(tempDevDir, "scheduler.js"),},};Object.keys(mapping).forEach((key) => {const { sourcePath, targetPath } = mapping[key];transformCjsToEsm(sourcePath, targetPath);});/*** 将 CommonJS 转换成 ES Module,部分三方库没有提供 ES Module 版本,比如React* @param {*} sourcePath* @param {*} targetPath*/function transformCjsToEsm(sourcePath, targetPath) {const content = fs.readFileSync(sourcePath, "utf-8");const babel = require("@babel/core");// 转换CommonJS代码为esmconst transformedCode = babel.transform(content, {plugins: ["transform-commonjs"],}).code;// 路径重写,将 require('react') 转换成 require('/@modules/react')// TODO: 两段代码合并const pathRewritedCode = babel.transform(transformedCode, {plugins: [customAliasPlugin],}).code;fs.writeFileSync(targetPath, pathRewritedCode);}
}

添加之后查看网络请求,可以看到已经成功请求到了 react.js 和 react-dom.js

看到控制台有报错,原因是在 App.js 中没有引入 React

React 自动引入

熟悉 React 的朋友都知道,在 React17 之前, 我们在使用 React 的时候,需要手动引入 React,原因是 JSX 语法会被转换成 React.createElement。

import React from "react";
function App() {return <div>hello world1</div>;
}

但是在 React17 之后,我们不需要手动引入 React 了, 有兴趣可以看看官网介绍, 因为 React 会自动注入到全局中,所以我们需要在 App.js 中添加 React 的引入

安装

yarn add  @babel/plugin-transform-react-jsx-development

我们在代码转化中添加自动引入的逻辑

const transformedCode = babel.transform(jsxCode, {plugins: ["@babel/plugin-transform-react-jsx-development", // 引入jsxcustomAliasPlugin,],
}).code;

可以看到代码成功做了转化

接着是 import { jsxDEV as _jsxDEV } from "/@modules/react/jsx-dev-runtime";的处理

在原来的 mapping 中添加 jsx-dev-runtime 的引入

mapping = {react: {sourcePath: path.resolve(rootPath,"node_modules/react/cjs/react.development.js"),targetPath: path.resolve(tempDevDir, "react.js"),},["react/jsx-dev-runtime"]: {sourcePath: path.resolve(rootPath,"node_modules/react/cjs/react-jsx-dev-runtime.development.js"),targetPath: path.resolve(tempDevDir, "jsx-dev-runtime.js"),},
};

可以看到 hello world1 已经成功渲染到页面上了

参考

  • vite-plugin-react

本文首发于个人Github前端开发笔记,由于笔者能力有限,文章难免有疏漏之处,欢迎指正

http://www.yayakq.cn/news/760242/

相关文章:

  • 虚拟主机网站建设步骤?学生html个人网页代码
  • 河北住房城乡建设厅官方网站吉林长春有做网站的吗
  • 网站开发中用到的英文单词设计网站的结构时下面哪个方法不好
  • 大众团购网站怎么做网站建设项目规划书社团宣传
  • 山东网站建设哪家公司好哪个网站用户体验较好
  • 做企业公示的数字证书网站网站建设工作进度表
  • 文山城乡建设部网站首页tp怎么安装wordpress
  • 怎么样做网站的目录结构系部网站建设管理方案
  • 龙岗南联网站建设沈阳网站建设找世纪兴
  • 充值网站分销站怎么做网站建设考虑因素
  • asp.net 网站 相册重庆网站建设红衫
  • 八里河风景区网站建设内容摘要网站ui升级怎么做
  • 网站建设费用要求集约化网站建设方案
  • 注册公司网上申请入口网站织梦生成手机网站
  • 12306网站建设投标书海山免费网站建设
  • 建设旅游网站的费用预算东莞哪家网站建设好
  • 找工程去哪个网站做网站前台和后台是什么
  • 济南定制网站建设电商网站建设讯息
  • 好的手机端网站模板下载安装工商局官网登录
  • 南沙网站建设公司哪家好做专题页的背景网站
  • 教育培训机构有关的网站建立网站外链常用的渠道有哪些
  • 高性能网站建设书籍网站开发需要什么软件
  • 国内开源建站cms建设网站服务器的方式有自营方式
  • 网站设计制作公司需要什么资质wordpress页面模板是哪个文件夹
  • 深圳建设监理协会网站seo文章优化技巧
  • 网站里的注册怎么做北京朝阳区楼盘
  • 网站设计公司排行深圳网站建设高端
  • 外汇网站建设wordpress弹窗视频
  • 如何制作简易网站呼和浩特建设工程安全管理网站
  • 网站建设教程在线五一网站个人空间