Merge branch 'dev' into next

This commit is contained in:
chenjiahan
2022-08-10 07:58:38 +08:00
73 changed files with 1801 additions and 395 deletions

View File

@@ -1,9 +1,10 @@
# Vant CLI
Vant CLI 是一个 Vue 组件库构建工具,通过 Vant CLI 可以快速搭建一套功能完备的 Vue 组件库。
Vant CLI 是一个基于 Vite 实现的 Vue 组件库构建工具,通过 Vant CLI 可以快速搭建一套功能完备的 Vue 组件库。
### 特性
- 基于 Vite 实现,享受愉悦的开发体验
- 提供丰富的命令,涵盖从开发测试到构建发布的完整流程
- 基于约定的目录结构,自动生成优雅的文档站点和组件示例
- 内置 ESLint 校验规则,提交代码时自动执行校验

View File

@@ -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`

View File

@@ -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",

View File

@@ -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 = [

View File

@@ -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),
]);
}

View File

@@ -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');

View File

@@ -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,
});
}
}

View 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,
});
});
}
});
}

View 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,
});
}

View 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;
}

View 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;
};

View 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(' ');
}

View 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',
},
},
};
}

View File

@@ -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: {

View File

@@ -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';