VueJS. Загрузка vue-компонентов по требованию в Laravel 5 (ленивая загрузка)
Руководство по ленивой загрузке vue-компонентов в Laravel 5.
Теория ленивой загрузки компонентов
Ленивая загрузка vue-компонентов (подключение при необходимости) реализуется несколькими инструментами:
- Vue - обеспечивает функционал асинхронной загрузки компонентов.
- Webpack - разделяет js-билды на части/chunks с помощью функции require.ensure().
- Babel плагин syntax-dynamic-import (не обязательно) - предоставляет функционал отложенной загрузки модулей (динамический импорт) функцией import().
Использование require.ensure()
Webpack предоставляет legacy способ для динамической загрузки модулей - require.ensure(), при использовании которой, webpack собирает модули по отдельным файлам (chunk) и подгружает их по требованию.
Пример динамической загрузки vue-компонента с использованием require.ensure(), подробнее:
const MyComponent = function(resolve) {
// @note Возвращает Promise!
return require.ensure([], function(require) {
// @note Функцию resolve() предоставляет Vue.js
return resolve(require('~/components/lazy/my-component'));
}, 'chunk-name');
};
Vue.component('my-component', MyComponent);
Пример динамической загрузки vue-компонента с использованием AMD синтаксиса:
const MyComponent = function(resolve) {
return require(['~/components/lazy/my-component'], function (module) {
// @note Функцию resolve() предоставляет Vue.js
return resolve(module);
});
};
Vue.component('my-component', MyComponent);
const MyComponent = resolve => require(['~/components/lazy/my-component'], m => resolve(m));
Vue.component('my-component', MyComponent);
Примечание
Не путаем функции require.ensure() и require.resolve()! Webpack ф-ция require.resolve() загружает модуль немедленно и возвращает его закешированный числовой id. Ф-ция не обеспечивает механизм динамической загрузки!
// @note Загружает модуль и возвращает его закешированный числовой идентификатор.
const cachedModuleId = require.resolve('~/components/lazy/my-component');
const fn = __webpack_modules__[cachedModuleId];
Использование import()
Ф-ция import() это более современный синтаксис для динамической загрузки модулей.
const MyComponent = function() {
return import(/* webpackChunkName: "doughnut-missions-chart" */ '~/components/lazy/my-component');
};
Vue.component('my-component', MyComponent);
const MyComponent = () => import('~/components/lazy/my-component');
Vue.component('my-component', MyComponent);
Расширенная конфигурация импорта (документация):
/* webpackPrefetch: true */
/* webpackPreload: true */
/* webpackMode: "lazy" */
/* webpackMode: "lazy-once" */
/* webpackMode: "eager" */
/* webpackMode: "weak" */
Для использования этой ф-ции необходимо установить плагин babel-plugin-syntax-dynamic-import:
npm install --save-dev babel-plugin-syntax-dynamic-import # 6-я версия
npm install --save-dev @babel/plugin-syntax-dynamic-import # 7-я версия
И добавить название плагина "syntax-dynamic-import" в конфиг .babelrc:
{
"plugins": [
"transform-runtime",
"transform-async-to-generator",
"syntax-dynamic-import"
]
}
Без установленного плагина webpack 3.5 выдает ошибку:
Syntax Error: Unexpected token (X.X)
> 62 | return resolve(import('~/components/lazy/my-component'));
| ^
Внимание!
Не оставляйте закомментированные строки с вызовом import() - плагин syntax-dynamic-import не учитывает комментарии и будет билдить лишние chunk'и!
Примечание
Есть еще плагины, которые предоставляют функционал deferred dynamic import(), которые я не проверял:
- https://github.com/airbnb/babel-plugin-dynamic-import-webpack ⭐ 407
- https://github.com/airbnb/babel-plugin-dynamic-import-node ⭐ 238
Конфигурация laravel-mix
По умолчанию в Laravel chunk-билды будут сохраняться в каталоге public/.
Пример конфигурации webpack.mix.js для перемещения всех билдов в каталог public/build/.
const path = require('path');
const mix = require('laravel-mix');
mix.setPublicPath('public/build/'); // @note Корневой путь к билдам
mix.setResourceRoot('build/'); // @note Задаем путь к шрифтам
mix
.js('resources/assets/js/app.js', 'app.build.js')
.sass('resources/assets/sass/app.scss', 'app.build.css')
.sourceMaps();
mix.webpackConfig({
output: {
path: path.resolve('public/build/'), // @note Задаем каталог для chunk'ов
publicPath: '/build/',
//filename: 'build.[name].js',
chunkFilename: '[name].chunk.js' // @note Плейсхолдеры: [id], [name], [chunkhash]
},
// ...
});
В шаблонах подключать assets'ы так:
<script src="{{ mix('app.build.js', 'build') }}"></script>
Материалы:
- Статья на инглише по ленивой загрузке Vue компонентов с использованием ф-ции import() - https://alexjoverm.github.io/2017/07/16/Lazy-load-in-Vue-using-Webpack-s-code-splitting/
- Документация VueJS "Асинхронные-компоненты" - https://ru.vuejs.org/v2/guide/components-dynamic-async.html#Асинхронные-компоненты
- Документация VueJS "Lazy Loading Routes" - https://router.vuejs.org/en/advanced/lazy-loading.html
- WebPack Сode Splitting - https://webpack.js.org/guides/code-splitting/
- Использование плагина babel-plugin-syntax-dynamic-import - https://github.com/JeffreyWay/laravel-mix/issues/1249#issuecomment-335112415
#require.ensure, #import(), #vue lazy load, #deferred module loading