Skip to content

[Vue] Add support for lazy-loading with Async Components #482

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Oct 4, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 34 additions & 10 deletions src/Vue/Resources/assets/dist/register_controller.js
Original file line number Diff line number Diff line change
@@ -1,15 +1,39 @@
import { defineAsyncComponent } from 'vue';

function registerVueControllerComponents(context) {
const vueControllers = {};
const importAllVueComponents = (r) => {
r.keys().forEach((key) => (vueControllers[key] = r(key).default));
};
importAllVueComponents(context);
window.resolveVueComponent = (name) => {
const component = vueControllers[`./${name}.vue`];
if (typeof component === 'undefined') {
throw new Error(`Vue controller "${name}" does not exist`);
const vueControllers = context.keys().reduce((acc, key) => {
acc[key] = undefined;
return acc;
}, {});
function loadComponent(name) {
const componentPath = `./${name}.vue`;
if (componentPath in vueControllers && typeof vueControllers[componentPath] === 'undefined') {
const module = context(componentPath);
if (module.default) {
vueControllers[componentPath] = module.default;
}
else if (module instanceof Promise) {
vueControllers[componentPath] = defineAsyncComponent(() => new Promise((resolve, reject) => {
module
.then((resolvedModule) => {
if (resolvedModule.default) {
resolve(resolvedModule.default);
}
else {
reject(new Error(`Cannot find default export in async Vue controller "${name}".`));
}
})
.catch(reject);
}));
}
else {
throw new Error(`Vue controller "${name}" does not exist.`);
}
}
return component;
return vueControllers[componentPath];
}
window.resolveVueComponent = (name) => {
return loadComponent(name);
};
}

Expand Down
47 changes: 36 additions & 11 deletions src/Vue/Resources/assets/src/register_controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
'use strict';

import type { Component } from 'vue';
import { defineAsyncComponent } from 'vue';

declare global {
function resolveVueComponent(name: string): Component;
Expand All @@ -20,21 +21,45 @@ declare global {
}

export function registerVueControllerComponents(context: __WebpackModuleApi.RequireContext) {
const vueControllers: { [key: string]: object } = {};
const vueControllers = context.keys().reduce((acc, key) => {
acc[key] = undefined;
return acc;
}, {} as Record<string, object | undefined>);

const importAllVueComponents = (r: __WebpackModuleApi.RequireContext) => {
r.keys().forEach((key) => (vueControllers[key] = r(key).default));
};
function loadComponent(name: string): object | never {
const componentPath = `./${name}.vue`;

if (componentPath in vueControllers && typeof vueControllers[componentPath] === 'undefined') {
const module = context(componentPath);
if (module.default) {
vueControllers[componentPath] = module.default;
} else if (module instanceof Promise) {
vueControllers[componentPath] = defineAsyncComponent(
() =>
new Promise((resolve, reject) => {
module
.then((resolvedModule) => {
if (resolvedModule.default) {
resolve(resolvedModule.default);
} else {
reject(
new Error(`Cannot find default export in async Vue controller "${name}".`)
);
}
})
.catch(reject);
})
);
} else {
throw new Error(`Vue controller "${name}" does not exist.`);
}
}

importAllVueComponents(context);
return vueControllers[componentPath] as object;
}

// Expose a global Vue loader to allow rendering from the Stimulus controller
window.resolveVueComponent = (name: string): object => {
const component = vueControllers[`./${name}.vue`];
if (typeof component === 'undefined') {
throw new Error(`Vue controller "${name}" does not exist`);
}

return component;
return loadComponent(name);
};
}
3 changes: 3 additions & 0 deletions src/Vue/Resources/assets/test/fixtures-lazy/Goodbye.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
<template>
<h1>Goodbye {{ name }}</h1>
</template>
11 changes: 10 additions & 1 deletion src/Vue/Resources/assets/test/register_controller.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,24 @@
import {registerVueControllerComponents} from '../src/register_controller';
import {createRequireContextPolyfill} from './util/require_context_poylfill';
import Hello from './fixtures/Hello.vue'
import Goodbye from './fixtures-lazy/Goodbye.vue'

require.context = createRequireContextPolyfill(__dirname);

describe('registerVueControllerComponents', () => {
it('test', () => {
it('test should resolve components synchronously', () => {
registerVueControllerComponents(require.context('./fixtures', true, /\.vue$/));
const resolveComponent = window.resolveVueComponent;

expect(resolveComponent).not.toBeUndefined();
expect(resolveComponent('Hello')).toBe(Hello);
});

it('test should resolve lazy components asynchronously', () => {
registerVueControllerComponents(require.context('./fixtures-lazy', true, /\.vue$/, 'lazy'));
const resolveComponent = window.resolveVueComponent;

expect(resolveComponent).not.toBeUndefined();
expect(resolveComponent('Goodbye')).toBe(Goodbye);
});
});
4 changes: 4 additions & 0 deletions src/Vue/Resources/doc/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,10 @@ You also need to add the following lines at the end to your ``assets/app.js`` fi
// they are not necessary.
registerVueControllerComponents(require.context('./vue/controllers', true, /\.vue$/));

// If you prefer to lazy-load your Vue.js controller components, in order to reduce to keep the JavaScript bundle the smallest as possible,
// and improve performances, you can use the following line instead:
//registerVueControllerComponents(require.context('./vue/controllers', true, /\.vue$/, 'lazy'));


Usage
-----
Expand Down
2 changes: 1 addition & 1 deletion ux.symfony.com/assets/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,4 @@ import Tab from 'bootstrap/js/dist/tab';

// initialize symfony/ux-react
registerReactControllerComponents(require.context('./react/controllers', true, /\.(j|t)sx?$/));
registerVueControllerComponents(require.context('./vue/controllers', true, /\.vue?$/));
registerVueControllerComponents(require.context('./vue/controllers', true, /\.vue?$/, 'lazy'));