尝试经过封装一个库来学习JavaScript(ES6)相关特性以及相关构建工具

介绍

Anikyu 是由本人所封装的一个补间动画库,基于JavaScript,能够为一个指定对象中的数值建立连续补间动画。html

Anikyu 源于我平常所写的一个demo —— 使用JavaScript实现动画。代码参考Tween.js的功能进行编写,缓动函数来自ECharts。前端

代码

Anikyu源代码仓库:anikyuvue

先从传说中的Vue CLI脚手架开始

毕业后我来了北京。到如今做为正式员工(2020年4月),我去过两个公司,都是初创公司,用的技术栈基本都是Vue.js这一套,经过Vue CLI工具便可搭建一个脚手架(空白项目/项目模板),不用任何配置,在初始化事后,脚手架就能够被启动了,而后咱们能够在此基础上进行开发。node

vue init webpack

image

以后咱们进入项目目录,能够看到以下的文件结构:
imagewebpack

事实上,咱们在此关心的内容并很少,仅仅是了解一下目录结构,以及该项目使用到了哪些依赖包。git

脚手架文件结构大体了解

参考自vuejs-templates webpackgithub

  • build/ - webpack相关配置
  • config/ - 有关项目的一些配置
  • src/ - 写项目业务代码的地方
  • static/ - 保存静态文件的地方
  • test/ - 写测试的地方
  • .babelrc - Bebel配置
  • .editorconfig - 编辑器配置
  • .eslintrc.js - ESLint配置
  • .eslintignore - ESLint忽略规则
  • .gitignore - Git忽略规则
  • index.html - index.html文档模板
  • package.json - 项目信息、构建脚本及依赖项
  • README.md - 自述文件

package.json 相关内容

package.json包含有当前项目的信息。web

image

  • name - 项目名称
  • version - 版本
  • description - 项目描述
  • author - 做者
  • private - 私有标识(设为true以防止项目被意外发布到npm)
  • scripts - 脚本
  • dependencies - 运行依赖项
  • devDependencies - 开发依赖项
  • engines - 引擎版本
  • browserslist - 浏览器列表

由此咱们能够了解到,咱们使用Vue CLI工具建立的项目:vue-router

  • 在生产环境运行时依赖于vue和vue-router这两个包。
  • 在开发环境下须要的包不少(主要是和前端工程化相关的内容),大体概括一下包括几个大类:shell

    • Babel
    • ESLint
    • Vue
    • Webpack
    • 其余工具

注:在最终打出来的生产环境包中仅包含运行依赖项,不会包含开发依赖项。

准备开始

上面咱们所看的是Vue脚手架初始项目的依赖,使用脚手架靠发的最终产品实质上是一个面向最终用户、基于Vue的项目,而咱们的目标是构建一个面向开发者的JavaScript的库。

事实上,咱们开发本身的库并不用像上面的脚手架同样须要不少依赖,准确来讲其实能够没必要任何开发依赖或是运行依赖。咱们彻底能够不借助任何前端工程化工具,手写代码,直接发布。惟一的问题是代码可能会稍显冗余,或不太严谨,或者无论遇到什么问题都得手动改代码。

因为前端工程化是趋势,本文的目的也主要是学习这些工程化工具。所以,借助上文中咱们所说起的一些工具:

  • Babel - 用于将ES6代码转译为ES5代码
  • ESLint - 用于确保代码风格符合规范
  • Webpack - 用于生成最终包

咱们就能够构建一个无需任何运行依赖项便可运行的库。

既然有了对Vue脚手架初始项目文件结构及其依赖项的大体了解,咱们是否是就能够依葫芦画瓢,来实现一个本身的库呢?

这里,以我以前所写的Anikyu这个库为例来进行介绍。

NPM配置

初始化项目

cd 到将要用于存储项目的空目录

使用命令

npm init

填写一些信息,生成一个package.json
image

