Меню

Почему не script в html

Фундаментальная уязвимость HTML при встраивании скриптов

Чтобы описать суть проблемы, мне нужно рассказать, как вообще устроен HTML. Вы наверняка в общих чертах представляли себе, но я все равно коротко пробегусь по основным моментам, которые понадобятся для понимания. Если кому-то не терпится, сразу переходите к сути.

HTML — это язык гипертекстовой разметки. Чтобы говорить на этом языке, нужно соблюдать его формат, иначе тот, кто читает написанное, не сможет вас понять. Например, в HTML у тегов есть атрибуты:

Тут [name] — это имя атрибута, а [value] — это его значение. В статье я буду использовать квадратные скобки вокруг кода, чтобы было понятно, где он начинается и заканчивается. После имени стои́т знак равенства, а после него — значение, заключенное в кавычки. Значение атрибута начинается сразу после первого символа кавычки и заканчивается сразу перед следующим символом кавычки, где бы он не находился. Это значит, что если вместо [value] вы запишете [OOO «Рога и копыта».] , то значение атрибута name будет [OOO ] , а еще у вашего элемента будет три других атрибута с именами: [рога] , [и] и [копыта».»] , но без значений.

Если это не то, чего вы ожидали, вам нужно как-то изменить значение атрибута, чтобы в нем не встречалась кавычка. Самое простое, что можно придумать — просто вырезать кавычки.

Тогда парсер HTML верно прочтет значение, но беда в том, что это будет другое значение. Вы хотели [OOO «Рога и копыта»] , а получили [OOO Рога и копыта.] . В каких-то случаях такое различие может быть критичным.

Чтобы вы могли указать в качестве значения любую строку, формат языка HTML предлагает возможность экранировать значения атрибутов. Вместо кавычки в строке значения вы можете записать последовательность символов [«] и парсер поймет, что в этом месте в исходной строке, которую вы хотите использовать в качестве значения атрибута, была кавычка. Такие последовательности называются HTML entities.

При этом, если в вашей исходной строке действительно была последовательность символов [«] , у вас все еще есть возможность записать её так, чтобы парсер не превратил её в кавычку — для этого надо заменить знак [&] на последовательность символов [&] , то есть вместо [«] вам нужно будет записать в сыром тексте ["] .

Получается, что преобразование из исходной строки в ту, которую мы запишем между двумя символами кавычек, является однозначным и обратимым. Благодаря этим преобразованиям можно записать и прочитать любую строку в качестве атрибута HTML-тега, не вдаваясь в суть её содержимого. Вы просто соблюдаете формат, и все работает.

Собственно, так работает большинство форматов, с которыми мы сталкиваемся: есть синтаксис, есть способ экранирования контента от этого синтаксиса и способ экранирования символов экранирования, если вдруг такая последовательность встречается в исходной строке. Большинство, но не…

Тег . Парсер HTML внутрь тега не заглядывает, для него это просто какой-то текст, который он потом отдает в парсер Javascript.

В свою очередь, Javascript — это самостоятельный язык с собственным синтаксисом, он, вообще говоря, никаким специальным образом не рассчитан на то, что будет встроен в HTML. В нем, как в любом другом языке, есть строковые литералы, в которых может быть что угодно. И, как вы уже должны были догадаться, может встретиться последовательность символов, означающая закрывающий тег .

Что тут должно происходить: переменной s должна присваиваться безобидная строка.

Что тут происходит на самом деле: Скрипт, в котором объявляется переменная s на самом деле заканчивается так: [var s = «surprise!] , что приводит к ошибке синтаксиса. Весь текст после него интерпретируется как чистый HTML и в него может быть внедрена любая разметка. В данном случае открывается новый тег ни в каком виде. А стандарт Javascript не запрещает такой последовательности быть где угодно в строковых литералах.

Получается парадоксальная ситуация: после встраивания валидного Javascript в валидный документ HTML абсолютно валидными средствами мы можем получить невалидный результат.

На мой взгляд это и является уязвимостью разметки HTML, приводящей к уязвимостям в реальных приложениях.

Как эксплуатируется уязвимость

Конечно, когда вы просто пишете какой-то код, трудно представить, что вы напишете в строке и не заметите проблем. Как минимум, подсветка синтаксиса даст вам знать, что тег закрылся раньше времени, как максимум, написанный вами код не запустится и вы будете долго искать, что произошло. Но это не является основной проблемой с этой уязвимостью. Проблема возникает там, где вы вставляете какой-то контент в Javascript, когда генерируете HTML. Вот частый кусок кода приложений на реакте с серверным рендерингом:

В initialState может появиться в любом месте, где данные поступают от пользователя или из других систем. JSON.stringify не будет менять такие строки при сериализации, потому что они полностью соответствуют формату JSON и Javascript, поэтому они просто попадут на страницу и позволят злоумышленнику выполнить произвольный Javascript в браузере пользователя.

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

Но на закрывающем теге приколы не заканчиваются. Опасность представляет и открывающий тег

Что видит здоровый человек и большинство подсветок синтаксиса в этом коде? Два тега ниже, хе-хе). Если вы до этого не сталкивались с подобным, то можете подумать, что я сейчас шучу. К сожалению, нет. Вот скриншот DOM-дерева примера выше:

