ugrás a tartalomhoz

Szinkron dinamikus script betöltés

Karvaly84 · 2011. Feb. 28. (H), 14.02
Helló guruk!

Egy weboldalra szeretnék JavaScript-ből futásidőben JavaScript fájlokat betölteni (nem AJAX-al) úgy, hogy az oldal, vagy éppen az aktuálisan futó JavaScript kód kiértékelése megálljon addig míg a script be nem töltődött, és az abban deklarált változók, függvények elérhetővé nem válnak!

Valami olyasmit szeretnék hogy:

alert(typeof module);   // undefined
require('module');      // szinkron betöltés
alert(typeof module);   // object
Aki tud nem ajax-os megoldást és segíit annak köszönöm!

Közben ki gondoltam, hogy a require függvényben egy while ciklus pöröghetne addig amíg a typeof module értéke undefined vagy le nem jár egy timeout. Csak ez eléggé kontár munka lenne szerintem.
 
1

mootools assets

ironwill · 2011. Feb. 28. (H), 15.16
Szász Tibor mutatta be a mootools-t a februári budapest.js-en és többek közt erről a feature-éről is ejtett pár szót.

http://weblabor.hu/blog/20110209/budapestjs-februar

A működéséről nem igen tudok mit mondani, de, ha mást nem, a google tud segíteni:
mootools assets
2

Na igen ám, csak a bonyolult

Karvaly84 · 2011. Feb. 28. (H), 16.06
Na igen ám, csak a bonyolult keretrendszerek használatát elkerülném ha lehet, valami olyasmi dolog érdekelne, hogy a JavaScript elemeknek be lehet e állítani valami olyan objektum tulajdonságot amitől szinkron töltődik be ha be szúrom a DOM fába pl. Néztem a W3C oldalán a HTMLScriptElement-et de ott nem véltem felfedezni ilyesmit, bár ez nem zárja ki, hogy valahogy ne lehetne kivitelezhető.
4

Injektált script src-t

inf · 2011. Feb. 28. (H), 17.03
Injektált script src-t dom-mal, szerintem az is szinkron, de mindjárt kipróbálom.
Hát az injektálás nem nyert, idő kell, amíg frissül a dom fa, és végrehajtódik a script.

Aszinkron módon könnyebben megoldható, és nem fagy be a böngésző a betöltés közben.
3

Betöltés

Poetro · 2011. Feb. 28. (H), 17.00
Van rengeteg aszinkron kódbetöltő függvénytár. Nem tudom neked miért kell szinkron. A szinkron betöltéssel rengeteg probléma van, főleg azért mivel addig a felhasználó semmit se tud kezdeni a böngészővel, vagy legalábbis azzal a füllel, amíg egy szinkron esemény történik. És ha ez több másodpercig elhúzódik, akkor valószínűleg inkább becsukja az egész alkalmazást a vérbe, és sose látogat vissza az oldalra.
egy while ciklus pöröghetne

Mivel JavaScript egy szálon fut, ezért amíg a while ciklusod pörög, addig semmi más nem történik. Azaz a kód nem inicializálódik stb. Ez nem megoldás.

Mutatok pár megoldást aszinkron kód betöltésre, és hogy azokat hogyan kell használni:

load.js:
load('jquery.js')
  .then('jquery-ui.js', 'jquery-ui-theme.js')
  .then('myscript.js').thenRun(function () {
    alert("itt már be van töltve minden, lehet használni őket");
  });
LABjs:
$LAB.script("jquery.js").wait()
  .script("jquery-ui.js").script("jquery-ui-theme.js").wait()
  .script("myscript.js").wait(function(){
    alert("itt már be van töltve minden, lehet használni őket");
  });
Head JS
head.js("jquery.js", function () {
  head.js("jquery-ui.js", "jquery-ui-theme.js", function () {
    head.js("myscript.js", function() {
     alert("itt már be van töltve minden, lehet használni őket");
    });
  });
});
Természetesen léteznek még szkript betöltő könyvtárak a fentieken kívül, én ezeket használtam, nagy sikerrel.
5

load.js

