Czy i kiedy JavaScript przestanie być językiem frontendu Internetu?

0
WeiXiao napisał(a):

A co gdyby Angular, React czy Vue.js pod spodem emitowały WebAssembly?

Tzn. masz na myśli core frameworków byłby napisany w Wasm, czy to, co piszą ludzie używający danego frameworka (np. możliwość kompilowania komponentów React do Wasm)?

Bo w przypadku Reacta, to prędzej bym obstawiał, że core mógłby w przyszłości korzystać z Wasm w jakiejś części i nawet użytkownicy mogliby tego nie odczuć. Tym niemniej same komponenty opierają się na JavaScript, więc wtedy trzeba by przekompilować JS do Wasm, co pewnie by się nie opłacało. Ew. trzeba byłoby zmienić sposób, w jaki się pisze komponenty w React.

W Vue jest o tyle lepiej, że tam niby też jest logika w JS, jednak jest też w komponentach jakiś system szablonów, który łatwiej sparsować i stranspilować.

(tj. porównajmy sobie pętle. Łatwiej byłoby przekompilować pętle w Vue, które opierają się na HTML-opodobnym systemie szablonów: https://vuejs.org/v2/guide/list.html
(tu mamy oczywistość i wprost napisaną pętlę "item in items", więc później jakiś kompilator szablonów mógłby sobie to skompilować w prosty sposób)

 <li v-for="item in items" :key="item.message">
    {{ item.message }}
  </li>

niż analogiczną konstrukcję w React, której w React w sumie nie ma. Każdy sobie na własną rękę robi pętle np. robiąc .map po tablicy JS. A tego już tak łatwo się nie przekompiluje, bo to cały język programowania z dynamicznymi konstruktami, a nie tylko parę znaczników w HTML-podobnym systemie szablonów).

Czyli za dużo JSa w komponentach, żeby dało się to skompilować 1:1 do Wasm. Chociaż kto wie, może developerzy Reacta pewne algorytmy z bebechów przeniosą do Wasma?

1
WeiXiao napisał(a):

Następnie jak chciałbyś to debugować? raz debuggerem rusta, a raz C# czy debuggerem z przeglądarki?

Nieee, do takich rzeczy będzie GraalVM :P

Czy może w ogóle pytasz tylko o to, czy da się wywołać funkcje wasmową wygenerowaną z C#, z wasmu wygenerowanego z Rusta?

Tak, mniej więcej o to. Ogólnie chodzi o możliwości łączenia kodu napisanego w różnych językach źródłowych, by nie okazało się, że niby kompilujemy wszystko do WASMa, ale integracja jest tylko w jedną stronę albo w ogóle w żadną (tzn. cały kod i biblioteki używane w aplikacji muszą być napisane w jednym języku źródłowym).

Pisząc w Scali.js jestem w stanie generalnie skorzystać ze wszystkich dostępnych bibliotek naklepanych w JSie (lub transpilowanych do niego i wystawiających API używalne z poziomu normalnego JSa). Glue code jest bardzo lekki i nie ogranicza (nie trzeba niczego serializować, prawie do wszystkiego do czego jest dostęp z poziomu JSa jest też dostęp z poziomu Scali.js).

1

A co gdyby Angular, React czy Vue.js pod spodem emitowały WebAssembly?

