react 구성 요소 라이브러리 구축 기록
1. 프로젝트 초기화
git init
npm init -y
node_modules
coverage
dist
es
lib
package-lock.json
2. storybook 기반 개발 테스트 환경 구축
2.1 프로젝트 신속 구축
npx -p @storybook/cli sb init --type react
├── .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에 1급 디렉터리
가 있고 디렉터리에
페이지가 있다고 가정하면 그에 대응하는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 테스트 코드 작성
import React from 'react';
import './index.css';
export default () => {
return (
<div>
react
div>
);
};
import Introduce from './pages/base/Introduce';
export default [
{
title: ' ',
module: ' ',
component: Introduce
}
];
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)));
{
"scripts": {
+ "start": "npm run storybook",
"storybook": "start-storybook -p 8080",
"build-storybook": "build-storybook"
},
}
npm start
2.4 웹 패키지 구성 사용자 정의
storybook은 자신의 웹 패키지 설정이 있지만 복잡한 상황을 충족시킬 수 없습니다. storybook에서 웹 패키지 설정을 만드는
.storybook/webpack.config.js
를 통해 정의할 수 있습니다.# 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'],
},
}
.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.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"
]
})
]
};
.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. 테스트 구성 요소의 생성과 발표
3.1 components 디렉터리 디자인
프로젝트 루트 디렉터리에components 디렉터리를 만들어서 구성 요소 라이브러리의 모든 구성 요소를 저장합니다.components 디렉터리의 구조는antd를 참고하여 설계합니다. 구성 요소 input-number가 있다고 가정하면components는 다음과 같습니다.
├── 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';
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 모듈은 웹 패키지를 통해component의 입구 파일을 완전하게 패키지 컴파일합니다
3.3.1 babel을 통해 구성 요소 라이브러리의 js 모듈을 컴파일하기
npm install cross-env @babel/cli -D
{
"scripts": {
+ "build:lib": "cross-env OUTPUT_MODULE=commonjs babel components -d lib --ignore **/__tests__",
+ "build:es": "babel components -d es --ignore **/__tests__"
}
}
3.3.2gulp를 통해 구성 요소 라이브러리 처리
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
/**
* @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',
));
{
"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 웹팩을 통한 어셈블리 라이브러리 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
/**
* @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(`
${name} v${version}
${description}
${LOGO}
`),
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;
{
"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 패키지 수정json
{
+ "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. 기타 구성
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
echo "module.exports = {extends: ['@commitlint/config-angular']};" > commitlint.config.js
# commitizen
commitizen init cz-conventional-changelog --save-dev --save-exact
{
"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
이 내용에 흥미가 있습니까?
현재 기사가 여러분의 문제를 해결하지 못하는 경우 AI 엔진은 머신러닝 분석(스마트 모델이 방금 만들어져 부정확한 경우가 있을 수 있음)을 통해 가장 유사한 기사를 추천합니다:
다양한 언어의 JSONJSON은 Javascript 표기법을 사용하여 데이터 구조를 레이아웃하는 데이터 형식입니다. 그러나 Javascript가 코드에서 이러한 구조를 나타낼 수 있는 유일한 언어는 아닙니다. 저는 일반적으로 '객체'{}...
텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
CC BY-SA 2.5, CC BY-SA 3.0 및 CC BY-SA 4.0에 따라 라이센스가 부여됩니다.