Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings
This repository was archived by the owner on Mar 7, 2022. It is now read-only.

Commit a2386e5

Browse filesBrowse files
committed
Add vueComponents support
1 parent 5fa79eb commit a2386e5
Copy full SHA for a2386e5

2 files changed

+188-119Lines changed: 188 additions & 119 deletions

File tree

Expand file treeCollapse file tree
Open diff view settings
Filter options
Expand file treeCollapse file tree
Open diff view settings
Collapse file

‎src/core/render/index.js‎

Copy file name to clipboardExpand all lines: src/core/render/index.js
+81-35Lines changed: 81 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -66,15 +66,6 @@ function renderMain(html) {
6666
.findAll('.markdown-section > *')
6767
.filter(elm => isMountedVue(elm));
6868

69-
// Store global data() return value as shared data object
70-
if (
71-
!vueGlobalData &&
72-
docsifyConfig.vueGlobalOptions &&
73-
typeof docsifyConfig.vueGlobalOptions.data === 'function'
74-
) {
75-
vueGlobalData = docsifyConfig.vueGlobalOptions.data();
76-
}
77-
7869
// Destroy/unmount existing Vue instances
7970
for (const mountedElm of mountedElms) {
8071
if (vueVersion === 2) {
@@ -101,6 +92,27 @@ function renderMain(html) {
10192
// Handle Vue content not mounted by markdown <script>
10293
if ('Vue' in window) {
10394
const vueMountData = [];
95+
const vueComponentNames = Object.keys(docsifyConfig.vueComponents || {});
96+
97+
// Register global vueComponents
98+
if (vueVersion === 2 && vueComponentNames.length) {
99+
vueComponentNames.forEach(name => {
100+
const isNotRegistered = !window.Vue.options.components[name];
101+
102+
if (isNotRegistered) {
103+
window.Vue.component(name, docsifyConfig.vueComponents[name]);
104+
}
105+
});
106+
}
107+
108+
// Store global data() return value as shared data object
109+
if (
110+
!vueGlobalData &&
111+
docsifyConfig.vueGlobalOptions &&
112+
typeof docsifyConfig.vueGlobalOptions.data === 'function'
113+
) {
114+
vueGlobalData = docsifyConfig.vueGlobalOptions.data();
115+
}
104116

105117
// vueOptions
106118
vueMountData.push(
@@ -109,48 +121,82 @@ function renderMain(html) {
109121
dom.find(markdownElm, cssSelector),
110122
vueConfig,
111123
])
112-
.filter(
113-
([elm, vueConfig]) => elm && Object.keys(vueConfig || {}).length
114-
)
124+
.filter(([elm, vueConfig]) => elm)
115125
);
116126

117127
// vueGlobalOptions
118-
if (Object.keys(docsifyConfig.vueGlobalOptions || {}).length) {
128+
if (docsifyConfig.vueGlobalOptions || vueComponentNames.length) {
119129
vueMountData.push(
120130
...dom
121131
.findAll('.markdown-section > *')
122132
// Remove duplicates
123133
.filter(elm => !vueMountData.some(([e, c]) => e === elm))
124-
.map(elm => [
125-
elm,
126-
!vueGlobalData
127-
? docsifyConfig.vueGlobalOptions
128-
: // Replace vueGlobalOptions data() return value with shared data
129-
// object. This provides a global store for all Vue instances
130-
// that receive vueGlobalOptions as their configuration.
131-
Object.assign({}, docsifyConfig.vueGlobalOptions, {
132-
data() {
133-
return vueGlobalData;
134-
},
135-
}),
136-
])
134+
// Detect Vue content
135+
.filter(elm => {
136+
const isVueMount =
137+
// is a component
138+
elm.tagName.toLowerCase() in
139+
(docsifyConfig.vueComponents || {}) ||
140+
// has a component(s)
141+
elm.querySelector(vueComponentNames.join(',') || null) ||
142+
// has brackets
143+
(docsifyConfig.vueGlobalOptions &&
144+
/{{2}[^{}]*}{2}/.test(elm.outerHTML)) ||
145+
// has directive
146+
/{\sv-(bind|cloak|else|else-if|for|html|if|is|model|on|once|pre|show|slot|text)=/.test(
147+
elm.outerHTML
148+
);
149+
150+
return isVueMount;
151+
})
152+
.map(elm => {
153+
// Clone global configuration
154+
const vueConfig = Object.assign(
155+
{},
156+
docsifyConfig.vueGlobalOptions || {}
157+
);
158+
159+
// Replace vueGlobalOptions data() return value with shared data object.
160+
// This provides a global store for all Vue instances that receive
161+
// vueGlobalOptions as their configuration.
162+
if (vueGlobalData) {
163+
vueConfig.data = function() {
164+
return vueGlobalData;
165+
};
166+
}
167+
168+
return [elm, vueConfig];
169+
})
137170
);
138171
}
139172

173+
// Mount
140174
for (const [mountElm, vueConfig] of vueMountData) {
141-
const isVueMount =
142-
// Valid tag
143-
mountElm.tagName !== 'SCRIPT' &&
144-
// Matches curly braces or HTML directives
145-
/{{2}[^{}]*}{2}|\sv-(bind|cloak|else|else-if|for|html|if|is|model|on|once|pre|show|slot|text)=/.test(
146-
mountElm.outerHTML
147-
);
175+
const isVueAttr = 'data-isvue';
176+
const isSkipElm =
177+
// Is an invalid tag
178+
mountElm.matches('pre, script') ||
179+
// Is a mounted instance
180+
isMountedVue(mountElm) ||
181+
// Has mounted instance(s)
182+
mountElm.querySelector(`[${isVueAttr}]`);
183+
184+
if (!isSkipElm) {
185+
mountElm.setAttribute(isVueAttr, '');
148186

149-
if (isVueMount && !isMountedVue(mountElm)) {
150187
if (vueVersion === 2) {
151188
new window.Vue(vueConfig).$mount(mountElm);
152189
} else if (vueVersion === 3) {
153-
window.Vue.createApp(vueConfig).mount(mountElm);
190+
const app = window.Vue.createApp(vueConfig);
191+
192+
// Register global vueComponents
193+
vueComponentNames.forEach(name => {
194+
const config = docsifyConfig.vueComponents[name];
195+
196+
app.component(name, config);
197+
});
198+
199+
app.mount(mountElm);
154200
}
155201
}
156202
}
Collapse file

‎test/e2e/vue.test.js‎

Copy file name to clipboardExpand all lines: test/e2e/vue.test.js
+107-84Lines changed: 107 additions & 84 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,18 @@ describe('Vue.js Compatibility', function() {
1010
function getSharedConfig() {
1111
const config = {
1212
config: {
13+
vueComponents: {
14+
'button-counter': {
15+
template: `
16+
<button @click="counter++">{{ counter }}</button>
17+
`,
18+
data: function() {
19+
return {
20+
counter: 0,
21+
};
22+
},
23+
},
24+
},
1325
vueGlobalOptions: {
1426
data: function() {
1527
return {
@@ -31,15 +43,17 @@ describe('Vue.js Compatibility', function() {
3143
},
3244
markdown: {
3345
homepage: stripIndent`
34-
# <span v-for="i in 5">{{ i }}</span>
46+
<div id="vuefor"><span v-for="i in 5">{{ i }}</span></div>
3547
36-
<div id="vueoptions">
48+
<button-counter id="vuecomponent">---</button-counter>
49+
50+
<div id="vueglobaloptions">
3751
<p v-text="msg">---</p>
3852
<button v-on:click="counter += 1">+</button>
3953
<span>{{ counter }}<span>
4054
</div>
4155
42-
<div id="vueglobaloptions">
56+
<div id="vueoptions">
4357
<p v-text="msg">---</p>
4458
<button v-on:click="counter += 1">+</button>
4559
<span>{{ counter }}<span>
@@ -79,105 +93,114 @@ describe('Vue.js Compatibility', function() {
7993

8094
// Tests
8195
// ---------------------------------------------------------------------------
82-
describe('Ignores Vue', function() {
83-
test(`content when Vue is not present`, async () => {
84-
const docsifyInitConfig = getSharedConfig();
85-
86-
await docsifyInit(docsifyInitConfig);
87-
await page.evaluate(() => {
88-
return 'Vue' in window === false;
89-
});
90-
await expect(page).toEqualText('h1', '{{ i }}');
91-
await expect(page).toEqualText('#vueglobaloptions p', '---');
92-
await expect(page).toEqualText('#vueoptions p', '---');
93-
await expect(page).toEqualText('#vuescript p', '---');
94-
});
95-
96-
test(`content when vueOptions and vueGlobalOptions are undefined`, async () => {
97-
const docsifyInitConfig = getSharedConfig();
98-
99-
docsifyInitConfig.config.vueGlobalOptions = undefined;
100-
docsifyInitConfig.config.vueOptions = undefined;
101-
docsifyInitConfig.scriptURLs = vueURLs[0];
102-
103-
await docsifyInit(docsifyInitConfig);
104-
await expect(page).toEqualText('h1', '{{ i }}');
105-
await expect(page).toEqualText('#vueglobaloptions p', '---');
106-
await expect(page).toEqualText('#vueoptions p', '---');
107-
await expect(page).toEqualText('#vuescript p', 'vuescript');
108-
});
109-
110-
test(`content when vueGlobalOptions data is undefined`, async () => {
111-
const docsifyInitConfig = getSharedConfig();
112-
113-
docsifyInitConfig.config.vueGlobalOptions.data = undefined;
114-
docsifyInitConfig.scriptURLs = vueURLs[0];
115-
116-
await docsifyInit(docsifyInitConfig);
117-
await expect(page).toEqualText('h1', '{{ i }}');
118-
await expect(page).toEqualText('#vueoptions p', 'vueoptions');
119-
await expect(page).toEqualText('#vueglobaloptions p', '---');
120-
await expect(page).toEqualText('#vuescript p', 'vuescript');
121-
});
122-
123-
test(`content when vueOptions data is undefined`, async () => {
124-
const docsifyInitConfig = getSharedConfig();
125-
126-
docsifyInitConfig.config.vueOptions['#vueoptions'].data = undefined;
127-
docsifyInitConfig.scriptURLs = vueURLs[0];
128-
129-
await docsifyInit(docsifyInitConfig);
130-
await expect(page).toEqualText('h1', '12345');
131-
await expect(page).toEqualText('#vueoptions p', 'vueglobaloptions');
132-
await expect(page).toEqualText('#vueglobaloptions p', 'vueglobaloptions');
133-
await expect(page).toEqualText('#vuescript p', 'vuescript');
134-
});
135-
136-
test(`<script> when executeScript is false`, async () => {
137-
const docsifyInitConfig = getSharedConfig();
138-
139-
docsifyInitConfig.config.executeScript = false;
140-
docsifyInitConfig.scriptURLs = vueURLs[0];
141-
142-
await docsifyInit(docsifyInitConfig);
143-
await expect(page).toEqualText('#vuescript p', 'vueglobaloptions');
144-
});
145-
});
146-
147-
describe('Renders Vue', function() {
148-
for (const vueURL of vueURLs) {
149-
const vueVersion = Number(vueURL.match(/vue(\d+)/)[1]); // 2|3
96+
for (const vueURL of vueURLs) {
97+
const vueVersion = Number(vueURL.match(/vue(\d+)/)[1]); // 2|3
15098

99+
describe(`Vue v${vueVersion}`, function() {
151100
for (const executeScript of [true, undefined]) {
152-
test(`Vue v${vueVersion}: renders when executeScript is ${executeScript}`, async () => {
153-
const docsifyInitConfig = getSharedConfig(vueVersion);
101+
test(`renders content when executeScript is ${executeScript}`, async () => {
102+
const docsifyInitConfig = getSharedConfig();
154103

155104
docsifyInitConfig.config.executeScript = executeScript;
156105
docsifyInitConfig.scriptURLs = vueURL;
157106

158107
await docsifyInit(docsifyInitConfig);
159108

160-
// Static data
161-
await expect(page).toEqualText('h1', '12345');
162-
await expect(page).toEqualText('#vueoptions p', 'vueoptions');
109+
// Static
110+
await expect(page).toEqualText('#vuefor', '12345');
111+
await expect(page).toEqualText('#vuecomponent', '0');
163112
await expect(page).toEqualText(
164113
'#vueglobaloptions p',
165114
'vueglobaloptions'
166115
);
116+
await expect(page).toEqualText('#vueglobaloptions span', '0');
117+
await expect(page).toEqualText('#vueoptions p', 'vueoptions');
118+
await expect(page).toEqualText('#vueoptions span', '0');
167119
await expect(page).toEqualText('#vuescript p', 'vuescript');
120+
await expect(page).toEqualText('#vuescript span', '0');
168121

169-
// Reactive data
170-
await expect(page).toEqualText('#vueoptions span', '0');
171-
await page.click('#vueoptions button');
172-
await expect(page).toEqualText('#vueoptions span', '1');
173-
await expect(page).toEqualText('#vueglobaloptions span', '0');
122+
// Reactive
123+
await page.click('#vuecomponent');
124+
await expect(page).toEqualText('#vuecomponent', '1');
174125
await page.click('#vueglobaloptions button');
175126
await expect(page).toEqualText('#vueglobaloptions span', '1');
176-
await expect(page).toEqualText('#vuescript span', '0');
127+
await page.click('#vueoptions button');
128+
await expect(page).toEqualText('#vueoptions span', '1');
177129
await page.click('#vuescript button');
178130
await expect(page).toEqualText('#vuescript span', '1');
179131
});
180132
}
181-
}
182-
});
133+
134+
test(`ignores content when Vue is not present`, async () => {
135+
const docsifyInitConfig = getSharedConfig();
136+
137+
await docsifyInit(docsifyInitConfig);
138+
await page.evaluate(() => {
139+
return 'Vue' in window === false;
140+
});
141+
await expect(page).toEqualText('#vuefor', '{{ i }}');
142+
await expect(page).toEqualText('#vuecomponent', '---');
143+
await expect(page).toEqualText('#vueglobaloptions p', '---');
144+
await expect(page).toEqualText('#vueoptions p', '---');
145+
await expect(page).toEqualText('#vuescript p', '---');
146+
});
147+
148+
test(`ignores content when vueComponents, vueOptions, and vueGlobalOptions are undefined`, async () => {
149+
const docsifyInitConfig = getSharedConfig();
150+
151+
docsifyInitConfig.config.vueComponents = undefined;
152+
docsifyInitConfig.config.vueGlobalOptions = undefined;
153+
docsifyInitConfig.config.vueOptions = undefined;
154+
docsifyInitConfig.scriptURLs = vueURL;
155+
156+
await docsifyInit(docsifyInitConfig);
157+
await expect(page).toEqualText('#vuefor', '{{ i }}');
158+
await expect(page).toEqualText('#vuecomponent', '---');
159+
await expect(page).toEqualText('#vueglobaloptions p', '---');
160+
await expect(page).toEqualText('#vueoptions p', '---');
161+
await expect(page).toEqualText('#vuescript p', 'vuescript');
162+
});
163+
164+
test(`ignores content when vueGlobalOptions is undefined`, async () => {
165+
const docsifyInitConfig = getSharedConfig();
166+
167+
docsifyInitConfig.config.vueGlobalOptions = undefined;
168+
docsifyInitConfig.scriptURLs = vueURL;
169+
170+
await docsifyInit(docsifyInitConfig);
171+
await expect(page).toEqualText('#vuefor', '{{ i }}');
172+
await expect(page).toEqualText('#vuecomponent', '0');
173+
await expect(page).toEqualText('#vueglobaloptions p', '---');
174+
await expect(page).toEqualText('#vueoptions p', 'vueoptions');
175+
await expect(page).toEqualText('#vuescript p', 'vuescript');
176+
});
177+
178+
test(`ignores content when vueOptions is undefined`, async () => {
179+
const docsifyInitConfig = getSharedConfig();
180+
181+
docsifyInitConfig.config.vueOptions['#vueoptions'] = undefined;
182+
docsifyInitConfig.scriptURLs = vueURL;
183+
184+
await docsifyInit(docsifyInitConfig);
185+
await expect(page).toEqualText('#vuefor', '12345');
186+
await expect(page).toEqualText('#vuecomponent', '0');
187+
await expect(page).toEqualText(
188+
'#vueglobaloptions p',
189+
'vueglobaloptions'
190+
);
191+
await expect(page).toEqualText('#vueoptions p', 'vueglobaloptions');
192+
await expect(page).toEqualText('#vuescript p', 'vuescript');
193+
});
194+
195+
test(`ignores <script> when executeScript is false`, async () => {
196+
const docsifyInitConfig = getSharedConfig();
197+
198+
docsifyInitConfig.config.executeScript = false;
199+
docsifyInitConfig.scriptURLs = vueURL;
200+
201+
await docsifyInit(docsifyInitConfig);
202+
await expect(page).toEqualText('#vuescript p', 'vueglobaloptions');
203+
});
204+
});
205+
}
183206
});

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.