From c7e06ad1b351cf7400f18efa32533b6255568f60 Mon Sep 17 00:00:00 2001 From: Jeff Date: Fri, 22 Nov 2019 17:43:19 -0800 Subject: [PATCH] docs(slots): generate slot documentation with extra properties --- docs/.vuepress/config.js | 5 ++ .../generateApiDocs/clientDynamicModules.js | 23 ++++++ .../generateApiDocs/components/ApiSlots.vue | 47 ++++++++++++ docs/.vuepress/generateApiDocs/debug.js | 10 +++ docs/.vuepress/generateApiDocs/enhanceApp.js | 2 + .../generateApiDocs/extendPageData.js | 2 +- docs/.vuepress/generateApiDocs/generator.js | 39 ---------- docs/.vuepress/generateApiDocs/index.js | 17 +---- .../generateApiDocs/utils/highlight.js | 5 +- .../generateApiDocs/utils/node/Slots.js | 18 +++++ .../generateApiDocs/utils/node/generator.js | 50 +++++++++++++ .../utils/node/getAdditionalSlotProperties.js | 45 +++++++++++ docs/api/slots.md | 75 +++---------------- docs/package.json | 4 + docs/yarn.lock | 40 +++++++++- 15 files changed, 260 insertions(+), 122 deletions(-) create mode 100644 docs/.vuepress/generateApiDocs/clientDynamicModules.js create mode 100644 docs/.vuepress/generateApiDocs/components/ApiSlots.vue create mode 100644 docs/.vuepress/generateApiDocs/debug.js delete mode 100644 docs/.vuepress/generateApiDocs/generator.js create mode 100644 docs/.vuepress/generateApiDocs/utils/node/Slots.js create mode 100644 docs/.vuepress/generateApiDocs/utils/node/generator.js create mode 100644 docs/.vuepress/generateApiDocs/utils/node/getAdditionalSlotProperties.js diff --git a/docs/.vuepress/config.js b/docs/.vuepress/config.js index 456b89f..55a94e2 100644 --- a/docs/.vuepress/config.js +++ b/docs/.vuepress/config.js @@ -75,6 +75,11 @@ module.exports = { '@vuepress/plugin-search', '@vuepress/plugin-nprogress', ], + extraWatchFiles: [ + '.vuepress/generateApiDocs/**/*.js', + '../src/**.*.js', + '../src/**.*.vue', + ], themeConfig: { repo: 'sagalbot/vue-select', editLinks: true, diff --git a/docs/.vuepress/generateApiDocs/clientDynamicModules.js b/docs/.vuepress/generateApiDocs/clientDynamicModules.js new file mode 100644 index 0000000..91dfd22 --- /dev/null +++ b/docs/.vuepress/generateApiDocs/clientDynamicModules.js @@ -0,0 +1,23 @@ +const generator = require('./utils/node/generator'); +const {green} = require('chalk'); + +/** + * Dynamically generates all API documentation with vue-docgen-api. + * The resulting object can be imported and used client-side via: + * + * import documentation from '@dynamic/api' + * + * @return {Promise<{name: string, content: string}>} + */ +async function clientDynamicModules (sourceDir) { + const docs = await generator(sourceDir); + + console.log(green('✅ Generated API documentation for Select.vue')); + + return { + name: 'api.js', + content: `export default ${JSON.stringify(docs)}`, + }; +} + +module.exports = clientDynamicModules; diff --git a/docs/.vuepress/generateApiDocs/components/ApiSlots.vue b/docs/.vuepress/generateApiDocs/components/ApiSlots.vue new file mode 100644 index 0000000..6ca3db5 --- /dev/null +++ b/docs/.vuepress/generateApiDocs/components/ApiSlots.vue @@ -0,0 +1,47 @@ + + + + + diff --git a/docs/.vuepress/generateApiDocs/debug.js b/docs/.vuepress/generateApiDocs/debug.js new file mode 100644 index 0000000..1c2005f --- /dev/null +++ b/docs/.vuepress/generateApiDocs/debug.js @@ -0,0 +1,10 @@ +const getSlotDefinitions = require('./utils/node/getAdditionalSlotProperties'); +const generator = require('./utils/node/generator'); + +(async () => { + + const component = await generator('/Users/sagalbot/Sites/vue-select/docs'); + + debugger; + +})(); diff --git a/docs/.vuepress/generateApiDocs/enhanceApp.js b/docs/.vuepress/generateApiDocs/enhanceApp.js index 06cf305..40657f7 100644 --- a/docs/.vuepress/generateApiDocs/enhanceApp.js +++ b/docs/.vuepress/generateApiDocs/enhanceApp.js @@ -1,8 +1,10 @@ // import docs from '@dynamic/api.js'; import ApiProps from './components/ApiProps'; import ApiEvents from './components/ApiEvents'; +import ApiSlots from './components/ApiSlots'; export default ({Vue, options, router, siteData}) => { Vue.component('api-props', ApiProps); + Vue.component('api-slots', ApiSlots); Vue.component('api-events', ApiEvents); } diff --git a/docs/.vuepress/generateApiDocs/extendPageData.js b/docs/.vuepress/generateApiDocs/extendPageData.js index 66da226..85f3dcc 100644 --- a/docs/.vuepress/generateApiDocs/extendPageData.js +++ b/docs/.vuepress/generateApiDocs/extendPageData.js @@ -1,4 +1,4 @@ -const generator = require('./generator'); +const generator = require('./utils/node/generator'); module.exports = async function (page) { const section = ['props', 'events', 'slots', 'methods'].find( diff --git a/docs/.vuepress/generateApiDocs/generator.js b/docs/.vuepress/generateApiDocs/generator.js deleted file mode 100644 index d360e9f..0000000 --- a/docs/.vuepress/generateApiDocs/generator.js +++ /dev/null @@ -1,39 +0,0 @@ -const docs = require('vue-docgen-api'); -const path = require('path'); -// const getMemberFilter = require('vue-docgen-api/dist/utils/getMemberFilter.js'); -const getMemberFilter = require('vue-docgen-api/dist/utils/getPropsFilter'); -const bt = require('@babel/types'); -const {NodePath} = require('ast-types'); -const {Documentation, ParseOptions, ComponentDoc} = require('vue-docgen-api'); - -/** - * Generate an object of API documentation. - * @param documentationRootDir - * @return {Promise} - */ -module.exports = async (documentationRootDir) => { - const file = path.resolve(documentationRootDir, - '../src/components/Select.vue'); - return await docs.parse(file, { - jsx: false, - // addScriptHandlers: [ - // /** - // * @param {Documentation} docs - // * @param {NodePath} path - // * @param {bt.File} astPath - // * @param {ParseOptions} options - // * @return {Promise} - // */ - // function (docs, path, astPath, options) { - // if (bt.isObjectExpression(path.node)) { - // const propsPath = path - // .get('properties') - // .filter(nodePath => bt.isObjectProperty(nodePath.node) && - // getMemberFilter('props')(nodePath)); - // - // - // } - // }, - // ], - }); -}; diff --git a/docs/.vuepress/generateApiDocs/index.js b/docs/.vuepress/generateApiDocs/index.js index c6535b9..50bd44b 100644 --- a/docs/.vuepress/generateApiDocs/index.js +++ b/docs/.vuepress/generateApiDocs/index.js @@ -1,7 +1,6 @@ const path = require('path'); -const chalk = require('chalk'); -const generator = require('./generator'); const extendPageData = require('./extendPageData'); +const clientDynamicModules = require('./clientDynamicModules'); /** * @param options @@ -12,22 +11,12 @@ module.exports = (options, {sourceDir}) => ({ name: 'vuepress-docgen', /** - * Dynamically generates all API documentation with vue-docgen-api. - * The resulting object can be imported and used client-side via: - * - * import documentation from '@dynamic/api' + * Generates API documentation for use on the client side. * * @see https://vuepress.vuejs.org/plugin/option-api.html#clientdynamicmodules * @return {Promise<{name: string, content: string}>} */ - async clientDynamicModules () { - const docs = await generator(sourceDir); - console.log(chalk.green('✅ Generated API documentation for Select.vue')); - return { - name: 'api.js', - content: `export default ${JSON.stringify(docs)}`, - }; - }, + clientDynamicModules: async () => await clientDynamicModules(sourceDir), /** * @see https://vuepress.vuejs.org/plugin/option-api.html#enhanceappfiles diff --git a/docs/.vuepress/generateApiDocs/utils/highlight.js b/docs/.vuepress/generateApiDocs/utils/highlight.js index 93de56a..9c8d064 100644 --- a/docs/.vuepress/generateApiDocs/utils/highlight.js +++ b/docs/.vuepress/generateApiDocs/utils/highlight.js @@ -3,6 +3,9 @@ import { highlight, languages } from 'prismjs'; /** * Returns code block with prism markup. * @param {String} snippet + * @param {String} language * @return {*} */ -export default snippet => highlight(snippet, languages.javascript, 'javascript') +export default (snippet, language = 'javascript') => { + return highlight(snippet, languages[language], language); +} diff --git a/docs/.vuepress/generateApiDocs/utils/node/Slots.js b/docs/.vuepress/generateApiDocs/utils/node/Slots.js new file mode 100644 index 0000000..f4a2561 --- /dev/null +++ b/docs/.vuepress/generateApiDocs/utils/node/Slots.js @@ -0,0 +1,18 @@ +module.exports = class Slots { + constructor (slots = {}) { + this.slots = slots; + } + + add (name, slot) { + this.slots[name] = slot; + return this; + } + + get definitions () { + return this.slots; + } + + absorb (slots) { + this.slots.map() + } +}; diff --git a/docs/.vuepress/generateApiDocs/utils/node/generator.js b/docs/.vuepress/generateApiDocs/utils/node/generator.js new file mode 100644 index 0000000..fdc8f85 --- /dev/null +++ b/docs/.vuepress/generateApiDocs/utils/node/generator.js @@ -0,0 +1,50 @@ +const path = require('path'); +const bt = require('@babel/types'); +const docs = require('vue-docgen-api'); +const {NodePath} = require('ast-types'); +const {Documentation, ParseOptions, ComponentDoc} = require('vue-docgen-api'); +const additionalSlotProperties = require('./getAdditionalSlotProperties'); + +/** + * Generate an object of API documentation. + * @param documentationRootDir + * @return {Promise} + */ +module.exports = async (documentationRootDir) => { + + const file = path.resolve( + documentationRootDir, + '../src/components/Select.vue', + ); + + const documentation = await docs.parse(file, { + jsx: false, + addScriptHandlers: [ + /** + * @param {Documentation} docs + * @param {NodePath} path + * @param {bt.File} astPath + * @param {ParseOptions} options + * @return {Promise} + */ + function (docs, path, astPath, options) { + + }, + ], + addTemplateHandlers: [ + /** + * @param {Documentation} docs + * @param {ASTElement} templateAst + * @param {TemplateParserOptions} options + */ + function (docs, templateAst, options) { + }, + ], + }); + + const definitions = additionalSlotProperties(file); + + documentation.slots = documentation.slots.map(slot => ({ ...slot, ...definitions[slot.name] })); + + return documentation; +}; diff --git a/docs/.vuepress/generateApiDocs/utils/node/getAdditionalSlotProperties.js b/docs/.vuepress/generateApiDocs/utils/node/getAdditionalSlotProperties.js new file mode 100644 index 0000000..85f1a9a --- /dev/null +++ b/docs/.vuepress/generateApiDocs/utils/node/getAdditionalSlotProperties.js @@ -0,0 +1,45 @@ +const fs = require('fs'); +const path = require('path'); +const cheerio = require('cheerio'); +const pick = require('lodash/pick'); +const prettier = require('prettier'); +const compiler = require('vue-template-compiler'); +const Slots = require('./Slots'); + +const t = require('@babel/types'); +const {parse} = require('@babel/parser'); +const traverse = require('@babel/traverse'); + +function pickBindingsFromElement ({attribs}) { + return pick( + attribs, + Object.keys(attribs) + .filter(attr => attr.indexOf(':') === 0 || attr === 'v-bind'), + ); +} + +/** + * @param pathToComponent + * @return {Object} + */ +function getAdditionalSlotProperties (pathToComponent) { + const slots = new Slots(); + const file = fs.readFileSync(path.resolve(pathToComponent)).toString(); + const {template} = compiler.parseComponent(file); + const $ = cheerio.load(template.content); + + $('slot').each(function (index, element) { + const bindings = pickBindingsFromElement(element) || {}; + const slotName = element.attribs.name || 'default'; + const content = prettier.format($.html(element), {parser: 'html'}); + + slots.add(slotName, { + content, + bindings, + }); + }); + + return slots.definitions; +} + +module.exports = getAdditionalSlotProperties; diff --git a/docs/api/slots.md b/docs/api/slots.md index 1f3a94f..bb75d63 100644 --- a/docs/api/slots.md +++ b/docs/api/slots.md @@ -1,65 +1,14 @@ -::: tip -Vue Select leverages scoped slots to allow for total customization of the presentation layer. -Slots can be used to change the look and feel of the UI, or to simply swap out text. +--- +title: API/Slots +api: slots +--- + +# Vue Select Slots + +::: tip Using These Docs +The documentation below displays the slots **as they are written in the template**. This lets you +see the default implementation as written in the Vue Select component, as well as any bindings that +a scoped slot offers. ::: -## Selected Option(s) - -### `selected-option` - -#### Scope: - -- `option {Object}` - A selected option - -```html - - {{ getOptionLabel(option) }} - -``` - -### `selected-option-container` - -#### Scope: - -- `option {Object}` - A selected option -- `deselect {Function}` - Method used to deselect a given option when `multiple` is true -- `disabled {Boolean}` - Determine if the component is disabled -- `multiple {Boolean}` - If the component supports the selection of multiple values - -```html - - - - {{ getOptionLabel(option) }} - - - - -``` - -## Component Actions - -### `spinner` - -```html - -
Loading...
-
-``` - -## Dropdown - -### `option` - -#### Scope: - -- `option {Object}` - The currently iterated option from `filteredOptions` - -```html - - {{ getOptionLabel(option) }} - -``` + diff --git a/docs/package.json b/docs/package.json index 395b9f8..6c155c8 100644 --- a/docs/package.json +++ b/docs/package.json @@ -18,15 +18,19 @@ "@vuepress/plugin-search": "^1.2.0", "ast-types": "^0.13.2", "chalk": "^3.0.0", + "cheerio": "^1.0.0-rc.3", "cross-env": "^5.2.0", "fuse.js": "^3.4.4", "gh-pages": "^0.11.0", + "lodash": "^4.17.15", "markdown-it": "^10.0.0", "node-sass": "^4.12.0", + "prettier": "^1.19.1", "prismjs": "^1.17.1", "sass-loader": "^7.1.0", "vue": "^2.6.10", "vue-docgen-api": "^4.0.4", + "vue-template-compiler": "^2.6.10", "vuepress": "^1.2.0", "vuex": "^3.1.0" } diff --git a/docs/yarn.lock b/docs/yarn.lock index 48788b7..9974979 100644 --- a/docs/yarn.lock +++ b/docs/yarn.lock @@ -2020,6 +2020,18 @@ character-parser@^2.1.1: dependencies: is-regex "^1.0.3" +cheerio@^1.0.0-rc.3: + version "1.0.0-rc.3" + resolved "https://registry.yarnpkg.com/cheerio/-/cheerio-1.0.0-rc.3.tgz#094636d425b2e9c0f4eb91a46c05630c9a1a8bf6" + integrity sha512-0td5ijfUPuubwLUu0OBoe98gZj8C/AA+RW3v67GPlGOrvxWjZmBXiBCRU+I8VEiNyJzjth40POfHiz2RB3gImA== + dependencies: + css-select "~1.2.0" + dom-serializer "~0.1.1" + entities "~1.1.1" + htmlparser2 "^3.9.1" + lodash "^4.15.0" + parse5 "^3.0.1" + chokidar@^2.0.2, chokidar@^2.0.3, chokidar@^2.1.6: version "2.1.8" resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-2.1.8.tgz#804b3a7b6a99358c3c5c61e71d8728f041cff917" @@ -2518,7 +2530,7 @@ css-select-base-adapter@^0.1.1: resolved "https://registry.yarnpkg.com/css-select-base-adapter/-/css-select-base-adapter-0.1.1.tgz#3b2ff4972cc362ab88561507a95408a1432135d7" integrity sha512-jQVeeRG70QI08vSTwf1jHxp74JoZsr2XSgETae8/xC8ovSnL2WF87GTLO86Sbwdt2lK4Umg4HnnwMO4YF3Ce7w== -css-select@^1.1.0: +css-select@^1.1.0, css-select@~1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/css-select/-/css-select-1.2.0.tgz#2b3a110539c5355f1cd8d314623e870b121ec858" integrity sha1-KzoRBTnFNV8c2NMUYj6HCxIeyFg= @@ -2915,6 +2927,14 @@ dom-serializer@0: domelementtype "^2.0.1" entities "^2.0.0" +dom-serializer@~0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-0.1.1.tgz#1ec4059e284babed36eec2941d4a970a189ce7c0" + integrity sha512-l0IU0pPzLWSHBcieZbpOKgkIn3ts3vAh7ZuFyXNwJxJXk/c4Gwj9xaTJwIDVQCXawWD0qb3IzMGH5rglQaO0XA== + dependencies: + domelementtype "^1.3.0" + entities "^1.1.1" + dom-walk@^0.1.0: version "0.1.1" resolved "https://registry.yarnpkg.com/dom-walk/-/dom-walk-0.1.1.tgz#672226dc74c8f799ad35307df936aba11acd6018" @@ -2925,7 +2945,7 @@ domain-browser@^1.1.1: resolved "https://registry.yarnpkg.com/domain-browser/-/domain-browser-1.2.0.tgz#3d31f50191a6749dd1375a7f522e823d42e54eda" integrity sha512-jnjyiM6eRyZl2H+W8Q/zLMA481hzi0eszAaBUzIVnmYVDBbnLxVNnfu1HgEBvCbL+71FrxMl3E6lpKH7Ge3OXA== -domelementtype@1, domelementtype@^1.3.1: +domelementtype@1, domelementtype@^1.3.0, domelementtype@^1.3.1: version "1.3.1" resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-1.3.1.tgz#d048c44b37b0d10a7f2a3d5fee3f4333d790481f" integrity sha512-BSKB+TSpMpFI/HOxCNr1O8aMOTZ8hT3pM3GQ0w/mWRmkhEDSFJkkyzz4XQsBV44BChwGkrDfMyjVD0eA2aFV3w== @@ -3926,7 +3946,7 @@ html-tags@^2.0.0: resolved "https://registry.yarnpkg.com/html-tags/-/html-tags-2.0.0.tgz#10b30a386085f43cede353cc8fa7cb0deeea668b" integrity sha1-ELMKOGCF9Dzt41PMj6fLDe7qZos= -htmlparser2@^3.3.0: +htmlparser2@^3.3.0, htmlparser2@^3.9.1: version "3.10.1" resolved "https://registry.yarnpkg.com/htmlparser2/-/htmlparser2-3.10.1.tgz#bd679dc3f59897b6a34bb10749c855bb53a9392f" integrity sha512-IgieNijUMbkDovyoKObU1DUhm1iwNYE/fuifEoEHfd1oZKZDaONBSkal7Y01shxsM49R4XaMdGez3WnF9UfiCQ== @@ -4779,7 +4799,7 @@ lodash.uniq@^4.5.0: resolved "https://registry.yarnpkg.com/lodash.uniq/-/lodash.uniq-4.5.0.tgz#d0225373aeb652adc1bc82e4945339a842754773" integrity sha1-0CJTc662Uq3BvILklFM5qEJ1R3M= -lodash@^4.0.0, lodash@^4.17.11, lodash@^4.17.13, lodash@^4.17.15, lodash@^4.17.3, lodash@^4.17.4, lodash@^4.17.5, lodash@~4.17.10: +lodash@^4.0.0, lodash@^4.15.0, lodash@^4.17.11, lodash@^4.17.13, lodash@^4.17.15, lodash@^4.17.3, lodash@^4.17.4, lodash@^4.17.5, lodash@~4.17.10: version "4.17.15" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.15.tgz#b447f6670a0455bbfeedd11392eff330ea097548" integrity sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A== @@ -5743,6 +5763,13 @@ parse-json@^4.0.0: error-ex "^1.3.1" json-parse-better-errors "^1.0.1" +parse5@^3.0.1: + version "3.0.3" + resolved "https://registry.yarnpkg.com/parse5/-/parse5-3.0.3.tgz#042f792ffdd36851551cf4e9e066b3874ab45b5c" + integrity sha512-rgO9Zg5LLLkfJF9E6CCmXlSE4UVceloys8JrFqCcHloC3usd/kJCyPDwH2SOlzix2j3xaP9sUX3e8+kvkuleAA== + dependencies: + "@types/node" "*" + parseurl@~1.3.2, parseurl@~1.3.3: version "1.3.3" resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.3.tgz#9da19e7bee8d12dff0513ed5b76957793bc2e8d4" @@ -6248,6 +6275,11 @@ prettier@1.16.3: resolved "https://registry.yarnpkg.com/prettier/-/prettier-1.16.3.tgz#8c62168453badef702f34b45b6ee899574a6a65d" integrity sha512-kn/GU6SMRYPxUakNXhpP0EedT/KmaPzr0H5lIsDogrykbaxOpOfAFfk5XA7DZrJyMAv1wlMV3CPcZruGXVVUZw== +prettier@^1.19.1: + version "1.19.1" + resolved "https://registry.yarnpkg.com/prettier/-/prettier-1.19.1.tgz#f7d7f5ff8a9cd872a7be4ca142095956a60797cb" + integrity sha512-s7PoyDv/II1ObgQunCbB9PdLmUcBZcnWOcxDh7O0N/UwDEsHyqkW+Qh28jW+mVuCdx7gLB0BotYI1Y6uI9iyew== + pretty-bytes@^5.1.0: version "5.3.0" resolved "https://registry.yarnpkg.com/pretty-bytes/-/pretty-bytes-5.3.0.tgz#f2849e27db79fb4d6cfe24764fc4134f165989f2"