webpack入門

寫在前面

第一次接觸webpack,是在一個react項目參與中,剛開始使用的時候,甚至不知道是做什么用的,只看到webpack.config.js文件中很多配置,但是都不太清楚是何用處,本地開發(fā)調(diào)試模式和build到生產(chǎn)環(huán)境都分不清,甚至曾經(jīng)在服務器上用開發(fā)模式運行過一段時間,菜的摳腳啊!最早只知道jQuery操控dom,js、css引入到html,瀏覽器就渲染出頁面了。完全不了解webpack這種構建工具,還可以通過express搭建個native-server,來實時調(diào)試代碼修改,當然了這個功能一般都是webpack自動完成的。我們可以在本機端口,訪問到我們的頁面,代碼的修改保存可以實時刷新重新渲染。

一、概念

webpack 是一個現(xiàn)代 JavaScript 應用程序的模塊打包器(module bundler)。所謂的模塊就是在平時的前端開發(fā)中,用到一些靜態(tài)資源,如JavaScript、CSS、圖片等文件,webpack就將這些靜態(tài)資源文件稱之為模塊。當 webpack 處理應用程序時,它會遞歸地構建一個依賴關系圖(dependency graph),其中包含應用程序需要的每個模塊,然后將所有這些模塊打包成少量的 bundle - 通常只有一個,由瀏覽器加載。
它是高度可配置的,但是,在開始前你需要先理解四個核心概念:入口(entry)、輸出(output)、loader、插件(plugins)。

1.入口(Entry)

webpack 創(chuàng)建應用程序所有依賴的關系圖(dependency graph)。圖的起點被稱之為入口起點(entry point)入口起點告訴 webpack 從哪里開始,并根據(jù)依賴關系圖確定需要打包的內(nèi)容。可以將應用程序的入口起點認為是根上下文(contextual root)app 第一個啟動文件
在 webpack 中,我們使用 webpack 配置對象(webpack configuration object) 中的entry
屬性來定義入口
接下來我們看一個最簡單的例子:webpack.config.js
module.exports = { entry: './path/to/my/entry/file.js'};

根據(jù)不同應用程序的需要,聲明entry屬性有多種方式。

2.出口(Output)

將所有的資源(assets)歸攏在一起后,還需要告訴 webpack 在哪里打包應用程序。webpack 的output屬性描述了如何處理歸攏在一起的代碼(bundled code)。
webpack.config.js

const path = require('path');  
module.exports = {
  entry: './path/to/my/entry/file.js',  
  output: { 
      path: path.resolve(__dirname, 'dist'),
      filename: 'my-first-webpack.bundle.js'
 }}

在上面的例子中,我們通過output.filename和output.path
屬性,來告訴 webpack bundle 的名稱,以及我們想要生成(emit)到哪里。
你可能看到項目生成(emitted 或 emit)貫穿我們整個文檔和插件 API。它是“生產(chǎn)(produced)”或“排放(discharged)”的特殊術語。

3.Loader

webpack 的目標是,讓 webpack 聚焦于項目中的所有資源(asset),而瀏覽器不需要關注考慮這些(明確的說,這并不意味著所有資源(asset)都必須打包在一起)。webpack 把每個文件(.css, .html, .scss, .jpg, etc.) 都作為模塊處理。然而 webpack 自身只理解 JavaScript
webpack loader 在文件被添加到依賴圖中時,其轉換為模塊。**
在更高層面,在 webpack 的配置中 loader 有兩個目標。
識別出(identify)應該被對應的 loader 進行轉換(transform)的那些文件。(test
屬性)
轉換這些文件,從而使其能夠被添加到依賴圖中(并且最終添加到 bundle 中)(use屬性)

webpack.config.js

const path = require('path');
const config = { 
   entry: './path/to/my/entry/file.js', 
   output: { 
       path: path.resolve(__dirname, 'dist'), 
       filename: 'my-first-webpack.bundle.js' 
},
   module: {
       rules: [ { 
          test: /\.txt$/, 
          use: 'raw-loader'
 } ] }
};
module.exports = config;

以上配置中,對一個單獨的 module 對象定義了rules屬性,里面包含兩個必須屬性:test和use。這告訴 webpack 編譯器(compiler) 如下信息:
“嘿,webpack 編譯器,當你碰到「在require()/import語句中被解析為 '.txt' 的路徑」時,在你對它打包之前,先使用 raw-loader轉換一下。”

