從 laravel-mix 0.8 升級到 1.4 版的記錄與坑
相信現在許多在使用 Laravel 的朋友,應該都是使用 laravel-mix 來處理前端的相關資源的打包, 不知道大家覺得 laravel-mix 用起來的感覺是如何?我自己覺得用起來真的沒有想像的好用, 雖然 Laravel 官方有提供文件說明操作的方法,但是我自己覺得它的擴展性很差,而且又把它再抽象化了。
webpack 雖然設定起來比較繁瑣,但其實你會遇到的問題,可能很多人都有遇過了, 也或許有人提出相關 issue,所以要尋找一些解決方式我自己覺得相對容易, webpack2 (現在是 webpack 3 囉!) 的文件相較於 webpack1 也真的友善許多 :smirk:, 老實說,我第一次看到 webpack 的文件真的覺得是天書,對新手開發者來說真的是一個痛點! 但是現在官方文件真的寫得清楚容易多了,像是 Guides 的部分我覺得就蠻不錯的, 對岸也有人將它翻譯成簡體文件,閱讀英文較吃力的朋友不妨可以參考。
升級的一些地雷
最近把公司專案的 laravel-mix 從 0.8.1 升級至目前最新的 1.4.2 版本,遇到的一些問題, 稍微做個整理和記錄,如果有打算升級 laravel-mix 的朋友,若有遇到相關問題可以參考看看 :metal:。
地雷一 - cross-env
比較早之前 Laravel 內的 package.json
版本的 scripts 大概是這樣:
{"scripts": {"dev": "node node_modules/cross-env/dist/bin/cross-env.js NODE_ENV=development node_modules/webpack/bin/webpack.js --progress --hide-modules --config=node_modules/laravel-mix/setup/webpack.config.js"}}
透過 cross-env
來解決跨平台設定 NODE_ENV
的問題,但是較早的版本是引用 node_modules
內的 js,
在新版的部分都會另外安裝 cross-env
,所以設定要改成如下:
{"scripts": {"dev": "cross-env NODE_ENV=development node_modules/webpack/bin/webpack.js --progress --hide-modules --config=node_modules/laravel-mix/setup/webpack.config.js"}}
地雷二 - mix.extract() & mix.autoload()
在前端,我們可能會用了不少的 library 來幫助我們做到介面上的美化,或是與使用者的互動,
但這些套件全部一次打包到一個 xxx.min.js
其實不是非常的好,當網站在載入的時候,會浪費非常多時間,
而且當你只是修改一小部分的 JavaScript 就必須要整個重新打包(包含那些 library),使用者載入網站就必須要再重新下載整個 bundle 檔,原先 cache 的檔案就沒用了!
laravel-mix 提供了一個 extract
的方法,讓你可以把一些 libray 給抽取出來,把它打包成 vendor.js
,
就是一個 Code Splitting 的概念,但是請記得按照以下順序把你的 JavaScript 給 include 到你的網頁中:
<script src="/js/manifest.js"></script><script src="/js/vendor.js"></script><script src="/js/app.js"></script>
一般在這裡會把 Vue
、axios
、lodash
等等 library 給提取出來,我自己在這裡也把 jQuery
也給提出出來,就遇到了一些問題,你必須還要再寫一個 mix.autoload
的方法,讓你所需要的 library 自動被 include(因為所用到的 Bootstrap 依賴 jQuery),目前在 Laravel 官方文件還看不到 autoload
方法的說明,請直接到 laravel-mix - Autoloading 查看文件!
mix.js('resources/assets/js/app.js', 'public/js/app.js').extract(['jquery','bootstrap-sass','axios','vue','lodash']).autoload({jquery: ['$', 'window.jQuery', 'jQuery', 'jquery'],vue: ['Vue', 'window.Vue'],axios: ['axios', 'window.axios'],lodash: ['lodash', 'window._']}).sass('resources/assets/sass/app.scss', 'public/css/app.css');
autoload
內是一個 object 的形式,每個 key 是你 library 名稱,value 的地方就是代表這些形式的變數會直接載入對應(key)的 libray;意思是,當我某個地方用到了 $
或是 jQuery
,webpack 會自動幫我把 library 給準備好,讓我可以使用,同理像是 Vue
、axios
也是一樣的道理。
但是!我這麼做了之後發現一載入到網站還是找不到 jQuery,在看 error log 得到了以下的訊息:
// other error messages...} else {// Browser globalsfactory(jQuery);}
解決方式是需要把 window 修改為 global:
- window.$ = window.jQuery = require('jquery');+ global.$ = global.jQuery = require('jquery');
如果有遇到這樣問題的朋友,或許可以試試看這個方法。
地雷三:如果用到了 async/await
我在撰寫 Vue Component 的時候,用到了 async/await
的語法,在 development 模式下沒什麼問題,但是跑 CI 卻炸掉了,
在本機沒有先測試過 npm run production
的結果,在 CI 是 run production 的 script,後來 debug 找到的錯誤訊息是:
ReferenceError: regeneratorRuntime is not defined
大概知道是因為 async/await
的關係,造成在 runtime 的時候錯誤,我的解決方式再去額外安裝 babel 的 transform-runtime:
$ yarn add babel-plugin-transform-runtime --dev$ yarn add babel-runtime
為了確保在 development 和 production 都可以運作正常,所以兩個都安裝,接著建立一個 .babelrc
:
{"plugins": ["transform-runtime"]}
在 plugins
內把 transform-runtime
加入進去就沒問題了。
一些使用的心得
在 Laravel 官方 mix 文件有個部分是 Copying Files & Directories,這裡主要是 mix 提供了方法讓你可以去 copy 檔案到你要的目錄下,如果是 copy node_modules
下的檔案,我覺得這有點多此一舉,其實可以用更容易的方式來做,應該可以節省一些 compile 的時間 :stuck_out_tongue:!
善用 path.join
Nodejs 內建的 path 的模組非常好用,這裡我們使用 join
的方法直接連結到 node_modules
目錄下的檔案:
const path = require('path');mix.styles([path.join(__dirname, 'node_modules/sweetalert/dist/sweetalert.css'),path.join(__dirname, 'node_modules/select2/dist/css/select2.min.css'),],'public/css/all.css');mix.combine([path.join(__dirname, 'node_modules/sweetalert/dist/sweetalert.min.js'),path.join(__dirname, 'node_modules/select2/dist/js/select2.min.js'),],'public/js/all.js');
這樣就可以不需要再多寫 mix.copy()
啦!順帶一提,mix.combine()
也是新的方法,類似 scripts()
,但是使用 combine
方法可以幫你將必要的檔案給 bundle 並 minify,詳細可以參考文件:Concatenation and Minification。
其他更多相關的方法我就沒使用過了,未來有機會用到或是遇到相關的問題再補上來。
如果文章內有任何說明上的錯誤,歡迎提出告訴我,希望大家可以一起多交流!:v:
補充
感謝 Recca 大大關於設定 processCssUrls
的建議,今天嘗試修改了一下,又減少了 bundle 的時間了!使用 processCssUrls
需要特別注意的是,像是 Bootstrap 內的 fonts 需要自己從 node_modules
copy 到 public
資料夾下,如果有用到 fontawesome
的話也要記得從 node_modules
複製到 public
資料夾,或者是其他相關你有用到圖片等等,這邊我踩的地雷是,要注意你複製到目的路徑,因為我在 extract()
有將 bootstrap-sass
給提取出來,所以在 copy 時必須對應到相對的資料夾下:
mix.copyDirectory('node_modules/bootstrap-sass/assets/fonts/bootstrap','public/fonts/vendor/bootstrap-sass/bootstarp');mix.copyDirectory('node_modules/font-awesome/fonts','public/fonts/vendor/font-awesome');
在 app.scss
內我們有 import font-awesome
,這裡有用到一個 $fa-font-path
變數,原先的路徑是:
~font-awesome/fonts/
但是在 bundle 完後,發現網頁上並沒有正確顯示,對應了上面的 mix.copyDirectory
的路徑必須改成:
../fonts/vendor/font-awesome
路徑會修改成是因為 bootstrap 現在是被 bundle 在 vendor.js
內,所以當他需要這些用到 font-awesome 就必須去調整在 $fa-font-path
的路徑,當然這不是絕對,你必須要根據你自己的專案目錄去調整相對路徑,我自己另外也調整了好幾個其他相關套件文件路徑。