项目指令
"scripts": {"preinstall": "npx only-allow pnpm", // 表示用pnpm 进行多包管理 且只能用pnpm
"prepare": "husky install", //husky进行Git管理
"init": "pnpm install && pnpm run build", //安装依赖并且构建打包
"start": "pnpm run dev", //运行dumi
"clean-dist": "rimraf 'packages/hooks/{lib,es,node_modules,dist}'", //删除packages文件下所有的构建产物以及依赖项
"clean": "pnpm run clean-dist && rimraf node_modules",
"dev": "dumi dev", //运行dumi
"build": "pnpm -r --filter=./packages/* run build", //运行pnpm run build 对packages下文件目录构建,-r循环遍历 filter过滤,通过相同的命令通过循环遍历分发到子包,让子包执行相同的命令
"build:doc": "dumi build", //静态资源的构建
"test": "jest", // 测试
"coveralls": "jest --coverage --coverageReporters=text-lcov | coveralls",//测试覆盖率
"pub": "pnpm run build && pnpm -r --filter=./packages/* publish", //发布
"pub:beta": "pnpm run build && pnpm -r --filter=./packages/* publish --tag beta",//发布beta版本的标记"encode-fe-lint-scan": "encode-fe-lint scan",//脚手架提供的代码扫描代码
"encode-fe-lint-fix": "encode-fe-lint fix" //脚手架提供的代码修复的能力},
创建一个空的npm 项目
npm init --y
因为不发包所有不需要version main(版本 和指定入口文件),设置“private”:"true",如下基础项目
{
"name": "pnpmreact",
"private":"true",
"description": "",
"scripts": { },
"keywords": [],
"author": "",
"license": "ISC"
}
全局安装encode-fe-lint工具
pnpm install -g encode-fe-lint
一键接入相应的规范:包含了ESLint styleLint commitlint markdownlint
encode-fe-lint init 等同于 npx encode-fe-lint
配置以下选项
step1 有以下选项可供选择,选择匹配的内容项
未使用 React、Vue、Node.js 的项目(JavaScript)
未使用 React、Vue、Node.js 的项目(TypeScript)
React 项目(JavaScript)
React 项目(TypeScript)
Rax 项目(JavaScript)
Rax 项目(TypeScript)
Vue 项目(JavaScript)
已选React 项目(TypeScript)
step2 是否需要使用 stylelint(若没有样式文件则不需要): (Y/n) : yes
Step3 是否需要使用 markdownlint(若没有 Markdown 文件则不需要): (Y/n) no
Step 4. 是否需要使用 Prettier 格式化代码: (Y/n) Y
package.json 中添加命令行配置项 和 依赖项 此处只能用pnpm, 要是用yarn 只能结合lerno 安装
"scripts": {
"preinstall":"npx only-allow pnpm",
"prepare": "husky install",
"init":"pnpm install",
"build": "pnpm -r --filter=./packages/* run build",
"start":"pnpm run dev",
"dev":"dumi dev"
},
"devDependencies": {
"@ant-design/icons": "^5.0.1",
"@babel/cli": "^7.10.1",
"@babel/core": "^7.10.2",
"@babel/plugin-transform-runtime": "^7.19.6",
"@babel/preset-env": "^7.0.0",
"@babel/preset-react": "^7.0.0",
"@babel/preset-typescript": "^7.0.0",
"@testing-library/jest-dom": "^5.16.5",
"@testing-library/react": "^13.4.0",
"@types/jest": "^29.4.0",
"@types/mockjs": "^1.0.7",
"@types/react-router": "^5.1.19",
"@umijs/fabric": "^2.1.0",
"antd": "^5.2.1",
"babel-plugin-import": "^1.12.0",
"coveralls": "^3.1.1",
"cross-env": "^7.0.3",
"del": "^5.1.0",
"dumi": "^1.1.48",
"encode-fe-lint": "^1.0.3",
"eslint": "^7.2.0",
"eslint-plugin-react-hooks": "^4.0.8",
"fast-glob": "^3.2.11",
"fs-extra": "^10.0.1",
"gray-matter": "^4.0.3",
"gulp": "^4.0.2",
"gulp-babel": "^8.0.0",
"gulp-typescript": "^6.0.0-alpha.1",
"husky": "^8.0.0",
"jest": "^29.4.1",
"jest-environment-jsdom": "^29.4.1",
"jest-localstorage-mock": "^2.4.18",
"mockjs": "^1.1.0",
"prettier": "^2.0.5",
"pretty-quick": "^3.1.3",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-drag-listview": "^0.1.6",
"react-router": "^6.4.2",
"react-shadow": "^19.0.3",
"rimraf": "^3.0.2",
"surge": "^0.21.3",
"tslib": "^2.4.1",
"ts-jest": "^29.0.5",
"typescript": "^5.1.3",
"webpack": "^4.43.0",
"webpack-cli": "^3.3.10",
"webpack-merge": "^4.2.2"
},
安装依赖项
pnpm install
因为要实现多包管理,所以需要在根目录下添加一个pnpm-workspace.yaml 就可以了,这是pnpm 默认支持的,文件名需手动输入
pnpm-workspace.yaml 文件内容如下:
packages: - 'packages/*'
根目录下创建packages/hooks 文件夹 ,多包的目录结构
打开dumi 创建的项目基本结构
pnpm run dev
手动根目录下添加一个tsconfig.json文件 学习ts 相关可以看这个文档
{
"root": true,
"compilerOptions": {
"target": "ES5", //指定目标语法
"moduleResolution": "node", //指定当前ts如何看待一个文件
"jsx": "react", //jsx 转化成react
"esModuleInterop": true,
"downlevelIteration": true,
"sourceMap": true,
"baseUrl": ".",
"paths": {
"@/*": ["src/*"],
"encodeHooks": ["./packages/hooks/src/index.ts"],
"encode-hooks": ["./packages/hooks/src/index.ts"],
"encodeHooks/lib/*": ["./packages/hooks/src/*"],
"encode-hooks/lib/*": ["./packages/hooks/src/*"]
},
"allowSyntheticDefaultImports": true,
"skipLibCheck": true,
"strictNullChecks": true,
"importHelpers": true
},
"exclude": ["node_modules", "lib", "es", "dist", "example"]}
多包的结构一般要配置两套ts规范,大包里面消费的是基本的规范tsconfig.json,子包基于大包创建一套自身的规范
根目录下创建tsconfig.pro.json,这个文件是我们后面会在子包中引用的规范
{
"extends": "./tsconfig.json",
"exclude": ["node_modules", "lib", "es", "dist", "**/__tests__", "**/__test__", "**/demo", "example","gulpfile.js"]}
添加构建工具配置
根目录下创建 webpack.common.js
module.exports={
output:{
libraryTarget:'umd',
globalObject:'this',
},
mode:'production',
resolve:{
extensions:['.json','.js']
}
}
dumi 文档相关的配置
根目录下创建一个config 文件夹 包含config.ts menus.ts 文件
babel-plugin-import :实现按需加载
config.ts
import {menus} from './menus'export default { exportStatic: {}, nodeModulesTransform: { type: 'none', exclude: [], }, history: { type: 'hash' }, extraBabelPlugins: [ [ 'babel-plugin-import', { libraryName: '@alifd/next', style: false, }, 'fusion', ], ], mode: 'site', title: 'react hooks', favicon: '/avatar.png', logo: '/logo.png', dynamicImport: {}, manifest: {}, hash: true, alias: { encodeHooks: process.cwd() + '/packages/hooks/src/index.ts', }, //引入站点 resolve: { includes: ['docs', 'packages/hooks/src'], }, links: [ { rel: 'stylesheet', href: 'https://unpkg.com/@alifd/theme-design-pro@0.6.2/dist/next-noreset.min.css', }, { rel: 'stylesheet', href: '/style.css' }, ], //导航栏的配置 navs: [ { title: '指南', path: '/guide' }, { title: 'Hooks', path: '/menus' }, ], //指向的docs文件夹下的路径,默认Index menus: { '/': [ { title: '首页', path: 'index', }, ], '/guide': [ { title: '介绍', // 左侧导航栏内容 path: '/guide', }, ], '/menus': menus, }, };
config 文件夹下的menus.ts 文件
menus.ts
export const menus = [ { title: '状态', // children: ['useToggle'], }, ];
新建docs 文件夹,包含index.md 、 guide /index.md文件夹
index.md
---title: 首页hero: image: /short-logo.png desc: React 业务 Hooks actions: - text: 指南 link: /guide - text: Hooks 列表 link: /hooksfooter: Copyright (c) © 2023 by encode studio, All Rights Reserved---## ✨ 特性- 可靠的代码健壮:使用 Typescript 构建,提供完善的类型定义文件- 完善的文档能力:支持文档记录,支持 demo 演示- 完整的测试用例:配套完整的测试用例,帮助您提升项目健壮性## 📦 安装```bash$ pnpm install --save encode-hooks# or$ yarn add encode-hooks```## 🔨 使用```tsimport { useToggle } from 'encode-hooks';```
guide /index.md
# encode-hooksReact 业务 Hooks## ⛰️ 能力支持### 1. 可靠的代码健壮使用 Typescript 构建,提供完善的类型定义文件### 2. 完善的文档能力支持文档记录,支持 demo 演示### 3. 完整的测试用例配套完整的测试用例,帮助您提升项目健壮性## 🌟 设计目的在前端项目开发中,我们通常有着各种各样可以复用的业务场景,如何能够将重复的代码量转为可复用的开发工具,是判断一个程序员编码水平及代码能力的衡量因素之一。但如何实现代码复用,也是前端开发同学乃至前端架构师都老生常谈的一个问题。除此之外,很多同学在平时的开发中只是实现最基本的页面开发,对于构建工具的使用,测试用例的编写都少有涉及,在前端 `gulp`、`grunt`、`webpack`、`esbuild`、`SWC`、`vite`、`trubo` 等构件工具越出越多的背景下,如何掌握和选择这些框架,也是能够体现是否到达高级前端开发工程师水平的一个衡量依据。因此,这里我们通过以 `React` 为前端框架,`React Hooks` 作为核心产出,从 0 ~ 1 手把手搭建一个前端业务 `Hooks` 库,从产品设计、框架选择、架构设计到最终的编码落地,通过一个完整的产品实现,解决如何提升代码复用的问题。## ⚒️ 技术选型### 包管理工具 -- pnpm作为基础包,选择社区内更推崇的`pnpm`作为包管理工具,主要原因有:1. `pnpm`安装速度更快,磁盘空间利用率高;2. `pnpm`的`lock`文件适用于多个单一子功能的模块,且能保证每个模块的依赖不耦合;3. 打包产物清晰,打包后产物确保全部为静态站点资源;### 构建工具 -- webpack & gulp1. 最终产物为多个基础子功能模块的耦合,选择`gulp`这种流程式的构建工具,能够更清晰的表达构建流程;2. 选择常用的`webpack`作为构建产物的声明式接入方式;### 静态文件打包工具 -- dumi就目前前端社区而言,`dumi`是当之无愧的为组件研发而生的静态站点解决方案;### 测试工具 -- jest`jest`功能全面,资料丰富,能够很好地支撑原子化集合的工具函数;## 其他### 生成`CHANGELOG`参考[conventional-changelog-cli](https://www.npmjs.com/package/conventional-changelog-cli),全局安装`conventional-changelog-cli`:```bashnpm install -g conventional-changelog-clipnpm run changelog```## 📧 联系- **GitHub**: <https://github.com/LiangHaley?tab=repositories></br>
public 下存放图片,静态图片路径默认指向public
至此静态文档框架已经搭建完成,接下来开启packages 源码的编写
packages/hooks 里面创建 package.json,当你引用的时候会自动判断是那种类型就引入相应的包,去相应格式的入口文件
{
"name":"demo-hooks",
"version": "0.0.1",
"main": "./lib/index.js",//默认引入的入口
"module": "./es/index.js", //esmoudle 规范
"types": "./lib/index.d.ts",//类型声明的文件
"unpkg": "dist/encodeHooks.js",//unpkg 入口
"scripts": {
"build":"gulp"
},
"files": [
"dist",
"lib",
"es",
"metadata.json",
"package.json",
"README.md"
],
"dependencies": {
"@babel/runtime": "^7.21.0",
"dayjs": "^1.9.1",
"intersection-observer": "^0.12.0",
"js-cookie": "^2.x.x",
"lodash-es": "^4.17.21",
"resize-observer-polyfill": "^1.5.1",
"screenfull": "^5.0.0",
"tslib": "^2.4.1"
},
"peerDependencies": {
"react": "^16.8.0 || ^17.0.0 || ^18.0.0"
},
"devDependencies": {
"@alifd/next": "^1.20.6",
"@ant-design/icons": "^5.0.1",
"@types/js-cookie": "^2.x.x",
"@types/lodash-es": "^4.17.7",
"antd": "^5.2.1",
"jest-websocket-mock": "^2.1.0",
"mockjs": "^1.1.0",
"react-drag-listview": "^0.1.6",
"react-json-view": "^1.21.3"
},
"engines": {
"node": ">=8.0.0"
},
"license": "MIT"
}
我们打包是gulp 结合 webpack 进行打包:
根目录下创建gulpfile.js
const gulp = require('gulp');const babel = require('gulp-babel');
const ts = require('gulp-typescript');
const del = require('del');gulp.task('clean', async function () {
await del('lib/**');
await del('es/**');
await del('dist/**');
});
gulp.task('cjs', function () {
return gulp.src(['./es/**/*.js'])
.pipe(babel({configFile: '../../.babelrc'}))
.pipe(gulp.dest('lib/'));
});
gulp.task('es', function () {
const tsProject = ts.createProject('tsconfig.pro.json', {
module: 'ESNext',
});
return tsProject.src().pipe(tsProject()).pipe(babel()).pipe(gulp.dest('es/'));});gulp.task('declaration', function () { const tsProject = ts.createProject('tsconfig.pro.json', { declaration: true, emitDeclarationOnly: true, }); return tsProject.src().pipe(tsProject()).pipe(gulp.dest('es/')).pipe(gulp.dest('lib/'));});gulp.task('copyReadme', async function () { await gulp.src('../../README.md').pipe(gulp.dest('../../packages/hooks'));});exports.default = gulp.series('clean', 'es','cjs','declaration','copyReadme');
根目录下配置.babelrc
module.exports = { extends: [ 'encode-fe-eslint-config/typescript/react','prettier',],};
子目录 packages/hooks 中创建gulpfile.js 并且继承外层根目录下的
packages/hooks/gulpfile.js
const gulp = require('gulp');const commonConfig = require('../../gulpfile');exports.default = gulp.series(commonConfig.default);
子目录 packages/hooks 下创建 tsconfig.json
{"extends": "../../tsconfig.json","compilerOptions": { "rootDir": "src", "composite": true }}
子目录 packages/hooks 下创建 tsconfig.pro.json
{
"extends": "../../tsconfig.pro.json", //继承大包的内容,大包子包共用同一套规范
"compilerOptions": {
"rootDir": "src"
}
}
然后pnpm run init 安装相关的依赖 pnpm run build 则将我们子包的的内容打包到es 文件夹下
这里面有两处问题:
1)pnpm run init 的时候可能会报错依赖拉不下来 删除pnpm-lock.yaml重新拉即可
2)pnpm run build 会报打包文件不匹配或找不到是因为外层package.json 中 script build 配置不对
由"build": "pnpm -r --filter=./packages/* run build", 改为
"build": "pnpm -r --filter=./packages/hooks run build", 重新npm run build 即可
此时构建基本的执行流程已经配置完成,gulp 打包的产物是 Esmoudle common.js 我们项目中引用的是es lib 文件夹下的内容
此时我们要发包的话要在packages/hooks 下 package.json 中添加
"files": [
"dist",
"lib",
"es",
"metadata.json",
"package.json",
"README.md"
],
发包时只会发布files里面的文件,没有files 包里面的所有内容都会被发布,一般发布构建之后的产物
webpack 构建
将gulp 打包的内容 es lib 内容产出到dist 下,别人用可以直接import {} from 'demoHooks'
packages/hooks
webpack.config.js
const merge = require('webpack-merge');
const common = require('../../webpack.common.js');
const path = require('path');
module.exports = merge(common, {
entry: './es/index.js',
output: {
filename: 'encodeHooks.js',// 输出的文件名称
library: 'encodeHooks', //dist 文件放在cdn 上别人可以通过这个引用到我们的包
path: path.resolve(__dirname, './dist'), }
});
配置添加webpck-cli 命令
package.json
"build":"gulp && webpack-cli" // 先gulp 打包 在 webpack-cli 构建
打包完的dist 目录的内容,
webpack 报错解决
Invalid configuration object. Webpack has been initialised using a configuration object that does not match the API schema.
- configuration has an unknown property 'reslove'. These properties are valid:
解决方案property reslove书写错误 正确为 resolve 这种一般单词拼写错误
我们引入npm 包一般两种方式
umd commonjs esm import xxx from ''demoHooks" 通过安装包用
通过在线的链接用 unpkg https:www.unpkg.com/ dist cdn js