Самое неприятное, что в отличие от закрывающего тега , который в Javascript может встретиться только внутри строковых литералов, последовательности символов и

А вы точно спецификация?

Спецификация HTML, помимо того, что запрещает использование легальных последовательностей символов внутри тега «‘; console.log(script.outerHTML); >>> «

Как видите, строка с сериализованным элементом не будет распаршена в элемент, аналогичный исходному. Преобразование DOM-дерево → HTML-текст в общем случает не является однозначным и обратимым. Некоторые DOM-деревья просто нельзя представить в виде исходного HTML-текста.

Как избежать проблем?

Как вы уже поняли, способа безопасно вставить Javascript в HTML нет. Но есть способы сделать Javascript безопасным для вставки в HTML (почувствуйте разницу). Правда для этого нужно быть предельно внимательным всё время, пока вы пишете что-то внутри тега

Точно так же можно экранировать и отдельные строки.

Другой совет — не встраивайте в тег «>

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

Код внутри выглядит ужасно и непривычно. Но это код, который попадет в сам HTML. В шаблонизаторе, который вы используете, можно сделать простой фильтр, который будет вставлять тег и экранировать все его содержимое. Вот так может выглядеть код в шаблонизаторе Django:

Т.е. html= это обёртка (функция, генератор, стрим), вызывающая htmlEncode, htmlEscape, и прочие подобные функции, делающие , > , » , & и прочее, ещё никто не отменял.
Если foreign-input, embedded и прочее вставляется в html-разметку, т.е. результат сперва разбирается html-парсером, до того как будет собственно «исполнен» тег скрипт, полифилы и прочее ничего с собственно html-стандартом не имееющее.

И если забыть про правильный эскейп, то скрипт-тег будет далеко не единственной проблемой…
XSS, XFS, и прочие радости можно словить даже тупо на «сломаной» encoding.

К homm: это не имеет вообще ничего общего с «фундаментальной уязвимостью». Совсем.

делающие , «, & и прочее, ещё никто не отменял.

И в результате у вас внутри строки Javascript будут именно , > , » , & , а не , > , » , & , которы были в исходной строке.

И если забыть про правильный эскейп, то скрипт-тег будет далеко не единственной проблемой…

Еще раз попробую донести свою мысль: скрипт-тег все еще останется проблемой, даже если не забыть про правильный эскейп. В случае тега script просто не предусмотрено никакого эскейпа для встраиваемого скрипта, на него просто наложены ограничения (причем с весьма извращенные), которых нет в Javascript.

И в результате у вас внутри строки Javascript будут именно , > .

Прошу прощения, и правда попутал тут, — забыл про XHTML vs. CDATA (никогда не вставляю подобное прямо в скрипт).

скрипт-тег все еще останется проблемой, даже если не забыть про правильный эскейп

Попробую и я вам донести свою мысль — про правильный эскейп просто НЕЛЬЗЯ забывать, ни в коем случае.
Просто, если внутри тега ожидается CDATA, то и эскейпить нужно для CDATA.
Для HTML — HtmlEncode, для JS внутри HTML — HtmlJSEncode (например )»> )…
И так далее.

А лучше просто класть foreign-inlput в предназначеные для этого теги (типа input , textarea и т.д.), и/или вовсе отдельным майм-стримом (т.е. application/json ).

про правильный эскейп просто НЕЛЬЗЯ забывать

Давайте еще раз. Скрипт-тег все еще останется проблемой, если вы НЕ ЗАБЫВАЕТЕ про правильный эскейп. Его для тега script просто не предусмотрено.

Пример который вы показали с onclick — это как раз пример того, где можно экранировать вставляемый скрипт, потому что атрибут onclick парсится точно так же как любые остальные атрибуты HTML и в нем может быть абсолютно любой контент, для его встраивания не нужно знать синтаксис скрипта.

В отличие от него, тег script парсится по своим, совершенно отдельным правилам. Он не позволяет вставить в себя произвольный Javascript, даже если он на 100% валидный Javascript. Он накладывает дополнительные требования на содержимое встриваемых скриптов, которые в случае Javascript могут быть соблюдены только парсингом и модификацией Javascript, а в случае встраивания скрипта на произвольном языке в общем случае не могут быть соблюдены.

А лучше просто класть foreign-inlput

Лучше или хуже это другой вопрос. Я не о лучших практиках говорю, а о проблеме в спецификации. Спецификация допускает встраивание в HTML языков, синтаксис которых позволяет выходить за пределы встраеваемого жлемента и не дает никаких способов этот синтаксис экранировать. Вместо этого она говорит: «там этого контента быть не должно. Как и кто его будет оттуда доставать? Да мне плевать.». Из-за этого часто возникают уязвимости уже в реальных приложениях, когда контент в теге скрипт генерируется на лету.

источник

HTML JavaScript

