Dwa tygodnie temu pisałem o RequireJS – sposobie na dynamicznie ładowane moduły w JavaScript. Biblioteka dość ciekawa, i spełniająca stawiane przed nią wymagania. Jednakże nie jest ona jedynym tego rodzaju rozwiązaniem. Dużą zaś zaletą RequireJS jest to, że nie potrzebujemy niczego by z niej skorzystać – działa ona po stronie klienta i tyle. Oczywiście to niesie pewne komplikacja za sobą – pliki konfiguracyjne, narzędzia optymalizujące itp. Itd.. Do tego stopnia, że pisanie aplikacji z wykorzystaniem RequireJS było i jest dość skomplikowane.

Z powodu problemów na jakie ludzie napotykali z wykorzystaniem RequireJS, powstało narzędzie Browserify – na którym oficjalnie miałem zakończyć serię ~2 lata temu. Teraz zaś pewnie może coś jeszcze dopiszę bo i powstały nowe narzędzia ;)

Browserify

browserify

Browserify to narzędzie do zarządzania zależności w ten sam sposób co aplikacje nodowe – dokładnie mówiąc piszemy kod tak jakbyśmy pisali kod bezpośrednio w node, z tą różnicą iż dzięki Browserify będziemy mogli ten kod wykorzystać po stronie klienta. Tak jak RequireJS wymagał od nas stworzenia pliku konfiguracyjnego, pliku startowego plus odpowiednio przygotowanego tagu script, tak Browserify jedyne co wymaga to załadowanie zależności/modułu poprzez metodę require (jak dokładnie to wygląda opisane jest w części jak działa Browserify). Efekt końcowy browserify jest bardziej zbliżony do efektu jaki osiągniemy po optymalizacji w RequireJS – która łączy nam wszystkie zależności w jeden plik.

Browserify nie da nam tego samego co daje RequireJS – dynamicznie ładowanych modułów (ładowanie skryptów nie załadowanych), zaś z miejsca daje już z optymalizowaną wersję, taką która nie wymaga od klienta wykonywania 20-30 dodatkowych odpytań by ściągnąć wszystkie zależności.

Browserify jest narzędziem linii poleceń, które instalujemy globalnie za pomocą NPM:

npm install -g browserify

Od tej pory dostępną mamy komendę browserify – wrócimy do niej później.

Jak działa Browserify?

Jak pamiętacie z ostatniego artykułu, node wykorzystuje implementacje CommonJS do zarządzania zależnościami. Wygląda to mniej więcej tak:

var express = require('express');
var app = express();

app.get('/', function (req, res) {
  res.send('Hello World!');
});

app.listen(3000, function () {
  console.log('Example app listening on port 3000!');
});

To co tutaj jest ważne, to, to, w jaki sposób ładujemy zależności – za pomocą słowa kluczowego require('module'). Dzięki temu Node wie, co i jak ma załadować gdyż dany plik tego wymaga do działania. Oczywiście wszystkie zależności muszą być zainstalowane przez npm:

npm install NAME

Teraz, browserify wykorzystuje to co daje nam node, czyli możemy pisać nasz kod tak jak aplikację nodową – jeżeli chcemy jquery to piszemy require('jquery') itp. Dla przykładu:

// jquery.js local file
var $ = require('jquery'),
    arr = [1,2,3,4,5,6,7,8,9,10];
    
var r = $.grep(arr, function (index, item) {
    if(item % 2 === 1) {
        return item;
    }  
});

console.log(r);

Jak widzicie kod nie różni się zbytnio od tego co byśmy napisali dla Node, z tą różnicą iż on nie zadziała, sprawdźcie sami:

node jquery.js

Dzieje się tak, gdyż nie każdy dostępny plugin npm jest przyjazny nodowi. JQuery wymaga do pracy obiekty window, a tego nie ma w node (są obejście, ale nie ważne).

Ale zróbmy tak, zainstalujmy paczkę is-prime (ostatnio to bardzo popularne), i napiszmy taką o to fajną aplikację:

//isprime.js local file
var isPrime = require('is-prime'),
    arr = [1,2,3,4,5,6,7,8,9,10,11,12,13,14],
    i = 0, 
    l = arr.length;
    
    
