Webpack 簡易打包與 Eva.js 試用

Lastor
Code 隨筆放置場
12 min readSep 6, 2021

這篇主要是 Eva.js 試用的採坑心得,而主要的坑都是出在 Webpack 上面……

這陣子想學一下 H5 遊戲開發相關的技能,在 Google 了一些情報與知識點之後,挑了阿里巴巴技術團隊開源出來的 Eva.js 來玩玩。這是一套專門給前端工程師開發 2D 互動小遊戲的框架,渲染引擎基於 pixi.js。淘寶內部團隊已經有許多實戰經驗。

至於為什麼選這套,而不選別的呢? 單純因為剛好看到這套的相關 demo ,看起來夠簡單,能在幾小時內完成、快速體驗,結果沒想到在 Webpack 搞了老半天。

Eva.js

先來講講 Eva.js 吧,用起來的感覺確實滿易懂的,但因為最近才開源出來讓大家用,相關討論跟 demo 都還很少,如果是第一次接觸 Web Game 想要直接拿來實戰可能需要再觀望一下。

如果是已經有相關製作經驗的,可以評估看看,這套淘寶內部已經有很多實戰經驗,不過並沒有所有的功能都開源出來的樣子。由於是華文體系的套件,有問題也能直接用中文去 Github 上發問。

這邊主要是跟著這篇教學,製作了一個簡單的案例:
掘金:所有前端都要看的2D游戏化互动入门基础知识

完成的頁面與 Github 也放上來,可以直接對照著看。
- Github
- 結果 Preview

內容上,是一個簡單的 Canvas 2D 元件,渲染出一張愛心圖片,然後添加一個循環的左右位移動畫,最後附加上一個 click 事件,點擊愛心之後會彈出一個 alert。

有興趣試試的,可自行參閱上面連結的教學,但環境設置之類的那篇並沒有寫,需要自己另外參看 Eva.js 的技術文件。還要有 Node.js 與 npm 體系以及webpack 的技術基礎,跟一些慧根,因為絕對會踩坑 (苦笑)。

接下來分享的都是文件沒有提的坑。

由於這套是給前端工程師使用,似乎預設你會依附在 Vue 或是 React 框架上,所以 webpack 打包部分沒有製作懶人機制,如果不使用這些框架,webpack 就得自己架了。當然,要用最近比較新的 Vite 之類也是可以的。

官方另外有提供的 webpack 腳手架 start-demo,想迴避 webpack 的也可以選擇用這個,但依賴的包也頗大的,並不亞於 Vue 跟 React 就是。我個人是選擇自己設置 webpack,畢竟本來目的就是學習,能順便摸摸 webpack 自然是很好的。

如果實在不想使用 Node 環境搞 webpack,Eva.js 也是有提供 CDN 的。官方文件也有給上 CDN 的網址,但不是每個模塊都有,需要自己去觀察命名規則,猜其他模塊的 CDN 網址,這邊就直接把規則列出來吧。

// 核心
$ npm i @eva/eva.js
CDN: //unpkg.com/@eva/eva.js@1.1.x/dist/EVA.min.js
// 渲染器
$ npm i @eva/plugin-renderer
CDN:
(略)/@eva/plugin-renderer@1.1.x/dist/EVA.plugin.renderer.min.js

CDN 是使用 unpkg.com,僅需在 @eva/ 前墜名後面加上模塊名稱即可。後面是版本號,最後在 dist/ 後面再用 dot 的方式寫一次模塊名,要注意 EVA 是大寫。

於 HTML 引入後,所有功能都會包在變數 EVA 底下,各模塊可以根據名稱推敲出來。

例如官方文件上關於 plugin-renderer 的引入範例。

import { RendererSystem } from '@eva/plugin-renderer'

使用 CDN 的話,是放在 EVA.plugin.renderer 底下。

const { RendererSystem } = window.EVA.plugin.renderer

然後要注意的是,官方關於 plugin-renderer 渲染模塊的引入範例,npm 只有安裝主模塊,但 CDN 卻有引入 PixiJS 以及 renderer-adapter 模塊。

用 npm 拉的話,會內包 PixiJS,但 renderer-adapter 模塊並沒有自帶。如果沒有從 npm 上一併拉下來,後續的操作 Eva.js 並不會噴錯,但會渲染不出任何東西,這個真的頗坑的,當下找很久才找到……

