загружать скрипты асинхронно - PullRequest
115 голосов
/ 11 октября 2011

Я использую несколько плагинов, пользовательских виджетов и некоторых других библиотек из JQuery. В результате у меня есть несколько файлов .js и .css. Мне нужно создать загрузчик для моего сайта, потому что это требует времени для загрузки. было бы хорошо, если бы я мог отобразить загрузчик, прежде чем импортировать все:

<script type="text/javascript" src="js/jquery-1.6.2.min.js"></script>
<script type="text/javascript" src="js/myFunctions.js"></script>
<link type="text/css" href="css/main.css" rel="stylesheet" />
... 
....
 etc

Я нашел несколько учебных пособий, которые позволяют мне асинхронно импортировать библиотеку JavaScript. например, я могу сделать что-то вроде:

  (function () {
        var s = document.createElement('script');
        s.type = 'text/javascript';
        s.async = true;
        s.src = 'js/jquery-ui-1.8.16.custom.min.js';
        var x = document.getElementsByTagName('script')[0];
        x.parentNode.insertBefore(s, x);
    })();

по какой-то причине, когда я делаю одно и то же для всех своих файлов, страницы не работают. Я так долго пытался найти, где проблема, но я просто не могу ее найти. Сначала я подумал, что это возможно потому, что некоторые функции javascript зависят от других. но я загрузил их в правильном порядке, используя функцию тайм-аута, когда одна завершена, я продолжил со следующей, и страница все еще ведет себя странно например, я не могу нажимать на ссылки и т.д. ... анимации все еще работают, хотя ..

1009 * В любом случае * Вот о чем я думал ... Я считаю, что браузеры имеют кеш, поэтому загрузка страницы в первый раз занимает много времени, а в следующий раз - быстро. так что я думаю о том, чтобы заменить мою страницу index.html страницей, которая загружает все эти файлы асинхронно. когда Ajax завершит загрузку всех этих файлов, перенаправьте на страницу, которую я планирую использовать. при использовании этой страницы загрузка не займет много времени, так как файлы должны быть уже включены в кеш браузера. на моей странице индекса (страница, где файлы .js и .css загружаются асинхронно), я не забочусь об ошибках. Я просто покажу загрузчик и перенаправлю страницу, когда закончу ... Является ли эта идея хорошей альтернативой? или я должен продолжать пытаться реализовать асинхронные методы? EDIT

способ загрузки всего асинхронного выглядит так:

importScripts();

function importScripts()
{
    //import: jquery-ui-1.8.16.custom.min.js
    getContent("js/jquery-1.6.2.min.js",function (code) {
                var s = document.createElement('script');
                s.type = 'text/javascript';
                //s.async = true;
                s.innerHTML=code;
                var x = document.getElementsByTagName('script')[0];
                x.parentNode.insertBefore(s, x);
                setTimeout(insertNext1,1);
            });


    //import: jquery-ui-1.8.16.custom.min.js
    function insertNext1()
    {
        getContent("js/jquery-ui-1.8.16.custom.min.js",function (code) {
                    var s = document.createElement('script');
                    s.type = 'text/javascript';
                    s.innerHTML=code;
                    var x = document.getElementsByTagName('script')[0];
                    x.parentNode.insertBefore(s, x);
                    setTimeout(insertNext2,1);
                });
    }

    //import: jquery-ui-1.8.16.custom.css
    function insertNext2()
    {

        getContent("css/custom-theme/jquery-ui-1.8.16.custom.css",function (code) {
                    var s = document.createElement('link');
                    s.type = 'text/css';
                    s.rel ="stylesheet";
                    s.innerHTML=code;
                    var x = document.getElementsByTagName('script')[0];
                    x.parentNode.insertBefore(s, x);
                    setTimeout(insertNext3,1);
                });
    }

    //import: main.css
    function insertNext3()
    {

        getContent("css/main.css",function (code) {
                    var s = document.createElement('link');
                    s.type = 'text/css';
                    s.rel ="stylesheet";
                    s.innerHTML=code;
                    var x = document.getElementsByTagName('script')[0];
                    x.parentNode.insertBefore(s, x);
                    setTimeout(insertNext4,1);
                });
    }

    //import: jquery.imgpreload.min.js
    function insertNext4()
    {
        getContent("js/farinspace/jquery.imgpreload.min.js",function (code) {
                    var s = document.createElement('script');
                    s.type = 'text/javascript';
                    s.innerHTML=code;
                    var x = document.getElementsByTagName('script')[0];
                    x.parentNode.insertBefore(s, x);
                    setTimeout(insertNext5,1);
                });
    }


    //import: marquee.js
    function insertNext5()
    {
        getContent("js/marquee.js",function (code) {
                    var s = document.createElement('script');
                    s.type = 'text/javascript';
                    s.innerHTML=code;
                    var x = document.getElementsByTagName('script')[0];
                    x.parentNode.insertBefore(s, x);
                    setTimeout(insertNext6,1);
                });
    }


    //import: marquee.css
    function insertNext6()
    {

        getContent("css/marquee.css",function (code) {
                    var s = document.createElement('link');
                    s.type = 'text/css';
                    s.rel ="stylesheet";
                    s.innerHTML=code;
                    var x = document.getElementsByTagName('script')[0];
                    x.parentNode.insertBefore(s, x);
                    setTimeout(insertNext,1);
                });
    }



    function insertNext()
    {
        setTimeout(pageReadyMan,10);        
    }
}


