define(["text!myCSS.css"], function(cssText) {
'use strict';
var link = document.createElement("link");
link.rel = "stylesheet";
link.type = "text/css"
link.href = "data:text/css,"+encodeURI(cssText);
var cssLinks = document.querySelectorAll("link[rel=stylesheet]");
if (cssLinks.length > 0) {
cssLinks[0].parentElement.insertBefore(link, cssLinks[0]);
} else {
document.querySelector("head").appendChild(link);
}
});
Начнем с ответа на вопрос «Зачем именно через тэг link?»
Дело в том, что от модуля обычно хочется получить возможность удобно кастомизировать его визуальное представление.
Если мы захотим использовать атрибут style прямо в нашей разметке, то в дальнейшем при переиспользовании нам придется как-то эти атрибуты менять — супер неудобно и не гибко.
Если мы захотим использовать тэг style, то возникает проблема с тем, что для кастомизации таких стилей придется также вставлять в документ тэг style с нужным содержимым, что также не очень удобно.
Если мы захотим использовать тэг link, то мы сможем просто подключить нашу собственную таблицу стилей после загруженной и подправить представление по-умолчанию под сиюминутные нужды.
Из вышеперечисленного выходит, что вариант с тэгом link наиболее гибкий, т.к. позволяет переопределить стили при использовании компонента наибольшим числом способов.
Как написано в документации RequireJS, для подгрузки css можно использовать такой код:
function loadCss(url) {
var link = document.createElement("link");
link.type = "text/css";
link.rel = "stylesheet";
link.href = url;
document.getElementsByTagName("head")[0].appendChild(link);
}
Он всем неплох, кроме того что вы не узнаете, когда именно будет подгружен ваш css (и будет ли вообще, могли же, например, в путях опечататься). Эта проблема чревата еще и тем, что может вызвать некрасивое поведение картинки, в случае если рендер страницы будет проводится до окончания загрузки файла.
А хочется написать компонент примерно так:
define(["text!myCSS.css", "text!myHTML.html"], function(cssText, htmlText) {
'use strict';
...
});
Но как же потом запихнуть полученный текст css в нашу страницу? Тэг style мог бы нам помочь, но ранее было решено, что наш вариант — это именно link.
Предлагаемое решение — это Data URL (если кто не знает, что это такое, здесь можно посмотреть).
Добавлять будем примерно так:
var link = document.createElement("link");
link.rel = "stylesheet";
link.type = "text/css"
link.href = "data:text/css,"+encodeURI(cssText);
var cssLinks = document.querySelectorAll("link[rel=stylesheet]");
if (cssLinks.length > 0) {
cssLinks[0].parentElement.insertBefore(link, cssLinks[0]);
} else {
document.querySelector("head").appendChild(link);
}
Метод несомненно имеет некоторые минусы, из очевидных — base64 кодирование увеличивает объем данных, теоретически можно столкнуться с ограничениями на длину URL для некоторых браузеров. Но если объем стилей небольшой, то метод выглядит вполне рабочим.
UPD:
Cпасибо oledje за подсказку, base64 кодирование для текста не нужно, лучше подойдет URL encoding, подправил код.
Комментарии (28)
serginho
04.08.2016 13:05+1
define(['css!styles/main'], function() { //code that requires the stylesheet: styles/main.css });
GerrAlt
04.08.2016 15:15взято из кода плагина по вашей ссылке:
var linkLoad = function(url, callback) { var link = document.createElement('link'); link.type = 'text/css'; link.rel = 'stylesheet'; if (useOnload) link.onload = function() { link.onload = function() {}; // for style dimensions queries, a short delay can still be necessary setTimeout(callback, 7); } else var loadInterval = setInterval(function() { for (var i = 0; i < document.styleSheets.length; i++) { var sheet = document.styleSheets[i]; if (sheet.href == link.href) { clearInterval(loadInterval); return callback(); } } }, 10); link.href = url; head.appendChild(link); }
т.е. если мы хотим получить именно link, то он подставляет url в тэг и тем или иным способом проверяет что загрузка завершена, вопрос в том, что произойдет если я минифицировал и у паковал исходники, и указанный файл уже на самом деле изначально выгружен браузеруserginho
04.08.2016 18:07Четно говоря, сам не проверял, но, судя по документации, этот модуль нормально работает с оптимизатором
https://github.com/guybedford/require-css#optimizer-configurationGerrAlt
04.08.2016 19:40вроде бы да, но загрузочная функция вызывается так:
cssAPI.load = function(cssId, req, load, config) { (useImportLoad ? importLoad : linkLoad)(req.toUrl(cssId + '.css'), load); }
т.е. в url всегда оказывается именно адрес css файла, а значит
var link = document.createElement('link'); link.type = 'text/css'; link.rel = 'stylesheet'; ... link.href = url; head.appendChild(link);
будет именно вставлять элемент с обычным URL, а не DataURL как в моем случае. Я не вижу варианта как при исполнении такого кода браузер смог бы обойтись без отправки запроса на получение указанного css файла (или обращения к кэшу, что в данном случае не играет роли — положить в кэш браузера содержимое css-файла из JS кода мне не представляется выполнимым)
oledje
05.08.2016 00:45+1Так и не понял в чем преимущество вашего способа по сравнению с Require-css как упомянул serginho выше. Для чего тратить ресурсы на кодировку в base64 всего листинга ccs файла и забивать DOM DataUtl'ом?
К тому же есть известная проблема с функцией `btoa` которая отказывается кодировать символы не входящие в Latin1. Попробуйте выполнить этот код:
btoa("кириллица");
Например с https://habracdn.net/habr/styles/1470318147/_build/global_main.css btoa работать отказывается. В целом это выглядит как будто вы поленились найти готовый инструмент и сделали свой велосипед. Или я чего-то не доглядел?GerrAlt
05.08.2016 10:19+1Я бы не стал говорить о преимуществе данного метода перед Require-css, это просто интересный (как мне показалось) способ включения css стиля в страницу. Если все же сравнивать два метода, то в ветке выше я высказал предположение, что при определенных настройках плагина будут проблемы в модуле, после обработки оптимизатором. С другой стороны у описанного в статье метода проблемы из-за base64 вцелом (как минимум избыточная трата ресурсов на перекодирование), и в реализации функции-кодировщика в частности.
Для постоянного применения я бы данный метод советовать не стал, но в определенных условиях способ может иметь преимущества (например в простоте: реализация занимает меньше 15 строчек, а ради какого-то модуля в проекте и css-ки из 2-х строк может незахотеться тащить плагин)
GerrAlt
05.08.2016 10:52+1Вы навели меня на интересную мысль: зачем вообще base64, если css это не бинарный формат а текстовый? В соответствии со спецификацией dataURL можно написать так:
link.href = "data:text/css,"+encodeURI(cssText);
Затраты на кодирование это не отменяет, но проблемы с символами вне Latin1 должно решить.
spmbt
05.08.2016 02:08+2Не нашёл ответа в статье о том, почему не рассматриваете загрузку через JS (document.styleSheets, insertRule, addRule)?
Ведь она соответствует требованиям модульности, имеет максимальную гибкость (можно выбрать место расположения правил (приоритет их), а не делать appendChild к группе уже подгруженных правил). Загрузка текстов полностью контролируется в Require или подобными механизмами, если файл не смог загрузиться.
Понятно, что тут написать надо побольше кода, но будем иметь самую суть управления стилями.
(Более того, меняя rules, получаем нетипичную гибкость манипулирования страницей в реалтайме, но этим в библиотеках и просто в приложениях никогда не пользуются, рассматривая правила как что-то статичное. Но это — другая тема.)GerrAlt
05.08.2016 10:34В статье по вашей ссылке для создания новой таблицы стилей, куда потом складываются правила, используется тэг style. Я бы хотел этого избежать, т.к. хочется задавать наиболее общие стили, которые при необходимости можно было наибольшим числом способов переопределить/изменить.
justboris
05.08.2016 12:26+1а разве есть разница в приоритете между стилями в теге <style /> и <link />?
Мой опыт говорит, что они равнозначны, выигрывают те, кто идет последним в документе.GerrAlt
05.08.2016 12:53+1похоже что вы правы, я пчм-то был уверен что встроенные таблицы стилей имеют преимущество при рендеренге перед внешними, но простейший тест
<html> <head> <style> body {background-color: black;} </style> <link rel="stylesheet" href="data:text/css,body%20%7Bbackground-color:%20red%7D"> </head> <body> </body> </html>
показывает обратное: body красный.
ArthurSupertramp
Webpack передавал привет
GerrAlt
вы имеете ввиду что Webpack лучше чем RequireJS, или что Webpack умеет аккуратно подгружать css?
bromzh
И первое, и второе.
stas404
И компот «Hot Module Replacement»
GerrAlt
а не подскажите каким образом он подгружает стили и встраивает их в страницу? я с webpack дела не имел никогда
ArthurSupertramp
он либо прямо в сборку пихает стили в виде строки, либо собирает их в отдельный css, если
попроситьконфиги нужные прописать.bromzh
Посмотрите скринкасты по вебпаку. Человек объясняет как пользоваться вебпаком очень доходчиво, по делу и без воды. Тему загрузки css тоже затрагивает.
GerrAlt
Спасибо за ссылку, выборочно посмотрел (вступление, и про css). Честно говоря не понравилось: если в двух словах то для моих задач выглядит огромным оверкиллом. Я бы даже рискнул сказать что судя по тому что я успел посмотреть RequireJS и Webpack немного для разного нужны. Мне кажется было бы корректнее сравнивать r.js и Webpack.
В общем после поверхностного знакомства осталось ощущения как от сравнения подходов написания инструментов под *nix и Windows, в одном случае минималистичность и «единственное предназначение», а в другом «умеем все» но как-то неаккуратно, исходники даже без минимайзера после упаковки выглядят совсем не так как проект при разработке, какие-то свои сложные внутренние абстракции типа цепочек загрузчиков с pitch функциями и т.д.
P.s. остался вопрос, а если у меня в названии какого-то файла в модуле или каталога присутствует символ "!" или даже 2 идущих подряд, то сборка не сломается?
faiwer
В своих задачах вы уже дошли до, гхм, необычных потребностей, коли уж внедряете CSS из AMD. Следующий шаг Webpack :)
Veikedo
Серьёзно. Мучался с requirejs и бандлингом долгое время. На вебпак не хотел переходить, потому что думал, что он сложен и непонятен.
В итоге всё же добрался до него и всё завелось за 10 минут.
Delphinum
А разве WebPack умеет динамически подгружать стили?
Veikedo
promise loader ?
Delphinum
Похоже на то. Спасибо.
enepomnyaschih
Пока этот баг открыт, к WebPack я не вернусь. Отладка превращается в ад. RequireJS — единственный подход к сборке проекта, не опирающийся на SourceMaps (ну, если не считать моего собственного сборщика).
zorro1211
Можно уже через Canary пользоваться нормально. В Chrome тоже можно включить эту фичу, почитайте комменты в вашей ссылке.
enepomnyaschih
Кстати, а вот и баг в самом WebPack, который изрядно доставляет проблем при подключении CSS. Единственное найденное мной решение — создание промежуточного файла all.css, импортирующего внутрь себя все остальные CSS-файлы в правильном порядке. Как видно по дате, разработчики не торопятся исправлять.