VueJS. Загрузка vue-компонентов по требованию в Laravel 5 (ленивая загрузка)

Категория: VueJS

Руководство по ленивой загрузке vue-компонентов в Laravel 5.

Теория ленивой загрузки компонентов

Ленивая загрузка vue-компонентов (подключение при необходимости) реализуется несколькими инструментами:

  1. Vue - обеспечивает функционал асинхронной загрузки компонентов.
  2. Webpack - разделяет js-билды на части/chunks с помощью функции require.ensure().
  3. 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

категория: VueJS