AssetMapper: просте та сучасне управління CSS і JS

Дата оновлення перекладу 2024-05-29

AssetMapper: просте та сучасне управління CSS і JS

Компонент AssetMapper дозволяє писати сучасні JavaScript і CSS без складнощів використання бандлера. Браузери вже підтримують багато сучасних функцій JavaScript на кшталт ствердження import та класів ES6. А протокол HTTP/2 означає, що об'єднання ваших ресурсів для зменшення кількості HTTP-з'єднань більше не є нагальною потребою. Цей компонент є легким шаром, який допомагає видавати ваші файли безпосередньо браузеру.

Компонент AssetMapper має дві основні функції:

  • Мапування та версіонування ресурсів : Всі файли всередині assets/ доступні публічно та є версіонованими. Наприклад, ви можете послатися на assets/styles/app.css у шаблоні з {{ asset('styles/app.css') }}. Фінальний URL міститиме хеш версії, на кшталт /assets/styles/app-3c16d9220694c0e56d8648f25e6035e9.css.
  • Importmaps : Нативна функція браузера, яка полегшує використання ствердження JavaScript import (наприклад, import { Modal } from 'bootstrap') без системи побудови. Це підтримується у всіх браузерах (завдяки shim) і є стандартом W3C.

Установка

Щоб встановити компонент AssetMapper, виконайте:

1
$ composer require symfony/asset-mapper symfony/asset symfony/twig-pack

На додаток до symfony/asset-mapper, це також гарантує, що у вас доступні Компонент Asset та Twig.

Якщо ви використовуєте Symfony Flex , ви закінчили! Рецепт щойно додав наступні файли:

  • assets/app.js Ваш головний файл JavaScript;
  • assets/styles/app.css Ваш головний файл CSS;
  • config/packages/asset_mapper.yaml Там, де ви визначаєте ваш ресурс "paths";
  • importmap.php Ваш файл конфігурації importmap.

Він також оновив файл templates/base.html.twig:

1
2
3
{% block javascripts %}
+    {% block importmap %}{{ importmap('app') }}{% endblock %}
{% endblock %}

Якщо ви не використовуєте Flex, вам знадобиться створити та оновити ці файли вручну. Дивіться останній рецепт asset-mapper, щоб дізнатися точний зміст цих файлів.

Мапування та посилання на ресурси

Компонент AssetMapper працює шляхом визначення каталогів/шляхів ресурсів, які ви хочете зробити публічними. Ці ресурси потім версіонуються і на них легко послатися. Завдяки файлу asset_mapper.yaml, ваш додаток запускається з одним мапованим шляхом: каталогом assets/.

Якщо ви створите файл assets/images/duck.png, ви можете послатися на нього у шаблоні за допомогою:

1
<img src="{{ asset('images/duck.png') }}">

Шлях - images/duck.png - відносний до вашого мапованого каталогу (assets/). Це відомо як логічний шлях до вашого ресурсу.

Якщо ви подивитеся на HTML у вашій сторінці, URL буде чимось на кшталт: /assets/images/duck-3c16d9220694c0e56d8648f25e6035e9.png. Якщо ви оновите цей файл, частина версії URL зміниться автоматично!

Подача ресурсів у розробці проти виробництва

У середовищі dev, URL - /assets/images/duck-3c16d9220694c0e56d8648f25e6035e9.png - обробляється та повертається вашим додатком Symfony. Для середовища prod, до розгортання, ви маєте виконати:

1
$ php bin/console asset-map:compile

Це фізично скопіює всі файли з ваших мапованих каталогів у public/assets/, щоб вони видавалися напряму вашим веб-сервером. Дивіться Розгортання , щоб дізнатися більше.

Tip

Якщо вам потрібно скопіювати скомпільовані ресурси в інше місце (наприклад, завантажити їх до S3), створіть сервіс, який реалізує Symfony\Component\AssetMapper\Path\PublicAssetsFilesystemInterface
і встановіть його ідентифікатор сервісу (або псевдонім) як asset_mapper.local_public_assets_filesystem. (щоб замінити вбудований сервіс).

Налагодження: як побачити всі маповані ресурси

Щоб побачити всі маповані ресурси у вашому додатку, виконайте:

1
$ php bin/console debug:asset-map

Це відобразить вам усі маповані шляхи та ресурси всередині кожного:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
Шляхи AssetMapper
-----------------

--------- --------------------------
 Шлях      Префікс з простором імен
--------- --------------------------
assets

Mapped Assets
-------------

------------------ ----------------------------------------------------
 Логічний шлях      Шлях файлової системи
------------------ ----------------------------------------------------
 app.js             assets/app.js
 styles/app.css     assets/styles/app.css
 images/duck.png    assets/images/duck.png

"Логічний шлях" - це шлях, який слід використовувати при посиланні на ресурс, наприклад, з шаблону.

Importmaps та написання JavaScript

Всі сучасні браузери підтримують ствердження import JavaScript та сучасні функції ES6 на кшталт класів. Тому цей код "просто працює":

