GitChat 做者:張旺
原文: webpack 從入門到工程實踐
關注微信公衆號:GitChat 技術雜談 ,一本正經的講技術javascript
本文較長,爲了節省你的閱讀時間,在文前列寫做思路以下:css
- 什麼是
webpack
,它要解決的是什麼問題?- 對
webpack
的主要配置項進行分析,雖然不會涉及太多細節,可是期待在本節能讓咱們知曉若是咱們有什麼需求,咱們該從哪些配置項着手修改?- 分析
create-react-app
的基礎配置文件。- 分享一些本身工做中對
webpack
的實踐。
本文的初衷是和你一塊兒理清webpack
的使用邏輯,以便能更加容易的編寫及拓展本身項目所需的配置文件。不過也得提早說明本文可能並非一篇好的能夠跟着操做的教程(想跟着一步步作的童鞋能夠看官方示例和webpack入門,看這篇就夠了。html
近年來,前端技術蓬勃發展,咱們想在js
更方便的實現html
, 社區就出現了jsx
,咱們以爲原生的css
不夠好用,社區就提出了scss
,less
,針對前端項目愈來愈強的模塊化開發需求,社區出現了AMD
,CommonJS
,ES2015 import
等等方案。遺憾的是,這些方案大多並不直接被瀏覽器支持,每每伴隨這些方案而生的還有另一些,讓這些新技術應用於瀏覽器的方案,咱們用babel
來轉換下一代的js
,轉換jsx
;咱們用各類工具轉換scss
,less
爲css
;咱們發現項目愈來愈複雜,代碼體積愈來愈大,又要開始尋找各類優化,壓縮,分割方案。前端工程化這個過程,真是讓咱們大費精力。咱們也大可能是在尋找前端模塊化解決方案的過程當中知曉了webpack
。前端
的確,webpack
的流行得益於野性生長的前端,其本質是一種前端模塊化打包解決方案,可是更重要的是它又是一個能夠融合運用各類前端新技術的平臺,明白webpack
的使用哲學後,只須要簡單的配置,咱們就能夠爲所欲爲的在webpack
項目中使用jsx/ts
,使用babel/postcss
等平臺提供的衆多其它功能,只需經過一條命令由源碼構建最終可用文件。能夠不誇張的說webpack
爲前端的工程化開發提供了一套相對容易和完整的解決方案。一些知名的腳手架工具,也大多基於webpack
(好比create-react-app
)。java
webpack
好難!我第一次複製別人的配置文件到個人項目中,發現以本身僅有的JS知識徹底看不懂時,也有這種感受。後來發現有這種感受實際上是由於本身看待webpack
的角度錯了,對大多數前端開發者而言,以往咱們接觸的各類庫,要麼相似jQuery
,經過$
符在前端項目中直接運行,所作的事情只在前端生效,要麼相似express.js
,在node.js
項目中直接require
後就可使用,所作的事情只在後端生效。webpack
的不一樣之處就在於,雖然咱們的配置文件位於前端項目中,但實際上它卻運行於node.js
,以後的處理結果又供前端使用(也可能供node使用)。因此學習以前,咱們轉變一下思惟,從node.js
的角度來看webpack
,不少事情就會簡單起來。node
咱們對下圖必定不陌生,假設如今咱們手中有一系列相互關聯的文件js
,jsx
,css
,less
,jpg
,咱們一步步的看看爲了把它們轉換爲項目最終須要的,瀏覽器可識別的文件,webpack
都作了什麼。react
若是不去考究細節,咱們大可把webpack
簡化理解爲一個函數,配置文件則是其參數,傳入合理的參數後,運行函數就能獲得咱們想要的結果。jquery
webpack
也只是一個打包工具,它可不是什麼智能ai
,咱們該從哪兒輸入文件,咱們想把輸出結果放哪裏,輸出結果應該長什麼樣,它都不知道。而咱們目前和webpack
函數交互的惟一方法就是經過參數,這就涉及到webpack
配置對象中兩個重要概念entry
和output
了,所以,咱們的配置對象至少具有如下結構:webpack
// 第一階段 { entry:{}, output:{} }
理想狀態是,咱們把全部本身編寫的文件都交給webpack
,讓它找明裏面的關係,進過必定處理後,給出最終咱們想要的結果。遺憾的是,webpack
也不會機械學習,咱們手頭的一堆文件之間的關係是本身肯定的,通常咱們的項目都會存在一個或幾個主文件,其它的全部的文件(模塊)都直接或間接的連接到了這些文件。咱們在entry
項中須要填寫的就是這些主文件的信息。git
不過咱們也不要嫌棄webpack
笨,經過咱們給的主文件路徑,經過分析它能構建最合適的依賴關係,這意味着只有用過的代碼纔會被打包,好比咱們在一個文件中寫了五個模塊,可是實際只用了其中一個,打包後的代碼只會包含引用過的模塊。
webpack
中不少地方的配置都有多種寫法,這也是其讓人疑惑的地方之一,很遺憾,咱們的第一個配置對象entry
就是如此。
entry
能夠是三種值:
entry:'./src/index.js'
,字符串也能夠是函數的返回值,如entry: () => './demo'
,單一入口占位符[name]
值爲main
(關於佔位符,稍後詳述);[react,react-dom]
,能夠把數組中的多個文件打包轉換爲一個chunk
;[name]
的值,屬性值能夠是上面的字符串和數組,以下:// 值得注意的是入口文件有幾個就會生成幾個獨立的依賴圖譜。 entry:{ main:'./src/index.js', second:'./src/index2.js', vendor: ['react','react-dom'] }
好吧,千辛萬苦,咱們在一堆各類類型的文件中找到了入口文件,這裏咱們假設爲./src/index.js
,此時咱們的配置對象以下:
// 第二階段 { entry:{ main:'./src/index.js' }, output:{} }
webpack
依據入口文件來構建依賴體系,每一個入口文件在打包完成後都具有其獨立的依賴圖譜,在此咱們暫時稱這些由主入口配置生成的文件爲主js
文件。
output
配置項做用於打包文件的輸出階段,其做用在於告知webpack
以何種方式輸出打包文件,關於output
,webpack
提供了衆多的可配置選項,咱們簡單介紹下最經常使用的選項。
output基本配置項
咱們都另存過文件,當咱們另存一個文件時,咱們須要肯定另存的文件名和另存的路徑,webpack
將打包後的結果導出的過程就相似於此,此過程由output
配置項控制,其最基本配置包括filename
和path
兩項。這兩項用以決定上述主js
文件的存儲行爲。
不過咱們程序的首頁每每不需用到某個主js
文件的全部代碼,實際開發中,咱們經常使用必定方法對代碼進行分割,方便按需加載,提高體驗。這類不具有獨立依賴的文件,咱們稱之爲chunkfile
。chunkfile
的命名,在output
中對應chunkFilename
項;
此外output
的publicPath
項,用於控制打包文件的相對或者絕對引用路徑,配置不當每每形成在運行時找不到文件。
咱們補充配置對象中output
的配置,以下:
// 第三階段 { entry:{ main:'./src/index.js' }, output:{ path: path.join(__dirname,'./dist'), name:'js/bundle-[name]-[hash].js', chunkFilename:'js/[name].chunk.js', publicPath:'/dist/' } }
上述代碼中用到了佔位符
[name]
,咱們對佔位符作統一解釋:
webpack
中常見的佔位符有多種,常見的以下:
[name]
:表明打包後文件的名稱,在entry
或代碼中(以後會看到)肯定;[id]
:webpack
給塊分配的內部chunk id
,若是你沒有隱藏,你能在打包後的命令行中看到;[hash]
:每次構建過程當中,生成的惟一 hash 值;[chunkhash]
: 依據於打包生成文件內容的 hash 值,內容不變,值不變;[ext]
: 資源擴展名,如js
,jsx
,png
等等;
output其它配置
output
配置項生效於保存這個過程,除了上面的基本配置,若是你想對這個階段的打包文件進行更改,均可在此配置項中進行相關設置。
好比output
提供了衆多關於hash
的屬性,讓咱們對[hash]
佔位符的值有更加精細的控制,如生成方式,使用的算法,預設的長度等等;如chunkLoadTimeout
屬性則容許咱們設置chunk
文件的請求超時時間。
工具都是依賴於需求來使用的,若是你此階段有別的需求,可點擊更多配置尋找解決方案。
咱們已經知道了webpack
中基本的輸入和輸出配置,可是webpack
對各模塊的處理過程,目前爲止,對咱們仍是一個謎。考慮到webpack
執行於node.js
環境,其自己只能理解js
文件,而咱們輸入的倒是一大堆不一樣格式的文件,毫無疑問,要作的第一件事情是對各種模塊進行處理,這就涉及到webpack
中第三個重要配置對象了---module
。
使用webpack
時,咱們經常據說,對webpack
而言,全部的文件都是模塊,前文中我也經常混用模塊和文件,不過本質上模塊和文件仍是不一樣的,webpack
裏,文件能夠當作模塊,而模塊卻不必定是一個獨立的文件。咱們先看看webpack
內置支持的模塊類型:
ES2015 import
(webpack2
開始內置支持)。require
。define
和require
語句。@import
。url(...)
和html文件中的<img src="..."/>
。咱們知道webpack
只能處理js
文件,咱們的瀏覽器也可能不支持一些最新的js語法,基於此,咱們須要對傳入的模塊進行必定的預處理,這就涉及到webpack
的又一核心概念 --- loader
,使用loader
,webpack
容許咱們打包任何JS以外的靜態資源。
loader的做用和基本用法
webpack
中,loader
的配置主要在module.rules
中進行,module.rules
是一個數組,咱們能夠把每一項看作一個Rule
,每一個Rule
主要作了如下兩件事:
loader
,(Rule.test
屬性)。loader
對文件進行相應的操做轉換,(Rule.use
屬性)。還記得前面咱們說過,咱們手頭的文件類型有js
,jsx
,css
,less
,jpg
嗎?咱們看看在webpack
中該如何處理和轉換它們。
注:如下loader
使用前需經過npm/cnpm/yarn
安裝:
module: { rules: [{ test: /(\.jsx|\.js)$/, use: { loader: "babel-loader", options: { presets: ["es2015", "react"] } }, exclude: /node_modules/ }, { test: /\.css$/, use: ["style-loader", "css-loader"] }, { test: /\.less$/, use: ["style-loader", "css-loader", "less-loader"] }] },
這就是webpack
中loader
的基本用法了,在module.rules
數組中進行配置便可,module.rules
是一個數組,裏面每一項(一個Rule
)表示以必定的規則匹配和處理某種或某幾種類型的文件。具體說來:
Rule.test
:表示匹配規則,它是一個正則表達式。Rule.use
:表示針對匹配的文件將使用的處理loader
,其值能夠是字符串,數組和對象,當是對象形式時,咱們可使用options
等命令進行進一步的配置。Rule
中的其它一些規則也大多圍繞匹配條件和應用結果展開,如Rule.exclude
和Rule.include
表示應該匹配或不該該匹配某資源;Rule.oneOf
表示對該資源只應用第一個匹配的loader
;Rule.enforce
則用於指定loader
的種類。loader能夠作什麼
webpack
的強大之處在於,能夠輕鬆在其中應用其它平臺提供的功能,好比說babel
,postcss
自己都是獨立的平臺。在webpack
中只須要添加babel-loader
和postcss-loader
就可使用。這兩個平臺自己也提供衆多的配置項,默認分別可在.babelrc 和postcss.config.js中完成,webpack
並不影響這些配置文件的使用。不過須要說明的可能不少童鞋是在學習webpack
時才接觸這兩個平臺,致使在這兩個平臺上遇到的問題誤覺得是webpack
的問題。
除了上述的轉換編譯,經過loader
,webpack
還容許咱們實現如下功能:
script-loader/babel-loader/ts-loader/coffee-loader
等。style-loader/css-loader/less-loader/sass-loader/postcss-loader
等。raw-loader/url-loader/file-loader/
等。csv-loader/xml-loader
等。html-loader/pug-loader/jade-loader/markdown-loader
等。mocha-loader/eslint-loader
等。關於各個loader更詳細的介紹,可點擊loaders查看。
module.noParse
關於module
,另外一個經常使用的配置項爲module.noParse
,經過它,咱們在構建過程當中能夠忽略大型的 library 以提升構建效率。
咱們來整理一下此階段,咱們的配置對象代碼,以下:
// 第四階段 { entry: { main: './src/index.js' }, output: { path: path.join(__dirname, './dist'), name: 'js/bundle-[name].js', chunkFilename: 'js/[name].chunk.js', publicPath: '/dist/' }, module: { rules: [{ test: /(\.jsx|\.js)$/, use: { loader: "babel-loader", options: { presets: ["es2015", "react"] } }, exclude: /node_modules/ }, { test: /\.css$/, use: ["style-loader", "css-loader"] }, { test: /\.less$/, use: ["style-loader", "css-loader", "less-loader"] }] } }
進過這一階段的處理,咱們的代碼其實已經能夠輸出使用了。不過這樣的輸出可能還不能讓人滿意,咱們想要抽離公共代碼;咱們想統一修改全部代碼中的某些值;咱們還想對代碼進行壓縮,去除全部的console
… , 總之這一階段的代碼仍是存在很大的改進空間的,這就是plugin
的用武之地了。
webpack
稱plugins
爲其backbone
,一切loader
不能作的處理均可由plugins
來作。此評價足見其重要性。
鑑於插件如此重要,webpack
內置了衆多的經常使用的plugins
,無需額外安裝就可直接使用。咱們先看看plugins
的基本配置方法,而後再分類介紹一下經常使用的plugins
。
plugins的使用方法
plugins
是一個數組,數組中的每一項都是某一個plugin
的實例,plugins
數組甚至能夠存在一個插件的多個實例。
下面代碼中,分別展現了webpack
內置插件和第三方插件的使用方法:
// 第三方插件須要在安裝後引入 const CleanWebpackPlugin = require("clean-webpack-plugin"); { ... plugins:[ new webpack.DefinePlugin({ "process.env": { NODE_ENV: JSON.stringify("production") } }), new CleanWebpackPlugin(["js"], { root: __dirname + "/stu/", verbose: true, dry: false }) ] }
一種插件其實就是一種函數,經過傳入不一樣的參數,插件可按咱們的需求實現不一樣的功能。不過插件數量衆多,咱們甚至還能夠本身來寫插件,每一個插件還有本身特定的配置規則,這也是webpack
讓人以爲難學的地方之一,不過好在做爲一個工具,對於咱們大多數人最須要掌握的plugins
並非那麼多,其它的待真的有相關需求再邊查邊學也不遲,webpack
的插件列表可參看這裏。
經常使用plugins的介紹
plugins
功能衆多,可是大多數plugin
的功能主要集中在兩方面:
index.html
,對生成文件存儲文件夾作必定的清理等。對代碼進行處理
BannerPlugin
:給代碼添加版權信息,如在plugins
數組中添加new BannerPlugin(‘GitChat’)
後能在打包生成的全部文件前添加註釋GitChat
詳見。CommonsChunkPlugin
,用於抽離代碼,具備多種用途 詳情查看CommonsChunkPlugin。
react react-dom
。Manifest
這類每次打包都會變化的內容,減輕打包時候的壓力,提高構建速度。CompressionWebpackPlugin
:使用配置的算法(如gzip
)壓縮打包生成的文件,詳見。DefinePlugin
:建立一個在編譯時可配置的全局常量,若是你自定義了一個全局變量PRODUCTION
,可在此設置其值來區分開發仍是生產環境詳見。EnvironmentPlugin
:其實是DefinePlugin
插件中對process.env
進行設置的簡寫形式,如new webpack.EnvironmentPlugin(['NODE_ENV', 'DEBUG'])
將設置process.env.NODE_ENV='DEBUG'
,EnvironmentPlugin。ExtractTextWebpackPlugin
:抽離css
文件爲單獨的css
文件,詳見。ProvidePlugin
:全局自動加載模塊,如添加new webpack.ProvidePlugin({$: 'jquery', jQuery: 'jquery'})
後,則全局不用在導入jquery
就能夠直接使用$
,ProvidePlugin。UglifyjsWebpackPlugin
:使用前須要先安裝,基於UglifyJS
壓縮代碼,支持其全部配置UglifyjsWebpackPlugin。輔助輸出打包後的代碼
HtmlWebpackPlugin
:使用前須要先安裝,爲你自動生成一個html
文件,該文件將自動依據entry
的配置引入依賴,若是你的文件名中添加了[hash]
等佔位符,這將很是有用, 詳見。CleanWebpackPlugin
:使用前須要先安裝,此插件容許你在配置之後,每次打包時,清空所配置的文件夾,若是你每次打包的文件名不一樣,這將很是有用 GitHub - clean-webpack-plugin。經過上述對不一樣插件的描述,你必定大體明白了,插件能夠作什麼,以後在開發的過程當中,若是你遇到的什麼須要在此階段解決的問題,大可搜索看看是否有相關的插件,推薦查閱awesome-webpack。
學習了插件之後,如今咱們的配置對象是以下這樣:
// 第5階段 { entry: { main: './src/index.js' }, output: { path: path.join(__dirname, './dist'), name: 'js/bundle-[name].js', chunkFilename: 'js/[name].chunk.js', publicPath: '/dist/' }, module: { rules: [{ test: /(\.jsx|\.js)$/, use: { loader: "babel-loader", options: { presets: ["es2015", "react"] } }, exclude: /node_modules/ }, { test: /\.css$/, use: ["style-loader", "css-loader"] }, { test: /\.less$/, use: ["style-loader", "css-loader", "less-loader"] }] }, plugins: [ new webpack .optimize .CommonsChunkPlugin({ name: 'vendor', filename: "js/[name]-[chunkhash].js" }), new webpack.optimize.CommonsChunkPlugin({ name: "manifest", minChunks: Infinity }), new webpack.ProvidePlugin({ Promise: "exports-loader?global.Promise!es6-promise", fetch: "exports-loader?self.fetch!whatwg-fetch" }), new HtmlWebpackPlugin({ filename: "index.html", template: "app/index.html", inject: "body" }), new CleanWebpackPlugin(["js"], { root: __dirname + "/stu/", verbose: true, dry: false }), new webpack.DefinePlugin({ "process.env": { NODE_ENV: JSON.stringify("production") } }) ] }
至此,從輸入entry
->處理loaders/plugins
->輸出output
,咱們講解了webpack
的核心功能,不過webpack
還提供其它的一些配置項,這些配置項大多從兩方面起做用,輔助開發、對構建過程當中的一些細節作調整。對這些屬性,下面只作簡單的介紹。
輔助開發的相關屬性
devtool
:打包後的代碼和原始的代碼每每存在較大的差別,此選項控制是否生成,以及如何生成 source map,用以幫助你進行調試,詳情可查看Devtool。
devServer
:經過配置devServer
選項,你能夠開啓一個本地服務器,webpack
爲此本地服務器提供了很是多的配置選項,點擊查看dev-server,你會發現經過合適的配置,你能夠擁有全部本地服務器可提供的功能。
watch
:啓用 Watch 模式後,webpack
將持續監放任何已解析文件的更改,從新構建文件,Watch 模式默認關閉,在開發時候若是開啓會很方便。
watchOptions
:一組用來定製 Watch 模式的選項: 詳見 watch。
performance
:本配置讓你設置打包後命令行中該如何展現性能提示,好比是否開啓提示,資源若是超過某個大小時該警告仍是報錯,詳見performance。
stats
:本選項讓你配置打包過程當中輸出的內容,如沒有輸出none
,標準輸出normal
,所有輸出verbose
,只輸出錯誤errors-only
等等。
精細配置相關屬性
content
:設置基礎路徑,默認使用當前目錄。resolve
:肯定模塊如何被解析,webpack
已經提供了合理的默認值,不過經過你的自定義配置,能夠對模塊解析實現更加精細的控制,如對某些經常使用模塊能夠經過設置別名以更容易引用,也可在此處設置可被忽略的後綴名,詳見 resolve。
target
:告知 webpack
須要打包的代碼執行的環境,針對 node 和 web 打包過程會有所不一樣,詳見Target。
externals
:讓打包生成的代碼中不添加某依賴項,而讓這些依賴項直接從用戶環境中獲取,在進行庫的開發時很是有用。
node
:是一個對象,其中每一個屬性都是 Node.js 全局變量或模塊的名稱,每一項的設置值均可以是(true/mock/empty/false
)中的一種,以肯定這些node中的對象在其它環境中是否可用。
webpack
還具有其它一些用的比較少的配置對象,詳見 Other Options。至此,咱們瞭解了webpack
經常使用的配置項及其意義。爲了檢測咱們的學習成果,咱們一塊兒分析一箇中等項目中的webpack
配置文件。配置文件來自於create-react-app
,使用create-react-app
新建項目後,執行npm run eject
可看到多個配置文件,這裏咱們選擇webpack.dev.js
。
const path = require('path'); const webpack = require('webpack'); const HtmlWebpackPlugin = require('html-webpack-plugin'); const CaseSensitivePathsPlugin = require('case-sensitive-paths-webpack-plugin'); const InterpolateHtmlPlugin = require('react-dev-utils/InterpolateHtmlPlugin'); const WatchMissingNodeModulesPlugin = require('react-dev-utils/WatchMissingNodeModulesPlugin'); const eslintFormatter = require('react-dev-utils/eslintFormatter'); const ModuleScopePlugin = require('react-dev-utils/ModuleScopePlugin'); module.exports = { devtool: 'cheap-module-source-map', entry: [ require.resolve('react-dev-utils/webpackHotDevClient'), require.resolve('./polyfills'), require.resolve('react-error-overlay'), 'src/index.js' ], output: { path: '/build/', pathinfo: true, filename: 'static/js/bundle.js', chunkFilename: 'static/js/[name].chunk.js', publicPath: '', devtoolModuleFilenameTemplate: info => path.resolve(info.absoluteResourcePath).replace(/\\/g, '/'), }, resolve: { modules: ['node_modules'], extensions: ['.web.js', '.js', '.json', '.web.jsx', '.jsx'], alias: { 'react-native': 'react-native-web', }, plugins: [ new ModuleScopePlugin('/src'), ], }, module: { strictExportPresence: true, rules: [{ test: /\.(js|jsx)$/, enforce: 'pre', use: [{ options: { formatter: eslintFormatter, }, loader: require.resolve('eslint-loader'), }, ], include: 'src', }, { exclude: [/\.html$/,/\.(js|jsx)$/,/\.css$/,/\.json$/,/\.bmp$/,/\.gif$/,/\.jpe?g$/,/\.png$/], loader: require.resolve('file-loader'), options: { name: 'static/media/[name].[hash:8].[ext]', }, }, { test: [/\.bmp$/, /\.gif$/, /\.jpe?g$/, /\.png$/], loader: require.resolve('url-loader'), options: { limit: 10000, name: 'static/media/[name].[hash:8].[ext]', }, }, { test: /\.(js|jsx)$/, include: 'src', loader: require.resolve('babel-loader'), options: { cacheDirectory: true, }, }, { test: /\.css$/, use: [ require.resolve('style-loader'), { loader: require.resolve('css-loader'), options: { importLoaders: 1, }, }, { loader: require.resolve('postcss-loader'), options: { ... }, }, ], }, ], }, plugins: [ new InterpolateHtmlPlugin({ NODE_ENV:'development', PUBLIC_URL:'' }), new HtmlWebpackPlugin({ inject: true, template: 'public/index.html', }), new webpack.NamedModulesPlugin(), new webpack.DefinePlugin({ 'process.env':{ NODE_ENV:"development", PUBLIC_URL:'" "' } }), new webpack.HotModuleReplacementPlugin(), new CaseSensitivePathsPlugin(), new WatchMissingNodeModulesPlugin(paths.appNodeModules), new webpack.IgnorePlugin(/^\.\/locale$/, /moment$/), ], node: { dgram: 'empty', fs: 'empty', net: 'empty', tls: 'empty', }, performance: { hints: false, }, };
對可能和你看到的webpack.config.dev.js
有所不一樣的說明::
npm run reject
以前,對create-react-app
的一些設置會影響這裏看到的配置文件。webpack.config.dev.js
中,部分值由外部函數生成,相關值,在上述代碼中直接改成了肯定的結果,如env.raw
在上述代碼中被替換爲:{ NODE_ENV:'development', PUBLIC_URL:'' }
create-react-app
在開發環境並不生成真實的文件到硬盤,上述代碼中的部分路徑可能有誤,見諒。推薦在看下面的分析前,花三分鐘看看上述文件,若是都能看得懂,那麼恭喜你,你已經明白webpack
的運做方式了,快去本身的項目中實踐吧,若是還有疑惑,也沒關係,咱們一塊兒來分析。
首先,咱們應該明確webpack.config.dev.js
執行於node
環境,目的在於返回webpack
須要的配置對象,所以其中可使用node提供的一些特殊變量和語法,好比__dirname
,又如引入模塊時採用CommonJS
模式。
此文件的開頭,首先經過require
語句引入了path
,webpack
和一系列webpack
插件,除了HtmlWebpackPlugin
在前文中咱們見過,其它的咱們都不曾見過,其實這些大可能是create-react-app
針對webpack
已有的插件改進或新開發的插件,因此不熟悉也正常,隨後咱們將一個個的弄清楚它們是幹嗎的。
devtool
此處的配置值爲cheap-module-source-map
,表明不帶列映射的 SourceMap
,將加載的 Source Map
簡化爲每行單獨映射。
entry
此處的entry
是一個數組,表明着四項的代碼都會添加到打包結果之中。
webpackHotDevClient
能夠被看作具備更好體驗的WebpackDevServer
。./ployfill.js
用以在瀏覽器中支持promise/fetch/object-assign
。react-error-overlay
在開發環境中使用,強制顯示錯誤頁面。./src/index.js
則是咱們的app
的主入口。output
在實際使用create-react-app
的過程當中,咱們並看不見開發環境的打包結果,所以此處的說明僅供參考。
path
指定,打包後文件存放的位置爲/build/
。pathinfo
爲true
,在打包文件後,在其中所包含引用模塊的信息,這在開發環境中有利於調試。filename
指定了打包的名字和基本的引用路徑static/js/bundle.js
。chunkFilename
:指定了非入口文件的名稱static/js/[name].chunk.js
。publicPath
:指定服務器讀取時的路徑,此處設置爲
。devtoolModuleFilenameTemplate
:這裏是一個函數,指定了map
位於磁盤的位置。resolve
modules
:指定了模塊的搜索的位置,這裏設置爲node_modules
。extensions
:指明在引用模塊時哪些後綴名能夠忽略,這裏忽略的文件名包括.js/.jsx/.web.js/.web.jsx等
。alias
:建立 import 或 require 的別名,使得部分模塊的引用變得簡單,安裝上文的設置,如今咱們能夠直接引用react-native
和react-native-web
了。plugins
:此處使用了ModuleScopePlugin
的實例,用以限制本身編寫的模塊只能從src
目錄中引入。modules
strictExportPresence
:這裏設置爲true
,代表文件中若是缺乏exports
時會直接報錯而不是警告。rules
:
js/jsx
文件前置使用eslintFormatter
,設置formatter
格式爲eslintFormatter
。exclude
中的衆多文件類型不使用file-loader
,並設置其它文件打包後的名稱按'static/media/[name].[hash:8].[ext]'
格式設置。js/jsx
文件調用babel-loader
處理轉換。css
文件,按順序調用style-loader
,css-loader
,postcss-loader
進行處理。plugins
這裏的一些插件,有的可能咱們還比較陌生,咱們一一介紹。
InterpolateHtmlPlugin
:和HtmlWebpackPlugin
串行使用,容許在index.html
中添加變量。HtmlWebpackPlugin
:自動生成帶有入口文件引用的index.html
。NamedModulesPlugin
:當開啓 HMR
的時候使用該插件會顯示模塊的相對路徑,建議用於開發環境。DefinePlugin
:這裏咱們設置了process.env.NODE_ENV
的值爲development
。HotModuleReplacementPlugin
:啓用模塊熱替換。CaseSensitivePathsPlugin
:若是路徑有誤則直接報錯。WatchMissingNodeModulesPlugin
:此插件容許你安裝庫後自動從新構建打包文件。new webpack.IgnorePlugin(/^\.\/locale$/, /moment$/)
:忽略所匹配的moment.js
。node
設置node
的dgram/fs/let/tls
模塊的的值,若是在其它環境中使用時值爲empty
。
performance
hints: false
:不提示測試環境的打包結果。
上文一直討論的是,webpack
各設置項的基本意義,目的在於讓你在有相關需求時,能知道該從哪一項下手查詢。不過看到這裏,若是你以前從未上手操做過webpack
可能依舊不知道該如何使用,下面我分析一下,我在本身的項目中是如何使用的。
官方文檔的guides部分已經就如何實踐提出了較多的建議,建議閱讀如下內容前先行閱讀。
webpack
在安裝後有多種調用方法。
webpack.config.js
文件,在其中完成配置,而後在命令行中執行webpack --config webpack.config.js
來使用,配置文件能夠是任何其它名稱(若是是webpack.config.js
,咱們直接使用webpack
命令)。npm
使用,在package.json
文件中的scripts
對象中添加相關命令使用,以後經過npm run
使用,以下:"scripts": { "build:prod": "webpack --progress --colors --watch --config webpack.prod.js", "build:dev": "webpack --progress --colors --watch --config webpack.dev.js" }
上面咱們分別構建了webpack.prod.js
和webpack.dev.js
來分別生成開發環境和生產環境的代碼,在命令行中執行npm run build:prod
和npm run build:dev
便可生成對應代碼。
關於緩存,官方文檔中有一節講解的很是詳細,請參見緩存。
webpack
提供了三種分割代碼的方法,分別是經過entry
,經過CommonsChunkPlugin
插件和經過動態import
(在webpack1.x
中時也經常使用require.ensure
來依據路由分割代碼)。
entry
的配置經常使用於多頁應用,CommonsChunkPlugin
的使用前文已作簡要敘述,下面簡單敘述下代碼分割原則及我實際工做中是如何使用動態import
來分割代碼的。
分割原則
目前工做中主要依據兩個原則來分隔代碼:
分割方法
咱們知道動態import
返回值實際上是一個Promise
,基於此,對應於我用的React,我常採用如下函數輔助加載。
// lib.js 定義懶加載函數 module.exports.withLazyLoading = function withLazyLoading(getComponent,Spinner = null) { return class LazyLoadingWrapper extends React.Component { constructor(props) { super(props); this.state = ({ Component: null, }) } componentWillMount() { const {onLoadingStart, onLoadingEnd, onError} = this.props; onLoadingStart(); getComponent() .then(esModule => { this.setState({Component: esModule.default}) }) .catch(err => { onError(err, this.props) }) } render() { const {Component} = this.state; if (!Component) return Spinner; return <Component {...this.props} /> } } };
對代碼的分割方法以下:
// 在須要的地方調用懶加載函數 import {withLazyLoading} from "lib"; // import {Loading} from 'Loadings'; export default withLazyLoading( () => { return import (/* webpackChunkName: "ConCard" */ "../../containers/ConCard.js") }, Loading());
簡要的說明一下上述代碼的意義,懶加載函數withLazyLoading
接受動態import
的組件和一個加載動畫做爲參數,動態import
的組件加載成功前顯示加載動畫組件,成功後顯示import
的組件,經過自定義各類各樣的Spinner
加載動畫,咱們能夠實現優雅的js
文件加載過程。
使用webpack --json > stats.json
命令能夠生成一個包含依賴關係的json
文件。webpack
提供了多種可視化工具幫咱們分析這個文件,我最喜歡的工具插件是BundleAnalyzerPlugin
,可經過下述方法引入該插件:
new BundleAnalyzerPlugin({ analyzerMode: 'static' })
添加此插件,再次構建完成時,瀏覽器中將自動打開一個相似下面這樣的網頁:
這樣咱們能夠輕易分析咱們的代碼分割是否合理,好比:
上圖是我以前項目中的一張截圖,第一次見到這張圖時仍是給了我不少後期優化的思路的,引用chat.js
的同時引入了moment.js
,而實際上該頁面只有一張圖表,這讓我考慮另尋圖表解決方案,lodash
,velocity
在最初的項目中使用過,後逐步去除,屬於遺留代碼,如今還存在說明在局部可能仍是用到了,這都是以後編碼的改進方向。
總以爲技術類的文章也是該有生命力的,花了很久寫完本文,回頭看發現有的內容仍是沒有表達或交待清楚。因此有任何建議,請隨意提出,咱們在Chat中繼續討論,我也將對本文作長期持續的修改。
針對webpack3.5.5
官網文檔,使用mindNode
製做了一個思惟導圖的草稿,此思惟導圖還需完善,以後將持續修改,點擊此處可查看,該思惟導圖示例以下。
另外,關於webpack1
和webapck2
的區別,官方文檔中有一部分作了詳細的講解,因此本文中不作贅述,看完之後若是還有疑問,以後咱們再詳細討論。