for(; i < l; ++i) {
    console.log(arr[i] + ' is prime: ' + isPrime(arr[i]));
}

Teraz jak odpalimy:

node isprime.js

To aplikacja nam zadziała. Czyli mamy require które wiemy, że działa po stronie serwera:

isprime in node
isprime.js in node

Możemy teraz wykorzystać magię browserify, które rozumie takie pliki i tworzy nam z nich plik… dla aplikacji klienckiej. Dokładniej mówiąc czyta on wszystko to co jest wymagane, i łączy w całość tak by powstał plik wynikowy, który możemy wykorzystać w przeglądarce.

By do zrobić wystarczy, że odpalimy komendę:

browserify isprime.js -o client.js

Dzięki czemu zostanie utworzony plik client.js zawierający wszystko to co jest potrzebne by móc ten skrypt odpalić. To samo zresztą możemy zrobić dla przykładu z jquery:

browserify jquery.js -o client-jquery.js

Obydwa pliki zaś wyglądają tak

// Client.js od isprime.js
(function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){
'use strict';

var numberIsInteger = require('number-is-integer');

function isPrime (n) {
 if(n === 1) {
 return false
 }
 if (n === 2 || n === 3) {
 return true;
 }
 else if ( (n % 2 === 0) || (n % 3 === 0) ){
 return false;
 }
 else {
 var p=5;
 var w=2;
 while ( p * p <= n ){
 if (n % p === 0) { return false; }
 p += w;
 w = 6 - w;
 }
 return true;
 }
}

module.exports = function (n) {
 if (typeof n !== 'number') {
 throw new TypeError('Expected a number');
 }
 if(n<=0) {
 throw new Error('The number must be a positive integer');
 }
 if(!numberIsInteger(n)){
 throw new Error('The number must be a integer');
 }
 return isPrime(n);
};

},{"number-is-integer":2}],2:[function(require,module,exports){
'use strict';
var numberIsFinite = require('is-finite');

module.exports = Number.isInteger || function (x) {
 return numberIsFinite(x) && Math.floor(x) === x;
};

},{"is-finite":3}],3:[function(require,module,exports){
'use strict';
var numberIsNan = require('number-is-nan');

module.exports = Number.isFinite || function (val) {
 return !(typeof val !== 'number' || numberIsNan(val) || val === Infinity || val === -Infinity);
};

},{"number-is-nan":4}],4:[function(require,module,exports){
'use strict';
module.exports = Number.isNaN || function (x) {
 return x !== x;
};

},{}],5:[function(require,module,exports){
var isPrime = require('is-prime'),
 arr = [1,2,3,4,5,6,7,8,9,10,11,12,13,14],
 i = 0, 
 l = arr.length;
 
 
for(; i < l; ++i) {
 console.log(arr[i] + ' is prime: ' + isPrime(arr[i]));
}
},{"is-prime":1}]},{},[5]);

Oraz

// Client-jquery od jquery.js
(function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){
var $ = require('jquery'),
 arr = [1,2,3,4,5,6,7,8,9,10];
 
var r = $.grep(arr, function (index, item) {
 if(item % 2 === 1) {
 return item;
 } 
});

console.log(r);
},{"jquery":2}],2:[
 function(require,module,exports){
// jquery code, removed for brevity

},{}]},{},[1]);

Jeżeli teraz dodacie jeden z nich do prostego HTML i otworzycie plik w przeglądarce to zobaczycie oczekiwany wynik w consoli

is-prime in browser
isprime.js in browser
jquery.js in browser
jquery.js in browser

No po prostu zajefajnie :) prosto i przenaszalnie prawie :)

Co jeszcze można powiedzieć…

Tak naprawdę to chyba najważniejsze było powiedziane, możemy pisać aplikację kliencką w taki sam sposób jak serwerową, nie musimy pisać żadnych plików konfiguracyjnych itp., zamiast tego podchodzimy do zależności jako coś, co i tak musi zostać załadowane na stronę kiedy użytkownik ją otwiera. Zamiast dodatkowych requestów mamy już to co jest nam na tej stronie potrzebne. Nic więcej, nic mniej.