В современном мире верстальщику, который не знает хотя бы основ JavaScript, будет очень сложно. Совершенно не обязательно быть гуру JavaScript, но основами данного языка владеть необходимо.
JavaScript — клиентский язык программирования (выполняется не на сервере, а непосредственно в браузере пользователя на его локальном компьютере).
JavaScript используется для создания веб-приложений и динамических сайтов способных взаимодействовать с пользователем. В этом уроке вы получите лишь общее представление об этом мощном инструменте веб-дизайна, т.к. изучение JavaScript не входит в начальный курс обучения HTML.
Прежде всего нужно запомнить, что JavaScript является регистрозависимым языком. По этой причине следует запоминать не только названия свойств, методов или ключевых слов JavaScript, но и заглавными или строчными буквами они пишутся.

Читайте также:  Почему на работе спать охота

Код JavaScript может быть написан либо непосредственно в самом HTML-документе, либо в отдельном файле.

Для записи JavaScript-кода в HTML-документе используется тег

В следующем примере код JavaScript написан непосредственно в самом HTML-документе. При загрузке страницы данный код сработает и вызовет всплывающее сообщение:

Запуск скрипта

В предыдущем примере скрипт запустился во время загрузки HTML-документа. Но что делать если вы не хотите, чтобы скрипт запускался автоматически? Вы легко можете сделать так, чтобы запуск скрипта осуществлялся только в том случае, если пользователь делает что-то на странице (например, перемещает курсор мыши или кликае ссылку).
Эти действия называются внутренними событиями (события, для краткости). Есть множество предопределенных внутренних событий, которые осуществляют запуск скрипта. Вы можете использовать обработчики событий, чтобы сообщить браузеру, какое событие должно вызвать тот или иной сценарий. События определяются как атрибуты внутри HTML-тега.
Допустим, вы хотите, чтобы появлялось сообщение после того, когда пользователь нажимает кнопку. Вы можете использовать обработчик событий onclick() для выполнения действий. В следующем примере будет отображено окно предупреждения JavaScript, содержащее сообщение:

Подключение внешнего скрипта

Если предполагается использовать один и тот же сценарий на многих веб-страницах, удобно разместить его в отдельном файле и затем сослаться на этот файл. Это целесообразно сделать даже в том случае, если код будет использован только на одной странице. Например, если сценарий слишком большой и громоздкий, то выделение его в отдельный файл облегчает восприятие и отладку кода веб-страницы.
JavaScript-код можно записать в отдельном файле с расширением .js , после чего подключать его к HTML-документу примерно так, как мы делали это с CSS-файлами.
Файл с расширением .js является обычными текстовым файлом, как и другие уже известные нам файлы с расширениями: .css и .html .
Создадим файл script.js и сохраним в нем небольшую функцию, созданную с помощью Javascript:

Для подключения JS-файлов также используется тег

источник

Почему бы не вставлять стили /скрипты в HTML вместо ссылки?

Мы объединяем файлы CSS и JavaScript для уменьшения количества HTTP-запросов, что повышает производительность. Результатом является HTML следующим образом:

Если у нас есть логика на стороне сервера /сборки, чтобы сделать все это для нас, почему бы не сделать это еще на один шаг и вставить эти конкатенированные стили и скрипты в HTML?

Это два меньше HTTP-запросов, но я не видел эту технику на практике. Почему бы и нет?

6 ответов

Поскольку сохранение HTTP-запросов малопригодно, когда вы достигаете его, разбивая кеширование. Если таблицы стилей и скрипты подаются отдельно, их можно кэшировать очень хорошо и амортизировать по многим, многим запросам на совершенно разные страницы. Если они задуманы на одной и той же странице HTML, их нужно перепрограммировать с каждым. Один. Запрос.

HTML этой страницы, например, составляет 13 КБ. 180 Кбайт CSS попал в кеш, а также 360 КБ JS. Оба тайника попадали в минус и сокращали практически всю полосу пропускания. Извините сетевой профайлер своего браузера и попробуйте его на некоторых других сайтах.

Просто потому, что действительно важна производительность Web! В 99% случаев это даст вам более быстрое время отклика конечного пользователя.

Вот несколько примеров из Velocity Conf.

  • Bing . Страница, которая была на 2 секунды медленнее, привела к снижению дохода /пользователя на 4,3%.
  • Google . Задержка в 400 миллисекунд вызвала снижение поисковых запросов /пользователей на 0,59%.
  • Yahoo ! «Задержка в 400 миллисекунд привела к падению полностраничного трафика на 5-9%.
  • Shopzilla . Ускорение своего сайта на 5 секунд увеличило коэффициент конверсии 7-12%, удвоило количество сеансов из поискового маркетинга и сократило количество необходимых серверов пополам. >
  • Mozilla . Бритье на 2,2 секунды с их целевых страниц увеличило загрузку конверсий на 15,4%, что, по их оценкам, приведет к 60 миллионам загрузок Firefox в год.
  • Netflix . Принятие одной оптимизации, сжатие gzip привело к ускорению на 13-25% и сокращению их исходящего сетевого трафика на 50%.

От Steve Souders, пионера в оптимизации веб-производительности,

80-90% времени отклика конечного пользователя расходуется на интерфейс — Start здесь сначала.

