<p style='text-align: center; color: #599FF0;'>Dayjs </p>
分享内容
框架结构
涉及技术
部分代码解读
总结
概述
Day.js 是一个轻量的处理时间和日期的 JavaScript 库,和 Moment.js 的 API 设计保持完全一样. 如果你曾经用过 Moment.js, 那么你已经知道如何使用 Day.js
优点
和Moment.js有着相同的API和模式 不可变、持久性 提供链式调用 国际化标准 超小的压缩体积,仅仅有2kb左右 极大多数的浏览器兼容
目录:
build/ -> 构建工具 index.js -> 入口文件 rollup.config.js -> rollup配置文件 dosc/ -> 文档 en -> 英文 ... -> 等等 src/ -> 源代码 locale/ -> 各种语言 zh-cn.js -> 中文 ... -> 等等 plugin/ -> 内部提供插件 advancedFormat/ -> 特色格式化 index.js -> index.js buddhistEra/ -> 支持农历 index.js -> .. isLeapYear/ -> 是否为闰年 index.js -> .. relativeTime/ -> 相对时间方法集合,包括 to、from、toNow、fromNow index.js -> .. constant.js -> 默认常量,包括时间单位,正则 index.js -> 主文件 util.js -> 工具方法 test/ -> jest自动化测试工具 ... -> 测试用例 .babelrc -> babel配置 .eslintrc.json -> eslint配置 .gitignore -> git忽略 .npmignore -> npm忽略 .travis.yml -> travis配置 CHANGELOG.md -> 自动输出的更新日志 CONTRIBUTING.md -> 贡献文档 LICENSE -> 协议 README.md -> readme index.d.ts -> ts静态检查声明文件 karma.saucs.conf.js -> karma配置文件 package.json -> 包配置
涉及技术:
名称 | 概述 |
---|---|
rollup | JavaScript 模块打包器,dayjs中仅仅用来打包,并没有用到太多的rollup特性,比如 Tree Shaking |
jest | JavaScript 测试框架 |
Karma | 测试过程管理工具,通常用于浏览器兼容 |
travis | 持续集成服务,与github绑定,仓库中只要有新的代码,就会自动抓取。然后,提供一个运行环境,执行测试,构建,并且部署到服务器。 |
semantic-release | 版本管理,自动化发布 |
typescript | javascript超集,用来提供类型检查 |
eslint | 代码检查 |
bebel | 代码编译 |
rollup配置:
// rollup.config.jsconst babel = require('rollup-plugin-babel');const uglify = require('rollup-plugin-uglify');module.exports = config => { const { input, fileName, name } = config; return { input: { input, external: ['dayjs'], // 外部文件,不进行打包 plugins: [ babel({ exclude: 'node_modules/**' }), uglify() ] }, output: { file: fileName, format: 'umd', // 采用umd规范 name: name || 'dayjs', // 重命名全局命名 globals: { dayjs: 'dayjs' } } }; };// index.js核心代码async function build(option) { const bundle = await rollup.rollup(option.input); await bundle.write(option.output); } (async () => { try { // 打包 locale const locales = await promisifyReadDir( path.join(__dirname, '../src/locale') ); locales.forEach(l => { build( configFactory({ input: `./src/locale/${l}`, fileName: `./locale/${l}`, name: `dayjs_locale_${formatName(l)}` }) ); }); // 打包plugins const plugins = await promisifyReadDir( path.join(__dirname, '../src/plugin') ); plugins.forEach(l => { build( configFactory({ input: `./src/plugin/${l}`, fileName: `./plugin/${l}`, name: `dayjs_plugin_${formatName(l)}` }) ); }); // 打包index build( configFactory({ input: './src/index.js', fileName: './dayjs.min.js' }) ); } catch (e) { console.error(e); // eslint-disable-line no-console } })();
src部分
constant.js:内是一些英文语义变量的定义
utils.js:包含一些常用的工具方法。
padStart(string, length, pad): 补充前缀 padZoneStr(negMinuts): 转换时间格式为hh(hour):mm(minute) => 为了转换格林威治时间 monthDiff(a, b): 返回月份差,基于a absFloor(n):忽略符号的Math.floor prettyUnit(units): 统一化单位 units => unit isUndefined(s):是否为undefined
//utils.js中的monthDiff,精度 <= 月 const monthDiff = (a, b) => { // function from moment.js in order to keep the same result // 代码优化来自moment.js,反而更难理解了。 // 月份差 const wholeMonthDiff = (b.year() - a.year()) * 12 + (b.month() - a.month()); // 同化年月 const anchor = a.clone().add(wholeMonthDiff, 'months'); // 比day的大小 const c = b - anchor < 0; // => b < anchor // 锚点2 const anchor2 = a.clone().add(wholeMonthDiff + (c ? -1 : 1), 'months'); // 返回精度至day的时间差 return Number( -( wholeMonthDiff + (b - anchor) / (c ? anchor - anchor2 : anchor2 - anchor) ) ); };
index.js
export default dayjs
let L = 'en' // global locale const Ls = {} // global loaded locale const isDayjs = d => d instanceof Dayjs; const dayjs = (date, c) => { if (isDayjs(date)) { return date.clone() } const cfg = c || {} cfg.date = date return new Dayjs(cfg) // eslint-disable-line no-use-before-define }
// parseDate方法 const parseDate = (date) => { let reg if (date === null) return new Date(NaN) // => Invalid Date if (Utils.isUndefined(date)) return new Date() if (date instanceof Date) return date if ((typeof date === 'string') && (/.*[^Z]$/i.test(date)) // Z代表格林威治时间和本地时间之间的时差 && (reg = date.match(C.REGEX_PARSE))) { // ^(\d{4})-?(\d{1,2})-?(\d{0,2})(.*?(\d{1,2}):(\d{1,2}):(\d{1,2}))?.?(\d{1,3})?$/ 见下图// 结果结构 => ["整体", "括号1的匹配", "括号2的匹配", "括号3的匹配", .....] return new Date( reg[1], reg[2] - 1, reg[3] || 1, reg[5] || 0, reg[6] || 0, reg[7] || 0, reg[8] || 0 ) } return new Date(date) // timestamp }// parseLocale方法const parseLocale = (preset, object, isLocal) => { let l if (!preset) return null if (typeof preset === 'string') { if (Ls[preset]) { l = preset } if (object) { Ls[preset] = object l = preset } } else { const { name } = preset Ls[name] = preset l = name } if (!isLocal) L = l return l }
[图片上传失败...(image-e077f8-1531296814278)]
// Dayjs类 class Dayjs { constructor(cfg) { this.parse(cfg) } parse(cfg) { this.$d = parseDate(cfg.date) this.init(cfg) } init(cfg) { this.$y = this.$d.getFullYear() this.$M = this.$d.getMonth() this.$D = this.$d.getDate() this.$W = this.$d.getDay() this.$H = this.$d.getHours() this.$m = this.$d.getMinutes() this.$s = this.$d.getSeconds() this.$ms = this.$d.getMilliseconds() this.$L = this.$L || parseLocale(cfg.locale, null, true) || L } $utils() {} isValid() {} isLeapYear() {} $compare(that) {} isSame(that) {} isBefore(that) {} isAfter(that) {} year() {} month() {} day() {} date() {} hour() {} minute() {} second() millisecond() {} unix() {} valueOf() {} daysInMonth() {} $locale() {} locale(preset, object) {} clone() {} toDate() {} toArray() {} toJSON() {} toISOString() {} toObject() {} toString() {} startOf(units, startOf) {} endOf(arg) {} $set(units, int) {} set(string, int) {} add(number, units) {} subtract(number, string) {} format(formatStr) {} diff(input, units, float) { const unit = Utils.prettyUnit(units) const that = dayjs(input) const diff = this - that let result = Utils.monthDiff(this, that) switch (unit) { case C.Y: result /= 12 break case C.M: break case C.Q: result /= 3 break case C.W: result = diff / C.MILLISECONDS_A_WEEK break case C.D: result = diff / C.MILLISECONDS_A_DAY break case C.H: result = diff / C.MILLISECONDS_A_HOUR break case C.MIN: result = diff / C.MILLISECONDS_A_MINUTE break case C.S: result = diff / C.MILLISECONDS_A_SECOND break default: // milliseconds result = diff } return float ? result : Utils.absFloor(result) } }
总结
结构简洁,清晰,在不失可用性的前提下,尽可能的简化体积
代码抽离彻底,无重复,无强耦合
使用了自动化测试,和版本控制、发布,大大解放人力
在阅读过程中,学到了如何规划中小型框架结构,如何使用自动化工具,也掌握了关于时间的一些细节,比如格林威治时间。
当然,dayjs也不是完美的,也有一些地方可以简化、优化,比如 undefined 替换为 void 0 以节约字节(rollup会将undefined替换为void 0)
作者:大西瓜叔叔
链接:https://www.jianshu.com/p/9f94c9caf09f