Wymagany styl węzła dla javascript w przeglądarce?


85

Czy są jakieś biblioteki dla javascript w przeglądarce, które zapewniają taką samą elastyczność / modułowość / łatwość użycia jak Node require?

Mówiąc bardziej szczegółowo: powód requirejest tak dobry, że:

  1. Umożliwia dynamiczne ładowanie kodu z innych lokalizacji (co moim zdaniem jest lepsze stylistycznie niż łączenie całego kodu w HTML)
  2. Zapewnia spójny interfejs do budowania modułów
  3. Moduły łatwo zależą od innych modułów (więc mógłbym napisać na przykład API, które wymaga jQuery, abym mógł używać jQuery.ajax()
  4. Załadowany javascript ma określony zakres , co oznacza, że ​​mógłbym załadować var dsp = require("dsp.js");i byłbym w stanie uzyskać dostęp dsp.FFT, co nie kolidowałoby z moim lokalnymvar FFT

Nie znalazłem jeszcze biblioteki, która robi to skutecznie. Rozwiązania, których zwykle używam, to:

  • coffeescript-concat - dość łatwo jest wymagać innego js, ​​ale musisz go skompilować, co oznacza, że ​​jest mniej świetny do szybkiego programowania (np. budowanie API w testach)

  • RequireJS - Jest popularny, prosty i rozwiązuje 1-3, ale brak zakresu jest prawdziwym przełomem (uważam, że head.js jest podobny, ponieważ brakuje mu zakresu, chociaż nigdy nie miałem okazji go użyć. Podobnie LABjs może ładować i łagodzić.wait() problemy z zależnościami, ale nadal nie obsługuje zakresu)

O ile wiem, wydaje się, że istnieje wiele rozwiązań dla dynamicznego i / lub asynchronicznego ładowania javascript, ale mają one te same problemy z zakresem, co samo ładowanie js z HTML. Bardziej niż cokolwiek innego, chciałbym mieć sposób na załadowanie javascript, który w ogóle nie zanieczyszcza globalnej przestrzeni nazw, ale nadal pozwala mi ładować i używać bibliotek (tak jak wymaga tego węzeł).

AKTUALIZACJA 2020: Moduły są teraz standardem w ES6, a od połowy 2020 roku są natywnie obsługiwane przez większość przeglądarek . Moduły obsługują ładowanie synchroniczne i asynchroniczne (przy użyciu Promise). Obecnie zalecam, aby większość nowych projektów korzystała z modułów ES6 i transpilera w celu powrotu do pojedynczego pliku JS dla starszych przeglądarek.

Zgodnie z ogólną zasadą, dzisiejsza przepustowość jest zwykle znacznie szersza niż wtedy, gdy zadawałem to pytanie. Tak więc w praktyce możesz rozsądnie zdecydować się zawsze używać transpilera z modułami ES6 i skupić się na wydajności kodu, a nie na sieci.

POPRZEDNIA EDYCJA (lub jeśli nie lubisz modułów ES6): Od czasu napisania tego, intensywnie korzystałem z RequireJS (który ma teraz znacznie bardziej przejrzystą dokumentację). Moim zdaniem RequireJS był naprawdę trafnym wyborem. Chciałbym wyjaśnić, jak ten system działa dla ludzi, którzy są tak zdezorientowani jak ja:

Możesz używać requirew codziennym rozwoju. Moduł może być czymkolwiek zwracanym przez funkcję (zazwyczaj jest to obiekt lub funkcja) i jest objęty zakresem jako parametr. Możesz także skompilować swój projekt w jednym pliku do wdrożenia przy użyciu r.js(w praktyce jest to prawie zawsze szybsze, mimo że requiremożna równolegle ładować skrypty).

Podstawową różnicą między RequireJS a stylem węzłów, których używa browserify (fajny projekt sugerowany przez tjameson), jest sposób, w jaki moduły są projektowane i wymagane:

  • RequireJS używa AMD (Async Module Definition). W AMD requirepobiera listę modułów (pliki javascript) do załadowania i funkcję wywołania zwrotnego. Po załadowaniu każdego z modułów wywołuje wywołanie zwrotne z każdym modułem jako parametr funkcji zwrotnej. Dzięki temu jest naprawdę asynchroniczny i dlatego dobrze nadaje się do sieci.
  • Węzeł używa CommonJS. W CommonJS requirejest to wywołanie blokujące, które ładuje moduł i zwraca go jako obiekt. Działa to dobrze w przypadku Node, ponieważ pliki są odczytywane z systemu plików, co jest wystarczająco szybkie, ale działa słabo w Internecie, ponieważ synchroniczne ładowanie plików może zająć znacznie więcej czasu.

W praktyce wielu programistów korzystało z Node (a tym samym z CommonJS), zanim kiedykolwiek zobaczyli AMD. Ponadto wiele bibliotek / modułów jest napisanych dla CommonJS (przez dodanie rzeczy do exportsobiektu), a nie dla AMD (przez zwrócenie modułu z definefunkcji). Dlatego wielu deweloperów korzystających z Node chce korzystać z bibliotek CommonJS w sieci. Jest to możliwe, ponieważ ładowanie z <script>tagu jest blokowane. Rozwiązania takie jak browserify pobierają moduły CommonJS (Node) i opakowują je, aby można było dołączyć je do tagów skryptów.

Dlatego też, jeśli tworzysz swój własny, wieloplikowy projekt dla sieci, zdecydowanie polecam RequireJS, ponieważ jest to naprawdę modułowy system dla sieci (choć, uczciwie mówiąc, uważam, że AMD jest o wiele bardziej naturalne niż CommonJS). Ostatnio to rozróżnienie straciło na znaczeniu, ponieważ RequireJS pozwala teraz zasadniczo używać składni CommonJS. Dodatkowo, RequireJS może służyć do ładowania modułów AMD w Node (chociaż wolę node-amd-loader ).


1
Uwaga RequireJS faktycznie obsługuje modułowość i może mieć określony zakres. Odkąd o to zapytałem, używam go trochę bardziej intensywnie. Moim zdaniem jest bogaty w funkcje, ale efektywne wykorzystanie wymaga dużej ilości dokumentacji, a zanim będzie doskonały, potrzebuje jakiejś formy pierwszorzędnego ładowania synchronicznego.
Alex Churchill,

1
Czy różnica między byciem asynchronicznym jest tak znacząca? ilekroć potrzebuję kodu, zasadniczo jestem zablokowany, aby móc kontynuować, ponieważ definiuje funkcje i muszę zrobić wszystko ...
Michael

Odpowiedzi:


17

Sprawdź ender . Robi to często.

Ponadto przeglądarka jest całkiem dobra. Użyłem „ require-kiss” ¹ i działa. Prawdopodobnie są inni.

Nie jestem pewien co do RequireJS. To nie to samo, co węzeł. Możesz napotkać problemy z ładowaniem z innych lokalizacji, ale może to zadziałać. O ile istnieje metoda dostarczania lub coś, co można wywołać.

TL; DR - polecam przeglądarkę lub wymagam pocałunku.


Aktualizacja:

1: pocałunek wymagający jest teraz martwy, a autor usunął go. Od tamtej pory używam RequireJS bez problemów. Autor require-kiss napisał pakmanager i pakman . Pełne ujawnienie, współpracuję z programistą.

Osobiście wolę RequireJS. Jest znacznie łatwiejszy do debugowania (możesz mieć oddzielne pliki w trakcie opracowywania i jeden plik wdrożony w środowisku produkcyjnym) i jest zbudowany na solidnym „standardzie”.


Fajnie, jeszcze nie próbowałem browserify, ale wygląda na to, czego potrzebuję.
Alex Churchill

Wydaje się, że link do pocałunku na żądanie jest martwy. Proste (ponowne) wyszukiwanie do niczego nie prowadzi - dokąd to poszło?
Joel Purra,

@JoelPurra - require-kiss został usunięty i zastąpiony przez pakmanager. Polecam teraz require-js. Zaktualizowałem odpowiedź.
beatgammit

tu ładna odpowiedź stary :), proszę sprawdzić pytanie, które właśnie zrobiłem, które jest podobne do tego (ale inne w tym samym czasie)? stackoverflow.com/questions/43237875/…
Webeng

