日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 前端技术 > HTML >内容正文

HTML

脚踏esbuild祥云,胸怀tsx利刃,身披scss羽衣,追寻前端的本质

發(fā)布時間:2023/11/16 HTML 110 coder
生活随笔 收集整理的這篇文章主要介紹了 脚踏esbuild祥云,胸怀tsx利刃,身披scss羽衣,追寻前端的本质 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

本文所有內(nèi)容,純屬個人觀點(diǎn),無意與任何人爭論

前端技術(shù)的現(xiàn)狀

我覺得前端技術(shù)發(fā)展到現(xiàn)在有兩個最主要的特征

  1. 前端工具鏈為前端工程化提供了強(qiáng)有力的支持

這方面主要是webpackrollup、esbuild等工具產(chǎn)生的價值,當(dāng)然還有背后的Node.js。

這些工具讓前端開發(fā)者可以更從容的開發(fā)大型前端項目。

  1. 前端開發(fā)框架提升了前端工程師的生產(chǎn)效率

這方面主要是AngularReact、VueSvelte等開發(fā)框架產(chǎn)生的價值。

這些框架讓開發(fā)者可以更容易的開發(fā)前端項目

前端工具鏈的價值毋庸置疑,但前端開發(fā)框架的價值與影響值得討論。

前端開發(fā)框架之所以能提升前端工程師的生產(chǎn)效率,是因為它為我們做了大量的封裝。

這種封裝工作在提升生產(chǎn)效率的同時也帶來了復(fù)雜性,甚至有些封裝工作的復(fù)雜程度遠(yuǎn)超了業(yè)務(wù)邏輯本身。

比如:我們修改一個變量的值,并把這個值更新到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;

如你所見,前端開發(fā)框架幫開發(fā)者做了大量的工作,比如:虛擬DOM,Diff算法,代理觀察變化等等。

大有 為了一碟醋,包了一鍋餃子 的嫌疑,就算這鍋餃子是尤雨溪幫我們包的,

我們也很難說餃子餡里油多了還是油少了,餃子皮是高筋面粉還是低筋面粉。

甚至現(xiàn)在大家都不考慮自己的身子適不適合吃餃子了,既然是尤雨溪幫我包的,那我一定要吃呀!

當(dāng)我們的頁面變卡、頁面占用的內(nèi)存逐漸上升最后OOM時,

我們有考慮過,如果不用這些框架,是不是這類問題更容易被發(fā)現(xiàn),更容易被控制呢?

(當(dāng)然,這里提到的問題,一定是我們吃餃子的姿勢不對導(dǎo)致的,不是餃子本身的問題_

回歸前端的本質(zhì)

我們要回到前端開發(fā)者刀耕火種、茹毛飲血的時代嗎?當(dāng)然不是。

那么哪些東西是我們不想放棄的?

  1. 組件化開發(fā)的模式

標(biāo)題欄一個組件,側(cè)邊欄一個組件,菜單一個組件,各個組件有各個組件各自的業(yè)務(wù)邏輯。

  1. 困扎代碼

發(fā)布之前,各個組件的代碼會被困扎到一起,產(chǎn)出很多個chunk文件,tree-shake會幫我們移除沒用到的代碼

  1. 熱更新或熱重載的能力

改了某個組件的代碼,能實(shí)時看到改動后的結(jié)果,如果達(dá)不到熱更新,那就保留最基本的熱重載能力。

  1. 樣式隔離

不一定要Shadow Dom,我們可以制定一套規(guī)則來約束組件的樣式。

  1. 強(qiáng)類型與智能提示

最好有TypeScript的強(qiáng)類型支持,寫組件的時候最好能有足夠多的智能提示

除了這些東西之外,

像虛擬Dom,Diff算法,Watch對象的變化,組件間通信,數(shù)據(jù)綁定等,

我們都可以拋棄,這些本來就是我們自己的工作,不需要框架來幫我們做。

歸根結(jié)底:在寫代碼的時候,我們要始終知道自己在做什么

方案

  1. 基于 Web Component 技術(shù)與相關(guān)的輔助工具

單純用 Web Component 開發(fā)的話,挺麻煩的。

要寫一個工具才才能提升我們使用這個方案的開發(fā)體驗,

比如把templatecss樣式代碼文件封裝到一個單獨(dú)的組件中

搞定這個工具沒那么容易,而且搞不好又回到了老路上,等于自己開發(fā)了一個前端框架,

我在這個方向上做過一些嘗試,后來就放棄了

  1. 基于 JSX/TSX 技術(shù)及相關(guān)輔助工具

現(xiàn)在VSCodeJSX/TSX語法支持的很好,esbuild也內(nèi)置支持對JSX/TSX的困扎

最關(guān)鍵的是:實(shí)現(xiàn)一個簡單的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/>);

這個組件的第一行導(dǎo)入了前面介紹的四個方法

注意:這個組件中沒有使用任何React對象的方法,也得導(dǎo)入React對象,而且必須叫React對象,不然esbuild不認(rèn)。

子組件示例

//主組件  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>
}

其他一些動態(tài)創(chuàng)建元素的方法也都支持,比如:

//示例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啟動調(diào)試服務(wù)器

先來看腳本代碼:

// ./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

啟動你得調(diào)試頁面了

如你所見,我們?yōu)閑sbuild增加了esbuild-sass-plugin插件,這樣我們就可以在tsx/jsx組件中使用scss樣式了

import "./Index.scss";

上面的模板html代碼中有一行這樣得腳本

new EventSource('/esbuild').addEventListener('change', () => location.reload())

此腳本為esbuild的熱重載服務(wù),

當(dāng)我們修改某個組件的代碼時,整個頁面會跟著刷新

這不是熱更新,只是熱重載,有它就夠了,上熱更新代價太大,就不要自行車了。

esbuild 打包產(chǎn)物

先看代碼

// ./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

打包代碼比較簡單,關(guān)鍵點(diǎn)是minify設(shè)置為true以壓縮輸出產(chǎn)物。

scss 隔離樣式

假設(shè)我們約定一個組件的根元素有一個父樣式,

這個父樣式約束著這個組件的所有子元素得樣式

那就可以用下面的代碼,讓組件的樣式作用于組件內(nèi),不污染全局樣式

//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 就不會影響其他組件內(nèi)的同名樣式了

總結(jié)

以上是生活随笔為你收集整理的脚踏esbuild祥云,胸怀tsx利刃,身披scss羽衣,追寻前端的本质的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。