原文連結:
https://medium.com/@rajaraodv/webpack-the-confusing-parts-58712f8fcad9
webpack的核心哲學
1. 任何皆模組
正如js檔案可以是"modules",任何其他的檔案,比如css, images, html都可以被視為modules。也就是說,你可以通過require("myJSfile.js")來載入js檔案,也可以通過require("mycssFile.css")載入css檔案。這也就意味著我們可以將任何工作成果視為更小的可管理的構建,可用於重用。
2. 僅僅在你需要使用的時候,僅僅載入你需要的asset. 典型的,打包器接收所有的模組輸入而最終生成以惡搞巨大的單個"bundle.js"檔案.但是很多真實的應用,這個bundel.js檔案可能會達到10MB-15MB的大小!因此,這種情況下會導致載入非常慢。webpack為了處理這種bundle過大的問題,webpack提供了幾個好用的功能用於split你的程式碼,並且產生出多個"bundle" files,並且可以async非同步地載入parts of the app,以便僅僅載入你當前需要的部分。
下面我們來一個個探討容易搞不清楚的webpack topic
Development vs production
首先我們要銘記在心的是webpack有非常朵的好功能,而這些功能中很大一部分僅僅是為"development-only"的功能,而一部分是"production-only"的,而剩下的部分是既可用於開發環境,又可用於生產環境。
典型地,大多數專案通過使用兩個大的webpack config file來分別處理dev和prod兩種場景。
要建立bundle,你可能會在package.json中這樣寫:
"scripts":{ // npm run build to build production bundles "build": "webpack --config webpack.config.prod.js", // npm run dev to generate development bundles and run dev server "dev": "webpack-dev-server" }
webpack CLI vs webpack-dev-server
webpack,作為打包工具,提供以下兩個介面,理解這一點非常重要:
1. webpack cli tool---這是預設的介面(隨著webpack的安裝而存在於.bin目錄中)
2. webpack-dev-server tool ---一個node.js server(你需要單獨安裝它)
webpack CLI(非常適合做prodcution build)
這個命令列工具通過cli接收一些配置option,或者通過一個config file來接收這些option(預設為webpack.config.js配置檔案),這些配置選項將用於webpack的打包過程。
雖然你可能是通過使用cli來開始學習webpack的,但是實際上命令列工具往往更多用於建立生產環境下使用的bundle.
用法:
OPTION 1: //Install it globally npm install webpack --g //Use it at the terminal $ webpack //<--Generates bundle using webpack.config.js OPTION 2 : //Install it locally & add it to package.json npm install webpack --save //Add it to package.json's script “scripts”: { “build”: “webpack --config webpack.config.prod.js -p”, ... } //Use it by running the following: "npm run build"
webpack-dev-server(非常適合建立開發的build,並serve靜態的assets)
webpack-dev-server是一個執行於8080埠的expresss nodejs server.這個server會自己呼叫webpack本身實現構建,並且server構建出來的bundle.這個server的好處是它可以提供比如"Live Reloading"或者"Hot Module Replacement(HMR)"的實用功能。
npm install webpack-dev-server --save //Use it at the terminal $ webpack-dev-server --inline --hot OPTION 2: // Add it to package.json's script “scripts”: { “start”: “webpack-dev-server --inline --hot”, ... } // Use it by running $ npm start Open browser at: http://localhost:8080
webpack vs webpack-dev-server options
需要說明的是,比如"inline"或者"hot"這些配置選項僅僅是對webpack-dev-server來適用的。而比如"hide-modules"僅僅針對webpack cli來說是適用的。
webpack-dev-server CLI options vs config options
另外需要說明的一點是:你可以通過以下兩種方式來傳入webpack-dev-server配置選項options:
1. 通過webpack.config.js的"devServer物件
2.通過webpack-dev-server的CLI options傳入:
//Via CLI webpack-dev-server --hot --inline //Via webpack.config.js devServer: { inline: true, hot:true }
我在測試中發現,有時通過devServer配置選項傳入引數(hot:true, inline:true)並不總是能夠工作,所以,我更喜歡通過在package.json中的cli option來傳入對應的引數:
//package.json { scripts: {“start”: “webpack-dev-server --hot --inline”} }
"hot" vs "inline" webpack-dev-server options:
"inline"選項使得針對整個頁面實現"live reloading"成為可能。"hot"選項使能了"hot module reloading"功能,而這個功能可以僅僅reload那些被變更過的component(而不是整個頁面重新載入).如果我們同時傳入兩個配置選項,那麼當source變化時,webpack-dev-server將會首先試圖做HRM,如果HMR不work,則會reload整個頁面。
//When the source changes, all 3 options generates new bundle but, //1. doesn't reload the browser page $ webpack-dev-server //2. reloads the entire browser page $ webpack-dev-server --inline //3. reloads just the module(HMR), or the entire page if HMR fails $ webpack-dev-server --inline --hot
"entry" --String Vs Array Vs Object
Entry這個配置項告訴webpack應用的root module或者starting point在哪裡。這個配置項可以是string,可以是array,也可以是object.這個靈活性可能讓我們搞得糊塗,但是這些不同型別的資料實際上是用於不同的目的的。
如果你有一個single starting point的話,那麼string, array, object都是一樣的結果。
entry-array
但是,如果你想追加多個互不依賴的檔案,那麼可以使用array格式
比如,你可能需要"googleAnalytics.js"到你的html中,那麼你可以告訴webpack追加該analytics.js到bundle.js的後面:
entry-object
如果你有一個多頁的web應用,有多個html檔案(index.html, profile.html等),而不是一個SPA w/ multi-views,
那麼你可以通過entry object告訴webpack需要一次性建立多個bundles。
下面的配置將產生兩個js bundle檔案:indexEntry.js和profileEntry.js分別應用於index.html和profile.html中
使用方法為:
//profile.html <script src=”dist/profileEntry.js”></script> //index.html <script src=”dist/indexEntry.js”></script>
entry-combination
你也可以在object entry裡面使用array entries。例如,下面的config將產生3個檔案:vendor.js和index.js, profile.js:
output-"path" vs "publicPath"
output告訴webpack我們將最終的輸出存放在哪裡一級如何存放。這裡兩個引數:path和publicPath可能會產生歧義和誤解。
"path"告訴webpack我們將最終的bundle存放在哪裡。耳"publicPath"則被多個webpack plugin來使用用於當產生production build時更新在html,css檔案中的url
例如,在你的css中,你可能有一個url從你的localhost來load './test.png‘。但是在生產環境下,test.png檔案可能存在於CDN中。這意味著你可能需要手工的更新這些url以便在生產環境中可以指向到正確的url地址。
為了解決這個手工修改url的問題,你可以使用webpack的publicPath引數,而這個引數對幾乎所有的plugin都是能夠理解並使用這個publicPath引數的,並且自動在建立production build時更新這些url.
// Development: Both Server and the image are on localhost .image { background-image: url(‘./test.png’); } // Production: Server is on Heroku but the image is on a CDN .image { background-image: url(‘https://someCDN/test.png’); }
loaders and chaning loaders
loaders是一些額外的node modules專門用於幫助'load'或者'import'各種型別的檔案到瀏覽器可以認識的js, css檔案格式。更進一步,loader也允許通過require或者import來import這些檔案到js中。
例如:你可以使用bable-loader來轉換es6寫的js程式碼到瀏覽器可以認識的es5 javascript:
module: { loaders: [{ test: /\.js$/, ←Test for ".js" file, if it passes, use the loader exclude: /node_modules/, ←Exclude node_modules folder loader: ‘babel’ ←use babel (short for ‘babel-loader’) }]
chaining loaders(從右往左)
多個loaders可以級聯起來,針對同一個檔案做不同的轉換。級聯是從右往左工作的,同時使用" ! "來隔離
例如,我們如果有一個"mycssfile.css"檔案,我們需要將它的內容dump到html的<style>css content</style>中。我們可以通過以下兩個loaders來實現這個功能:css-loader和style-loader
module: { loaders: [{ test: /\.css$/, loader: ‘style!css’ <--(short for style-loader!css-loader) }]
下面這張圖可以解釋這個過程是如何工作的:
1. 首先webpack在module中檢索相關依賴,webpack看到mycssfile.css通過require來做import,因此mycssfile.css將作為dependency來處理,webpack首先將該css檔案給到'css-loader'來做處理
2. css-loader載入所有的css內容以及該css內容中自己的depency(比如@import othercss),並儲存為json. webpack然後將這個結果傳給style-loader繼續處理。
3. style-loader則接收這個json,並且增加<style>css contents</style>並將這個字串插入到index.html檔案中
loaders Themselves can be configured
loaders可以通過傳入不同的parameters以不同的方式來工作。
在下面的例子中,我們配置url-loader當image小於1024位元組的話直接使用DataURLs。我們可以傳入limit引數來指定這個大小。
.babelrc檔案
babel-laoder使用"presets"配置選項使能如何轉換es6為es5的過程,一級如何解析react’s jsx 到js檔案。我們可以通過query引數傳入配置:
module: { loaders: [ { test: /\.jsx?$/, exclude: /(node_modules|bower_components)/, loader: 'babel', query: { presets: ['react', 'es2015'] } } ] }
然而,很多專案中babel的配置專案可能很多,這種情況下,你就可以把balble的這些配置專案放到.babelrc檔案中去。babel-loader將自動載入這個.babelrc檔案如果它存在的話。
所以在很多例子中,你可能會看到:
//webpack.config.js module: { loaders: [ { test: /\.jsx?$/, exclude: /(node_modules|bower_components)/, loader: 'babel' } ] } //.bablerc { “presets”: [“react”, “es2015”] }
Plugins
plugins是一些針對輸出的bundle執行特別的工作的node modules.
例如,uglifyJSPlugin的作用是接收bundle.js作為輸入並且混淆最小化該檔案以減少檔案的體積。
類似地,extract-text-webpack-plugin這個plugin則內部通過使用css-loader,style-loader來收集所有css內容並整合到一處並最終抽出這些css內容到一個單一的style.css檔案中,並且在index.html中產生一個指向該style.css的link。
//webpack.config.js //Take all the .css files, combine their contents and it extract them to a single "styles.css" var ETP = require("extract-text-webpack-plugin"); module: { loaders: [ {test: /\.css$/, loader:ETP.extract("style-loader","css-loader") } ] }, plugins: [ new ExtractTextPlugin("styles.css") //Extract to styles.css file ] }
需要說明的是,如果你想在html中inline使用這些css樣式,你可以不使用該plugin,而僅僅通過使用css, style loaders達到目的:
module: { loaders: [{ test: /\.css$/, loader: ‘style!css’ <--(short for style-loader!css-loader) }]
loaders vs plugins
loaders僅在bundle生成過程中或者生成之前針對單個的檔案做轉換。
而plugin則在bundle建立的結束之後針對整個bundle或者chunk level執行操作。一些像commonsChunksPlugin的外掛則更進一步會修正bundles本身是如何建立的過程。
resolving file extensions
很多webpack config檔案有一個resolve extensions屬性,並有一個空字串作為值。
這個空字串用於幫助resolve imports without extensions,比如require("./myJSFile")或者import myJSFile從"./myJSFile”而不用加上檔案的副檔名.
{
resolve: {
extensions: [‘’, ‘.js’, ‘.jsx’]
}
}