Использование внешних файлов создает более быстрые страницы, потому что файлы JavaScript и CSS кэшируются браузером /сетями /прокси (как определено в HTTP-протоколе с заголовками Cache). JavaScript и CSS, которые встроены в HTML-документы, загружаются каждый раз при запросе документа HTML. Это уменьшает количество HTTP-запросов, которые необходимы, но увеличивает размер HTML-документа. Если вы используете JQuery-подобные скрипты, легко отказаться от 300 Кбайт скриптов и не верьте, что у каждого есть пропускная способность 100 Мбит /с с низкой задержкой, запуская одно приложение — браузер — открытое на вашем веб-сайте. В 99% случаев это даст вам более быстрое время отклика конечного пользователя.

Частота, с которой внешние элементы JavaScript и CSS кэшируются относительно количества запрошенных HTML-документов, также важна. Если пользователи на вашем сайте имеют несколько просмотров страниц за сеанс, и многие из ваших страниц повторно используют одни и те же скрипты и таблицы стилей (пакеты), существует большая потенциальная выгода от кэшированных внешних файлов.

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

Здесь вы можете прочитать здесь , почему проблемы производительности ( Отказ от ответственности: я автор)

Последняя версия HTTP была создана еще в 1999 году. В 1999 году all подключился к Интернету с помощью dial-up. Интернет был очень медленным. 16 лет спустя все перешло очень много, но протоколы, которые мы используем, не имеют.

Ответы, которые мы не должны вводить, потому что это мешает кешированию, немного вводят в заблуждение, особенно в эпоху сверхбыстрого Интернета. Когда вы на самом деле выполняете вычисления, часто наблюдается незначительная разница между временем загрузки с использованием кеш-теплых и холодных пользователей, если у вас есть встроенный. Тот факт, что там есть небольшая разница не присуща, потому что вы встроены, но из-за негибкого дизайна HTTP /1.1.

Протокол SPDY реализует что-то, называемое сервером push . Это, по сути, вносит вклад в сам документ HTML и в протокол. Интеллектуальный сервер будет знать, какие ресурсы у клиента уже есть. Немой сервер просто отправит все независимо — это все равно будет преимуществом производительности, но может стоить с точки зрения пропускной способности. Если браузер имеет содержимое в кеше, он может просто отказаться от входящих копий. Сервер ждет, пока HTML не будет загружен до отправки дополнительных ресурсов. Теоретически браузер может отправить сигнал для отмены нажатия на сервер.

HTTP /2.0 основан на SPDY и, скорее всего, будет реализовывать серверный push, но вы можете теоретически начать использовать SPDY сегодня. Таким образом, настоящая причина, по которой мы не являемся встроенной, является одним из наследия — существующие в настоящее время протоколы являются старыми и недостаточно гибкими, чтобы достичь «наложения уровня протокола».

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

JavaScript, появляющийся в HTML, может столкнуться с проблемами разбора, как в этом примере:

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

Если вы обслуживаете свой контент в формате XML, у вас есть часть пути, используя разделы CDATA. Однако CDATA имеет аналогичную проблему:

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

Разделение всех стилей позволяет и поощряет повторное использование и общие файлы.

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

Для вас конкретный вопрос, хотя . Если сервер сделан для самой минимизации, он усложняет работу с ресурсами и отлаживает проблемы. Однако многие фреймворки теперь делают это на уровне файла, например. все cs и все js. Например, рубин на рельсовых каркасах теперь минимизирует свои активы для производства. 5-10 дополнительных HTTP-запросов обычно не являются узким местом, тем больше, если есть 100+ запросов HTTP (которые вы часто получаете с изображениями).

Дополнительный шаг на самом деле, включая код на самих страницах, будет иметь недостаток в больших страницах, которые вам необходимо будет тщательно контролировать последовательность загрузки, а страница не сможет отображать контент часто без остальной части (теперь большая) страница загружается.

источник

Погружение в темные воды загрузки скриптов

Введение

В этой статье я хочу научить вас как загружать в браузер JavaScript и выполнять его.

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

Для затравки, вот как спецификация определяет различные способы загрузки и выполнения скриптов:

Как и все спецификации WHATWG, на первый взгляд данная спецификация выглядит как последствия кассетной бомбы на фабрике Scrabble. Но, прочитав ее на 5 раз и вытерев кровь из своих глаз, начинаешь находить ее довольно интересной:

Мое первое подключение скрипта

Ах, блаженная простота. В данном случае браузер скачает оба скрипта параллельно и выполнит их как можно скорее, сохраняя заданный порядок. «2.js» не будет выполняться пока не выполнится «1.js» (или не сможет этого сделать), «1.js» не выполнится пока не выполнится предыдущий скрипт или стиль, и т.д. и т.п.

Читайте также:  Почему выкидывает сталкер чистое небо

К сожалению, браузеры блокируют дальнейшую отрисовку страницы, пока это все происходит. Еще со времен «первого века веба» это обусловлено DOM API, который позволяет строкам добавляться к содержимому, которое пережовывает парсер, например с помощью document.write . Более современные браузеры продолжат сканировать и парсить документ в фоновом режиме и загружать нужный сторонний контент (js, картинки, css и т.д.), но отрисовка по-прежнему будет блокирована.

