脚踏esbuild祥云,胸怀tsx利刃,身披scss羽衣,追寻前端的本质
本文所有内容,纯属个人观点,无意与任何人争论
前端技术的现状
我觉得前端技术发展到现在有两个最主要的特征
- 前端工具链为前端工程化提供了强有力的支持
这方面主要是
webpack
、rollup
、esbuild
等工具产生的价值,当然还有背后的Node.js
。这些工具让前端开发者可以更从容的开发大型前端项目。
- 前端开发框架提升了前端工程师的生产效率
这方面主要是
Angular
、React
、Vue
和Svelte
等开发框架产生的价值。这些框架让开发者可以更容易的开发前端项目
前端工具链的价值毋庸置疑,但前端开发框架的价值与影响值得讨论。
前端开发框架之所以能提升前端工程师的生产效率,是因为它为我们做了大量的封装。
这种封装工作在提升生产效率的同时也带来了复杂性,甚至有些封装工作的复杂程度远超了业务逻辑本身。
比如:我们修改一个变量的值,并把这个值更新到Dom中,
在不使用前端框架时,我们一般会写这样的代码
let count = 0
count + = 1;
let dom = document.getElementById("id")
dom.innerHTML = count
使用前端框架后,写的代码变成了这样:
// Vue
// <div>{{count}}</div>
let count = ref(0)
count.value += 1
// React
// <div>{count}</div>
const [count, setCount] = useState(0);
setCount(count + 1);
// Svelte
// <div>{count}</div>
let count = 0;
count += 1;
如你所见,前端开发框架帮开发者做了大量的工作,比如:虚拟DOM,Diff算法,代理观察变化等等。
大有 为了一碟醋,包了一锅饺子
的嫌疑,就算这锅饺子是尤雨溪帮我们包的,
我们也很难说饺子馅里油多了还是油少了,饺子皮是高筋面粉还是低筋面粉。
甚至现在大家都不考虑自己的身子适不适合吃饺子了,既然是尤雨溪帮我包的,那我一定要吃呀!
当我们的页面变卡、页面占用的内存逐渐上升最后OOM时,
我们有考虑过,如果不用这些框架,是不是这类问题更容易被发现,更容易被控制呢?
(当然,这里提到的问题,一定是我们吃饺子的姿势不对导致的,不是饺子本身的问题_)
回归前端的本质
我们要回到前端开发者刀耕火种、茹毛饮血的时代吗?当然不是。
那么哪些东西是我们不想放弃的?
- 组件化开发的模式
标题栏一个组件,侧边栏一个组件,菜单一个组件,各个组件有各个组件各自的业务逻辑。
- 困扎代码
发布之前,各个组件的代码会被困扎到一起,产出很多个chunk文件,
tree-shake
会帮我们移除没用到的代码
- 热更新或热重载的能力
改了某个组件的代码,能实时看到改动后的结果,如果达不到热更新,那就保留最基本的热重载能力。
- 样式隔离
不一定要Shadow Dom,我们可以制定一套规则来约束组件的样式。
- 强类型与智能提示
最好有
TypeScript
的强类型支持,写组件的时候最好能有足够多的智能提示
除了这些东西之外,
像虚拟Dom,Diff算法,Watch对象的变化,组件间通信,数据绑定等,
我们都可以抛弃,这些本来就是我们自己的工作,不需要框架来帮我们做。
归根结底:在写代码的时候,我们要始终知道自己在做什么。
方案
- 基于
Web Component
技术与相关的辅助工具
单纯用 Web Component 开发的话,挺麻烦的。
要写一个工具才才能提升我们使用这个方案的开发体验,
比如把
template
、css样式
和代码文件
封装到一个单独的组件中搞定这个工具没那么容易,而且搞不好又回到了老路上,等于自己开发了一个前端框架,
我在这个方向上做过一些尝试,后来就放弃了
- 基于
JSX/TSX
技术及相关辅助工具
现在
VSCode
对JSX/TSX
语法支持的很好,esbuild也内置支持对JSX/TSX
的困扎最关键的是:实现一个简单的
JSX/TSX
解析器非常容易(不依赖React库)
JSX/TSX解析器
废话不多说,直接看解析器的代码吧:
// React.ts
let appendChild = (children: any,node: Node)=> {
if (Array.isArray(children)) {
for (const child of children) {
if(child) appendChild(child,node)
}
} else if (typeof children === "string" || typeof children === "number") {
let textNode = document.createTextNode(children as any)
node.appendChild(textNode)
} else if (typeof children.nodeType === "number") {
node.appendChild(children)
}
}
let appendAttr = (attr: object,node: HTMLElement)=>{
for (let key of Object.keys(attr)) {
let val = attr[key];
if(key === "style"){
node.setAttribute("style", val)
} else if(typeof val === "function"){
if(key.startsWith("on")){
node.addEventListener(key.toLocaleLowerCase().substring(2), val)
}
} else if(typeof val === "object"){
node[key] = val
}
else {
node.setAttribute(key, val)
}
}
}
let createElement = (tag: any, attr: any, ...children: any[]) => {
if(typeof tag === "string"){
let node = document.createElement(tag);
if(attr) appendAttr(attr,node)
if(children) appendChild(children,node)
return node;
} else if(typeof tag === "function"){
let obj = tag({...attr,children})
return obj
}
}
let Fragment = (attr:any) =>{
const fragment = document.createDocumentFragment()
appendChild(attr.children, fragment)
return fragment
}
export default {
createElement,
Fragment
}
没错,就这么4个简单的方法,就能解析大部分JSX/TSX
语法
像在
JSX/TSX
中使用SVG这类需求,我就直接忽略了,遇到这类需求用原始的HTML方法处理最好
下面是一个简单的示例
import React from "./React";
let App = ()=>{
let count = 1;
return <div>{count}</div>
}
document.body.appendChild(<App/>);
这个组件的第一行导入了前面介绍的四个方法
注意:这个组件中没有使用任何React对象的方法,也得导入React对象,而且必须叫React对象,不然esbuild不认。
子组件示例
//主组件 App.tsx
import React from "./React";
import LeftPanel from "./LeftPanel";
import MainPanel from "./MainPanel";
let App = ()=>{
return <><LeftPanel/><MainPanel/></>
}
document.body.appendChild(<App/>);
// 子组件 LeftPanel.tsx
import React from "./React";
export default function () {
let count = 1;
return <div>{count}</div>
}
其他一些动态创建元素的方法也都支持,比如:
//示例1
<div>
{[...Array(8)].map((v,i)=><div>{`${i}`}</div>) }
</div>
//示例2
let container = document.getElementById("container");
for(let i=0;i<6;i++){
let row = <div class="row"></div>
for(let j=0;j<7;j++){
let cell = <div><div class="cellHeader">{obj.content}</div></div>
row.appendChild(cell)
}
container.append(row)
}
用esbuild启动调试服务器
先来看脚本代码:
// ./script/dev.js
let esbuild = require("esbuild")
let {sassPlugin} = require("esbuild-sass-plugin")
let fs = require("fs")
let startDevServer = async ()=>{
let content = `<html><head>
<meta http-equiv="Content-Type" content="text/html;charset=UTF-8">
<link rel="stylesheet" href="./Index.css">
</head><body>
<script src="./Index.js"></script>
<script>
new EventSource('/esbuild').addEventListener('change', () => location.reload())
</script>
</body></html>`;
await fs.writeFile(`./dist/Index.html`,content)
let ctx = await esbuild.context({
entryPoints: [`./Index.tsx`],
bundle: true,
outdir: 'dist',
plugins: [sassPlugin()],
sourcemap:true
})
await ctx.watch()
let { host, port } = await ctx.serve({
servedir: 'dist',
})
let devServerAddr = `http://localhost:${port}/index.html`
console.log(devServerAddr)
}
startDevServer();
有了这个脚本之后,你只要在package.json中加一行这样的指令
"dev": "node ./script/dev.js",
就可以通过这个命令行命令
npm run dev
启动你得调试页面了
如你所见,我们为esbuild增加了esbuild-sass-plugin
插件,这样我们就可以在tsx/jsx
组件中使用scss
样式了
import "./Index.scss";
上面的模板html代码中有一行这样得脚本
new EventSource('/esbuild').addEventListener('change', () => location.reload())
此脚本为esbuild
的热重载服务,
当我们修改某个组件的代码时,整个
页面会跟着刷新
这不是热更新,只是热重载,有它就够了,上热更新代价太大,就不要自行车了。
esbuild 打包产物
先看代码
// ./script/release.js
let esbuild = require("esbuild")
let {sassPlugin} = require("esbuild-sass-plugin")
let fs = require("fs")
let release = async ()=>{
let content = `<html><head>
<meta http-equiv="Content-Type" content="text/html;charset=UTF-8">
<link rel="stylesheet" href="./Index.css">
</head><body><script src="./Index.js"></script></body></html>`;
await fs.writeFile(`./release/Index.html`,content)
let ctx = await esbuild.build({
entryPoints: [`./Index.tsx`],
bundle: true,
outdir: 'release',
plugins: [sassPlugin()],
minify: true,
sourcemap:false
})
console.log("build ok")
}
release();
package.json中加入:
"release": "node ./script/release.js"
打包指令:
npm run release
打包代码比较简单,关键点是minify
设置为true
以压缩输出产物。
scss 隔离样式
假设我们约定一个组件的根元素有一个父样式,
这个父样式约束着这个组件的所有子元素得样式
那就可以用下面的代码,让组件的样式作用于组件内,不污染全局样式
//ViewDay.scss
#ViewDay{
cursor: pointer;
.bgLine{
//
}
#JobContainer{
//
}
}
// 子组件 ViewDay.tsx
import React from "./React";
import "./ViewDay.scss";
export default function () {
return <div id="ViewDay">
<div class="bgLine"></div>
<div id="JobContainer"></div>
</div>
}
这样 .bgLine
和 #JobContainer
就不会影响其他组件内的同名样式了
脚踏esbuild祥云,胸怀tsx利刃,身披scss羽衣,追寻前端的本质的更多相关文章
- Vim 神器的打造方式
Vim 神器的打造方式 [字体:大 中 小] Vim 是一个上古神器,本篇文章主要持续总结使用 Vim 的过程中不得不了解的一些指令和注意事项,以及持续分享一个前端工作者不得不安装的一些插 ...
- webpack编译流程漫谈
前言 weback在web构建工具的激烈竞争中逐渐脱引而出. 无论是编译速度.报错提示.可扩展性等都给前端开发者耳目一新的感觉.本篇文章是个人对webpack的一点小研究总结. webpack在开发者 ...
- vim助手
移动光标 hjkl 2w 向前移动两个单词 3e 向前移动到第 3 个单词的末尾 0 移动到行首 $ 当前行的末尾 gg 文件第一行 G 文件最后一行 行号+G 指定行 <ctrl>+o ...
- vim神器(学习笔记)
#本文并非原创,属于本人学习中的记录笔记或是转存笔记,如果涉及到哪位高人的创作权益,敬请海涵! Vim 是一个上古神器,本篇文章主要持续总结使用 Vim 的过程中不得不了解的一些指令和注意事项,以及持 ...
- vim配置与使用
Vim 是一个上古神器,本篇文章主要持续总结使用 Vim 的过程中不得不了解的一些指令和注意事项,以及持续分享一个前端工作者不得不安装的一些插件,而关于 Vim 的简介,主题的选择,以及为何使用 vi ...
- react-cli
更新日志: v1.2.0 ...未完待续 v1.1.0 添加editorconfig 配置ESLint 集成prettier 集成 lint-staged 实现细节: 添加editorconfig e ...
- 从零开始, 开发一个 Web Office 套件 (1): 富文本编辑器
这是一个系列博客, 最终目的是要做一个基于HTML Canvas 的, 类似于微软 Office 的 Web Office 套件, 包括: 文档, 表格, 幻灯片... 等等. 富文本编辑器 万里长征 ...
- 打造一款高逼格的Vim神器
点击上方"开源Linux",选择"设为星标" 回复"学习"获取独家整理的学习资料! 作者:枫上雾棋 链接:https://segmentfa ...
- CSS篇(上)
紧接着HTML篇的CSS篇开啦,老铁们快来围观... 1.介绍一下标准的CSS盒子模型?低版本IE的盒子模型有什么不同? 1>有两种:IE盒子模型 W3C盒子模型 2>盒模型:内 ...
- Web前端三大框架_vue源码笔记
一.VUE 1.1 MVVM VUE也是基于MVVM模式实现的.特点就是数据双向绑定 在MVVM模式中,分成三个部分: M 模型 model V 视图 view VM 视图-模型 view-model ...
随机推荐
- Blazor前后端框架Known-V1.2.9
V1.2.9 Known是基于C#和Blazor开发的前后端分离快速开发框架,开箱即用,跨平台,一处代码,多处运行. Gitee: https://gitee.com/known/Known Gith ...
- .NET Core多线程 (1) Thread与Task
去年换工作时系统复习了一下.NET Core多线程相关专题,学习了一线码农老哥的<.NET 5多线程编程实战>课程,我将复习的知识进行了总结形成本专题.同时也特别推荐有兴趣的读者去学习一线 ...
- 部署安装kafka集群
准备 zookeeper节点: 172.50.13.103 172.50.13.104 172.50.13.105 kafka版本: 2.13-2.7.0 安装步骤 部署安装zookeeper集群.参 ...
- Ceph-介绍
Ceph架构简介及使用场景介绍 一.Ceph简介 Ceph是一个统一的分布式存储系统,设计初衷是提供较好的性能.可靠性和可扩展性. 二.Ceph特点 1.高性能 - 采用CRUSH算法,数据分布均衡, ...
- 产品代码都给你看了,可别再说不会DDD(五):请求处理流程
这是一个讲解DDD落地的文章系列,作者是<实现领域驱动设计>的译者滕云.本文章系列以一个真实的并已成功上线的软件项目--码如云(https://www.mryqr.com)为例,系统性地讲 ...
- LeetCode297:hard级别中最简单的存在,java版,用时击败98%,内存击败百分之九十九
本篇概览 因为欣宸个人水平有限,在刷题时一直不敢面对hard级别的题目,生怕出现一杯茶一包烟,一道hard做一天的窘境 这种恐惧心理一直在,直到遇见了它:LeetCode297,建议不敢做hard题的 ...
- 达梦数据库-DW-国产化--九五小庞
武汉达梦数据库股份有限公司成立于2000年,是国内领先的数据库产品开发服务商,国内数据库基础软件产业发展的关键推动者.公司为客户提供各类数据库软件及集群软件.云计算与大数据等一系列数据库产品及相关技术 ...
- 拯救Win7,2023该如何正确升级?
对于现存的Win7系统用户,微软曾多次提醒将在2023年1月停止对Win7与Win8.1的安全更新和技术支持.而转眼已经来到2023,时间已到,对于Win7,微软已经再也不管了,停止为Win7用户提供 ...
- paramiko免密登陆
paramiko免密登陆 # -*- coding: utf-8 -*- import paramiko pkey='D:/pycharm_workspace/testpy/ssh_paramiko_ ...
- 为什么 Rust 备受开发者青睐?
引子 作为一名敏锐的前端开发者,您可能早已对 Rust 有所耳闻,毕竟近几年,使用 Rust 开发的前端构建工具每经发布,其卓越的性能数据总是能带来社区的一阵惊叹. 图片来源:https://swc. ...