Eva.js 的部分大概就先這樣吧,CDN 版的模塊引入方式可以參看我的 Github 上的 public/index_cdn.jspublic/index.html,HTML 文件上有註解,可自行把需要的跟不需要的註解掉。npm 版本可以參看 src/index.js

CDN 與 npm 版本,主要只差在引入方式的不同,後續的 code 是一樣的。而 npm 版本的 Webpack 問題,下面繼續來聊聊。

Webpack

相信應該大部分前端都是作為終端用戶,其實很少會碰到需要自己設置 webpack 情況,但如果想更提升自己的能力,早晚都躲不掉 webpack。(當然,也可以用別的打包工具)

先簡單說說個人對 webpack 的理解,傳統方法上,如果想讓瀏覽器能跑 npm 套件,排除做套件的人有弄 CDN 之外,最暴力的方法就是直接把那包套件下載下來放到專案裡,然後一起推到 Server 上。

但如果該套件有依賴其他 npm 套件,而其他套件又有依賴其他套件,那就會非常的頭痛。而 webpack 主要在做的事,就是把拆分的 script 檔給合併回同一個檔案,所有需要的 code 都打包到同一個檔案之後,瀏覽器自然就能運行了。

有打包工具之後,就可以更自由的用模塊化的思路,在 node 環境來寫 code,透過 import / export 把檔案串起來,編輯器的 Intellisense 也能順利工作。同時,不會像暴力法那樣,誤拉到很多根本沒用到的 code 佔空間。

這個 Eva.js 的體驗專案,就需要 webpack 幫忙把它合併回去,讓瀏覽器能夠運行。由於目的單純,其實 webpack 設定理論上也很單純,理論上只要跟其他預處理器一樣,指定來源的 source 檔,然後指定要 output 到哪就行了。

// webpack.config.js
const path = require('path')
module.exports = {
context: path.resolve(__dirname, 'src'), // 指定要打包的根目錄
entry: './index.js', // 指定 main 進入點檔案
output: {
path: path.resolve(__dirname, 'dist'), // output 路徑
filename: 'index.js', // output 檔名
},
}

這邊不需要太進階的 webpack 功能,理論上一切都非常簡單,然而實際 build 的時候,直接噴了一堆 Error……

Error: Can't resolve 'path' in 'E:\...\node_modules\@eva\...webpack < 5 used to include polyfills for node.js core modules by default. This is no longer the case. Verify if you need this module and configure a polyfill for it.

一開始看到一堆 Error 訊息,真的瞬間不知所措,心想… 這不應該啊?? 後來才注意到,原因以及解決方法,其實 webpack 都寫在 Error 訊息上了。

大致上來說,webpack 5 開始不再內包 node 的核心模塊,像是 path、fs 那些,而這些模塊也不屬於 npm module,所以只要用到了,webpack 就不知道該怎麼打包它們。

解決方法也寫在後面,Eva.js 的渲染模塊依賴 pixi.js 而 pixi.js 有使用到 path 與 url 這兩個 node 模塊,錯誤訊息提示我們要另外安裝這兩個模塊的 npm 版本。

If you want to include a polyfill, you need to:
- add a fallback 'resolve.fallback: {
"path": require.resolve("path-browserify")
}'
- install 'path-browserify'

path 模塊的 npm 版本叫做 path-browserify 而 url 則維持原名 url,把它們從 npm 上安裝下來之後,根據提示訊息,在 config 上添加指令,從內容上看起來,概念應該是類似 catch error,讓 webpack 對該模塊名報錯時,告訴 webpack 改用 npm 模塊。

// webpack.config.js
module.exports = {
resolve: {
fallback: {
path: require.resolve('path-browserify'),
url: require.resolve('url/'),
},
},
}

搞定這兩個之後,就能正常 build 了。如果沒其他追求,可用就好,那到這邊就可以告個段落。

Webpack config devtool

webpack 可以正常打包之後,首當其衝的一個煩人問題是,Chrome 會出現一整排關於 source map 的黃字警告,說它找不到 map 檔。這玩意是一份給瀏覽器的索引檔,讓瀏覽器知道編譯前的檔案情況,讓開發時可以更人性化。

例如說,編譯完成之後的 script,行數一定跟原本我們寫的不一樣,那 debug 時就會出現問題,瀏覽器如果照編譯後的 script 去回報 error line 的位置,那行數鐵定會跟我們實際的位置不一樣,這樣 debug 起來非常的痛苦。