Вот почему гуру и специалисты производительности советуют размещать элементы script в конце документа, потому что это блокирует меньше всего контента. К сожалению, это означает, что ваш скрипт не будет увиден браузером до того времени, как будет скачен весь HTML, а также уже запущена загрузка CSS, картинок и iframe-ов. Современные браузеры достаточно умны, чтобы отдавать приоритет JavaScript над визуальной частью, но мы можем сделать лучше.

Спасибо, IE! (нет, я это без сарказма)

В Microsoft обнаружили эти проблемы с производительностью и ввели «defer» в Internet Explorer 4. В основном, оно говорит следующее: «Я обещаю ничего не вставлять в парсер, используя штуки, наподобие document.write . Если я нарушу это обещание, вы можете меня наказать любым приемлемым вам способом». Этот атрибут был введен в HTML4 и он также появился в других браузерах.

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

Как и кассетная бомба на фабрике овец, «defer» стал мохнатым беспорядком. Помимо «src» и «defer», а также тегов скрипт и динамически загружаемых скриптов, у нас есть 6 паттернов добавления скрипта. Естественно, что браузеры не договорились о порядке, в котором они должны выполняться. Mozilla замечательно описала эту проблему еще в 2009.

WHATWG сделали это поведение явным, объявив, что «defer» не будет иметь никакого эффекта на скрипты, которые были добавлены динамически или не имеют «src». В противном случае, скрипты с «defer» должны запускаться в заданном порядке после того, как документ был распарсен.

Спасибо, IE! (ну ладно, теперь с сарказмом)

Одно дали — другое отобрали. К сожалению, есть неприятный баг в IE4-9, который может спровоцировать выполнение скриптов в неверном порядке. Вот что происходит:

Допустим, что на странице есть параграф, ожидаемый порядок логов — [1, 2, 3], но в IE9 и ниже результат будет [1, 3, 2]. Некоторые операции DOM вынуждают IE приостановить выполнение текущего скрипта и перед продолжением начать выполнение других скриптов в очереди.

Тем не менее, даже в реализациях без бага, таких как IE10 и других браузерах, выполнение скрипта будет отложено до момента, когда весь документ будет загружен и распарсен. Это удобно, если вы в любом случае ждете DOMContentLoaded , но если вы хотите получить реальный прирост производительности, то вскоре вы начнете использовать listeners и bootstrapping…

HTML5 спешит на помощь

HTML5 дал нам новый атрибут «async», который предполагает, что вы также не используете document.write, но при этом не ожидает окончания парсинга документа. Браузер параллельно скачает оба скрипта и выполнит их как можно скорее.

К сожалению, так как они постараются выполниться максимально скоро, «2.js» может выполниться раньше «1.js». Это отлично, если они не зависят друг от друга. Например, если «1.js» — это отслеживающий скрипт, не имеющий ничего общего с «2.js». Но если «1.js» — это CDN-копия jQuery, от которой зависит «2.js», то ваша страница будет устлана ошибками, как после кассетной бомбы в… я не знаю… здесь я ничего не придумал.

Я знаю, нам нужна JavaScript-библиотека!

В Святом Граале содержится набор скриптов, загружающихся сразу, без блокирования отрисовки страницы и выполняющихся максимально скоро, в том порядке, в котором мы их добавили. К сожалению, HTML ненавидит вас и не позволит вам этого сделать.

Проблема была решена с помощью JavaScript на разный манер. Некоторые способы требовали от вас вносить изменения в JavaScript, оборачивать всё в callback, который библиотека вызовет в правильном порядке (например, RequireJS). Другие использовали XHR для параллельной загрузки, а затем eval() в нужном порядке, который не работает для скриптов на другом домене, если там нет заголовка CORS и поддержки его в браузере. Некоторые использовали даже супер-магические хаки, как это было сделано в последнем LabJS.

Хаки всяческим образом обманывали браузер, чтобы тот загружал ресурс, вызывая при этом событие по окончанию загрузки, но не начинал его выполнение. В LabJS скрипт сначала добавлялся с некорректным mime-типом, например
Скрипты, которые созданы и добавлены динамически, асинхронные по-умолчанию, они не блокируют отрисовку и выполняются сразу после загрузки, что означает, что они могут появиться в неверном порядке. Однако мы можем явно пометить их неасинхронными:

Это даст нашим скриптам сочетание с поведением, которое не может быть достигнуто чистым HTML. Явно заданные неасинхронными, скрипты добавляются в очередь на выполнение, такую же, как они попадали в нашем первом примере на чистом HTML. Однако, создаваемые динамически, они будут выполняться вне парсинга документа, что не будет блокировать отрисовку, пока они будут загружаться (не путайте неасинхронную загрузку скрипта с синхронным XHR, что никогда не является хорошей вещью).