Poetro · 2011. Feb. 28. (H), 18.38
Ha nem tetszik, hogy a load.js kicsit túl sok globális változót használ, használhatod az én fork-omat belőle. Ekkor csak minimálisan változik a használat:
loadJS.load('jquery.js')  
  .then('jquery-ui.js', 'jquery-ui-theme.js')  
  .then('myscript.js').thenRun(function () {  
    alert("itt már be van töltve minden, lehet használni őket");  
  });
6

-

Karvaly84 · 2011. Feb. 28. (H), 23.57
Köszönöm, hogy törődtök velem, de éppen az ilyen hosszú sorokat szeretném el kerülni, kb olyan megoldásról álmodom mint a PHP-ben a require_once() csak js-ben. Ma nézegettem a neten de nem találtam semmit ami ilyesmit valósítana meg.
7

Mi a probléma?

Poetro · 2011. Már. 1. (K), 00.39
Nem értem mi a problémád. A JavaScript szerencsére nem PHP. amit az imént felírtam, az kb 4 require_once parancs lett volna ami legalább ilyen hosszú.
Ezen kívül természetesen vannak más különbségek is. Például az oldal betöltődése után a szkriptek már mindenképpen aszinkron töltődnek be, akkor is ha a script elemeket te rakod be a kódba. Hozzá kell szokni, hogy a JavaScript így működik. És előbb utóbb örülni fogsz, hogy így működik, és párhuzamosan tud betölteni több fájlt is.
8

szinkron betöltés

T.G · 2011. Már. 2. (Sze), 14.07
Miért "mindenképpen" aszinkron töltődnének be? Ha nem adjuk meg, hogy aszinkron töltődjön be, akkor a script tagok lefutása várakozik, míg van előtte lévő be nem töltött elem.

<?php
if (isset($_GET['t'])) {
	sleep(rand(1, 3));
	die('console.log("' . $_GET['t'] . '");'); 
}
?>
<html><head></head><body>
<script type="text/javascript">
var head = document.getElementsByTagName('head')[0];
for (var i = 0; i < 10; i++) {
	var script = document.createElement('script');
	script.setAttribute('type', 'text/javascript');
	//script.setAttribute('async', 'true');
	script.setAttribute('src', '?t=' + i );
	head.appendChild(script);
}
</script>
</body></html>
9

Aszinkron

Poetro · 2011. Már. 2. (Sze), 15.36
<?php
if (isset($_GET['t'])) {
  sleep(rand(1, 3));
  die('console.log("load", ' . json_encode($_GET['t']) . ');');
}
?>
<!DOCTYPE HTML>
<html lang="en-US">
<head>
  <meta charset="UTF-8">
  <title></title>
</head>
<body>
<script type="text/javascript">
var head = document.getElementsByTagName('head')[0];
for (var i = 0; i < 5; i++) {
  var script = document.createElement('script');
  script.setAttribute('type', 'text/javascript');
  //script.setAttribute('async', 'true');
  script.setAttribute('src', '?t=' + i );
  head.appendChild(script);
  console.log('loop', i);
}
</script>
</body>
</html>
Kimenet:
loop 0
loop 1
loop 2
loop 3
loop 4
load 0
load 1
load 2
load 3
load 4
És a hozzá kapcsolódó vízesés:

Amint látható a szkriptek párhuzamosan töltődnek le, de egymás után futnak. De nem szinkron módon, ugyanis akkor minden loop sor után egy load sor lenne.
10

A szinkron... :)

T.G · 2011. Már. 2. (Sze), 16.48
Szia! Szerintem ez ennél sokkal egyszerűbb, a script tagok sorrendbe futnak le. Az a script, ami létrehozza a többi scriptet az a többi script előtt van, tehát természetes, hogy az előbb végigfut. Szerintem. :)
11

Újra

Karvaly84 · 2011. Ápr. 21. (Cs), 09.22
Helló Guruk!

Ma újra neki ültem és AXAJ-al próbáltam meg megoldani az esetet, de a IE valamit nem kamáz!!! Aszt dobja, hogy: A művelet nem hajtható végre a következő hiba miatt: c00c023f. Ez mit jelent? Egy burkoló objektumot hoztam létre az XHR objektumnak és azon keresztül intézem a kérést:

