dayjs 分享

作者: 小雨小雨丶 | 来源:发表于2018-09-12 16:51 被阅读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)

    相关文章

      网友评论

      • 朱昆iamkuncom:rollup会将undefined替换为void 0, 所以为了代码的可读性 没有必要刻意写成 void 0
        小雨小雨丶:嗯 后来查看编译后的代码 知道了这块

      本文标题:dayjs 分享

      本文链接:https://www.haomeiwen.com/subject/fdwbyftx.html