1
2
3
4
5
// assets/app.js
import Duck from './duck.js';

const duck = new Duck('Waddles');
duck.quack();
1
2
3
4
5
6
7
8
9
// assets/duck.js
export default class {
    constructor(name) {
        this.name = name;
    }
    quack() {
        console.log(`${this.name} says: Quack!`);
    }
}

Завдяки функції Twig {{ importmap() }}, про яку ви дізнаєтеся у цьому розділі, файл assets/app.js завантажується та виконується браузером.

Tip

При імпорті відносних файлів обов'язково додавайте розширення .js. На відміну від Node, в середовищі браузера розширення є обов'язковим.

Імпорт сторонніх пакетів JavaScript

Припустимо, ви хочете використати пакет npm, наприклад bootstrap. Технічно, це можна зробити, імпортувавши його повну URL-адресу, наприклад, з CDN:

1
import { Alert } from 'https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/+esm';

Але ой! Необхідність додавання цієї URL-адреси - це біль! Замість цього ми можемо додати її до нашої "importmap" за допомогою команди importmap:require. Цю команду можна використати для завантаження будь-якого пакету npm:

1
$ php bin/console importmap:require bootstrap

Це додає пакет bootstrap до вашого файлу importmap.php:

1
2
3
4
5
6
7
8
9
10
// importmap.php
return [
    'app' => [
        'path' => './assets/app.js',
        'entrypoint' => true,
    ],
    'bootstrap' => [
        'version' => '5.3.0',
    ],
];

Note

Іноді пакет, наприклад bootstrap, може мати одну або більше залежностей, таких як @popperjs/core. Команда importmap:require додасть як основний пакет так і його залежності. Якщо пакет містить основний файл CSS, його також буде додано (див. Робота зі стороннім CSS ).

Note

Якщо ви отримуєте помилку 404, можливо, виникла проблема з пакетом JavaScript, який не дозволяє йому обслуговуватися CDN jsDelivr. Наприклад, у пакеті можуть бути відсутні такі властивості, як main або module у файлі конфігурації package.json. Спробуйте звернутися до утримувача пакету з проханням виправити ці проблеми.

Тепер ви можете імпортувати пакет bootstrap як звичайно:

1
2
import { Alert } from 'bootstrap';
// ...

Усі пакети в importmap.php завантажуються до каталогу assets/vendor/,
який слід ігнорувати git'у (рецепт Flex додає його до .gitignore за вас). Щоб завантажити файли на інші комп'ютери, вам слід виконати наступну команду якщо деякі з них відсутні:

1
$ php bin/console importmap:install

Ви можете оновити сторонні пакети до їхніх поточних версій виконавши:

1
2
3
4
5
6
7
8
# перелічує застарілі пакети і показує їхні найновіші версії 
$ php bin/console importmap:outdated
# оновлює всі застарілі пакети
$ php bin/console importmap:update

# ви також можете запустити команди лише для заданого списку пакетів 
$ php bin/console importmap:update bootstrap lodash
$ php bin/console importmap:outdated bootstrap lodash

Як працює importmap?

Як цей файл importmap.php дозволяє вам імпортувати bootstrap? Це відбувається завдяки функції Twig {{ importmap() }} в base.html.twig, яка виводить importmap:

1
2
3
4
5
6
7
<script type="importmap">{
    "imports": {
        "app": "/assets/app-4e986c1a2318dd050b1d47db8d856278.js",
        "/assets/duck.js": "/assets/duck-1b7a64b3b3d31219c262cf72521a5267.js",
        "bootstrap": "https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/+esm"
    }
}</script>

Мапи імпорту - це вбудована функція браузера. Коли ви імпортуєте bootstrap з JavaScript, браузер подивиться на importmap і побачить, що він повинен отримати пакет з асоційованого шляху.

Але звідки взявся запис імпорту /assets/duck.js? Це не живе в importmap.php.Чудове питання!

Файл assets/app.js вище імпортує ./duck.js. Коли ви імпортуєте файл за допомогою відносного шляху, ваш браузер шукає цей файл відносно того, що імпортує його. Отже, він шукатиме /assets/duck.js. Цей URL був би правильним, за винятком того, що файл duck.js є версіонованим. На щастя, компонент AssetMapper бачить цей імпорт і додає мапування з /assets/duck.js до правильного, версіонованого імені файлу. Результат: імпорт ./duck.js просто працює!

Функція importmap() також виводить ES-модуль shim, щоб старіші браузери розуміли мапи імпорту (див. конфігурацію polyfill ).

Точка входу та попереднє завантаження "app"

"Точка входу" - це основний файл JavaScript, який завантажує браузер, і ваш додаток починається з нього за замовчуванням:

1
2
3
4
5
6
7
8
// importmap.php
return [
    'app' => [
        'path' => './assets/app.js',
        'entrypoint' => true,
    ],
    // ...
];

На додаток до мапи імпорту, {{ importmap('app') }} у base.html.twig виводить декілька інших речей, зокрема:

1
<script type="module">import 'app';</script>

Цей рядок вказує браузеру завантажити запис мапи імпорту app, що призводить до виконання коду у файлі assets/app.js.

Функція importmap() також виводить набір "попередніх завантажень":

1
2
<link rel="modulepreload" href="/assets/app-4e986c1a2318dd050b1d47db8d856278.js">
<link rel="modulepreload" href="/assets/duck-1b7a64b3b3d31219c262cf72521a5267.js">

Це оптимізація продуктивності, про яку ви можете дізнатися нижче у Продуктивність: Додавання попереднього завантаження .

Імпорт конкретних файлів зі стороннього пакета

Іноді вам буде потрібно імпортувати конкретний файл з пакета. Наприклад, припустимо, ви інтегруєте highlight.js і хочете імпортувати лише ядро і певну мову:

1
2
3
4
5
import hljs from 'highlight.js/lib/core';
import javascript from 'highlight.js/lib/languages/javascript';

hljs.registerLanguage('javascript', javascript);
hljs.highlightAll();

У цьому випадку додавання пакета highlight.js до вашого файлу importmap.php не спрацює: що б ви не імпортували - наприклад, highlight.js/lib/core - має точно співпадати із записом у файлі importmap.php.

Замість цього використайте importmap:require і передайте йому точні шляхи, які вам потрібні. Цей приклад також показує, як можна вимагати декілька пакетів одночасно:

1
$ php bin/console importmap:require highlight.js/lib/core highlight.js/lib/languages/javascript

Глобальні змінні на кшталт jQuery

Можливо, ви звикли покладатися на глобальні змінні, такі як змінна $ в jQuery:

1
2
3
4
5
// assets/app.js
import 'jquery';

// app.js або будь-який інший файл
$('.something').hide(); // НЕ ПРАЦЮВАТИМЕ!

Але у модульному середовищі (наприклад, в AssetMapper), коли ви імпортуєте бібліотеку типу jquery, вона не створює глобальну змінну. Натомість, вам слід імпортувати її і встановити як змінну у кожному файлі, де вона вам потрібна:

1
2
import $ from 'jquery';
$('.something').hide();

Ви навіть можете зробити це з тегу вбудованого скрипта:

1
2
3
4
<script type="module">
    import $ from 'jquery';
    $('.something').hide();
</script>

Якщо вам необхідно зробити щось глобальною змінною, зробіть це вручну зсередини app.js:

1
2
3
import $ from 'jquery';
// речі у "window" стають глобальними змінними
window.$ = $;

Робота з CSS

CSS можна додати на вашу сторінку, імпортувавши його з файлу JavaScript. За замовчуванням assets/app.js вже імпортує assets/styles/app.css

1
2
3
4
// assets/app.js
import '../styles/app.css';

// ...

Коли ви викликаєте importmap('app') у base.html.twig, AssetMapper аналізує assets/app.js (і будь-які файли JavaScript, які він імпортує), шукаючи ствердження import для CSS-файлів. Фінальна колекція CSS-файлів відображається на сторінці у вигляді тегів link у тому порядку, в якому вони були імпортовані.

Note

Імпорт файлу CSS не є чимось, що підтримується за замовчуванням модулями JavaScript. AssetMapper робить це, додаючи спеціальний запис importmap для кожного CSS-файлу. Ці спеціальні записи є валідними, але нічого не роблять. AssetMapper додає тег <link> для кожного CSS-файлу, але коли JavaScript виконує твердження import, нічого додаткового не відбувається.

Робота зі стороннім CSS

Іноді пакет JavaScript міститиме один або декілька файлів CSS. Наприклад, пакет bootstrap має файл dist/css/bootstrap.min.css.

Ви можете вимагати файли CSS так само, як і файли JavaScript:

1
$ php bin/console importmap:require bootstrap/dist/css/bootstrap.min.css

Щоб додати його на сторінку, імпортуйте його з файлу JavaScript:

1
2
3
4
// assets/app.js
import 'bootstrap/dist/css/bootstrap.min.css';

// ...

Tip

Деякі пакети, такі як bootstrap, рекламують, що вони містять файл CSS файл. У тих випадках, коли ви importmap:require bootstrap,
файл CSS також буде додано до importmap.php для зручності. Якщо якийсь пакет не рекламує свій CSS-файл у властивості style файлу конфігурації package.json, спробуйте зв'язатися з утримувачем пакета і попросити його додати це.

Шляхи всередині CSS-файлів

Зсередини CSS ви можете посилатися на інші файли за допомогою звичайної функції CSS url() і відносним шляхом до цільового файлу:

1
2
3
4
5
/* assets/styles/app.css */
.quack {
    /* file lives at assets/images/duck.png */
    background-image: url('../images/duck.png');
}

Шлях у фінальному файлі app.css буде автоматично включено до оновленої URL-адреси для duck.png:

1
2
3
4
/* public/assets/styles/app-3c16d9220694c0e56d8648f25e6035e9.css */
.quack {
    background-image: url('../images/duck-3c16d9220694c0e56d8648f25e6035e9.png');
}

Використання Tailwind CSS

Щоб використовувати CSS-фреймворк Tailwind з компонентом AssetMapper, ознайомтеся з symfonycasts/tailwind-bundle.

Використання Sass

Щоб використовувати Sass з компонентом AssetMapper, ознайомтеся з symfonycasts/sass-bundle.

Лінивий імпорт CSS з файлу JavaScript

Якщо у вас є деякий CSS, який ви хочете завантажити ліниво, ви можете зробити це за допомогою звичайного, "динамічного" синтаксису імпорту:

1
2
3
4
// assets/any-file.js
import('./lazy.css');

// ...

У цьому випадку lazy.css буде завантажено асинхронно, а потім додано на сторінку. Якщо ви використовуєте динамічний імпорт для лінивого завантаження JavaScript-файлу, і цей файл імпортує файл CSS (використовуючи нединамічний синтаксис import), цей файл CSS також буде завантажено асинхронно.

Проблеми та налагодження

Є декілька розповсюджених проблем та помилок з якими ви можете зіштовхнутися.

Відсутній запис importmap

Одна з найпоширеніших помилок буде виходити з консолі вашого браузера і буде приблизно такою:

Failed to resolve module specifier " bootstrap". Relative references must start with either "/", "./", or "../".

Або:

The specifier "bootstrap" was a bare specifier, but was not remapped to anything. Relative module specifiers must start with "./", "../" or "/".

Це означає, що десь у вашому JavaScript ви імпортуєте сторонній пакет, наприклад, import 'bootstrap'. Браузер намагається знайти цей пакет у вашому файлі importmap, але його там немає.

Майже завжди це можна виправити, додавши його до вашого importmap:

1
$ php bin/console importmap:require bootstrap

Note

Деякі браузери, такі як Firefox, показують де знаходиться цей код "імпорту", тоді як інші, наприклад, Chrome, наразі це не показують.

404 Не знайдено для файлу JavaScript, CSS або зображення

Іноді файл JavaScript, який ви імпортуєте (наприклад, import './duck.js'), або файл CSS/зображення, на який ви посилаєтеся, не буде знайдено, і ви побачите помилку 404 у консолі вашого браузера. Ви також помітите, що в URL 404 відсутній хеш версії в імені файлу (наприклад, 404 до /assets/duck.js замість шляху на кшталт /assets/duck.1b7a64b3b3d31219c262cf72521a5267.js).

Зазвичай це відбувається тому, що шлях вказано неправильно. Якщо ви посилаєтеся на файл безпосередньо в шаблоні Twig:

1
<img src="{{ asset('images/duck.png') }}">

Тоді шлях, який ви передаєте asset() має бути "логічним шляхом" до файлу. Використайте команду debug:asset-map, щоб побачити всі валідні логічні шляхи у вашому додатку.

Швидше за все, ви імпортуєте непрацюючий ресурс з файлу CSS (наприклад @import url('other.css')) або файлу JavaScript:

1
2
// assets/controllers/farm-controller.js
import '../farm/chicken.js';

При цьому шлях має бути відносним до файлу, який його імпортує (а в JavaScript-файлах він повинен починатися з ./ або ../). У цьому випадку ../farm/chicken.js буде вказувати на assets/farm/chicken.js. Щоб переглянути список всіх невалідних імпортованих даних у вашому додатку, виконайте:

1
2
$ php bin/console cache:clear
$ php bin/console debug:asset-map

Будь-який невалідний імпорт буде показано у вигляді попереджень у верхній частині екрану (переконайтеся, що у вас встановлено symfony/monolog-bundle):

1
2
WARNING   [asset_mapper] Unable to find asset "../images/ducks.png" referenced in "assets/styles/app.css".
WARNING   [asset_mapper] Unable to find asset "./ducks.js" imported from "assets/app.js".

Попередження про відсутній ресурс у коментованому коді

Компонент AssetMapper шукає у ваших файлах JavaScript рядки import, щоб він міг автоматично додати їх до вашої мапи імпорту . Це робиться через регулярний вираз і працює дуже добре, хоча і не ідеально. Якщо ви закоментуєте імпорт, його все одно буде знайдено і додано до вашої карти імпорту. Це нічому не зашкодить, але може бути несподіванкою.

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

Розгортання з компонентом AssetMapper

Коли ви будете готові до розгортання, "скомпілюйте" свої ресурси під час розгортання:

1
$ php bin/console asset-map:compile

Це запише всі ваші версіоновані файли ресурсів до каталогу public/assets/, разом з кількома JSON-файлами (manifest.json, importmap.json тощо),
щоб importmap можна було відобразити миттєво.

Оптимізація продуктивності

Щоб ваш сайт на основі AssetMapper працював без збоїв, вам потрібно зробити кілька речей. Якщо ви хочете скоротити шлях, ви можете скористатися таким сервісом, як Cloudflare, який автоматично зробить більшість з цих дій за вас:

  • Використовуйте HTTP/2: Ваш веб-сервер повинен працювати за протоколом HTTP/2 (або HTTP/3), щоб браузер міг завантажувати ресурси паралельно. HTTP/2 автоматично ввімкнено в Caddy і може бути активовано в Nginx і Apache. Або проксуйте ваш сайт через сервіс, наприклад, Cloudflare, який автоматично увімкне HTTP/2 для вас.
  • Стискайте свої ресурси: Ваш веб-сервер повинен стискати (наприклад, за допомогою gzip) ваші ресурси (JavaScript, CSS, зображення) перед відправкою їх браузеру. Ця функція автоматично вмикається в Caddy і може бути активована в Nginx та Apache. Або ж проксуйте ваш сайт через сервіс на кшталт Cloudflare, який автоматично стискатиме ваші ресурси для вас. У Cloudflare ви також можете увімкнути auto minify для подальшого стискання ваших ресурсів (наприклад, видалення пробілів та коментарів з файлів JavaScript і CSS).
  • Встановлюйте довготривалі заголовки Expires: Ваш веб-сервер повинен встановлювати довготривалі заголовки Expires у ваших ресурсах. Оскільки компонент AssetMapper включає хеш версії у назві файлу кожного ресурсу, ви можете безпечно встановити заголовок Expires на дуже довгий час у майбутньому (наприклад, 1 рік). Це не відбувається автоматично на будь-якому веб-сервері, але це можна легко ввімкнути.

Після того, як ви виконали ці дії, ви можете використовувати такий інструмент, як Lighthouse для перевірки продуктивності вашого сайту!

Продуктивність: розуміння попереднього завантаження

Одна з поширених проблем, про яку може повідомити LightHouse, наступна:

Avoid Chaining Critical Requests

Декілька елементів у цьому списку це нормально. Але якщо цей список довгий або деякі елементи на глибині багатьох рівнів, то вам це треба виправити за допомогою "попереднього завантаження". Щоб зрозуміти проблему, уявіть, що у вас таке налаштування:

  • assets/app.js імпортує ./duck.js
  • assets/duck.js імпортує bootstrap

Коли браузер завантажує сторінку, відбувається наступне:

  1. Браузер завантажує assets/app.js;
  2. Потім він бачить імпорт ./duck.js і завантажує assets/duck.js;
  3. Потім він бачить імпорт bootstrap і завантажує assets/bootstrap.js.

Замість того, щоб завантажувати всі 3 файли паралельно, браузер буде змушений завантажувати їх по черзі, коли він їх виявлятиме. Це може негативно вплинути на продуктивність.

AssetMapper уникає цієї проблеми, виводячи "попередньо завантажені" теги link. Логіка працює наступним чином:

A) Коли ви викликаєте importmap('app') у вашому шаблоні, компонент AssetMapper переглядає файл assets/app.js і знаходить усі файли JavaScript які він імпортує, або файли, які імпортують ці файли, тощо.

B) Потім він виводить тег link для кожного з цих файлів з атрибутом rel="preload". Це вказує браузеру почати завантаження цих файлів негайно, навіть якщо він ще не бачив ствердження import для них.

Крім того, якщо у вашому додатку доступний компонент WebLink, Symfony додасть заголовок Link у відповідь, щоб попередньо завантажити файли CSS.

Поширені запитання

Чи об'єднує компонент AssetMapper ресурси?

Ні! Але це тому, що в цьому більше немає необхідності!

У минулому було прийнято об'єднувати ресурси, щоб зменшити кількість HTTP-запитів, які були зроблені. Завдяки розвитку веб-серверів, таких як HTTP/2, зазвичай не є проблемою тримати ваші ресурси окремо і дозволити браузеру завантажувати їх паралельно. Насправді,
якщо ви тримаєте їх окремо, коли ви оновлюєте один ресурс, браузер може продовжувати використовувати кешовану версію всіх інших ваших ресурсів.

Дивіться Оптимізація для отримання більш детальної інформації.

Чи мінімізує компонент AssetMapper ресурси?

Ні! Мінімізація або стиснення ресурсів важлива, але може бути виконана вашим веб-сервером або за допомогою сервісу на кшталт Cloudflare. Дивіться Оптимізація для більш детальної інформації.

Чи готовий компонент AssetMapper до виробництва? Він продуктивний?

Так! Дуже! Компонент AssetMapper використовує досягнення у технологіях браузерів (наприклад importmaps і нативну підтримку import ) і веб-серверів (наприклад, HTTP/2, який дозволяє ресурсам завантажуватися паралельно). Дивіться інші питання про мінімізацію та комбінування і Оптимізацію для більш детальної інформації.

Сайт https://ux.symfony.com працює на компоненті AssetMapper і має 99% оцінку від Google Lighthouse.

Чи працює компонент AssetMapper з усіма браузерами?

Так! Такі функції, як importmaps та ствердження import підтримуються у всіх сучасних браузерах, але компонент AssetMapper постачається з ES-модулем shim для підтримки importmap у старих браузерах. Отже, він працює скрізь (див. примітку нижче).

