让前端飞

dayjs 分享

2018-09-12  本文已影响3人  小雨小雨丶

<p style='text-align: center; color: #599FF0;'>Dayjs </p>

分享内容

  1. 框架结构
  2. 涉及技术
  3. 部分代码解读
  4. 总结

概述

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.js
const 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
    }
})();

rollup-cli: https://github.com/a13821190779/rollup-cli


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)
            }
        }

总结

  1. 结构简洁,清晰,在不失可用性的前提下,尽可能的简化体积
  2. 代码抽离彻底,无重复,无强耦合
  3. 使用了自动化测试,和版本控制、发布,大大解放人力

在阅读过程中,学到了如何规划中小型框架结构,如何使用自动化工具,也掌握了关于时间的一些细节,比如格林威治时间。
当然,dayjs也不是完美的,也有一些地方可以简化、优化,比如 undefined 替换为 void 0 以节约字节(rollup会将undefined替换为void 0)

上一篇下一篇

猜你喜欢

热点阅读