重要的是要記得,在 webpack 配置中定義 loader 時,要定義在module.rules中,而不是rules。然而,在定義錯誤時 webpack 會給出嚴重的警告。為了使你受益于此,如果沒有按照正確方式去做,webpack 會“給出嚴重的警告”

4.插件(Plugins)

然而由于 loader 僅在每個文件的基礎上執(zhí)行轉換,而插件(plugins)
更常用于(但不限于)在打包模塊的 “compilation” 和 “chunk” 生命周期執(zhí)行操作和自定義功能(查看更多)。webpack 的插件系統(tǒng)極其強大和可定制化。
想要使用一個插件,你只需要require()它,然后把它添加到plugins
數(shù)組中。多數(shù)插件可以通過選項(option)自定義。你也可以在一個配置文件中因為不同目的而多次使用同一個插件,這時需要通過使用new
來創(chuàng)建它的一個實例。
webpack.config.js

const HtmlWebpackPlugin = require('html-webpack-plugin');//installed via npmconst 
webpack = require('webpack'); //to access built-in plugins
const path = require('path');
const config = {
   entry: './path/to/my/entry/file.js',
   output: {
      path: path.resolve(__dirname, 'dist'),
      filename: 'my-first-webpack.bundle.js' 
}, 
   module: {
      rules: [ {
          test: /\.txt$/,
          use: 'raw-loader' 
} ] },
    plugins: [ 
     new webpack.optimize.UglifyJsPlugin(), 
     new HtmlWebpackPlugin({template: './src/index.html'}) 
]};
module.exports = config;

二、Webpack的核心原理

Webpack的兩個最核心的原理分別是:

  1. 一切皆模塊正如js文件可以是一個“模塊(module)”一樣,其他的(如css、image或html)文件也可視作模 塊。因此,你可以require('myJSfile.js')亦可以require('myCSSfile.css')。這意味著我們可以將事物(業(yè)務)分割成更小的易于管理的片段,從而達到重復利用等的目的。
  2. 按需加載傳統(tǒng)的模塊打包工具(module bundlers)最終將所有的模塊編譯生成一個龐大的bundle.js文件。但是在真實的app里邊,“bundle.js”文件可能有10M到15M之大可能會導致應用一直處于加載中狀態(tài)。因此Webpack使用許多特性來分割代碼然后生成多個“bundle”文件,而且異步加載部分代碼以實現(xiàn)按需加載。

三、幾處說明

1.開發(fā)模式和生產(chǎn)模式

在package.json文件加入如下的scripts項:

"scripts": {
// 運行npm run build 來編譯生成生產(chǎn)模式下的bundles
"build": "webpack --config webpack.config.prod.js",
// 運行npm run dev來生成開發(fā)模式下的bundles以及啟動本地server
"dev": "webpack-dev-server"
}

2.webpack-dev-server

我們每修改一次就要需要輸入 npm run dev 是一件非常無聊的事情,幸運的是,我們可以把讓他自己運行,那就是使用webpack-dev-server。

除了提供模塊打包功能,Webpack還提供了一個基于Node.js Express框架的開發(fā)服務器,它是一個靜態(tài)資源Web服務器,對于簡單靜態(tài)頁面或者僅依賴于獨立服務的前端頁面,都可以直接使用這個開發(fā)服務器進行開發(fā)。在開發(fā)過程中,開發(fā)服務器會監(jiān)聽每一個文件的變化,進行實時打包,并且可以推送通知前端頁面代碼發(fā)生了變化,從而可以實現(xiàn)頁面的自動刷新。

  • 安裝:
    npm install --save-dev webpack-dev-server
  • 調(diào)整npm的package.json中scripts 部分開發(fā)命令的配置
{
  "scripts": {
      "test": "echo \"Error: no test specified\" && exit 1",
      "pub": "webpack --config webpack.pub.config.js",
      "dev": "webpack-dev-server  --config webpack.dev.config.js --devtool eval --progress --colors --hot --content-base src"
  }
}

在dev的配置中做了以上改變之后,webpack-dev-server 會在 localhost:8080 建立一個 Web 服務器。
幾個參數(shù)的解釋:
--devtool eval:為你的代碼創(chuàng)建源地址。當有任何報錯的時候可以讓你更加精確地定位到文件和行號
--progress:顯示合并代碼進度
--colors -- hot:命令行中顯示顏色
--content-base 指向設置的輸出目錄
--手動訪問 http://localhost:8080
簡單來說,當你運行 npm run dev的時候,webpack會幫你會啟動一個 Web 服務器,然后監(jiān)聽文件修改,然后自動重新合并你的代碼。真的非常簡潔。

