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

做幼儿园网站软文广告100字

做幼儿园网站,软文广告100字,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.khdw.cn/news/4247.html

相关文章:

  • 公司网站改版要怎么做绍兴seo排名收费
  • 做团购网站需要注册哪些商标百度快照怎么做
  • 烟台专业做网站公司有哪些网站制作开发
  • 什么网页游戏可以赚钱seo推广优化公司哪家好
  • 成都设计公司名字seo是什么单位
  • ps做网站要求高吗软件开发交易平台
  • 350做网站深圳市场推广外包团队
  • 设计模板修饰演示文稿qq关键词排名优化
  • 如何做情趣网站网站网络营销
  • 上海营销网站建设定制服务营销网站建设培训学校
  • 集团公司做网站的好处有什么qq群怎么优化排名靠前
  • dede网站qq类源码发布悬赏任务的推广平台
  • 工程项目备案信息查询seo全称是什么
  • 怎么给网站做301谷歌seo引擎优化
  • 网站删除代码网络营销策划书总结
  • wordpress同步微信素材烟台seo外包
  • 单页网站如何制作公司免费推广网站
  • 要对网页中各个元素四川seo优化
  • 制作闹钟网站全国各城市感染高峰进度查询
  • 学做网站多久沈阳seo排名优化教程
  • wordpress忘记百家号优化
  • win8风格网站 源码网站推广系统方案
  • 淄博做网站搜外seo
  • 做网站专用素材怎么提交网址让百度收录
  • 网站建设与管理是哪个软件电商网站模板
  • 说明怎样做才能通过互联网访问你制作的网站seo百度关键词优化
  • 拍卖网站功能需求文档以下属于网站seo的内容是
  • 免费.网站最新域名百度关键词优化软件
  • 品牌网站建设-建站之路免费推广的渠道有哪些
  • 珠海澳门网站建设公司哪家好网络推广怎么找客户