from:https://www.jianshu.com/p/9349c30a6b3e?utm_campaign=maleskine&utm_content=note&utm_medium=seo_notes&utm_source=recommendationcss
GitChat技術雜談html
前言前端
本文較長,爲了節省你的閱讀時間,在文前列寫做思路以下:node
什麼是 webpack,它要解決的是什麼問題?react
對webpack的主要配置項進行分析,雖然不會涉及太多細節,可是期待在本節能讓咱們知曉若是咱們有什麼需求,咱們該從哪些配置項着手修改?jquery
分析 create-react-app 的基礎配置文件。webpack
分享一些本身工做中對webpack的實踐。git
本文的初衷是和你一塊兒理清 webpack 的使用邏輯,以便能更加容易的編寫及拓展本身項目所需的配置文件。es6
不過也得提早說明本文可能並非一篇好的能夠跟着操做的教程(想跟着一步步作的童鞋能夠看官方示例(github
https://webpack.js.org/guides/)和 webpack 入門,看這篇就夠了(
http://www.jianshu.com/p/42e11515c10f)。
換個角度看待 webpack
近年來,前端技術蓬勃發展,咱們想在 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)。
webpack好難!我第一次複製別人的配置文件到個人項目中,發現以本身僅有的JS知識徹底看不懂時,也有這種感受。
後來發現有這種感受實際上是由於本身看待webpack的角度錯了,對大多數前端開發者而言,以往咱們接觸的各類庫,要麼相似jQuery,經過$符在前端項目中直接運行,所作的事情只在前端生效,要麼相似express.js,在node.js項目中直接require後就可使用,所作的事情只在後端生效。
webpack的不一樣之處就在於,雖然咱們的配置文件位於前端項目中,但實際上它卻運行於node.js,以後的處理結果又供前端使用(也可能供node使用)。因此學習以前,咱們轉變一下思惟,從node.js的角度來看webpack,不少事情就會簡單起來。
咱們對下圖必定不陌生,假設如今咱們手中有一系列相互關聯的文件js,jsx,css,less,jpg,咱們一步步的看看爲了把它們轉換爲項目最終須要的,瀏覽器可識別的文件,webpack都作了什麼。
顯示大圖
對 webpack 主要配置項的分析
若是不去考究細節,咱們大可把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中常見的佔位符有多種,常見的以下:
[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。
對模塊的處理:module 的配置
使用webpack時,咱們經常據說,對webpack而言,全部的文件都是模塊,前文中我也經常混用模塊和文件,不過本質上模塊和文件仍是不一樣的,webpack裏,文件能夠當作模塊,而模塊卻不必定是一個獨立的文件。咱們先看看webpack內置支持的模塊類型:
ES2015 import(webpack2開始內置支持)。
CommonJS require。
AMD define 和 require 語句。
css/less/sass 中的@ import。
樣式中的 url(...) 和 html 文件中的 。
咱們知道 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");
{
...
plugins:[ new webpack.DefinePlugin({ "process.env": {
NODE_ENV: JSON.stringify("production")
}
}), new CleanWebpackPlugin(["js"], {
root: __dirname + "/stu/",
verbose: true,
dry: false
})
]
}
一種插件其實就是一種函數,經過傳入不一樣的參數,插件可按咱們的需求實現不一樣的功能。不過插件數量衆多,咱們甚至還能夠本身來寫插件,每一個插件還有本身特定的配置規則,這也是webpack讓人以爲難學的地方之一,不過好在做爲一個工具,對於咱們大多數人最須要掌握的plugins並非那麼多,其它的待真的有相關需求再邊查邊學也不遲,webpack的插件列表可參看這裏(
https://webpack.js.org/plugins/)。
經常使用 plugins 的介紹
plugins功能衆多,可是大多數plugin的功能主要集中在兩方面:
對前一階段打包後的代碼進行處理,如添加替換一些內容,分割代碼爲多塊,添加一些全局設置等。
輔助輸出,如自動生成帶有連接的index.html,對生成文件存儲文件夾作必定的清理等。
對代碼進行處理
BannerPlugin:給代碼添加版權信息,如在plugins數組中添加new BannerPlugin(‘GitChat’)後能在打包生成的全部文件前添加註釋GitChat詳見(
https://webpack.js.org/plugins/banner-plugin/)。
CommonsChunkPlugin,用於抽離代碼,具備多種用途 詳情查看CommonsChunkPlugin(
https://webpack.js.org/plugins/commons-chunk-plugin)。
抽離不一樣文件的共享代碼,減小chunk間的重複代碼,有效利用緩存。
抽離可能整個項目都在使用的第三方模塊,好比 react react-dom。
將多個子 chunk 中的共用代碼打包進父 chunk 或使用異步加載的單獨chunk。
抽離 Manifest 這類每次打包都會變化的內容,減輕打包時候的壓力,提高構建速度。
CompressionWebpackPlugin:使用配置的算法(如gzip)壓縮打包生成的文件,詳見(
https://webpack.js.org/plugins/compression-webpack-plugin)。
DefinePlugin:建立一個在編譯時可配置的全局常量,若是你自定義了一個全局變量PRODUCTION,可在此設置其值來區分開發仍是生產環境詳見(
https://webpack.js.org/plugins/define-plugin/)。
EnvironmentPlugin:其實是DefinePlugin插件中對process.env進行設置的簡寫形式,如new webpack.EnvironmentPlugin(['NODE_ENV', 'DEBUG'])將設置process.env.NODE_ENV='DEBUG',EnvironmentPlugin(
https://webpack.js.org/plugins/environment-plugin/)。
ExtractTextWebpackPlugin:抽離css文件爲單獨的css文件,詳見(
https://webpack.js.org/plugins/extract-text-webpack-plugin)。
ProvidePlugin:全局自動加載模塊,如添加new webpack.ProvidePlugin({$: 'jquery', jQuery: 'jquery'})後,則全局不用在導入jquery就能夠直接使用$,ProvidePlugin(
https://webpack.js.org/plugins/provide-plugin/)。
UglifyjsWebpackPlugin:使用前須要先安裝,基於UglifyJS壓縮代碼,支持其全部配置 UglifyjsWebpackPlugin(
https://webpack.js.org/plugins/uglifyjs-webpack-plugin/)。
輔助輸出打包後的代碼
HtmlWebpackPlugin:使用前須要先安裝,爲你自動生成一個html文件,該文件將自動依據entry的配置引入依賴,若是你的文件名中添加了[hash]等佔位符,這將很是有用, 詳見(
https://webpack.js.org/plugins/html-webpack-plugin/)。
CleanWebpackPlugin:使用前須要先安裝,此插件容許你在配置之後,每次打包時,清空所配置的文件夾,若是你每次打包的文件名不一樣,這將很是有用 GitHub - clean-webpack-plugin(
https://github.com/johnagan/clean-webpack-plugin)。
經過上述對不一樣插件的描述,你必定大體明白了,插件能夠作什麼,以後在開發的過程當中,若是你遇到的什麼須要在此階段解決的問題,大可搜索看看是否有相關的插件,推薦查閱awesome-webpack(
https://github.com/webpack-contrib/awesome-webpack#webpack-plugins)。
學習了插件之後,如今咱們的配置對象是以下這樣:
// 第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(
https://webpack.js.org/configuration/devtool/)。
devServer:
經過配置devServer選項,你能夠開啓一個本地服務器,webpack爲此本地服務器提供了很是多的配置選項,查看 dev-server (
https://webpack.js.org/configuration/dev-server/),你會發現經過合適的配置,你能夠擁有全部本地服務器可提供的功能。
watch:
啓用 Watch 模式後,webpack 將持續監放任何已解析文件的更改,從新構建文件,Watch 模式默認關閉,在開發時候若是開啓會很方便。
watchOptions:
一組用來定製 Watch 模式的選項: 詳見 watch(
https://webpack.js.org/configuration/watch/)。
performance:
本配置讓你設置打包後命令行中該如何展現性能提示,好比是否開啓提示,資源若是超過某個大小時該警告仍是報錯,詳見 performance (
https://webpack.js.org/configuration/performance/)。
stats:
本選項讓你配置打包過程當中輸出的內容,如沒有輸出none,標準輸出normal,所有輸出verbose,只輸出錯誤errors-only等等。
精細配置相關屬性
content:設置基礎路徑,默認使用當前目錄。
resolve:
肯定模塊如何被解析,webpack已經提供了合理的默認值,不過經過你的自定義配置,能夠對模塊解析實現更加精細的控制,如對某些經常使用模塊能夠經過設置別名以更容易引用,也可在此處設置可被忽略的後綴名,詳見 resolve(
https://webpack.js.org/configuration/resolve/)。
target:
告知 webpack 須要打包的代碼執行的環境,針對 node 和 web 打包過程會有所不一樣,詳見Target(
https://webpack.js.org/configuration/target/)。
externals:
讓打包生成的代碼中不添加某依賴項,而讓這些依賴項直接從用戶環境中獲取,在進行庫的開發時很是有用。
node:
是一個對象,其中每一個屬性都是 Node.js 全局變量或模塊的名稱,每一項的設置值均可以是(true/mock/empty/false)中的一種,以肯定這些node中的對象在其它環境中是否可用。
此外webpack還具有其它一些用的比較少的配置對象,詳見 Other Options(
https://webpack.js.org/configuration/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: {
...
},
},
],
}, ],
},
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 (
https://doc.webpack-china.org/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便可生成對應代碼。
爲生產環境指定合理的緩存
關於緩存,官方文檔中有一節講解的很是詳細,請參見 緩存(
https://doc.webpack-china.org/guides/caching)。
合理分割代碼
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
}
}
};
對代碼的分割方法以下:
// 在須要的地方調用懶加載函數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製做了一個思惟導圖的草稿,此思惟導圖還需完善,以後將持續修改,在此處(
https://github.com/zhangwang1990/blogs/tree/master/sources/mindMaps)可查看,該思惟導圖示例以下。
另外,關於webpack1和webapck2的區別,官方文檔中有一部分作了詳細的講解(
https://webpack.js.org/guides/migrating/),因此本文中不作贅述,看完之後若是還有疑問,以後咱們再詳細討論。