Open gogoend opened 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,前端生涯又开始了(虽然至今我还是觉得我只是个会做网页的设计师)
毕业后我来了北京。到现在作为正式员工(2020年4月),我去过两个公司,都是初创公司,用的技术栈基本都是Vue.js这一套,通过Vue CLI工具即可搭建一个脚手架(空白项目/项目模板),不用任何配置,在初始化过后,脚手架就可以被启动了,然后我们可以在此基础上进行开发。
vue init webpack
之后我们进入项目目录,可以看到如下的文件结构:
事实上,我们在此关心的内容并不多,仅仅是了解一下目录结构,以及该项目使用到了哪些依赖包。
package.json包含有当前项目的信息。
由此我们可以了解到,我们使用Vue CLI工具创建的项目:
注:在最终打出来的生产环境包中仅包含运行依赖项,不会包含开发依赖项。
上面我们所看的是Vue脚手架初始项目的依赖,使用脚手架靠发的最终产品实质上是一个面向最终用户、基于Vue的项目,而我们的目标是构建一个面向开发者的JavaScript的库。
事实上,我们开发自己的库并不用像上面的脚手架一样需要很多依赖,准确来说其实可以不必任何开发依赖或是运行依赖。我们完全可以不借助任何前端工程化工具,手写代码,直接发布。唯一的问题是代码可能会稍显冗余,或不太严谨,或者不管遇到什么问题都得手动改代码。
由于前端工程化是趋势,本文的目的也主要是学习这些工程化工具。因此,借助上文中我们所提及的一些工具:
我们就可以构建一个无需任何运行依赖项即可运行的库。
既然有了对Vue脚手架初始项目文件结构及其依赖项的大致了解,我们是不是就可以依葫芦画瓢,来实现一个自己的库呢?
这里,以我之前所写的Anikyu这个库为例来进行介绍。
cd 到将要用于存储项目的空目录
使用命令
npm init
填写一些信息,生成一个package.json
package.json 文件的全部内容已经展现在命令行窗口中,其中包含有在上文没有提及的一些字段:
import Anikyu from 'anikyu'
)这样我们就创建了一个空白项目。
如果我们不需要使用工程化工具,只需在当前目录下创建一个index.js文件(对于package.js的入口文件),然后将代码写在其中即可。
与此同时,别忘了在项目文件夹里初始化一个Git仓库,以确保项目文件出现问题时可以从仓库中找回。
可以将下文提及的相关开发依赖复制到package.json中devDependencies字段下,之后运行npm i
来直接安装;或者在命令行中手动运行npm i --save-dev
+依赖名称 进行安装。(由于在中国直接访问npm的速度很慢,因此这里使用了cnpm)
"eslint": "^6.8.0"
ESLint 相关依赖以及配置可以先全局安装ESLint,然后进入项目文件夹,通过eslint --init
来初始化。
下图是初始化结束后发生的相关变化。
"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/cli": "^7.8.3",
"@babel/core": "^7.8.3",
"@babel/polyfill": "^7.8.3",
"@babel/preset-env": "^7.8.3"
(暂未了解)
通过上文我们对Vue CLI脚手架的了解,我们也可以在我们的库中规划出文件结构。
Anikyu是这样规划的:
Anikyu库代码使用ES6语法来进行组织,例如引入(import ... from ...)、导出(export ...)、类(class)。
由于Anikyu核心代码之前已经写好(我这里就不再从头写一遍了),因此我们可以将项目中src目录拷贝到新项目根目录下。
同时也修改一下package.json中的入口文件为src/anikyu.js。此时这就是一个未进行打包的、由很多零散文件所组成的库。如果你的浏览器支持运行ES Module,那你将能够在浏览器中直接运行这个库。
执行器是Anikyu计算补间的核心,目标对象中值的计算、改变,以及事件的触发也由执行器来进行。
缓动函数来自ECharts中的相关示例。
Anikyu是一个动画对象,那对于动画状态的监听(例如监听动画帧的请求、动画的结束)使用和事件相类似的机制会更好一些。
Anikyu类基于EventDoer类,继承关系如图所示:
EventDoer类似浏览器中自带的EventTarget对象,可以为Anikyu对象添加事件监听。当Anikyu示例的某一动画阶段正在请求帧或是播放完成的时候,能够触发相关事件监听函数。
Anikyu类则用于控制动画的播放过程,包括暂停、继续、废弃等等。
包含了一些常用工具,如计算CSS实际值、事件触发、生成范围内随机数、数值限制、时间获取。
当前仅包含了requestAnimationFrame的polyfill,以兼容IE9浏览器。
该文件是Anikyu的出口,用于进行混入polyfill等操作。
如果你早前对Webpack进行过全局安装(即只需在运行框/cmd.exe中输入webpack不会报找不到命令),那在这一步骤中,你只需在命令行中输入:
webpack ./src/anikyu.js
即可完成打包,打包好的文件默认保存在dist目录下,文件名为main.js。
通过这种方式打包,我们发现以下几个问题:
因此,我们还需要对Webpack进行深入配置。
( 参考Webpack官网 - Authoring Libraries )
此时,我们在根目录创建一个webpack.config.js,在其中写入Webpack配置。
这里的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
命令,文件即可开始打包。
上一步中打的包符合umd规范,能够在浏览器中通过传统的script标签进行引入。但根据我的观察,很多类库(如Vue.js、Three.js)都提供了支持ES Module的包,事实上这似乎也正在成为一种趋势。经过本人各种百度,貌似让Webpack打出ES Module包的方法是引入EsmWebpackPlugin
扩展(来自@purtuga/esm-webpack-plugin包)。
我们将该扩展引入到webpack.config.js中
在plugins字段中引入,libraryTarget改为var。之后我们再进行打包,即可打包出ES Module包,实际测试,一切正常。
在之前安装依赖的过程中,我们已经安装过了babel-loader
,和其它各种各样的loader一样,它处理的是js文件(虽然Webpack原生支持处理js,但相关不兼容老旧浏览器的代码并没有经过转译过程)。
在webpack.config.js中的module字段里添加rule,表示遇上js文件时就使用babel进行处理。
我认为,在浏览器环境下,原生支持ES Module的浏览器必然也支持Anikyu中所使用的相关ES6特性,因此ES Module包我没有使用Babel,仅对umd包使用Babel。
这是Anikyu的配置,但对于其具体配置详情本人目前暂无了解。
请参阅 Config Files · Babel 。
{
"presets": [
[
"@babel/preset-env",
{
"useBuiltIns": "usage",
"targets": {
"browsers": ["last 2 versions", "ie >= 9"]
}
}
]
]
}
在配置完成后,我们可以重新运行webpack进行打包,此时Babel就可以对不兼容老旧浏览器的代码进行转译,使得库可供不支持ES6等特性的浏览器使用。
行文至此,Anikyu库的开发、打包实际上已经可以告一段落,但开发过程中有的地方还可以继续优化,例如代码风格可能还不够规范、每次运行打包都要输入webpack不太方便。
那接下来的步骤我们就对这些细节进行优化。
上面的步骤中我们已经安装好了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里设置忽略。
在我们日常开发项目过程中,例如我们要打包一个项目,一般会执行npm run build
,而不是手动执行webpack
。要对此进行配置,我们需要修改package.json中的script。
{
"test": "echo \"Error: no test specified\" && exit 0",
"lint": "eslint src --ext js",
"build": "webpack-cli"
}
在这里,我们添加了lint和build两个脚本,原有的test脚本由于我不会配置,所以先让它 return 0
。
到此,Anikyu库的开发已经结束,假设现在经过测试,一切运行正常,我们就可以对包进行发布。
到npm | build amazing things自行注册一个npm账号(如有可跳过)
在命令行运行npm login
,输入登录凭据来登录
登陆完成后,执行npm publish
,即可将包发布到NPM
(尴尬了,刚刚不慎把这里的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
前期没做过深入了解,只是发现Webpack用途广泛(日常项目以及招聘信息等很多地方都提到这个,顺便也学一下),于是就尝试使用Webpack来进行打包。但Webpack似乎有个问题,在IE8下,某个地方会提示无法使用Object.defineProperty方法(可能和Vue.js不支持IE8是同一个原因),导致报错。但我自己写的代码里似乎没用到Object.defineProperty方法,定眼一看,代码似乎来自于Webpack(后来在官网发现Webpack的确只能够兼容到IE9),遂考虑更换一个打包工具。
到后面瞄了一下Three.js、Vue.js和ECharts的打包工具,用的都是Rollup。在某个分支里我也对其进行了配置,但问题就在于:
最终,遂暂时弃疗Rollup。
历时两天,整篇文章终于写完了。这大概就是从0到1搭建一个前端工程化项目的过程吧。不过本人目前还是处于很菜的状态,不是很确定上文的相关表达有没有很准确、很通俗易懂。如果你对Anikyu这个库很中意,不妨拿来用一用吧。发现问题,欢迎提issue。
Anikyu是一个面向本人兴趣编程的项目,而不是面向公司KPI编程的项目。
代码
Anikyu源代码仓库:anikyu