16

Napisałem mały skrypt, który umożliwia asynchroniczne i synchroniczne ładowanie plików Javascript, co może być tutaj przydatne. Nie ma żadnych zależności i jest kompatybilny z Node.js i CommonJS. Instalacja jest dość łatwa:

$ npm install --save @tarp/require

Następnie po prostu dodaj następujące wiersze do kodu HTML, aby załadować moduł główny:

<script src="/node_modules/@tarp/require/require.min.js"></script>
<script>Tarp.require({main: "./scripts/main"});</script>

Wewnątrz modułu głównego (i oczywiście dowolnego modułu podrzędnego) możesz użyć require() co znasz z CommonJS / NodeJS. Pełną dokumentację i kod można znaleźć na GitHub .


1
To niewiarygodnie fajne. Dziękuję za napisanie tego!
OldTimeGuitarGuy

Dziękuję @OldTimeGuitarGuy :)
Torben,

jak używasz funkcji, która jest w main.js? Na przykład main.js ma proste myFunctiondo alert("hello"). Dzwonię main.myFunction()? To nie zadziała?
Brian Wiley

Czy musisz go używać Tarp.require({ expose: true });, aby działał? Jak w twoim teście?
Brian Wiley,

10

Odmiana świetnej odpowiedzi Ilyi Kharlamova , z pewnym kodem, który sprawi, że będzie dobrze działać z narzędziami programistycznymi Chrome.