所以需要另外設定 webpack 在 development 模式時,要如何生成 source map,詳細可以參看 webpack 官方文件。這邊其實頗麻煩,因為提供的選項非常多,需要花時間理解一下。

大致上來說,就是熱更新速度與 map 品質的抉擇,不生 map 一定是最快的。最後我是選了 cheap-module-source-map ,各模式的差異網上很多人介紹的很詳細,想了解的話直接 Google 就可以了。

// webpack.config.js
module.exports = {
mode: 'development',
devtool: 'cheap-module-source-map',
}

指定完 devtool 使用何種模式生成 map 之後,需另外設定這是開發模式,不然 webpack 會默認產品模式也使用這設定,但一般來說,產品模式是不希望生成 map 的。

處理 index.html

相信在架 webpack 的過程,應該都會注意到,我主要編譯的對象是 script,理所當然把 script 都放到 src 資料夾下。那 index.html 跟 css 這類的檔案該放哪呢? 是一個有趣的問題。

直接把 html 跟 css 都放到 src,會發現依照目前設定,webpack 並不會處理它們,畢竟現在只設定了跟 js 相關的部分。

有一個很單純的解決思路,是直接把 html 跟 css 直接放到 dist 裡面,但這會造成流程上的困難,因為我們通常會把 dist 設為 git 忽略項目。同時,dist 是輸出區,可以預期裡面的檔案會變動很大,非常有可能因為誤操作,結果把 html 給刪了。

這個問題,相信解決的思路應該有很多種,而我這邊想到的是很直覺的方式,直接模仿 React、Vue 的架構,把 html、css 另外放在 pubilc 資料夾,讓 git 管理,在 webpack build 之後,在把它們 copy 到 dist 資料夾。

很肯定的是,這想法 100% 不會只有我有,所以循線 Google 很直接的就能找到一個叫 copy-webpack-plugin 的 npm 套件,來將這個操作自動化。

// webpack.config.js
module.exports = {
context: path.resolve(__dirname, 'src'),
entry: './index.js',
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'index.js',
},
plugins: [
new CopyPlugin({
patterns: [
{ from: '../public/index.html', to: '' },
{ from: '../public/style.css', to: '' },
],
}),
],
}

這邊有個坑要注意一下,from 指定的路徑是以原本 context 所設定的 src 資料夾作為 root,由於 public 不是放在 src 底下,所以路徑需要注意一下。再來 to 的目標也同理,是以 output 指定的資料夾為 root,如果不需要改檔名,直接給空字串就好。

build 前自動清空編譯資料夾

最後來處理一個也比較讓人煩躁的問題,這類預處理編譯跟 FTP 上傳都很類似,都是一種「單向輸出」的方式,並不會自動進行雙向比對。

舉個例子來說,原本我有個檔案叫做 scriptA.js,開發途中覺得這命名不好,想換個名子叫做 scriptB.js,更名之後再 build,會發現 scriptA.js 仍舊殘留在 dist 資料夾中,變成一個幽靈文件。

處理上,在 build 前先清空 dist 是比較簡單的方式,當然,一定也有巨人幫我們把輪子做好了,循線 Google 就能找到一個叫做 clean-webpack-plugin 的 npm 套件。

如果只希望單純的在 build 前將 dist 全清空,就不需要太複雜的設定,直接接上 webpack 就可以了。

// webpack.config.js
module.exports = {
plugins: [
new CleanWebpackPlugin(),
],
}

這個套件其實可以做的操作還不少,像是 Eva.js 官方的 start-demo 的設定方式,就走的是另一種思路。他是選擇把 index.html 這類檔案,直接放在輸出資料夾,然後設定 clean-webpack-plugin 只變更 main.js,也就是說,利用了這套件的機制,來縮小輸出資料夾的掌控範圍。

// webpack.config.js from Eva.js start-demo
module.exports = {
plugins: [
new CleanWebpackPlugin({
root: __dirname + '/docs',
cleanStaleWebpackAssets: false,
cleanOnceBeforeBuildPatterns: ['main.js'],
}),
],
}

webpack 的相關設定很活,社群也很大,各種自己專案的需求,可能其他人也都碰到過,大多時候只要 Google 一下,關鍵字下對,都能找到合適的解決方案。

--

--

Lastor
Code 隨筆放置場

Web Frontend / 3D Modeling / Game and Animation. 設計本科生,前遊戲業 3D Artist,專擅日本動畫與遊戲相關領域。現在轉職為前端工程師,以專業遊戲美術的角度涉足 Web 前端開發。