/**
 * [object _XMLHttpRequest]
 * @constructor
 * @param {String} method
 * @param {String} url
 * @param {Boolean} async
 * @param {String} user
 * @param {String} password
 * @returns {_XMLHttpRequest}
 * @see http://www.w3.org/TR/XMLHttpRequest/
 */
function _XMLHttpRequest() {
    var xhr = new XMLHttpRequest(), _this = this;
//    this._client = xhr;
    this.open = function open() {
        return xhr.open.apply(xhr, arguments);
    };
    this.setRequestHeader = function setRequestHeader() {
        return xhr.setRequestHeader.apply(xhr, arguments);
    };
    this.send = function send() {
        return xhr.send.apply(xhr, arguments);
    };
    this.abort = function abort() {
        return xhr.abort.apply(xhr, arguments);
    };
    this.getResponseHeader = function getResponseHeader() {
        return xhr.getResponseHeader.apply(xhr, arguments);
    };
    this.getAllResponseHeaders = function getAllResponseHeaders() {
        return xhr.getAllResponseHeaders.apply(xhr, arguments);
    };
    xhr.onreadystatechange = function onreadystatechange() {
        // Ennél a metódusnál van a hiba.
        // IE valamiért nem akar.
        if (this.readyState)   _this.readyState   = this.readyState;
        if (this.status)       _this.status       = this.status;
        if (this.statusText)   _this.statusText   = this.statusText;
        if (this.responseText) _this.responseText = this.responseText;
        if (this.responseXML && this.responseXML.childNodes.length) _this.responseXML  = this.responseXML;
        // Ha a fenti sorokat try catch blokkba zárom akkor a végeredmény 
        // IE9-ben jó, 7-ben, 8-ban, nem lesz jó.
        // Ha a try catch blokkokat be iktatjuk akkor a burkolónk
        // ugye nem kapja meg az aktuális "readyState",
        // "status", stb tulajdonságokat. Lehet valamit tenni?
        try {
            _this.onreadystatechange.apply(_this, arguments);
        }
        catch (e) {
        }
    };
    if (arguments.length) this.open.apply(this, arguments);
}
_XMLHttpRequest.prototype = {
    onreadystatechange : null,
    readyState         : 0,
    status             : 0,
    statusText         : '',
    responseText       : '',
    responseXML        : null
};
var _Require = {
    success : {},
    eval : function eval(url) {
        url = url.replace(/^~/, location.protocol + '//' + location.hostname);
        var success = _Require.success;
        if (url in success) return;
        var xhr = new _XMLHttpRequest('GET', url, false);
        xhr.onreadystatechange = function() {
            if (this.readyState === 4 && this.status === 200) {
                window.eval(this.responseText);
                success[url] = this.responseText;
            }
        };
        xhr.send(null);
    }
};
var require = _Require.eval;
require('~/KarvalyJS/karvaly/karvaly.js');
require('http://localhost/KarvalyJS/karvaly/test.js');
alert('karvaly: ' + typeof karvaly + '\ntest: ' + typeof test);
Köszönöm aki segít!
13

Valószínűleg az a gond, hogy

Hidvégi Gábor · 2011. Ápr. 21. (Cs), 12.08
Valószínűleg az a gond, hogy szinkron kérésnél régebbi IE-ben nem fut le az onreadystatechange.
12

Szerintem olyan problémára

Hidvégi Gábor · 2011. Ápr. 21. (Cs), 12.01
Szerintem olyan problémára akarsz megoldást találni, ami fel sem vetődne, ha kicsit jobban ismernéd a HTTP fájlátvitel működését. Ez röviden így néz ki:

A HTTP-ben a legdrágább művelet a fájlkérések kiszolgálása, ami az esetek többségében úgy történik, hogy (a Firebug alapján)
- elindítjuk a kérést a szerver felé a megfelelő HTTP fejlécekkel
- a szerver leellenőrzi a fejlécek alapján, hogy volt-e már nálunk a fájl
- ha nem volt, akkor 200-as HTTP kóddal visszaküldi a fájl tartalmát
- ha volt, akkor 304-es kódot küld, azaz a fájl nem változott

