Сегодняшняя статья – скорая помощь для всех, кто столкнулся с неразрешимой проблемой относительных путей в CSS (LESS) и атрибута baseHref. Если ваше angular-приложение в проде размещается не в корне, а в отдельной папке, добро пожаловать под кат, подробное описание проблемы и способ решения, все там.
Проблема
Современные крупные сайты, это, как правило, набор больших и маленьких SPA-приложений, связанных между собой какой-то навигацией. Поэтому ситуация, когда локальное приложение у вас запускается от корня папки, а продовое размещается во внутренней папке, встречается, ну буквально, на каждом шагу.
В чем тут сложность? Конечно же с путями к ассетам. Например, если у нас в компоненте подключена картинка в качестве background-image, то, когда мы собираем приложение в тестовом режиме локально, путь к ней вполне можно указать от корня сайта:
.image {
background-image: url(/assets/img/my-image.png);
}
То есть, нам не нужно учитывать иерархию компонентов и структуру приложения. Какая бы вложенность не была, такой путь будет работать, потому что картинки у нас лежат в папке assests, а сайт работает из корня папки.
А теперь представим, что наше фронтовое приложение в проде лежит в папке my-app. Тогда, чтобы в проде эта картинка отобразилась, путь к ней должен учитывать еще и эту папку, то есть он должен быть таким:
.image {
background-image: url(/my-app/assets/img/my-image.png);
}
Для решения этой задачи, достаточно долгое время в Angular использовался специальный параметр. Чтобы понять, как именно, вот вам небольшой экскурс в историю.
BaseHref to rescue
baseHref – это опция angular.json, которая позволяет указать различные пути размещения приложения, локально и на продакшене.
Следвательно, для нашего гипотетического приложения, в секции: architect –> build -> configurations -> production -> baseHref — мы указываем значение «my-app».
...
"architect": {
"build": {
"configurations": {
"production": {
"baseHref": "/my-app/",
...
А в секции serve, оставляем дефолтное значение: «/»
...
"architect": {
"serve": {
"options": {
"baseHref": "/"
...
Что же фактически происходило, когда мы указывали подобные настройки и выполняли билд приложения для публикации?
Ко всем путям в CSS добавлялось то значение, которое мы указали в опции baseHref. Если сделать билд приложения с указанными настройками (Angular <= 8), то где-то в недрах файла main.js, вы обнаружите измененные пути:
["src","/my-app/assets/my-image.png" alt=""]
При этом, локально все по-прежнему работало с путями от корня:
["src","/assets/my-image.png" alt=""]
Я говорю об этом в прошедшем времени, потому что во время миграции с седьмой версии на восьмую, у разработчиков начались приключения.
Breaking changes и временное решение
В один прекрасный релиз, команда разработки ангуляра решила, что подобная замена путей в CSS вещь нехорошая и отключила этот функционал. Как следствие, все, кто обновил фреймворк – получили проблему в проде, пропавшие изображения, заданные через CSS, от корня сайта.
К счастью дело было не вполне безнадежное, и если в том же angular.json опцию: architect –> build -> configurations -> production -> rebaseRootRelativeCssUrls установить в true – проблема решалась.
...
"architect": {
"build": {
"configurations": {
"production": {
"rebaseRootRelativeCssUrls": true,
...
Опять беда
Тут бы казалось, проблема решена, можно расходиться. Но, внимательный разработчик, смотрит на описание новой опции (на самом деле нет), которое гласит:
«Change root relative URLs in stylesheets to include base HREF and deploy URL. Use only for compatibility and transition. The behavior of this option is non-standard and will be removed in the next major release.»
Или проще говоря, праздник на вашей улице ненадолго, опцию удалят в следующем мажорном релизе. Сказано – сделано, в версии 11 опцию выпилили, ничего не оставив взамен.
Точнее так: взамен оставили рекомендацию всегда и везде в CSS использовать относительные пути. Что конечно хорошо и правильно, если б не одна проблема.
Относительные пути нельзя использовать во вложенных компонентах, в режиме serve, приложение просто не соберется, выдав ошибку:
Failed to compile.
./src/app/inner/inner.component.less
Module Error (from ./node_modules/postcss-loader/src/index.js):
(Emitted value instead of an instance of Error) CssSyntaxError: \projects\path-fix\src\app\inner\inner.component.less:2:18: Can't resolve 'assets/my-image.png' in 'I:\projects\path-fix\src\app\inner'
1 | .someclass {
> 2 | background: url("assets/my-image.png");
Да и с чего бы ему собираться, внутри папки с компонентом у нас ассетов нет, они в корне лежат.
А вложенные компоненты в angular, это все, кроме родительского. Ну, то есть можно конечно писать нечто в CSS «/../../../../../assest/my-image.png», но кому в здравом уме, это понравится делать?
Особенно, когда у вас давно написанное приложение, с матрешкой из овер 9000 компонентов, которые раньше прекрасно работали с коротким путем /assets.
Решение
К счастью есть решение, максимально простое и удобное, работающее с LESS. Достаточно в пути к ассетам, указать карет ^
backgorund-image: url("^assets/my-image.png")
Теперь в режиме serve – приложение соберется без проблем, потому что при билде приложения, postCSS-плагин этот символ просто игнорирует, возвращая указанный путь.
Таким образом, в режиме серва, мы можем использовать относительные пути к ассетам, в любом компоненте, не беспокоясь об иерархии приложения и различном размещении его локально и в проде.
Проверено на Angular 11.1.1
Источник решения