注意點
用webpack-dev-server生成bundle.js文件是在內(nèi)存中的,并沒有實際生成;
如果引用的文件夾中已經(jīng)有bundle.js就不會自動刷新了,你需要先把bundle.js文件手動刪除(后期有插件可以完成);
用webstorm的同學注意了,因為webstorm是自動保存的,所以可能識別的比較慢,你需要手動的ctrl+s一下;

幾個報錯

  • webpack版本的問題
    如果webpack使用的1.x的版本,那么webpack-dev-server也要使用1.x的版本,否則會報如下錯誤:Connot find module 'webpack/bin/config-yargs'。
  • 端口占用問題
    如果已經(jīng)有一個工程中使用了webpack-dev-server,并且在運行中,沒有關掉的話,那么8080端口就被占用了,此時如果在另一個工程中使用webpack-dev-server就會報錯:Error: listen EADDRINUSE 127.0.0.1:8080。
    webpack-dev-server(有利于在開發(fā)模式下編譯)
    這是一個基于Express.js框架開發(fā)的web server,默認監(jiān)聽8080端口。server內(nèi)部調(diào)用Webpack,這樣做的好處是提供了額外的功能如熱更新“Live Reload”以及熱替換“Hot Module Replacement”(即HMR)。
    webpack-dev-server的“hot” 和 “inline”選項
    “inline”選項會為入口頁面添加“熱加載”功能,“hot”選項則開啟“熱替換(Hot Module Reloading)”,即嘗試重新加載組件改變的部分(而不是重新加載整個頁面)。如果兩個參數(shù)都傳入,當資源改變時,webpack-dev-server將會先嘗試HRM(即熱替換),如果失敗則重新加載整個入口頁面。

// 當資源發(fā)生改變,以下三種方式都會生成新的bundle,但是又有區(qū)別:
// 1. 不會刷新瀏覽器
$ webpack-dev-server
//2. 刷新瀏覽器
$ webpack-dev-server --inline
//3. 重新加載改變的部分,HRM失敗則刷新頁面
$ webpack-dev-server --inline --hot

3.“entry”:值分別是字符串、數(shù)組和對象的情況

  • 數(shù)組類型
    添加多個彼此不互相依賴的文件,你可以使用數(shù)組格式的值。
  • 對象
    現(xiàn)在,假設你的應用是多頁面的(multi-page application)而不是SPA,有多個html文件(index.html和profile.html)。然后你通過一個對象告訴Webpack為每一個html生成一個bundle文件。
    以下的配置將會生成兩個js文件:indexEntry.js和profileEntry.js分別會在index.html和profile.html中被引用。
  • 混合類型
    enter對象里使用數(shù)組類型,例如下面的配置將會生成3個文件:vender.js(包含三個文件),index.js和profile.js文件。
57ece0f30001dd1c08000371.png

4. output:“path”項和“publicPath”項output項

告訴webpack怎樣存儲輸出結果以及存儲到哪里。output的兩個配置項“path”和“publicPath”可能會造成困惑。
“path”僅僅告訴Webpack結果存儲在哪里,然而“publicPath”項則被許多Webpack的插件用于在生產(chǎn)模式下更新內(nèi)嵌到css、html文件里的url值。

5..babelrc 文件

babal-loader使用”presets“配置項來標識如何將ES6語法轉成ES5以及如何轉換React的JSX成js文件。我們可以用如下的方式使用”query“參數(shù)傳入配置:

module: {
 loaders: [ { test: /\.jsx?$/,
 exclude: /(node_modulesbower_components)/, 
 loader: 'babel',
 query: { presets: ['react', 'es2015'] } } ]
 }

然而在很多項目里babal的配置可能比較大,因此你可以把babal-loader的配置項單獨保存在一個名為”.babelrc“的文件中,在執(zhí)行時babal-loader將會自動加載.babelrc文件。
所以在很多例子里,你可能會看到:

//webpack.config.js module:
 {
 loaders: [ { test: /\.jsx?$/, 
exclude: /(node_modulesbower_components)/, 
loader: 'babel' } ] 
} 
//.bablerc 
{ presets: ['react', 'es2015'] }

