用gulp+webpack構建多頁應用——記一次Node多頁應用的構建過程


通過參考網上的一些構建方法,當然也在開發過程中進行了一番實踐,最終搭建了一套適用於當前多頁應用的構建方案,當然該方案還處於draft版本,會在后續的演進過程中不斷的優化。

個人覺得該方案的演進過程相對於成果而言更值得記錄。但在此之前,我們先簡單介紹下項目的整體架構,這樣大家會更明白為什么要采用這樣的構建方式。下圖列出了用戶在瀏覽器中輸入url到頁面ready的過程,可以看出這是一個典型的服務端直出架構,其中作為前端工程師的我們主要關注點是放在瀏覽器端以及Node層。在Node層,我們對koa的進行了封裝,並采用了類似於eggjs的MVC架構,同時使用pug作為模板引擎,技術棧其實並不復雜。

V0.0.1:使用webpack對前端資源進行構建

在構建過程中要做什么事呢?相信不同的人有不同的見解:

  • 對靜態資源進行壓縮,減少傳輸字節;
  • 為避免瀏覽器讀取了舊的緩存文件,需要為靜態資源添加MD5戳;
  • 為CSS屬性自動添加vendor prefix;
  • ......

上面所列出的事項也是我們在構建過程中所需要考慮的。在項目早期的構建方案中,我們選擇使用webpack作為構建工具,原因其實很簡單:功能強大、用的人多,所以一拍腦袋就選擇它了。當然,webpack也確實不負眾望,通過它,我們可以像寫Node一樣直接引入其他的文件,在使用前期確實給我們帶來了很多的便捷。

但是我們的項目畢竟和webpack主流的使用場景,如React、Vue等項目還是有很大的不同之處,在使用webpack的過程中陸續出現了一些水土不服的地方,雖然都最后都通過一些方式解決了,但是這也促使我們在思考,是否有其他更合適的方案。 

問題一:

如何讓webpack打包所有資源文件?

解決方法:

webpack會將entry作為入口起點,找到所有依賴項並對其進行構建。由於webpack只認識JavaScript文件,所以對於非JavaScript文件需要使用loader將其轉為webpack能夠處理的模塊。所以說,如果某個資源文件需要被webpack構建,那么這個資源文件必須是從entry可達的。對我們的項目來說,最理想的情況是以pug模板文件作為入口,但是由於pug模板文件需要的數據是從server獲取的,而在構建階段是不可知的,所以,只能退而求其次,使用JavaScript文件作為入口。

對於不同類型的文件,我們采用了不同的策略:

  • 對於圖片,我們使用了webpack提供的require.context方法,它可以讓我們使用正則的方式來引入相應的模塊。所以我們添加了下述文件,並將其置於webpack的entry屬性中。
require.context('./public', true, /\.*\.(jpg|png|jpeg|gif|ico)$/i)
  • 對於css文件,當然也可以采用上述的方式,但是這會將所有的css編譯到一個文件中,導致生成的文件過大,從而使頁面加載耗時較長。所以我們只能選擇在相應的JavaScript文件中添加 import './xxxx.less' 來告訴webpack:你需要構建xxxx less/css文件了。這樣做功能是實現了,但是卻並不優雅,同時也會使JavaScript變得不純粹,也沒辦法被其他不用webpack作為構建工具的項目所重用了。

問題二:

在Node從server獲取數據后,會將pug模板渲染成html並將其發送回瀏覽器端,那么在模板中如何保持對靜態資源的引用呢?因為構建工具會為所有的資源文件添加MD5戳,所以我們在編碼時並不知道確切的文件名是什么。

解決方法:

利用webpack-manifest-plugin插件,它會在webpack構建完成后生成一個manifest.json文件,該文件會列出原始文件名與構建后的文件名之間的匹配關系。通過將manifest.json作為參數傳入pug的render方法中,這樣在pug模板中就可以通過類似於 img(src=manifest["logo.png"]) 的方式來保持對靜態資源的引用。

V1.0.0-beta:使用gulp+webpack對前端資源進行構建

問題總是有方法解決的,但是這樣寫起來總有一些別扭,也感覺很不優雅,這致使我們思考webpack是否真的適用於我們的項目。通過一番討論,最終決定嘗試使用gulp+webpack的方式進行構建。

  1. 利用webpack構建JavaScript資源,可以方便的利用webpack模塊的思想,使得JavaScript之間相互引用變得簡單、便捷;
  2. 利用gulp構建css, images等其他資源。

使用webpack對JavaScript進行構建時,為了不至於每次添加一個新文件,都要修改webpack的配置,所以我們寫了一個方法將所有的JavaScript都放入webpack的entry屬性中。 

function entries(globPath) {
    const files = glob.sync(globPath);
    let key, name, ext, entries = {};

    for (let file of files) {
        ext = path.extname(file);
        name = path.basename(file, ext);
        if (name.startsWith('_')) {
            continue;
        }

        entries[name] = path.join(__dirname, file);
    }
    return entries
}

使用時,只需要在globPath中輸入指定的js路徑就可以了,如 

let webpackConfig = {
    entry: entries('./public/**/*.js'),
}

而在實際的開發中我們發現,有一些JavaScript文件存在的目的就是被其他文件引用,例如一些helper方法,它們是不會作為webpack的entry存在的,所以我們在entries方法中只尋找不是以下划線(_)開頭的JavaScript文件,因此對於一些只有helper方法的JavaScript文件,只需要將其文件名以_開頭,這樣就不會被添加到webpack的entry屬性中了,這也算是該構建方案中的一個小彩蛋~~~

在使用gulp對其他資源的構建過程中,我們用到了很多成熟的gulp插件,其中我認為比較重要的是gulp-rev和gulp-rev-replace。其中gulp-rev插件用來為css、images等資源文件添加MD5戳並生成相應的manifest.json文件,同時搭配使用gulp-rev-replace插件將pug、css等文件中存在於manifest.json里的文件名進行替換,這樣我們在pug模板中引用資源文件就可以直接寫 img(src=logo.png) 而不需要像以前那么復(chou)雜(lou)了。

再說一個題外話,其實在使用gulp-rev+gulp-rev-replace插件之前,我們嘗試使用過gulp-md5-plus插件。gulp-md5-plus插件使用起來超級方便,它可以添加MD5戳,也可以替換文件名,但是該插件暫不支持添加自定義前綴的功能,而這個功能對我們確實必須的。因為在生產環境中,所有的資源文件會放到CDN上,而測試環境中的資源文件則放在對應的測試服務器上,所以說,同樣的一張圖片,在測試環境的地址可能是public/images/logo.png,而生產環境卻是//cdn.demo.com/images/logo.png,所以我們需要支持自定義前綴功能,並使用類似下述的代碼為其添加前綴 var prefix = process.env.NODE_ENV === 'production' ? '//cdn.demo.com/': '/public/' 。

 

寫在最后:

上述構建方案也許不是那么完美,但是對於當前的項目來說,確是一個相對而言較為合適的。當然也不排除后續該方案會被其他更優雅的方案所替代。但是通過這么多次的嘗試、重構,才真正體會到了什么叫“適合的才是最好的😄”。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM