怎么写一个react组件库_写一个react组件库 - CSDN
精华内容
参与话题
  • 2019独角兽企业重金招聘Python工程师标准>>> ...
    <!DOCTYPE html>
    <html lang="en">
    
    <head>
      <meta charset="UTF-8">
      <meta name="viewport" content="width=device-width, initial-scale=1.0">
      <meta http-equiv="X-UA-Compatible" content="ie=edge">
      <title>Document</title>
    </head>
    
    <body>
      <div class='wrapper'>
    
      </div>
    </body>
    
    <script>
      const createDOMFromString = (domString) => {
        const div = document.createElement('div')
        div.innerHTML = domString
        return div
      }
    
      class Component {
        constructor(props = {}) {
          this.props = props;
        }
        setState(state) {
          const oldEl = this.el;
          this.state = state;
          this._renderDOM()
          if (this.onStateChange) this.onStateChange(oldEl, this.el);
        }
    
    
        _renderDOM() {
          this.el = createDOMFromString(this.render());
          if (this.onClick) {
            this.el.addEventListener('click', this.onClick.bind(this), false);
          }
          return this.el;
        }
      }
    
      class LikeButton extends Component {
    
        constructor(props) {
          super(props);
          this.state = {
            isLiked: false
          };
        }
    
    
    
    
    
        onClick() {
          this.setState({
            isLiked: !this.state.isLiked
          })
        }
    
    
    
        render() {
          return `
          <button class='like-btn' style="background-color:${this.props.bgColor}">
           <span class='like-text'>${this.state.isLiked ? '取消' : '点赞'}</span>
           <span>?</span>
           </button>`
        }
      }
    
      class RedBlueButton extends Component {
        constructor(props) {
          super(props);
          this.state = {
            color: 'red'
          }
        }
    
        onClick() {
          this.setState({
            color: 'blue'
          })
        }
    
        render() {
          return ` <div style='color: ${this.state.color};'>${this.state.color}</div>`;
        }
      }
    
      const mount = (component, wrapper) => {
        wrapper.appendChild(component._renderDOM());
        component.onStateChange = (oldEl, newEl) => {
          wrapper.insertBefore(newEl, oldEl);
          wrapper.removeChild(oldEl);
        }
      }
    
      const wrapper = document.querySelector('.wrapper')
      mount(new LikeButton({
        bgColor: 'red'
      }), wrapper)
      mount(new LikeButton(), wrapper)
      mount(new RedBlueButton(), wrapper)
    </script>
    
    </html>

     

    转载于:https://my.oschina.net/lilugirl2005/blog/3036263

    展开全文
  • react 组件库搭建记录

    2019-06-10 15:14:38
    、 初始化项目 初始化项目 git init npm init -y 复制代码 创建 .gitignore node_modules coverage dist es lib package-lock.json 复制代码二、 基于 storybook 搭建开发测试环境 2.1 项目快速搭建 参考...

    一、 初始化项目

    • 初始化项目
    git init
    npm init -y
    复制代码
    • 创建 .gitignore
    node_modules
    coverage
    dist
    es
    lib
    
    package-lock.json
    复制代码

    二、 基于 storybook 搭建开发测试环境

    2.1 项目快速搭建

    npx -p @storybook/cli sb init --type react
    复制代码
    • 当前目录介绍

    stories: storybook 主目录相当于一般项目下的 src 目录 index.stories.js: storybook 的入口文件 .storybook: storybook 配置目录

    ├── .gitignore
    ├── package.json
    ├── package-lock.json
    ├── stories
    │   ├── index.stories.js
    └── .storybook
        ├── addons.js
        └── config.js
    复制代码

    2.2 目录架构调整

    stories/pages 目录,用于存放 storybook 所有相关页面, stories/config.js 作为页面配置在入口文件 index.stories.js 中进行引用并根据该配置渲染出所有相关页面,同时假设我们 storybook 有一级目录 基本 且目录下有 介绍 页面, 那么对应的添加 stories/pages/base/Introduce 目录,目录下 api.md 用于编写对应组件 api 文档(当然介绍页面并没有 api 文档),index.css 作为当前页面的样式文件,index.jsx 则作为当前页面的入口文件, subpage 则用于存放页面的子页面。

    ├── .gitignore
    ├── package.json
    ├── package-lock.json
    ├── stories
    │   ├── config.js
    │   ├── index.stories.js
    │   └── pages
    │       └── base
    │           └── Introduce
    │               ├── api.md
    │               ├── index.css
    │               ├── index.jsx
    │               └── subpage
    └── .storybook
        ├── addons.js
        └── config.js
    复制代码

    2.3 编写测试代码

    • 编写 stories/pages/base/Introduce/index.jsx
    import React from 'react';
    import './index.css';
    export default () => {
      return (
        <div>
          react 组件介绍
        </div>
      );
    };
    
    复制代码
    • 编写 stories/config.js
    import Introduce from './pages/base/Introduce';
    export default [
      {
        title: '介绍',
        module: '基本',
        component: Introduce
      }
    ];
    复制代码
    • 编写 stories/index.stories.js
    import React from 'react';
    import { storiesOf } from '@storybook/react';
    import config from './config';
    
    config.forEach( v => (storiesOf(v.module, module).add(v.title, v.component)));
    复制代码
    • 添加 npm 脚本
    {
      "scripts": {
    +  "start": "npm run storybook",
        "storybook": "start-storybook -p 8080",
        "build-storybook": "build-storybook"
      },
    }
    复制代码
    • 执行脚本 npm start

    2.4 自定义 webpack 配置

    storybook 虽然有自己的 webpack 配置, 但是显然无法满足一些复杂的情况, 在 storybook 中可通过创建 .storybook/webpack.config.js 来自定义 webpack 配置。

    • 下载相关依赖
    # 1. webpack 安装
    npm install webpack webpack-cli -D
    
    # 2. babel-loader 相关依赖包安装
    npm install babel-loader @babel/core -D
    
    #  3. babel 相关预设依赖包安装
    npm install @babel/preset-env @babel/preset-react -D
    
    #  4. babel 相关插件依赖包安装
    npm install @babel/plugin-transform-runtime -D
    npm install @babel/plugin-proposal-decorators -D
    npm install @babel/plugin-transform-async-to-generator -D
    
    # 5. eslint-loader 相关依赖
    npm install eslint eslint-loader -D
    
    # 6. eslint 相关插件安装
    npm install babel-eslint eslint-plugin-babel eslint-plugin-react -D
    
    # 7. 样式文件加载配置所需依赖
    npm install style-loader css-loader sass-loader node-sass postcss-loader -D
    
    # 8. postcss-loader 相关插件依赖包安装
    npm install autoprefixer -D
    
    # 9. 图片字体加载所需依赖
    npm install url-loader file-loader -D
    
    # 10. 文本文件加载所需依赖
    npm install raw-loader -D
    复制代码
    • 创建 .storybook/webpack.config.js 文件
    const path = require('path');
    const webpack = require('webpack');
    
    // 路径别名
    const alias = {};
    
    module.exports = {
      mode: 'production',
      module: {
        rules: [
          { // js 模块打包
            test: /\.(mjs|js|jsx)$/,
            exclude: [ path.resolve(__dirname, 'node_modules') ],
            use: ['babel-loader', 'eslint-loader']
          }, { // 样式文件打包
            test: /\.(css|scss)$/,
            use: [
              'style-loader', {
                loader: 'css-loader',
                options: {
                  sourceMap: false,
                }
              }, {
                loader: 'postcss-loader',
                options: { javascriptEnabled: true, sourceMap: false },
              }, {
                loader: 'sass-loader'
              }
            ],
          }, { // 文字图片打包
            test: /\.(png|jpg|gif|woff|svg|eot|ttf)$/,
            use: [{
              loader: 'url-loader',
              options: {
                limit: 10 * 1000,
              }
            }]
          }, { // 文本文件加载(后期可能需要引入 markdown 文件)
            test: /\.(txt|md)$/,
            use: 'raw-loader',
          },
        ]
      },
    
      plugins: [
        new webpack.ContextReplacementPlugin(/moment[/\\]locale$/, /zh-cn|en-gb/),
      ],
    
      // 解析模块
      resolve: {
        alias,
        // 自动解析确定的扩展
        extensions: ['.mjs', '.js', '.jsx'],
      },
    }
    复制代码
    • 项目下新增 babel 配置文件 .babelrc
    {
      "plugins": [
        // 为api提供沙箱的垫片方案,不会污染全局的 api
        ["@babel/plugin-transform-runtime"],
        // 修饰器
        ["@babel/plugin-proposal-decorators", { "legacy": true }],
        // asyn await 支持
        ["@babel/plugin-transform-async-to-generator"]
      ],
      "presets": ["@babel/preset-react", "@babel/preset-env"]
    }
    复制代码
    • 项目下新增 postcss 配置文件 postcss.config.js
    module.exports = {
      plugins: [
        require("autoprefixer")({
          browsers: [
            "last 2 versions",
            "Android >= 4.4",
            "Firefox ESR",
            "not ie < 9",
            "ff >= 30",
            "chrome >= 34",
            "safari >= 6",
            "opera >= 12.1",
            "ios >= 6"
          ]
        })
      ]
    };
    复制代码
    • 项目下新增 eslint 配置文件 .eslintrc.js .eslintignore
    // .eslintrc.js 配置文件
    module.exports = {
      parserOptions: {
        ecmaVersion: 8,
        sourceType: "module",
        ecmaFeatures: {
          jsx: true
        }
      },
      parser: "babel-eslint",
      plugins: ["babel", "react"],
      extends: "eslint:recommended",
      env: {
        es6: true,
        browser: true,
        commonjs: true
      },
      globals: {
        process: true,
        describe: true,
        it: true,
        __dirname: true,
        expect: true,
        jest: true,
        beforeAll: true,
        afterEach: true
      },
      rules: {
        "object-shorthand": "error",
        "generator-star-spacing": ["error", "after"],
        camelcase: ["error", { properties: "never" }],
        eqeqeq: ["error", "smart"],
        "linebreak-style": ["error", "unix"],
        "new-cap": "error",
        "no-array-constructor": "error",
        "no-lonely-if": "error",
        "no-loop-func": "error",
        "no-param-reassign": "error",
        "no-sequences": "error",
        "no-shadow-restricted-names": "error",
        "no-unneeded-ternary": "error",
        "no-unused-expressions": "error",
        "no-unused-vars": "off",
        "no-use-before-define": ["error", "nofunc"],
        "no-var": "error",
        "prefer-arrow-callback": "error",
        "prefer-spread": "error",
        "prefer-template": "error",
        "wrap-iife": ["error", "inside"],
        yoda: ["error", "never"],
        "react/jsx-uses-react": "error",
        "react/jsx-uses-vars": "error",
        "react/jsx-no-undef": ["error", { allowGlobals: true }],
        "react/jsx-no-bind": ["error", { allowArrowFunctions: true }],
        "react/jsx-key": "error",
        "react/no-unknown-property": "error",
        "react/no-string-refs": "error",
        "react/no-direct-mutation-state": "error",
        "no-console": "off"
      }
    };
    复制代码
    # .eslintignore 配置文件
    tests
    node_modules
    *.bundle.js
    *.js.map
    .history
    dist
    .vscode
    **/*.snap
    复制代码

    三、 测试组件的创建和发布

    3.1 components 目录设计

    项目根目录下创建 components 目录用于存放组件库的所有组件, components 目录的结构则参考 antd 进行设计, 假设有组件 input-number 那么 components 可以是下面这样:

    • index.js 作为组件库的入口文件
    • assets/iconfont 存放字体图标文件
    • assets/style 用于存放通用样式文件
    • input-number 用于存放 input-number 组件的所有源码
    • input-number/index.jsx 作为组件的入口文件
    • input-number/style 用于存放组件的所有样式文件
    • input-number/style/index.js 组件样式的入口文件(该组件引入组件所有需要的样式文件,之后配合 babel-plugin-import 实现按需加载功能 )
    • __tests__ 用于存放组件的单元测试
    ├── components
    │   ├── assets
    │   │   ├── iconfont
    │   │   └── style
    │   ├── index.js
    │   └── input-number
    │       ├── index.jsx
    │       ├── style
    │       │   ├── index.js
    │       │   └── index.scss
    │       └── __tests__
    ....
    复制代码

    3.2 测试组件 input-number 编写

    • 编写 components/input-number/index.jsx
    import React from 'react';
    
    export default () => {
      return (
        <div className="qyrc-input-num">
          测试组件: input-number
        </div>
      );
    };
    
    复制代码
    • 编写 components/input-number/style/index.scss
    .qyrc-input-num {
      color: #999;
    }
    复制代码
    • 编写 components/input-number/style/index.js
    // 如果组件需要额外样式文件则需要一同引入
    import './index.scss';
    复制代码
    • 编写 components/index.js 导出组件
    export { default as InputNumber } from './input-number';
    复制代码
    • 在 storybook 中对组件进行测试

    在 stories/pages/base/Introduceindex.jsx 引用 InputNumber 组件,并运行项目对组件进行测试

    import React from 'react';
    import './index.css';
    import './index.scss';
    // 引入 InputNumber 组件和样式
    import { InputNumber } from '../../../../components';
    import '../../../../components/input-number/style/index';
    
    export default () => {
      return (
        <div>
          react 组件介绍
          <InputNumber />
        </div>
      );
    };
    复制代码

    3.3 配置脚本对组件进行编译打包

    对于 npm 包我们在发布时需要对所需要发布的文件进行简单的编译和打包, 对于我们的组件库我们常常需要将组件库编译为 ES 模块和 CommonJS 以及 UMD 模块。

    ES 模块和 CommonJS 模块的编译方式大同小异, 都是通过 babel 针对组件库中 js 模块进行编译对于其他文件则只需进行简单拷贝,当然针对样式文件还需要额外编译一份 css 文件, 唯一区别的是打包后的 js 模块不同一个是 ES 模块一个是 CommonJS 模块,同时编译后的 ES 模块和 CommonJS 模块的目录结构需要满足 babel-plugin-import 原理,从而实现按需加载功能。

    UMD 模块则是通过 webpack 针对 component 中入口文件进行完整的打包编译

    3.3.1 通过 babel 对组件库中 js 模块进行编译

    • 安装所需依赖
    npm install cross-env @babel/cli -D
    复制代码
    • 编写 npm 脚本: 针对组件库中的 js 模块编译为 ES 模块和 CommonJS 模块
    {
      "scripts": {
    +   "build:lib": "cross-env OUTPUT_MODULE=commonjs babel components -d lib --ignore **/__tests__",
    +   "build:es": "babel components -d es --ignore **/__tests__"
      }
    }
    复制代码

    3.3.2 通过 gulp 对组件库中进行处理

    • 安装所需依赖
    npm install gulp -D
    npm install gulp-sass -D
    npm install gulp-concat -D
    npm install gulp-autoprefixer -D
    npm install gulp-cssnano -D
    npm install gulp-filesize -D
    npm install gulp-sourcemaps -D
    npm install gulp-rename -D
    npm install gulp-replace -D
    复制代码
    • 创建 scripts/gulpfile.js
    /**
     * @name gulpfile.js
     * @description 打包项目css依赖
     * @description 参考 cuke-ui
     */
    
    const fs = require('fs');
    const path = require('path');
    const gulp = require('gulp');
    const concat = require('gulp-concat');
    const sass = require('gulp-sass');
    const autoprefixer = require('gulp-autoprefixer');
    const cssnano = require('gulp-cssnano');
    const size = require('gulp-filesize');
    const sourcemaps = require('gulp-sourcemaps');
    const rename = require('gulp-rename');
    const replace = require('gulp-replace');
    const { name } = require('../package.json');
    
    const browserList = [
      'last 2 versions',
      'Android >= 4.0',
      'Firefox ESR',
      'not ie < 9'
    ];
    
    const DIR = {
      // 输入目录
      scss: path.resolve(__dirname, '../components/**/*.scss'),
      buildSrc: path.resolve(__dirname, '../components/**/style/*.scss'),
      style: path.resolve(__dirname, '../components/**/style/index.js'),
      
      // 输入目录
      lib: path.resolve(__dirname, '../lib'),
      es: path.resolve(__dirname, '../es'),
      dist: path.resolve(__dirname, '../dist')
    };
    
    // 拷贝 scss 文件
    gulp.task('copyScss', () => {
      return gulp
        .src(DIR.scss)
        .pipe(gulp.dest(DIR.lib))
        .pipe(gulp.dest(DIR.es));
    });
    
    // 对 scss 进行编译后拷贝
    gulp.task('copyCss', () => {
      return gulp
        .src(DIR.scss)
        .pipe(sourcemaps.init())
        .pipe(sass())
        .pipe(autoprefixer({ browsers: browserList }))
        .pipe(size())
        .pipe(cssnano())
        .pipe(gulp.dest(DIR.lib))
        .pipe(gulp.dest(DIR.es));
    });
    
    // 创建 style/css.js
    gulp.task('createCss', () => {
      return gulp
        .src(DIR.style)
        .pipe(replace(/\.scss/, '.css'))
        .pipe(rename({ basename: 'css' }))
        .pipe(gulp.dest(DIR.lib))
        .pipe(gulp.dest(DIR.es));
    });
    
    // 编译打包所有组件的样式至 dis 目录
    gulp.task('dist', () => {
      return gulp
        .src(DIR.buildSrc)
        .pipe(sourcemaps.init())
        .pipe(sass())
        .pipe(autoprefixer({ browsers: browserList }))
        .pipe(concat(`${name}.css`))
        .pipe(size())
        .pipe(gulp.dest(DIR.dist))
        .pipe(sourcemaps.write())
        .pipe(rename(`${name}.css.map`))
        .pipe(size())
        .pipe(gulp.dest(DIR.dist))
        .pipe(cssnano())
        .pipe(concat(`${name}.min.css`))
        .pipe(size())
        .pipe(gulp.dest(DIR.dist))
        .pipe(sourcemaps.write())
        .pipe(rename(`${name}.min.css.map`))
        .pipe(size())
        .pipe(gulp.dest(DIR.dist));
    });
    
    gulp.task('default', gulp.parallel(
      'dist',
      'copyCss',
      'copyScss',
      'createCss',
    ));
    复制代码
    • 添加 npm 脚本
    {
      "scripts": {
    +   "build:css": "cd scripts && gulp",
        "build:lib": "cross-env OUTPUT_MODULE=commonjs babel components -d lib --ignore **/__tests__",
        "build:es": "babel components -d es --ignore **/__tests__"
      }
    }
    复制代码

    3.3.3 通过 webpack 对组件库进行 UMD 模块 打包

    • 相关依赖包安装
    npm install uglifyjs-webpack-plugin -D
    npm install optimize-css-assets-webpack-plugin -D
    npm install mini-css-extract-plugin -D
    npm install progress-bar-webpack-plugin -D
    复制代码
    • 创建 scripts/build.umd.js
    /**
     * @name UMD 模块 打包
     * @description 参考 cuke-ui
     * @description 输出目录 [dist]
     * CMD Node.js 环境
     * AMD 浏览器环境
     * UMD 两种环境都可以执行
     */
    
    const fs = require("fs");
    const path = require("path");
    const webpack = require("webpack");
    const UglifyJsPlugin = require("uglifyjs-webpack-plugin");
    const OptimizeCSSAssetsPlugin = require("optimize-css-assets-webpack-plugin");
    const MiniCssExtractPlugin = require('mini-css-extract-plugin');
    const ProgressBarPlugin = require('progress-bar-webpack-plugin');
    
    const { version, name, description } = require("../package.json");
    
    const LOGO = `
                  __                    _
      _______  __/ /_____        __  __(_)
     / ___/ / / / //_/ _ \\______/ / / / /
    / /__/ /_/ / ,< /  __/_____/ /_/ / /  
    \\___/\\__,_/_/|_|\\___/     \\__,_/_/
    
    `
    
    const config = {
      mode: "production",
      entry: {
        [name]: ["./components/index.js"]
      },
    
      //umd 模式打包
      output: {
        library: name,
        libraryTarget: "umd",
        umdNamedDefine: true, // 是否将模块名称作为 AMD 输出的命名空间
        path: path.join(process.cwd(), "dist"),
        filename: "[name].min.js"
      },
      //react 和 react-dom 不打包
      externals: {
        react: {
          root: "React",
          commonjs2: "react",
          commonjs: "react",
          amd: "react"
        },
        "react-dom": {
          root: "ReactDOM",
          commonjs2: "react-dom",
          commonjs: "react-dom",
          amd: "react-dom"
        }
      },
      resolve: {
        enforceExtension: false,
        extensions: [".js", ".jsx", ".json", ".less", ".css"]
      },
      module: {
        rules: [
          {
            test: /\.js[x]?$/,
            use: [
              {
                loader: "babel-loader"
              }
            ],
            exclude: "/node_modules/",
            include: [path.resolve("components")]
          },
          {
            test: /\.(le|c)ss$/,
            use: [
              MiniCssExtractPlugin.loader,
              "css-loader",
              { loader: "postcss-loader", options: { sourceMap: false } },
              {
                loader: "sass-loader",
                options: {
                  sourceMap: false
                }
              }
            ]
          },
          {
            test: /\.(jpg|jpeg|png|gif|cur|ico)$/,
            use: [
              {
                loader: "file-loader",
                options: {
                  name: "images/[name][hash:8].[ext]" //遇到图片  生成一个images文件夹  名字.后缀的图片
                }
              }
            ]
          }
        ]
      },
      optimization: {
        minimizer: [
          new UglifyJsPlugin({
            cache: true,
            parallel: true,
            uglifyOptions: {
              compress: {
                drop_debugger: true,
                drop_console: false
              },
            }
          }),
          new OptimizeCSSAssetsPlugin({
            // 压缩css  与 ExtractTextPlugin 配合使用
            cssProcessor: require("cssnano"),
            cssProcessorOptions: { discardComments: { removeAll: true } }, // 移除所有注释
            canPrint: true // 是否向控制台打印消息
          })
        ],
        noEmitOnErrors: true,
      },
      plugins: [
        new ProgressBarPlugin(),
        new MiniCssExtractPlugin({
          filename: "[name].min.css"
        }),
        // 在打包的文件之前 加上版权说明
        new webpack.BannerPlugin(` \n ${name} v${version} \n ${description} \n ${LOGO}\n`),
        new webpack.DefinePlugin({
          "process.env.NODE_ENV": JSON.stringify("production"),
          __DEBUG__: false,
        }),
        new webpack.LoaderOptionsPlugin({
          minimize: true
        }),
        new webpack.IgnorePlugin(/^\.\/locale$/, /moment$/),
      ]
    };
    
    module.exports = config;
    
    复制代码
    • 添加 npm 脚本
    {
      "scripts": {
    +   "build:publish": "npm run build:lib && npm run build:es && npm run build:css && npm run build:umd",
    +   "build:umd": "webpack --config ./scripts/build.umd.js",
        "build:css": "cd scripts && gulp",
        "build:lib": "cross-env OUTPUT_MODULE=commonjs babel components -d lib --ignore **/__tests__",
        "build:es": "babel components -d es --ignore **/__tests__"
      }
    }
    复制代码

    3.4 修改 package.json

    • private 设置项目是否是私有包
    • files 设置在发布包时需要发布的文件和目录
    • main 设置包的入口文件
    • module 设置 npm 包的模块入口
    • peerDependencies 设置 npm 包同等依赖包
    {
    + "private": false,
    + "files": [
    +   "lib",
    +   "es",
    +   "dist",
    +   "LICENSE"
    + ],
    + "main": "lib/index.js",
    + "module": "es/index.js",
    + "peerDependencies": {
    +   "react": ">=16.8.0",
    +   "react-dom": ">=16.8.0"
    + },
    }
    复制代码

    3.4 组件发布

    • 组件库编译
    npm run build:publish
    复制代码
    • 组件发布
    # 1. 切换官方源头
    npm config set registry http://registry.npmjs.org
    
    # 2. 登录 npm
    npm login
    
    # 3. 发布包
    npm publish --access public
    
    # 4. 如果需要则切换回淘宝源
    npm config set registry https://registry.npm.taobao.org/
    复制代码

    四、 其他配置

    4.1 commit 校验以及版本发布配置

    • 依赖包安装
    # husky 包安装
    npm install husky --save-dev
    
    # commitlint 所需包安装
    npm install @commitlint/config-angular @commitlint/cli --save-dev
    
    # commitizen 包安装
    npm install commitizen --save-dev
    npm install commitizen -g
    
    # standard-version 包安装
    npm install standard-version --save-dev
    复制代码
    • commitlint 和 commitizen 配置
    # 生成 commitlint 配置文件
    echo "module.exports = {extends: ['@commitlint/config-angular']};" > commitlint.config.js
    # commitizen 初始化
    commitizen init cz-conventional-changelog --save-dev --save-exact
    复制代码
    • 更新 package.json
    {
      "scripts": {
    +   "commit": "git-cz",
    +   "release": "standard-version"
      },
    + "husky": {
    +   "hooks": {
    +     "commit-msg": "commitlint -E HUSKY_GIT_PARAMS"
    +   }
    + }
    }
    复制代码

    4.2 editorconfig 配置

    • 项目下新增 .editorconfig 配置文件
    # http://editorconfig.org
    root = true
    
    [*]
    indent_style = space
    indent_size = 2
    end_of_line = lf
    charset = utf-8
    trim_trailing_whitespace = true
    insert_final_newline = true
    
    [*.md]
    trim_trailing_whitespace = false
    
    [Makefile]
    indent_style = tab
    复制代码
    展开全文
  • 构建自己的React UI组件库: ():从v0.0.0到 v0.0.1 (本文) (二):构建首页 (三):文档编写 (四):擦干净细节 (五):推广、宣传、迭代 &lt;= to be continue = 在这里可以访问到我的组件库: BB-...

    序言

    该系列文章将跟随作者的开发进度持续更新。

    预计内容如下:


    构建自己的React UI组件库:

    (一):从v0.0.0到 v0.0.1 (本文)

    (二):构建首页

    (三):文档编写

    (四):擦干净细节

    (五):推广、宣传、迭代

    <= to be continue =


    在这里可以访问到我的组件库: BB-color

    以及npm地址: BB-color


    约定

    1. 本系列文章尽可能多的涉及到每个步骤、每个文件和每个更新。
    2. 本系列文章中,整个项目的开始基于官方提供的 creat-react-app 进行react构建,所有内容将使用最新的库版本进行开发。
      • create-react-app v2.1.1
      • react v16.7.0
      • webpack v4.19.1
      • babel 7+

    必备知识

    • 基本掌握 React
    • 会使用 Git
    • JavaScript、CSS等基础知识

    正文开始


    背景故事

    也许是在一次突发奇想,想着创建一个自己的,针对react的,ui组件库。期间也百度过很多内容,不过都不太如人意,之后又用谷歌搜索了相关的内容,搜到了很多,但其中总缺少一些关键的环节。我试着参考ant-design,却发现里面的内容过于庞大。 总之摸爬滚打了很久,终于成功的构建了一个属于自己的UI组件库。

    那么作为本系列的第一章,会讲述如何从一个空白页开始,搭建出一个能通过npm下载,引入react使用的UI组件


    前期准备

    1. github与npm

    首先我们要有github与npm的账号,没有账号的同学请自行申请

    github官网

    npm官网

    下一步,在github上创建组(organization),可以在页面的右上角看见下图的内容

    注1:

    我们可以创建自己的个人仓库(repository),也可以通过创建组(organization)->组中创建仓库(repository)

    组会有一个单独的文件库,你能和你的成员共同开发,并且在一个组里能包含多个与该组有关的代码。比如ant-design也是所属Ant Design Team这个组,而Ant Design Team这个组里包含了多个围绕ant-design创建的其他项目。

    考虑到日后的扩展,创建一个UI组件库,我个人更推荐创建组,然后在组中创建仓库。 当然,你也可以凭自己喜好创建


    点击New organization -->

    填写与组有关的内容(这部分可以随意填写或选择,对之后我们构建UI组件库没有影响)-->

    在组里创建仓库


    在创建仓库时要注意一点,下图抹茶色框里,开源协议,选择MIT协议。红框里面无所谓选择,如果选择了None后续需要自己配置

    与协议相关的知识不在本文范围内,感兴趣的同学可以自己百度

    千里之行始于足下,很棒!第一步准备已经完成。


    2. 构建代码

    注2:

    如果是windows电脑,可以使用 git bash,他能让你更加顺手。当然,如果你愿意,使用系统自带的命令行工具也是可以的。


    首先,我们创建一个空白文件夹以存放我们的本地代码。

    我是在 e盘的workspace文件夹里创建了一个空文件:test

    之后在gitbash中进入到该文件,并使用 create-react-app 构建我们的代码。

    # 可以通过运行以下代码全局安装 create-react-app
    npm i create-react-app -g
    
    # 运行以下代码进行构建
    create-react-app my-app
    复制代码

    当构建完成之后,我们能在test文件夹里发现一个新文件: my-app

    使用编译器打开my-app,你能看见如下图所示的内容。

    这是我们通过create-react-app构建生成的文件目录。(文件目录结构会在文件重构后解释,位于 【前期准备-3.目录重构】)


    *2.1. 执行 npm run eject (非必要)

    注3:

    执行 npm run eject 不是必要的,但对以后我们做自定义处理有帮助。

    如果你只想使用React的默认配置,或者只是想做一个简单的库,那么可以跳到 前期准备 - 3.目录重构


    使用git bash , 进入my-app文件后执行npm run eject

    补充知识点A:

    在默认情况下,我们是使用 react-scripts 打包执行我们的代码,在配置方面的问题我们不需要去考虑,这样的操作能让我们专注于代码的编写。但是如果有更多的需求,需要自己配置相应的功能,那么使用React提供的 react-scripts eject 能让我们接管所有的配置项。但是要注意,这是不可逆操作。


    在执行完操作后,你能发现你的文件目录变成了这样


    3. 目录重构

    现在我们要删除我们用不上的文件,添加我们需要的文件夹。具体的删除和保留,可以查看下图。

    下图中,左为重构前,右为重构后

    如果你没有执行 npm run eject ,那么请参考上图中重构后的部分,修改 ./public./src 中的文件。


    OK,现在准备工作已经全部完成。

    下面开始编写我们的代码吧。


    代码编写

    1.修改代码:

    // src/App.js
    
    import React, { Component } from 'react';
    
    class App extends Component {
      render() {
        return (
          <div className="App">
            hello
          </div>
        );
      }
    }
    
    export default App;
    
    复制代码
    // src/index.js
    
    import React from 'react';
    import ReactDOM from 'react-dom';
    import App from './App';
    
    ReactDOM.render(<App />, document.getElementById('root'));
    
    复制代码

    2. 组件编写

    创建 src/components/button 文件夹,随后创建相应文件编写Button组件

    // src/components/index.js
    
    export { default as Button } from './button';
    复制代码
    // src/components/button/index.js
    
    import React from 'react';
    import PropTypes from 'prop-types';
    import './index.css';
    
    const Button = ({ text }) => <button className="btn">这是一个组件按钮{text}</button>
    
    Button.propTypes = {
      text: PropTypes.any
    };
    
    export default Button;
    
    复制代码
    // src/components/button/index.css
    
    .btn {
      height: 50px;
    }
    复制代码

    3. 查看效果

    完成组件编写后,修改我们的App.js

    // src/App.js
    
    import React, { Component } from 'react';
    import { Button } from './components';
    
    class App extends Component {
      render() {
        return (
          <div className="App">
            <Button text={'hhhasdasdh'}></Button>
          </div>
        );
      }
    }
    
    export default App;
    复制代码

    完成以上步骤后,我们的组件就编写完成,现在让我们来看看效果

    使用git bash,执行 npm start

    如果能正常显示,那么我们组件就做好了。


    单独编译出组件模块

    1. 依赖安装

    在此之前,我们需要安装以下依赖,

    npm install --save-dev @babel/cli @babel/core @babel/plugin-proposal-class-properties @babel/preset-env @babel/preset-react cpx cross-env
    复制代码

    每个依赖的作用可以自行百度。以后会在文中补上(咕咕咕)

    2. 代码编写

    在根目录创建 .babelrc.js 文件

    // .babelrc.js
    
    'use strict';
    
    const output = process.env.BABEL_OUTPUT;
    const modules = output == null ? false : output;
    
    const options = {
      presets: [
        ['@babel/env', { loose: true, modules }], '@babel/react'
      ],
      plugins: [
        '@babel/proposal-object-rest-spread', ['@babel/proposal-class-properties', { loose: true }]
      ],
    };
    
    module.exports = options;
    复制代码

    修改package.json

    // package.json
    {
    ...
    
    "scripts": {
        ... 
        // build:commonjs 为构建commonJS版
        "build:commonjs": "cross-env BABEL_OUTPUT=commonjs babel src/components --out-dir lib/ --ignore **/__tests__,**/__mocks__",
            
        // build:esm 为构建es module版
        "build:esm": "babel src/components --out-dir esm/ --ignore **/__tests__,**/__mocks__",
            
        // 下面2行为机构化复制css文件,因为我们使用原生css,直接复制即可
        "copy-css:esm": "cpx \"src/components/**/*.css\" esm",
        "copy-css:lib": "cpx \"src/components/**/*.css\" lib"
      },
    
    ...
    }
    复制代码

    执行

    npm run build:commonjs
    npm run build:esm
    npm run copy-css:lib
    npm run copy-css:esm
    复制代码

    如果成功生成了如上的文件,恭喜你,距离成功只剩一步了。


    令人激动的发布

    1. 修改配置

    在发布之前,我们还要做最后一步处理,修改一些必要的配置属性

    // .gitignore
    
    ...
    
    // 不要将我们编译出的文件提交到Git
    /esm
    /lib
    
    ...
    复制代码
    // package.json
    
    {
      // name为我们的项目名,在发布到npm时,项目名由此项决定 !!
      "name": "my-ui-components",
      // version为项目版本号,在发布到npm时,版本号由此项决定 !!
      "version": "0.0.1",
      // 描述
      "description": "A Design UI library for React",
      // 默认入口文件,当你作为组件引用时,会以此作为默认文件引入
      "main": "lib/index.js",
      // 同为入口文件,区别可以参考后文的知识点C
      "module": "esm/index.js",
      // 作者,这里写你自己的github名就好
      "author": "ParaSLee",
      // 证书
      "license": "MIT",
      // 你的git仓库
      "repository": {
        "type": "git",
        // 后面的AAA填写你创建组名,BBB为组里项目的名字,如果你没有创建组,那么可以去掉AAA
        "url": "git+https://github.com/AAA/BBB.git"
      },
      "bugs": {
        // 后面的AAA填写你创建组名,BBB为组里项目的名字,如果你没有创建组,那么可以去掉AAA,填写完成后可以复制链接到URL中检验能不能访问,能访问并且正确那么没问题
        "url": "https://github.com/AAA/BBB/issues"
      },
      // 发布到npm的文件
      "files": [
        "esm",
        "lib"
      ],
      // githubreadme页,也是在npm上访问npm包时的主页
      "homepage": "https://github.com/AAA/BBB#readme",
      // 搜索关键字
      "keywords": [
        "AAA",
        "BBB",
        "react",
        "react-component",
        "component",
        "components",
        "ui",
        "framework",
        "frontend"
      ],
      
      ...
      // 该内容可以在知识点D中查看
      "peerDependencies": {
        "react": ">=16.0.0",
        "react-dom": ">=16.0.0"
      },
          
      ...
      
    }
    复制代码

    补充知识点B:

    版本号分三级,分别为:

    • 一级,全新版本
    • 二级,大改进
    • 三级,小升级或者 bug 修复

    为何从 0.0.1 开始?因为 0.x.x 可以认为是非正式版本、测试版,而从 1.x.x 开始,就是正式发布的版本了。

    补充知识点C:

    最早的 npm 包都是基于 CommonJS 规范,当 require('package1') 的时候,就会根据 main 字段去查找入口文件。而 CommonJS 规范的包都是以 main 字段表示入口文件了,如果使用 ES Module 的也用 main 字段则会造成困扰,于是 rollup 便使用了另一个字段:module。

    引用:segmentfault.com/a/119000001…

    补充知识点D:

    dependencies:开发时会使用,打包时不会移除(仅存在于打包后的包中)

    devDependencies:开发中会使用的依赖,打包时会移除

    peerDependencies:如果你使用了这个依赖(我们所编写的UI组件库),那么你也要安装peerDependencies中的依赖(react、react-dom)

    引用:www.cnblogs.com/wonyun/p/96…


    2. 发布到github

    我们在之前的步骤中,都是在本地进行开发,现在我们将我们的源码发布到github上。

    注4:

    在发布之前还有一个重要的操作,首先在github和npm上搜索你的项目名,要确保一定不能有重名!!!

    my-app和my-app01 是不算重名的,

    如果发生了重名,记得修改你github的项目名和package.json中的name名,两者尽量保持一致性!!


    首先,使用git将代码拉取下来。

    进入我们之前创建的代码库,将代码克隆(clone) 下来

    随后转移文件,将我们本地的文件转移至我们的克隆下来的文件里。


    提交代码。

    首先执行

    # 这里的作用在release中添加相应的版本
    git tag -a 'v0.0.1' -m 'first commit'
    git push origin v0.0.1
    复制代码

    之后,正常提交代码到github

    git add .
    git commit -m 'first comit'
    git push -u origin master
    复制代码

    3. 发布到npm

    下一步,发布我们的代码到npm

    在保证已经登录自己的npm账号后

    npm login #登录
    npm publish
    
    # 输出以下信息说明发布成功, xxxxx为你的项目名称
    # + xxxxx@0.0.1
    复制代码

    到了庆祝的时候了,你已经完成了自己的UI组件库,并且能直接通过 npm install xxxx 进行安装。


    使用

    你可以重新开一个react项目,执行npm install

    // 引入按钮组件
    import { Button } from "xxxx";
    复制代码

    为自己的成功感到骄傲吧!



    尾声

    这是一篇长达3300字的文章,在工作之余写这篇文章也花费了我相当长得时间。

    在下一期文章中,我会以这篇文章的结尾作为起点,进行下一步的开发。

    文章不久就会发布,内容为构建我们自己的官方网站



    如果有任何不当或缺失的地方,希望能在评论区指出,理性交流。

    如需转载请注明作者与原文地址

    作者:ParaSLee

    原文:juejin.im/post/5c28bb…

    展开全文
  • 最近项目开发新模块的时候,每次新模块的时候需要创建一个组件的时候(包含组件css,index.js,组件js),就只能会拷贝其他组件修改名称 ,但是了1-2个后发现效率太低了,而且极容易出错,所以自己写一个npm包来...

    前言

    最近写项目开发新模块的时候,每次写新模块的时候需要创建一个组件的时候(包含组件css,index.js,组件js),就只能会拷贝其他组件修改名称 ,但是写了1-2个后发现效率太低了,而且极容易出错,所以自己写一个npm包来减少工作量,下面就一步一步来创建一个属于自己的npm仓库

    首先第一步创建一个package.json文件,打开终端,输入以下命令:

     npm init  

    然后会依次提示项目名称、版本、项目描述、入口文件…一直回车,直到出现Is this ok? (yes),然后输入yes,创建一个package.json文件就完成了,接下来在根目录创建一个index.js文件,文件内容为:

    #!/usr/bin/env node   //告诉node使用终端运行
    const fs = require('fs'); //文件系统
    const program = require('commander'); //终端输入处理框架
    const package = require('./package.json'); //获取版本信息
    program.version(package.version, '-v,--version')
           .command('init <name>')
           .action(name=>{
               console.log(name)
           })
      program.parse(process.argv);

    安装commander:

    cnpm i -d commander 

    接下来我们就可以看看效果,安装执行

    node index.js -v 
    输出:1.0.0
    node index.js init header
    输出:header
    node index.js -h 
    输出: Usage: index [options] [command]
    
      Options:
    
        -v,--version  output the version number
        -h, --help    output usage information
    
      Commands:
    
        init <name>

    代码正常运行,接下来修改package.json里面bin,增加以下代码:

      "bin": {
        "temp": "index.js" 
      },

    到这里我们第一步就算完成了,怎么发布npm包呢?流程如下:
    - 首先在[npm官网][1]中注册账号(如有忽略)
    - 注册完执行npm adduser 依次输入帐号,密码,邮箱,
    - npm version patch
    - npm publish就可以提交了
    npm发布流程踩过的坑
    - 使用npm提交,不要使用cnpm
    - 每次修改都需要修改版本号npm version patch
    - 项目名称 npm仓库是否有这个项目

    发布成功后,我们全局安装,刚刚我提交的项目名称为template-react-cli,所以执行全局安装,使用npm安装,cnpm会有短暂延迟

    npm i template-react-cli -g
    temp -v    
    输出:1.0.0 
    temp init footer 
    输出: footer

    属于我们的npm可以正常使用了,接下来丰富我们的npm包,先安装依赖:

    cnpm i -d download-git-repo handlebars inquirer log-symbols ora

    安装完成后,丰富我们的功能index.js文件如下:

    #!/usr/bin/env node
    const fs = require('fs');
    
    const program = require('commander');
    const download = require('download-git-repo'); //下载模版文件
    const chalk = require('chalk');  //美化终端
    const symbols = require('log-symbols'); //美化终端
    const handlebars = require('handlebars'); //修改模版文件内容
    
    const ora = require('ora'); //提示下载
    var inquirer = require('inquirer');  //提示文本
    const package = require('./../package.json'); //获取版本信息
    const re = new RegExp("^[a-zA-Z]+$"); //检查文件名是否是英文,只支持英文
    
    program
      .version(package.version, '-v,--version')
      .command('init <name>')
      .action(name => {
        if (!re.test(name)) { //检查文件名是否是英文
          console.log(symbols.error, chalk.red('错误!请输入英文名称'));
          return 
        } 
        if (!fs.existsSync(name)) { //检查项目中是否有该文件
          inquirer  
            .prompt([
              {
                type: 'list',
                name: 'type',
                message: '请选择模版类型?',
                choices: [
                  'react-component------ES6组件',
                  'react-function------函数组件',
                  'react-redux------ES6组件',
                ],
              },
            ]) 
            .then(answers => {
                //用户选择后回调
              console.log(symbols.success,chalk.green('开始创建..........,请稍候'));
              const spinner = ora('正在下载模板...');
              spinner.start();
              const type = getType(answers)
              download(`github:NewPrototype/template/#${type}`, name, err => {
                if (err) {
                  spinner.fail();
                } else {
                  spinner.succeed();
                  var files = fs.readdirSync(name);
                  for(let i=0;i<files.length;i++){ //修改文件内容
                    let fileName=`${name}/${files[i]}`;
                    if(fs.existsSync(`${name}/${files[i]}`)){
                      const content = fs.readFileSync(fileName).toString();
                      const result = handlebars.compile(content)({template:name,});
                      fs.writeFileSync(fileName, result);
                    }
    
                  }
                  let count = 0; //所有文件标题修改完成,提示
                  for (let i = 0; i < files.length; i++) {
                    if(files[i]=='index.js'||files[i]=='action.js'||files[i]=='reducer.js'||files[i]=='saga.js'){
                      continue
                    }
                    //获取文件列表
                    var index = files[i].indexOf('.');
                    fs.rename(
                      `${name}/${files[i]}`,
                      `${name}/${name}${files[i].substring(index)}`,
                      err => {
                        if (err) {
                          console.log('---错误');
                        }
                        count++;
                        if (count+1 == files.length) { //排除index.js文件
                          console.log(symbols.success, chalk.green('模版创建成功'));
                        }
                      }
                    );
                  }
                }
              });
            });
        } else {
          console.log(symbols.error, chalk.red('有相同名称模版'));
        }
      });
    
    program.parse(process.argv);
    
    const getType = (type) => {
      let str = 'master';
      switch (type.type) {
        case "react-component------ES6组件":
          str = "component"
          break;
          case "react-function------函数组件":
          str = "master"
          break;
          case "react-redux------ES6组件":
          str = "redux"
          break;
        default:
          break;
      }
      return str
    }

    然后重新提交文件到npm仓库,方法和上面发布流程一样,发布完成后:

    npm i template-react-cli -g
    先检查版本号
    temp -v  
    输出:1.02
    然后检查功能:
    temp init header 
    输出:? 请选择模版类型? (Use arrow keys)
    ❯ react-component------ES6组件
      react-function------函数组件
      react-redux------ES6组件
    选择想要的选项,回车
    输出:✔ 开始创建..........,请稍候
    ⠏ 正在下载模板...
    等待下载完成
    输出:✔ 模版创建成功



    可以看到当前目录下面创建了一个header文件夹,里面包含js文件和stylcss文件,到这里整个流程就完成了,这里是下载了模版文件[模版地址][2],大家也可以写出符合自己风格的模版文件。

    后言

    有了node我们可以做很多很多的事情,以后开发新模块的时候就可以偷懒了!纯手打给个赞可好?

    github

    https://github.com/NewPrototype/template-cli

    模版github

    https://github.com/NewPrototype/template

    展开全文
  • 项目基于webpack 简单配置 当前目录结构: |- /config //webpack配置项 |- webpack.common.js |- webpack.dev.js |- webpack.prod.js |- /dist //出口文件 |- ...... |- /node_modules |- /src //...
  • React UI 组件库

    2019-06-06 14:02:58
    上篇文章中了流行的前端UI几大框架,发现大部分评价都是Vue的...Material-UI是React组件库来实现Google的Material Design风格UI界面框架。也是首React的UI工具集之。使用它可以快速搭建出赏心悦目的应用界...
  • 一个简易的React组件库,包含日期、日历、关联选择、树形列表、下拉选框、多级联动、提示等等
  • 2020最流行的React组件库推荐

    千次阅读 2020-02-23 12:07:34
    React-Bootstrap 是一个可重复使用的 React 组件库,也是最受欢迎的前端框架之一。目前同样是在为1.0.0版本而积极开发中。 Ant Design ???????? 遵循 Ant Design 规范,React Ant Design 是一个开箱即用的高质量 ...
  • React 的普及似乎在不断增长,在 Stack overflow 2017 年最受欢迎的组件库中,React 处于领先地位:React 的虚拟 DOM,声明性地描述用户界面和模拟界面状态的能力,以及相对较低的门槛,都使 React 成为构建 UI 很好...
  • 更新 :您可以使用npm包 create-... 通过一点点工作,它也可以用于创建React组件库,该库可以发布到 npm 并在其他React应用程序中使用。 这是您需要做的: 1.使用创建一个新项目 create-react-app : create-r...
  • 使用 webpack 开发 React 组件库

    千次阅读 2019-01-31 11:15:32
    使用 Webpack 开发 React 组件库 组件库设计要求 使用 webpack 做模块开发以及打包 打包后的格式为 umd 模块 Code Splitting 代码分割 第三方包排除打包 react、antd 、lodash 等 样式文件抽离 借助 babel-plugin-...
  • React 最好的 ui 组件库集锦

    万次阅读 2018-06-26 15:54:37
    【转载】https://www.rails365.net/articles/react-zui-hao-de-ui-zu-jian-ku-ji-jin这里有篇讨论,说了哪个才是 React 最好的 ui 组件库。https://discuss.reactjs.org/t/best-ui-library-for-react/2953/5我也不...
  • react常用组件库

    2020-03-17 01:04:42
    react 常用 UI组件库 项目的好不如组件用的好-_-! Ant Design 是一个致力于提升『用户』和『设计者』使用体验的设计语言 ;旨在统一中台 项目的前端 UI 设计,屏蔽不必要的设计差异和实现成本,解放设计和前端的...
  • A components library for React. 一个基于 React 的PC组件库
  • Reactive Maps 是一个 React 组件库用来构建可以实时更新的地图
  • 作为成熟的UI组件库,它能够提供提供整套UI组件用来满足使用需求,能大大减少开发成本。 在使用了他人提供的组件库后,我自然就会有兴趣去了解一下别人开发的组件库到底是如何设计的,如何进行相关的组件封装。...
  • 最近针对日常业务需求使用react封装了一套[组件库], 大概记录下整个开发过程中的心得。由于篇幅原因,在这里只对开发过程中比较纠结的选型和打包等进行讨论,后续再对具体组件的封装进行讨论。文章首发于个人博客 ...
  • 如何才能更舒适的写一个React组件? 这个问题不管是对个人,还对团队,都是一个疑难问题,它涉及的不仅仅只是工程构建的问题,还涉及了对组件的运营维护成本问题上。 总所周知,一个React组件就是一个npm包,我们...
  • 如何开发React UI组件库

    千次阅读 2019-06-11 15:49:46
    技术栈:React + Webpack + TypeScript + Docz 先上脚手架目录结构 docz:docz文档生成器的配置目录 ...注意:本脚手架只是基本配置,内部代码比较简洁,主要是起到一个学习引导作用。 组件开发...
  • 构建自己的React UI组件库: ():从v0.0.0到 v0.0.1 (二):构建首页 (三):文档编写 (本文) (四):擦干净细节 (五):推广、宣传、迭代 &lt;= to be continue = 在这里可以访问到我的组件库: BB-...
1 2 3 4 5 ... 20
收藏数 44,523
精华内容 17,809
关键字:

怎么写一个react组件库