6.plugin插件

插件一般都是用于輸出bundle的node模塊。
例如,uglifyJSPlugin獲取bundle.js然后壓縮和混淆內(nèi)容以減小文件體積。
類似的extract-text-webpack-plugin內(nèi)部使用css-loader和style-loader來收集所有的css到一個地方最終將結果提取結果到一個獨立的”styles.css“文件,并且在html里邊引用style.css文件。

//webpack.config.js 
// 獲取所有的.css文件,合并它們的內(nèi)容然后提取css內(nèi)容到一個獨立的”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 ]
 }

注意:如果你只是想把css使用style標簽內(nèi)聯(lián)到html里,你不必使用extract-text-webpack-plugin,僅僅使用css loader和style loader即可:

module: {
 loaders: [{
     test: /\.css$/, 
     loader: 'style!css' // (short for style-loader!css-loader)
 }]

7.加載器和插件

加載器就是webpack準備的一些預處理工具,比如編譯jsx和es6的加載器,處理sass等....

使用加載器的步驟也很簡單,首先是安裝依賴,然后在配置文件的module中加一個字段module字段,在module寫上loaders,在loaders中寫上相應的配置。
常用加載器:

  • 編譯jsx和ES6到原生js
    安裝以下的依賴
    npm install --save-dev babel-loader babel-core babel-preset-es2015 babel-preset-react
    修改開發(fā)配置文件
module: {
  loaders: [
      {
          test: /\.jsx?$/, // 用正則來匹配文件路徑,這段意思是匹配 js 或者 jsx
          loader: 'babel',// 加載模塊 "babel" 是 "babel-loader" 的縮寫
          query: {
              presets: ['es2015', 'react']
          }
      }
  ]
}
  • 加載CSS
    加載 CSS 需要 css-loader 和 style-loader,他們做兩件不同的事情,css-loader會遍歷 CSS 文件,然后找到 url() 表達式然后處理他們,style-loader 會把原來的 CSS 代碼插入頁面中的一個 style 標簽中。

Loader處理單獨的文件級別并且通常作用于包生成之前或生成的過程中。
而插件則是處理包(bundle)或者chunk級別,且通常是bundle生成的最后階段。一些插件如commonschunkplugin甚至更直接修改bundle的生成方式。

四、webpack的特點

  • 對 CommonJS 、AMD 、ES6的語法做了兼容;
  • 對js、css、圖片等資源文件都支持打包;
  • 串聯(lián)式 模塊加載器 以及 插件機制 ,讓其具有更好的靈活性和擴展性,例如提供對CoffeeScript、ES6的支持;
  • 有獨立的配置文件webpack.config.js;
  • 可以將代碼切割成不同的chunk,實現(xiàn)按需加載,降低了初始化時間;
  • 支持 SourceUrls 和 SourceMaps,易于調(diào)試;
  • 具有強大的Plugin接口,大多是內(nèi)部插件,使用起來比較靈活;
  • webpack 使用異步 IO 并具有多級緩存。這使得 webpack 很快且在增量編譯上更加快;
    webpack最常用與spa應用,主要是vue和React,其實它就非常像Browserify,但是將應用打包為多個文件。如果單頁面應用有多個頁面,那么用戶只從下載對應頁面的代碼. 當他么訪問到另一個頁面, 他們不需要重新下載通用的代碼。
最后編輯于
?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內(nèi)容

  • 前言 眾所周知目前比較火的工具就是gulp和webpack,但webpack和gulp卻有所不同,本人兩者的底層研...
    cduyzh閱讀 1,392評論 0 13
  • 無意中看到zhangwnag大佬分享的webpack教程感覺受益匪淺,特此分享以備自己日后查看,也希望更多的人看到...
    小小字符閱讀 8,242評論 7 35
  • GitChat技術雜談 前言 本文較長,為了節(jié)省你的閱讀時間,在文前列寫作思路如下: 什么是 webpack,它要...
    蕭玄辭閱讀 12,721評論 7 110
  • Webpack 第一章 Webpack 簡介 Instagram團隊在進行前端開發(fā)的過程中,發(fā)現(xiàn)當項目組成員越來越...
    whitsats閱讀 639評論 0 1
  • 1.為什么要使用webpack 現(xiàn)今的很多網(wǎng)頁其實可以看做是功能豐富的應用,它們擁有著復雜的JavaScript代...
    YINdevelop閱讀 516評論 0 5