Merge pull request #1189 from docsifyjs/allow-config-function

Allow configs to be functions
This commit is contained in:
Joe Pea 2020-06-12 22:23:35 -07:00 committed by GitHub
commit da9af18c4e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 1177 additions and 1029 deletions

11
.editorconfig Normal file
View File

@ -0,0 +1,11 @@
# http://EditorConfig.org
root = true
[*]
charset = utf-8
indent_style = space
indent_size = 2
end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true

View File

@ -9,8 +9,16 @@ const version = process.env.VERSION || require('../package.json').version
const chokidar = require('chokidar')
const path = require('path')
const build = function (opts) {
rollup
/**
* @param {{
* input: string,
* output?: string,
* globalName?: string,
* plugins?: Array<import('rollup').Plugin>
* }} opts
*/
async function build(opts) {
await rollup
.rollup({
input: opts.input,
plugins: (opts.plugins || []).concat([
@ -27,31 +35,35 @@ const build = function (opts) {
var dest = 'lib/' + (opts.output || opts.input)
console.log(dest)
bundle.write({
return bundle.write({
format: 'iife',
output: opts.globalName ? {name: opts.globalName} : {},
file: dest,
strict: false
})
})
.catch(function (err) {
console.error(err)
})
}
const buildCore = function () {
build({
async function buildCore() {
const promises = []
promises.push(build({
input: 'src/core/index.js',
output: 'docsify.js'
})
output: 'docsify.js',
}))
if (isProd) {
build({
promises.push(build({
input: 'src/core/index.js',
output: 'docsify.min.js',
plugins: [uglify()]
})
}))
}
await Promise.all(promises)
}
const buildAllPlugin = function () {
async function buildAllPlugin() {
var plugins = [
{name: 'search', input: 'search/index.js'},
{name: 'ga', input: 'ga.js'},
@ -64,8 +76,8 @@ const buildAllPlugin = function () {
{name: 'gitalk', input: 'gitalk.js'}
]
plugins.forEach(item => {
build({
const promises = plugins.map(item => {
return build({
input: 'src/plugins/' + item.input,
output: 'plugins/' + item.name + '.js'
})
@ -73,47 +85,59 @@ const buildAllPlugin = function () {
if (isProd) {
plugins.forEach(item => {
build({
promises.push(build({
input: 'src/plugins/' + item.input,
output: 'plugins/' + item.name + '.min.js',
plugins: [uglify()]
})
}))
})
}
await Promise.all(promises)
}
async function main() {
if (!isProd) {
chokidar
.watch(['src/core', 'src/plugins'], {
atomic: true,
awaitWriteFinish: {
stabilityThreshold: 1000,
pollInterval: 100
}
})
.on('change', p => {
console.log('[watch] ', p)
const dirs = p.split(path.sep)
if (dirs[1] === 'core') {
buildCore()
} else if (dirs[2]) {
const name = path.basename(dirs[2], '.js')
const input = `src/plugins/${name}${
/\.js/.test(dirs[2]) ? '' : '/index'
}.js`
build({
input,
output: 'plugins/' + name + '.js'
})
}
})
.on('ready', () => {
console.log('[start]')
buildCore()
buildAllPlugin()
})
} else {
await Promise.all([
buildCore(),
buildAllPlugin()
])
}
}
if (!isProd) {
chokidar
.watch(['src/core', 'src/plugins'], {
atomic: true,
awaitWriteFinish: {
stabilityThreshold: 1000,
pollInterval: 100
}
})
.on('change', p => {
console.log('[watch] ', p)
const dirs = p.split(path.sep)
if (dirs[1] === 'core') {
buildCore()
} else if (dirs[2]) {
const name = path.basename(dirs[2], '.js')
const input = `src/plugins/${name}${
/\.js/.test(dirs[2]) ? '' : '/index'
}.js`
main().catch((e) => {
console.error(e)
process.exit(1)
})
build({
input,
output: 'plugins/' + name + '.js'
})
}
})
.on('ready', () => {
console.log('[start]')
buildCore()
buildAllPlugin()
})
} else {
buildCore()
buildAllPlugin()
}

View File

@ -5,8 +5,8 @@ const {spawn} = require('child_process')
const args = process.argv.slice(2)
fs.readdir(path.join(__dirname, '../src/themes'), (err, files) => {
if (err) {
console.log('err', err)
return
console.error('err', err)
process.exit(1)
}
files.map(async (file) => {
if (/\.styl/g.test(file)) {
@ -31,11 +31,17 @@ fs.readdir(path.join(__dirname, '../src/themes'), (err, files) => {
});
stylusCMD.on('close', (code) => {
console.log(`[Stylus Build ] child process exited with code ${code}`);
const message = `[Stylus Build ] child process exited with code ${code}`
if (code !== 0) {
console.error(message);
process.exit(code)
}
console.log(message);
});
} else {
return
}
})
})
})

View File

@ -8,5 +8,8 @@ files.forEach(file => {
file = path.resolve('lib/themes', file)
cssnano(fs.readFileSync(file)).then(result => {
fs.writeFileSync(file, result.css)
}).catch(e => {
console.error(e)
process.exit(1)
})
})

View File

@ -24,11 +24,12 @@ rollup
var dest = 'packages/docsify-server-renderer/build.js'
console.log(dest)
bundle.write({
return bundle.write({
format: 'cjs',
file: dest
})
})
.catch(function (err) {
console.error(err)
process.exit(1)
})

View File

@ -1,6 +1,6 @@
# Configuration
You can configure the `window.$docsify`.
You can configure Docsify by defining `window.$docsify` as an object:
```html
<script>
@ -12,6 +12,24 @@ You can configure the `window.$docsify`.
</script>
```
The config can also be defined as a function, in which case the first arg is the Docsify `vm` instance. The function should return a config object. This can be useful for referencing `vm` in places like the markdown configuration:
```html
<script>
window.$docsify = function(vm) {
return {
markdown: {
renderer: {
code(code, lang) {
// ... use `vm` ...
},
},
},
};
};
</script>
```
## el
- Type: `String`

1775
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -81,7 +81,7 @@
"eslint-plugin-prettier": "^3.1.2",
"esm": "^3.1.4",
"husky": "^3.1.0",
"jsdom": "^15.1.1",
"jsdom": "^16.2.2",
"lerna": "^3.17.0",
"lint-staged": "^10.1.2",
"live-server": "^1.2.1",
@ -97,6 +97,7 @@
"rollup-plugin-node-resolve": "^5.2.0",
"rollup-plugin-replace": "^2.2.0",
"rollup-plugin-uglify": "^6.0.4",
"serve-handler": "^6.1.2",
"start-server-and-test": "^1.10.6",
"stylus": "^0.54.5"
},

23
src/core/Docsify.js Normal file
View File

@ -0,0 +1,23 @@
import { initMixin } from './init';
import { routerMixin } from './router';
import { renderMixin } from './render';
import { fetchMixin } from './fetch';
import { eventMixin } from './event';
import initGlobalAPI from './global-api';
export function Docsify() {
this._init();
}
const proto = Docsify.prototype;
initMixin(proto);
routerMixin(proto);
renderMixin(proto);
fetchMixin(proto);
eventMixin(proto);
/**
* Global API
*/
initGlobalAPI();

View File

@ -2,7 +2,7 @@ import { merge, hyphenate, isPrimitive, hasOwn } from './util/core';
const currentScript = document.currentScript;
export default function() {
export default function(vm) {
const config = merge(
{
el: '#app',
@ -36,7 +36,9 @@ export default function() {
relativePath: false,
topMargin: 0,
},
window.$docsify
typeof window.$docsify === 'function'
? window.$docsify(vm)
: window.$docsify
);
const script =

View File

@ -6,6 +6,10 @@ import { Compiler } from './render/compiler';
import { slugify } from './render/slugify';
import { get } from './fetch/ajax';
// TODO This is deprecated, kept for backwards compatibility. Remove in next
// major release. We'll tell people to get everything from the DOCSIFY global
// when using the global build, but we'll highly recommend for them to import
// from the ESM build (f.e. lib/docsify.esm.js and lib/docsify.min.esm.js).
export default function() {
window.Docsify = {
util,

View File

@ -1,45 +1,8 @@
import { initMixin } from './init';
import { routerMixin } from './router';
import { renderMixin } from './render';
import { fetchMixin } from './fetch';
import { eventMixin } from './event';
import initGlobalAPI from './global-api';
/**
* Fork https://github.com/bendrucker/document-ready/blob/master/index.js
* @param {Function} callback The callbacack to be called when the page is loaded
* @returns {Number|void} If the page is already laoded returns the result of the setTimeout callback,
* otherwise it only attaches the callback to the DOMContentLoaded event
*/
function ready(callback) {
const state = document.readyState;
if (state === 'complete' || state === 'interactive') {
return setTimeout(callback, 0);
}
document.addEventListener('DOMContentLoaded', callback);
}
function Docsify() {
this._init();
}
const proto = Docsify.prototype;
initMixin(proto);
routerMixin(proto);
renderMixin(proto);
fetchMixin(proto);
eventMixin(proto);
/**
* Global API
*/
initGlobalAPI();
import { documentReady } from './util/dom';
import { Docsify } from './Docsify';
/**
* Run Docsify
*/
// eslint-disable-next-line no-unused-vars
ready(_ => new Docsify());
documentReady(_ => new Docsify());

View File

@ -9,7 +9,7 @@ import { initLifecycle, callHook } from './lifecycle';
export function initMixin(proto) {
proto._init = function() {
const vm = this;
vm.config = config();
vm.config = config(vm);
initLifecycle(vm); // Init hooks
initPlugin(vm); // Install plugins

View File

@ -101,3 +101,19 @@ export function toggleClass(el, type, val) {
export function style(content) {
appendTo(head, create('style', content));
}
/**
* Fork https://github.com/bendrucker/document-ready/blob/master/index.js
* @param {Function} callback The callbacack to be called when the page is loaded
* @returns {Number|void} If the page is already laoded returns the result of the setTimeout callback,
* otherwise it only attaches the callback to the DOMContentLoaded event
*/
export function documentReady(callback, doc = document) {
const state = doc.readyState;
if (state === 'complete' || state === 'interactive') {
return setTimeout(callback, 0);
}
doc.addEventListener('DOMContentLoaded', callback);
}

View File

@ -19,6 +19,21 @@ function ready(callback) {
document.addEventListener('DOMContentLoaded', callback);
}
module.exports.initJSDOM = initJSDOM;
/** @param {string} markup - The HTML document to initialize JSDOM with. */
function initJSDOM(markup, options = {}) {
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;
}
module.exports.init = function(
fixture = 'default',
config = {},
@ -39,15 +54,9 @@ module.exports.init = function(
const rootPath = path.join(__dirname, 'fixtures', fixture);
const dom = new JSDOM(markup);
const dom = initJSDOM(markup);
dom.reconfigure({ url: 'file:///' + rootPath });
global.window = dom.window;
global.document = dom.window.document;
global.navigator = dom.window.navigator;
global.location = dom.window.location;
global.XMLHttpRequest = dom.window.XMLHttpRequest;
// Mimic src/core/index.js but for Node.js
function Docsify() {
this._init();

134
test/unit/docsify.test.js Normal file
View File

@ -0,0 +1,134 @@
/* global before after */
/* eslint-disable no-global-assign */
require = require('esm')(module /* , options */);
const http = require('http');
const handler = require('serve-handler');
const { expect } = require('chai');
const { initJSDOM } = require('../_helper');
const port = 9753;
const docsifySite = 'http://127.0.0.1:' + port;
const markup = /* html */ `<!DOCTYPE html>
<html>
<head></head>
<body>
<div id="app"></div>
<script src="/lib/docsify.js"></script>
</body>
</html>
`;
/** @type {ReturnType<typeof http.createServer>} */
let server;
describe('Docsify public API', () => {
before(async () => {
server = http.createServer((request, response) => {
// You pass two more arguments for config and middleware
// More details here: https://github.com/zeit/serve-handler#options
return handler(request, response);
});
await new Promise(r => server.listen(port, r));
});
after(async () => {
server.close(err => {
if (err) {
console.error(err); // eslint-disable-line
process.exit(1);
}
// eslint-disable-next-line
console.log('Server closed.');
});
});
it('global APIs are available', async () => {
// const DOM = new (require('jsdom').JSDOM)(markup, {
const DOM = initJSDOM(markup, {
url: docsifySite,
runScripts: 'dangerously',
resources: 'usable',
});
const { documentReady } = require('../../src/core/util/dom');
await new Promise(resolve => documentReady(resolve, DOM.window.document));
// If the script was built successfully for production, then it should load
// and the following APIs should be available:
expect(typeof DOM.window.Docsify).to.equal('object');
expect(typeof DOM.window.Docsify.util).to.equal('object');
expect(typeof DOM.window.Docsify.dom).to.equal('object');
expect(typeof DOM.window.Docsify.get).to.equal('function');
expect(typeof DOM.window.Docsify.slugify).to.equal('function');
expect(typeof DOM.window.Docsify.version).to.equal('string');
expect(typeof DOM.window.DocsifyCompiler).to.equal('function');
expect(typeof DOM.window.marked).to.equal('function');
expect(typeof DOM.window.Prism).to.equal('object');
});
describe('Docsify config function', function() {
it('allows $docsify to be a function', async function() {
initJSDOM(markup, { url: docsifySite });
window.configFunctionCalled = false;
window.$docsify = function(vm) {
// Check public API (that which is available at this point)
expect(vm).to.be.an.instanceof(Object);
expect(vm.constructor.name).to.equal('Docsify');
expect(vm.$fetch).to.be.an.instanceof(Function);
expect(vm.$resetEvents).to.be.an.instanceof(Function);
expect(vm.route).to.be.an.instanceof(Object);
window.configFunctionCalled = true;
return {};
};
const { documentReady } = require('../../src/core/util/dom');
const { Docsify } = require('../../src/core/Docsify');
await new Promise(resolve => documentReady(resolve));
new Docsify(); // eslint-disable-line
expect(window.configFunctionCalled).to.equal(true);
});
it('provides the hooks and vm API to plugins', async function() {
initJSDOM(markup, { url: docsifySite });
window.pluginFunctionCalled = false;
window.$docsify = function(vm) {
const vm1 = vm;
return {
plugins: [
function(hook, vm2) {
expect(vm1).to.equal(vm2);
expect(hook.init).to.be.an.instanceof(Function);
expect(hook.beforeEach).to.be.an.instanceof(Function);
expect(hook.afterEach).to.be.an.instanceof(Function);
expect(hook.doneEach).to.be.an.instanceof(Function);
expect(hook.mounted).to.be.an.instanceof(Function);
expect(hook.ready).to.be.an.instanceof(Function);
window.pluginFunctionCalled = true;
},
],
};
};
const { documentReady } = require('../../src/core/util/dom');
const { Docsify } = require('../../src/core/Docsify');
await new Promise(resolve => documentReady(resolve));
new Docsify(); // eslint-disable-line
expect(window.pluginFunctionCalled).to.equal(true);
});
});
});