<dfn id="hx5t3"><strike id="hx5t3"><em id="hx5t3"></em></strike></dfn>

    <thead id="hx5t3"></thead><nobr id="hx5t3"><font id="hx5t3"><rp id="hx5t3"></rp></font></nobr>

    <listing id="hx5t3"></listing>

    <var id="hx5t3"></var>
    <big id="hx5t3"></big>

      
      

      <output id="hx5t3"><ruby id="hx5t3"></ruby></output>
      <menuitem id="hx5t3"><dfn id="hx5t3"></dfn></menuitem>

      <big id="hx5t3"></big>

        而井

        而井 查看完整檔案

        深圳編輯  |  填寫畢業院校  |  填寫所在公司/組織 github.com/RIO-LI 編輯
        編輯

        如果你還想學習更多的前端知識,歡迎關于我的微信公眾號【前端列車長】,在那里我將分享更多前端干貨!

        個人動態

        而井 贊了文章 · 4月9日

        你不知道的 「 import type 」

        image.png

        背景

        TypeScript 3.8 帶來了一個新特性:僅僅導入 / 導出聲明。

        上一篇文章中, 我們使用了這個特性,解決了: 引入類型文件報錯的問題。

        其實這個特性并不復雜,但是我們需要了解其背后的機制原理,并了解 Babel 和 TypeScript 是如何一起工作的。

        本文主要內容:

        • 什么是「 僅僅導入 / 導出聲明 」
        • Babel和TypeScript是如何一起工作的

        正文

        首先, 先介紹一下這個特性。

        什么是「 僅僅導入 / 導出聲明 」

        為了能讓我們導入類型,TypeScript 重用了 JavaScript 導入語法。

        例如在下面的這個例子中,我們確保 JavaScript 的值 doThing 以及 TypeScript 類型 Options 一同被導入:

        // ./foo.ts
        interface Options {
          // ...
        }
        
        export function doThing(options: Options) {
          // ...
        }
        
        // ./bar.ts
        import { doThing, Options } from './foo.js';
        
        function doThingBetter(options: Options) {
          // do something twice as good
          doThing(options);
          doThing(options);
        }

        這很方便的,因為在大多數的情況下,我們不必擔心導入了什么 —— 僅僅是我們想導入的內容。

        遺憾的是,這僅是因為一個被稱之為「導入省略」的功能而起作用。

        當 TypeScript 輸出一個 JavaScript 文件時,TypeScript 會識別出 Options 僅僅是當作了一個類型來使用,它將會刪除 Options。

        // ./foo.js
        export function doThing(options: Options) {
          // ...
        }
        
        // ./bar.js
        import { doThing } from './foo.js';
        
        function doThingBetter(options: Options) {
          // do something twice as good
          doThing(options);
          doThing(options);
        }

        在通常情況下,這種行為都是比較好的。但是它會導致一些其他問題。

        首先,在一些場景下,TypeScript 會混淆導出的究竟是一個類型還是一個值。

        比如在下面的例子中, MyThing 究竟是一個值還是一個類型?

        import { MyThing } from './some-module.js';
        
        export { MyThing };

        如果單從這個文件來看,我們無從得知答案。

        如果 Mything 僅僅是一個類型,Babel 和 TypeScript 使用的 transpileModule API 編譯出的代碼將無法正確工作,并且 TypeScript 的 isolatedModules 編譯選項將會提示我們,這種寫法將會拋出錯誤。

        問題的關鍵在于,沒有一種方式能識別它僅僅是個類型,以及是否應該刪除它,因此「導入省略」并不夠好。

        同時,這也存在另外一個問題,TypeScript 導入省略將會去除只包含用于類型聲明的導入語句。

        對于含有副作用的模塊,這造成了明顯的不同行為。于是,使用者將會不得不添加一條額外的聲明語句,來確保有副作用。

        // This statement will get erased because of import elision.
        import { SomeTypeFoo, SomeOtherTypeBar } from './module-with-side-effects';
        
        // This statement always sticks around.
        import './module-with-side-effects';

        一個我們看到的具體例子是出現在 Angularjs(1.x)中, services 需要在全局在注冊(它是一個副作用),但是導入的 services 僅僅用于類型聲明中。

        // ./service.ts
        export class Service {
          // ...
        }
        register('globalServiceId', Service);
        
        // ./consumer.ts
        import { Service } from './service.js';
        
        inject('globalServiceId', function(service: Service) {
          // do stuff with Service
        });

        結果 ./service.js 中的代碼不會被執行,導致在運行時會被中斷。

        在 TypeScript 3.8 版本中,我們添加了一個僅僅導入/導出 聲明語法來作為解決方式。

        import type { SomeThing } from "./some-module.js";
        
        export type { SomeThing };

        import type 僅僅導入被用于類型注解或聲明的聲明語句,它總是會被完全刪除,因此在運行時將不會留下任何代碼。

        與此相似,export type 僅僅提供一個用于類型的導出,在 TypeScript 輸出文件中,它也將會被刪除。

        值得注意的是,類在運行時具有值,在設計時具有類型。它的使用與上下文有關。

        當使用 import type 導入一個類時,你不能做類似于從它繼承的操作。

        import type { Component } from "react";
        
        interface ButtonProps {
            // ...
        }
        
        class Button extends Component<ButtonProps> {
            //               ~~~~~~~~~
            // error! 'Component' only refers to a type, but is being used as a value here.
        
            // ...
        }

        如果在之前你使用過 Flow,它們的語法是相似的。

        一個不同的地方是我們添加了一個新的限制條件,來避免可能混淆的代碼。

        // Is only 'Foo' a type? Or every declaration in the import?
        // We just give an error because it's not clear.
        
        import type Foo, { Bar, Baz } from "some-module";
        //     ~~~~~~~~~~~~~~~~~~~~~~
        // error! A type-only import can specify a default import or named bindings, but not both.

        與 import type 相關聯,我們提供來一個新的編譯選項:importsNotUsedAsValues,通過它可以來控制沒被使用的導入語句將會被如何處理,它的名字是暫定的,但是它提供來三個不同的選項。

        • remove,這是現在的行為 —— 丟棄這些導入語句。這仍然是默認行為,沒有破壞性的更改
        • preserve,它將會保留所有的語句,即使是從來沒有被使用。它可以保留副作用。
        • error,它將會保留所有的導入(與 preserve 選項相同)語句,但是當一個值的導入僅僅用于類型時將會拋出錯誤。如果你想確保沒有意外導入任何值,這會是有用的,但是對于副作用,你仍然需要添加額外的導入語法。

        對于該特性的更多信息,參考該 PR。

        Babel 和 TypeScript 是如何一起工作的

        TypeScript 做了兩件事

        1. 將靜態類型檢查添加到 JavaScript 代碼中。
        2. 將 TS + JS 代碼轉換為各種JS版本。

        Babel 也做第二件事。

        Babel的方法(特別是transform-typescript插件時)是: 先刪除類型,然后進行轉換。

        這樣,就即可以使用 Babel 的所有優點,同時仍然能夠提供 ts 文件。

        看個例子:

        babel 編譯前:

        // example.ts
        import { Color } from "./types";
        const changeColor = (color: Color) => {
          window.color = color;
        };

        babel 編譯后:

        // example.js
        const changeColor = (color) => {
          window.color = color;
        };

        在這里,babel 不能告訴 example.ts 那個 Color 實際上是一個類型。

        因此,babel 也被迫錯誤地將此聲明保留了轉換后的代碼中。

        為什么會這樣?

        Babel在轉譯過程中一次明確地處理一個文件。

        大概是因為 babel 團隊并不想像 TypeScript 那樣, 在相同的類型解析過程中進行構建,只是為了刪除這些類型吧。

        isolatedModules

        isolatedModules 是什么

        isolatedModules是TypeScript編譯器選項,旨在充當保護措施。

        tsc 做類型檢查時,當監測到 isolatedModules 是開啟的,就會報類型錯誤。

        如果錯誤未解決,將影響獨立處理文件的編譯工具(babel)。

        From TypeScript docs:

        Perform additional checks to ensure that separate compilation (such as with transpileModule or @babel/plugin-transform-typescript) would be safe.

        From Babel docs:

        --isolatedModules This is the default Babel behavior, and it can't be turned off because Babel doesn't support cross-file analysis.

        換句話說,每個ts文件都必須能夠獨立進行編譯。

        isolatedModules 標志可防止我們引入模棱兩可的import。

        下面看兩個具體的例子看幾個例子,了解 isolatedModules 標記的重要性。

        1. 混合導入, 混合導出

        在這里,我們采用在 types.ts 文件中定義的類型,然后從中重新導出它們。

        打開 isolatedModules 時,此代碼不會 通過類型檢查。

        // types.ts
        export type Playlist = {
          id: string;
          name: string;
          trackIds: string[];
        };
        
        export type Track = {
          id: string;
          name: string;
          artist: string;
          duration: number;
        };
        
        // lib-ambiguous-re-export.ts
        export { Playlist, Track } from "./types";
        export { CreatePlaylistRequestParams, createPlaylist } from "./api";

        Babel 轉換后:

        // dist/types.js
        --empty--
        
        // dist/lib-ambiguous-re-export.js
        export { Playlist, Track } from "./types";
        export { CreatePlaylistRequestParams, createPlaylist } from "./api";

        報錯:

        image.png

        一些理解:

        • Babel 從我們的types模塊中刪除了所有內容,它僅包含類型。
        • Babel 沒有對我們的 lib 模塊進行任何轉換。Playlist 并且 Track 應該由 Babel 移除。從Node 的角度來看,Node 做模塊解析時,會發現 types.js 中引入的文件是空的,報錯:文件不存在。
        • 如截圖所示,tsc 類型檢查過程立即將這些模糊的重新導出報告為錯誤。

        2. 顯式類型導入,顯式類型導出

        這次,我們明確地將中的類型重新導出lib-import-export.ts。

        打開 isolatedModules時,此代碼將通過 tsc 類型檢查。

        編譯前:

        // types.ts
        export type Playlist = {
          id: string;
          name: string;
          trackIds: string[];
        };
        
        // lib-import-export.ts
        import {
          Playlist as PlaylistType,
          Track as TrackType,
        } from "./types";
        
        import {
          CreatePlaylistRequestParams as CreatePlaylistRequestParamsType,
          createPlaylist
        } from "./api";
        
        export type Playlist = PlaylistType;
        export type Track = TrackType;
        export type CreatePlaylistRequestParams = CreatePlaylistRequestParamsType;
        export { createPlaylist };

        編譯后:

        // dist/types.js
        --empty-- TODO or does babel remove it all together?
        
        // lib-import-export.js
        import { createPlaylist } from "./api";
        export { createPlaylist };

        此時:

        • Babel仍輸出一個空 types.js 文件。但這沒關系,因為我們編譯的lib-import-export.js器沒再引用它。

        TypeScript 3.8

        如先前介紹, TypeScript 3.8 引入了新的語法 -- 「 僅僅導入 / 導出聲明 」。

        該語法在使用時為類型解析過程增加了確定性。

        現在,編譯器(無論是tsc,babel還是其他)都將能夠查看單個文件,并取消導入或導出(如果它是TypeScript類型)。

        import type ... from — 讓編譯器知道您要導入的內容絕對是一種類型。

        export type ... from — 一樣, 僅用作導出。

        
        // src/lib-type-re-export.ts
        export type { Track, Playlist } from "./types";
        export type { CreatePlaylistRequestParams } from "./api";
        export { createPlaylist } from "./api";
        
        // 會被編譯為:
        
        // dist/lib-type-re-export.js
        export { createPlaylist } from "./api";

        更多參考

        1. TS文檔的新部分:https://www.typescriptlang.org/docs/handbook/modules.html#importing-types
        2. 引入了類型導入的TS PR。PR說明中有很多很棒的信息:https : //github.com/microsoft/TypeScript/pull/35200
        3. TS 3.8公告:https : //devblogs.microsoft.com/typescript/announcing-typescript-3-8-beta/#type-only-imports-exports
        4. Babel PR,增強了babel解析器和transform-typescript插件,以利用新語法。隨Babel 7.9一起發布:https : //github.com/babel/babel/pull/11171
        查看原文

        贊 2 收藏 1 評論 0

        而井 贊了文章 · 4月9日

        淺探 Web Worker 與 JavaScript 沙箱

        一些「炒冷飯」的背景介紹

        本文并不會從頭開始介紹 Web Worker 的基礎知識和基本 API 的使用等(只是部分有涉及),若還未了解過 Web Worker,可參考查閱 W3C 標準 Workers 文檔 中的相關介紹。

        自從 2014 年 HTML5 正式推薦標準發布以來,HTML5 增加了越來越多強大的特性和功能,而在這其中,工作線程(Web Worker)概念的推出讓人眼前一亮,但未曾隨之激起多大的浪花,并被在其隨后工程側的 Angular、Vue、React 等框架的「革命」浪潮所淹沒。當然,我們總會偶然看過一些文章介紹,或出于學習的目的做過一些應用場景下的練習,甚或在實際項目中的涉及大量數據計算場景中真的使用過。但相信也有很多人和我一樣茫然,找不到這種高大上的技術在實際項目場景中能有哪些能起到廣泛作用的應用。

        究其原因,Web Worker 獨立于 UI 主線程運行的特性使其被大量考慮進行性能優化方面的嘗試(比如一些圖像分析、3D 計算繪制等場景),以保證在進行大量計算的同時,頁面對用戶能有及時的響應。而這些性能優化的需求在前端側一方面涉及頻率低,另一方面也能通過微任務或服務端側處理來解決,它并不能像 Web Socket 這種技術為前端頁面下的輪詢場景的優化能帶來質的改變。

        直至 2019 年爆火的微前端架構的出現,基于微應用間 JavaScript 沙箱隔離的需求,Web Worker 才得以重新從邊緣化的位置躍入到我的中心視野。根據我已經了解到的 Web Worker 的相關知識,我知道了 Web Worker 是工作在一個獨立子線程下(雖然這個子線程比起 Java 等編譯型語言的子線程實現得還有點弱,如無法加鎖等),線程之間自帶隔離的特性,那基于這種「物理」性的隔離,能不能實現 JavaScript 運行時的隔離呢?

        本文接下來的內容,將介紹我在探索基于 Web Worker 實現 JavaScript 沙箱隔離方案過程中的一些資料收集、理解以及我的踩坑和思考的過程。雖然可能整篇文章內容都在「炒冷飯」,但還是希望我的探索方案的過程能對正在看這篇文章的你有所幫助。

        JavaScript 沙箱

        在探索基于 Web Worker 的解決方案之前,我們先要對當前要解決的問題——JavaScript 沙箱有所了解。

        提到沙箱,我會先想到出于興趣玩過的沙盒游戲,但我們要探索的 JavaScript 沙箱不同于沙盒游戲,沙盒游戲注重對世界基本元素的抽象、組合以及物理力系統的實現等,而 JavaScript 沙箱則更注重在使用共享數據時對操作狀態的隔離。

        在現實與 JavaScript 相關的場景中,我們知道平時使用的瀏覽器就是一個沙箱,運行在瀏覽器中的 JavaScript 代碼無法直接訪問文件系統、顯示器或其他任何硬件。Chrome 瀏覽器中每個標簽頁也是一個沙箱,各個標簽頁內的數據無法直接相互影響,接口都在獨立的上下文中運行。而在同一個瀏覽器標簽頁下運行 HTML 頁面,有哪些更細節的、對沙箱現象有需求的場景呢?

        當我們作為前端開發人員較長一段時間后,我們很輕易地就能想到在同一個頁面下,使用沙箱需求的諸多應用場景,譬如:

        1. 執行從不受信的源獲取到的第三方 JavaScript 代碼時(比如引入插件、處理 jsonp 請求回來的數據等)。

        2. 在線代碼編輯器場景(比如著名的 codesandbox)。

        3. 使用服務端渲染方案。

        4. 模板字符串中的表達式的計算。

        5. ... ...

        這里我們先回到開頭,先將前提假設在我正在面對的微前端架構設計下。在微前端架構(推薦文章 Thinking in Microfrontend 、擁抱云時代的前端開發架構——微前端 等)中,其最關鍵的一個設計便是各個子應用間的調度實現以及其運行態的維護,而運行時各子應用使用全局事件監聽、使全局 CSS 樣式生效等常見的需求在多個子應用切換時便會成為一種污染性的副作用,為了解決這些副作用,后來出現的很多微前端架構(如 乾坤)有著各種各樣的實現。譬如 CSS 隔離中常見的命名空間前綴、Shadow DOM、 乾坤 sandbox css 的運行時動態增刪等,都有著確實行之有效的具體實踐,而這里最麻煩棘手的,還是微應用間的 JavaScript 的沙箱隔離。

        在微前端架構中,JavaScript 沙箱隔離需要解決如下幾個問題:

        1. 掛在 window 上的全局方法/變量(如 setTimeout、滾動等全局事件監聽等)在子應用切換時的清理和還原。

        2. Cookie、LocalStorage 等的讀寫安全策略限制。

        3. 各子應用獨立路由的實現。

        4. 多個微應用共存時相互獨立的實現。

        乾坤 架構設計中,關于沙箱有兩個入口文件需要關注,一個是 proxySandbox.ts,另一個是 snapshotSandbox.ts,他們分別基于 Proxy 實現代理了 window 上常用的常量和方法以及不支持 Proxy 時降級通過快照實現備份還原。結合其相關開源文章分享,簡單總結下其實現思路:起初版本使用了快照沙箱的概念,模擬 ES6 的 Proxy API,通過代理劫持 window ,當子應用修改或使用 window 上的屬性或方法時,把對應的操作記錄下來,每次子應用掛載/卸載時生成快照,當再次從外部切換到當前子應用時,再從記錄的快照中恢復,而后來為了兼容多個子應用共存的情況,又基于 Proxy 實現了代理所有全局性的常量和方法接口,為每個子應用構造了獨立的運行環境。

        另外一種值得借鑒的思路是阿里云開發平臺的 Browser VM,其核心入口邏輯在 Context.js 文件中。它的具體實現思路是這樣的:

        1. 借鑒 with 的實現效果,在 webpack 編譯打包階段為每個子應用代碼包裹一層代碼(見其插件包 breezr-plugin-os 下相關文件),創建一個閉包,傳入自己模擬的 window、document、location、history 等全局對象(見 根目錄下 相關文件)。

        2. 在模擬的 Context 中,new 一個 iframe 對象,提供一個和宿主應用空的(about:blank) 同域 URL 來作為這個 iframe 初始加載的 URL(空的 URL 不會發生資源加載,但是會產生和這個 iframe 中關聯的 history 不能被操作的問題,這時路由的變換只支持 hash 模式),然后將其下的原生瀏覽器對象通過 contentWindow 取出來(因為 iframe 對象天然隔離,這里省去了自己 Mock 實現所有 API 的成本)。

        3. 取出對應的 iframe 中原生的對象之后,繼續對特定需要隔離的對象生成對應的 Proxy,然后對一些屬性獲取和屬性設置,做一些特定的實現(比如 window.document 需要返回特定的沙箱 document 而不是當前瀏覽器的document 等)。

        4. 為了文檔內容能夠被加載在同一個 DOM 樹上,對于 document,大部分的 DOM 操作的屬性和方法仍舊直接使用宿主瀏覽器中的 document 的屬性和方法處理等。

        總的來說,在 Browser VM 的實現中, 可以看出其實現部分還是借鑒了 乾坤 或者說其他微前端架構的思路,比如常見全局對象的代理和攔截。并且借助 Proxy 特性,針對 Cookie、LocalStorage 的讀寫同樣能做一些安全策略的實現等。但其最大的亮點還是借助 iframe 做了一些取巧的實現,當這個為每個子應用創建的 iframe 被移除時,寫在其下 window 上的變量和 setTimeout、全局事件監聽等也會一并被移除;另外基于 Proxy,DOM 事件在沙箱中做記錄,然后在宿主中生命周期中實現移除,能夠以較小的開發成本實現整個 JavaScript 沙箱隔離的機制。

        除了以上社區中現在比較火的方案,最近我也在 大型 Web 應用插件化架構探索 一文中了解到了 UI 設計領域的 Figma 產品也基于其插件系統產出了一種隔離方案。起初 Figma 同樣是將插件代碼放入 iframe 中執行并通過 postMessage 與主線程通信,但由于易用性以及 postMessage 序列化帶來的性能等問題,Figma 選擇還是將插件放入主線程去執行。Figma 采用的方案是基于目前還在草案階段 Realm API,并將 JavaScript 解釋器的一種 C++ 實現 Duktape 編譯到了 WebAssembly,然后將其嵌入到 Realm 上下文中,實現了其產品下的三方插件的獨立運行。這種方案和探索的基于 Web Worker 的實現可能能夠結合得更好,持續關注中。

        Web Worker 與 DOM 渲染

        在了解了 JavaScript 沙箱的「前世今生」之后,我們將目光投回本文的主角——Web Worker 身上。

        正如本文開頭所說,Web Worker 子線程的形式也是一種天然的沙箱隔離,理想的方式,是借鑒 Browser VM 的前段思路,在編譯階段通過 Webpack 插件為每個子應用包裹一層創建 Worker 對象的代碼,讓子應用運行在其對應的單個 Worker 實例中,比如:

        __WRAP_WORKER__(`/* 打包代碼 */ }`);
        ?
        function __WRAP_WORKER__(appCode) {
         var blob = new Blob([appCode]);
         var appWorker = new Worker(window.URL.createObjectURL(blob));
        } 

        但在了解過微前端下 JavaScript 沙箱的實現過程后,我們不難發現幾個在 Web Worker 下去實現微前端場景的 JavaScript 沙箱必然會遇到的幾個難題:

        1. 出于線程安全設計考慮,Web Worker 不支持 DOM 操作,必須通過 postMessage 通知 UI 主線程來實現。

        2. Web Worker 無法訪問 window、document 之類的瀏覽器全局對象。

        其他諸如 Web Worker 無法訪問頁面全局變量和函數、無法調用 alert、confirm 等 BOM API 等問題,相對于無法訪問 window、document 全局對象已經是小問題了。不過可喜的是,Web Worker 中可以正常使用 setTimeout、setInterval 等定時器函數,也仍能發送 ajax 請求。

        所以,當先要解決問題,便是在單個 Web Worker 實例中執行 DOM 操作的問題了。首先我們有一個大前提:Web Worker 中無法渲染 DOM,所以,我們需要基于實際的應用場景,將 DOM 操作進行拆分。

        React Worker DOM

        因為我們微前端架構中的子應用局限在 React 技術棧下,我先將目光放在了基于 React 框架的解決方案上。

        在 React 中,我們知道其將渲染階段分為對 DOM 樹的改變進行 Diff 和實際渲染改變頁面 DOM 兩個階段這一基本事實,那能不能將 Diff 過程置于 Web Worker 中,再將渲染階段通過 postMessage 與主線程進行通信后放在主線程進行呢?簡單一搜,頗為汗顏,已經有大佬在 5、6 年前就有嘗試了。這里我們可以參考下 react-worker-dom 的開源代碼。

        react-worker-dom 中的實現思路很清晰。其在 common/channel.js 中統一封裝了子線程和主線程互相通信的接口和序列化通信數據的接口,然后我們可以看到其在 Worker 下實現 DOM 邏輯處理的總入口文件在 worker 目錄下,從該入口文件順藤摸瓜,可以看到其實現了計算 DOM 后通過 postMessage 通知主線程進行渲染的入口文件 WorkerBridge.js 以及其他基于 React 庫實現的 DOM 構造、Diff 操作、生命周期 Mock 接口等相關代碼,而接受渲染事件通信的入口文件在 page 目錄下,該入口文件接受 node 操作事件后再結合 WorkerDomNodeImpl.js 中的接口代碼實現了 DOM 在主線程的實際渲染更新。

        簡單做下總結?;?React 技術棧,通過在 Web Worker 下實現 Diff 與渲染階段的進行分離,可以做到一定程度的 DOM 沙箱,但這不是我們想要的微前端架構下的 JavaScript 沙箱。先不談拆分 Diff 階段與渲染階段的成本與收益比,首先,基于技術??蚣艿奶厥庑运龅倪@諸多努力,會隨著這個框架本身版本的升級存在著維護升級難以掌控的問題;其次,假如各個子應用使用的技術??蚣懿煌?,要為這些不同的框架分別封裝適配的接口,擴展性和普適性弱;最后,最為重要的一點,這種方法暫時還是沒有解決 window 下資源共享的問題,或者說,只是啟動了解決這個問題的第一步。

        接下來,我們先繼續探討 Worker 下實現 DOM 操作的另外一種方案。window 下資源共享的問題我們放在其后再作討論。

        AMP WorkerDOM

        在我開始糾結于如 react-worker-dom 這種思路實際落地開發的諸多「天塹」問題的同時,瀏覽過其他 DOM 框架因為同樣具備插件機制偶然迸進了我的腦海,它是 Google 的 AMP。

        AMP 開源項目 中除了如 amphtml 這種通用的 Web 組件框架,還有很多其他工程采用了 Shadow DOM、Web Component 等新技術,在項目下簡單刷了一眼后,我欣喜地看到了工程 worker-dom。

        粗略翻看下 worker-dom 源碼,我們在 src 根目錄下可以看到 main-threadworker-thread 兩個目錄,分別打開看了下后,可以發現其實現拆分 DOM 相關邏輯和 DOM 渲染的思路和上面的 react-worker-dom 基本類似,但 worker-dom 因為和上層框架無關,其下的實現更為貼近 DOM 底層。

        先看 worker-thread DOM 邏輯層的相關代碼,可以看到其下的 dom 目錄 下實現了基于 DOM 標準的所有相關的節點元素、屬性接口、document 對象等代碼,上一層目錄中也實現了 Canvas、CSS、事件、Storage 等全局屬性和方法。

        接著看 main-thread,其關鍵功能一方面是提供加載 worker 文件從主線程渲染頁面的接口,另一方面可以從 worker.tsnodes.ts 兩個文件的代碼來理解。

        worker.ts 中像我最初所設想的那樣包裹了一層代碼,用于自動生成 Worker 對象,并將代碼中的所有 DOM 操作都代理到模擬的 WorkerDOM 對象上:

        const code = `
              'use strict';
              (function(){
                ${workerDOMScript}
                self['window'] = self;
                var workerDOM = WorkerThread.workerDOM;
                WorkerThread.hydrate(
                  workerDOM.document,
                  ${JSON.stringify(strings)},
                  ${JSON.stringify(skeleton)},
                  ${JSON.stringify(cssKeys)},
                  ${JSON.stringify(globalEventHandlerKeys)},
                  [${window.innerWidth}, ${window.innerHeight}],
                  ${JSON.stringify(localStorageInit)},
                  ${JSON.stringify(sessionStorageInit)}
                );
                workerDOM.document[${TransferrableKeys.observe}](this);
                Object.keys(workerDOM).forEach(function(k){self[k]=workerDOM[k]});
        }).call(self);
        ${authorScript}
        //# sourceURL=${encodeURI(config.authorURL)}`;
        this[TransferrableKeys.worker] = new Worker(URL.createObjectURL(new Blob([code])));

        nodes.ts 中,實現了真實元素節點的構造和存儲(基于存儲數據結構是否以及如何在渲染階段有優化還需進一步研究源碼)。

        同時,在 transfer 目錄下的源碼,定義了邏輯層和 UI 渲染層的消息通信的規范。

        總的來看,AMP WorkerDOM 的方案拋棄了上層框架的約束,通過從底層構造了 DOM 所有相關 API 的方式,真正做到了與框架技術棧無關。它一方面完全可以作為上層框架的底層實現,來支持各種上層框架的二次封裝遷移(如工程 amp-react-prototype),另一方面結合了當前主流 JavaScript 沙箱方案,通過模擬 window、document 全局方法的并代理到主線程的方式實現了部分的 JavaScript 沙箱隔離(暫時沒看到路由隔離的相關代碼實現)。

        當然,從我個人角度來看,AMP WorkerDOM 也有其當前在落地上一定的局限性。一個是對當前主流上層框架如 Vue、React 等的遷移成本及社區生態的適配成本,另一個是其在單頁應用下的尚未看到有相關實現方案,在大型 PC 微前端應用的支持上還無法找到更優方案。

        其實,在了解完 AMP WorkerDOM 的實現方案之后,基于 react-worker-dom 思路的后續方案也可以有個大概方向了:渲染通信的后續過程,可考慮結合 Browser VM 的相關實現,在生成 Worker 對象的同時,也生成一個 iframe 對象,然后將 DOM 下的操作都通過 postMessage 發送到主線程后,以與其綁定的 iframe 兌現來執行,同時,通過代理將具體的渲染實現再轉發給原 WorkerDomNodeImpl.js 邏輯來實現 DOM 的實際更新。

        小結與一些個人前瞻

        首先聊一聊個人的一些總結。Web Worker 下實現微前端架構下的 JavaScript 沙箱最初是出于一點個人靈光的閃現,在深入調研后,雖然最終還是因為這樣那樣的問題導致在方案落地上無法找到最優解從而放棄采用社區通用方案,但仍不妨礙我個人對 Web Worker 技術在實現插件類沙箱應用上的持續看好。插件機制在前端領域一直是津津樂道的一種設計,從 Webpack 編譯工具到 IDE 開發工具,從 Web 應用級的實體插件到應用架構設計中插件擴展設計,結合 WebAssembly 技術,Web Worker 無疑將在插件設計上占據舉足輕重的地位。

        其次是一些個人的一些前瞻思考。其實從 Web Worker 實現 DOM 渲染的調研過程中可以看到,基于邏輯與 UI 分離的思路,前端后續的架構設計有很大機會能夠產生一定的變革。目前不管是盛行的 Vue 還是 React 框架,其框架設計不論是 MVVM 還是結合 Redux 之后的 Flux,其本質上仍舊還是由 View 層驅動的框架設計(個人淺見),其具備靈活性的同時也產生著性能優化、大規模項目層級升上后的協作開發困難等問題,而基于 Web Worker 的邏輯與 UI 分離,將促使數據獲取、處理、消費整個流程的進一步的業務分層,從而固化出一整套的 MVX 設計思路。

        當然,以上這些我個人還處于初步調研的階段,不成熟之處還需多加琢磨。且聽之,后續再實踐之。

        作者:ES2049 / 靳志凱
        文章可隨意轉載,但請保留此原文鏈接。
        非常歡迎有激情的你加入 ES2049 Studio,簡歷請發送至 caijun.hcj@alibaba-inc.com 。
        查看原文

        贊 6 收藏 4 評論 0

        而井 回答了問題 · 4月8日

        webpack @import 使用路徑別名問題?

        1、vscode安裝插件Path Intellisense
        2、配置插件

        "path-intellisense.mappings": {
            "@": "${workspaceRoot}/src"
        }

        3、在項目package.json所在同級目錄下創建文件jsconfig.json

        {
            "compilerOptions": {
                "target": "ES6",
                "module": "commonjs",
                "allowSyntheticDefaultImports": true,
                "baseUrl": "./",
                "paths": {
                  "@/*": ["src/*"]
                }
            },
            "exclude": [
                "node_modules"
            ]
        }

        關注 3 回答 2

        而井 回答了問題 · 4月8日

        字符串用特定符號分割

        str.split("").join("|")

        關注 4 回答 3

        而井 回答了問題 · 4月8日

        ant design vue 中popover在table的fixed列中渲染重復

        最好的方法就是點擊的時候實時創建一個popover,否則你數據一多,創建那么多popover也是消耗性能的

        關注 2 回答 1

        而井 回答了問題 · 4月7日

        vue,移動端h5,如何實現類似微信聊天列表的長按下拉框效果?

        我這里有一個非Vue項目模仿【模仿微信聊天列表長按出現菜單的效果】的,和你要的效果一樣,你看完改造成Vue組件很簡單,
        demo在線地址,

        用手機打開或者chrome F12切換到移動端模式查看,具體的代碼倉庫在https://gitee.com/rioli/list-popper-menu

        如果覺得可以的話,采用這個答案,順便給https://gitee.com/rioli/list-popper-menu點個star。

        此外,如果你還想學習更多的前端知識,歡迎關于我的微信公眾號【前端列車長】,在那里我會分享更多前端知識、干貨。

        關注 2 回答 1

        而井 回答了問題 · 4月7日

        如何修改iframe #document 里面的元素樣式

        var iframeDoc = document.querySelector('#qrCode iframe').contentDocument獲取iframe中的document,然后iframeDoc.querySelector('img.qrcode')就可以獲取哪個二維碼元素,接下來就是正常的修改img的size操作就靠你的聰明才智了。

        關注 2 回答 2

        而井 關注了用戶 · 4月7日

        前端小智 @minnanitkong

        我不是什么大牛,我其實想做的就是一個傳播者。內容可能過于基礎,但對于剛入門的人來說或許是一個窗口,一個解惑之窗。我要先堅持分享20年,大家來一起見證吧。

        關注 10193

        而井 回答了問題 · 4月7日

        開源項目 Fork 后NPM 包發布管理

        修改一下package.json中的name名,按照正常的發布npm包流程即可,這里有一篇如何發布npm包的教程npm發布包教程(二):發布包

        關注 2 回答 1

        而井 回答了問題 · 4月7日

        ali-react-table分頁功能?

        本身是用于大數據的表格,沒有提供分頁功能?,F在組件化開發的情況下面,分頁功能并不需要和表格功能進行強綁定。如果你需要分頁功能,可以考慮引進react-paginate,這是使用這個分頁組件的demo代碼

        關注 2 回答 1

        認證與成就

        • 獲得 173 次點贊
        • 獲得 23 枚徽章 獲得 1 枚金徽章, 獲得 6 枚銀徽章, 獲得 16 枚銅徽章

        擅長技能
        編輯

        開源項目 & 著作
        編輯

        • hk-paparazzo

          Paparazzo is a simple, elegant and powerful event module that helps you quickly observe specific events, just like a paparazzi tracking celebrities.The most API style likes the Nodejs's Event module.If you are very skilled at Nodejs's Event module, you can get started quickly.

        • gulp-add-query-fragment

          a simple gulp plugin.Add the query parameter and the fragment anchor to the specified element link。https://www.npmjs.com/package/gulp-add-query-fragment

        • bootstrap-select-search-pro

          增強bootstrap-select插件的搜索功能,使得用戶輸入搜索詞即使匹配不到,也可以作為新選項添加到下拉框并選中。 $('you-selector').selectpicker({ keepInputsWhenNoResult: true //啟動用戶輸入但本來數據中沒有的文本保持在下拉選型中 })

        • is-async-await-supported

          check if async/await is available, it can run in browser and nodejs

        注冊于 2015-06-08
        個人主頁被 2.4k 人瀏覽

        一本到在线是免费观看_亚洲2020天天堂在线观看_国产欧美亚洲精品第一页_最好看的2018中文字幕