diff --git a/docs/site/mobile.js b/docs/site/entry.js similarity index 100% rename from docs/site/mobile.js rename to docs/site/entry.js diff --git a/packages/vant-cli/README.md b/packages/vant-cli/README.md index e78b541f5..207e7af4b 100644 --- a/packages/vant-cli/README.md +++ b/packages/vant-cli/README.md @@ -68,8 +68,5 @@ yarn add @vant/cli --dev - [命令](https://github.com/youzan/vant/tree/dev/packages/vant-cli/docs/commands.md) - [配置指南](https://github.com/youzan/vant/tree/dev/packages/vant-cli/docs/config.md) - [目录结构](https://github.com/youzan/vant/tree/dev/packages/vant-cli/docs/directory.md) +- [桌面端组件](https://github.com/youzan/vant/tree/dev/packages/vant-cli/docs/desktop.md) - [更新日志](https://github.com/youzan/vant/tree/dev/packages/vant-cli/changelog.md) - -## 关于桌面端组件 - -目前 Vant Cli 仅支持移动端组件的预览,桌面端组件暂不支持预览(欢迎 PR)。 diff --git a/packages/vant-cli/docs/config.md b/packages/vant-cli/docs/config.md index ffca51a88..87fbea3ee 100644 --- a/packages/vant-cli/docs/config.md +++ b/packages/vant-cli/docs/config.md @@ -12,6 +12,7 @@ - [site.versions](#siteversions) - [site.baiduAnalytics](#sitebaiduanalytics) - [site.searchConfig](#sitesearchconfig) + - [site.hideSimulator](#sitehidesimulator) - [Webpack](#webpack) - [Babel](#babel) - [默认配置](#-1) @@ -165,6 +166,8 @@ module.exports = { path: 'home', // 导航项文案 title: '介绍', + // 是否隐藏当前页右侧的手机模拟器(默认不隐藏) + hideSimulator: true, }, ], }, @@ -222,6 +225,13 @@ module.exports = { 配置内容参见 [docsearch](https://docsearch.algolia.com/docs/behavior)。 +### site.hideSimulator + +- Type: `boolean` +- Default: `false` + +是否隐藏所有页面右侧的手机模拟器,默认不隐藏 + ### site.htmlPluginOptions - Type: `object` diff --git a/packages/vant-cli/docs/desktop.md b/packages/vant-cli/docs/desktop.md new file mode 100644 index 000000000..af8b43b50 --- /dev/null +++ b/packages/vant-cli/docs/desktop.md @@ -0,0 +1,84 @@ +## 关于桌面端组件 + +Vant Cli 也支持预览桌面端组件,你可以在组件的 `demo` 目录下新建一个 `.vue` 文件,并在组件的 `README` 中按如下格式声明要预览的组件: + +```html +./demo/MyDemo.vue +``` + +`demo-code` 标签中间的文本为 `README` 到 `demo` 文件的相对路径。 + +``` +button +├─ demo # 组件示例 +│ └─ MyDemo.vue # 要预览的 demo 文件 +├─ index.js # 组件入口 +├─ index.less # 组件样式 +└─ README.md # 组件文档 +``` + +![image](https://user-images.githubusercontent.com/5093611/111076378-0e981a00-8527-11eb-8e3f-31f0be7e4021.png) + +`demo-code` 标签支持以下属性: + +| 名称 | 类型 | 描述 | +| --------- | ------- | --------------------------------------- | +| compact | boolean | 紧凑模式 | +| transform | boolean | 防止预览区内 fixed 定位的元素飞出预览区 | +| inline | boolean | 只显示组件本身,不显示预览区边框和代码 | + +### `compact` + +```html +./demo/MyDemo.vue +``` + +![image](https://user-images.githubusercontent.com/5093611/111076728-77cc5d00-8528-11eb-85f1-e7217344ab14.png) + +### `transform` + +```html +./demo/MyDemo.vue +``` + +![image](https://user-images.githubusercontent.com/5093611/111076799-d5f94000-8528-11eb-973f-c9d69f91d2a7.png) + +### `inline` + +```html +./demo/MyDemo.vue +``` + +![image](https://user-images.githubusercontent.com/5093611/111076845-15c02780-8529-11eb-9cfb-76c9b25dc2a2.png) + +### 去除手机模拟器 + +对于 PC 端的组件,如果不需要右侧的手机模拟器,可以在 `vant.config.js` 文件中设置 `site.hideSimulator` 为 `true`,这样在所有页面都会隐藏手机模拟器,也可以只针对具体页面设置。 + +```js +module.exports = { + site: { + defaultLang: 'zh-CN', + hideSimulator: true, // 所有页面都不显示 + locales: { + 'zh-CN': { + title: 'Vant', + description: '轻量、可靠的移动端 Vue 组件库', + hideSimulator: true, // 中文下所有页面都不显示 + nav: [ + { + title: '基础组件', + items: [ + { + path: 'button', + title: 'Button 按钮', + hideSimulator: true, // 只针对某个页面不显示 + }, + ], + }, + ], + }, + }, + }, +}; +``` diff --git a/packages/vant-cli/package.json b/packages/vant-cli/package.json index a77cadfb6..75a7ae957 100644 --- a/packages/vant-cli/package.json +++ b/packages/vant-cli/package.json @@ -73,6 +73,7 @@ "commander": "^5.1.0", "consola": "^2.12.2", "conventional-changelog": "^3.1.21", + "copy-text-to-clipboard": "^3.0.1", "cross-env": "^7.0.2", "css-loader": "^3.5.3", "eslint": "^6.8.0", diff --git a/packages/vant-cli/site/desktop/App.vue b/packages/vant-cli/site/desktop/App.vue index 5161bafbc..7375a108e 100644 --- a/packages/vant-cli/site/desktop/App.vue +++ b/packages/vant-cli/site/desktop/App.vue @@ -5,6 +5,7 @@ :config="config" :versions="versions" :simulator="simulator" + :has-simulator="hasSimulator" :lang-configs="langConfigs" > @@ -28,6 +29,7 @@ export default { return { packageVersion, simulator: `${path}mobile.html${location.hash}`, + hasSimulator: true, }; }, @@ -67,16 +69,16 @@ export default { watch: { lang(val) { setLang(val); - this.setTitle(); + this.setTitleAndToogleSimulator(); }, }, created() { - this.setTitle(); + this.setTitleAndToogleSimulator(); }, methods: { - setTitle() { + setTitleAndToogleSimulator() { let { title } = this.config; if (this.config.description) { @@ -84,6 +86,20 @@ export default { } document.title = title; + + const navItems = this.config.nav.reduce( + (result, nav) => [...result, ...nav.items], + [] + ); + const current = navItems.find((item) => { + return item.path === this.$route.meta.name; + }); + + this.hasSimulator = !( + config.site.hideSimulator || + this.config.hideSimulator || + (current && current.hideSimulator) + ); }, }, }; diff --git a/packages/vant-cli/site/desktop/components/DemoPlayground.vue b/packages/vant-cli/site/desktop/components/DemoPlayground.vue new file mode 100644 index 000000000..12f9d1587 --- /dev/null +++ b/packages/vant-cli/site/desktop/components/DemoPlayground.vue @@ -0,0 +1,188 @@ + + + + + diff --git a/packages/vant-cli/site/desktop/components/index.vue b/packages/vant-cli/site/desktop/components/index.vue index c087db09c..363c66559 100644 --- a/packages/vant-cli/site/desktop/components/index.vue +++ b/packages/vant-cli/site/desktop/components/index.vue @@ -8,12 +8,12 @@ @switch-version="$emit('switch-version', $event)" /> - + - + @@ -39,6 +39,7 @@ export default { lang: String, versions: Array, simulator: String, + hasSimulator: Boolean, langConfigs: Array, config: { type: Object, diff --git a/packages/vant-cli/site/desktop/main.js b/packages/vant-cli/site/desktop/main.js index 33c2a7708..c934ded76 100644 --- a/packages/vant-cli/site/desktop/main.js +++ b/packages/vant-cli/site/desktop/main.js @@ -2,18 +2,23 @@ import Vue from 'vue'; import App from './App'; import { router } from './router'; import { scrollToAnchor } from './utils'; +import DemoPlayground from './components/DemoPlayground'; if (process.env.NODE_ENV !== 'production') { Vue.config.productionTip = false; } -new Vue({ - el: '#app', - mounted() { - if (this.$route.hash) { - scrollToAnchor(this.$route.hash); - } - }, - render: h => h(App), - router, -}); +Vue.component(DemoPlayground.name, DemoPlayground); + +setTimeout(() => { + new Vue({ + el: '#app', + mounted() { + if (this.$route.hash) { + scrollToAnchor(this.$route.hash); + } + }, + render: (h) => h(App), + router, + }); +}, 0); diff --git a/packages/vant-cli/src/compiler/gen-site-desktop-shared.ts b/packages/vant-cli/src/compiler/gen-site-desktop-shared.ts index 0ce42825a..b52003d62 100644 --- a/packages/vant-cli/src/compiler/gen-site-desktop-shared.ts +++ b/packages/vant-cli/src/compiler/gen-site-desktop-shared.ts @@ -47,9 +47,9 @@ function resolveDocuments(components: string[]): DocumentItem[] { if (locales) { const langs = Object.keys(locales); - langs.forEach(lang => { + langs.forEach((lang) => { const fileName = lang === defaultLang ? 'README.md' : `README.${lang}.md`; - components.forEach(component => { + components.forEach((component) => { docs.push({ name: formatName(component, lang), path: join(SRC_DIR, component, fileName), @@ -57,7 +57,7 @@ function resolveDocuments(components: string[]): DocumentItem[] { }); }); } else { - components.forEach(component => { + components.forEach((component) => { docs.push({ name: formatName(component), path: join(SRC_DIR, component, 'README.md'), @@ -65,26 +65,35 @@ function resolveDocuments(components: string[]): DocumentItem[] { }); } - const staticDocs = glob.sync(normalizePath(join(DOCS_DIR, '**/*.md'))).map(path => { - const pairs = parse(path).name.split('.'); - return { - name: formatName(pairs[0], pairs[1] || defaultLang), - path, - }; - }); + const staticDocs = glob + .sync(normalizePath(join(DOCS_DIR, '**/*.md'))) + .map((path) => { + const pairs = parse(path).name.split('.'); + return { + name: formatName(pairs[0], pairs[1] || defaultLang), + path, + }; + }); - return [...staticDocs, ...docs.filter(item => existsSync(item.path))]; + return [...staticDocs, ...docs.filter((item) => existsSync(item.path))]; +} + +function genInstall() { + return `import Vue from 'vue'; +import PackageEntry from './package-entry'; +import './package-style'; +`; } function genImportDocuments(items: DocumentItem[]) { return items - .map(item => `import ${item.name} from '${normalizePath(item.path)}';`) + .map((item) => `import ${item.name} from '${normalizePath(item.path)}';`) .join('\n'); } function genExportDocuments(items: DocumentItem[]) { return `export const documents = { - ${items.map(item => item.name).join(',\n ')} + ${items.map((item) => item.name).join(',\n ')} };`; } @@ -104,9 +113,12 @@ export function genSiteDesktopShared() { const dirs = readdirSync(SRC_DIR); const documents = resolveDocuments(dirs); - const code = `${genImportConfig()} + const code = `${genInstall()} +${genImportConfig()} ${genImportDocuments(documents)} +Vue.use(PackageEntry); + ${genExportConfig()} ${genExportDocuments(documents)} ${genExportVersion()} diff --git a/packages/vant-cli/src/config/webpack.base.ts b/packages/vant-cli/src/config/webpack.base.ts index 78e8382de..ebc1d1824 100644 --- a/packages/vant-cli/src/config/webpack.base.ts +++ b/packages/vant-cli/src/config/webpack.base.ts @@ -62,6 +62,15 @@ if (existsSync(tsconfigPath)) { ); } +const VUE_LOADER = { + loader: 'vue-loader', + options: { + compilerOptions: { + preserveWhitespace: false, + }, + }, +}; + export const baseConfig: WebpackConfig = { mode: 'development', resolve: { @@ -71,17 +80,7 @@ export const baseConfig: WebpackConfig = { rules: [ { test: /\.vue$/, - use: [ - CACHE_LOADER, - { - loader: 'vue-loader', - options: { - compilerOptions: { - preserveWhitespace: false, - }, - }, - }, - ], + use: [CACHE_LOADER, VUE_LOADER], }, { test: /\.(js|ts|jsx|tsx)$/, @@ -113,7 +112,7 @@ export const baseConfig: WebpackConfig = { }, { test: /\.md$/, - use: [CACHE_LOADER, 'vue-loader', '@vant/markdown-loader'], + use: [CACHE_LOADER, VUE_LOADER, '@vant/markdown-loader'], }, ], }, diff --git a/packages/vant-cli/yarn.lock b/packages/vant-cli/yarn.lock index 91e06334d..198230119 100644 --- a/packages/vant-cli/yarn.lock +++ b/packages/vant-cli/yarn.lock @@ -3945,6 +3945,11 @@ copy-descriptor@^0.1.0: resolved "https://registry.yarnpkg.com/copy-descriptor/-/copy-descriptor-0.1.1.tgz#676f6eb3c39997c2ee1ac3a924fd6124748f578d" integrity sha1-Z29us8OZl8LuGsOpJP1hJHSPV40= +copy-text-to-clipboard@^3.0.1: + version "3.0.1" + resolved "https://registry.npm.taobao.org/copy-text-to-clipboard/download/copy-text-to-clipboard-3.0.1.tgz?cache=0&sync_timestamp=1613626493019&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fcopy-text-to-clipboard%2Fdownload%2Fcopy-text-to-clipboard-3.0.1.tgz#8cbf8f90e0a47f12e4a24743736265d157bce69c" + integrity sha1-jL+PkOCkfxLkokdDc2Jl0Ve85pw= + core-js-compat@^3.6.2: version "3.6.4" resolved "https://registry.yarnpkg.com/core-js-compat/-/core-js-compat-3.6.4.tgz#938476569ebb6cda80d339bcf199fae4f16fff17" diff --git a/packages/vant-markdown-loader/src/extract-demo.js b/packages/vant-markdown-loader/src/extract-demo.js new file mode 100644 index 000000000..b142fce78 --- /dev/null +++ b/packages/vant-markdown-loader/src/extract-demo.js @@ -0,0 +1,35 @@ +const path = require('path'); +const fs = require('fs'); +const parser = require('./md-parser'); + +function hyphenate(str) { + return str.replace(/\B([A-Z])/g, '-$1').toLowerCase(); +} + +module.exports = function extraDemo(content) { + const markdownDir = path.dirname(this.resourcePath); + const demoLinks = []; + + content = content.replace( + /([\s\S]*?)<\/demo-code>/g, + function (_, attrs, link) { + link = link.trim(); // 去换行符 + const tag = hyphenate(path.basename(link, '.vue')); + const fullLink = path.join(markdownDir, link); + demoLinks.indexOf(fullLink) === -1 && demoLinks.push(fullLink); + const demoContent = fs.readFileSync(fullLink, { encoding: 'utf8' }); + const demoParseredContent = parser.render( + '```html\n' + demoContent + '\n```' + ); + return ` + + <${tag} /> + + `; + } + ); + + return [content, demoLinks]; +}; diff --git a/packages/vant-markdown-loader/src/index.js b/packages/vant-markdown-loader/src/index.js index d04430519..5db6452e7 100644 --- a/packages/vant-markdown-loader/src/index.js +++ b/packages/vant-markdown-loader/src/index.js @@ -1,59 +1,61 @@ +const path = require('path'); const loaderUtils = require('loader-utils'); -const MarkdownIt = require('markdown-it'); -const markdownItAnchor = require('markdown-it-anchor'); const frontMatter = require('front-matter'); -const highlight = require('./highlight'); +const parser = require('./md-parser'); const linkOpen = require('./link-open'); const cardWrapper = require('./card-wrapper'); -const { slugify } = require('transliteration'); +const extractDemo = require('./extract-demo'); +const sideEffectTags = require('./side-effect-tags'); + +function camelize(str) { + return str.replace(/-(\w)/g, (_, c) => (c ? c.toUpperCase() : '')); +} function wrapper(content) { + let demoLinks; + let styles; + [content, demoLinks] = extractDemo.call(this, content); + [content, styles] = sideEffectTags(content); content = cardWrapper(content); - content = escape(content); - return ` - +${styles.join('\n')} `; } -const parser = new MarkdownIt({ - html: true, - highlight, -}).use(markdownItAnchor, { - level: 2, - slugify, -}); - -module.exports = function(source) { +module.exports = function (source) { let options = loaderUtils.getOptions(this) || {}; this.cacheable && this.cacheable(); @@ -74,5 +76,5 @@ module.exports = function(source) { linkOpen(parser); } - return options.wrapper(parser.render(source), fm); + return options.wrapper.call(this, parser.render(source), fm); }; diff --git a/packages/vant-markdown-loader/src/md-parser.js b/packages/vant-markdown-loader/src/md-parser.js new file mode 100644 index 000000000..3940d886f --- /dev/null +++ b/packages/vant-markdown-loader/src/md-parser.js @@ -0,0 +1,14 @@ +const MarkdownIt = require('markdown-it'); +const markdownItAnchor = require('markdown-it-anchor'); +const highlight = require('./highlight'); +const { slugify } = require('transliteration'); + +const parser = new MarkdownIt({ + html: true, + highlight, +}).use(markdownItAnchor, { + level: 2, + slugify, +}); + +module.exports = parser; diff --git a/packages/vant-markdown-loader/src/side-effect-tags.js b/packages/vant-markdown-loader/src/side-effect-tags.js new file mode 100644 index 000000000..a7f99fd5b --- /dev/null +++ b/packages/vant-markdown-loader/src/side-effect-tags.js @@ -0,0 +1,14 @@ +module.exports = function sideEffectTags(content) { + const styles = []; + + // 从模版中移除 script 标签 + content = content.replace(/[\s\S]*?<\/script>/g, ''); + + // 从模版中移除 style 标签,并收集到 styles 数组中,以转移为 .vue 文件 的 style 标签 + content = content.replace(/([\s\S]*?)<\/style>/g, (_, css) => { + styles.push(``); + return ''; + }); + + return [content, styles]; +}; diff --git a/test/demo.ts b/test/demo.ts index d231c6a7b..9353c4410 100644 --- a/test/demo.ts +++ b/test/demo.ts @@ -1,5 +1,5 @@ import Vue, { CreateElement } from 'vue'; -import '../docs/site/mobile'; +import '../docs/site/entry'; import Locale from '../src/locale'; import { mount, later } from '.'; diff --git a/webpack.config.js b/webpack.config.js index d5e3de6a0..0be3a9521 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -5,7 +5,8 @@ module.exports = function () { return { entry: { - 'site-mobile': ['./docs/site/mobile'], + 'site-mobile': ['./docs/site/entry'], + 'site-desktop': ['./docs/site/entry'], }, }; };