Skip to content

Commit 8fa08d0

Browse files
Kocalweaverryan
authored andcommitted
[Vue] Add support for lazy-loading with Async Components
1 parent 7c1d0da commit 8fa08d0

File tree

6 files changed

+88
-23
lines changed

6 files changed

+88
-23
lines changed

src/Vue/Resources/assets/dist/register_controller.js

Lines changed: 34 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,39 @@
1+
import { defineAsyncComponent } from 'vue';
2+
13
function registerVueControllerComponents(context) {
2-
const vueControllers = {};
3-
const importAllVueComponents = (r) => {
4-
r.keys().forEach((key) => (vueControllers[key] = r(key).default));
5-
};
6-
importAllVueComponents(context);
7-
window.resolveVueComponent = (name) => {
8-
const component = vueControllers[`./${name}.vue`];
9-
if (typeof component === 'undefined') {
10-
throw new Error(`Vue controller "${name}" does not exist`);
4+
const vueControllers = context.keys().reduce((acc, key) => {
5+
acc[key] = undefined;
6+
return acc;
7+
}, {});
8+
function loadComponent(name) {
9+
const componentPath = `./${name}.vue`;
10+
if (componentPath in vueControllers && typeof vueControllers[componentPath] === 'undefined') {
11+
const module = context(componentPath);
12+
if (module.default) {
13+
vueControllers[componentPath] = module.default;
14+
}
15+
else if (module instanceof Promise) {
16+
vueControllers[componentPath] = defineAsyncComponent(() => new Promise((resolve, reject) => {
17+
module
18+
.then((resolvedModule) => {
19+
if (resolvedModule.default) {
20+
resolve(resolvedModule.default);
21+
}
22+
else {
23+
reject(new Error(`Cannot find default export in async Vue controller "${name}".`));
24+
}
25+
})
26+
.catch(reject);
27+
}));
28+
}
29+
else {
30+
throw new Error(`Vue controller "${name}" does not exist.`);
31+
}
1132
}
12-
return component;
33+
return vueControllers[componentPath];
34+
}
35+
window.resolveVueComponent = (name) => {
36+
return loadComponent(name);
1337
};
1438
}
1539

src/Vue/Resources/assets/src/register_controller.ts

Lines changed: 36 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
'use strict';
1111

1212
import type { Component } from 'vue';
13+
import { defineAsyncComponent } from 'vue';
1314

1415
declare global {
1516
function resolveVueComponent(name: string): Component;
@@ -20,21 +21,45 @@ declare global {
2021
}
2122

2223
export function registerVueControllerComponents(context: __WebpackModuleApi.RequireContext) {
23-
const vueControllers: { [key: string]: object } = {};
24+
const vueControllers = context.keys().reduce((acc, key) => {
25+
acc[key] = undefined;
26+
return acc;
27+
}, {} as Record<string, object | undefined>);
2428

25-
const importAllVueComponents = (r: __WebpackModuleApi.RequireContext) => {
26-
r.keys().forEach((key) => (vueControllers[key] = r(key).default));
27-
};
29+
function loadComponent(name: string): object | never {
30+
const componentPath = `./${name}.vue`;
31+
32+
if (componentPath in vueControllers && typeof vueControllers[componentPath] === 'undefined') {
33+
const module = context(componentPath);
34+
if (module.default) {
35+
vueControllers[componentPath] = module.default;
36+
} else if (module instanceof Promise) {
37+
vueControllers[componentPath] = defineAsyncComponent(
38+
() =>
39+
new Promise((resolve, reject) => {
40+
module
41+
.then((resolvedModule) => {
42+
if (resolvedModule.default) {
43+
resolve(resolvedModule.default);
44+
} else {
45+
reject(
46+
new Error(`Cannot find default export in async Vue controller "${name}".`)
47+
);
48+
}
49+
})
50+
.catch(reject);
51+
})
52+
);
53+
} else {
54+
throw new Error(`Vue controller "${name}" does not exist.`);
55+
}
56+
}
2857

29-
importAllVueComponents(context);
58+
return vueControllers[componentPath] as object;
59+
}
3060

3161
// Expose a global Vue loader to allow rendering from the Stimulus controller
3262
window.resolveVueComponent = (name: string): object => {
33-
const component = vueControllers[`./${name}.vue`];
34-
if (typeof component === 'undefined') {
35-
throw new Error(`Vue controller "${name}" does not exist`);
36-
}
37-
38-
return component;
63+
return loadComponent(name);
3964
};
4065
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
<template>
2+
<h1>Goodbye {{ name }}</h1>
3+
</template>

src/Vue/Resources/assets/test/register_controller.test.ts

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,15 +12,24 @@
1212
import {registerVueControllerComponents} from '../src/register_controller';
1313
import {createRequireContextPolyfill} from './util/require_context_poylfill';
1414
import Hello from './fixtures/Hello.vue'
15+
import Goodbye from './fixtures-lazy/Goodbye.vue'
1516

1617
require.context = createRequireContextPolyfill(__dirname);
1718

1819
describe('registerVueControllerComponents', () => {
19-
it('test', () => {
20+
it('test should resolve components synchronously', () => {
2021
registerVueControllerComponents(require.context('./fixtures', true, /\.vue$/));
2122
const resolveComponent = window.resolveVueComponent;
2223

2324
expect(resolveComponent).not.toBeUndefined();
2425
expect(resolveComponent('Hello')).toBe(Hello);
2526
});
27+
28+
it('test should resolve lazy components asynchronously', () => {
29+
registerVueControllerComponents(require.context('./fixtures-lazy', true, /\.vue$/, 'lazy'));
30+
const resolveComponent = window.resolveVueComponent;
31+
32+
expect(resolveComponent).not.toBeUndefined();
33+
expect(resolveComponent('Goodbye')).toBe(Goodbye);
34+
});
2635
});

src/Vue/Resources/doc/index.rst

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,10 @@ You also need to add the following lines at the end to your ``assets/app.js`` fi
4747
// they are not necessary.
4848
registerVueControllerComponents(require.context('./vue/controllers', true, /\.vue$/));
4949
50+
// If you prefer to lazy-load your Vue.js controller components, in order to reduce to keep the JavaScript bundle the smallest as possible,
51+
// and improve performances, you can use the following line instead:
52+
//registerVueControllerComponents(require.context('./vue/controllers', true, /\.vue$/, 'lazy'));
53+
5054
5155
Usage
5256
-----

ux.symfony.com/assets/app.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,4 +14,4 @@ import Tab from 'bootstrap/js/dist/tab';
1414

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

0 commit comments

Comments
 (0)