W sposobie wykorzystania jest to bardzo proste, a do tego, idealnie da się to połączyć z jakimkolwiek procesem budowania aplikacji, czy to będzie grunt czy gulp, taski są wystarczy je tylko skonfigurować.

Oczywiście, w zależności od problemu możemy na różne sposoby budować nasz plik wyjściowy – jest parę przełączników, ale nie zawsze ich potrzebujemy. Bardzo dużo dobrych informacji na temat wykorzystania browserify można znaleźć w podręczniku dostępny na stronach github.

Co dodatkowego nam daje browserify?

Dzięki temu, że możemy pisać aplikację tak samo na serwer jak na klienta (biorąc pod uwagę te paczki które będą działać…) możemy osiągnąć coś co się zwie aplikacje typu Isomorphic JavaScript (pierwszy raz zdefiniowany term chyba przez Spike Brehm z AirBnB). Czyli coś co działa tak samo na serwerze jak i kliencie, czyli może nastąpić code-share. Oczywiście browserify tego nam nie gwarantuje, ale daje nam taką możliwość, co jest dość ciekawe :)

Podsumowanie

Browserify jest na pewno ciekawym wyjściem z problemów na jakie możemy natrafić z RequireJS, do tego jest moim zdaniem dużo prostszy w obsłudze, ale wymaga Node. Coś za coś. Jeszcze sam nie miałem możliwości wykorzystania go w jakimś większym projekcie, ale w kilku moich pet projects, sprawdził się idealnie. Dlatego zachęcam do jego spróbowania i podzielenia się wrażeniami :)

No chyba, że już z niego korzystaliście? Podoba się on wam? :)

5 KOMENTARZE

  1. @m

    masz racje, niezbyt jasno sie wyrazilem, juz to poprawiam.

    update juz poprawilem, dodalem opis, moim zdaniem jest to juz czytelniejsze i bardziej zrozumiale. jezeli nie, to daj znac, z checia dodam dodatkowe opisy.

  2. Ten blog zawiera bardzo ciekawe artykuły, jednak roi się w nim od błędów tak, że aż w oczy kole. Dla przykładu, pierwszy akapit: “Dwa tygodnie temu pisałem o RequireJS – sposobie na dynamicznie ładowane moduły w JavaScript. Biblioteka JEST dość ciekawa [brak przecinka] i spełniająca stawiane przed nią wymagania. Jednakże nie jest ona jedynym tego rodzaju rozwiązaniem. Dużą zaś zaletą RequireJS jest to, że nie potrzebujemy niczego [przecinek!] by z niej skorzystać – działa ona po stronie klienta i tyle. Oczywiście to niesie pewne komplikacjE za sobą – pliki konfiguracyjne, narzędzia optymalizujące itp. [przecinek] itd.. [wielokropek składający się z 2 kropek?] [przecinek] Do [do] tego stopnia, że pisanie aplikacji z wykorzystaniem RequireJS było i jest dość skomplikowane.”. To TYLKO jeden akapit. To TYLKO jeden artykuł. Te błędy zniechęcają mnie do czytania Twoich tekstów. Twoje wpisy na facebooku również były tak chaotyczne, że zrezygnowałem ze śledzenia Twojego profilu. Panie Jakubie, może by tak pozostać przy samych mowach?

    • dzięki, literówki poprawię.

      Przykro mi, że błędy zniechęcają cię do czytania moich tekstów. Jednak mam takie same podejście do mojego bloga jak Sir Richard Branson w swojej gazecie miał. Richard jest dyslektykiem tak samo jak ja. Mogę z tym walczyć, mogę wydawać setki tysięcy na korektę, albo robić co w mojej mocy by tego było mało ale by się zdarzały. Niektóre artykuły są lepiej napisane, niektóre gorzej. Trudno. Ja się nauczyłem z tym żyć. Wolę widzieć osobiście literówkę niż słyszeć błędnie wymawiane słowa. Ale może dlatego, że słuch mam lepiej wyczulony gdyż właśnie musiałem czymś nadrabiać tę swoją dysleksję?

      Co do Facebooka, to nie wiem, nie rozumiem. Jakbyś podał przykład to może byłbym to wstanie poprawić.

      Tak czy siak, dzięki konstruktywny ? za feedback.

Comments are closed.