У вашому власному коді, якщо ви покладаєтеся на сучасні можливості функцій JavaScript ES6, наприклад, синтаксис класів, це підтримується у всіх браузерах, окрім найстаріших. Якщо вам необхідно підтримувати дуже старі браузери, вам слід скористатися таким інструментом, як Encore замість компонента AssetMapper.

Note

Ствердження import не може бути полізаповненим або шиммінговим, щоб працювати на всіх браузерах. Однак, лише найстаріші браузери не підтримують його - в основному IE 11 (який більше не підтримується Microsoft і має менше 0.4% глобального використання).

Функція importmap налаштовується для роботи у усіх браузерах за допомогою компонента AssetMapper. Однак, шим не працює з "динамічним" імпортом:

1
2
3
4
5
6
7
// це працює
import { add } from './math.js';

// це не працюватиме у найстаріших браузерах
import('./math.js').then(({ add }) => {
    // ...
});

Якщо ви хочете використовувати динамічний імпорт і вам потрібна підтримка певних старих браузерів (https://caniuse.com/import-maps), ви можете скористатися функцією importShim() з shim: https://www.npmjs.com/package/es-module-shims#user-content-polyfill-edge-case-dynamic-import

Чи можу я використовувати це з Sass або Tailwind?

Звичайно! Дивіться Використання Tailwind CSS або Використання Sass .

Чи можу я використовувати це з TypeScript?

Звичайно! Дивіться Використання TypeScript .

Чи можу я використовувати це з JSX або Vue?

Напевно, ні. І якщо ви пишете додаток на React, Svelte або іншому фронтенд-фреймворку, вам, ймовірно, буде краще використовувати безпосередньо їхні інструменти.

JSX можна скомпілювати безпосередньо у нативний JavaScript-файл, але якщо ви використовуєте багато JSX, вам, ймовірно, захочеться використати інструмент на кшталт Encore . Дивіться Документацію UX React для більш детальної інформації про використання з компонентом AssetMapper.

Vue-файли можуть бути написані на нативному JavaScript, і вони будуть працювати з з компонентом AssetMapper. Але ви не можете писати однофайлові компоненти (тобто файли .vue) за допомогою компонента, оскільки вони мають бути використані у системі побудови. Зверніться до Документації UX Vue.js для більш детальної інформації про використання з з компонентом AssetMapper.

Використання Tailwind CSS

See also

Ознайомтеся з symfonycasts/tailwind-bundle для ще простішого способу використання Tailwind з Symfony.

Хочете використовувати CSS фреймворк Tailwind з компонентом AssetMapper? Немає проблем. Спочатку встановіть бінарність tailwindcss. Її можна встановити за допомогою npm (виконайте npm --init, якщо у вас ще немає файлу package.json):

1
$ npm install -D tailwindcss

Або ви можете встановити окрему бінарність Tailwind, яка не вимагає Node.

Далі, згенеруйте файл tailwind.config.js:

1
2
3
4
$ npx tailwindcss init

# or with the standalone binary:
$ ./tailwindcss init

Оновіть tailwind.config.js, щоб вказати на ваш шаблон та файли JavaScript:

1
2
3
4
5
6
7
8
// tailwind.config.js
// ....

-   content: [],
+   content: [
+       "./assets/**/*.js",
+       "./templates/**/*.html.twig",
+   ],

Потім додайте базові рядки до вашого файлу assets/styles/app.css:

1
2
3
4
/* assets/styles/app.css */
@tailwind base;
@tailwind components;
@tailwind utilities;

Тепер, коли Tailwind налаштовано, запустіть бінарність tailwindcss в режимі "watch", щоб побудувати CSS за новим шляхом assets/app.built.css:

1
2
3
4
$ npx tailwindcss build -i assets/styles/app.css -o assets/styles/app.built.css --watch

# або з окремою бінарністю:
$ ./tailwindcss build -i assets/styles/app.css -o assets/styles/app.built.css --watch

Нарешті, замість того, щоб вказувати безпосередньо на styles/app.css у вашому шаблоні, вкажіть на новий файл styles/app.built.css:

1
2
3
4
{# templates/base.html.twig #}

- <link rel="stylesheet" href="{{ asset('styles/app.css') }}">
+ <link rel="stylesheet" href="{{ asset('styles/app.built.css') }}">

Готово! Ви можете ігнорувати файл assets/styles/app.built.css з Git або зафіксувати його, щоб полегшити розгортання.

Використання TypeScript

Щоб використовувати TypeScript з компонентом AssetMapper, ознайомтеся зі статтею sensiolabs/typescript-bundle.

Сторонні пакети та користувацькі шляхи ресурсів

Усі пакети, які мають каталог Resources/public/ або public/, будуть автоматично мати цей каталог доданий як "шлях до ресурсів" за допомогою простору імен: bundles/<BundleName>`. Наприклад, якщо ви використовуєте BabdevPagerfantaBundle і виконаєте команду debug:asset-map, ви побачите ресурс, логічний шлях до якого буде bundles/babdevpagerfanta/css/pagerfanta.css.

Це означає, що ви можете відображати ці ресурси у своїх шаблонах за допомогою функції asset():

1
<link rel="stylesheet" href="{{ asset('bundles/babdevpagerfanta/css/pagerfanta.css') }}">

Насправді, цей шлях - bundles/babdevpagerfanta/css/pagerfanta.css - вже працює у додатках без компонента AssetMapper, оскільки команда assets:install копіює ресурси з пакетів у public/bundles/. Однак, коли компонент AssetMapper увімкнено, файл pagerfanta.css буде автоматично
буде версіонуватися! У результаті буде виведено щось на кшталт:

1
<link rel="stylesheet" href="/assets/bundles/babdevpagerfanta/css/pagerfanta-ea64fc9c55f8394e696554f8aeb81a8e.css">

Перевизначення сторонніх ресурсів

Якщо ви хочете перевизначити сторонній ресурс, ви можете зробити це, створивши файл у вашому каталозі assets/ з тим самим ім'ям. Наприклад, якщо ви хочете перевизначити файл pagerfanta.css, створіть файл за адресою assets/bundles/babdevpagerfanta/css/pagerfanta.css. Цей файл використовуватиметься замість оригінального файлу.

Note

Якщо пакет відображає свої власні ресурси, але вони використовують не пакет за замовчуванням пакет ресурсів , то компонент AssetMapper не буде використано. Це відбувається, наприклад, з EasyAdminBundle.

Імпорт ресурсів поза каталогом assets/

Наразі ви не можете імпортувати ресурси, які знаходяться за межами вашого шляху ресурсів (тобто з каталогу assets/). Наприклад, це не спрацює:

1
2
3
4
/* assets/styles/app.css */

/* ви можете досягти вищезазначених ресурсів/ */
@import url('../../vendor/babdev/pagerfanta-bundle/Resources/public/css/pagerfanta.css');

Однак, ви отримаєте помилку на кшталт цієї:

Запис мапи імпорту "some/package" містить шлях "vendor/some/package/assets/foo.js" але його немає в жодному з ваших шляхів до ресурсів.

Це означає, що ви вказуєте на валідний файл, але цьго файлу немає у жодному з ваших шляхів до ресурсів. Ви можете виправити це, додавши шлях до вашого файлу asset_mapper.yaml:

1
2
3
4
5
6
# config/packages/asset_mapper.yaml
framework:
    asset_mapper:
        paths:
            - assets/
            - vendor/some/package/assets

Потім спробуйте ще раз виконати команду.

Опції конфігурації

Ви можете побачити всі доступні опції конфігурації та деяку інформацію, виконавши:

1
$ php bin/console config:dump framework asset_mapper

Деякі з найбільш важливих опцій описані нижче.

framework.asset_mapper.paths

Ця конфігурація містить всі каталоги, які будуть проскановані на наявність ресурсів. Це може бути простим списком:

1
2
3
4
5
framework:
    asset_mapper:
        paths:
            - assets/
            - vendor/some/package/assets

Ви можете надати кожному шляху "простір імен", який буде використано у мапі ресурсів:

1
2
3
4
5
framework:
    asset_mapper:
        paths:
            assets/: ''
            vendor/some/package/assets/: 'some-package'

У цьому випадку "логічний шлях" до всіх файлів у каталозі vendor/some/package/assets/ буде доповнено префіксом some-package - наприклад, some-package/foo.js.

framework.asset_mapper.excluded_patterns

Це список глобальних патернів, які будуть виключені з мапи ресурсів:

1
2
3
4
framework:
    asset_mapper:
        excluded_patterns:
            - '*/*.scss'

Ви можете використати команду debug:asset-map, щоб перевірити, що очікувані вами файли включені у мапу ресурсів.

framework.asset_mapper.exclude_dotfiles

Чи вилучати з маппера будь-які файли, що починаються з .. Цей корисно, якщо ви хочете уникнути витоку чутливих файлів, таких як .env або .gitignore у файлах, опублікованих маппером ресурсів.

1
2
3
framework:
    asset_mapper:
        exclude_dotfiles: true

Ця опція ввімкнена за замовчуванням.

framework.asset_mapper.importmap_polyfill

Конфігурує полізаповнення для старих браузерів. За замовчуванням, ES-модуль shim завантажується через CDN (тобто значенням за замовчуванням для цього параметра є `es-module-shims`):

1
2
3
4
5
6
7
8
9
framework:
    asset_mapper:
        # встановіть цю опцію як false, щоб повністю вимкнути shim
        # (ваш сайт/додаток не буде працювати в старих браузерах)
        importmap_polyfill: false

        # ви також можете використовувати користувацьке полізаповнення, додавши його до файлу importmap.php
        # і встановивши цю опцію як ключ цього файлу у файлі importmap.php
        # importmap_polyfill: 'custom_polyfill'

Tip

Ви можете вказати AssetMapper завантажити ES-модуль shim локально за допомогою за допомогою наступної команди, не змінюючи конфігурацію:

1
$ php bin/console importmap:require es-module-shims

framework.asset_mapper.importmap_script_attributes

Це список атрибутів, які будуть додані до тегів <script>, відображених функцією
Twig {{ importmap() }}:

1
2
3
4
framework:
    asset_mapper:
        importmap_script_attributes:
            crossorigin: 'anonymous'

CSS та JavaScript для конкретних сторінок

Іноді ви можете вирішити включити файли CSS або JavaScript лише на певних сторінках. Для JavaScript найпростіший спосіб - завантажити файл за допомогою динамічного імпорту:

1
2
3
4
5
6
7
const someCondition = '...';
if (someCondition) {
    import('./some-file.js');

    // або використати async/await
    // const something = await import('./some-file.js');
}

Інший варіант - створити окрему точку входу . Наприклад, створіть файл checkout.js, який містить всі необхідні вам JavaScript і CSS:

1
2
3
4
// assets/checkout.js
import './checkout.css';

// ...

Далі додайте це до importmap.php і позначте його як точку входу:

1
2
3
4
5
6
7
8
9
// importmap.php
return [
    // the 'app' entrypoint ...

    'checkout' => [
        'path' => './assets/checkout.js',
        'entrypoint' => true,
    ],
];

Нарешті, на сторінці, де потрібен цей JavaScript, викличте importmap() і передайте як app, так і checkout:

1
2
3
4
5
6
7
8
9
10
{# templates/products/checkout.html.twig #}
{#
    Перевизначити блок "importmap" з base.html.twig.
    Якщо у вас немає цього блоку, додайте його довкола виклику {{ importmap('app') }}.
#}
{% block importmap %}
    {# НЕ викликати parent() #}

    {{ importmap(['app', 'checkout']) }}
{% endblock %}

Передавши app і checkout, функція importmap() виведе importmap, а також додасть тег <script type="module">, який завантажить файл app.js і файл checkout.js. Важливо не викликати parent() у блоці importmap. Кожна сторінка може мати тільки мати одну мапу імпорту, тому importmap() повинен бути викликаний рівно один раз.

Якщо з якихось причин ви хочете виконати тільки checkout.js і не app.js, передайте в importmap() тільки checkout.

Система кешування компонента AssetMapper у розробці

Під час розробки вашого додатку в режимі налагодження, компонент AssetMapper обчислюватиме зміст кожного файлу ресурсів і кешуватиме його. Щоразу, коли цей файл змінюється, компонент автоматично переобчислить зміст.

Система також враховує "залежності": Якщо app.css містить @import url('other.css'), то зміст файлу app.css також буде переобчислюватися щоразу, коли змінюватиметься other.css. Це пов'язано з тим, що хеш версії other.css зміниться... що призведе до зміни кінцевого змісту app.css, оскільки він містить в собі фінальне ім'я файлу other.css.

Здебільшого ця система просто працює. Але якщо у вас є файл, який не переобчислюється, коли ви цього очікуєте, ви можете виконати:

1
$ php bin/console cache:clear

Це змусить компонент AssetMapper переобчислити зміст усіх файлів.

Проведіть аудити безпеки для ваших залежностей

Подібно до npm, компонент AssetMapper постачається з командою, яка перевіряє вразливості у залежностях вашого додатку:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
$ php bin/console importmap:audit

--------  -----------------------------------------------------  ---------  -------  ------------  -----------------------------------------------------
Тяжкість  Назва                                                  Пакет      Версія   Виправлено в  Більше інформації
--------  -----------------------------------------------------  ---------  -------  ------------  -----------------------------------------------------
Середня   вразливість jQuery до міжсайтового скриптингу          jquery     3.3.1    3.5.0         https://api.github.com/advisories/GHSA-257q-pV89-V3xv
Висока    забруднення прототипу в JSON5 методом розбору          json5      1.0.0    1.0.2         https://api.github.com/advisories/GHSA-9c47-m6qq-7p4h
Середня   semver вразливий до відмови RegExp  в обслуговуванні   semver     4.3.0    5.7.2         https://api.github.com/advisories/GHSA-c2qf-rxjj-qqgw
Критична  забруднення прототипу в minimist                       minimist   1.1.3    1.2.6         https://api.github.com/advisories/GHSA-xvch-5gv4-984h
Середня   вразливі залежності ESLint                             minimist   1.1.3    1.2.2         https://api.github.com/advisories/GHSA-7fhm-mqm4-2wp7
Середня   Bootstrap вразливий до міжсайтового скриптингу         bootstrap  4.1.3    4.3.1         https://api.github.com/advisories/GHSA-9v3M-8fp8-mi99
--------  -----------------------------------------------------  ---------  -------  ------------  -----------------------------------------------------

знайдено 7 пакетів: проведено аудито 7 / пропущено 0
знайдено 6 вразливостей: 1 критична / 1 висока / 4 середні

Команда поверне код завершення 0, якщо вразливості не знайдено, або код завершення 1 у протилежному випадку. Це означає, що ви можете легко інтегрувати цю команду як частину вашого CI, щоб отримувати попередження щоразу, коли буде знайдено нову вразливість.

Tip

Команда приймає опцію --format для вибору формату виведення між txt та json.