Wrzucam to bardziej jako ciekawostkę niż faktyczne rozwiązanie, bo liczba osób, która z tego korzysta jest znikoma, ale już powstał AssemblyScript (https://www.assemblyscript.org/).

Bazuje on na rozszerzonej składni TypeScript np o dodatkowe typy liczbowe U16, U32, F32 zamiast standardowego number. Duża część rzeczy jest taka sama jak w oryginalnym TypeScript, więc przepisanie silnika frameworka takiego jak Angular nie byłoby, aż takie trudne, ale nie wiem jakby sobie poradził pod względem wydajności.

2

Tak w temacie, oto co bryluje dzisiaj na /r/programming: https://certitude.consulting/blog/en/invisible-backdoor/

0

@Wibowit:

Tak, mniej więcej o to. Ogólnie chodzi o możliwości łączenia kodu napisanego w różnych językach źródłowych, by nie okazało się, że niby kompilujemy wszystko do WASMa, ale integracja jest tylko w jedną stronę albo w ogóle w żadną (tzn. cały kod i biblioteki używane w aplikacji muszą być napisane w jednym języku źródłowym).

Z uwagi na to że nie chce mi się reverse engineerować Blazora, to wygenerowałem WASMA z C:

emcc hello.c -s WASM=1 -o hello.html

#include <stdio.h>

int test_xiao() // to się niewexportowało bo zapomniałem dodać param -s EXPORT_ALL=1 
{
        return 123;
}

int main() {
        return 1234;
}

dla którego WASM 2 WAT wygląda tak:

(module
  (type (;0;) (func (result i32)))
  (type (;1;) (func (param i32)))
  (type (;2;) (func (param i32) (result i32)))
  (type (;3;) (func))
  (type (;4;) (func (param i32 i32) (result i32)))
  (type (;5;) (func (param i32 i32 i32) (result i32)))
  (type (;6;) (func (param i32 i64 i32) (result i64)))
  (func (;0;) (type 3)
    call 6)
  (func (;1;) (type 0) (result i32)
    (local i32 i32 i32 i32 i32)
    global.get 0
    local.set 0
    i32.const 16
    local.set 1
    local.get 0
    local.get 1
    i32.sub
    local.set 2
    i32.const 0
    local.set 3
    local.get 2
    local.get 3
    i32.store offset=12
    i32.const 1234
    local.set 4
    local.get 4
    return)
  (func (;2;) (type 4) (param i32 i32) (result i32)
    (local i32)
    call 1
    local.set 2
    local.get 2
    return)
  (func (;3;) (type 0) (result i32)
    global.get 0)
  (func (;4;) (type 1) (param i32)
    local.get 0
    global.set 0)
  (func (;5;) (type 2) (param i32) (result i32)
    (local i32 i32)
    global.get 0
    local.get 0
    i32.sub
    i32.const -16
    i32.and
    local.tee 1
    global.set 0
    local.get 1)
  (func (;6;) (type 3)
    i32.const 5243936
    global.set 2
    i32.const 1044
    i32.const 15
    i32.add
    i32.const -16
    i32.and
    global.set 1)
  (func (;7;) (type 0) (result i32)
    global.get 0
    global.get 1
    i32.sub)
  (func (;8;) (type 0) (result i32)
    global.get 1)
  (func (;9;) (type 2) (param i32) (result i32)
    i32.const 1)
  (func (;10;) (type 1) (param i32))
  (func (;11;) (type 1) (param i32))
  (func (;12;) (type 1) (param i32))
  (func (;13;) (type 0) (result i32)
    i32.const 1024
    call 11
    i32.const 1032)
  (func (;14;) (type 3)
    i32.const 1024
    call 12)
  (func (;15;) (type 2) (param i32) (result i32)
    (local i32 i32)
    block  ;; label = @1
      block  ;; label = @2
        local.get 0
        i32.eqz
        br_if 0 (;@2;)
        block  ;; label = @3
          local.get 0
          i32.load offset=76
          i32.const -1
          i32.gt_s
          br_if 0 (;@3;)
          local.get 0
          call 16
          return
        end
        local.get 0
        call 9
        local.set 1
        local.get 0
        call 16
        local.set 2
        local.get 1
        i32.eqz
        br_if 1 (;@1;)
        local.get 0
        call 10
        local.get 2
        return
      end
      i32.const 0
      local.set 2
      block  ;; label = @2
        i32.const 0
        i32.load offset=1036
        i32.eqz
        br_if 0 (;@2;)
        i32.const 0
        i32.load offset=1036
        call 15
        local.set 2
      end
      block  ;; label = @2
        call 13
        i32.load
        local.tee 0
        i32.eqz
        br_if 0 (;@2;)
        loop  ;; label = @3
          i32.const 0
          local.set 1
          block  ;; label = @4
            local.get 0
            i32.load offset=76
            i32.const 0
            i32.lt_s
            br_if 0 (;@4;)
            local.get 0
            call 9
            local.set 1
          end
          block  ;; label = @4
            local.get 0
            i32.load offset=20
            local.get 0
            i32.load offset=28
            i32.le_u
            br_if 0 (;@4;)
            local.get 0
            call 16
            local.get 2
            i32.or
            local.set 2
          end
          block  ;; label = @4
            local.get 1
            i32.eqz
            br_if 0 (;@4;)
            local.get 0
            call 10
          end
          local.get 0
          i32.load offset=56
          local.tee 0
          br_if 0 (;@3;)
        end
      end
      call 14
    end
    local.get 2)
  (func (;16;) (type 2) (param i32) (result i32)
    (local i32 i32)
    block  ;; label = @1
      local.get 0
      i32.load offset=20
      local.get 0
      i32.load offset=28
      i32.le_u
      br_if 0 (;@1;)
      local.get 0
      i32.const 0
      i32.const 0
      local.get 0
      i32.load offset=36
      call_indirect (type 5)
      drop
      local.get 0
      i32.load offset=20
      br_if 0 (;@1;)
      i32.const -1
      return
    end
    block  ;; label = @1
      local.get 0
      i32.load offset=4
      local.tee 1
      local.get 0
      i32.load offset=8
      local.tee 2
      i32.ge_u
      br_if 0 (;@1;)
      local.get 0
      local.get 1
      local.get 2
      i32.sub
      i64.extend_i32_s
      i32.const 1
      local.get 0
      i32.load offset=40
      call_indirect (type 6)
      drop
    end
    local.get 0
    i32.const 0
    i32.store offset=28
    local.get 0
    i64.const 0
    i64.store offset=16
    local.get 0
    i64.const 0
    i64.store offset=4 align=4
    i32.const 0)
  (func (;17;) (type 0) (result i32)
    i32.const 1040)
  (table (;0;) 1 1 funcref)
  (memory (;0;) 256 256)
  (global (;0;) (mut i32) (i32.const 5243936))
  (global (;1;) (mut i32) (i32.const 0))
  (global (;2;) (mut i32) (i32.const 0))
  (export "memory" (memory 0))
  (export "__wasm_call_ctors" (func 0))
  (export "main" (func 2))
  (export "__indirect_function_table" (table 0))
  (export "fflush" (func 15))
  (export "__errno_location" (func 17))
  (export "stackSave" (func 3))
  (export "stackRestore" (func 4))
  (export "stackAlloc" (func 5))
  (export "emscripten_stack_init" (func 6))
  (export "emscripten_stack_get_free" (func 7))
  (export "emscripten_stack_get_end" (func 8)))

oraz drugi programik LLVM IR:

; ModuleID = 'main.ll'
source_filename = "main.ll"

; Function Attrs: nofree norecurse nosync nounwind readnone willreturn mustprogress
define dso_local i32 @Test(i32 %0, i32 %1) local_unnamed_addr #0 {
  ret i32 5
}