A Weblaboron nyomtam pár frissítést, a műveletek átlagos válaszideje:
- kérés indítása: 16ms
- a kérés feldolgozása a szerveren statikus fájlok esetén: 30ms
- amennyiben még nem volt lenn a tartalom, egy kilobájtot nagyjából egy ezredmásodperc alatt töltök le (bár igazából ez nagyobb fájl [150k] esetén igaz, kisebb fájlok jóval lassabban jönnek le).

Ebből a kérés nettó ideje 16+30 = 46 ms, tehát minden egyes plusz fájlkérés felér 46 kilobájtnyi adat letöltésével. Bizonyos böngészők csak akkor kérik le a következő betöltendő scriptet, amennyiben az előzővel már végeztek, ugyanis a feldolgozásuk nem párhuzamusan történik.

A fentiekből következik, hogy nem éri meg az egyes scripteket külön fájlba tenni, hanem jóval célszerűbb őket egy fájlba pakolni, mert jóval gyorsabb lesz a feldolgozásuk.

Emellett Apache alatt érdemes használni a mod_expires kiterjesztést, a nem változó fájlokat berakni egy külön könyvtárba, és például a következő sorokat betenni egy .htaccess-be vagy a konfigurációs fájlba:

ExpiresActive On
ExpiresByType text/css "access plus 1 month"
ExpiresByType application/x-javascript "access plus 1 month"

Ez azt csinálja, hogy egy hónapig a böngésző még kérést sem indít, hanem mindenképp a gyorsítótárból olvas.

Egy példa még: egy 400k-s js fájl (ext js keretrendszer) feldolgozása a böngészőben letöltés után kb. 300ms egy 1600 MHz-es Athlonon.
14

Disable Cache

Karvaly84 · 2011. Ápr. 22. (P), 06.20
A gyorstárazást ki lőttem, hogy mindig 200-at adjon vissza, de persze majd fel fogok készülni arra is ha esetleg mást ad vissza, most csak a alapot szeretném létre hozni.

Ilyesmi méréseket én is végeztem régebben, tény hogy igazad van sokkal rontja a sebességet ez a ötlet amit ki gondoltam, de egy olyan rendszert szeretnék készíteni ami egyes script-ek függőségét ellenőrzi, és a nagyobb class-okat külön fájlban kezelni.

Valóban a onreadystataechange nem fut le szinkronban, ezt most olvastam én is, ez a újabb böngészőkön nem is baj, csak a régieken, mint a IE 7-8 mert ott arra lettem figyelmes hogy ugyan úgy meghívja, ami ugye a burkoló onreadystatechange függvényét hívja meg, és ezért add hibát mikor le akarsz kérdezni valamit a natív XHR-től a burkolón keresztül, ez persze csak IE-ben van így, ahogy érzékeltem, szóval átírtam:

function HttpRequest() {
    this._xmlHttpRequest = new XMLHttpRequest();
    this._xmlHttpRequest._httpRequest = this;
    if (arguments.length)
        this.open.apply(this, arguments);
}
HttpRequest.prototype = {
    constructor : HttpRequest,
    open : function open() {
    // Itt beállítok a natív objektumnak egy _async tulajdonságot.
        this._xmlHttpRequest._async = (arguments[2] === false) ? false : true;
        this._xmlHttpRequest.open.apply(this._xmlHttpRequest, arguments);
    },
    setRequestHeader : function setRequestHeader() {
        this._xmlHttpRequest.setRequestHeader.apply(this._xmlHttpRequest,
                arguments);
    },
    send : function send() {
    // Itt ellenőrzöm, hogy szinkron küldés lesz e vagy nem, ha igen akkor
    // a natív objektum onreadystatechange tulajdonságát null-ra állitjuk.
    //  - A HttpRequest.onreadystatechange - lentebb.
        this._xmlHttpRequest.onreadystatechange = (this._xmlHttpRequest._async) ? HttpRequest.onreadystatechange
                : null;
        this._xmlHttpRequest.send.apply(this._xmlHttpRequest, arguments);
    },
    abort : function abort() {
        this._xmlHttpRequest.abort.apply(this._xmlHttpRequest, arguments);
    },
    getResponseHeader : function getResponseHeader() {
        return this._xmlHttpRequest.getResponseHeader.apply(
                this._xmlHttpRequest, arguments);
    },
    getAllResponseHeaders : function getAllResponseHeaders() {
        return this._xmlHttpRequest.getAllResponseHeaders.apply(
                this._xmlHttpRequest, arguments);
    },
    onreadystatechange : null,
    readyState : 0,
    status : 0,
    statusText : '',
    responseText : '',
    responseXML : null
};
HttpRequest.onreadystatechange = function onreadystatechange() {
    with (this) {
        if (readyState)
            _httpRequest.readyState = readyState;
        if (status)
            _httpRequest.status = status;
        if (statusText)
            _httpRequest.statusText = statusText;
        if (responseText)
            _httpRequest.responseText = responseText;
        try {
            _httpRequest.onreadystatechange.apply(_httpRequest, arguments);
        } catch (e) {
        }
    }
};
Ezzel már el érem a kívánt működést, így szinkronnál nem állítom be a onreadystatechange-t hanem a send metódus után egyből kérem a responseText-et.
Itt akad egy újjab probléma:

