Skip to content

Commit

Permalink
update: render Vue templates by default when import vue resources. (#…
Browse files Browse the repository at this point in the history
…2327)

* update: render vue by default.

---------

Co-authored-by: John Hildenbiddle <jhildenbiddle@users.noreply.github.com>
  • Loading branch information
Koooooo-7 and jhildenbiddle committed Dec 22, 2023
1 parent 02e525c commit 25e715b
Show file tree
Hide file tree
Showing 2 changed files with 82 additions and 79 deletions.
126 changes: 65 additions & 61 deletions src/core/render/index.js
Expand Up @@ -90,6 +90,7 @@ export function Render(Base) {

// Handle Vue content not mounted by markdown <script>
if ('Vue' in window) {
const vueGlobalOptions = docsifyConfig.vueGlobalOptions || {};
const vueMountData = [];
const vueComponentNames = Object.keys(
docsifyConfig.vueComponents || {}
Expand All @@ -109,10 +110,10 @@ export function Render(Base) {
// Store global data() return value as shared data object
if (
!this.#vueGlobalData &&
docsifyConfig.vueGlobalOptions &&
typeof docsifyConfig.vueGlobalOptions.data === 'function'
vueGlobalOptions.data &&
typeof vueGlobalOptions.data === 'function'
) {
this.#vueGlobalData = docsifyConfig.vueGlobalOptions.data();
this.#vueGlobalData = vueGlobalOptions.data();
}

// vueMounts
Expand All @@ -125,64 +126,67 @@ export function Render(Base) {
.filter(([elm, vueConfig]) => elm)
);

// Template syntax, vueComponents, vueGlobalOptions
if (docsifyConfig.vueGlobalOptions || vueComponentNames.length) {
const reHasBraces = /{{2}[^{}]*}{2}/;
// Matches Vue full and shorthand syntax as attributes in HTML tags.
//
// Full syntax examples:
// v-foo, v-foo[bar], v-foo-bar, v-foo:bar-baz.prop
//
// Shorthand syntax examples:
// @foo, @foo.bar, @foo.bar.baz, @[foo], :foo, :[foo]
//
// Markup examples:
// <div v-html>{{ html }}</div>
// <div v-text="msg"></div>
// <div v-bind:text-content.prop="text">
// <button v-on:click="doThis"></button>
// <button v-on:click.once="doThis"></button>
// <button v-on:[event]="doThis"></button>
// <button @click.stop.prevent="doThis">
// <a :href="url">
// <a :[key]="url">
const reHasDirective = /<[^>/]+\s([@:]|v-)[\w-:.[\]]+[=>\s]/;

vueMountData.push(
...dom
.findAll('.markdown-section > *')
// Remove duplicates
.filter(elm => !vueMountData.some(([e, c]) => e === elm))
// 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 curly braces
reHasBraces.test(elm.outerHTML) ||
// has content directive
reHasDirective.test(elm.outerHTML);

return isVueMount;
})
.map(elm => {
// Clone global configuration
const vueConfig = {
...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 (this.#vueGlobalData) {
vueConfig.data = () => this.#vueGlobalData;
}

return [elm, vueConfig];
})
);
// Template syntax, vueComponents, vueGlobalOptions ...
const reHasBraces = /{{2}[^{}]*}{2}/;
// Matches Vue full and shorthand syntax as attributes in HTML tags.
//
// Full syntax examples:
// v-foo, v-foo[bar], v-foo-bar, v-foo:bar-baz.prop
//
// Shorthand syntax examples:
// @foo, @foo.bar, @foo.bar.baz, @[foo], :foo, :[foo]
//
// Markup examples:
// <div v-html>{{ html }}</div>
// <div v-text="msg"></div>
// <div v-bind:text-content.prop="text">
// <button v-on:click="doThis"></button>
// <button v-on:click.once="doThis"></button>
// <button v-on:[event]="doThis"></button>
// <button @click.stop.prevent="doThis">
// <a :href="url">
// <a :[key]="url">
const reHasDirective = /<[^>/]+\s([@:]|v-)[\w-:.[\]]+[=>\s]/;

vueMountData.push(
...dom
.findAll('.markdown-section > *')
// Remove duplicates
.filter(elm => !vueMountData.some(([e, c]) => e === elm))
// 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 curly braces
reHasBraces.test(elm.outerHTML) ||
// has content directive
reHasDirective.test(elm.outerHTML);

return isVueMount;
})
.map(elm => {
// Clone global configuration
const vueConfig = {
...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 (this.#vueGlobalData) {
vueConfig.data = () => this.#vueGlobalData;
}

return [elm, vueConfig];
})
);

// Not found mounts but import Vue resource
if (vueMountData.length === 0) {
return;
}

// Mount
Expand Down
35 changes: 17 additions & 18 deletions test/e2e/vue.test.js
Expand Up @@ -96,6 +96,23 @@ test.describe('Vue.js Compatibility', () => {
const vueVersion = Number(vueURL.match(/vue(\d+)/)[1]); // 2|3

test.describe(`Vue v${vueVersion}`, () => {
test(`Parse templates and render content when import Vue v${vueVersion} resources`, async ({
page,
}) => {
const docsifyInitConfig = {
config: {},
markdown: {
homepage: stripIndent`
<div id="vuefor"><span v-for="i in 5">{{ i }}</span></div>
`,
},
};

docsifyInitConfig.scriptURLs = vueURL;

await docsifyInit(docsifyInitConfig);
await expect(page.locator('#vuefor')).toHaveText('12345');
});
for (const executeScript of [true, undefined]) {
test(`renders content when executeScript is ${executeScript}`, async ({
page,
Expand Down Expand Up @@ -147,24 +164,6 @@ test.describe('Vue.js Compatibility', () => {
await expect(page.locator('#vuescript p')).toHaveText('---');
});

test(`ignores content when vueComponents, vueMounts, and vueGlobalOptions are undefined`, async ({
page,
}) => {
const docsifyInitConfig = getSharedConfig();

docsifyInitConfig.config.vueComponents = undefined;
docsifyInitConfig.config.vueGlobalOptions = undefined;
docsifyInitConfig.config.vueMounts = undefined;
docsifyInitConfig.scriptURLs = vueURL;

await docsifyInit(docsifyInitConfig);
await expect(page.locator('#vuefor')).toHaveText('{{ i }}');
await expect(page.locator('#vuecomponent')).toHaveText('---');
await expect(page.locator('#vueglobaloptions p')).toHaveText('---');
await expect(page.locator('#vuemounts p')).toHaveText('---');
await expect(page.locator('#vuescript p')).toHaveText('vuescript');
});

test(`ignores content when vueGlobalOptions is undefined`, async ({
page,
}) => {
Expand Down

1 comment on commit 25e715b

@vercel
Copy link

@vercel vercel bot commented on 25e715b Dec 22, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.