webpack-易混淆部分的解釋

世有因果知因求果發表於2018-03-25

原文連結:

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’]
 }
}

 

相關文章