目标
最后打包的结果可以达到以下功能
- 同时支持
CommonJs
和ESModule
两种模块系统 - 浏览器支持使用
script
标签引入 ESModule
支持tree-shaking
- 支持
TypeScript
接下来将逐步完成每一个功能点
开始吧
说明
- 源代码编写采用 TypeScript
创建 Package
-
创建一个文件夹,名字叫它为 uodule 吧 😄
-
进入 uodule 文件夹
-
初始化 package.json
mkdir yyds
cd yyds
npm init -y
- 局部安装
TypeScript
(全局也可以)
npm install typescript -D
- 初始化 tsconfig.json
npx tsc --init
- 精简 tsconfig.json
留下这些即可
{
/* Visit https://aka.ms/tsconfig to read more about this file */
"compilerOptions": {
"target": "ESNext",
"module": "ESNext",
"declaration": true,
// 因为最后是采用 Rollup 进行打包
// 所以这里只需要生成类型文件即可
"emitDeclarationOnly": true,
"declarationDir": "types",
"removeComments": false,
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"strict": true,
"skipLibCheck": true,
"moduleResolution": "node"
}
}
package.json
{
"name": "uodule",
"version": "0.0.1",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC",
"devDependencies": {
"typescript": "^4.7.4"
}
}
编写代码
简单编写几个功能函数:
padZero
: 对小于 10 的整形数字进行补 0isMobileByUa
: 通过 UserAgent 判断当前设备是否移动端
padZero.ts
/**
* 向 < 10 的整形数值进行补 0
* @param {number} n
* @returns {string}
*/
export function padZero(n: number) {
if (!Number.isInteger(n)) {
return n
}
return (n < 10 ? `0${n}` : String(n))
}
isMobileByUa.ts
/**
* 通过检测设备 ua
* 判断是否是移动端设备
* @returns {boolean}
*/
export function isMobileByUa(userAgent = window.navigator.userAgent) {
const reg = /(Android|iPhone|Windows Phone|iPad|webOS|BlackBerry|mobile)/i
return reg.test(userAgent)
}
index.ts
export * from './padZero'
export * from './isMobileByUa'
当前的目录结构如下:
├── src
│ ├── index.ts
│ ├── isMobileByUa.ts
│ └── padZero.ts
├── tsconfig.json
├── package-lock.json
└── package.json
模块导出
在构建之前,需要先了解清楚,我们究竟要如何去进行构建?
-
需要兼容 CommonJs 系统
打包一份 CJS 格式的文件
-
需要兼容 ESModule 系统
打包一份 ESM 格式的文件
-
需要兼容 Script 标签引入
打包一份 UMD 格式的文件,关于什么是 UMD,可以参考文章最下面的
参考文章
如果要打包 UMD 格式的文件的话,那么就可以把单独的 CJS 的文件给省略了。因为 UMD 格式的文件兼容 CJS 并且会向浏览器 window 对象中挂载全局对象。
那么最后只需要构建以下文件:
- ESM 格式文件
- UMD 格式文件
- TS 类型文件
关于文件导出
可以参考 package.json 中的 exports、main、module 字段 文章。不再细述,直接给出配置 + 简单解释
{
"name": "uodule",
"version": "0.0.1",
"description": "",
// 旧版本的 Nodejs 文件入口
"main": "index.js",
// 类型文件入口
"types": "index.d.ts",
// 优先级高于 main 字段,支持条件导出
// 下述意思: 支持 exports 字段的 Nodejs
// 在遇到使用 import 关键字导入模块时,取 index.mjs 文件
// 使用其他导入方式时,都取 index.js 文件
"exports": {
"import": "./index.mjs",
"default": "./index.js"
},
"scripts": {
...
},
"devDependencies": {
...
}
}
构建配置
往 package.json 中增加三个 script 命令
# 生成 TS 类型文件
npm set-script es "tsc"
# 构建 ESM、UMD 格式的文件
npm set-script build "rollup -c"
# 串行(同步)执行上述两命令
npm set-script generate "npm run es && npm run build"
- rollup 配置
需要先安装依赖
rollup
构建工具@rollup/plugin-typescript
用于识别 TS 的 rollup 插件rollup-plugin-delete
用于删除文件的 rollup 插件rollup-plugin-dts
用于集合 TS 类型文件的 rollup 插件rollup-plugin-terser
用于压缩文件的 rollup 插件tslib
@rollup/plugin-typescript 需要的依赖(同等依赖)
npm install rollup @rollup/plugin-typescript rollup-plugin-delete rollup-plugin-dts rollup-plugin-terser tslib -D
最终的 rollup.config.js
配置文件如下:
import typescript from '@rollup/plugin-typescript'
import dts from 'rollup-plugin-dts'
import del from 'rollup-plugin-delete'
import { terser } from 'rollup-plugin-terser'
import { defineConfig } from 'rollup'
const publicConfig = {
format: 'umd',
name: 'uodule'
}
const config = defineConfig([
{
input: 'src/index.ts',
output: [
{
file: 'index.js',
...publicConfig
},
{
file: 'index.min.js',
...publicConfig,
plugins: [
terser()
]
}
],
plugins: [
typescript({
declaration: false,
target: "ES5"
})
]
},
{
input: 'src/index.ts',
output: {
file: 'index.mjs',
format: 'esm'
},
plugins: [
typescript({
declaration: false
})
]
},
// 归并 .d.ts 文件
{
input: 'types/index.d.ts',
output: {
file: 'index.d.ts',
format: 'es'
},
plugins: [
// 将类型文件全部集中到一个文件中
dts(),
// 在构建完成后,删除 types 文件夹
del({
targets: 'types',
hook: 'buildEnd'
})
]
}
])
export default config
- 执行构建命令
npm run generate
发布 Package
此处省略,请参考关于 发布 npm 包
的文章
使用
本文章的 uodule 已经发布到 npm 和上传至 github,仅供学习参考
Nodejs
CommonJs
const { padZero } = require('uodule')
const uodule = require('uodule')
console.log(padZero)
console.log(uodule)
/*
[Function: padZero]
{
isMobileByUa: [Function: isMobileByUa],
padZero: [Function: padZero]
}
*/
ESModule
import * as uodule from 'uodule'
import { padZero } from 'uodule'
// 这种引入方式将会报错,因为源码中并没有使用 export default
import uodule from 'uodule'
console.log(padZero)
console.log(uodule)
/*
[Function: padZero]
[Module: null prototype] {
isMobileByUa: [Function: isMobileByUa],
padZero: [Function: padZero]
}
*/
前端项目
用法和 ESModule 一样,并在构建的时候支持 tree-shaking
浏览器
<script src="https://unpkg.com/uodule@0.0.4/index.min.js"></script>
发布到 npm 上的包,都可以使用 unpkg CDN 服务,具体请看 unpkg