package.json 文件的所有内容已经展示在命令行窗口中,其中包含有在上文没有说起的一些字段:

  • main: 文件入口(即在项目中引入库时实际引入的文件,例如ES6语法 import Anikyu from 'anikyu'
  • repository: 代码仓库相关信息
  • keywords: 项目关键词(能够用于优化npm搜索)
  • license: 项目许可证

这样咱们就建立了一个空白项目。

若是咱们不须要使用工程化工具,只需在当前目录下建立一个index.js文件(对于package.js的入口文件),而后将代码写在其中便可。

与此同时,别忘了在项目文件夹里初始化一个Git仓库,以确保项目文件出现问题时能够从仓库中找回。

安装一些开发依赖

能够将下文说起的相关开发依赖复制到package.json中devDependencies字段下,以后运行npm i 来直接安装;或者在命令行中手动运行 npm i --save-dev+依赖名称 进行安装。(因为在中国直接访问npm的速度很慢,所以这里使用了cnpm)

image

ESLint相关

"eslint": "^6.8.0"

ESLint 相关依赖以及配置能够先全局安装ESLint,而后进入项目文件夹,经过eslint --init来初始化。
下图是初始化结束后发生的相关变化。
image

"eslint-plugin-import": "^2.20.1",
    "eslint-plugin-node": "^11.0.0",
    "eslint-plugin-promise": "^4.2.1",
    "eslint-plugin-standard": "^4.0.1"

若初始化配置的时候选择了相关内容,同时也会安装相关依赖(此处暂未了解,今天看项目的时候发现多了这四个依赖,建议先别复制)

Webpack相关

"webpack": "^4.41.5",
    "webpack-cli": "^3.3.10",
    "babel-loader": "^8.0.6",
    "@purtuga/esm-webpack-plugin": "^1.2.1",
    "clean-webpack-plugin": "^3.0.0"
  • webpack、webpack-cli

Webpack的核心;不知为什么彷佛两个都须要安装,不安装其中一个会报错?

  • babel-loader

用于在Webpack中对js文件使用Babel进行处理

  • @purtuga/esm-webpack-plugin

用于将库打包为符合ES Module的包

  • clean-webpack-plugin

打包以前将打包目标文件夹(/dist)进行清空

Babel相关

"@babel/cli": "^7.8.3",
    "@babel/core": "^7.8.3",
    "@babel/polyfill": "^7.8.3",
    "@babel/preset-env": "^7.8.3"

(暂未了解)

规划项目文件结构

经过上文咱们对Vue CLI脚手架的了解,咱们也能够在咱们的库中规划出文件结构。

Anikyu是这样规划的:

  • demo/ - 包含一些使用了该库的DEMO,让用户经过运行该文件夹里的文件,了解你的库能够作什么
  • dist/ - 用于保存打包后产生的文件
  • src/ - 写Anikyu代码的地方

    • polyfill/ - polyfill相关代码

      • requestAnimationFrame.js requestAnimationFrame polyfill
    • anikyu_class.js - 定义Anikyu类
    • anikyu.js - Anikyu的入口文件,同时混入polyfill
    • easing_funcs.js - 定义缓动函数
    • event_doer.js - 定义EventDoer类
    • executor.js - 定义动画执行函数
    • util.js - 定义一些工具类函数
  • CHANGELOG.md - 项目变动日志
  • LICENSE - 许可
  • README.md - 英文自述文档
  • README.zh-CN.md - 中文自述文档

规划代码结构

Anikyu库代码使用ES6语法来进行组织,例如引入(import ... from ...)、导出(export ...)、类(class)。

因为Anikyu核心代码以前已经写好(我这里就再也不从头写一遍了),所以咱们能够将项目中src目录拷贝到新项目根目录下。

image

同时也修改一下package.json中的入口文件为src/anikyu.js。此时这就是一个未进行打包的、由不少零散文件所组成的库。若是你的浏览器支持运行ES Module,那你将可以在浏览器中直接运行这个库。

image

执行器(executor.js)

执行器是Anikyu计算补间的核心,目标对象中值的计算、改变,以及事件的触发也由执行器来进行。

缓动函数(easing_funcs.js)

缓动函数来自ECharts中的相关示例。

Anikyu类(anikyu_class.js)与EventDoer类(event_doer.js)

Anikyu是一个动画对象,那对于动画状态的监听(例如监听动画帧的请求、动画的结束)使用和事件相相似的机制会更好一些。

Anikyu类基于EventDoer类,继承关系如图所示:

image

EventDoer相似浏览器中自带的EventTarget对象,能够为Anikyu对象添加事件监听。当Anikyu示例的某一动画阶段正在请求帧或是播放完成的时候,可以触发相关事件监听函数。

Anikyu类则用于控制动画的播放过程,包括暂停、继续、废弃等等。

工具函数(util.js)

包含了一些经常使用工具,如计算CSS实际值、事件触发、生成范围内随机数、数值限制、时间获取。

polyfill

当前仅包含了requestAnimationFrame的polyfill,以兼容IE9浏览器。

Anikyu(anikyu.js)

该文件是Anikyu的出口,用于进行混入polyfill等操做。

使用Webpack进行打包

webpack试用

若是你早前对Webpack进行过全局安装(即只需在运行框/cmd.exe中输入webpack不会报找不到命令),那在这一步骤中,你只需在命令行中输入:

webpack ./src/anikyu.js

便可完成打包,打包好的文件默认保存在dist目录下,文件名为main.js。
image

经过这种方式打包,咱们发现如下几个问题:

  1. 文件是默认被压缩的
  2. 引入该文件后,其中的属性、方法彷佛没法以预想的方式经过ES Module或传统script被访问到

所以,咱们还须要对Webpack进行深刻配置。

打包为符合umd规范的包

( 参考[Webpack官网 - Authoring Libraries
](https://webpack.js.org/guides...

此时,咱们在根目录建立一个webpack.config.js,在其中写入Webpack配置。

image

这里的module.exports能够接收一个配置对象(只打包一个文件),也能够接收由多个相似的配置对象组成的数组(打包多个文件)。这里咱们建立Anikyu 一种版本的两个文件 —— 通过压缩的文件(anikyu.min.js)和未经压缩的文件(anikyu.js)。两个文件都符合umd规范,即可以在不支持ES Module的浏览器中直接运行,区别仅在于代码是否被压缩。

咱们看一看配置对象,以下是未压缩的UMD版本的配置。

{
    entry: './src/anikyu.js',
    output: {
        path: path.resolve(__dirname, 'dist'),
        filename: 'anikyu.js',
        library: 'Anikyu',
        libraryTarget: 'umd',
        libraryExport: 'default',
        globalObject: 'this'
    },
    mode: 'production',
    optimization:{
        minimize: false
    }
}
  • entry - 入口文件。Webpack可以根据该文件自动层层查找在该文件中引入的文件,而后统一打包
  • output - 输出配置

    • path - 输出目录
    • filename - 输出文件名
    • library - 库的名称。用户引入库后将以该名称进行调用
    • libraryTarget - 库的目标格式。表示库可以以哪些方式导入,这里值为umd,表示库符合umd规范。
    • libraryExport - 对外暴露的库的属性
    • globalObject - 运行环境全局对象的名称
  • mode - 打包环境( development 或 production )
  • optimization - 打包优化

    • minimize - 是否压缩代码(默认为true)

以后咱们在根目录下,不带参数直接执行webpack命令,文件便可开始打包。

打包为可以经过ES Module引入的包

上一步中打的包符合umd规范,可以在浏览器中经过传统的script标签进行引入。但根据个人观察,不少类库(如Vue.js、Three.js)都提供了支持ES Module的包,事实上这彷佛也正在成为一种趋势。通过本人各类百度,貌似让Webpack打出ES Module包的方法是引入EsmWebpackPlugin扩展(来自@purtuga/esm-webpack-plugin包)。

咱们将该扩展引入到webpack.config.js中

image

在plugins字段中引入,libraryTarget改成var。以后咱们再进行打包,便可打包出ES Module包,实际测试,一切正常。

在Webpack中使用Babel

配置webpack.config.js

在以前安装依赖的过程当中,咱们已经安装过了babel-loader,和其它各类各样的loader同样,它处理的是js文件(虽然Webpack原生支持处理js,但相关不兼容老旧浏览器的代码并无通过转译过程)。
image

在webpack.config.js中的module字段里添加rule,表示赶上js文件时就使用babel进行处理。

我认为,在浏览器环境下,原生支持ES Module的浏览器必然也支持Anikyu中所使用的相关ES6特性,所以ES Module包我没有使用Babel,仅对umd包使用Babel。

配置.babelrc

这是Anikyu的配置,但对于其具体配置详情本人目前暂无了解。

请参阅 Config Files · Babel

{
  "presets": [
    [
      "@babel/preset-env",
      {
        "useBuiltIns": "usage",
        "targets": {
          "browsers": ["last 2 versions", "ie >= 9"]
        }
      }
    ]
  ]
}

在配置完成后,咱们能够从新运行webpack进行打包,此时Babel就能够对不兼容老旧浏览器的代码进行转译,使得库可供不支持ES6等特性的浏览器使用。


行文至此,Anikyu库的开发、打包实际上已经能够告一段落,但开发过程当中有的地方还能够继续优化,例如代码风格可能还不够规范、每次运行打包都要输入webpack不太方便。

那接下来的步骤咱们就对这些细节进行优化。


配置ESLint

上面的步骤中咱们已经安装好了ESLint,生成了.eslintrc.js这个配置文件,其中的配置都是在执行初始化ESLint命令后根据你的选择所生成的,此时ESLint规则便已经生效。

我在该文件中的“rules”加入了额外的一些规则,以符合我本身写代码的习惯。

"rules": {
    "indent": [
        "error",
        "tab"
    ],
    "linebreak-style": [
        "error",
        "windows"
    ],
    "quotes": [
        "error",
        "single"
    ],
    "semi": [
        "error",
        "always"
    ],
    "space-before-function-paren": 1,
    "space-infix-ops": 1,
    "spaced-comment": 1
}
  • 缩进:tab
  • 换行符:windows风格
  • 引号:单引号
  • 分号:必须
  • 函数声明语句(左括号以前)前的空格:1
  • 运算符空格:1
  • 注释符号(//)后的空格:1

若有文件无需被ESLint检查,可在.eslintignore里设置忽略。

配置 NPM 脚本

在咱们平常开发项目过程当中,例如咱们要打包一个项目,通常会执行 npm run build,而不是手动执行webpack。要对此进行配置,咱们须要修改package.json中的script。

image

{
    "test": "echo \"Error: no test specified\" && exit 0",
    "lint": "eslint src --ext js",
    "build": "webpack-cli"
 }

在这里,咱们添加了lint和build两个脚本,原有的test脚本因为我不会配置,因此先让它 return 0

  • lint - 用于检查代码风格,发现问题时会报错
  • build - 用于执行代码打包

发布到NPM

到此,Anikyu库的开发已经结束,假设如今通过测试,一切运行正常,咱们就能够对包进行发布。

  • npm | build amazing things自行注册一个npm帐号(若有可跳过)
  • 在命令行运行npm login,输入登陆凭据来登陆

image

  • 登录完成后,执行npm publish,便可将包发布到NPM

image

(尴尬了,刚刚不慎把这里的demo版本发布出去了,原本当前线上版本是0.2.2,这里初始化之后默认版本是1.0.0,忘改了;不过还好我及时用 npm unpublish --force 撤回了刚刚的发布)

今后,世界各地的人将可以经过npm install anikyu --save来安装Anikyu依赖。

开发中经历的一些小事情

我想起啥的时候就写些啥吧。。。

是经过直接传入仍是使用相似事件的机制来处理动画播放期间要执行的函数?

早期开始作这个库的时候,我试过直接在配置中传入函数做为参数,例如:

new Anikyu({
    onAnimate: function(){...},
    onFinish: function(){...}
})

但这样作存在的问题是,若是须要在事件被触发后执行多个函数,这种方式不是很灵活。

正如好久之前在DOM文档里写相关事件处理函数:

window.onload = function (event){ ... }

所以我尝试让Anikyu直接继承浏览器自带的EventTarget对象(该对象提供了咱们所熟知的.addEventListener等方法)。在不一样浏览器上进行测试后,发现任何版本的IE浏览器都没法经过 new EventTarget() 的方式来调用。在继续测试、查阅文档过程当中,发现EventTarget类并不可以支持Anikyu所需的全部API。

最终我编写、模拟了一个和EventTarget类类似的EventDoer类,由Anikyu类继承。

在Anikyu实例上可这样调用:

let ani = new Anikyu(...)
ani.addEventListener('animate',function(e){
...
})

参考自EventTarget - Web APIs | MDN

是使用Rollup仍是Webpack?

前期没作过深刻了解,只是发现Webpack用途普遍(平常项目以及招聘信息等不少地方都提到这个,顺便也学一下),因而就尝试使用Webpack来进行打包。但Webpack彷佛有个问题,在IE8下,某个地方会提示没法使用Object.defineProperty方法(可能和Vue.js不支持IE8是同一个缘由),致使报错。但我本身写的代码里彷佛没用到Object.defineProperty方法,定眼一看,代码彷佛来自于Webpack(后来在官网发现Webpack的确只可以兼容到IE9),遂考虑更换一个打包工具。

到后面瞄了一下Three.js、Vue.js和ECharts的打包工具,用的都是Rollup。在某个分支里我也对其进行了配置,但问题就在于:

  1. 代码压缩配置不对
  2. Babel配置不对
  3. 打包后代码莫名运行不了

最终,遂暂时弃疗Rollup。

完结撒花~

历时两天,整篇文章终于写完了。这大概就是从0到1搭建一个前端工程化项目的过程吧。不过本人目前仍是处于很菜的状态,不是很肯定上文的相关表达有没有很准确、很通俗易懂。若是你对Anikyu这个库很中意,不妨拿来用一用吧。发现问题,欢迎提issue。

提示一下

Anikyu是一个面向本人兴趣编程的项目,而不是面向公司KPI编程的项目。

相关文章
相关标签/搜索