本文較長,爲了節省你的閱讀時間,在文前列寫做思路以下:css
什麼是webpack,它要解決的是什麼問題?
對webpack的主要配置項進行分析,雖然不會涉及太多細節,可是期待在本節能讓咱們知曉若是咱們有什麼需求,咱們該從哪些配置項着手修改?
分析create-react-app的基礎配置文件。
分享一些本身工做中對webpack的實踐。
本文的初衷是和你一塊兒理清webpack的使用邏輯,以便能更加容易的編寫及拓展本身項目所需的配置文件。不過也得提早說明本文可能並非一篇好的能夠跟着操做的教程(想跟着一步步作的童鞋能夠看官方示例和webpack入門,看這篇就夠了。html
換個角度看待webpack前端
近年來,前端技術蓬勃發展,咱們想在js更方便的實現html , 社區就出現了jsx,咱們以爲原生的css不夠好用,社區就提出了scss,less,針對前端項目愈來愈強的模塊化開發需求,社區出現了AMD,CommonJS,ES2015 import等等方案。遺憾的是,這些方案大多並不直接被瀏覽器支持,每每伴隨這些方案而生的還有另一些,讓這些新技術應用於瀏覽器的方案,咱們用babel來轉換下一代的js,轉換jsx;咱們用各類工具轉換scss,less爲css;咱們發現項目愈來愈複雜,代碼體積愈來愈大,又要開始尋找各類優化,壓縮,分割方案。前端工程化這個過程,真是讓咱們大費精力。咱們也大可能是在尋找前端模塊化解決方案的過程當中知曉了webpack。node
的確,webpack的流行得益於野性生長的前端,其本質是一種前端模塊化打包解決方案,可是更重要的是它又是一個能夠融合運用各類前端新技術的平臺,明白webpack的使用哲學後,只須要簡單的配置,咱們就能夠爲所欲爲的在webpack項目中使用jsx/ts,使用babel/postcss等平臺提供的衆多其它功能,只需經過一條命令由源碼構建最終可用文件。能夠不誇張的說webpack爲前端的工程化開發提供了一套相對容易和完整的解決方案。一些知名的腳手架工具,也大多基於webpack(好比create-react-app)。react
webpack好難!我第一次複製別人的配置文件到個人項目中,發現以本身僅有的JS知識徹底看不懂時,也有這種感受。後來發現有這種感受實際上是由於本身看待webpack的角度錯了,對大多數前端開發者而言,以往咱們接觸的各類庫,要麼相似jQuery,經過$符在前端項目中直接運行,所作的事情只在前端生效,要麼相似express.js,在node.js項目中直接require後就可使用,所作的事情只在後端生效。webpack的不一樣之處就在於,雖然咱們的配置文件位於前端項目中,但實際上它卻運行於node.js,以後的處理結果又供前端使用(也可能供node使用)。因此學習以前,咱們轉變一下思惟,從node.js的角度來看webpack,不少事情就會簡單起來。jquery
咱們對下圖必定不陌生,假設如今咱們手中有一系列相互關聯的文件js,jsx,css,less,jpg,咱們一步步的看看爲了把它們轉換爲項目最終須要的,瀏覽器可識別的文件,webpack都作了什麼。webpack
webpack作了什麼es6
對webpack主要配置項的分析web
若是不去考究細節,咱們大可把webpack簡化理解爲一個函數,配置文件則是其參數,傳入合理的參數後,運行函數就能獲得咱們想要的結果。正則表達式
webpack也只是一個打包工具,它可不是什麼智能ai,咱們該從哪兒輸入文件,咱們想把輸出結果放哪裏,輸出結果應該長什麼樣,它都不知道。而咱們目前和webpack函數交互的惟一方法就是經過參數,這就涉及到webpack配置對象中兩個重要概念entry和output了,所以,咱們的配置對象至少具有如下結構:
// 第一階段
{
entry:{},
output:{}
}
入口配置entry
理想狀態是,咱們把全部本身編寫的文件都交給webpack,讓它找明裏面的關係,進過必定處理後,給出最終咱們想要的結果。遺憾的是,webpack也不會機械學習,咱們手頭的一堆文件之間的關係是本身肯定的,通常咱們的項目都會存在一個或幾個主文件,其它的全部的文件(模塊)都直接或間接的連接到了這些文件。咱們在entry項中須要填寫的就是這些主文件的信息。
不過咱們也不要嫌棄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
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中常見的佔位符有多種,常見的以下:
[id]:webpack給塊分配的內部chunk id,若是你沒有隱藏,你能在打包後的命令行中看到;
[hash]:每次構建過程當中,生成的惟一 hash 值;
[chunkhash]: 依據於打包生成文件內容的 hash 值,內容不變,值不變;
output其它配置
output配置項生效於保存這個過程,除了上面的基本配置,若是你想對這個階段的打包文件進行更改,均可在此配置項中進行相關設置。
好比output提供了衆多關於hash的屬性,讓咱們對[hash]佔位符的值有更加精細的控制,如生成方式,使用的算法,預設的長度等等;如chunkLoadTimeout屬性則容許咱們設置chunk文件的請求超時時間。
工具都是依賴於需求來使用的,若是你此階段有別的需求,可點擊更多配置尋找解決方案。
咱們已經知道了webpack中基本的輸入和輸出配置,可是webpack對各模塊的處理過程,目前爲止,對咱們仍是一個謎。考慮到webpack執行於node.js環境,其自己只能理解js文件,而咱們輸入的倒是一大堆不一樣格式的文件,毫無疑問,要作的第一件事情是對各種模塊進行處理,這就涉及到webpack中第三個重要配置對象了---module。
對模塊的處理:module的配置
使用webpack時,咱們經常據說,對webpack而言,全部的文件都是模塊,前文中我也經常混用模塊和文件,不過本質上模塊和文件仍是不一樣的,webpack裏,文件能夠當作模塊,而模塊卻不必定是一個獨立的文件。咱們先看看webpack內置支持的模塊類型:
ES2015 import(webpack2開始內置支持)。
CommonJS require。
AMD define和require語句。
css/less/sass 中的@import。
樣式中的url(user-gold-cdn.xitu.io/2017/11/23/…。
咱們知道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的用武之地了。
plugins的配置
webpack稱plugins爲其backbone,一切loader不能作的處理均可由plugins來作。此評價足見其重要性。
鑑於插件如此重要,webpack內置了衆多的經常使用的plugins,無需額外安裝就可直接使用。咱們先看看plugins的基本配置方法,而後再分類介紹一下經常使用的plugins。
plugins的使用方法
plugins是一個數組,數組中的每一項都是某一個plugin的實例,plugins數組甚至能夠存在一個插件的多個實例。
下面代碼中,分別展現了webpack內置插件和第三方插件的使用方法:
// 第三方插件須要在安裝後引入
const CleanWebpackPlugin = require("clean-webpack-plugin");
{
user-gold-cdn.xitu.io/2017/11/23/…
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。
抽離不一樣文件的共享代碼,減小chunk間的重複代碼,有效利用緩存。
抽離可能整個項目都在使用的第三方模塊,好比react react-dom。
將多個子chunk中的共用代碼打包進父chunk或使用異步加載的單獨chunk。
抽離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。
分析create-react-app中webpack的配置
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: {
user-gold-cdn.xitu.io/2017/11/23/…
},
},
],
}, ],
},
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.config.dev.js執行於node環境,目的在於返回webpack須要的配置對象,所以其中可使用node提供的一些特殊變量和語法,好比__dirname,又如引入模塊時採用CommonJS模式。
此文件的開頭,首先經過require語句引入了path,webpack和一系列webpack插件,除了HtmlWebpackPlugin在前文中咱們見過,其它的咱們都不曾見過,其實這些大可能是create-react-app針對webpack已有的插件改進或新開發的插件,因此不熟悉也正常,隨後咱們將一個個的弄清楚它們是幹嗎的。
對module.exports的分析
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:
Rule1:對js/jsx文件前置使用eslintFormatter,設置formatter格式爲eslintFormatter。
Rule2:對exclude中的衆多文件類型不使用file-loader,並設置其它文件打包後的名稱按'static/media/name.[hash:8].ext'格式設置。
Rule3: 對js/jsx文件調用babel-loader處理轉換。
Rule4: 對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部分已經就如何實踐提出了較多的建議,建議閱讀如下內容前先行閱讀。
結合npm使用
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來分割代碼的。
分割原則
目前工做中主要依據兩個原則來分隔代碼:
前端路由:依據路由對應的頁面進行分割,這種分割以後的體驗相似於小程序中每次打開新頁加載對應頁面的js文件。
針對邏輯交互比較複雜的頁面,若是某個較複雜的組件需被某操做觸發後才呈現,也會把該組件分割出來。
分割方法
咱們知道動態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 {https://user-gold-cdn.xitu.io/2017/11/23/15fe79d230c8387dthis.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'
})
添加此插件,再次構建完成時,瀏覽器中將自動打開一個相似下面這樣的網頁:
BundleAnalyzerPlugin
這樣咱們能夠輕易分析咱們的代碼分割是否合理,好比:
分割後文件過大的主要緣由是在於引入了那些模塊。
分析大多後的多文件中存不存在對某些比較大的模塊的重複引用,方便咱們進一步修正本身的配置文件。
上圖是我以前項目中的一張截圖,第一次見到這張圖時仍是給了我不少後期優化的思路的,引用chat.js的同時引入了moment.js,而實際上該頁面只有一張圖表,這讓我考慮另尋圖表解決方案,lodash,velocity在最初的項目中使用過,後逐步去除,屬於遺留代碼,如今還存在說明在局部可能仍是用到了,這都是以後編碼的改進方向。
後記
總以爲技術類的文章也是該有生命力的,花了很久寫完本文,回頭看發現有的內容仍是沒有表達或交待清楚。因此有任何建議,請隨意提出,咱們在Chat中繼續討論,我也將對本文作長期持續的修改。
針對webpack3.5.5官網文檔,使用mindNode製做了一個思惟導圖的草稿,此思惟導圖還需完善,以後將持續修改,點擊此處可查看,該思惟導圖示例以下。
思惟導圖局部示例
另外,關於webpack1和webapck2的區別,官方文檔中有一部分作了詳細的講解,因此本文中不作贅述,看完之後若是還有疑問,以後咱們再詳細討論。