简单小应用,turbo 的确可以基本上不写 js,但目前的 turbo 处理 redirect_to,flash 等等,感觉不是很优雅,希望 turbo8 出来后能处理的更优雅。我的熊猫监控 https://jiankong.xmtui.com 就用 turbo frame 了,redirect_to 的时候还是加了几行 JS。
turbo8 用到 morphdom 理念,在 turbo:before-render,turbo:before-frame-render,turbo:load 等 turbo 事件中做事情,
import "flowbite/dist/flowbite.turbo.js";
import "@hotwired/turbo-rails"
import "./controllers"
import Trix from "trix";
import "@rails/actiontext"
import idiomorph from "./utils/idiomorph";
import { StreamActions } from "@hotwired/turbo";
import {
shouldPerformTransition,
performTransition,
} from "turbo-view-transitions";
let prevPath = window.location.pathname;
const morphRender = (prevEl, newEl) => {
return idiomorph.morph(prevEl, newEl, {
callbacks: {
beforeNodeMorphed: (fromEl, toEl) => {
console.log("🚀 ~ file: application.js:20 ~ morphRender ~ fromEl, toEl:", fromEl, toEl)
if (typeof fromEl !== "object" || !fromEl.hasAttribute) return true;
if (fromEl.isEqualNode(toEl)) return false;
if (
fromEl.hasAttribute("data-morph-permanent") &&
toEl.hasAttribute("data-morph-permanent")
) {
return false;
}
return true;
},
},
});
};
document.addEventListener("turbo:before-render", (event) => {
Turbo.navigator.currentVisit.scrolled = prevPath === window.location.pathname;
prevPath = window.location.pathname;
event.detail.render = async (prevEl, newEl) => {
await new Promise((resolve) => setTimeout(() => resolve(), 0));
await morphRender(prevEl, newEl);
};
if (shouldPerformTransition()) {
// Make sure rendering is synchronous in this case
event.detail.render = (prevEl, newEl) => {
morphRender(prevEl, newEl);
};
event.preventDefault();
performTransition(document.body, event.detail.newBody, async () => {
await event.detail.resume();
});
}
});
document.addEventListener("turbo:before-frame-render", (event) => {
event.detail.render = (prevEl, newEl) => {
Idiomorph.morph(prevEl, newEl.children, { morphStyle: "innerHTML" });
};
});
document.addEventListener("turbo:before-stream-render", (event) => {
if (shouldPerformTransition()) {
const fallbackToDefaultActions = event.detail.render;
event.detail.render = (streamEl) => {
if (streamEl.action == "update" || streamEl.action == "replace") {
const [target] = streamEl.targetElements;
if (target) {
return performTransition(
target,
streamEl.templateElement.content,
async () => {
await fallbackToDefaultActions(streamEl);
},
{ transitionAttr: "data-turbo-stream-transition" }
);
}
}
return fallbackToDefaultActions(streamEl);
};
}
});
document.addEventListener("turbo:load", () => {
if (shouldPerformTransition()) Turbo.cache.exemptPageFromCache();
});
以上是最近在折腾时看到的引到我的项目里用,这里用到https://github.com/bigskysoftware/idiomorph,BaseCamp 引用这个 idiomorph 去封装 turbo8
turbo 和 Stimulus 结合 rails 的 route 和 controller 在 view 层上做到单页面应用的效果,让开发者不去走前后端分离开发也能实现 JavaScript 框架实现的,One-Person-FrameWork
想要优雅,你自己拓展 turbo action, 可以解决你跳转和 flash 的问题
turbo stream 可以访问你页面任何 DOM,
但是你只是单独的 turbo frame, flash 的结构要变,得跟着 frame 走,确实不够优雅
turbo stream 确实可以让你尽可能的不写 js, 但是会让你尽可能多的和服务端做交互,也就是你的 Req 会变多, 并且所有客户端逻辑都散落在你服务端的 turbo stream 的代码中,你要维护这些,以及耦合的 dom_id 所以你需要合理的遵循 rails 的方式来组织你的 view 虽然有这些缺点,但是我觉得体验还是挺好的,因为统一了客户端的行为
DOM Merge 算法解决不了楼主的问题了,
虽然官方在推idiomorph
但是他大部分场景性能还不如 morphdom
替换一下 Turbo.PageRenderer
就行了
Turbo.PageRenderer.prototype.assignNewBody = function () {
if (document.body) {
morphdom(document.body, this.newElement, {
onBeforeElUpdated: (fromEl, toEl) => !fromEl.isEqualNode(toEl),
});
} else document.documentElement.appendChild(this.newElement);
};
opal 理論上是能用的,但用到 js 庫都要寫個 binding,性能也會比正常 js 慢,有不少人嘗試過做 opal 的組件框架,如 hyperstack 和 clearwater,不過目前看都是棄坑的狀態,現在前端都 ts 和 vite 了,opal 做點簡單的可能還行,不然開發體驗跟不上
我有个理论就是寄生性语言都会死掉。
从 CoffeScript 开始,即使是今天热度很高的 TypeScript。他们都会因为 JavaScript 不断完善而被淘汰掉。
归根结底还是因为浏览器执行的就是 JavaScript,绑定 UI 的语言使用的是单线程的模型,不同于其他语言,思维方式不同。其他语言即使去封装,比如用 Ruby 语法写 JavaScript,最后结果就是:你不仅要关心 Ruby 还要关心 JavaScript 还要关心他们之间的差异和版本区别,痛苦翻倍。
如果你已经理解 JavaScript,还不如去写 JavaScript。最后就会发现这样的换写毫无意义。
除非 Ruby 被浏览器支持。但是不太可能。浏览器是比操作系统甚至还复杂的软件,他的更新换代周期很慢,牵扯很多,往往是厂商们博弈后的结果。最后,你没什么选择。市场现状也说明了这个问题。
回答一下第三个问题。wasm 现在在前端运用的其实比较少。
前端说白了其实就是操作状态、渲染 UI。
什么是状态?例如一个 Switch 开关是开还是关;一个 Button 是被 hover、被 click、loading 中、disable 中;你的用户列表展示哪些用户,他们的名字、头像都是什么……这是前端最常用到的状态。
怎么操作 UI?通过 DOM 操作,比如最常用的操作,做一次网络请求,修改按钮的状态:
function submit() {
const button = 找到 Button
button.设置为 loading
button.设置为 不可点击
request API
button.设置为 正常
button.设置为 可以点击
// ...
}
WASM 的优势是在计算上比 JS 更快,但是他的局限在与无法操作 DOM,目前只有 JS 可以操作 DOM,上面的函数无法用 wasm 写出来。
那我们在关键计算上可以用 wasm 吗?可以,但是 99.9999% 的情况下都没有必要。
第一个问题是,JavaScript 目前的主流引擎都内置强大的 JIT 系统,尤其是 chrome 的 v8,它可以让 JS 的热点代码拥有强大的性能,而 wasm 是没有的。绝大部分前端执行的计算都是短暂的,在 JIT 的加持下,二者的性能差距其实非常小。即使真的有耗费时间的计算,我们也可以利用 WebWorker 来启动工作线程处理,让计算不阻塞 UI。
第二个问题,传递参数给 wasm 的时候有转化成本,你传入的数据越大,转化成本就越高,并且 wasm 不能进行网络请求,你只能从外部传入数据。到最后会发现,这个性能损耗,还不如直接写 JS。
wasm 现在在前端有哪些应用呢?(服务器端其实也有应用,但我不太了解)
这个解释到位,只有 turbo 实现不了的时候,才用 js
优先级是 turbo frame -> turbo stream -> javascript
@willx wasm 可以请求网络,这里有个端口扫描的 https://github.com/avilum/portsscan
我们还真在客户端用,不过是内部系统,为了在 https 的页面访问本地 http 服务用于打印,另一个方式是写个浏览器扩展
不是的哦,wasm 本身没有网络能力,他只能调用 JavaScript 的 API。
在这里打断点就可以看到它实际上的调用是 Go 语言帮你做了一层编译,类似把 net/http 包编译成了 Call("fetch")
,本质上还是调用 JS、然后把网络请求的结果变成 uint8 数组进行传递。