寫在前面
第一次接觸webpack,是在一個react項目參與中,剛開始使用的時候,甚至不知道是做什么用的,只看到webpack.config.js文件中很多配置,但是都不太清楚是何用處,本地開發調試模式和build到生產環境都分不清,甚至曾經在服務器上用開發模式運行過一段時間,菜的摳腳啊!最早只知道jQuery操控dom,js、css引入到html,瀏覽器就渲染出頁面了。完全不了解webpack這種構建工具,還可以通過express搭建個native-server,來實時調試代碼修改,當然了這個功能一般都是webpack自動完成的。我們可以在本機端口,訪問到我們的頁面,代碼的修改保存可以實時刷新重新渲染。
一、概念
webpack 是一個現代 JavaScript 應用程序的模塊打包器(module bundler)。所謂的模塊就是在平時的前端開發中,用到一些靜態資源,如JavaScript、CSS、圖片等文件,webpack就將這些靜態資源文件稱之為模塊。當 webpack 處理應用程序時,它會遞歸地構建一個依賴關系圖(dependency graph),其中包含應用程序需要的每個模塊,然后將所有這些模塊打包成少量的 bundle - 通常只有一個,由瀏覽器加載。
它是高度可配置的,但是,在開始前你需要先理解四個核心概念:入口(entry)、輸出(output)、loader、插件(plugins)。
1.入口(Entry)
webpack 創建應用程序所有依賴的關系圖(dependency graph)。圖的起點被稱之為入口起點(entry point)。入口起點告訴 webpack 從哪里開始,并根據依賴關系圖確定需要打包的內容。可以將應用程序的入口起點認為是根上下文(contextual root) 或 app 第一個啟動文件。
在 webpack 中,我們使用 webpack 配置對象(webpack configuration object) 中的entry
屬性來定義入口。
接下來我們看一個最簡單的例子:webpack.config.js
module.exports = { entry: './path/to/my/entry/file.js'};
根據不同應用程序的需要,聲明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。它是“生產(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 僅在每個文件的基礎上執行轉換,而插件(plugins)
更常用于(但不限于)在打包模塊的 “compilation” 和 “chunk” 生命周期執行操作和自定義功能(查看更多)。webpack 的插件系統極其強大和可定制化。
想要使用一個插件,你只需要require()它,然后把它添加到plugins
數組中。多數插件可以通過選項(option)自定義。你也可以在一個配置文件中因為不同目的而多次使用同一個插件,這時需要通過使用new
來創建它的一個實例。
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的兩個最核心的原理分別是:
- 一切皆模塊正如js文件可以是一個“模塊(module)”一樣,其他的(如css、image或html)文件也可視作模 塊。因此,你可以require('myJSfile.js')亦可以require('myCSSfile.css')。這意味著我們可以將事物(業務)分割成更小的易于管理的片段,從而達到重復利用等的目的。
- 按需加載傳統的模塊打包工具(module bundlers)最終將所有的模塊編譯生成一個龐大的bundle.js文件。但是在真實的app里邊,“bundle.js”文件可能有10M到15M之大可能會導致應用一直處于加載中狀態。因此Webpack使用許多特性來分割代碼然后生成多個“bundle”文件,而且異步加載部分代碼以實現按需加載。
三、幾處說明
1.開發模式和生產模式
在package.json文件加入如下的scripts項:
"scripts": {
// 運行npm run build 來編譯生成生產模式下的bundles
"build": "webpack --config webpack.config.prod.js",
// 運行npm run dev來生成開發模式下的bundles以及啟動本地server
"dev": "webpack-dev-server"
}
2.webpack-dev-server
我們每修改一次就要需要輸入 npm run dev 是一件非常無聊的事情,幸運的是,我們可以把讓他自己運行,那就是使用webpack-dev-server。
除了提供模塊打包功能,Webpack還提供了一個基于Node.js Express框架的開發服務器,它是一個靜態資源Web服務器,對于簡單靜態頁面或者僅依賴于獨立服務的前端頁面,都可以直接使用這個開發服務器進行開發。在開發過程中,開發服務器會監聽每一個文件的變化,進行實時打包,并且可以推送通知前端頁面代碼發生了變化,從而可以實現頁面的自動刷新。
- 安裝:
npm install --save-dev webpack-dev-server
- 調整npm的package.json中scripts 部分開發命令的配置
{
"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 服務器。
幾個參數的解釋:
--devtool eval:為你的代碼創建源地址。當有任何報錯的時候可以讓你更加精確地定位到文件和行號
--progress:顯示合并代碼進度
--colors -- hot:命令行中顯示顏色
--content-base 指向設置的輸出目錄
--手動訪問 http://localhost:8080
簡單來說,當你運行 npm run dev的時候,webpack會幫你會啟動一個 Web 服務器,然后監聽文件修改,然后自動重新合并你的代碼。真的非常簡潔。
注意點
用webpack-dev-server生成bundle.js文件是在內存中的,并沒有實際生成;
如果引用的文件夾中已經有bundle.js就不會自動刷新了,你需要先把bundle.js文件手動刪除(后期有插件可以完成);
用webstorm的同學注意了,因為webstorm是自動保存的,所以可能識別的比較慢,你需要手動的ctrl+s一下;
幾個報錯
- webpack版本的問題
如果webpack使用的1.x的版本,那么webpack-dev-server也要使用1.x的版本,否則會報如下錯誤:Connot find module 'webpack/bin/config-yargs'。 - 端口占用問題
如果已經有一個工程中使用了webpack-dev-server,并且在運行中,沒有關掉的話,那么8080端口就被占用了,此時如果在另一個工程中使用webpack-dev-server就會報錯:Error: listen EADDRINUSE 127.0.0.1:8080。
webpack-dev-server(有利于在開發模式下編譯)
這是一個基于Express.js框架開發的web server,默認監聽8080端口。server內部調用Webpack,這樣做的好處是提供了額外的功能如熱更新“Live Reload”以及熱替換“Hot Module Replacement”(即HMR)。
webpack-dev-server的“hot” 和 “inline”選項
“inline”選項會為入口頁面添加“熱加載”功能,“hot”選項則開啟“熱替換(Hot Module Reloading)”,即嘗試重新加載組件改變的部分(而不是重新加載整個頁面)。如果兩個參數都傳入,當資源改變時,webpack-dev-server將會先嘗試HRM(即熱替換),如果失敗則重新加載整個入口頁面。
// 當資源發生改變,以下三種方式都會生成新的bundle,但是又有區別:
// 1. 不會刷新瀏覽器
$ webpack-dev-server
//2. 刷新瀏覽器
$ webpack-dev-server --inline
//3. 重新加載改變的部分,HRM失敗則刷新頁面
$ webpack-dev-server --inline --hot
3.“entry”:值分別是字符串、數組和對象的情況
- 數組類型
添加多個彼此不互相依賴的文件,你可以使用數組格式的值。 - 對象
現在,假設你的應用是多頁面的(multi-page application)而不是SPA,有多個html文件(index.html和profile.html)。然后你通過一個對象告訴Webpack為每一個html生成一個bundle文件。
以下的配置將會生成兩個js文件:indexEntry.js和profileEntry.js分別會在index.html和profile.html中被引用。 - 混合類型
enter對象里使用數組類型,例如下面的配置將會生成3個文件:vender.js(包含三個文件),index.js和profile.js文件。
4. output:“path”項和“publicPath”項output項
告訴webpack怎樣存儲輸出結果以及存儲到哪里。output的兩個配置項“path”和“publicPath”可能會造成困惑。
“path”僅僅告訴Webpack結果存儲在哪里,然而“publicPath”項則被許多Webpack的插件用于在生產模式下更新內嵌到css、html文件里的url值。
5..babelrc 文件
babal-loader使用”presets“配置項來標識如何將ES6語法轉成ES5以及如何轉換React的JSX成js文件。我們可以用如下的方式使用”query“參數傳入配置:
module: {
loaders: [ { test: /\.jsx?$/,
exclude: /(node_modulesbower_components)/,
loader: 'babel',
query: { presets: ['react', 'es2015'] } } ]
}
然而在很多項目里babal的配置可能比較大,因此你可以把babal-loader的配置項單獨保存在一個名為”.babelrc“的文件中,在執行時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然后壓縮和混淆內容以減小文件體積。
類似的extract-text-webpack-plugin內部使用css-loader和style-loader來收集所有的css到一個地方最終將結果提取結果到一個獨立的”styles.css“文件,并且在html里邊引用style.css文件。
//webpack.config.js
// 獲取所有的.css文件,合并它們的內容然后提取css內容到一個獨立的”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標簽內聯到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
修改開發配置文件
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、圖片等資源文件都支持打包;
- 串聯式 模塊加載器 以及 插件機制 ,讓其具有更好的靈活性和擴展性,例如提供對CoffeeScript、ES6的支持;
- 有獨立的配置文件webpack.config.js;
- 可以將代碼切割成不同的chunk,實現按需加載,降低了初始化時間;
- 支持 SourceUrls 和 SourceMaps,易于調試;
- 具有強大的Plugin接口,大多是內部插件,使用起來比較靈活;
- webpack 使用異步 IO 并具有多級緩存。這使得 webpack 很快且在增量編譯上更加快;
webpack最常用與spa應用,主要是vue和React,其實它就非常像Browserify,但是將應用打包為多個文件。如果單頁面應用有多個頁面,那么用戶只從下載對應頁面的代碼. 當他么訪問到另一個頁面, 他們不需要重新下載通用的代碼。