前端面試之webpack篇
https://blog.csdn.net/sinat_17775997/article/details/78122999css
隨着現代前端開發的複雜度和規模愈來愈龐大,已經不能拋開工程化來獨立開發了,如react的jsx代碼必須編譯後才能在瀏覽器中使用;又如sass和less的代碼瀏覽器也是不支持的。 而若是摒棄了這些開發框架,那麼開發的效率將大幅降低。在衆多前端工程化工具中,webpack脫穎而出成爲了當今最流行的前端構建工具。 然而大多數的使用者都只是單純的會使用,而並不知道其深層的原理。但願經過如下的面試題總結能夠幫助你們溫故知新、查缺補漏,知其然而又知其因此然。html
- webpack與grunt、gulp的不一樣?
- 與webpack相似的工具還有哪些?談談你爲何最終選擇(或放棄)使用webpack?
- 有哪些常見的Loader?他們是解決什麼問題的?
- 有哪些常見的Plugin?他們是解決什麼問題的?
- Loader和Plugin的不一樣?
- webpack的構建流程是什麼?從讀取配置到輸出文件這個過程儘可能說全
- 是否寫過Loader和Plugin?描述一下編寫loader或plugin的思路?
- webpack的熱更新是如何作到的?說明其原理?
- 如何利用webpack來優化前端性能?(提升性能和體驗)
- 如何提升webpack的構建速度?
- 怎麼配置單頁應用?怎麼配置多頁應用?
- npm打包時須要注意哪些?如何利用webpack來更好的構建?
- 如何在vue項目中實現按需加載?
1. webpack與grunt、gulp的不一樣?
三者都是前端構建工具,grunt和gulp在早期比較流行,如今webpack相對來講比較主流,不過一些輕量化的任務仍是會用gulp來處理,好比單獨打包CSS文件等。前端
grunt和gulp是基於任務和流(Task、Stream)的。相似jQuery,找到一個(或一類)文件,對其作一系列鏈式操做,更新流上的數據, 整條鏈式操做構成了一個任務,多個任務就構成了整個web的構建流程。vue
webpack是基於入口的。webpack會自動地遞歸解析入口所須要加載的全部資源文件,而後用不一樣的Loader來處理不一樣的文件,用Plugin來擴展webpack功能。react
因此總結一下:webpack
gulp和grunt須要開發者將整個前端構建過程拆分紅多個`Task`,併合理控制全部`Task`的調用關係 webpack須要開發者找到入口,並須要清楚對於不一樣的資源應該使用什麼Loader作何種解析和加工web
gulp更像後端開發者的思路,須要對於整個流程瞭如指掌 webpack更傾向於前端開發者的思路面試
2. 與webpack相似的工具還有哪些?談談你爲何最終選擇(或放棄)使用webpack?
一樣是基於入口的打包工具還有如下幾個主流的:npm
從應用場景上來看:json
- webpack適用於大型複雜的前端站點構建
- rollup適用於基礎庫的打包,如vue、react
- parcel適用於簡單的實驗性項目,他能夠知足低門檻的快速看到效果
因爲parcel在打包過程當中給出的調試信息十分有限,因此一旦打包出錯難以調試,因此不建議複雜的項目使用parcel
3.有哪些常見的Loader?他們是解決什麼問題的?
- file-loader:把文件輸出到一個文件夾中,在代碼中經過相對 URL 去引用輸出的文件
- url-loader:和 file-loader 相似,可是能在文件很小的狀況下以 base64 的方式把文件內容注入到代碼中去
- source-map-loader:加載額外的 Source Map 文件,以方便斷點調試
- image-loader:加載而且壓縮圖片文件
- babel-loader:把 ES6 轉換成 ES5
- css-loader:加載 CSS,支持模塊化、壓縮、文件導入等特性
- style-loader:把 CSS 代碼注入到 JavaScript 中,經過 DOM 操做去加載 CSS。
- eslint-loader:經過 ESLint 檢查 JavaScript 代碼
4.有哪些常見的Plugin?他們是解決什麼問題的?
- define-plugin:定義環境變量
- commons-chunk-plugin:提取公共代碼
- uglifyjs-webpack-plugin:經過UglifyES壓縮ES6代碼
5.Loader和Plugin的不一樣?
不一樣的做用
- Loader直譯爲"加載器"。Webpack將一切文件視爲模塊,可是webpack原生是隻能解析js文件,若是想將其餘文件也打包的話,就會用到loader。 因此Loader的做用是讓webpack擁有了加載和解析非JavaScript文件的能力。
- Plugin直譯爲"插件"。Plugin能夠擴展webpack的功能,讓webpack具備更多的靈活性。 在 Webpack 運行的生命週期中會廣播出許多事件,Plugin 能夠監聽這些事件,在合適的時機經過 Webpack 提供的 API 改變輸出結果。
不一樣的用法
- Loader在module.rules中配置,也就是說他做爲模塊的解析規則而存在。 類型爲數組,每一項都是一個Object,裏面描述了對於什麼類型的文件(test),使用什麼加載(loader)和使用的參數(options)
- Plugin在plugins中單獨配置。 類型爲數組,每一項是一個plugin的實例,參數都經過構造函數傳入。
6.webpack的構建流程是什麼?從讀取配置到輸出文件這個過程儘可能說全
Webpack 的運行流程是一個串行的過程,從啓動到結束會依次執行如下流程:
- 初始化參數:從配置文件和 Shell 語句中讀取與合併參數,得出最終的參數;
- 開始編譯:用上一步獲得的參數初始化 Compiler 對象,加載全部配置的插件,執行對象的 run 方法開始執行編譯;
- 肯定入口:根據配置中的 entry 找出全部的入口文件;
- 編譯模塊:從入口文件出發,調用全部配置的 Loader 對模塊進行翻譯,再找出該模塊依賴的模塊,再遞歸本步驟直到全部入口依賴的文件都通過了本步驟的處理;
- 完成模塊編譯:在通過第4步使用 Loader 翻譯完全部模塊後,獲得了每一個模塊被翻譯後的最終內容以及它們之間的依賴關係;
- 輸出資源:根據入口和模塊之間的依賴關係,組裝成一個個包含多個模塊的 Chunk,再把每一個 Chunk 轉換成一個單獨的文件加入到輸出列表,這步是能夠修改輸出內容的最後機會;
- 輸出完成:在肯定好輸出內容後,根據配置肯定輸出的路徑和文件名,把文件內容寫入到文件系統。
在以上過程當中,Webpack 會在特定的時間點廣播出特定的事件,插件在監聽到感興趣的事件後會執行特定的邏輯,而且插件能夠調用 Webpack 提供的 API 改變 Webpack 的運行結果。
7.是否寫過Loader和Plugin?描述一下編寫loader或plugin的思路?
Loader像一個"翻譯官"把讀到的源文件內容轉義成新的文件內容,而且每一個Loader經過鏈式操做,將源文件一步步翻譯成想要的樣子。
編寫Loader時要遵循單一原則,每一個Loader只作一種"轉義"工做。 每一個Loader的拿到的是源文件內容(source),能夠經過返回值的方式將處理後的內容輸出,也能夠調用this.callback()方法,將內容返回給webpack。 還能夠經過 this.async()生成一個callback函數,再用這個callback將處理後的內容輸出出去。 此外webpack還爲開發者準備了開發loader的工具函數集——loader-utils。
相對於Loader而言,Plugin的編寫就靈活了許多。 webpack在運行的生命週期中會廣播出許多事件,Plugin 能夠監聽這些事件,在合適的時機經過 Webpack 提供的 API 改變輸出結果。
8.webpack的熱更新是如何作到的?說明其原理?
webpack的熱更新又稱熱替換(Hot Module Replacement),縮寫爲HMR。 這個機制能夠作到不用刷新瀏覽器而將新變動的模塊替換掉舊的模塊。
原理:
首先要知道server端和client端都作了處理工做
- 第一步,在 webpack 的 watch 模式下,文件系統中某一個文件發生修改,webpack 監聽到文件變化,根據配置文件對模塊從新編譯打包,並將打包後的代碼經過簡單的 JavaScript 對象保存在內存中。
- 第二步是 webpack-dev-server 和 webpack 之間的接口交互,而在這一步,主要是 dev-server 的中間件 webpack-dev-middleware 和 webpack 之間的交互,webpack-dev-middleware 調用 webpack 暴露的 API對代碼變化進行監控,而且告訴 webpack,將代碼打包到內存中。
- 第三步是 webpack-dev-server 對文件變化的一個監控,這一步不一樣於第一步,並非監控代碼變化從新打包。當咱們在配置文件中配置了devServer.watchContentBase 爲 true 的時候,Server 會監聽這些配置文件夾中靜態文件的變化,變化後會通知瀏覽器端對應用進行 live reload。注意,這兒是瀏覽器刷新,和 HMR 是兩個概念。
- 第四步也是 webpack-dev-server 代碼的工做,該步驟主要是經過 sockjs(webpack-dev-server 的依賴)在瀏覽器端和服務端之間創建一個 websocket 長鏈接,將 webpack 編譯打包的各個階段的狀態信息告知瀏覽器端,同時也包括第三步中 Server 監聽靜態文件變化的信息。瀏覽器端根據這些 socket 消息進行不一樣的操做。固然服務端傳遞的最主要信息仍是新模塊的 hash 值,後面的步驟根據這一 hash 值來進行模塊熱替換。
- webpack-dev-server/client 端並不可以請求更新的代碼,也不會執行熱更模塊操做,而把這些工做又交回給了 webpack,webpack/hot/dev-server 的工做就是根據 webpack-dev-server/client 傳給它的信息以及 dev-server 的配置決定是刷新瀏覽器呢仍是進行模塊熱更新。固然若是僅僅是刷新瀏覽器,也就沒有後面那些步驟了。
- HotModuleReplacement.runtime 是客戶端 HMR 的中樞,它接收到上一步傳遞給他的新模塊的 hash 值,它經過 JsonpMainTemplate.runtime 向 server 端發送 Ajax 請求,服務端返回一個 json,該 json 包含了全部要更新的模塊的 hash 值,獲取到更新列表後,該模塊再次經過 jsonp 請求,獲取到最新的模塊代碼。這就是上圖中 七、八、9 步驟。
- 而第 10 步是決定 HMR 成功與否的關鍵步驟,在該步驟中,HotModulePlugin 將會對新舊模塊進行對比,決定是否更新模塊,在決定更新模塊後,檢查模塊之間的依賴關係,更新模塊的同時更新模塊間的依賴引用。
- 最後一步,當 HMR 失敗後,回退到 live reload 操做,也就是進行瀏覽器刷新來獲取最新打包代碼。
9.如何利用webpack來優化前端性能?(提升性能和體驗)
用webpack優化前端性能是指優化webpack的輸出結果,讓打包的最終結果在瀏覽器運行快速高效。
- 壓縮代碼。刪除多餘的代碼、註釋、簡化代碼的寫法等等方式。能夠利用webpack的UglifyJsPlugin和ParallelUglifyPlugin來壓縮JS文件, 利用cssnano(css-loader?minimize)來壓縮css
- 利用CDN加速。在構建過程當中,將引用的靜態資源路徑修改成CDN上對應的路徑。能夠利用webpack對於output參數和各loader的publicPath參數來修改資源路徑
- 刪除死代碼(Tree Shaking)。將代碼中永遠不會走到的片斷刪除掉。能夠經過在啓動webpack時追加參數--optimize-minimize來實現
- 提取公共代碼。
10.如何提升webpack的構建速度?
- 多入口狀況下,使用CommonsChunkPlugin來提取公共代碼
- 經過externals配置來提取經常使用庫
- 利用DllPlugin和DllReferencePlugin預編譯資源模塊 經過DllPlugin來對那些咱們引用可是絕對不會修改的npm包來進行預編譯,再經過DllReferencePlugin將預編譯的模塊加載進來。
- 使用Happypack 實現多線程加速編譯
- 使用webpack-uglify-parallel來提高uglifyPlugin的壓縮速度。 原理上webpack-uglify-parallel採用了多核並行壓縮來提高壓縮速度
- 使用Tree-shaking和Scope Hoisting來剔除多餘代碼
11.怎麼配置單頁應用?怎麼配置多頁應用?
單頁應用能夠理解爲webpack的標準模式,直接在entry中指定單頁應用的入口便可,這裏再也不贅述
多頁應用的話,可使用webpack的 AutoWebPlugin來完成簡單自動化的構建,可是前提是項目的目錄結構必須遵照他預設的規範。 多頁應用中要注意的是:
- 每一個頁面都有公共的代碼,能夠將這些代碼抽離出來,避免重複的加載。好比,每一個頁面都引用了同一套css樣式表
- 隨着業務的不斷擴展,頁面可能會不斷的追加,因此必定要讓入口的配置足夠靈活,避免每次添加新頁面還須要修改構建配置
12.npm打包時須要注意哪些?如何利用webpack來更好的構建?
Npm是目前最大的 JavaScript 模塊倉庫,裏面有來自全世界開發者上傳的可複用模塊。你可能只是JS模塊的使用者,可是有些狀況你也會去選擇上傳本身開發的模塊。 關於NPM模塊上傳的方法能夠去官網上進行學習,這裏只講解如何利用webpack來構建。
NPM模塊須要注意如下問題:
- 要支持CommonJS模塊化規範,因此要求打包後的最後結果也遵照該規則。
- Npm模塊使用者的環境是不肯定的,頗有可能並不支持ES6,因此打包的最後結果應該是採用ES5編寫的。而且若是ES5是通過轉換的,請最好連同SourceMap一同上傳。
- Npm包大小應該是儘可能小(有些倉庫會限制包大小)
- 發佈的模塊不能將依賴的模塊也一同打包,應該讓用戶選擇性的去自行安裝。這樣能夠避免模塊應用者再次打包時出現底層模塊被重複打包的狀況。
- UI組件類的模塊應該將依賴的其它資源文件,例如.css文件也須要包含在發佈的模塊裏。
基於以上須要注意的問題,咱們能夠對於webpack配置作如下擴展和優化:
- CommonJS模塊化規範的解決方案: 設置output.libraryTarget='commonjs2'使輸出的代碼符合CommonJS2 模塊化規範,以供給其它模塊導入使用
- 輸出ES5代碼的解決方案:使用babel-loader把 ES6 代碼轉換成 ES5 的代碼。再經過開啓devtool: 'source-map'輸出SourceMap以發佈調試。
- Npm包大小盡可能小的解決方案:Babel 在把 ES6 代碼轉換成 ES5 代碼時會注入一些輔助函數,最終致使每一個輸出的文件中都包含這段輔助函數的代碼,形成了代碼的冗餘。解決方法是修改.babelrc文件,爲其加入transform-runtime插件
- 不能將依賴模塊打包到NPM模塊中的解決方案:使用externals配置項來告訴webpack哪些模塊不須要打包。
- 對於依賴的資源文件打包的解決方案:經過css-loader和extract-text-webpack-plugin來實現,配置以下:
13.如何在vue項目中實現按需加載?
Vue UI組件庫的按需加載 爲了快速開發前端項目,常常會引入現成的UI組件庫如ElementUI、iView等,可是他們的體積和他們所提供的功能同樣,是很龐大的。 而一般狀況下,咱們僅僅須要少許的幾個組件就足夠了,可是咱們卻將龐大的組件庫打包到咱們的源碼中,形成了沒必要要的開銷。
不過不少組件庫已經提供了現成的解決方案,如Element出品的babel-plugin-component和AntDesign出品的babel-plugin-import 安裝以上插件後,在.babelrc配置中或babel-loader的參數中進行設置,便可實現組件按需加載了。
單頁應用的按需加載 如今不少前端項目都是經過單頁應用的方式開發的,可是隨着業務的不斷擴展,會面臨一個嚴峻的問題——首次加載的代碼量會愈來愈多,影響用戶的體驗。
經過import(*)語句來控制加載時機,webpack內置了對於import(*)的解析,會將import(*)中引入的模塊做爲一個新的入口在生成一個chunk。 當代碼執行到import(*)語句時,會去加載Chunk對應生成的文件。import()會返回一個Promise對象,因此爲了讓瀏覽器支持,須要事先注入Promise polyfill