Dynamické nahrávání JS souborů, zn. efektivní, i z komponent a na 14 řádcích kódu

Upozornění: Tohle vlákno je hodně staré a informace nemusí být platné pro současné Nette.
Tharos
Člen | 1030
+
+1
-

Ahoj,

na fóru se poměrně často řeší, jak připojit do stránky externí JavaScripty (a styly jakbysmet). Samozřejmě se vždycky dají plácnout někam dovnitř <body></body>, ale řekněme, že hledáme estetičtější řešení. Kritéria jsou, aby to bylo elegantní, výkonné a pokud možno to i umožňovalo lazy vytváření komponent, které si nakonec ale také budou chtít něco málo svého skriptového ke stránce připojit.

Pro řešení tohoto problému již vzniklo několik komponent a já osobně je nedokáži překousnout proto, protože to jsou komponenty. :) Rozumějme potomci Nette\Application\UI\Control. Jde o to, že se mi nelíbí, když se v Presenteru, který si tak hezky řídí životní cyklus aplikace, najednou řeší nějaké JavaScriptové soubory okořeněné špetkou CSS… To jsou věci, které IMHO do Presenteru moc nepatří. A nemyslím si, že by to bylo zbytečné puritánství. Prostě ho to znepřehledňuje a přenáší to do něj věci, které… které mně osobně prostě vyhovuje mít jinde. (I když to lze omluvit tím, že Presenter má za úkol inicializovat view, a tohle tedy do něj tedy taky v jistém smyslu patří.)

Populární WebLoader od Honzy Marka elegantně slučuje závislosti do minimálního počtu souborů, čímž výrazně šetří HTTP požadavky a to je rozhodně fajn věc. Na druhou stranu už není tak černobílé, jestli je tohle rychlejší, protože moderní prohlížeče závislosti stahují paralelně a teoreticky tedy vyjde nastejno, jestli se má stáhnout jeden 10 kB soubor, anebo čtyři 2 kB (neřešme nyní hlavičku 304). Jisté zdroje dokonce hlásají, že paralelní stahování je rychlejší. Předesílám ale, že kdo byste tohle důmyslné slučování odmítli oželet, nemusíte číst dál. :)

Používám řešení, které mi přijde až směšně jednoduché (proto jsem repositár s ním pracovně nazval FoolishLoader), ale přijde mi, že ze všech nejelegantněji řeší mé požadavky. Zajímal by mě váš názor na to, totiž jestli mi třeba někde něco neuniká. :)

Představme si, že máme do stránky připojené knihovny jQuery a Head JS (a třeba i netteForms.js, ale to už není podstatné). Připojme si nyní ještě jeden externí skript s následujícím obsahem:

jQuery(function($) {

	basepath = $('body').data('basepath'); // It is useful to have basepath in global scope

	// load required JavaScripts
	$('[data-js]').each(function(index, element) {
		var script = $(element).data('js');
		if (script instanceof Array) {
			$.each(script, function (index, script) {
				head.js(basepath + script);
			})
		} else {
			head.js(basepath + script);
		}
	});

});

No, a to je vlastně všechno. Fígl je v tom, že jakýkoliv element může obsahovat atribut data-js s názvem skriptu (respektive skriptů, pojme to i JSON pole), které potřebuje, a Head JS se již postará o efektivní paralelní stažení a spuštění.

Úplně nejlepšího výkonu se dá dosáhnout například následujícím layoutem:

<!DOCTYPE html>
<html>
<head>
	<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
	<link rel="stylesheet" media="screen,projection,tv" href="{$basePath}/css/core.css" type="text/css">
	<title>FoolishLoader</title>
</head>

<body data-basePath="{$basePath}">

	{include #content}

	<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js"></script>
	<script src="{$basePath}/libs/head.load.min.js"></script>
	<script src="{$basePath}/libs/netteForms.js"></script>
	<script src="{$basePath}/libs/foolishLoader.js"></script>
</body>
</html>

Pokud někde v šabloně pohledu vykreslíme komponentu s následujícím kódem:

<div id="control" data-js="/js/simpleControl.js">I'm a very simple control.</div>

tak se v ideální moment (tj. při DOM ready) připojí a spustí požadovaný skript a to vše i bez narušení lazy vytváření komponenty a bez znepřehledňování kódu Presenteru.

Když to shrnu, vidím v tomhle přístupu následující výhody:

  1. JS a CSS se řeší ve view vrstvě, nikoliv v Presenteru.
  2. Je to efektivní, komunikace se serverem typicky vypadá nějak takhle (prvotní načtení bez použití cache).
  3. Pokud by nějaká komponenta připojovala již připojený skript, Head JS to pozná a už jej znovu nepřipojuje.
  4. Je to vlastně řešení na zmíněných 14 řádcích kódu, pro vytahování se počítáno bez mezer ;).

Má tohle nějakou zásadní slabinu? Nahrál jsem na GitHub komplexní ukázku, jak to celém může fungovat v praxi (je tam i ta jednoduchá komponenta). Připojovat se takto dají samozřejmě i CSS soubory (definované třeba přes data-css).

Tharos
Člen | 1030
+
0
-
  1. Je to efektivní, komunikace se serverem typicky vypadá nějak takhle (prvotní načtení bez použití cache).

K tomuhle bych ještě rád dodal, že při prvotním nahrávání je mé řešení o něco pomalejší, než například WebLoader. To proto, protože v mém řešeí se skript při DOM ready začíná teprve stahovat, zatímco při využití WebLoaderu již bude pravděpodobně stažen a při DOM ready se už třeba jen spouští. Nicméně tenhle rozdíl se v podstatě smaže v okamžiku, kdy už je skript uložený v cache (pravda, že úplně dokonale také ne, protože v mém řešení se na něj ještě prohlížeč při DOM ready ptá, ale rozdíl už by měl být skutečně minimální).

Matúš Matula
Člen | 257
+
0
-

Ahoj,

myslim ze na fore sa dost riesi pripajanie css a js v ramci nejakeho modulu alebo komponenty, kedy vela ludi (aspon myslim, som medzi nimi:) uprednostnuje mat ich v nejakej adresarovej strukture v ramci modulu/komponenty pre prehladnost a jednoduchsiu znovupouzitelnost. Takto su zvonku nepristupne a je nutne ich nejako spristupnit, to riesi napr. spominany WebLoader skopirovanim do www_root/webtemp.

Pre nacitanie public js je to imo uzitocne, jednoduche, pekne :)

Inak co sa tyka nacitavania css navrhovanym sposobom, tak na ludi s vypnutym js uz hadzes definitivne bobek? :)

Tharos
Člen | 1030
+
0
-

Dobrá poznámka, že závislosti, které mají být uložené mimo veřejné složky, takhle řešit nelze. Já právě tušil, že mi tam ještě někde něco uniká ;).

Co se CSS týče, no, já tohle používám jenom pro JavaScript. :) A to, zda bych na uživatele bez zapnutého JavaScriptu eventuálně kašlal, by záviselo především na cílové skupině toho či onoho webu.

Editoval Tharos (11. 3. 2012 22:29)