// get the content of url and pass that content to specified function
function getContent( url, callBackFunction )
{
     // attempt to create the XMLHttpRequest and make the request
     try
     {
        var asyncRequest; // variable to hold XMLHttpRequest object
        asyncRequest = new XMLHttpRequest(); // create request object

        // register event handler
        asyncRequest.onreadystatechange = function(){
            stateChange(asyncRequest, callBackFunction);
        } 
        asyncRequest.open( 'GET', url, true ); // prepare the request
        asyncRequest.send( null ); // send the request
     } // end try
     catch ( exception )
     {
        alert( 'Request failed.' );
     } // end catch
} // end function getContent

// call function whith content when ready
function stateChange(asyncRequest, callBackFunction)
{
     if ( asyncRequest.readyState == 4 && asyncRequest.status == 200 )
     {
           callBackFunction(asyncRequest.responseText);
     } // end if
} // end function stateChange

и странная часть заключается в том, что вся работа стиля плюс все функции javascript. хотя страница почему-то заморожена ...

Ответы [ 19 ]

1 голос
/ 04 мая 2018

Я написал небольшой пост, чтобы помочь с этим, вы можете прочитать больше здесь https://timber.io/snippets/asynchronously-load-a-script-in-the-browser-with-javascript/,, но я прикрепил вспомогательный класс ниже. Он автоматически будет ждать загрузки скрипта и вернет указанный атрибут окна, как только это произойдет.

export default class ScriptLoader {
  constructor (options) {
    const { src, global, protocol = document.location.protocol } = options
    this.src = src
    this.global = global
    this.protocol = protocol
    this.isLoaded = false
  }

  loadScript () {
    return new Promise((resolve, reject) => {
      // Create script element and set attributes
      const script = document.createElement('script')
      script.type = 'text/javascript'
      script.async = true
      script.src = `${this.protocol}//${this.src}`

      // Append the script to the DOM
      const el = document.getElementsByTagName('script')[0]
      el.parentNode.insertBefore(script, el)

      // Resolve the promise once the script is loaded
      script.addEventListener('load', () => {
        this.isLoaded = true
        resolve(script)
      })

      // Catch any errors while loading the script
      script.addEventListener('error', () => {
        reject(new Error(`${this.src} failed to load.`))
      })
    })
  }

  load () {
    return new Promise(async (resolve, reject) => {
      if (!this.isLoaded) {
        try {
          await this.loadScript()
          resolve(window[this.global])
        } catch (e) {
          reject(e)
        }
      } else {
        resolve(window[this.global])
      }
    })
  }
}

Использование выглядит так:

const loader = new Loader({
    src: 'cdn.segment.com/analytics.js',
    global: 'Segment',
})

// scriptToLoad will now be a reference to `window.Segment`
const scriptToLoad = await loader.load()
1 голос
/ 11 октября 2011

Я бы посоветовал вам взглянуть на Modernizr . Это небольшая облегченная библиотека, в которую вы можете асинхронно загружать свой javascript с помощью функций, которые позволяют вам проверить, загружен ли файл, и выполнить сценарий в другом указанном вами месте.

Вот пример загрузки jquery:

Modernizr.load([
  {
    load: '//ajax.googleapis.com/ajax/libs/jquery/1.6.1/jquery.js',
    complete: function () {
      if ( !window.jQuery ) {
            Modernizr.load('js/libs/jquery-1.6.1.min.js');
      }
    }
  },
  {
    // This will wait for the fallback to load and
    // execute if it needs to.
    load: 'needs-jQuery.js'
  }
]);
0 голосов
/ 15 марта 2017

Рассматривали ли вы использовать Fetch Injection?Я прокрутил библиотеку с открытым исходным кодом под названием fetch-inject для обработки подобных случаев.Вот как может выглядеть ваш загрузчик с использованием библиотеки lib:

fetcInject([
  'js/jquery-1.6.2.min.js',
  'js/marquee.js',
  'css/marquee.css',
  'css/custom-theme/jquery-ui-1.8.16.custom.css',
  'css/main.css'
]).then(() => {
  'js/jquery-ui-1.8.16.custom.min.js',
  'js/farinspace/jquery.imgpreload.min.js'
})

Для обнаружения возможностей обратной совместимости и возврата к элементам XHR Injection или Script DOM или просто вставьте теги на страницу, используя document.write.

0 голосов
/ 11 октября 2011

Ну, x.parentNode возвращает элемент HEAD, поэтому вы вставляете скрипт непосредственно перед тегом head.Может быть, в этом проблема.

Попробуйте вместо x.parentNode.appendChild().

0 голосов
/ 26 ноября 2015

Вот мое собственное решение для устранения JavaScript, блокирующего рендеринг:

// put all your JS files here, in correct order
const libs = {
  "jquery": "https://code.jquery.com/jquery-2.1.4.min.js",
  "bxSlider": "https://cdnjs.cloudflare.com/ajax/libs/bxslider/4.2.5/jquery.bxslider.min.js",
  "angular": "https://ajax.googleapis.com/ajax/libs/angularjs/1.5.0-beta.2/angular.min.js",
  "ngAnimate": "https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.5.0-beta.2/angular-animate.min.js"
}
const loadedLibs = {}
let counter = 0

const loadAsync = function(lib) {
  var http = new XMLHttpRequest()
  http.open("GET", libs[lib], true)
  http.onload = () => {
    loadedLibs[lib] = http.responseText
    if (++counter == Object.keys(libs).length) startScripts()
  }
  http.send()
}

const startScripts = function() {
  for (var lib in libs) eval(loadedLibs[lib])
  console.log("allLoaded")
}

for (var lib in libs) loadAsync(lib)

Короче говоря, он загружает все ваши скрипты асинхронно, а затем выполняет их последовательно.

Githubрепо : https://github.com/mudroljub/js-async-loader

0 голосов
/ 29 апреля 2015

Вы можете использовать LABJS или RequreJS

Скриптовые загрузчики типа LABJS, RequireJS улучшат скорость и качество вашего кода.

0 голосов
/ 06 марта 2015

Проверьте это https://github.com/stephen-lazarionok/async-resource-loader. В нем есть пример, который показывает, как загрузить JS, CSS и несколько файлов одним выстрелом.

0 голосов
/ 10 августа 2018

Вот небольшая функция ES6, если кто-то хочет использовать ее в React, например

import {uniqueId} from 'lodash' // optional
/**
 * @param {String} file The path of the file you want to load.
 * @param {Function} callback (optional) The function to call when the script loads.
 * @param {String} id (optional) The unique id of the file you want to load.
 */
export const loadAsyncScript = (file, callback, id) => {
  const d = document
  if (!id) { id = uniqueId('async_script') } // optional
  if (!d.getElementById(id)) {
    const tag = 'script'
    let newScript = d.createElement(tag)
    let firstScript = d.getElementsByTagName(tag)[0]
    newScript.id = id
    newScript.async = true
    newScript.src = file
    if (callback) {
      // IE support
      newScript.onreadystatechange = () => {
        if (newScript.readyState === 'loaded' || newScript.readyState === 'complete') {
          newScript.onreadystatechange = null
          callback(file)
        }
      }
      // Other (non-IE) browsers support
      newScript.onload = () => {
        callback(file)
      }
    }
    firstScript.parentNode.insertBefore(newScript, firstScript)
  } else {
    console.error(`The script with id ${id} is already loaded`)
  }
}
0 голосов
/ 11 октября 2011

Я бы посоветовал сначала изучить возможность минимизации файлов и посмотреть, дает ли это достаточно большой прирост скорости. Если ваш хост работает медленно, попробуйте поместить этот статический контент в CDN.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...