mirror of
https://gitee.com/docsifyjs/docsify.git
synced 2024-12-02 03:59:19 +08:00
Add vueComponents support
This commit is contained in:
parent
5fa79ebcdb
commit
a2386e5a59
@ -66,15 +66,6 @@ function renderMain(html) {
|
||||
.findAll('.markdown-section > *')
|
||||
.filter(elm => isMountedVue(elm));
|
||||
|
||||
// Store global data() return value as shared data object
|
||||
if (
|
||||
!vueGlobalData &&
|
||||
docsifyConfig.vueGlobalOptions &&
|
||||
typeof docsifyConfig.vueGlobalOptions.data === 'function'
|
||||
) {
|
||||
vueGlobalData = docsifyConfig.vueGlobalOptions.data();
|
||||
}
|
||||
|
||||
// Destroy/unmount existing Vue instances
|
||||
for (const mountedElm of mountedElms) {
|
||||
if (vueVersion === 2) {
|
||||
@ -101,6 +92,27 @@ function renderMain(html) {
|
||||
// Handle Vue content not mounted by markdown <script>
|
||||
if ('Vue' in window) {
|
||||
const vueMountData = [];
|
||||
const vueComponentNames = Object.keys(docsifyConfig.vueComponents || {});
|
||||
|
||||
// Register global vueComponents
|
||||
if (vueVersion === 2 && vueComponentNames.length) {
|
||||
vueComponentNames.forEach(name => {
|
||||
const isNotRegistered = !window.Vue.options.components[name];
|
||||
|
||||
if (isNotRegistered) {
|
||||
window.Vue.component(name, docsifyConfig.vueComponents[name]);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Store global data() return value as shared data object
|
||||
if (
|
||||
!vueGlobalData &&
|
||||
docsifyConfig.vueGlobalOptions &&
|
||||
typeof docsifyConfig.vueGlobalOptions.data === 'function'
|
||||
) {
|
||||
vueGlobalData = docsifyConfig.vueGlobalOptions.data();
|
||||
}
|
||||
|
||||
// vueOptions
|
||||
vueMountData.push(
|
||||
@ -109,48 +121,82 @@ function renderMain(html) {
|
||||
dom.find(markdownElm, cssSelector),
|
||||
vueConfig,
|
||||
])
|
||||
.filter(
|
||||
([elm, vueConfig]) => elm && Object.keys(vueConfig || {}).length
|
||||
)
|
||||
.filter(([elm, vueConfig]) => elm)
|
||||
);
|
||||
|
||||
// vueGlobalOptions
|
||||
if (Object.keys(docsifyConfig.vueGlobalOptions || {}).length) {
|
||||
if (docsifyConfig.vueGlobalOptions || vueComponentNames.length) {
|
||||
vueMountData.push(
|
||||
...dom
|
||||
.findAll('.markdown-section > *')
|
||||
// Remove duplicates
|
||||
.filter(elm => !vueMountData.some(([e, c]) => e === elm))
|
||||
.map(elm => [
|
||||
elm,
|
||||
!vueGlobalData
|
||||
? docsifyConfig.vueGlobalOptions
|
||||
: // Replace vueGlobalOptions data() return value with shared data
|
||||
// object. This provides a global store for all Vue instances
|
||||
// that receive vueGlobalOptions as their configuration.
|
||||
Object.assign({}, docsifyConfig.vueGlobalOptions, {
|
||||
data() {
|
||||
return vueGlobalData;
|
||||
},
|
||||
}),
|
||||
])
|
||||
// Detect Vue content
|
||||
.filter(elm => {
|
||||
const isVueMount =
|
||||
// is a component
|
||||
elm.tagName.toLowerCase() in
|
||||
(docsifyConfig.vueComponents || {}) ||
|
||||
// has a component(s)
|
||||
elm.querySelector(vueComponentNames.join(',') || null) ||
|
||||
// has brackets
|
||||
(docsifyConfig.vueGlobalOptions &&
|
||||
/{{2}[^{}]*}{2}/.test(elm.outerHTML)) ||
|
||||
// has directive
|
||||
/{\sv-(bind|cloak|else|else-if|for|html|if|is|model|on|once|pre|show|slot|text)=/.test(
|
||||
elm.outerHTML
|
||||
);
|
||||
|
||||
return isVueMount;
|
||||
})
|
||||
.map(elm => {
|
||||
// Clone global configuration
|
||||
const vueConfig = Object.assign(
|
||||
{},
|
||||
docsifyConfig.vueGlobalOptions || {}
|
||||
);
|
||||
|
||||
// Replace vueGlobalOptions data() return value with shared data object.
|
||||
// This provides a global store for all Vue instances that receive
|
||||
// vueGlobalOptions as their configuration.
|
||||
if (vueGlobalData) {
|
||||
vueConfig.data = function() {
|
||||
return vueGlobalData;
|
||||
};
|
||||
}
|
||||
|
||||
return [elm, vueConfig];
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
// Mount
|
||||
for (const [mountElm, vueConfig] of vueMountData) {
|
||||
const isVueMount =
|
||||
// Valid tag
|
||||
mountElm.tagName !== 'SCRIPT' &&
|
||||
// Matches curly braces or HTML directives
|
||||
/{{2}[^{}]*}{2}|\sv-(bind|cloak|else|else-if|for|html|if|is|model|on|once|pre|show|slot|text)=/.test(
|
||||
mountElm.outerHTML
|
||||
);
|
||||
const isVueAttr = 'data-isvue';
|
||||
const isSkipElm =
|
||||
// Is an invalid tag
|
||||
mountElm.matches('pre, script') ||
|
||||
// Is a mounted instance
|
||||
isMountedVue(mountElm) ||
|
||||
// Has mounted instance(s)
|
||||
mountElm.querySelector(`[${isVueAttr}]`);
|
||||
|
||||
if (!isSkipElm) {
|
||||
mountElm.setAttribute(isVueAttr, '');
|
||||
|
||||
if (isVueMount && !isMountedVue(mountElm)) {
|
||||
if (vueVersion === 2) {
|
||||
new window.Vue(vueConfig).$mount(mountElm);
|
||||
} else if (vueVersion === 3) {
|
||||
window.Vue.createApp(vueConfig).mount(mountElm);
|
||||
const app = window.Vue.createApp(vueConfig);
|
||||
|
||||
// Register global vueComponents
|
||||
vueComponentNames.forEach(name => {
|
||||
const config = docsifyConfig.vueComponents[name];
|
||||
|
||||
app.component(name, config);
|
||||
});
|
||||
|
||||
app.mount(mountElm);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -10,6 +10,18 @@ describe('Vue.js Compatibility', function() {
|
||||
function getSharedConfig() {
|
||||
const config = {
|
||||
config: {
|
||||
vueComponents: {
|
||||
'button-counter': {
|
||||
template: `
|
||||
<button @click="counter++">{{ counter }}</button>
|
||||
`,
|
||||
data: function() {
|
||||
return {
|
||||
counter: 0,
|
||||
};
|
||||
},
|
||||
},
|
||||
},
|
||||
vueGlobalOptions: {
|
||||
data: function() {
|
||||
return {
|
||||
@ -31,15 +43,17 @@ describe('Vue.js Compatibility', function() {
|
||||
},
|
||||
markdown: {
|
||||
homepage: stripIndent`
|
||||
# <span v-for="i in 5">{{ i }}</span>
|
||||
<div id="vuefor"><span v-for="i in 5">{{ i }}</span></div>
|
||||
|
||||
<div id="vueoptions">
|
||||
<button-counter id="vuecomponent">---</button-counter>
|
||||
|
||||
<div id="vueglobaloptions">
|
||||
<p v-text="msg">---</p>
|
||||
<button v-on:click="counter += 1">+</button>
|
||||
<span>{{ counter }}<span>
|
||||
</div>
|
||||
|
||||
<div id="vueglobaloptions">
|
||||
<div id="vueoptions">
|
||||
<p v-text="msg">---</p>
|
||||
<button v-on:click="counter += 1">+</button>
|
||||
<span>{{ counter }}<span>
|
||||
@ -79,105 +93,114 @@ describe('Vue.js Compatibility', function() {
|
||||
|
||||
// Tests
|
||||
// ---------------------------------------------------------------------------
|
||||
describe('Ignores Vue', function() {
|
||||
test(`content when Vue is not present`, async () => {
|
||||
const docsifyInitConfig = getSharedConfig();
|
||||
|
||||
await docsifyInit(docsifyInitConfig);
|
||||
await page.evaluate(() => {
|
||||
return 'Vue' in window === false;
|
||||
});
|
||||
await expect(page).toEqualText('h1', '{{ i }}');
|
||||
await expect(page).toEqualText('#vueglobaloptions p', '---');
|
||||
await expect(page).toEqualText('#vueoptions p', '---');
|
||||
await expect(page).toEqualText('#vuescript p', '---');
|
||||
});
|
||||
|
||||
test(`content when vueOptions and vueGlobalOptions are undefined`, async () => {
|
||||
const docsifyInitConfig = getSharedConfig();
|
||||
|
||||
docsifyInitConfig.config.vueGlobalOptions = undefined;
|
||||
docsifyInitConfig.config.vueOptions = undefined;
|
||||
docsifyInitConfig.scriptURLs = vueURLs[0];
|
||||
|
||||
await docsifyInit(docsifyInitConfig);
|
||||
await expect(page).toEqualText('h1', '{{ i }}');
|
||||
await expect(page).toEqualText('#vueglobaloptions p', '---');
|
||||
await expect(page).toEqualText('#vueoptions p', '---');
|
||||
await expect(page).toEqualText('#vuescript p', 'vuescript');
|
||||
});
|
||||
|
||||
test(`content when vueGlobalOptions data is undefined`, async () => {
|
||||
const docsifyInitConfig = getSharedConfig();
|
||||
|
||||
docsifyInitConfig.config.vueGlobalOptions.data = undefined;
|
||||
docsifyInitConfig.scriptURLs = vueURLs[0];
|
||||
|
||||
await docsifyInit(docsifyInitConfig);
|
||||
await expect(page).toEqualText('h1', '{{ i }}');
|
||||
await expect(page).toEqualText('#vueoptions p', 'vueoptions');
|
||||
await expect(page).toEqualText('#vueglobaloptions p', '---');
|
||||
await expect(page).toEqualText('#vuescript p', 'vuescript');
|
||||
});
|
||||
|
||||
test(`content when vueOptions data is undefined`, async () => {
|
||||
const docsifyInitConfig = getSharedConfig();
|
||||
|
||||
docsifyInitConfig.config.vueOptions['#vueoptions'].data = undefined;
|
||||
docsifyInitConfig.scriptURLs = vueURLs[0];
|
||||
|
||||
await docsifyInit(docsifyInitConfig);
|
||||
await expect(page).toEqualText('h1', '12345');
|
||||
await expect(page).toEqualText('#vueoptions p', 'vueglobaloptions');
|
||||
await expect(page).toEqualText('#vueglobaloptions p', 'vueglobaloptions');
|
||||
await expect(page).toEqualText('#vuescript p', 'vuescript');
|
||||
});
|
||||
|
||||
test(`<script> when executeScript is false`, async () => {
|
||||
const docsifyInitConfig = getSharedConfig();
|
||||
|
||||
docsifyInitConfig.config.executeScript = false;
|
||||
docsifyInitConfig.scriptURLs = vueURLs[0];
|
||||
|
||||
await docsifyInit(docsifyInitConfig);
|
||||
await expect(page).toEqualText('#vuescript p', 'vueglobaloptions');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Renders Vue', function() {
|
||||
for (const vueURL of vueURLs) {
|
||||
const vueVersion = Number(vueURL.match(/vue(\d+)/)[1]); // 2|3
|
||||
for (const vueURL of vueURLs) {
|
||||
const vueVersion = Number(vueURL.match(/vue(\d+)/)[1]); // 2|3
|
||||
|
||||
describe(`Vue v${vueVersion}`, function() {
|
||||
for (const executeScript of [true, undefined]) {
|
||||
test(`Vue v${vueVersion}: renders when executeScript is ${executeScript}`, async () => {
|
||||
const docsifyInitConfig = getSharedConfig(vueVersion);
|
||||
test(`renders content when executeScript is ${executeScript}`, async () => {
|
||||
const docsifyInitConfig = getSharedConfig();
|
||||
|
||||
docsifyInitConfig.config.executeScript = executeScript;
|
||||
docsifyInitConfig.scriptURLs = vueURL;
|
||||
|
||||
await docsifyInit(docsifyInitConfig);
|
||||
|
||||
// Static data
|
||||
await expect(page).toEqualText('h1', '12345');
|
||||
await expect(page).toEqualText('#vueoptions p', 'vueoptions');
|
||||
// Static
|
||||
await expect(page).toEqualText('#vuefor', '12345');
|
||||
await expect(page).toEqualText('#vuecomponent', '0');
|
||||
await expect(page).toEqualText(
|
||||
'#vueglobaloptions p',
|
||||
'vueglobaloptions'
|
||||
);
|
||||
await expect(page).toEqualText('#vuescript p', 'vuescript');
|
||||
|
||||
// Reactive data
|
||||
await expect(page).toEqualText('#vueoptions span', '0');
|
||||
await page.click('#vueoptions button');
|
||||
await expect(page).toEqualText('#vueoptions span', '1');
|
||||
await expect(page).toEqualText('#vueglobaloptions span', '0');
|
||||
await expect(page).toEqualText('#vueoptions p', 'vueoptions');
|
||||
await expect(page).toEqualText('#vueoptions span', '0');
|
||||
await expect(page).toEqualText('#vuescript p', 'vuescript');
|
||||
await expect(page).toEqualText('#vuescript span', '0');
|
||||
|
||||
// Reactive
|
||||
await page.click('#vuecomponent');
|
||||
await expect(page).toEqualText('#vuecomponent', '1');
|
||||
await page.click('#vueglobaloptions button');
|
||||
await expect(page).toEqualText('#vueglobaloptions span', '1');
|
||||
await expect(page).toEqualText('#vuescript span', '0');
|
||||
await page.click('#vueoptions button');
|
||||
await expect(page).toEqualText('#vueoptions span', '1');
|
||||
await page.click('#vuescript button');
|
||||
await expect(page).toEqualText('#vuescript span', '1');
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
test(`ignores content when Vue is not present`, async () => {
|
||||
const docsifyInitConfig = getSharedConfig();
|
||||
|
||||
await docsifyInit(docsifyInitConfig);
|
||||
await page.evaluate(() => {
|
||||
return 'Vue' in window === false;
|
||||
});
|
||||
await expect(page).toEqualText('#vuefor', '{{ i }}');
|
||||
await expect(page).toEqualText('#vuecomponent', '---');
|
||||
await expect(page).toEqualText('#vueglobaloptions p', '---');
|
||||
await expect(page).toEqualText('#vueoptions p', '---');
|
||||
await expect(page).toEqualText('#vuescript p', '---');
|
||||
});
|
||||
|
||||
test(`ignores content when vueComponents, vueOptions, and vueGlobalOptions are undefined`, async () => {
|
||||
const docsifyInitConfig = getSharedConfig();
|
||||
|
||||
docsifyInitConfig.config.vueComponents = undefined;
|
||||
docsifyInitConfig.config.vueGlobalOptions = undefined;
|
||||
docsifyInitConfig.config.vueOptions = undefined;
|
||||
docsifyInitConfig.scriptURLs = vueURL;
|
||||
|
||||
await docsifyInit(docsifyInitConfig);
|
||||
await expect(page).toEqualText('#vuefor', '{{ i }}');
|
||||
await expect(page).toEqualText('#vuecomponent', '---');
|
||||
await expect(page).toEqualText('#vueglobaloptions p', '---');
|
||||
await expect(page).toEqualText('#vueoptions p', '---');
|
||||
await expect(page).toEqualText('#vuescript p', 'vuescript');
|
||||
});
|
||||
|
||||
test(`ignores content when vueGlobalOptions is undefined`, async () => {
|
||||
const docsifyInitConfig = getSharedConfig();
|
||||
|
||||
docsifyInitConfig.config.vueGlobalOptions = undefined;
|
||||
docsifyInitConfig.scriptURLs = vueURL;
|
||||
|
||||
await docsifyInit(docsifyInitConfig);
|
||||
await expect(page).toEqualText('#vuefor', '{{ i }}');
|
||||
await expect(page).toEqualText('#vuecomponent', '0');
|
||||
await expect(page).toEqualText('#vueglobaloptions p', '---');
|
||||
await expect(page).toEqualText('#vueoptions p', 'vueoptions');
|
||||
await expect(page).toEqualText('#vuescript p', 'vuescript');
|
||||
});
|
||||
|
||||
test(`ignores content when vueOptions is undefined`, async () => {
|
||||
const docsifyInitConfig = getSharedConfig();
|
||||
|
||||
docsifyInitConfig.config.vueOptions['#vueoptions'] = undefined;
|
||||
docsifyInitConfig.scriptURLs = vueURL;
|
||||
|
||||
await docsifyInit(docsifyInitConfig);
|
||||
await expect(page).toEqualText('#vuefor', '12345');
|
||||
await expect(page).toEqualText('#vuecomponent', '0');
|
||||
await expect(page).toEqualText(
|
||||
'#vueglobaloptions p',
|
||||
'vueglobaloptions'
|
||||
);
|
||||
await expect(page).toEqualText('#vueoptions p', 'vueglobaloptions');
|
||||
await expect(page).toEqualText('#vuescript p', 'vuescript');
|
||||
});
|
||||
|
||||
test(`ignores <script> when executeScript is false`, async () => {
|
||||
const docsifyInitConfig = getSharedConfig();
|
||||
|
||||
docsifyInitConfig.config.executeScript = false;
|
||||
docsifyInitConfig.scriptURLs = vueURL;
|
||||
|
||||
await docsifyInit(docsifyInitConfig);
|
||||
await expect(page).toEqualText('#vuescript p', 'vueglobaloptions');
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
|
Loading…
Reference in New Issue
Block a user