//
///- REQUIRE FN
// equivalent to require from node.js
function require(url){
    if (url.toLowerCase().substr(-3)!=='.js') url+='.js'; // to allow loading without js suffix;
    if (!require.cache) require.cache=[]; //init cache
    var exports=require.cache[url]; //get from cache
    if (!exports) { //not cached
            try {
                exports={};
                var X=new XMLHttpRequest();
                X.open("GET", url, 0); // sync
                X.send();
                if (X.status && X.status !== 200)  throw new Error(X.statusText);
                var source = X.responseText;
                // fix (if saved form for Chrome Dev Tools)
                if (source.substr(0,10)==="(function("){ 
                    var moduleStart = source.indexOf('{');
                    var moduleEnd = source.lastIndexOf('})');
                    var CDTcomment = source.indexOf('//@ ');
                    if (CDTcomment>-1 && CDTcomment<moduleStart+6) moduleStart = source.indexOf('\n',CDTcomment);
                    source = source.slice(moduleStart+1,moduleEnd-1); 
                } 
                // fix, add comment to show source on Chrome Dev Tools
                source="//@ sourceURL="+window.location.origin+url+"\n" + source;
                //------
                var module = { id: url, uri: url, exports:exports }; //according to node.js modules 
                var anonFn = new Function("require", "exports", "module", source); //create a Fn with module code, and 3 params: require, exports & module
                anonFn(require, exports, module); // call the Fn, Execute the module
                require.cache[url]  = exports = module.exports; //cache obj exported by module
            } catch (err) {
                throw new Error("Error loading module "+url+": "+err);
            }
    }
    return exports; //require returns object exported by module
}
///- END REQUIRE FN

Dziękuję Lucio! Od dawna szukałem takiego minimalnego rozwiązania. Rozszerzyłem go, aby obsługiwał ścieżki względne. Zobacz poniżej.
Trausti Kristjansson

9

Zdaję sobie sprawę, że mogą być początkujący, którzy chcą uporządkować swój kod. Jest rok 2020 i jeśli zastanawiasz się nad modułową aplikacją JS, powinieneś już teraz zacząć od npm i Webpack .

Oto kilka prostych kroków, aby rozpocząć:

  1. W katalogu głównym projektu uruchom, npm init -yaby zainicjować projekt npm
  2. Pobierz pakiet modułów Webpack: npm install webpack webpack-cli
  3. Utwórz plik index.html:
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>App</title>
</head>
<body>

    <script src="_bundle.js"></script>
</body>
</html>

Zwróć szczególną uwagę na _bundle.jsplik - będzie to ostateczny plik JS wygenerowany przez webpacka, nie będziesz go bezpośrednio modyfikować (czytaj dalej).

  1. Utwórz plik, <project-root>/app.jsw którym zaimportujesz inne moduły:
const printHello = require('./print-hello');

printHello();
  1. Utwórz przykładowy print-hello.jsmoduł:
module.exports = function() {
    console.log('Hello World!');
}
  1. Utwórz <project-root>/webpack.config.jsi skopiuj i wklej następujące elementy:
var path = require('path');

module.exports = {
  entry: './app.js',
  output: {
    path: path.resolve(__dirname),
    filename: '_bundle.js'
  }
};

W powyższym kodzie są 2 punkty:

  • wpis app.jsto miejsce, w którym napiszesz swój kod JS. Zaimportuje inne moduły, jak pokazano powyżej.
  • wyjście _bundle.jsto ostateczny pakiet wygenerowany przez webpack. Oto, co zobaczy Twój html na końcu.

-7. Otwórz package.jsi zamień scriptsna następujące polecenie:

  "scripts": {
    "start": "webpack --mode production -w"
  },
  1. I wreszcie uruchomić zegarek skryptu app.jsi wygenerować _bundle.jsplik za pomocą polecenia: npm start.
  2. Miłego kodowania!

1
w 2020 powinno to być oznaczone jako poprawna odpowiedź
p13rnd

5
(function () {
    // c is cache, the rest are the constants
    var c = {},s="status",t="Text",e="exports",E="Error",r="require",m="module",S=" ",w=window;
    w[r]=function R(url) {
        url+=/.js$/i.test(url) ? "" : ".js";// to allow loading without js suffix;
        var X=new XMLHttpRequest(),module = { id: url, uri: url }; //according to the modules 1.1 standard
        if (!c[url])
            try {
                X.open("GET", url, 0); // sync
                X.send();
                if (X[s] && X[s] != 200) 
                    throw X[s+t];
                Function(r, e, m, X['response'+t])(R, c[url]={}, module); // Execute the module
                module[e] && (c[url]=module[e]);
            } catch (x) {
                throw w[E](E+" in "+r+": Can't load "+m+S+url+":"+S+x);
            }
        return c[url];
    }
})();

Lepiej nie używać w produkcji ze względu na blokowanie. (W node.js, require () jest wywołaniem blokującym).


nie powinno być „export: {}” właściwością „module”? a wezwanie be (R, module.exports, module)
Lucio M. Tato

@ LucioM.Tato Nie jestem pewien, nie widzę żadnej wzmianki o module.exports w standardzie Modules 1.1 . Zawsze możesz zadzwonić do require (module.id), aby uzyskać eksport
Ilya Kharlamov

Tak. Masz rację, myślałem o implementacji modułów node.js. Dodałem kilka modyfikacji do kodu w twojej naprawdę dobrej odpowiedzi, aby grał dobrze z Chrome Dev Tools (używam go jako Debug time IDE). Podam kod jako kolejną odpowiedź na to pytanie, na wypadek, gdyby ktoś inny przydał się.
Lucio M. Tato


1

Require-stub - zapewnia zgodność węzła requirew przeglądarce, rozwiązuje zarówno moduły, jak i ścieżki względne. Używa techniki podobnej do TKRequire (XMLHttpRequest). Powstały kod można w pełni przeglądać w przeglądarce, co require-stubmoże służyć jako zamiennik watchify.


0

Oto rozszerzenie fantastycznej odpowiedzi Lucio M. Tato, które pozwala na rekurencyjne ładowanie modułów ze ścieżkami względnymi.

Oto projekt github zawierający rozwiązanie i przykład, jak go używać:

https://github.com/trausti/TKRequire.js

Aby użyć TKRequire.js, umieść następujący wiersz w nagłówku

<script type = "text / javascript" src = "./ TKRequire.js"> </script>

Następnie załaduj moduły tak jak w node.js:

var MyModule = require ("./relative / path / to / MyModule.js");


Dzięki Trausti. Jeśli używasz javascript, powinieneś sprawdzić github.com/luciotato/LiteScript (beta). PD: Na jakim poziomie CC Saga grasz teraz? : P
Lucio M. Tato

linki są martwe, jak możemy pobrać i spojrzeć na Twój kod?
PA.
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.