; Function Attrs: nofree norecurse nosync nounwind readnone willreturn mustprogress
define dso_local i32 @Test2() local_unnamed_addr #0 {
  ret i32 23
}

attributes #0 = { nofree norecurse nosync nounwind readnone willreturn mustprogress }

WASM 2 WAT:

(module
  (type (;0;) (func))
  (type (;1;) (func (param i32 i32) (result i32)))
  (type (;2;) (func (result i32)))
  (func $__wasm_call_ctors (type 0))
  (func $Test (type 1) (param i32 i32) (result i32)
    i32.const 5)
  (func $Test2 (type 2) (result i32)
    i32.const 23)
  (memory (;0;) 2)
  (global (;0;) (mut i32) (i32.const 66560))
  (global (;1;) i32 (i32.const 1024))
  (global (;2;) i32 (i32.const 1024))
  (global (;3;) i32 (i32.const 1024))
  (global (;4;) i32 (i32.const 66560))
  (global (;5;) i32 (i32.const 0))
  (global (;6;) i32 (i32.const 1))
  (export "memory" (memory 0))
  (export "__wasm_call_ctors" (func $__wasm_call_ctors))
  (export "Test" (func $Test))
  (export "Test2" (func $Test2))
  (export "__dso_handle" (global 1))
  (export "__data_end" (global 2))
  (export "__global_base" (global 3))
  (export "__heap_base" (global 4))
  (export "__memory_base" (global 5))
  (export "__table_base" (global 6)))