Скрипт выше должен быть встроен в head страниц, начиная очередь загрузок как можно раньше, без нарушения постепенной отрисовки, и начиная выполнять как можно раньше, в заданном вами порядке. «2.js» может свободно загружаться до «1.js», но он не будет выполнен до тех пор, пока «1.js» успешно не скачается и выполнится или не сможет сделать что-либо из этого. Ура! Асинхронная загрузка, но выполнение по порядку!

Загрузка скриптов этим методом поддерживается везде, где поддерживается атрибут async, за исключением Safari 5.0 (на 5.1 все хорошо). Кроме того все версии Firefox и Opera, которые не поддерживают атрибут async, все равно выполняют динамически-добавленные скрипты в правильном порядке.

Это самый быстрый способ загружать скрипты, так? Так?

Ну если вы динамически решаете какие скрипты загружать — да, иначе — возможно, что нет. В примере выше браузер должен распарсить и загрузить скрипт, чтобы определить какие скрипты загружать. Это скрывает ваши скрипты от сканеров предзагрузки. Браузеры используют эти сканеры для обнаружения ресурсов, которые вы скорее всего посетите следующими и для обнаружения ресурсов страницы пока парсер заблокирован другим ресурсом.

Мы можем добавить обнаружаемость обратно, поместив это в head документа:

Это сообщает браузеру о том, что страница требует 1.js и 2.js и видима для предзагрузчиков. link[rel=subresource] похож на link[rel=prefetch] , но с другой семантикой. К сожалению, это поддерживается только в Chrome, и вам нужно объявлять скрипты для загрузки дважды: первый — в элементах link, второй — в вашем скрипте.

Эта статья меня удручает

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

С появлением HTTP2/SPDY вы сможете уменьшить накладные ресурсы до точки, где доставка скриптов в маленьких самостоятельно-кэшированных файлах будет самым быстрым способом. Только представьте:

Каждый enhancement-скрипт имеет дело с конкретным компонентом страницы, но требует вспомогательные функции в dependencies.js. В идеале, мы хотим загрузить все асинхронно, затем выполнить enhancement-скрипт как можно раньше, в любом порядке, но после dependencies.js. Это прогрессивное прогрессивное улучшение!

К сожалению, нет декларативного способа для того, чтобы достичь этого, только если модифицировать сами скрипты для отслеживания состояния загрузки dependencies.js. Даже async=false не решит эту проблему, потому что выполнение enhancement-10.js будет заблокировано на 1-9. По факту, есть только один браузер, в котором можно достичь этого без хаков…

У IE есть идея!

IE грузит скрипты не так, как другие браузеры.

IE начинает закачивать «whatever.js» сейчас, другие же браузеры не начнут загрузку до того момента, пока скрипт не будет добавлен к документу. У IE также есть событие «readystatechange» и свойство «readystate», которые сообщают о процессе загрузки. Это на самом деле очень полезно, потому что позволяет нам управлять загрузкой и выполнением скриптов независимо друг от друга.

Мы можем строить сложные модели зависимости, выбирая когда добавлять скрипты в документ. IE поддерживает такую модель, начиная с 6-ой версии. Довольно интересно, но у этого есть такой же недостаток с обнаруживаемостью браузером, как и у async=false .

Хватит! Как я должен загружать скрипты?

Ладно, ладно. Если вы хотите загружать скрипты способом, который не блокирует отрисовку, не требует дублирования и имеет прекрасную поддержку браузеров, то я советую вот этот:

Именно этот. В конце элемента body. Да, быть веб-разработчиком — это как быть царем Сизифом (бум! 100 хипстерских очков за упоминание греческой мифологии!). Ограничения HTML и браузеров не позволяют нам сделать сильно лучше.

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

Иуу, должно быть что-то получше, что мы можем использовать сейчас?

Ладно, ради бонусных очков, если вы всерьез думаете о производительности и не боитесь сложности и дублирования, то можете объединить несколько рассмотренных трюков.

Во-первых, мы добавим объявление subresource для предзагрузчиков:

Затем прямо в head документа мы загружаем наши скрипты с помощью JavaScript, используя async=false , уступая место скрипту для IE на основе readystate, который, в свою очередь, уступает место defer.

Спецификация говорит: Скачивай вместе, выполняй по порядку после любого ожидающего CSS, блокируй отрисовку, пока не закончишь
Браузер отвечает: Да, сэр!

Defer

Спецификация говорит: Скачивай вместе, выполняй по порядку до DOMContentLoaded. Игнорируй «defer» для скриптов без «src».
IE

Только зарегистрированные пользователи могут участвовать в опросе. Войдите, пожалуйста.

источник

Погружение в темные воды загрузки скриптов

Введение

В этой статье я хочу научить вас как загружать в браузер JavaScript и выполнять его.

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

Читайте также:  Почему молодые люди становятся террористами

Для затравки, вот как спецификация определяет различные способы загрузки и выполнения скриптов:

Как и все спецификации WHATWG, на первый взгляд данная спецификация выглядит как последствия кассетной бомбы на фабрике Scrabble. Но, прочитав ее на 5 раз и вытерев кровь из своих глаз, начинаешь находить ее довольно интересной:

Мое первое подключение скрипта

