为组内实现一个公有通用的组件库,解放反复劳动力,提高效率,让你的代码被更多小伙伴应用。

本文是笔者总结的一篇对于构建组件库的一些教训和思考,心愿在我的项目中有所帮忙。

注释开始...

初始化一个根底我的项目

生成根底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.tswebpack.dev.tswebpack.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.outputlibrary.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.tsentry入口文件

// 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技术学苑
好好学习,天天向上!