为组内实现一个公有通用的组件库,解放反复劳动力,提高效率,让你的代码被更多小伙伴应用。
本文是笔者总结的一篇对于构建组件库的一些教训和思考,心愿在我的项目中有所帮忙。
注释开始...
初始化一个根底我的项目
生成根底package.json
npm init -y
装置我的项目指定须要的插件
npm i webpack webpack-cli html-webpack-plugin @babel/core @babel/cli @babel/preset-env webpack-dev-server --save-dev
webpack
官网反对ts
编写配置环境,不过须要装置几个插件反对,参考官网configuration-languages,咱们明天应用ts
配置webpack
。
配置反对配置文件ts
npm install --save-dev typescript ts-node @types/node @types/webpack
批改tsconfig.json
{ "compilerOptions": { ... "module": "commonjs", "target": "es5", ... }}
在.eslintrc.js
中的相干配置,配置env.node:true
,具体参考如下
module.exports = { "env": { "browser": true, "es2021": true, "node": true }, "extends": [ "eslint:recommended", "plugin:@typescript-eslint/recommended" ], "parser": "@typescript-eslint/parser", "parserOptions": { "ecmaVersion": "latest", "sourceType": "module" }, "plugins": [ "@typescript-eslint" ], "rules": { "@typescript-eslint/no-var-requires": 0, "@typescript-eslint/no-non-null-assertion": 0, }}
在根config
目录新建webpack.common.ts
、webpack.dev.ts
、webpack.prod.ts
// webpack.common.tsimport * as path from 'path';import * as webpack from 'webpack';// 配置devServerimport 'webpack-dev-server';const configCommon: webpack.Configuration = { entry: { app: path.join(__dirname, '../src/index.ts') }, output: { path: path.join(__dirname, '../dist'), // clean: true }, module: { rules: [ { test: /\.js$/, use: ['babel-loader'], exclude: /node_modules/ }, { test: /\.ts(x?)$/, use: [ { loader: 'babel-loader' }, { loader: 'ts-loader' } ], exclude: /node_modules/ } ] }, resolve: { extensions: ['.tsx', '.ts', '.js'] }, devServer: { static: { directory: path.join(__dirname, '../example') // 批改默认动态服务拜访public目录 } }};module.exports = configCommon;
webpack.dev.ts
// config/webpack.dev.tsimport * as path from 'path';import * as webpack from 'webpack';const { merge } = require('webpack-merge');const HtmlWebpackPlguin = require('html-webpack-plugin');const webpackCommon = require('./webpack.common');const devConfig: webpack.Configuration = merge(webpackCommon, { devtool: 'inline-source-map', plugins: [ new HtmlWebpackPlguin({ inject: true, filename: 'index.html', // 只能是文件名,不能是xxx/index.html 会造成页面模版加载ejs解析谬误 template: path.resolve(__dirname, '../example/index.html'), title: 'example' }) ]});module.exports = devConfig;
webpack.prod.ts
// webpack.prod.tsconst { merge } = require('webpack-merge');import * as webpack from 'webpack';const commonConfig = require('./webpack.common');const prodConfig: webpack.Configuration = merge(commonConfig, { mode: 'production'});module.exports = prodConfig;
咱们在根目录下创立webpack.config.ts
// webpack.config.tstype PlainObj = Record<string, any>;const devConfig = require('./config/webpack.dev');const prdConfig = require('./config/webpack.prod');module.exports = (env: PlainObj, argv: PlainObj) => { // 开发环境 argv会获取package.json中设置--mode的值 if (argv.mode === 'development') { return devConfig; } return prdConfig;};
在package.json
中
"scripts": { "test": "echo \"Error: no test specified\" && exit 1", "start": "webpack serve --mode development", "build": "webpack --mode production" },
运行npm run start
咱们看下src/index.ts
const domApp = document.getElementById('app');console.log(11122);domApp!.innerHTML = 'hello word';
以上所有的这些根本都是为了反对ts
环境,还有反对ts
可配置webpack
环境
当初咱们试图将一些通用的工具函数奉献给其余小伙伴用了。
在src
新建其余工具函数,例如在之前咱们所用到的timerChunk
分时函数
timerChunk.ts
分时函数
// timerChunk.ts// 分时函数module.exports = (sourceArr: any[] = [], callback: (args: unknown) => void, count = 1, wait = 200) => { let ret: any, timer: any = null; const renderData = () => { for (let i = 0; i < Math.min(count, sourceArr.length); i++) { // 取出数据 ret = sourceArr.shift(); callback(ret); } }; return () => { if (!timer) { // 利用定时器每隔200ms取出数据 timer = setInterval(() => { // 如果数据取完了,就清空定时器 if (sourceArr.length === 0) { clearInterval(timer); ret = null; return; } renderData(); }, wait); } };};
memorize
缓存函数
// src/memorize.ts/** * @desption 缓存函数 * @param {*} callback * @returns */export const memorize = (callback: callBack) => { let cache = false; let result: unknown = null; return () => { // 如果缓存标识存在,则间接返回缓存的后果 if (cache) { return result; } else { // 将执行的回调函数赋值给后果 result = callback(); // 把缓存开关关上 cache = true; // 革除传入的回调函数 callback = null; return result; } };};
isType.ts
检测数据类型
/** * @desption 判断根底数据类型以及援用数据类型,代替typeof * @param {*} val * @returns */export const isType = (val: string | object | number | any[]) => { return (type: string) => { return Object.prototype.toString.call(val) === `[object ${type}]`; };};
formateUrl.ts
获取url
参数
import { isType } from './isType';/** * @desption 将url参数转换成对象 * @param params * @returns */export const formateUrl = (params: string) => { if (isType(params)('String')) { if (/^http(s)?/.test(params)) { const url = new URL(params); // 将参数转换成http://localhost:8080?a=1&b=2 -> {a:1,b:2} return Object.fromEntries(url.searchParams.entries()); } // params如果为a=1&b=2,则转换成{a:1,b:2} return Object.fromEntries(new URLSearchParams(params).entries()); }};
lazyFunction.ts
懒加载函数
import { memorize } from './memorize';/** * @desption 懒加载可执行函数 * @param {*} factory * @returns */export const lazyFunction = (factory: callBack) => { const fac: any = memorize(factory); const f = (...args: unknown[]) => fac()(...args); return f;};
hasOwn.ts
判断一个对象的属性是否存在
const has = Reflect.has;const hasOwn = (obj: Record<string, any>, key: string) => has.call(obj, key);export { hasOwn };
mergeDeep.ts
深拷贝对象
import { isType } from './isType';import { memorize } from './memorize';/** * @desption 深拷贝一个对象 * @param {*} obj * @param {*} targets */export const mergeDeep = (obj: object, targets: object) => { const descriptors = Object.getOwnPropertyDescriptors(targets); // todo 针对不同的数据类型做value解决 const helpFn = (val: any) => { if (isType(val)('String')) { return val; } if (isType(val)('Object')) { return Object.assign(Object.create({}), val); } if (isType(val)('Array')) { const ret: any[] = []; // todo 辅助函数,递归数组外部, 这里递归能够思考用分时函数来代替优化 const loopFn = (curentVal: any[]) => { curentVal.forEach((item) => { if (isType(item)('Object')) { ret.push(helpFn(item)); } else if (isType(item)('Array')) { loopFn(item); } else { ret.push(item); } }); }; loopFn(val); return ret; } }; for (const name of Object.keys(descriptors)) { // todo 依据name取出对象属性的每个descriptor const descriptor = descriptors[name]; if (descriptor.get) { const fn = descriptor.get; Object.defineProperty(obj, name, { configurable: false, enumerable: true, writable: true, get: memorize(fn) // 参考https://github.com/webpack/webpack/blob/main/lib/index.js }); } else { Object.defineProperty(obj, name, { value: helpFn(descriptor.value), writable: true }); } } return obj;};
咱们在src
中创立了以上所有的工具函数
咱们在src/index.ts
将下面所有的工具函数导入
// const domApp = document.getElementById('app');// console.log(11122);// domApp!.innerHTML = 'hello word';export * from './memorize';export * from './lazyFunction';export * from './hasOwn';export * from './getOrigin';export * from './formateUrl';export * from './mergeDeep';export * from './isType';
当初须要打包不同环境的lib
,通用就是umd
,cjs
,esm
这三种形式
次要要是批改下webpack.config.output
的library.type
,参考官网outputlibrary
咱们在config
目录下新建一个webpack.target.ts
import * as webpack from 'webpack';const prdConfig = require('./webpack.prod');const { name } = require('../package.json');enum LIBARY_TARGET { umd = 'umd', cjs = 'cjs', esm = 'esm'}const targetUMD: webpack.Configuration = { ...prdConfig, output: { ...prdConfig.output, filename: 'umd/index.js', library: { name, type: 'umd' } }};const targetCJS: webpack.Configuration = { ...prdConfig, output: { ...prdConfig.output, filename: 'cjs/index.js', library: { name, type: 'commonjs' } }};const targetESM: webpack.Configuration = { ...prdConfig, experiments: { outputModule: true }, output: { ...prdConfig.output, filename: 'esm/index.js', library: { type: 'module', export: 'default' } }};const libraryTargetConfig = new Map([ [LIBARY_TARGET.umd, targetUMD], [LIBARY_TARGET.cjs, targetCJS], [LIBARY_TARGET.esm, targetESM]]);module.exports = libraryTargetConfig;
在webpack.config.ts
引入webpack.target.ts
// webpack.config.tstype PlainObj = Record<string, any>;const devConfig = require('./config/webpack.dev');const libraryTargetConfig = require('./config/webpack.target');module.exports = (env: PlainObj, argv: PlainObj) => { console.log(argv); // 开发环境 argv会获取package.json中设置--mode的值 if (argv.mode === 'development') { return devConfig; } return libraryTargetConfig.has(argv.env.target) ? libraryTargetConfig.get(argv.env.target) : libraryTargetConfig.get('umd');};
而后咱们在package.json
中配置不同模式打包
"scripts": { "test": "echo \"Error: no test specified\" && exit 1", "start": "webpack serve --mode development", "build:umd": "webpack --mode production --env target=umd", "build:esm": "webpack --mode production --env target=esm", "build:cjs": "webpack --mode production --env target=cjs", "build": "npm run build:umd && npm run build:esm && npm run build:cjs" },
当咱们顺次执行npm run build
在example
目录下新建测试index.ts
,同时记得批改webpack.dev.ts
的entry
入口文件
// example/index.ts// okimport * as nice_utils from '../src/index';// umd// const nice_utils = require('../dist/umd/index.js');// cjs// const { nice_utils } = require('../dist/cjs/index.js');// esm error// import nice_utils from '../dist/esm/index.js';const appDom = document.getElementById('app');appDom!.innerHTML = 'hello, 欢送关注公众号:Web技术学苑,好好学习,天天向上!';console.log(nice_utils);console.log('formateUrl:', nice_utils.formateUrl('http://www.example.com?name=Maic&age=18'));console.log('hasOwn:', nice_utils.hasOwn({ publictext: 'Web技术学苑' }, 'publictext'));console.log('isType:', nice_utils.isType('Web技术学苑')('String'));
咱们运行npm run start
,测试运行下example
是否ok
我发现esm
打包进去的竟然用不了,这就很坑了,难道是模块应用的问题?
然而其余两种貌似是ok
的
npm 公布组件
咱们当初将这包公布到npm
上吧
npm run build
生成dist
包,并且批改package.json
文件的main
,指定到dist/umd/index.js
下
{ "name": "@maicfir/nice_utils", "version": "1.0.4", "description": "一个好用的工具类库", "main": "dist/umd/index.js", "types": "src/types/global.d.ts", ...}
npm login
- 输出本人
npm
账户和明码 - 输出本人明码后,须要输出邮箱,而后npm会给你邮箱发个
code
,把code
输出即可
- 输出本人
npm publish
- 查看npm上是否胜利,具体能够查看nice_utils
总结
- 利用
webpack5
配置打包ts
环境,次要是让webpack5
配置文件反对ts
- 组织
webpack5
打包不同library.type
,反对打包成不同type
,umd
,cjs
,ejs
三种类型 - 编写具体工具类函数
- 将本人写的工具类公布到
npm
或者私服上,让工具类变成通用工具代码 - 本文示例code-example
欢送关注公众号:Web技术学苑
好好学习,天天向上!