From b8e5cf81c78ecc732eaae9e88452cd9611432843 Mon Sep 17 00:00:00 2001 From: yuxuanhuo Date: Sat, 8 Aug 2020 11:16:54 +0800 Subject: [PATCH 1/4] =?UTF-8?q?feat(markdown):=20=E6=B7=BB=E5=8A=A0markdow?= =?UTF-8?q?n=E7=BB=84=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../docs/stories/markdown/index.stories.tsx | 24 ++++++ packages/ui/markdown/.eslintrc.js | 7 ++ packages/ui/markdown/.storybook/main.js | 73 +++++++++++++++++++ packages/ui/markdown/.storybook/preview.js | 16 ++++ packages/ui/markdown/babel.config.js | 11 +++ packages/ui/markdown/package.json | 65 +++++++++++++++++ packages/ui/markdown/scripts/build.sh | 18 +++++ packages/ui/markdown/src/Markdown.tsx | 56 ++++++++++++++ packages/ui/markdown/src/index.less | 34 +++++++++ packages/ui/markdown/src/index.tsx | 56 ++++++++++++++ packages/ui/markdown/src/plugins/common.ts | 13 ++++ .../markdown/src/plugins/createImagePlugin.ts | 46 ++++++++++++ .../markdown/src/plugins/createLinkPlugin.ts | 47 ++++++++++++ packages/ui/markdown/src/plugins/index.ts | 7 ++ .../ui/markdown/stories/index.stories.tsx | 26 +++++++ packages/ui/markdown/tsconfig.json | 8 ++ yarn.lock | 60 +++++++++++++-- 17 files changed, 561 insertions(+), 6 deletions(-) create mode 100644 packages/ui/docs/stories/markdown/index.stories.tsx create mode 100644 packages/ui/markdown/.eslintrc.js create mode 100644 packages/ui/markdown/.storybook/main.js create mode 100644 packages/ui/markdown/.storybook/preview.js create mode 100644 packages/ui/markdown/babel.config.js create mode 100644 packages/ui/markdown/package.json create mode 100644 packages/ui/markdown/scripts/build.sh create mode 100644 packages/ui/markdown/src/Markdown.tsx create mode 100644 packages/ui/markdown/src/index.less create mode 100644 packages/ui/markdown/src/index.tsx create mode 100644 packages/ui/markdown/src/plugins/common.ts create mode 100644 packages/ui/markdown/src/plugins/createImagePlugin.ts create mode 100644 packages/ui/markdown/src/plugins/createLinkPlugin.ts create mode 100644 packages/ui/markdown/src/plugins/index.ts create mode 100644 packages/ui/markdown/stories/index.stories.tsx create mode 100644 packages/ui/markdown/tsconfig.json diff --git a/packages/ui/docs/stories/markdown/index.stories.tsx b/packages/ui/docs/stories/markdown/index.stories.tsx new file mode 100644 index 00000000..72449469 --- /dev/null +++ b/packages/ui/docs/stories/markdown/index.stories.tsx @@ -0,0 +1,24 @@ +import * as React from 'react'; +import Markdown from '@osui/markdown'; + +export default { + title: 'OSUI-Markdown', +}; + +const content = ` +# Writing template strings in Markdown + +## Writing template strings in Markdown + +adsfasdfasdf + +asdfasdf + +\`\`\`jsx + +\`\`\` +`; + +export const Demo = () => ( + +); diff --git a/packages/ui/markdown/.eslintrc.js b/packages/ui/markdown/.eslintrc.js new file mode 100644 index 00000000..5928ca1d --- /dev/null +++ b/packages/ui/markdown/.eslintrc.js @@ -0,0 +1,7 @@ +module.exports = { + extends: require.resolve('@reskript/config-lint/config/eslint'), + rules: { + 'react/jsx-uses-react': 'error', + }, + root: true, +}; diff --git a/packages/ui/markdown/.storybook/main.js b/packages/ui/markdown/.storybook/main.js new file mode 100644 index 00000000..34ac8402 --- /dev/null +++ b/packages/ui/markdown/.storybook/main.js @@ -0,0 +1,73 @@ +const {getBabelConfig} = require('@reskript/config-babel'); +const {loaders} = require('@reskript/config-webpack'); +const path = require('path'); + +const loaderOptions = { + cwd: process.cwd(), + srcDirectory: 'src', + projectSettings: { + build: { + extractCSS: false, + styleResources: [ + require.resolve('@osui/theme/dist/antd-vars-patch.less'), + require.resolve('@osui/theme/dist/less-functions-overrides.less'), + ], + }, + }, +}; + +module.exports = { + stories: [ + '../stories/**/*.stories.[tj]s{,x}', + '../stories/**/*.stories.mdx' + ], + addons: [ + { + name: '@storybook/addon-docs', + options: { + configureJSX: true, + babelOptions: {}, + sourceLoaderOptions: null, + }, + }, + '@storybook/addon-storysource', // https://github.com/storybookjs/storybook/tree/master/addons/storysource + '@storybook/addon-a11y/register', // https://github.com/storybookjs/storybook/tree/master/addons/a11y + '@storybook/addon-viewport/register', // https://github.com/storybookjs/storybook/tree/master/addons/viewport + ], + webpackFinal: async (config) => { + config.module.rules.push({ + test: /\.(js|jsx|ts|tsx)$/, + use: [ + { + loader: 'babel-loader', + options: getBabelConfig(), + }, + ], + }); + config.module.rules.push({ + test: /\.less$/, + loaders: [ + loaders.style(loaderOptions), + loaders.css(loaderOptions), + loaders.less(loaderOptions), + loaders.styleResources(loaderOptions), + ], + }); + // 如果使用css modules 请打开这个,并按需匹配css modules 文件 + // 这个同时打开了classnames loader, 可以用 import c from 'xxx.less' + // config.module.rules.push({ + // test: /\.less$/, + // loaders: [ + // loaders.classNames(), + // loaders.style(loaderOptions), + // loaders.cssModules(loaderOptions), + // loaders.less(loaderOptions), + // loaders.styleResources(loaderOptions), + // ], + // }); + + config.resolve.extensions.push('.ts', '.tsx'); + config.resolve.alias['@'] = path.resolve(__dirname, '../src'); + return config; + }, +}; diff --git a/packages/ui/markdown/.storybook/preview.js b/packages/ui/markdown/.storybook/preview.js new file mode 100644 index 00000000..de3eb1e4 --- /dev/null +++ b/packages/ui/markdown/.storybook/preview.js @@ -0,0 +1,16 @@ +import {addParameters} from '@storybook/react'; +import {DocsPage, DocsContainer} from '@storybook/addon-docs/blocks'; +import {INITIAL_VIEWPORTS} from '@storybook/addon-viewport'; +import '@osui/theme/dist/theme/vars.css'; + +addParameters({ + docs: { + container: DocsContainer, + page: DocsPage, + }, + viewport: { + viewports: { + ...INITIAL_VIEWPORTS + } + }, +}); diff --git a/packages/ui/markdown/babel.config.js b/packages/ui/markdown/babel.config.js new file mode 100644 index 00000000..d222841d --- /dev/null +++ b/packages/ui/markdown/babel.config.js @@ -0,0 +1,11 @@ +const {getTransformBabelConfig} = require('@reskript/config-babel'); + +const options = { + browserSupport: {}, + usage: 'build', + mode: 'production', + polyfill: false, + hostType: 'library', +}; + +module.exports = getTransformBabelConfig(options); diff --git a/packages/ui/markdown/package.json b/packages/ui/markdown/package.json new file mode 100644 index 00000000..009c7e71 --- /dev/null +++ b/packages/ui/markdown/package.json @@ -0,0 +1,65 @@ +{ + "name": "@osui/markdown", + "version": "0.1.0", + "description": "", + "main": "dist/index.js", + "scripts": { + "lint": "skr lint", + "build": "sh scripts/build.sh", + "storybook": "start-storybook -p 8700", + "clean": "rm -rf node_modules && rm -rf dist", + "test": "echo \"Error: no test specified\" && exit 1" + }, + "sideEffects": [ + "dist/*.less" + ], + "devDependencies": { + "@babel/cli": "^7.10.5", + "@reskript/cli": "^0.9.0", + "@reskript/cli-lint": "^0.9.9", + "@reskript/config-babel": "^0.9.0", + "@reskript/config-lint": "^0.12.3", + "@reskript/config-webpack": "^0.11.2", + "@storybook/addon-a11y": "^5.3.19", + "@storybook/addon-docs": "^5.3.19", + "@storybook/addon-storysource": "^5.3.19", + "@storybook/addon-viewport": "^5.3.19", + "@storybook/addons": "^5.3.19", + "@storybook/client-api": "^5.3.19", + "@storybook/react": "^5.3.19", + "@types/classnames": "^2.2.10", + "@types/react-dom": "^16.9.8", + "antd": "^4.4.0", + "babel-loader": "^8.1.0", + "cpy-cli": "^3.1.1", + "husky": "^4.2.5", + "prop-types": "^15.7.2", + "react": "^16.13.1", + "react-dom": "^16.13.1", + "react-is": "^16.13.1", + "typescript": "^3.9.6" + }, + "peerDependencies": { + "antd": ">=4.4.0", + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + }, + "repository": { + "type": "git", + "url": "https://gitee.com/yuxuanhuo/osui/tree/master" + }, + "author": "huoyuxuan", + "license": "MIT", + "husky": { + "hooks": { + "pre-commit": "skr lint ./src" + } + }, + "dependencies": { + "classnames": "^2.2.6", + "github-markdown-css": "^4.0.0", + "remark": "^12.0.1", + "remark-html": "^12.0.0", + "unist-util-visit": "^2.0.3" + } +} diff --git a/packages/ui/markdown/scripts/build.sh b/packages/ui/markdown/scripts/build.sh new file mode 100644 index 00000000..f1cf5724 --- /dev/null +++ b/packages/ui/markdown/scripts/build.sh @@ -0,0 +1,18 @@ +# export PATH=$NODEJS_BIN_LATEST:$YARN_BIN_LATEST:$PATH + +echo "node $(node -v)" +echo "npm $(npm -v)" +echo "yarn $(yarn -v)" + +rm -rf dist + +yarn install --production=false +# yarn test + +$(npm bin)/cpy '**/*' '!**/*.ts' '!**/*.tsx' '!**/*.js' '!**/*.jsx' ../tmp/ --cwd=src/ --parents +$(npm bin)/tsc +$(npm bin)/babel tmp --out-dir dist --ignore "src/**/*.test.js" --copy-files --source-maps + +rm -rf tmp + +echo "build success" diff --git a/packages/ui/markdown/src/Markdown.tsx b/packages/ui/markdown/src/Markdown.tsx new file mode 100644 index 00000000..a240b643 --- /dev/null +++ b/packages/ui/markdown/src/Markdown.tsx @@ -0,0 +1,56 @@ +/** + * @file Markdown 组件 + * @author zhangguoqing02 + */ +import * as React from 'react'; +import classNames from 'classnames'; +import remark from 'remark'; +import htmlPlugin from 'remark-html'; +import 'github-markdown-css/github-markdown.css'; +import './index.less'; + +const clsPrefix = 'osui-markdown'; + +interface Props { + content?: string; + className?: string; + plugins?: any[]; +} + +const getHtml = (content: string, plugins: any[]) => { + let processor = remark(); + plugins.forEach(plugin => { + processor = processor.use(plugin); + }); + return processor.use(htmlPlugin).processSync(content).toString(); +}; + +const Markdown: React.FC = ({content = '', className, plugins = []}) => { + /** + * NOTE 这里有 xss 攻击的可能性,但是以下两种方案均不成立 + * 1. const html = xss(...); + * 不成立的地方在于 - [ ] 会首先转译成 ,然后 xss 会把它 stringify + * 2. const html = ...processSync(xss(content))... + * 不成立的地方在于 markdown 的 code block ``` 中的内容会被首先转译,导致 > 转译为 > + * 这个问题首先是由安全部在效率云发现,而 iCode 作为内部系统,安全性较高一些 + * 故首先 revert 至不处理 xss + * 可能的解决方案如下 + * 利用 remark 找到会被解析为 html 的代码,对这些 html 运行 xss,然后重新 stringify 并得到 html + * 相关的 icafe 卡片 + * @see http://newicafe.baidu.com/issue/icode-12580/show?cid=5 + * @see http://newicafe.baidu.com/issue/icode-11691/show?cid=5 + */ + + const html = getHtml(content, plugins); + + return ( +
+ ); +}; + +export default Markdown; diff --git a/packages/ui/markdown/src/index.less b/packages/ui/markdown/src/index.less new file mode 100644 index 00000000..ed389ebf --- /dev/null +++ b/packages/ui/markdown/src/index.less @@ -0,0 +1,34 @@ +@osui-markdown-class-prefix: osui-alert; +.@{osui-markdown-class-prefix} { + .main { + padding: 20px; + + ul { + list-style: disc; + } + + ol { + list-style: decimal; + } + + ul, + ol { + ul { + list-style: circle; + } + + ol { + list-style: lower-latin; + } + } + + strong { + font-weight: 900; + } + + // 覆盖 prism 中的样式 + pre code::selection { + background-color: var(--ee-theme-primary-color); + } + } +} diff --git a/packages/ui/markdown/src/index.tsx b/packages/ui/markdown/src/index.tsx new file mode 100644 index 00000000..a240b643 --- /dev/null +++ b/packages/ui/markdown/src/index.tsx @@ -0,0 +1,56 @@ +/** + * @file Markdown 组件 + * @author zhangguoqing02 + */ +import * as React from 'react'; +import classNames from 'classnames'; +import remark from 'remark'; +import htmlPlugin from 'remark-html'; +import 'github-markdown-css/github-markdown.css'; +import './index.less'; + +const clsPrefix = 'osui-markdown'; + +interface Props { + content?: string; + className?: string; + plugins?: any[]; +} + +const getHtml = (content: string, plugins: any[]) => { + let processor = remark(); + plugins.forEach(plugin => { + processor = processor.use(plugin); + }); + return processor.use(htmlPlugin).processSync(content).toString(); +}; + +const Markdown: React.FC = ({content = '', className, plugins = []}) => { + /** + * NOTE 这里有 xss 攻击的可能性,但是以下两种方案均不成立 + * 1. const html = xss(...); + * 不成立的地方在于 - [ ] 会首先转译成 ,然后 xss 会把它 stringify + * 2. const html = ...processSync(xss(content))... + * 不成立的地方在于 markdown 的 code block ``` 中的内容会被首先转译,导致 > 转译为 > + * 这个问题首先是由安全部在效率云发现,而 iCode 作为内部系统,安全性较高一些 + * 故首先 revert 至不处理 xss + * 可能的解决方案如下 + * 利用 remark 找到会被解析为 html 的代码,对这些 html 运行 xss,然后重新 stringify 并得到 html + * 相关的 icafe 卡片 + * @see http://newicafe.baidu.com/issue/icode-12580/show?cid=5 + * @see http://newicafe.baidu.com/issue/icode-11691/show?cid=5 + */ + + const html = getHtml(content, plugins); + + return ( +
+ ); +}; + +export default Markdown; diff --git a/packages/ui/markdown/src/plugins/common.ts b/packages/ui/markdown/src/plugins/common.ts new file mode 100644 index 00000000..68859080 --- /dev/null +++ b/packages/ui/markdown/src/plugins/common.ts @@ -0,0 +1,13 @@ +/** + * @file 公用代码 + * @author zhangguoqing02 + */ + +// 匹配非协议名开头的字符串,说明此字符串不用做转换 +// `(?!p)` 是零宽负向先行断言,表示接下来的字符串都不与 p 匹配 +// 此处如 `http://`, `ftp://` 或者 `//` 开头的字符串均不能匹配 +export const REGEXP_FILE_PATH = /^(?!(\w+:)?\/\/)/; + +// 通过 url 中 `:` 前面的部分,判断当前是文件位置还是文件夹位置 +// 如 `xxx/blob/` 就是文件位置,而 `xxx/tree/` 就是文件夹位置 +export const REGEXP_IS_FILE = /\/(?:blob|edit|create)\/[^/]+:/; diff --git a/packages/ui/markdown/src/plugins/createImagePlugin.ts b/packages/ui/markdown/src/plugins/createImagePlugin.ts new file mode 100644 index 00000000..ea3d605e --- /dev/null +++ b/packages/ui/markdown/src/plugins/createImagePlugin.ts @@ -0,0 +1,46 @@ +/** + * @file remark image 相对路径替换插件 + * @author zhangguoqing02 + */ +import {dirname, join} from 'path'; +import {Node} from 'unist'; +import visit from 'unist-util-visit'; +import {REGEXP_FILE_PATH, REGEXP_IS_FILE} from './common'; + +interface Params { + name: string; + ref: string; + path?: string; +} + +const getICodeRawFilePath = (relativePath: string, params: Params) => { + const {name, ref, path = ''} = params; + const targetPath = (() => { + if (relativePath.startsWith('/')) { + return relativePath.slice(1); + } + else { + const isFile = path && REGEXP_IS_FILE.test(window.location.pathname); + + // 如果 relativePath 不以 / 开头,说明它是相对路径 + return join( + isFile ? dirname(path) : path, + relativePath + ); + } + })(); + + return `/rest/files/blob/get/fileraw?repo=${name}&commit=${ref}&path=${targetPath}`; +}; + +const createImagePlugin = (params: Params) => () => (tree: Node) => { + visit(tree, 'image', node => { + const url = node.url as string; + if (REGEXP_FILE_PATH.test(url)) { + // eslint-disable-next-line no-param-reassign + node.url = getICodeRawFilePath(url, params); + } + }); +}; + +export default createImagePlugin; diff --git a/packages/ui/markdown/src/plugins/createLinkPlugin.ts b/packages/ui/markdown/src/plugins/createLinkPlugin.ts new file mode 100644 index 00000000..895fad97 --- /dev/null +++ b/packages/ui/markdown/src/plugins/createLinkPlugin.ts @@ -0,0 +1,47 @@ +/** + * @file remark link 相对路径替换插件 + * @author zhangguoqing02 + */ +import {dirname, join} from 'path'; +import {Node} from 'unist'; +import visit from 'unist-util-visit'; +import {REGEXP_FILE_PATH, REGEXP_IS_FILE} from './common'; + +interface Params { + name: string; + ref: string; + path?: string; +} + +// 根据相对路径获取兼容 iCode 的绝对路径 +const getICodePath = (relativePath: string, params: Params) => { + const {name, ref, path = ''} = params; + const targetPrefix = `/repos/${name}/blob/${ref}:`; + + // 如果 relativePath 是 / 开头表示的绝对路径,如:/src/index.js, + // 则直接返回从仓库根目录开始的文件路径 + if (relativePath.startsWith('/')) { + return targetPrefix + relativePath.slice(1); + } + + const isFile = path && REGEXP_IS_FILE.test(window.location.pathname); + const targetPath = join( + isFile ? dirname(path) : path, + relativePath + ); + + return targetPrefix + targetPath; +}; + +// 传入 Router 中的参数 params +const createLinkPlugin = (params: Params) => () => (tree: Node) => { + visit(tree, 'link', node => { + const url = node.url as string; + if (REGEXP_FILE_PATH.test(url)) { + // eslint-disable-next-line no-param-reassign + node.url = getICodePath(url, params); + } + }); +}; + +export default createLinkPlugin; diff --git a/packages/ui/markdown/src/plugins/index.ts b/packages/ui/markdown/src/plugins/index.ts new file mode 100644 index 00000000..a1549b85 --- /dev/null +++ b/packages/ui/markdown/src/plugins/index.ts @@ -0,0 +1,7 @@ +/** + * @file 导出 remark 的插件 + * @author zhangguoqing02 + */ + +export {default as createImagePlugin} from './createImagePlugin'; +export {default as createLinkPlugin} from './createLinkPlugin'; diff --git a/packages/ui/markdown/stories/index.stories.tsx b/packages/ui/markdown/stories/index.stories.tsx new file mode 100644 index 00000000..d740904d --- /dev/null +++ b/packages/ui/markdown/stories/index.stories.tsx @@ -0,0 +1,26 @@ +import * as React from 'react'; +import Markdown from '../src/Markdown'; + +export default { + title: 'OSUI-Markdown', +}; + +const content = ` +# Writing template strings in Markdown + +## Writing template strings in Markdown + +adsfasdfasdf + +asdfasdf + +\`\`\`jsx + +\`\`\` +`; + +export const Demo = () => ( +
+ +
+); diff --git a/packages/ui/markdown/tsconfig.json b/packages/ui/markdown/tsconfig.json new file mode 100644 index 00000000..3a24a511 --- /dev/null +++ b/packages/ui/markdown/tsconfig.json @@ -0,0 +1,8 @@ +{ + "extends": "../../../tsconfig.json", + "compilerOptions": { + "rootDir": "./src", + "outDir": "./tmp" + }, + "include": ["src"] +} diff --git a/yarn.lock b/yarn.lock index c0b9fcb8..e099dd58 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7833,6 +7833,11 @@ gitconfiglocal@^1.0.0: dependencies: ini "^1.3.2" +github-markdown-css@^4.0.0: + version "4.0.0" + resolved "https://registry.npm.taobao.org/github-markdown-css/download/github-markdown-css-4.0.0.tgz#be9f4caf7a389228d4c368336260ffc909061f35" + integrity sha1-vp9Mr3o4kijUw2gzYmD/yQkGHzU= + github-slugger@^1.0.0: version "1.3.0" resolved "http://registry.npm.baidu-int.com/github-slugger/-/github-slugger-1.3.0.tgz#9bd0a95c5efdfc46005e82a906ef8e2a059124c9" @@ -8207,6 +8212,11 @@ hast-util-from-parse5@^6.0.0: vfile "^4.0.0" web-namespaces "^1.0.0" +hast-util-is-element@^1.0.0: + version "1.0.4" + resolved "https://registry.npm.taobao.org/hast-util-is-element/download/hast-util-is-element-1.0.4.tgz#059090a05cc02e275df1ad02caf8cb422fcd2e02" + integrity sha1-BZCQoFzALidd8a0CyvjLQi/NLgI= + hast-util-parse-selector@^2.0.0: version "2.2.4" resolved "http://registry.npm.baidu-int.com/hast-util-parse-selector/-/hast-util-parse-selector-2.2.4.tgz#60c99d0b519e12ab4ed32e58f150ec3f61ed1974" @@ -8228,6 +8238,29 @@ hast-util-raw@6.0.0: xtend "^4.0.0" zwitch "^1.0.0" +hast-util-sanitize@^3.0.0: + version "3.0.0" + resolved "https://registry.npm.taobao.org/hast-util-sanitize/download/hast-util-sanitize-3.0.0.tgz#4cdc26b2991b3bf90ee74b5c932e14d907549312" + integrity sha1-TNwmspkbO/kO50tcky4U2QdUkxI= + dependencies: + xtend "^4.0.0" + +hast-util-to-html@^7.0.0: + version "7.1.1" + resolved "https://registry.npm.taobao.org/hast-util-to-html/download/hast-util-to-html-7.1.1.tgz#39818b8bbfcb8eaa87846a120b3875487b27d094" + integrity sha1-OYGLi7/LjqqHhGoSCzh1SHsn0JQ= + dependencies: + ccount "^1.0.0" + comma-separated-tokens "^1.0.0" + hast-util-is-element "^1.0.0" + hast-util-whitespace "^1.0.0" + html-void-elements "^1.0.0" + property-information "^5.0.0" + space-separated-tokens "^1.0.0" + stringify-entities "^3.0.1" + unist-util-is "^4.0.0" + xtend "^4.0.0" + hast-util-to-parse5@^6.0.0: version "6.0.0" resolved "http://registry.npm.baidu-int.com/hast-util-to-parse5/-/hast-util-to-parse5-6.0.0.tgz#1ec44650b631d72952066cea9b1445df699f8479" @@ -8239,6 +8272,11 @@ hast-util-to-parse5@^6.0.0: xtend "^4.0.0" zwitch "^1.0.0" +hast-util-whitespace@^1.0.0: + version "1.0.4" + resolved "https://registry.npm.taobao.org/hast-util-whitespace/download/hast-util-whitespace-1.0.4.tgz#e4fe77c4a9ae1cb2e6c25e02df0043d0164f6e41" + integrity sha1-5P53xKmuHLLmwl4C3wBD0BZPbkE= + hastscript@^5.0.0: version "5.1.2" resolved "http://registry.npm.baidu-int.com/hastscript/-/hastscript-5.1.2.tgz#bde2c2e56d04c62dd24e8c5df288d050a355fb8a" @@ -10071,7 +10109,7 @@ mdast-util-definitions@^3.0.0: dependencies: unist-util-visit "^2.0.0" -mdast-util-to-hast@9.1.0: +mdast-util-to-hast@9.1.0, mdast-util-to-hast@^9.0.0: version "9.1.0" resolved "http://registry.npm.baidu-int.com/mdast-util-to-hast/-/mdast-util-to-hast-9.1.0.tgz#6ef121dd3cd3b006bf8650b1b9454da0faf79ffe" integrity sha1-bvEh3TzTsAa/hlCxuUVNoPr3n/4= @@ -13304,6 +13342,16 @@ remark-footnotes@1.0.0: resolved "http://registry.npm.baidu-int.com/remark-footnotes/-/remark-footnotes-1.0.0.tgz#9c7a97f9a89397858a50033373020b1ea2aad011" integrity sha1-nHqX+aiTl4WKUAMzcwILHqKq0BE= +remark-html@^12.0.0: + version "12.0.0" + resolved "https://registry.npm.taobao.org/remark-html/download/remark-html-12.0.0.tgz#f39d2a5e173cce777981cb4171b4ea860313d72a" + integrity sha1-850qXhc8znd5gctBcbTqhgMT1yo= + dependencies: + hast-util-sanitize "^3.0.0" + hast-util-to-html "^7.0.0" + mdast-util-to-hast "^9.0.0" + xtend "^4.0.1" + remark-mdx@1.6.16: version "1.6.16" resolved "http://registry.npm.baidu-int.com/remark-mdx/-/remark-mdx-1.6.16.tgz#13ee40ad0614a1cc179aca3604d7f1b79e498a2f" @@ -13376,9 +13424,9 @@ remark-stringify@^8.0.0: unherit "^1.0.4" xtend "^4.0.1" -remark@^12.0.0: +remark@^12.0.0, remark@^12.0.1: version "12.0.1" - resolved "http://registry.npm.baidu-int.com/remark/-/remark-12.0.1.tgz#f1ddf68db7be71ca2bad0a33cd3678b86b9c709f" + resolved "https://registry.npm.taobao.org/remark/download/remark-12.0.1.tgz?cache=0&sync_timestamp=1594993800845&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fremark%2Fdownload%2Fremark-12.0.1.tgz#f1ddf68db7be71ca2bad0a33cd3678b86b9c709f" integrity sha1-8d32jbe+ccorrQozzTZ4uGuccJ8= dependencies: remark-parse "^8.0.0" @@ -14393,7 +14441,7 @@ string_decoder@~1.1.1: dependencies: safe-buffer "~5.1.0" -stringify-entities@^3.0.0: +stringify-entities@^3.0.0, stringify-entities@^3.0.1: version "3.0.1" resolved "http://registry.npm.baidu-int.com/stringify-entities/-/stringify-entities-3.0.1.tgz#32154b91286ab0869ab2c07696223bd23b6dbfc0" integrity sha1-MhVLkShqsIaassB2liI70jttv8A= @@ -15290,9 +15338,9 @@ unist-util-visit-parents@^3.0.0: "@types/unist" "^2.0.0" unist-util-is "^4.0.0" -unist-util-visit@2.0.3, unist-util-visit@^2.0.0: +unist-util-visit@2.0.3, unist-util-visit@^2.0.0, unist-util-visit@^2.0.3: version "2.0.3" - resolved "http://registry.npm.baidu-int.com/unist-util-visit/-/unist-util-visit-2.0.3.tgz#c3703893146df47203bb8a9795af47d7b971208c" + resolved "https://registry.npm.taobao.org/unist-util-visit/download/unist-util-visit-2.0.3.tgz?cache=0&sync_timestamp=1594459623892&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Funist-util-visit%2Fdownload%2Funist-util-visit-2.0.3.tgz#c3703893146df47203bb8a9795af47d7b971208c" integrity sha1-w3A4kxRt9HIDu4qXla9H17lxIIw= dependencies: "@types/unist" "^2.0.0" -- Gitee From ab9036c43e3f46428d7676527fe066349de804b8 Mon Sep 17 00:00:00 2001 From: yuxuanhuo Date: Sat, 8 Aug 2020 11:27:39 +0800 Subject: [PATCH 2/4] =?UTF-8?q?fix(alert):=20=E8=B0=83=E6=95=B4alert=20sto?= =?UTF-8?q?rybook=E5=BC=95=E5=85=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/ui/alert/stories/index.stories.tsx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/ui/alert/stories/index.stories.tsx b/packages/ui/alert/stories/index.stories.tsx index 8aa058aa..b3f38414 100644 --- a/packages/ui/alert/stories/index.stories.tsx +++ b/packages/ui/alert/stories/index.stories.tsx @@ -1,7 +1,6 @@ import * as React from 'react'; -import {Alert} from 'antd'; import {CheckCircleFilled, InfoCircleFilled} from '@ant-design/icons'; -import '@osui/theme/dist/theme/vars.css'; +import Alert from '../src'; export default { title: 'OSUI-Alert', -- Gitee From 5ef63db89de00ff92a703e9e99d5e6aa1ed3bfde Mon Sep 17 00:00:00 2001 From: yuxuanhuo Date: Mon, 10 Aug 2020 10:21:41 +0800 Subject: [PATCH 3/4] =?UTF-8?q?feat(ui):=20ui=E5=8C=85=E5=AF=BC=E5=87=BA?= =?UTF-8?q?=E6=89=80=E6=9C=89=E7=BB=84=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/ui/ui/.eslintrc.js | 7 ++++++ packages/ui/ui/README.md | 17 ++++++++++++++ packages/ui/ui/package.json | 40 +++++++++++++++++++++++++++++++++ packages/ui/ui/scripts/build.sh | 20 +++++++++++++++++ packages/ui/ui/src/index.ts | 2 ++ packages/ui/ui/tsconfig.json | 8 +++++++ 6 files changed, 94 insertions(+) create mode 100644 packages/ui/ui/.eslintrc.js create mode 100644 packages/ui/ui/README.md create mode 100644 packages/ui/ui/package.json create mode 100644 packages/ui/ui/scripts/build.sh create mode 100644 packages/ui/ui/src/index.ts create mode 100644 packages/ui/ui/tsconfig.json diff --git a/packages/ui/ui/.eslintrc.js b/packages/ui/ui/.eslintrc.js new file mode 100644 index 00000000..5928ca1d --- /dev/null +++ b/packages/ui/ui/.eslintrc.js @@ -0,0 +1,7 @@ +module.exports = { + extends: require.resolve('@reskript/config-lint/config/eslint'), + rules: { + 'react/jsx-uses-react': 'error', + }, + root: true, +}; diff --git a/packages/ui/ui/README.md b/packages/ui/ui/README.md new file mode 100644 index 00000000..7a4de3b2 --- /dev/null +++ b/packages/ui/ui/README.md @@ -0,0 +1,17 @@ +# @osui/ui + +## 安装说明 + +``` +yarn install @osui/ui +``` + +named import 组件 + +``` +import {Alert} from '@osui/ui'; +``` + +## 说明 + +在发包之前手动build,不与yarn build统一build,因为依赖可能还没build完 diff --git a/packages/ui/ui/package.json b/packages/ui/ui/package.json new file mode 100644 index 00000000..0073704e --- /dev/null +++ b/packages/ui/ui/package.json @@ -0,0 +1,40 @@ +{ + "name": "@osui/ui", + "version": "0.1.0", + "description": "", + "main": "dist/index.js", + "module": "dist/index.js", + "scripts": { + "lint": "skr lint", + "build": "echi \"release manual build\"", + "build-all": "sh scripts/build.sh", + "storybook": "start-storybook -p 8700", + "clean": "rm -rf node_modules && rm -rf dist", + "test": "echo \"Error: no test specified\" && exit 1" + }, + "sideEffects": [ + "dist/**/*.less" + ], + "devDependencies": { + "cpy-cli": "^3.1.1", + "typescript": "^3.9.6" + }, + "peerDependencies": { + "antd": ">=4.4.0", + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + }, + "repository": { + "type": "git", + "url": "https://gitee.com/yuxuanhuo/osui/tree/master" + }, + "author": "huoyuxuan", + "license": "MIT", + "husky": { + "hooks": { + "pre-commit": "skr lint ./src" + } + }, + "dependencies": { + } +} diff --git a/packages/ui/ui/scripts/build.sh b/packages/ui/ui/scripts/build.sh new file mode 100644 index 00000000..c339490e --- /dev/null +++ b/packages/ui/ui/scripts/build.sh @@ -0,0 +1,20 @@ +# export PATH=$NODEJS_BIN_LATEST:$YARN_BIN_LATEST:$PATH + +rm -rf dist + +ROOT=../ # 在packages/ui/ui 目录下执行本脚本, ROOT目录应该是packages/ui/ui +EXCLUDE_FOLDER='docs$|ui$|\/$' # docs, ui, / 目录都要过滤掉 + +# 注意:需要提前build好各个包 +# 把各个包的dist目录复制到 dist +find $ROOT -maxdepth 1 -type d | grep -v -E $EXCLUDE_FOLDER | while IFS= read -r d; do + COMPONENT=`echo $d | sed 's/..\///'` + echo "building $COMPONENT" + mkdir -p dist/$COMPONENT + cp -R $d/dist/* dist/$COMPONENT +done + +# build index +$(npm bin)/tsc + +echo "build success" diff --git a/packages/ui/ui/src/index.ts b/packages/ui/ui/src/index.ts new file mode 100644 index 00000000..d5ce82cc --- /dev/null +++ b/packages/ui/ui/src/index.ts @@ -0,0 +1,2 @@ +// @ts-nocheck +export {default as Alert} from './alert'; diff --git a/packages/ui/ui/tsconfig.json b/packages/ui/ui/tsconfig.json new file mode 100644 index 00000000..e9eb0ce1 --- /dev/null +++ b/packages/ui/ui/tsconfig.json @@ -0,0 +1,8 @@ +{ + "extends": "../../../tsconfig.json", + "compilerOptions": { + "rootDir": "./src", + "outDir": "./dist" + }, + "include": ["src"] +} -- Gitee From 651c61d7daa246868ddfbf5cb8e62dcd8c86fa24 Mon Sep 17 00:00:00 2001 From: yuxuanhuo Date: Mon, 10 Aug 2020 10:22:35 +0800 Subject: [PATCH 4/4] =?UTF-8?q?docs:=20=E7=BB=84=E4=BB=B6=E5=BC=80?= =?UTF-8?q?=E5=8F=91=E6=96=87=E6=A1=A3,=20=E5=88=9B=E5=BB=BA=E7=BB=84?= =?UTF-8?q?=E4=BB=B6=E8=84=9A=E6=9C=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 62 +++++++++++++++++++++++++++----- package.json | 3 +- packages/ui/alert/README.md | 30 ++++++++++++++++ packages/ui/markdown/README.md | 30 ++++++++++++++++ scripts/new-component.sh | 21 +++++++++++ templates/component/README.md | 30 ++++++++++++++++ templates/component/package.json | 2 +- 7 files changed, 168 insertions(+), 10 deletions(-) create mode 100644 packages/ui/alert/README.md create mode 100644 packages/ui/markdown/README.md create mode 100644 scripts/new-component.sh create mode 100644 templates/component/README.md diff --git a/README.md b/README.md index a9690b93..c5a550b5 100644 --- a/README.md +++ b/README.md @@ -1,22 +1,18 @@ # osui -#### 介绍 +## 介绍 osui是基于开源组件库(例如antd),封装的一套业务性质的组件 -#### 软件架构 +## 软件架构 采用monorepo模式, 快速了解monorepo基本用法:https://zhuanlan.zhihu.com/p/71385053 可以参考这篇文章快速了解monorepo的基本用法 +## 使用说明 -#### 安装教程 - -#### 使用说明 - - -#### 参与贡献 +## 参与贡献 1. Fork 本仓库 (可以不fork直接拉分支) 2. 新建 feat_xxx 或者 fix_xxx 分支,对分支名尽量和 convertional commits(见下面)保持一致 @@ -35,5 +31,55 @@ osui是基于开源组件库(例如antd),封装的一套业务性质的组 最短操作路径: yarn commit -> 选feat或者fix -> 回车跳过 -> 输入你干了啥概述(字数限制) -> 回车跳过 -> 回车(没有breaking change的话) -> 回车 +熟练之后,可以用git commit 提交符合conventional commits的message。 + 4. 新建 Pull Request +## 开发说明 + +### 准备工作 + +1. clone 代码库到本地 +2. 执行: + +``` +yarn +``` + +### 如何开发一个新组件 + +#### 创建新组件 + +执行 + +``` +yarn new-component 组件名 +``` + +该命令会在`packages/ui/`目录下创建`组件名`目录,并将`package.json`中的`name`改为`@osui/组件名` + +(模板在`templates/component`下, 也可以手动复制到`packages/ui`目录下,但要记得替换`组件名`相关内容) + +**注意:** + +组件名小写字母,多个词用`-`分割,例如`app-layout` + +##### `yarn new-component` 不好用? + +手动方式: + +1. 把`templates/component`复制到`packages/ui`目录下,重命名成新建组件 +2. 将`package.json`和`README.md`中的`{componentName}`换成新命名的组件 +3. 将`{CapComponentName}`换成大写字母开头,camelcase的形式 + +#### 开发新组件 + +组件开发代码在组件文件夹`src/`目录下。开发时可以通过storybook进行调试: + +1. 在`stories/`目录下创建`index.stories.tsx` +2. 从`src`中引入组件, 仿照`alert` +3. `yarn` +4. `yarn storybook` + +示例: 参考`alert`组件 + diff --git a/package.json b/package.json index 20f08045..504f5bed 100644 --- a/package.json +++ b/package.json @@ -15,7 +15,8 @@ "storybook": "start-storybook -p 8777 -s public", "prepack": "yarn build", "version": "lerna version --conventional-commits --no-push", - "commit": "git-cz" + "commit": "git-cz", + "new-component": "sh scripts/new-component.sh" }, "devDependencies": { "@commitlint/cli": "^9.1.1", diff --git a/packages/ui/alert/README.md b/packages/ui/alert/README.md new file mode 100644 index 00000000..41fbd482 --- /dev/null +++ b/packages/ui/alert/README.md @@ -0,0 +1,30 @@ +# @osui/alert + +## 安装说明 + +### 方式一 + +单包使用 + +``` +yarn install @osui/alert +``` + +``` +import Alert from '@osui/alert' +``` + +### 方式二 + +从 `@osui/alert` 统一引入 + +``` +yarn install @osui/alert +``` + +``` +import {Alert} from '@osui/alert'; +``` + + + diff --git a/packages/ui/markdown/README.md b/packages/ui/markdown/README.md new file mode 100644 index 00000000..df00cdf4 --- /dev/null +++ b/packages/ui/markdown/README.md @@ -0,0 +1,30 @@ +# @osui/markdown + +## 安装说明 + +### 方式一 + +单包使用 + +``` +yarn install @osui/markdown +``` + +``` +import Markdown from '@osui/markdown' +``` + +### 方式二 + +从 `@osui/markdown` 统一引入 + +``` +yarn install @osui/markdown +``` + +``` +import {Markdown} from '@osui/markdown'; +``` + + + diff --git a/scripts/new-component.sh b/scripts/new-component.sh new file mode 100644 index 00000000..05735b40 --- /dev/null +++ b/scripts/new-component.sh @@ -0,0 +1,21 @@ +#!/bin/bash +# @author huoyuxuan +# 用于根据模板创建组件 +set -e + +COMPONENT_NAME=$1 + +echo "create $COMPONENT_NAME" + +# 复制template +cp -R templates/component packages/ui/$COMPONENT_NAME + +# 把组件包名替换 +find packages/ui/$COMPONENT_NAME -type f -exec sed -i "s/{componentName}/${COMPONENT_NAME}/g" {} \; + +# 把组件名替换成第一个字母大写, 把 - 换成大小写 +CAP_COMPONENT_NAME=`echo ${COMPONENT_NAME^} | sed -E 's/-(.)/\U\1/g'` +echo $CAP_COMPONENT_NAME +find packages/ui/$COMPONENT_NAME -type f -exec sed -i "s/{CapComponentName}/${CAP_COMPONENT_NAME}/g" {} \; + + diff --git a/templates/component/README.md b/templates/component/README.md new file mode 100644 index 00000000..4ec47dab --- /dev/null +++ b/templates/component/README.md @@ -0,0 +1,30 @@ +# @osui/{componentName} + +## 安装说明 + +### 方式一 + +单包使用 + +``` +yarn install @osui/{componentName} +``` + +``` +import {CapComponentName} from '@osui/{componentName}' +``` + +### 方式二 + +从 `@osui/ui` 统一引入 + +``` +yarn install @osui/ui +``` + +``` +import {{CapComponentName}} from '@osui/ui'; +``` + + + diff --git a/templates/component/package.json b/templates/component/package.json index 1dfede0d..9a288319 100644 --- a/templates/component/package.json +++ b/templates/component/package.json @@ -1,5 +1,5 @@ { - "name": "@osui/alert", + "name": "@osui/{componentName}", "version": "0.1.0", "description": "", "main": "dist/index.js", -- Gitee