docsify/server.js

305 lines
11 KiB
JavaScript

import liveServer from 'live-server';
import { qualifyURL } from './packages/docsify-server-renderer/src/utils';
const isSSR = !!process.env.SSR;
const middleware = [];
const port = 3000;
main();
async function main() {
if (isSSR) {
// Using JSDom here because the server relies on a small subset of DOM APIs.
// The URL used here serves no purpose other than to give JSDOM an HTTP
// URL to operate under (it probably can be anything).
initJSDOM('', { url: 'http://127.0.0.1:' + port });
const { Renderer, getServerHTMLTemplate } = await import(
'./packages/docsify-server-renderer/index'
);
const renderer = new Renderer({
// The default template is simple, without any plugins or scripts, just docsify.js.
// template: getServerHTMLTemplate(),
template: /* html */ `
<html lang="en">
<head>
<title>docsify</title>
<meta name="description" content="A magical documentation generator." />
<link rel="icon" href="_media/favicon.ico" />
<meta
name="viewport"
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0"
/>
<meta
name="keywords"
content="doc,docs,documentation,gitbook,creator,generator,github,jekyll,github-pages"
/>
<link rel="stylesheet" href="/themes/vue.css" title="vue" />
<link rel="stylesheet" href="/themes/dark.css" title="dark" disabled />
<link rel="stylesheet" href="/themes/buble.css" title="buble" disabled />
<link rel="stylesheet" href="/themes/pure.css" title="pure" disabled />
<style>
nav.app-nav li ul {
min-width: 100px;
}
#carbonads {
box-shadow: none !important;
width: auto !important;
}
</style>
</head>
<body>
<!-- Order matters here! -->
<!-- FIRST, specify where the $docsify config object will be injected: -->
<!--inject-config-->
<script src="//cdn.jsdelivr.net/npm/docsify-plugin-carbon@1/index.js"></script>
<!-- Currently the config object is injected, and anything dynamic
that can not be 'JSON.stringify'ed (f.e. functions)
should be added in the HTML to the config object. -->
<script>
globalThis.$docsify = {
...globalThis.$docsify,
// TODO: Vue components currently only work when switching to a
// page dynamic after the first render (after initial SSR). On
// initial SSR, the Vue components do not come to life, but
// will once you switch pages and come back.
vueComponents: {
'button-counter': {
template:
'<button @click="count += 1">You clicked me {{ count }} times</button>',
data: function () {
return { count: 0 };
},
},
},
vueGlobalOptions: {
data: function () {
return {
count: 0,
message: 'Hello, World!',
// Fake API response
images: [
{
title: 'Image 1',
url: 'https://picsum.photos/150?random=1',
},
{
title: 'Image 2',
url: 'https://picsum.photos/150?random=2',
},
{
title: 'Image 3',
url: 'https://picsum.photos/150?random=3',
},
],
};
},
computed: {
timeOfDay: function () {
const date = new Date();
const hours = date.getHours();
if (hours < 12) {
return 'morning';
} else if (hours < 18) {
return 'afternoon';
} else {
return 'evening';
}
},
},
methods: {
hello: function () {
alert(this.message);
},
},
},
vueMounts: {
'#counter': {
data: function () {
return { count: 0 };
},
},
},
// TODO Plugins don't work on initial SSR render, only on dynamic page switches.
plugins: [
DocsifyCarbon.create('CEBI6KQE', 'docsifyjsorg'),
function (hook, vm) {
hook.beforeEach(function (markdown) {
if (/githubusercontent\\.com/.test(vm.route.file)) {
url = vm.route.file
.replace('raw.githubusercontent.com', 'github.com')
.replace(/\\/master/, '/blob/master');
} else if (/jsdelivr\\.net/.test(vm.route.file)) {
url = vm.route.file
.replace('cdn.jsdelivr.net/gh', 'github.com')
.replace('@master', '/blob/master');
} else {
url =
'https://github.com/docsifyjs/docsify/blob/develop/docs/' +
vm.route.file;
}
var editHtml = '[:memo: Edit Document](' + url + ')\\n';
return (
editHtml +
markdown +
'\\n\\n----\\n\\n' +
'<a href="https://docsify.js.org" target="_blank" style="color: inherit; font-weight: normal; text-decoration: none;">Powered by docsify</a>'
);
});
},
],
};
</script>
<!-- SECOND, load Docsify and plugins as needed: -->
<script src="/lib/docsify.js"></script>
<!-- FIXME Search plugin causes SSR to break. -->
<!-- <script src="/lib/plugins/search.js"></script> -->
<!--
<script src="/lib/plugins/emoji.js"></script>
<script src="/lib/plugins/front-matter.js"></script>
-->
<script src="//cdn.jsdelivr.net/npm/prismjs/components/prism-bash.min.js"></script>
<script src="//cdn.jsdelivr.net/npm/prismjs/components/prism-markdown.min.js"></script>
<script src="//cdn.jsdelivr.net/npm/prismjs/components/prism-nginx.min.js"></script>
<script src="//cdn.jsdelivr.net/npm/prismjs/components/prism-php.min.js"></script>
<script src="//cdn.jsdelivr.net/npm/vue@2/dist/vue.min.js"></script>
<!-- FINALLY, specify where to inject the rendered content. -->
<!--inject-app-->
</body>
</html>
`,
config: {
name: 'docsify',
// repo: 'docsifyjs/docsify',
alias: {
'.*?/awesome':
'https://raw.githubusercontent.com/docsifyjs/awesome-docsify/master/README.md',
'.*?/changelog':
'https://raw.githubusercontent.com/docsifyjs/docsify/master/CHANGELOG.md',
'/.*/_navbar.md': '/_navbar.md',
'/es/(.*)':
'https://raw.githubusercontent.com/docsifyjs/docs-es/master/$1',
'/de-de/(.*)':
'https://raw.githubusercontent.com/docsifyjs/docs-de/master/$1',
'/ru-ru/(.*)':
'https://raw.githubusercontent.com/docsifyjs/docs-ru/master/$1',
'/zh-cn/(.*)':
'https://cdn.jsdelivr.net/gh/docsifyjs/docs-zh@master/$1',
// In SSR mode, the _sidebar fallback mechanism is not implemented,
// so without this, sub pages will show their specific sidebar
// instead of the top-level sidebar.
//
// TODO SSR needs to have the fallback mechanism too, so it matches
// with the dynamic-site behavior by default, without having to add
// this line.
'/.*/_sidebar.md': '/_sidebar.md',
},
auto2top: true,
// TODO better handling of these cases, perhaps a (sane?) default value.
// basePath: undefined, // breaks: if base path is not set while in SSR mode, code tries to operate on an undefined value and errors.
// basePath: '', // breaks: similar to undefined, server doesn't lookup file correctly.
// If this is used, then after the initial payload is sent to the
// client, the client will then make all following requests to
// docsify.js.org for dynamic markdown rendering.
// basePath: 'https://docsify.js.org/',
basePath: '/docs',
hasSSR: true,
// coverpage: true, FIXME, not working in SSR.
executeScript: true,
loadSidebar: true,
loadNavbar: true,
mergeNavbar: true,
maxLevel: 4,
subMaxLevel: 2,
search: {
noData: {
'/es/': '¡No hay resultados!',
'/de-de/': 'Keine Ergebnisse!',
'/ru-ru/': 'Никаких результатов!',
'/zh-cn/': '没有结果!',
'/': 'No results!',
},
paths: 'auto',
placeholder: {
'/es/': 'Buscar',
'/de-de/': 'Suche',
'/ru-ru/': 'Поиск',
'/zh-cn/': '搜索',
'/': 'Search',
},
pathNamespaces: ['/es', '/de-de', '/ru-ru', '/zh-cn'],
},
},
});
middleware.push(function (req, res, next) {
const url = new URL(qualifyURL(req.url));
// If the hasSSR flag is enabled, then dynamic fetches of markdown from a
// "hydrated" cilent should not be compiled to HTML.
if (url.searchParams.has('hasSSR')) {
console.log('Skipping markdown handling for already-loaded client.');
return next();
}
// Only handle markdown files or folders.
if (/(\.md|\/[^.]*)$/.test(url.pathname)) {
console.log('Render markdown to HTML on the server for file ', req.url);
// This eventually feeds through the getFileName() function in
// src/core/router/history/base.js.
renderer.renderToString(req.url).then(html => res.end(html));
return;
}
// TODO There will surely be edge cases. Add an option to force certain files?
console.log('Skipping markdown handling of file ' + req.url);
return next();
});
}
const params = {
port,
watch: ['lib', 'docs', 'themes'],
middleware,
};
liveServer.start(params);
}
async function initJSDOM(markup, options) {
const { JSDOM } = (await import('jsdom')).default;
const dom = new JSDOM(markup, options);
global.window = dom.window;
global.document = dom.window.document;
global.navigator = dom.window.navigator;
global.location = dom.window.location;
global.XMLHttpRequest = dom.window.XMLHttpRequest;
return dom;
}