No to jak się przyjrzysz na sam format tego WebAssemblyTextFormat, no to widać że łatwo to można złączyć, co akurat przetestowałem z ręki:

(module
  (type (;0;) (func (result i32)))
  (type (;1;) (func (param i32)))
  (type (;2;) (func (param i32) (result i32)))
  (type (;3;) (func))
  (type (;4;) (func (param i32 i32) (result i32)))
  (type (;5;) (func (param i32 i32 i32) (result i32)))
  (type (;6;) (func (param i32 i64 i32) (result i64)))
  (type (;7;) (func))
  (type (;8;) (func (param i32 i32) (result i32)))
  (type (;9;) (func (result i32)))
  (func (;0;) (type 3)
    call 6)
  (func (;1;) (type 0) (result i32)
    (local i32 i32 i32 i32 i32)
    global.get 0
    local.set 0
    i32.const 16
    local.set 1
    local.get 0
    local.get 1
    i32.sub
    local.set 2
    i32.const 0
    local.set 3
    local.get 2
    local.get 3
    i32.store offset=12
    i32.const 1234
    local.set 4
    local.get 4
    return)
  (func (;2;) (type 4) (param i32 i32) (result i32)
    (local i32)
    call 1
    local.set 2
    local.get 2
    return)
  (func (;3;) (type 0) (result i32)
    global.get 0)
  (func (;4;) (type 1) (param i32)
    local.get 0
    global.set 0)
  (func (;5;) (type 2) (param i32) (result i32)
    (local i32 i32)
    global.get 0
    local.get 0
    i32.sub
    i32.const -16
    i32.and
    local.tee 1
    global.set 0
    local.get 1)
  (func (;6;) (type 3)
    i32.const 5243936
    global.set 2
    i32.const 1044
    i32.const 15
    i32.add
    i32.const -16
    i32.and
    global.set 1)
  (func (;7;) (type 0) (result i32)
    global.get 0
    global.get 1
    i32.sub)
  (func (;8;) (type 0) (result i32)
    global.get 1)
  (func (;9;) (type 2) (param i32) (result i32)
    i32.const 1)
  (func (;10;) (type 1) (param i32))
  (func (;11;) (type 1) (param i32))
  (func (;12;) (type 1) (param i32))
  (func (;13;) (type 0) (result i32)
    i32.const 1024
    call 11
    i32.const 1032)
  (func (;14;) (type 3)
    i32.const 1024
    call 12)
  (func (;15;) (type 2) (param i32) (result i32)
    (local i32 i32)
    block  ;; label = @1
      block  ;; label = @2
        local.get 0
        i32.eqz
        br_if 0 (;@2;)
        block  ;; label = @3
          local.get 0
          i32.load offset=76
          i32.const -1
          i32.gt_s
          br_if 0 (;@3;)
          local.get 0
          call 16
          return
        end
        local.get 0
        call 9
        local.set 1
        local.get 0
        call 16
        local.set 2
        local.get 1
        i32.eqz
        br_if 1 (;@1;)
        local.get 0
        call 10
        local.get 2
        return
      end
      i32.const 0
      local.set 2
      block  ;; label = @2
        i32.const 0
        i32.load offset=1036
        i32.eqz
        br_if 0 (;@2;)
        i32.const 0
        i32.load offset=1036
        call 15
        local.set 2
      end
      block  ;; label = @2
        call 13
        i32.load
        local.tee 0
        i32.eqz
        br_if 0 (;@2;)
        loop  ;; label = @3
          i32.const 0
          local.set 1
          block  ;; label = @4
            local.get 0
            i32.load offset=76
            i32.const 0
            i32.lt_s
            br_if 0 (;@4;)
            local.get 0
            call 9
            local.set 1
          end
          block  ;; label = @4
            local.get 0
            i32.load offset=20
            local.get 0
            i32.load offset=28
            i32.le_u
            br_if 0 (;@4;)
            local.get 0
            call 16
            local.get 2
            i32.or
            local.set 2
          end
          block  ;; label = @4
            local.get 1
            i32.eqz
            br_if 0 (;@4;)
            local.get 0
            call 10
          end
          local.get 0
          i32.load offset=56
          local.tee 0
          br_if 0 (;@3;)
        end
      end
      call 14
    end
    local.get 2)
  (func (;16;) (type 2) (param i32) (result i32)
    (local i32 i32)
    block  ;; label = @1
      local.get 0
      i32.load offset=20
      local.get 0
      i32.load offset=28
      i32.le_u
      br_if 0 (;@1;)
      local.get 0
      i32.const 0
      i32.const 0
      local.get 0
      i32.load offset=36
      call_indirect (type 5)
      drop
      local.get 0
      i32.load offset=20
      br_if 0 (;@1;)
      i32.const -1
      return
    end
    block  ;; label = @1
      local.get 0
      i32.load offset=4
      local.tee 1
      local.get 0
      i32.load offset=8
      local.tee 2
      i32.ge_u
      br_if 0 (;@1;)
      local.get 0
      local.get 1
      local.get 2
      i32.sub
      i64.extend_i32_s
      i32.const 1
      local.get 0
      i32.load offset=40
      call_indirect (type 6)
      drop
    end
    local.get 0
    i32.const 0
    i32.store offset=28
    local.get 0
    i64.const 0
    i64.store offset=16
    local.get 0
    i64.const 0
    i64.store offset=4 align=4
    i32.const 0)
  (func (;17;) (type 0) (result i32)
    i32.const 1040)
	
	(func $__wasm_call_ctors (type 7))
	(func $Test (type 8) (param i32 i32) (result i32)
	i32.const 5)
	(func $Test2 (type 9) (result i32)
	i32.const 23)
	
  (table (;0;) 1 1 funcref)
  (memory (;0;) 256 256)
  (global (;0;) (mut i32) (i32.const 5243936))
  (global (;1;) (mut i32) (i32.const 0))
  (global (;2;) (mut i32) (i32.const 0))
  (global (;3;) (mut i32) (i32.const 66560))
  (global (;4;) i32 (i32.const 1024))
  (global (;5;) i32 (i32.const 1024))
  (global (;6;) i32 (i32.const 1024))
  (global (;7;) i32 (i32.const 66560))
  (global (;8;) i32 (i32.const 0))
  (global (;9;) i32 (i32.const 1))
  (export "memory" (memory 0))
  (export "__wasm_call_ctors" (func 0))
  (export "main" (func 2))
  (export "__indirect_function_table" (table 0))
  (export "fflush" (func 15))
  (export "__errno_location" (func 17))
  (export "stackSave" (func 3))
  (export "stackRestore" (func 4))
  (export "stackAlloc" (func 5))
  (export "emscripten_stack_init" (func 6))
  (export "emscripten_stack_get_free" (func 7))
  (export "emscripten_stack_get_end" (func 8)))
  (export "__wasm_call_ctors" (func $__wasm_call_ctors))
  (export "Test" (func $Test))
  (export "Test2" (func $Test2))
  (export "__dso_handle" (global 3))
  (export "__data_end" (global 4))
  (export "__global_base" (global 5))
  (export "__heap_base" (global 6))
  (export "__memory_base" (global 7))
  (export "__table_base" (global 8)))