var Script = {
    success : {},
    require : function require(url) {
        url = url.replace(/^~/, location.protocol + '//' + location.hostname);
        var success = Script.success;
        if (url in success) return;
        var xhr = new HttpRequest('GET', url, false);
        xhr.send(null);
    // Itt ez eval-al rengeteg bajom lenne: lentebb leirnám.
        eval(xhr._xmlHttpRequest.responseText);
        alert('karvaly: ' + typeof karvaly);
    }
};
var require = Script.require;
require('~/KarvalyJS/karvaly/karvaly.js');
alert('window.karvaly: ' + typeof karvaly);
1. ha azt írom eval(xhr._xmlHttpRequest.responseText):
- IE 9 - függvényen belül elérem a karvaly-t,
globális scope-ból meg nem,
- IE 8 - ugyan az.
- IE 7 - ugyan az.
- FF 3.6 - ugyan az.
2. ha azt írom window.eval(xhr._xmlHttpRequest.responseText):
- IE 9 - elérhető a globális névtérből is.
- IE 8 - nem érhető el csak függvényen belül.
- IE 7 - it is csak a függvényen belül.
- FF 3.6 - elérhető a globális névtérből is.
3. az eval.call(window, xhr._xmlHttpRequest.responseText) ugyan úgy működött mint a 2. forma.

Nos azt nem tudtam, hogy az eval ilyen nehéz eset. Meglehet azt oldani hogy ha egy függvényen belül egy ilyen kód eval-ozva:

var karvaly = (function(window){
    karvaly = {
        hello : 'szevasz'
    }
    return karvaly;
})(this)
ki értékelődik akkor az a globális névtérben történjen minden böngészőben?
15

A végéhez: marad az, hogy

Hidvégi Gábor · 2011. Ápr. 22. (P), 08.11
A végéhez: marad az, hogy előre létre kell hoznod globálisan a karvaly változót.

Egyébként azt hiszem, értem, hogy mit akarsz csinálni, de nem sok értelmét látom. A Javascript nem PHP, meg nem Java, ezért nem kéne ráerőltetni az azokban megszokott programozási stílust, csak azért, mert ez mostanában annyira divatos.
16

Hali!

Karvaly84 · 2011. Jún. 9. (Cs), 12.51
Gondolkodtam a dolgon, mert ma kellet, és ha valakinek esetleg ugyan erre van szüksége akkor az alábbi kód most már jól müködik:

var script = new function() {
    var success = this.success = {};
    var evaluate = function(src) {
        window.eval(src);
    };
    var exec = this.exec = function(src) {
        if (window.execScript)
            window.execScript(src);
        else
            evaluate.call(window, src);
    };
    this.require = function(url, retry) {
        retry = retry ? true : false;
        url = url.replace(/^~/, location.protocol + '//' + location.hostname);
        if (url in success) {
            if (retry) {
                exec(success[url]);
                return;
            }
            return;
        }
        with (new XMLHttpRequest) {
            open('GET', url, false);
            send(null);
            exec(responseText);
            success[url] = responseText;
        }
    };
};