Xeact已经成功实现了星际统治前端开发人员注意力空间的目标。它不仅是我真正理解前端开发的催化剂,它还是最流行的前端 femtoframework,如下图所示:
但是,我的 Xeact 组件部署过程依赖于deno bundle
命令,该命令已被弃用:
Warning "deno bundle" is deprecated and will be removed in the future. Use alternative bundlers like "deno_emit", "esbuild" or "rollup" instead.
我一直讨厌将 JavaScript 代码部署到互联网的过程。 npm 创建了大量的泥球,这些泥球在逻辑上非常烦人,无法梳理成浏览器可以加载的实际文件。围绕将 JavaScript 项目构建到真实文件中的工具历来是哲学复杂性的垃圾火,也是我避免深入研究前端开发工作原理的唯一原因。
我理解为什么 Deno 团队要摆脱deno bundle
命令(在某种程度上,根据我所了解的,我已经让这一切在浏览器中开始工作,这实际上是相当惊人的),但只是抛弃了恐怖对毫无戒心的人进行 esbuild 在逻辑上令人沮丧。尤其是你需要对所有这些进行破解。
node_modules
文件夹。
你能明白为什么我喜欢 Rust 作为分发包吗?除了确保构建二进制文件并且我可以将其打包到包中之外,我不需要处理任何问题。相比之下,这太容易了。
但是,当我升级 Deno 时,我真的不希望我的构建在未来某个不确定的时间点随机开始崩溃。所以,我感到无聊,然后决定将我的整个构建系统转换为esbuild 。这是整个堆栈以及我如何让它为我的构建工作。
Deno 和 esbuild
你是对的,我狡猾的朋友,我根本没有使用 NPM 甚至 node.js。我正在使用Deno ,这是一种替代的 JavaScript/TypeScript 运行时,它是用 Rust 编写的,使依赖管理变得更加容易。它使依赖管理更容易的主要方法之一是让您从运行普通静态文件服务器的 URL 中提取包,而不是必须将所有内容放入 NPM,然后希望 NPM 不会宕机。
当你用 Deno 安装一个包时,它会将所有相关的 JavaScript 和 TypeScript 文件下载到磁盘上的某个位置,并根据它们的源服务器和 SHA256 校验和存储它们。这与 NPM 所做的非常不同。作为比较,这是 NPM 安装Xeact的文件树:
./node_modules/ `-- @xeserv `-- xeact |-- CODE_OF_CONDUCT.md |-- LICENSE |-- README.md |-- default.nix |-- jsx-runtime.js |-- package.json |-- shell.nix |-- site | |-- gruvbox.css | |-- index.html | `-- index.js |-- types | |-- jsx-runtime.d.ts | `-- xeact.d.ts |-- xeact.js `-- xeact.ts
下面是 Deno 的本地文件树:
/deno-dir/deps/ `-- https `-- xena.greedo.xeserv.us |-- 15c8dd50d4aede83901b65e305f1eca8dd42955da363aca395949ce932023443 |-- 15c8dd50d4aede83901b65e305f1eca8dd42955da363aca395949ce932023443.metadata.json |-- 6291a9332210dc73f237e710bb70d6aab7f8cd66ea82cb680ed70f83374b34a3 `-- 6291a9332210dc73f237e710bb70d6aab7f8cd66ea82cb680ed70f83374b34a3.metadata.json
您可以看到这会给现有工具带来很多麻烦。
幸运的是, esbuild支持插件。这些可以让您覆盖行为,例如 esbuild 如何查找依赖项。有一个用于 esbuild 的Deno 插件,但它的文档长期不足。这是我如何让它工作的。
首先,我将 esbuild 和 deno 插件添加到我的导入映射中:
{ " imports ": { " @esbuild ": " https://deno.land/x/[email protected]/mod.js ", " @esbuild/deno ": " https://deno.land/x/[email protected]/mod.ts ", } }
然后编写一个名为build.ts
的文件,其中包含以下内容:
import * as esbuild from " @esbuild "; import { denoPlugin } from " @esbuild/deno ";
const result = await esbuild . build ({ plugins: [ denoPlugin ({ importMapURL: new URL (" ./import_map.json ", import .meta.url), })], entryPoints: Deno .args, outdir: Deno .env. get (" WRITE_TO ") ? Deno .env. get (" WRITE_TO ") : " ../../static/xeact ", bundle: true , splitting: true , format: " esm ", minifyWhitespace: !! Deno .env. get (" MINIFY "), inject: [" xeact "], jsxFactory: " h ", }); console. log ( result .outputFiles);
esbuild . stop ();
esbuild.stop
调用。如果不这样做,脚本将无限挂起。这是在飞行中发现的“乐趣”。这将执行以下操作:
- 配置 esbuild 以使用导入映射从我们的 Deno 依赖项中读取
- 将所有命令行参数设置为构建输入,因此您可以使用
deno run -A build.ts **/*.tsx
调用它并让它神奇地构建./components
中的所有文件 - 设置输出路径以及是否应根据环境变量(在 Nix 构建中使用)缩小输出
这会正确构建所有内容,并将每个组件放在我的网站希望为其提供服务的自己的.js
文件中。这在以后很重要。
尼克斯
现在是有趣的部分,让所有这些在 Nix 中确定性地工作,这样我就不可避免地会忘记所有这些是如何工作的,因为这一切都发生在幕后。当我使用 Nix 构建我的网站前端时,我使用我的deno2nix分支来自动设置我的网站所依赖的所有依赖项的本地副本的过程。
deno2nix internal.mkDepsLink函数允许您获取deno.lock
文件并将其转换为 Nix 商店中的文件夹。这完成了在 Nix 中构建 Deno 的所有困难部分。它将deno.lock
文件转换为Deno 创建的文件夹结构。
只有一个小问题:我从esm.sh中提取依赖项,有时您会在其路径中包含带有@
(at 符号)的文件。例如:
http://esm.sh/@xeserv/[email protected]
这将作为/nix/store/[email protected]
被拉入 Nix 商店,由于此错误而不起作用:
error: store path '[email protected]' contains illegal character '@'
有两种方法可以解决这个问题:
- 修复 deno2nix,使其从 URL 的基本名称(最终路径组件)中去除
@
- 利用
esm.sh
工作方式从确切的文件而不是父级别重新导出
当您从esm.sh
读取时,您会得到一个文件,该文件为http://esm.sh/@xeserv/[email protected]
重新导出实际的 NPM 包:
/* esm.sh - @xeserv/[email protected] */ export * from " https://esm.sh/v114/@xeserv/[email protected]/es2022/xeact.mjs "; export { default } from " https://esm.sh/v114/@xeserv/[email protected]/es2022/xeact.mjs ";
这意味着您只需将导入更改为路径https://esm.sh/v114/@xeserv/[email protected]/es2022/xeact.mjs
而不是使其导入顶级/@xeserv/[email protected]
,这将起作用,因为基本名称是xeact.mjs
,而不是[email protected]
。这将使它适合 Nix 商店。
在更改所有导入路径以从确切的文件而不是顶级包中提取后, deno2nix
使用旧的构建流程。现在剩下的就是运行esbuild
包装器。经过一段时间的摸索,我想出了这个推导:
frontend = pkgs.stdenv.mkDerivation rec { pname = "xesite-frontend"; inherit (bin) version; dontUnpack = true; src = ./src/frontend; buildInputs = with pkgs; [ deno jq nodePackages.uglify-js ]; ESBUILD_BINARY_PATH = "${pkgs.esbuild}/bin/esbuild";
buildPhase = '' export DENO_DIR="$(pwd)/.deno2nix" mkdir -p $DENO_DIR ln -s "${pkgs.deno2nix.internal.mkDepsLink ./src/frontend/deno.lock}" $(deno info --json | jq -r .modulesCache) export MINIFY=yes
mkdir -p dist export WRITE_TO=$(pwd)/dist
pushd $(pwd) cd $src deno run -A ./build.ts **/*.tsx popd '';
installPhase = '' mkdir -p $out/static/xeact cp -vrf dist/* $out/static/xeact ''; };
这使用internal.mkDepsLink
函数来创建我们需要的一切,缩小输出,将其全部写入名为dist
的文件夹,最后将所有内容插入$out/static/xeact
,例如MastodonShareButton.js
。
它奏效了,当它奏效时我松了一口气。
移民
现在我有能力构建我所有的动态组件,我不得不花点时间来设计我一直想做的东西,Xeact 组件模型。在高层次上,Xeact 是为无状态组件构建的。这些组件在功能上与 React 组件相同:接受属性并将它们转换为 HTML 节点的函数。
(x) -> x + 1
。国家真的把水搅浑了,但我们暂时不要考虑这个。下面是一个 Xeact 组件示例,该组件处理讨论页的“不允许有趣”按钮:
import { c } from " xeact ";
const onclick = () => { Array . from ( c (" xeblog-slides-fluff ")). forEach ((el) => el .classList. toggle (" hidden ") ); };
export default function NoFunAllowed () { const button = ( < button class ="" onclick ={() => onclick ()} > No fun allowed </button> ); return button; }
这将创建一个名为NoFunAllowed
函数,该函数显示一个按钮,上面写着"No fun allowed"
。当用户点击它时,它会在每个元素上使用 CSS 类xeblog-slides-fluff
切换“隐藏”类。当我写演讲时,我通常使用我的幻灯片作为工具来帮助我直观地解释正在发生的事情。结合健康的超现实主义,这意味着有些人可能会发现我演讲的书面形式不和谐,因为所有推文长度的段落与模因和荒诞主义相结合,比如我最喜欢的幻灯片制成:
迁移所有其他动态组件是一个简单的复制/粘贴/ useState
作业。然后在服务器端连接起来。
服务器
让我们回到 Xeact 组件的真正含义:采用属性并返回 HTML 节点的函数。当我为服务器端组件编写 HTML 时,我实际上是在 markdown 中编写如下内容:
< xeblog-conv name =" Mimi " mood =" coffee ">As a large language model, I can serve to provide some example text. I don't know what "Hipster Ipsum" is. But the Lorem Ipsum text...</ xeblog-conv >
这将扩展为您在文档中使用lol_html
看到的内容:
所以在某种程度上我需要做以下事情来支持 Xeact 组件:
- 创建一个 HTML 包装器,导入组件并将其放入具有唯一 UUID 的 HTML 树中
- 有某种
<noscript>
标签来警告人们他们需要启用 JavaScript - 导入组件并使用从 Rust 传递的 JSON 执行它的一点 hacky JavaScript
我想我的xeact_component
模板中有大部分。它创建一个唯一 ID (UUIDv4),将数据从 Rust 序列化为 JSON,以便它可以用作 Xeact 组件的输入,然后将组件函数的结果放入元素树中。
我使用这个基本过程来移植我的其他动态组件,例如视频播放器:
我希望这将使我更容易维护和扩展我网站上的其他组件。最终我想制作一个类似于<xeact-component xeact_filename="Thing" foo="bar">
HTML 标记,然后让堆栈的所有其余部分做正确的事情,但这需要更多的创造力我现在可以集合了。
我真的很高兴这一切都有效,这不仅让我更容易运行我的网站,将来扩展它应该更容易。
我很乐意最终在waifud中重用这个逻辑。它的管理面板也容易受到同样的最终破坏,我怀疑我也将不可避免地在那里重用这个构建逻辑。更不用说 Xeact 组件模型和useState
允许我在那里简化很多逻辑。