Ах, блаженная простота. В данном случае браузер скачает оба скрипта параллельно и выполнит их как можно скорее, сохраняя заданный порядок. «2.js» не будет выполняться пока не выполнится «1.js» (или не сможет этого сделать), «1.js» не выполнится пока не выполнится предыдущий скрипт или стиль, и т.д. и т.п.

К сожалению, браузеры блокируют дальнейшую отрисовку страницы, пока это все происходит. Еще со времен «первого века веба» это обусловлено DOM API, который позволяет строкам добавляться к содержимому, которое пережовывает парсер, например с помощью document.write . Более современные браузеры продолжат сканировать и парсить документ в фоновом режиме и загружать нужный сторонний контент (js, картинки, css и т.д.), но отрисовка по-прежнему будет блокирована.

Вот почему гуру и специалисты производительности советуют размещать элементы script в конце документа, потому что это блокирует меньше всего контента. К сожалению, это означает, что ваш скрипт не будет увиден браузером до того времени, как будет скачен весь HTML, а также уже запущена загрузка CSS, картинок и iframe-ов. Современные браузеры достаточно умны, чтобы отдавать приоритет JavaScript над визуальной частью, но мы можем сделать лучше.

Спасибо, IE! (нет, я это без сарказма)

В Microsoft обнаружили эти проблемы с производительностью и ввели «defer» в Internet Explorer 4. В основном, оно говорит следующее: «Я обещаю ничего не вставлять в парсер, используя штуки, наподобие document.write . Если я нарушу это обещание, вы можете меня наказать любым приемлемым вам способом». Этот атрибут был введен в HTML4 и он также появился в других браузерах.

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

Как и кассетная бомба на фабрике овец, «defer» стал мохнатым беспорядком. Помимо «src» и «defer», а также тегов скрипт и динамически загружаемых скриптов, у нас есть 6 паттернов добавления скрипта. Естественно, что браузеры не договорились о порядке, в котором они должны выполняться. Mozilla замечательно описала эту проблему еще в 2009.

WHATWG сделали это поведение явным, объявив, что «defer» не будет иметь никакого эффекта на скрипты, которые были добавлены динамически или не имеют «src». В противном случае, скрипты с «defer» должны запускаться в заданном порядке после того, как документ был распарсен.

Спасибо, IE! (ну ладно, теперь с сарказмом)

Одно дали — другое отобрали. К сожалению, есть неприятный баг в IE4-9, который может спровоцировать выполнение скриптов в неверном порядке. Вот что происходит:

Допустим, что на странице есть параграф, ожидаемый порядок логов — [1, 2, 3], но в IE9 и ниже результат будет [1, 3, 2]. Некоторые операции DOM вынуждают IE приостановить выполнение текущего скрипта и перед продолжением начать выполнение других скриптов в очереди.

Тем не менее, даже в реализациях без бага, таких как IE10 и других браузерах, выполнение скрипта будет отложено до момента, когда весь документ будет загружен и распарсен. Это удобно, если вы в любом случае ждете DOMContentLoaded , но если вы хотите получить реальный прирост производительности, то вскоре вы начнете использовать listeners и bootstrapping…

HTML5 спешит на помощь

HTML5 дал нам новый атрибут «async», который предполагает, что вы также не используете document.write, но при этом не ожидает окончания парсинга документа. Браузер параллельно скачает оба скрипта и выполнит их как можно скорее.

К сожалению, так как они постараются выполниться максимально скоро, «2.js» может выполниться раньше «1.js». Это отлично, если они не зависят друг от друга. Например, если «1.js» — это отслеживающий скрипт, не имеющий ничего общего с «2.js». Но если «1.js» — это CDN-копия jQuery, от которой зависит «2.js», то ваша страница будет устлана ошибками, как после кассетной бомбы в… я не знаю… здесь я ничего не придумал.

Я знаю, нам нужна JavaScript-библиотека!

В Святом Граале содержится набор скриптов, загружающихся сразу, без блокирования отрисовки страницы и выполняющихся максимально скоро, в том порядке, в котором мы их добавили. К сожалению, HTML ненавидит вас и не позволит вам этого сделать.

Проблема была решена с помощью JavaScript на разный манер. Некоторые способы требовали от вас вносить изменения в JavaScript, оборачивать всё в callback, который библиотека вызовет в правильном порядке (например, RequireJS). Другие использовали XHR для параллельной загрузки, а затем eval() в нужном порядке, который не работает для скриптов на другом домене, если там нет заголовка CORS и поддержки его в браузере. Некоторые использовали даже супер-магические хаки, как это было сделано в последнем LabJS.

Хаки всяческим образом обманывали браузер, чтобы тот загружал ресурс, вызывая при этом событие по окончанию загрузки, но не начинал его выполнение. В LabJS скрипт сначала добавлялся с некорректным mime-типом, например
Скрипты, которые созданы и добавлены динамически, асинхронные по-умолчанию, они не блокируют отрисовку и выполняются сразу после загрузки, что означает, что они могут появиться в неверном порядке. Однако мы можем явно пометить их неасинхронными:

