嘗試經過封裝一個庫來學習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編程的項目。

相關文章
相關標籤/搜索