I oto rezultat:

screenshot-20211111173539.png

czyl idziała :)

No generalnie jeżeli ktoś/coś faktycznie generuje pure-wasm, a nie generuje wasm czegoś, co będzie mieliło kod (e.g Mono), to nie widzę problemu.

1
LukeJL napisał(a):

C# ma spore grono fanów, ale przecież nie wszyscy znają C#. Co jeśli ktoś zrobi w przyszłości coś jak Blazor, tylko do Pythona? Albo do PHPa? I wyjdzie na to, że każdy będzie pisać frontend w czym mu się podoba? Może będzie ileś obozów.

Teraz też są obozy, tylko w jednym języku. :)
Myślę, że to by było całkiem dobre rozwiązanie, gdyby każdy mógł pisać backend i frontend w swoim jednym, ulubionym języku, jak za starych, dobrych czasów.

3

Odpowiadając na pytanie to uważam, że prawdopodobnie dopóki internet będzie wyglądał tak jak dzisiaj tak długo JS będzie jego domyślnym frontendem.
Niedawno w PHPArchitect był fajny artykuł o tytule "PHP is the worst", który opisywał to zjawisko z perspektywy PHP i kilku innych języków typu C.

Teza artykuł można by streścić tak, że wygrywają nie "najlepsze/najładniejsze" języki, tylko te bardziej "populistyczne", które najpierw dadzą użytkownikowi to o co on prosi (nawet jak to jest złe), a potem w odpowiednim momencie się "cywilizują" i znajdują balans między zaspokajaniem użytkownika a dobrymi praktykami. Wiele popularnych dziś języków przechodziło ten scenariusz i JS robi dokładnie to samo. Jak było zapotrzebowanie na "animowane guziczki", to byl JS, który nadawał się do animowania guziczków, teraz JS ewoluuje i zawsze jest poniżej oczekiwania "hardkorowych geeków", ale w zupełności zaspokaja potrzeby przeciętnego frontendowca, więc nie ma impulsu do zmiany.