Это даст нашим скриптам сочетание с поведением, которое не может быть достигнуто чистым HTML. Явно заданные неасинхронными, скрипты добавляются в очередь на выполнение, такую же, как они попадали в нашем первом примере на чистом HTML. Однако, создаваемые динамически, они будут выполняться вне парсинга документа, что не будет блокировать отрисовку, пока они будут загружаться (не путайте неасинхронную загрузку скрипта с синхронным XHR, что никогда не является хорошей вещью).

Скрипт выше должен быть встроен в head страниц, начиная очередь загрузок как можно раньше, без нарушения постепенной отрисовки, и начиная выполнять как можно раньше, в заданном вами порядке. «2.js» может свободно загружаться до «1.js», но он не будет выполнен до тех пор, пока «1.js» успешно не скачается и выполнится или не сможет сделать что-либо из этого. Ура! Асинхронная загрузка, но выполнение по порядку!

Загрузка скриптов этим методом поддерживается везде, где поддерживается атрибут async, за исключением Safari 5.0 (на 5.1 все хорошо). Кроме того все версии Firefox и Opera, которые не поддерживают атрибут async, все равно выполняют динамически-добавленные скрипты в правильном порядке.

Это самый быстрый способ загружать скрипты, так? Так?

Ну если вы динамически решаете какие скрипты загружать — да, иначе — возможно, что нет. В примере выше браузер должен распарсить и загрузить скрипт, чтобы определить какие скрипты загружать. Это скрывает ваши скрипты от сканеров предзагрузки. Браузеры используют эти сканеры для обнаружения ресурсов, которые вы скорее всего посетите следующими и для обнаружения ресурсов страницы пока парсер заблокирован другим ресурсом.

Мы можем добавить обнаружаемость обратно, поместив это в head документа:

Это сообщает браузеру о том, что страница требует 1.js и 2.js и видима для предзагрузчиков. link[rel=subresource] похож на link[rel=prefetch] , но с другой семантикой. К сожалению, это поддерживается только в Chrome, и вам нужно объявлять скрипты для загрузки дважды: первый — в элементах link, второй — в вашем скрипте.

Эта статья меня удручает

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

С появлением HTTP2/SPDY вы сможете уменьшить накладные ресурсы до точки, где доставка скриптов в маленьких самостоятельно-кэшированных файлах будет самым быстрым способом. Только представьте:

Каждый enhancement-скрипт имеет дело с конкретным компонентом страницы, но требует вспомогательные функции в dependencies.js. В идеале, мы хотим загрузить все асинхронно, затем выполнить enhancement-скрипт как можно раньше, в любом порядке, но после dependencies.js. Это прогрессивное прогрессивное улучшение!

К сожалению, нет декларативного способа для того, чтобы достичь этого, только если модифицировать сами скрипты для отслеживания состояния загрузки dependencies.js. Даже async=false не решит эту проблему, потому что выполнение enhancement-10.js будет заблокировано на 1-9. По факту, есть только один браузер, в котором можно достичь этого без хаков…

У IE есть идея!

IE грузит скрипты не так, как другие браузеры.

IE начинает закачивать «whatever.js» сейчас, другие же браузеры не начнут загрузку до того момента, пока скрипт не будет добавлен к документу. У IE также есть событие «readystatechange» и свойство «readystate», которые сообщают о процессе загрузки. Это на самом деле очень полезно, потому что позволяет нам управлять загрузкой и выполнением скриптов независимо друг от друга.

Мы можем строить сложные модели зависимости, выбирая когда добавлять скрипты в документ. IE поддерживает такую модель, начиная с 6-ой версии. Довольно интересно, но у этого есть такой же недостаток с обнаруживаемостью браузером, как и у async=false .

Хватит! Как я должен загружать скрипты?

Ладно, ладно. Если вы хотите загружать скрипты способом, который не блокирует отрисовку, не требует дублирования и имеет прекрасную поддержку браузеров, то я советую вот этот:

Именно этот. В конце элемента body. Да, быть веб-разработчиком — это как быть царем Сизифом (бум! 100 хипстерских очков за упоминание греческой мифологии!). Ограничения HTML и браузеров не позволяют нам сделать сильно лучше.

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

Иуу, должно быть что-то получше, что мы можем использовать сейчас?

Ладно, ради бонусных очков, если вы всерьез думаете о производительности и не боитесь сложности и дублирования, то можете объединить несколько рассмотренных трюков.

Во-первых, мы добавим объявление subresource для предзагрузчиков:

Затем прямо в head документа мы загружаем наши скрипты с помощью JavaScript, используя async=false , уступая место скрипту для IE на основе readystate, который, в свою очередь, уступает место defer.

Спецификация говорит: Скачивай вместе, выполняй по порядку после любого ожидающего CSS, блокируй отрисовку, пока не закончишь
Браузер отвечает: Да, сэр!

Defer

Спецификация говорит: Скачивай вместе, выполняй по порядку до DOMContentLoaded. Игнорируй «defer» для скриптов без «src».
IE

Только зарегистрированные пользователи могут участвовать в опросе. Войдите, пожалуйста.

источник