Angular 11, BaseHref и относительные пути в LESS

Сегодняшняя статья – скорая помощь для всех, кто столкнулся с неразрешимой проблемой относительных путей в 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
Источник решения


Posted

in

,

by

Comments

Leave a Reply

Your email address will not be published. Required fields are marked *