mirror of
https://github.com/youzan/vant.git
synced 2025-10-18 01:17:15 +00:00
Merge branch 'dev' into next
This commit is contained in:
@@ -1,9 +1,10 @@
|
||||
# Vant CLI
|
||||
|
||||
Vant CLI 是一个 Vue 组件库构建工具,通过 Vant CLI 可以快速搭建一套功能完备的 Vue 组件库。
|
||||
Vant CLI 是一个基于 Vite 实现的 Vue 组件库构建工具,通过 Vant CLI 可以快速搭建一套功能完备的 Vue 组件库。
|
||||
|
||||
### 特性
|
||||
|
||||
- 基于 Vite 实现,享受愉悦的开发体验
|
||||
- 提供丰富的命令,涵盖从开发测试到构建发布的完整流程
|
||||
- 基于约定的目录结构,自动生成优雅的文档站点和组件示例
|
||||
- 内置 ESLint 校验规则,提交代码时自动执行校验
|
||||
|
@@ -25,6 +25,12 @@
|
||||
yarn add stylelint@13 @vant/stylelint-config
|
||||
```
|
||||
|
||||
### 移除 vetur 相关配置
|
||||
|
||||
由于 Vue 3 推荐使用 volar 而不是 vetur,因此移除了 vetur 相关的配置文件。
|
||||
|
||||
现在会默认生成 WebStorm 所需的 web-types.json 文件到 `lib/web-types.json` 目录下。
|
||||
|
||||
## v4.0.3
|
||||
|
||||
`2022-07-02`
|
||||
|
@@ -54,7 +54,6 @@
|
||||
"@docsearch/js": "^3.0.0",
|
||||
"@types/jest": "^27.0.3",
|
||||
"@vant/eslint-config": "^3.3.2",
|
||||
"@vant/markdown-vetur": "^2.3.0",
|
||||
"@vant/touch-emulator": "^1.3.2",
|
||||
"@vitejs/plugin-vue": "^3.0.1",
|
||||
"@vitejs/plugin-vue-jsx": "^2.0.0",
|
||||
|
@@ -14,7 +14,7 @@ import { genStyleDepsMap } from '../compiler/gen-style-deps-map.js';
|
||||
import { genComponentStyle } from '../compiler/gen-component-style.js';
|
||||
import { SRC_DIR, LIB_DIR, ES_DIR } from '../common/constant.js';
|
||||
import { genPackageStyle } from '../compiler/gen-package-style.js';
|
||||
import { genVeturConfig } from '../compiler/gen-vetur-config.js';
|
||||
import { genWebStormTypes } from '../compiler/web-types/index.js';
|
||||
import {
|
||||
isDir,
|
||||
isSfc,
|
||||
@@ -137,7 +137,7 @@ async function buildPackageStyleEntry() {
|
||||
async function buildBundledOutputs() {
|
||||
setModuleEnv('esmodule');
|
||||
await compileBundles();
|
||||
genVeturConfig();
|
||||
genWebStormTypes();
|
||||
}
|
||||
|
||||
const tasks = [
|
||||
|
@@ -3,7 +3,6 @@ import {
|
||||
ES_DIR,
|
||||
LIB_DIR,
|
||||
DIST_DIR,
|
||||
VETUR_DIR,
|
||||
SITE_DIST_DIR,
|
||||
} from '../common/constant.js';
|
||||
|
||||
@@ -14,7 +13,6 @@ export async function clean() {
|
||||
remove(ES_DIR),
|
||||
remove(LIB_DIR),
|
||||
remove(DIST_DIR),
|
||||
remove(VETUR_DIR),
|
||||
remove(SITE_DIST_DIR),
|
||||
]);
|
||||
}
|
||||
|
@@ -21,7 +21,6 @@ export const ROOT = findRootDir(CWD);
|
||||
export const ES_DIR = join(ROOT, 'es');
|
||||
export const LIB_DIR = join(ROOT, 'lib');
|
||||
export const DOCS_DIR = join(ROOT, 'docs');
|
||||
export const VETUR_DIR = join(ROOT, 'vetur');
|
||||
export const SITE_DIST_DIR = join(ROOT, 'site-dist');
|
||||
export const VANT_CONFIG_FILE = join(ROOT, 'vant.config.mjs');
|
||||
export const PACKAGE_JSON_FILE = join(ROOT, 'package.json');
|
||||
|
@@ -1,25 +0,0 @@
|
||||
import markdownVetur from '@vant/markdown-vetur';
|
||||
import {
|
||||
SRC_DIR,
|
||||
VETUR_DIR,
|
||||
getVantConfig,
|
||||
getPackageJson,
|
||||
} from '../common/constant.js';
|
||||
|
||||
// generate vetur tags & attributes
|
||||
export function genVeturConfig() {
|
||||
const pkgJson = getPackageJson();
|
||||
const vantConfig = getVantConfig();
|
||||
const options = vantConfig.build?.vetur;
|
||||
|
||||
if (options) {
|
||||
markdownVetur.parseAndWrite({
|
||||
name: vantConfig.name,
|
||||
path: SRC_DIR,
|
||||
test: /README\.md/,
|
||||
version: pkgJson.version,
|
||||
outputDir: VETUR_DIR,
|
||||
...options,
|
||||
});
|
||||
}
|
||||
}
|
153
packages/vant-cli/src/compiler/web-types/formatter.ts
Normal file
153
packages/vant-cli/src/compiler/web-types/formatter.ts
Normal file
@@ -0,0 +1,153 @@
|
||||
/* eslint-disable no-continue */
|
||||
import { Articles } from './parser.js';
|
||||
import {
|
||||
formatOptions,
|
||||
formatType,
|
||||
removeVersion,
|
||||
toKebabCase,
|
||||
} from './utils.js';
|
||||
import { VueEventArgument, VueTag } from './type.js';
|
||||
|
||||
function formatComponentName(name: string, tagPrefix: string) {
|
||||
return tagPrefix + toKebabCase(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* format arguments of events
|
||||
* input = value: { foo: foo or 1, bar: bar or 2 }, value2: { one: 1 and 1, two: 2 and 2 }, foo: bar
|
||||
* output = [{ name: 'value', type: '{ foo: foo or 1, bar: bar or 2 }' }, { name: 'value2', type: '{ one: 1 and 1, two: 2 and 2 }'}, { name: 'foo', type: 'bar' }]
|
||||
*/
|
||||
function formatArguments(input: string): VueEventArgument[] {
|
||||
if (input === '-') return [];
|
||||
const args: VueEventArgument[] = [];
|
||||
const items = [];
|
||||
input = formatType(input);
|
||||
while (input.length > 0) {
|
||||
if (/(?!_)\w/.test(input[0])) {
|
||||
const val = input.match(/(\w|\s|\p{P}|\||\[|\]|>|<)+/)![0] || '';
|
||||
input = input.substring(val.length);
|
||||
items.push(val);
|
||||
} else if (input[0] === '{') {
|
||||
const val = input.match(/\{[^}]+\}/)![0] || '';
|
||||
input = input.substring(val.length);
|
||||
items.push(val);
|
||||
} else if ([':', ',', '_', ' '].includes(input[0])) {
|
||||
input = input.substring(1);
|
||||
} else {
|
||||
const val = input.match(/( |'|\||\w)+/)![0] || '';
|
||||
input = input.substring(val.length);
|
||||
items.push(val);
|
||||
}
|
||||
}
|
||||
|
||||
for (let i = 0; i < items.length; i += 2) {
|
||||
args.push({
|
||||
name: items[i],
|
||||
type: items[i + 1],
|
||||
});
|
||||
}
|
||||
|
||||
return args;
|
||||
}
|
||||
|
||||
function getNameFromTableTitle(tableTitle: string, tagPrefix: string) {
|
||||
tableTitle = tableTitle.trim();
|
||||
if (tableTitle.includes(' ')) {
|
||||
return formatComponentName(tableTitle, tagPrefix).split(' ')[0];
|
||||
}
|
||||
}
|
||||
|
||||
function findTag(vueTags: VueTag[], name: string) {
|
||||
const matched = vueTags.find((item) => item.name === name);
|
||||
|
||||
if (matched) {
|
||||
return matched;
|
||||
}
|
||||
|
||||
const newTag: VueTag = {
|
||||
name,
|
||||
slots: [],
|
||||
events: [],
|
||||
attributes: [],
|
||||
};
|
||||
|
||||
vueTags.push(newTag);
|
||||
|
||||
return newTag;
|
||||
}
|
||||
|
||||
export function formatter(
|
||||
vueTags: VueTag[],
|
||||
articles: Articles,
|
||||
tagPrefix = ''
|
||||
) {
|
||||
if (!articles.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
const mainTitle = articles[0].content;
|
||||
const defaultName = mainTitle
|
||||
? formatComponentName(mainTitle.split(' ')[0], tagPrefix)
|
||||
: '';
|
||||
const tables = articles.filter((article) => article.type === 'table');
|
||||
|
||||
tables.forEach((item) => {
|
||||
const { table } = item;
|
||||
const prevIndex = articles.indexOf(item) - 1;
|
||||
const prevArticle = articles[prevIndex];
|
||||
|
||||
if (!prevArticle || !prevArticle.content || !table || !table.body) {
|
||||
return;
|
||||
}
|
||||
|
||||
const tableTitle = prevArticle.content;
|
||||
|
||||
if (tableTitle.includes('Props')) {
|
||||
const name = getNameFromTableTitle(tableTitle, tagPrefix) || defaultName;
|
||||
const tag = findTag(vueTags, name);
|
||||
|
||||
table.body.forEach((line) => {
|
||||
const [name, desc, type, defaultVal, options] = line;
|
||||
tag.attributes!.push({
|
||||
name: removeVersion(name),
|
||||
default: defaultVal,
|
||||
description: desc,
|
||||
options: formatOptions(options),
|
||||
value: {
|
||||
type: formatType(type),
|
||||
kind: 'expression',
|
||||
},
|
||||
});
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (tableTitle.includes('Events')) {
|
||||
const name = getNameFromTableTitle(tableTitle, tagPrefix) || defaultName;
|
||||
const tag = findTag(vueTags, name);
|
||||
|
||||
table.body.forEach((line) => {
|
||||
const [name, desc, args] = line;
|
||||
tag.events!.push({
|
||||
name: removeVersion(name),
|
||||
description: desc,
|
||||
arguments: formatArguments(args),
|
||||
});
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (tableTitle.includes('Slots')) {
|
||||
const name = getNameFromTableTitle(tableTitle, tagPrefix) || defaultName;
|
||||
const tag = findTag(vueTags, name);
|
||||
|
||||
table.body.forEach((line) => {
|
||||
const [name, desc] = line;
|
||||
tag.slots!.push({
|
||||
name: removeVersion(name),
|
||||
description: desc,
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
54
packages/vant-cli/src/compiler/web-types/index.ts
Normal file
54
packages/vant-cli/src/compiler/web-types/index.ts
Normal file
@@ -0,0 +1,54 @@
|
||||
import glob from 'fast-glob';
|
||||
import { join } from 'path';
|
||||
import fse from 'fs-extra';
|
||||
import { mdParser } from './parser.js';
|
||||
import { formatter } from './formatter.js';
|
||||
import { genWebTypes } from './web-types.js';
|
||||
import { Options, VueTag } from './type.js';
|
||||
import { normalizePath } from './utils.js';
|
||||
import {
|
||||
SRC_DIR,
|
||||
LIB_DIR,
|
||||
getVantConfig,
|
||||
getPackageJson,
|
||||
} from '../../common/constant.js';
|
||||
|
||||
async function readMarkdown(options: Options) {
|
||||
const mds = await glob(normalizePath(`${options.path}/**/*.md`));
|
||||
return mds
|
||||
.filter((md) => options.test.test(md))
|
||||
.map((path) => fse.readFileSync(path, 'utf-8'));
|
||||
}
|
||||
|
||||
export async function parseAndWrite(options: Options) {
|
||||
if (!options.outputDir) {
|
||||
throw new Error('outputDir can not be empty.');
|
||||
}
|
||||
|
||||
const mds = await readMarkdown(options);
|
||||
const vueTags: VueTag[] = [];
|
||||
|
||||
mds.forEach((md) => {
|
||||
const parsedMd = mdParser(md);
|
||||
formatter(vueTags, parsedMd, options.tagPrefix);
|
||||
});
|
||||
|
||||
const webTypes = genWebTypes(vueTags, options);
|
||||
fse.outputFileSync(
|
||||
join(options.outputDir, 'web-types.json'),
|
||||
JSON.stringify(webTypes, null, 2)
|
||||
);
|
||||
}
|
||||
|
||||
export function genWebStormTypes() {
|
||||
const pkgJson = getPackageJson();
|
||||
const vantConfig = getVantConfig();
|
||||
|
||||
parseAndWrite({
|
||||
name: vantConfig.name,
|
||||
path: SRC_DIR,
|
||||
test: /README\.md/,
|
||||
version: pkgJson.version,
|
||||
outputDir: LIB_DIR,
|
||||
});
|
||||
}
|
109
packages/vant-cli/src/compiler/web-types/parser.ts
Normal file
109
packages/vant-cli/src/compiler/web-types/parser.ts
Normal file
@@ -0,0 +1,109 @@
|
||||
/* eslint-disable no-cond-assign */
|
||||
const TITLE_REG = /^(#+)\s+([^\n]*)/;
|
||||
const TABLE_REG = /^\|.+\r?\n\|\s*-+/;
|
||||
const TD_REG = /\s*`[^`]+`\s*|([^|`]+)/g;
|
||||
const TABLE_SPLIT_LINE_REG = /^\|\s*-/;
|
||||
|
||||
type TableContent = {
|
||||
head: string[];
|
||||
body: string[][];
|
||||
};
|
||||
|
||||
export type Article = {
|
||||
type: string;
|
||||
content?: string;
|
||||
table?: TableContent;
|
||||
level?: number;
|
||||
};
|
||||
|
||||
export type Articles = Article[];
|
||||
|
||||
function readLine(input: string) {
|
||||
const end = input.indexOf('\n');
|
||||
|
||||
return input.substr(0, end !== -1 ? end : input.length);
|
||||
}
|
||||
|
||||
function splitTableLine(line: string) {
|
||||
line = line.replace(/\\\|/g, 'JOIN');
|
||||
|
||||
const items = line
|
||||
.split('|')
|
||||
.map((item) => item.trim().replace(/JOIN/g, '|'));
|
||||
|
||||
// remove pipe character on both sides
|
||||
items.pop();
|
||||
items.shift();
|
||||
|
||||
return items;
|
||||
}
|
||||
|
||||
function tableParse(input: string) {
|
||||
let start = 0;
|
||||
let isHead = true;
|
||||
|
||||
const end = input.length;
|
||||
const table: TableContent = {
|
||||
head: [],
|
||||
body: [],
|
||||
};
|
||||
|
||||
while (start < end) {
|
||||
const target = input.substr(start);
|
||||
const line = readLine(target);
|
||||
|
||||
if (!/^\|/.test(target)) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (TABLE_SPLIT_LINE_REG.test(target)) {
|
||||
isHead = false;
|
||||
} else if (!isHead && line.includes('|')) {
|
||||
const matched = line.trim().match(TD_REG);
|
||||
|
||||
if (matched) {
|
||||
table.body.push(splitTableLine(line));
|
||||
}
|
||||
}
|
||||
|
||||
start += line.length + 1;
|
||||
}
|
||||
|
||||
return {
|
||||
table,
|
||||
usedLength: start,
|
||||
};
|
||||
}
|
||||
|
||||
export function mdParser(input: string): Articles {
|
||||
const article = [];
|
||||
let start = 0;
|
||||
const end = input.length;
|
||||
|
||||
while (start < end) {
|
||||
const target = input.substr(start);
|
||||
|
||||
let match;
|
||||
if ((match = TITLE_REG.exec(target))) {
|
||||
article.push({
|
||||
type: 'title',
|
||||
content: match[2],
|
||||
level: match[1].length,
|
||||
});
|
||||
|
||||
start += match.index + match[0].length;
|
||||
} else if ((match = TABLE_REG.exec(target))) {
|
||||
const { table, usedLength } = tableParse(target.substr(match.index));
|
||||
article.push({
|
||||
type: 'table',
|
||||
table,
|
||||
});
|
||||
|
||||
start += match.index + usedLength;
|
||||
} else {
|
||||
start += readLine(target).length + 1;
|
||||
}
|
||||
}
|
||||
|
||||
return article;
|
||||
}
|
45
packages/vant-cli/src/compiler/web-types/type.ts
Normal file
45
packages/vant-cli/src/compiler/web-types/type.ts
Normal file
@@ -0,0 +1,45 @@
|
||||
import { PathLike } from 'fs';
|
||||
|
||||
export type VueSlot = {
|
||||
name: string;
|
||||
description: string;
|
||||
};
|
||||
|
||||
export type VueEventArgument = {
|
||||
name: string;
|
||||
type: string;
|
||||
};
|
||||
|
||||
export type VueEvent = {
|
||||
name: string;
|
||||
description?: string;
|
||||
arguments?: VueEventArgument[];
|
||||
};
|
||||
|
||||
export type VueAttribute = {
|
||||
name: string;
|
||||
default: string;
|
||||
description: string;
|
||||
options: string[];
|
||||
value: {
|
||||
kind: 'expression';
|
||||
type: string;
|
||||
};
|
||||
};
|
||||
|
||||
export type VueTag = {
|
||||
name: string;
|
||||
slots?: VueSlot[];
|
||||
events?: VueEvent[];
|
||||
attributes?: VueAttribute[];
|
||||
description?: string;
|
||||
};
|
||||
|
||||
export type Options = {
|
||||
name: string;
|
||||
path: PathLike;
|
||||
test: RegExp;
|
||||
version: string;
|
||||
outputDir?: string;
|
||||
tagPrefix?: string;
|
||||
};
|
28
packages/vant-cli/src/compiler/web-types/utils.ts
Normal file
28
packages/vant-cli/src/compiler/web-types/utils.ts
Normal file
@@ -0,0 +1,28 @@
|
||||
// myName -> my-name
|
||||
export function toKebabCase(input: string): string {
|
||||
return input.replace(
|
||||
/[A-Z]/g,
|
||||
(val, index) => (index === 0 ? '' : '-') + val.toLowerCase()
|
||||
);
|
||||
}
|
||||
|
||||
// name `v2.0.0` -> name
|
||||
export function removeVersion(str: string) {
|
||||
return str.replace(/`(\w|\.)+`/g, '').trim();
|
||||
}
|
||||
|
||||
// *boolean* -> boolean
|
||||
// _boolean_ -> boolean
|
||||
export function formatType(type: string) {
|
||||
return type.replace(/(^(\*|_))|((\*|_)$)/g, '');
|
||||
}
|
||||
|
||||
export function normalizePath(path: string): string {
|
||||
return path.replace(/\\/g, '/');
|
||||
}
|
||||
|
||||
// `default` `primary` -> ['default', 'primary']
|
||||
export function formatOptions(options?: string) {
|
||||
if (!options) return [];
|
||||
return options.replace(/`/g, '').split(' ');
|
||||
}
|
19
packages/vant-cli/src/compiler/web-types/web-types.ts
Normal file
19
packages/vant-cli/src/compiler/web-types/web-types.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
import type { VueTag, Options } from './type.js';
|
||||
|
||||
// create web-types.json to provide autocomplete in JetBrains IDEs
|
||||
export function genWebTypes(tags: VueTag[], options: Options) {
|
||||
return {
|
||||
$schema:
|
||||
'https://raw.githubusercontent.com/JetBrains/web-types/master/schema/web-types.json',
|
||||
framework: 'vue',
|
||||
name: options.name,
|
||||
version: options.version,
|
||||
contributions: {
|
||||
html: {
|
||||
tags,
|
||||
attributes: [],
|
||||
'types-syntax': 'typescript',
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
@@ -14,12 +14,19 @@ export function getViteConfigForPackage({
|
||||
const { name, build } = getVantConfig();
|
||||
const entryExtension = build?.extensions?.esm || '.js';
|
||||
const entry = join(ES_DIR, `index${entryExtension}`);
|
||||
const shouldReplaceEnv = minify || formats?.includes('umd');
|
||||
|
||||
return {
|
||||
root: CWD,
|
||||
|
||||
logLevel: 'silent',
|
||||
|
||||
define: shouldReplaceEnv
|
||||
? {
|
||||
'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV),
|
||||
}
|
||||
: undefined,
|
||||
|
||||
build: {
|
||||
lib: {
|
||||
name,
|
||||
@@ -30,6 +37,7 @@ export function getViteConfigForPackage({
|
||||
return minify ? `${name}${suffix}.min.js` : `${name}${suffix}.js`;
|
||||
},
|
||||
},
|
||||
|
||||
// terser has better compression than esbuild
|
||||
minify: minify ? 'terser' : false,
|
||||
rollupOptions: {
|
||||
|
1
packages/vant-cli/src/module.d.ts
vendored
1
packages/vant-cli/src/module.d.ts
vendored
@@ -4,4 +4,3 @@ declare module 'hash-sum';
|
||||
declare module '@babel/core';
|
||||
declare module 'release-it';
|
||||
declare module 'conventional-changelog';
|
||||
declare module '@vant/markdown-vetur';
|
||||
|
Reference in New Issue
Block a user