前言
先说一下写这个系列文章的念头
哈,起因应该是之前在那块听过一次课,里面讲的编写一个vite
插件,使用marked
将转换md
文件转换成js
代码然后在Vue
组件中使用的一个案例(用md
来生成组件库文档)。 代码大概是这样子的。当时的vite
版本使用的是2.5.3
。
import path from "path";
import fs from "fs";
import { marked } from "marked";
const mdToJs: (str: string) => string = function (str: string) {
return JSON.stringify(marked.parse(str));
};
export default function md() {
return {
name: "md",
// code 表示文件中的源代码, id 表示文件的路径
transform(code: string, id: string) {
// ??
if (id.endsWith(".md")) {
let genCode = "export default" + mdToJs(code);
return {
code: genCode,
map: null,
};
}
},
transforms: [
{
// 用于 rollup // 插件
test: (context: any) => context.path.endsWith(".md"),
transform: ({ code }: any) => mdToJs(code),
},
],
};
}
当时比较懵逼的有下面这两个问题
- 插件是在那个时机执行的?
- transform 中的参数是从哪里传递过来的?
现在回头看看发现这其实就是一个问题,搞清楚了插件的运行时机,自然也就知道了里面的参数是从哪传递过来的了。好了下面我们正式进入正题,开始我们的vite
之旅。
1.安装依赖
npm install connect es-module-lexer resolve check-is-array esbuild fast-glob fs-extra serve-static magic-string chokidar ws --save
2.Connect
- Connect是
node
的一个可扩展的 HTTP 服务器框架,使用称为中间件的“插件”。 Connect
是一个将各种“中间件”粘合在一起以处理请求的简单框架。- 主要的组件是一个连接“应用程序”。这将存储所有的中间件添加,本身是一个功能。
Connect
的核心是“使用”中间件。中间件被添加为一个“堆栈”,其中传入的请求将一个接一个地执行每个中间件,直到中间件不在其中调用next ()
。Use ()
方法还接受一个可选的路径字符串,该字符串与传入请求 URL 的开头匹配。这允许基本的路由。- 存在“错误处理”中间件的特殊情况。有些中间件中的函数只有 4 个参数。当一个中间件将错误传递给下一个中间件时,应用程序将继续寻找在该中间件之后声明的错误中间件并调用它,跳过该中间件之上的任何错误中间件和该中间件之下的任何非错误中间件。
- 最后一步是在服务器中实际使用
Connect
应用程序。那个。Listen ()
方法是启动HTTP
服务器的一种方便方法(与HTTP
相同)。运行的Node.js
版本中的服务器监听方法)。
使用方法如下
const connect = require("connect");
const http = require("http");
const middlewares = connect();
middlewares.use(function (req, res, next) {
console.log("中间件1");
next();
});
middlewares.use(function (req, res, next) {
console.log("中间件2");
next();
});
middlewares.use(function (req, res, next) {
res.end("你好 Connent");
});
http.createServer(middlewares).listen(4444);
我们使用浏览器访问这个服务地址,如下,会有这两个请求到达这个服务,所以每个中间件函数分别执行了两次。
3.serve-static
- serve-static是一个静态文件中中间件
const connect = require("connect");
const static = require("serve-static");
const http = require("http");
const path = require("path");
const middlewares = connect();
const publicPath = path.resolve(__dirname, "../../public");
middlewares.use(static(publicPath));
http.createServer(middlewares).listen(3001);
访问效果
4.es-module-lexer
- es-module-lexer是一个 JS 模块语法解析器
const { init, parse } = require("es-module-lexer");
(async () => {
await init;
const [imports, exports] = parse(
`import _ from 'lodash';\nexport var a = 100`
);
console.log(imports);
console.log(exports);
})();
打印如下
5.resolve
- es-module-lexer实现了 node 的
require.resolve()
算法
const resolve = require("resolve");
const res = resolve.sync("connect", { basedir: __dirname });
console.log(res);
6. fast-glob
- fast-glob该包提供了一些方法,用于遍历文件系统,并根据
Unix Bash shell
使用的规则返回与指定模式的定义集匹配的路径名
const fg = require("fast-glob");
(async () => {
const entries = await fg(["**/*.js"]);
console.log(entries);
})();
7. magic-string
- magic-string是一个用来操作字符串的库
const MagicString = require("magic-string");
const ms = new MagicString("var age = 10");
ms.overwrite(10, 12, "11");
console.log(ms.toString());
8. esbuild
8.1 API 使用
- esbuild是一个 JS 打包工具
代码使用
const path = require("path");
const { build } = require("esbuild");
(async function () {
await build({
absWorkingDir: process.cwd(),
entryPoints: [path.resolve("main.js")],
outfile: path.resolve("dist/main.js"),
bundle: true,
write: true,
format: "esm",
});
})();
打包效果
8.2 插件编写
let envPlugin = {
name: "env",
setup(build) {
//拦截名为env的导入路径,以便esbuild不会尝试将它们映射到文件系统位置
//用env-ns名称空间标记它们,以便为该插件保留它们
build.onResolve({ filter: /^env$/ }, ({ path }) => ({
path,
namespace: "env-ns", //默认是file
}));
//加载带有env-ns名称空间标记的路径,它们的行为就像指向包含环境变量的JSON文件一样
build.onLoad({ filter: /.*/, namespace: "env-ns" }, () => ({
contents: JSON.stringify(process.env),
loader: "json",
}));
},
};
require("esbuild")
.build({
entryPoints: ["app.js"],
bundle: true,
outfile: "out.js",
plugins: [envPlugin],
})
.catch(() => process.exit(1));
执行效果
在这里说明一下在开发环境下只是使用了esbuild
的打包能力将依赖的包文件进行整理到一个文件中去,打包环境下使用esbuild
进行代码的压缩功能。