gogoend / blog

blogs, ideas, etc.
MIT License
9 stars 2 forks source link

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

Open gogoend opened 4 years ago

gogoend commented 4 years ago

代码

Anikyu源代码仓库:anikyu

gogoend commented 4 years ago

还是先说几句

当年大三,我开始学前端的时候,是从Dreamweaver开始的。

那时的天,是多么的蓝;

那时的水,是多么的清;

那时候的前端,还是jQuery的天下(据说Vue.js才刚出现);

那时的我,还在搞Cinema 4D+After Effects+Dreamweaver。

所以Dreamweaver对于我来说只是个用于拿来设计的工具。

直到到了大四,开始找工作。

问个CSS,Flex?Grid?啥。。。

问个基础的JavaScript,ajax?原型链?闭包?call/apply/bind?ES6?啥玩意儿?

来再深入一点,jQuery?Bootstrap?Vue.js?React?Angular?Axios?ECharts?ElementUI?Antd?iView?微信小程序?Webpack?Babel?Node.js?靠,这是些啥牛鬼蛇神?

我只知道Dreamweaver,大概知道HTML+CSS+JavaScript的一点点皮毛,外加些许Three.js。。。外加大一时学过但基本忘得差不多的 C 和 C++ ,以及大三搞期末作业随便学的PHP、MySQL。

没错,我连jQuery都不会;至于Three.js,曾记得大一的时候隔壁软工专业的一同学拿来和我分享过,后来做毕设的时候脑子一抽,想把3D和网页进行结合,选了这个题目,想起来看了看。

最后终于找到了一个实习。UI设计师,不过还好,实习生时间非常多,公司时间也十分宽松,朝九晚六,准时上班准时下班,我就有了大把时间,哈哈。做设计师期间,我又评估了一下,发现各种甲方需求真的很令人头疼,Photoshop、illustrator和InDesign等等各种稿子改来改去(其实本人比较喜欢After Effects、Premiere和Cinema 4D,没把优势发挥出来),emmmmmm,我可能是个没有感情的改稿子机器。。。遂重拾Dreamweaver,前端生涯又开始了(虽然至今我还是觉得我只是个会做网页的设计师)

gogoend commented 4 years ago

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

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

vue init webpack

image

之后我们进入项目目录,可以看到如下的文件结构: image

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

脚手架文件结构大致了解

参考自vuejs-templates webpack

package.json 相关内容

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

image

由此我们可以了解到,我们使用Vue CLI工具创建的项目:

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

gogoend commented 4 years ago

准备开始

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

事实上,我们开发自己的库并不用像上面的脚手架一样需要很多依赖,准确来说其实可以不必任何开发依赖或是运行依赖。我们完全可以不借助任何前端工程化工具,手写代码,直接发布。唯一的问题是代码可能会稍显冗余,或不太严谨,或者不管遇到什么问题都得手动改代码。

由于前端工程化是趋势,本文的目的也主要是学习这些工程化工具。因此,借助上文中我们所提及的一些工具:

我们就可以构建一个无需任何运行依赖项即可运行的库。

既然有了对Vue脚手架初始项目文件结构及其依赖项的大致了解,我们是不是就可以依葫芦画瓢,来实现一个自己的库呢?

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

gogoend commented 4 years ago

NPM配置

初始化项目

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

使用命令

npm init

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

package.json 文件的全部内容已经展现在命令行窗口中,其中包含有在上文没有提及的一些字段:

这样我们就创建了一个空白项目。

如果我们不需要使用工程化工具,只需在当前目录下创建一个index.js文件(对于package.js的入口文件),然后将代码写在其中即可。

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

gogoend commented 4 years ago

安装一些开发依赖

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

image

ESLint相关

    "eslint": "^6.8.0"

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

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"

Babel相关

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

(暂未了解)

gogoend commented 4 years ago

规划项目文件结构

通过上文我们对Vue CLI脚手架的了解,我们也可以在我们的库中规划出文件结构。

Anikyu是这样规划的:

规划代码结构

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等操作。

gogoend commented 4 years ago

使用Webpack进行打包

webpack试用

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

webpack ./src/anikyu.js

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

通过这种方式打包,我们发现以下几个问题:

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

因此,我们还需要对Webpack进行深入配置。

打包为符合umd规范的包

( 参考Webpack官网 - Authoring Libraries

此时,我们在根目录创建一个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
    }
}

之后我们在根目录下,不带参数直接执行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包,实际测试,一切正常。

gogoend commented 4 years ago

在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等特性的浏览器使用。

gogoend commented 4 years ago

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

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

gogoend commented 4 years ago

配置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
}

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

gogoend commented 4 years ago

配置 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

gogoend commented 4 years ago

发布到NPM

到此,Anikyu库的开发已经结束,假设现在经过测试,一切运行正常,我们就可以对包进行发布。

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

从此,世界各地的人将能够通过npm install anikyu --save来安装Anikyu依赖。

gogoend commented 4 years ago

开发中经历的一些小事情

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

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

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

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。

gogoend commented 4 years ago

完结撒花~

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

提示一下

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