Stworzenie czegoś co zastąpi JS/PHP czy cokolwiek popularnego jest wielkim wyzwaniem, bo język tak na prawdę jest drugorzędny - ważniejszy jest ekosystem, a trudno zbudować ekosystem w czymś niszowym jak tuż za rogiem masz odpowiedzi na wszystkie problemy tu i teraz. Nawet giganci łamią sobie na tym zęby, więc chyba trzeba się po prostu pogodzić z tym, że JS zostanie z nami jeszcze długo. Podobnie jest z PHP na którym wszyscy wieszają psy, a ja mam wrażenie, że do mojej emerytury w PHP dociągnę.

1

Ja bardzo lubię JavaScript, poza tym aby użyć WASM trzeba użyć specjalnych narzędzi do jego skompilowania i zintegrowania z dokumentem HTML. Wstawienie znacznika <SCRIPT> jest dużo łatwiejsze. Złe w JavaScripcie są te nowe dziwaczne dodatki, ale bez nich jest to przecież normalny język skryptowy.

3

Nie rozumiem trochę tematu.

To tak jakby ktoś się zastanawiał dlaczego mcdonald nie oferuje dań z górnej półki.

No cóż, czasem liczy się ilość, a nie jakość i tak samo jest z javascriptem, windowsem i innymi podrzędnymi rzeczami. Tu ludzie po prostu nie przejmują się tym co wybierają, kierują się opinią i faktem, że ich nie stać na więcej.

1 użytkowników online, w tym zalogowanych: 0, gości: 1