更新:Webpack4已經(jīng)發(fā)布飘言,本篇是基于Webpack3的衣形,請注意。
更正:
1.package.json中使用了應該使用本地webpack而不是全局webpack热凹。"scripts": { "test": "echo \"Error: no test specified\" && exit 1", "dist": "webpack", "build:dll": "webpack --config webpack.config.dll.js", "build:dev": "webpack-dev-server --config webpack.config.dev.js --open", "build:pord": "webpack --config webpack.config.pord.js" },
應為:
"scripts": { "test": "echo \"Error: no test specified\" && exit 1", "dist": "./node_modules/.bin/webpack", "build:dll": "./node_modules/.bin/webpack --config webpack.config.dll.js", "build:dev": "./node_modules/.bin/webpack-dev-server --config webpack.config.dev.js --open", "build:pord": "./node_modules/.bin/webpack --config webpack.config.pord.js" },
- webpack.config.js文件中output.publicPath缺失泵喘,應為:
output: { publicPath: "http://www.example.com/", // <---- 這里填寫web訪問地址,域名般妙、IP、CDN等 filename: '[name]_[chunkhash:20].js', // 輸出帶20位hash的文件名 path: path.resolve(__dirname, 'dist'), // 輸出路徑 },
output.publicPath會更正文件或資源的前綴相速,在正式部署時特別必要碟渺。
一、什么是webpack突诬,為什么要使用它苫拍?
1.官方解釋
webpack是一個現(xiàn)代JavaScript應用程序的模塊打包器(module bundler)。
2.個人理解
webpack是一個模塊打包機:它做的事情是旺隙,分析你的項目結構绒极,找到JavaScript模塊進行處理,找到其它的模塊如css及一些瀏覽器不能直接運行的拓展語言模塊Scss蔬捷,TypeScript等垄提,并選擇相應的插件進行處理榔袋,并將處理結果轉換打包為合適的格式供瀏覽器使用。
3.優(yōu)缺點
Grunt以及Gulp是在一個配置文件中指定對文件處理的具體流程铡俐,如校驗->編譯->組合->壓縮等凰兑。配置比較繁瑣,但是可定制性比較高审丘。
webpack把整個項目當成整體吏够,從入口查找文件關系,然后整體的以文件為單位使用loader對代碼進行處理滩报,最后打包為一個或者多個瀏覽器識別的文件锅知。配置相對簡單,處理速度快脓钾,高級場景下定制能力有限售睹,但是大部分情況下可以替代Grunt以及Gulp。
二惭笑、安裝webpack
1.安裝
1.1前置條件
安裝nodejs侣姆,nodejs.org,不再詳述
1.2安裝方式
在終端運行命令:
# 全局安裝
npm install -g webpack
# 安裝到項目目錄
npm install --save-dev webpack
上面兩個命令可以選擇執(zhí)行一個沉噩,但建議執(zhí)行兩個捺宗。項目中安裝一個置指定版本的webpack,全局的webpack作為備份項川蒙。
三蚜厉、項目中的webpack
1.項目
如果已經(jīng)有npm項目請略過。
# 前往工作區(qū)畜眨,[$WORKSPACE]替換為自己的工作區(qū)路徑
cd [$WORKSPACE]
# 創(chuàng)建項目目錄
mkdir webpack_demo
# 初始化package.json昼牛,可以一路略過
npm init
2.安裝webpack
# 安裝的項目目錄
npm install --save-dev webpack
3.Demo01 - Hello Webpack
建立如下目錄結構
.
├── dist
│ └── index.html
├── package.json
├── public
└── src
├── Welcome.js
└── index.js
代碼如下:
<!--index.html-->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Webpack Sample Project</title>
</head>
<body>
<div id='root'>
</div>
<script src="bundle.js"></script>
</body>
</html>
// index.js
import welcome from './Welcome.js'
document.querySelector("#root").appendChild(welcome());
// Welcome.js
export default function() {
var welcome = document.createElement('div');
welcome.textContent = "Hello webpack!";
return welcome;
};
webpack可以通過命令行使用,可以先通過下面的方式調(diào)用:
$> node_modules/.bin/webpack src/index.js dist/bundle.js
Hash: 098382ad52c485fbdff8
Version: webpack 3.6.0
Time: 66ms
Asset Size Chunks Chunk Names
bundle.js 2.78 kB 0 [emitted] main
[0] ./src/index.js 96 bytes {0} [built]
[1] ./src/Welcome.js 138 bytes {0} [built]
此時可以動過瀏覽器訪問dist/index.html
4.Demo02 - 通過配置文件來使用webpack
webpack雖然支持使用命令行的方式配置使用康聂,但Demo01中使用命令行的方式輸入起來還是比較繁瑣贰健。webpack支持使用配置文件進行參數(shù)配置。
在項目根目錄下創(chuàng)建webpack.config.js文件(這也是webpack的默認配置文件):
const path = require('path');
module.exports = {
entry: './src/index.js',
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, 'dist')
}
};
然后可以執(zhí)行命令進行編譯打包:
# 指定配置文件
./node_modules/.bin/webpack --config webpack.config.js
# 使用默認配置文件
./node_modules/.bin/webpack
# 使用作用域最近的webpack恬汁,并且使用默認配置文件
webpack
雖然現(xiàn)在可以直接使用webpack
命令進行打包伶椿,但是如果需要定制其他的打包配置時,就會比較麻煩氓侧,這個時候可以用npm腳本脊另。
在package.json中scripts項下添加:
{
...
"scripts": {
"dist": "webpack"
},
...
}
此時可以通過命令行npm run dist
進行打包。
5.資源管理,loader
5.1.Demo03 - CSS樣式
CSS需要使用css-loader和style-loader進行處理约巷。css-loader解釋@import和url()偎痛,會import/require()后再解析它們。style-loader會把原來的CSS代碼插入頁面中的一個style標簽中独郎。
先安裝style-loader和css-loader
npm install --save-dev style-loader css-loader
修改webpack.config.js如下:
const path = require('path');
module.exports = {
entry: './src/index.js',
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, 'dist')
},
module: {
rules: [{
test: /\.css$/,
use: [
'style-loader',
'css-loader'
]
}]
}
};
修改Welcome.js如下:
import './style.css';
export default function() { // 這里之前的‘module.exports = ’寫法打包正常踩麦,但是運行時報錯
var welcome = document.createElement('div');
welcome.textContent = "Hello webpack!";
welcome.classList.add('welcome');
return welcome;
};
添加src/style.css
文件:
.welcome {
color: red;
}
npm run dist
打包后運行枚赡,可以看到welcome樣式已經(jīng)發(fā)生改變。
5.2.Demo04 - 圖片資源
圖片等靜態(tài)資源在開發(fā)階段時的相對路徑與生產(chǎn)環(huán)境中的不盡相同靖榕,所以需要對這些文件進行路徑轉換标锄,此時可以使用file-loader。另外如果圖片較多茁计,會發(fā)送很多請求料皇,而瀏覽器能夠同時發(fā)送的請求數(shù)有限,大量的圖片資源會降低也面性能星压,可以通過url-loader把部分圖片編碼打包到文件中践剂,url-loader是對file-loader的封裝,這里使用url-loader娜膘。
安裝file-loader逊脯、url-loader:
npm install --save-dev file-loader url-loader
添加資源文件src/image/iconrar.png
(105KB),src/image/iconrar.png
(87KB)
修改webpack.config.js:
...
module.exports = {
...
module: {
rules: [
...
{
test: /\.(png|svg|jpg|gif)$/,
use: [
{
loader: 'url-loader',
options: {
limit: 102400, // 這里以100KB為分界線
name: '[name].[ext]',
}
}
]
}
...
]
}
...
};
修改src/index.js
文件:
import welcome from './Welcome.js'
import image from './Image.js'
import './style.css';
const root = document.querySelector("#root")
root.appendChild(welcome());
root.appendChild(image('image1'));
root.appendChild(image('image2'));
添加src/Image.js
文件:
export default function(className) {
var image = document.createElement('div');
image.classList.add(className);
return image;
}
在src/style.js
中添加樣式
...
.image1 {
width: 200px;
height: 200px;
background-image: url('./image/iconrar.png');
background-size: contain;
}
.image2 {
width: 200px;
height: 200px;
background-image: url('./image/iconzip.png');
background-size: contain;
}
npm run dist
打包后運行,可以看到圖片已經(jīng)添加到了頁面上竣贪。觀察dist
文件夾军洼,只有一個圖片拷貝進來,然后再觀察bundle.js文件演怎,會發(fā)現(xiàn)有一片圖片編碼區(qū)匕争。
6.Demo05 - 分片打包
隨著項目體量的增加,整個項目打包為單一的bundle可能會嚴重影響首屏加載時間爷耀,這個時候可以把原來單一的bundle分為多個文件甘桑,也就是webpack分片打包。
修改webpack.config.js歹叮,在配置中添加入口跑杭,這里可以使用動態(tài)入口,如使用node代碼遍歷代碼文件咆耿。
...
entry: {
index: './src/index.js',
image: './src/Image.js',
welcome: './src/Welcome.js',
},
output: {
filename: '[name].js',
path: path.resolve(__dirname, 'dist'),
},
...
此時嘗試打包成功德谅,但是訪問index.html異常,控制臺報錯萨螺。因為index.html中引用的bundle.js已經(jīng)變更為三個js文件女阀。因為這個入口配置可能經(jīng)常改變,手動去dist/index.html
修改script引用比較繁瑣屑迂,可以使用HtmlWebpackPlugin
插件來自動生成dist/index.html
,或者使用html-webpack-template
插件在模版的基礎上生成dist/index.html
冯键。這里使用HtmlWebpackPlugin
插件惹盼。
安裝html-webpack-plugin
npm install --save-dev html-webpack-plugin
修改webpack.config.js
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
...
plugins: [
new HtmlWebpackPlugin({ title: 'Webpack Sample Project' })
]
...
};
使用html-webpack-plugin
生成的dist/index.html
已經(jīng)沒有id為root的div了,我們修改一下src/index.js
import welcome from './Welcome.js'
import image from './Image.js'
import './style.css';
const root = document.querySelector('body')
root.appendChild(welcome());
root.appendChild(image('image1'));
root.appendChild(image('image2'));
現(xiàn)在已經(jīng)可以使用webpack來分片打包了惫确。webpack把打包后的內(nèi)容放置在dist
目錄中手报,目錄中的內(nèi)容部署后蚯舱,瀏覽器就能夠訪問到。但是由于瀏覽器的緩存策略掩蛤,如果我們在部署新版本時不更改資源的文件名枉昏,瀏覽器可能會認為它沒有被更新,就會使用它的緩存版本揍鸟。由于緩存的存在,當你需要獲取新的代碼時,就會顯得很棘手争占。
為了解決這一問題肿仑,我們可以使用output.filename
來進行文件名替換,來確保瀏覽器獲取到修改后的文件腥泥。
修改webpack.config.js匾南,在輸出的文件名中加入哈希碼chunkhash
...
output: {
filename: '[name]_[chunkhash].js',
path: path.resolve(__dirname, 'dist'),
},
...
這時候打包后可以正常訪問,但是你會發(fā)現(xiàn)dist
文件夾下會有很多文件蛔外,這是因為隨著每次修改項目文件蛆楞,項目文件的chunkhash發(fā)生改變,會生成新的輸出文件夹厌,但是舊有的文件沒有被清除豹爹。通過使用clean-webpack-plugin
插件可以在每次打包前刪除dist文件夾。
安裝clean-webpack-plugin
npm install --save-dev clean-webpack-plugin
修改webpack.config.js
const path = require('path');
const CleanWebpackPlugin = require('clean-webpack-plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
...
plugins: [
new CleanWebpackPlugin(['dist']),
new HtmlWebpackPlugin({ title: 'Webpack Sample Project' })
]
...
};
現(xiàn)在完整的webpack.config.js
文件如下:
const path = require('path');
const CleanWebpackPlugin = require('clean-webpack-plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
entry: {
index: './src/index.js',
image: './src/Image.js',
welcome: './src/Welcome.js',
},
output: {
filename: '[name]_[chunkhash].js',
path: path.resolve(__dirname, 'dist'),
},
module: {
rules: [{
test: /\.css$/,
use: [
'style-loader',
'css-loader'
]
}, {
test: /\.(png|svg|jpg|gif)$/,
use: [
{
loader: 'url-loader',
options: {
limit: 102400,
name: '[name]_[hash:20].[ext]',
outputPath: 'image/'
}
}
]
}]
},
plugins: [
new CleanWebpackPlugin(['dist']),
new HtmlWebpackPlugin({ title: 'Webpack Sample Project' })
]
};
7.Demo06 - 調(diào)試開發(fā)和熱加載HMR
生產(chǎn)環(huán)境中僅部署時需要打包一下尊流,但是在開發(fā)環(huán)境中需要不停的運行命令進行打包帅戒,是一件十分繁瑣的事情,可以使用webpack-dev-server
工具在代碼發(fā)生改變時自動打包代碼崖技。
另外逻住,我們會在上面配置文件的基礎上對開發(fā)環(huán)境dev和生產(chǎn)環(huán)境pord進行擴展。需要使用webpack-merge
模塊迎献。
安裝webpack-dev-server
和webpack-merge
:
npm install --save-dev webpack-dev-server webpack-merge
創(chuàng)建webpack.config.dev.js
文件:
const merge = require('webpack-merge');
const webpack = require('webpack');
const config = require('./webpack.config.js');
module.exports = merge(config, {
devtool: 'cheap-module-eval-source-map', // 選擇一個合適的sourcemap項
output: {
filename: '[name].js', // 在熱加載時不能使用chunkhash
},
plugins: [
new webpack.HotModuleReplacementPlugin()
],
devServer: {
contentBase: './dist',
historyApiFallback: true,
hot: true,
inline: true,
host: '127.0.0.1',
port: 8080,
}
});
修改package.json
瞎访,添加npm腳本:
{
...
"scripts": {
"dist": "webpack",
"build:dev": "webpack-dev-server --config webpack.config.dev.js --open"
},
...
}
現(xiàn)在嘗試運行npm run build:dev
,瀏覽器會自動訪問127.0.0.1:8080/index.html
,即contentBase
指定的目錄下的index.html文件吁恍。
8.Demo07 - 混淆壓縮和生產(chǎn)打包
生產(chǎn)環(huán)境下代碼簡單編譯打包扒秸,不經(jīng)混淆壓縮就部署,容易被別有用心的人獲取并分析代碼冀瓦,可能會增加產(chǎn)權問題和安全風險伴奥。webpack自帶的uglifyjs-webpack-plugin插件可以來做混淆壓縮
安裝uglifyjs-webpack-plugin:
npm install --save-dev uglifyjs-webpack-plugin
創(chuàng)建webpack.config.pord.js
文件:
const merge = require('webpack-merge');
const UglifyJSPlugin = require('uglifyjs-webpack-plugin');
const config = require('./webpack.config.js');
module.exports = merge(config, {
plugins: [
new UglifyJSPlugin()
]
});
修改package.json
,添加npm腳本:
{
...
"scripts": {
"dist": "webpack",
"build:dev": "webpack-dev-server --config webpack.config.dev.js --open",
"build:pord": "webpack --config webpack.config.pord.js"
},
...
}
現(xiàn)在嘗試運行npm run build:pord
翼闽,這個時候可以查看dist
文件夾下的js文件拾徙,可以看到js已經(jīng)被混淆壓縮了。同時感局,uglifyjs-webpack-plugin
實現(xiàn)了tree-shaking
能力尼啡,能夠優(yōu)化掉未使用的代碼暂衡。
9.Dome08 - 公共代碼分離
公共代碼分為兩部分,一部分是三方的庫崖瞭,一部分是項目中寫的復用程度比較高的代碼狂巢。實例項目中三方庫使用lodash
和moment
,而src/Welcome.js
和src/Image.js
作為服用程度比較高的庫被單獨打包书聚。
我們需要新建入口文件復制index.js
為index2.js
唧领,作為第二個入口文件。這樣src/Welcome.js
和src/Image.js
為兩個入口公用的模塊寺惫,可以單獨打爆出來疹吃。
打包公共模塊在開發(fā)環(huán)境和生產(chǎn)環(huán)境都適用,所以修改webpack.config.js
文件如下:
const path = require('path');
const webpack = require('webpack');
const CleanWebpackPlugin = require('clean-webpack-plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
entry: {
vendor: ['lodash', 'moment'], // 三方庫
common: ['./src/Welcome.js', './src/Image.js'], // 項目中復用程度高的代碼
index: './src/index.js',
index2: './src/index2.js',
},
output: {
filename: '[name]_[chunkhash:20].js', // 輸出帶20位hash的文件名
path: path.resolve(__dirname, 'dist'), // 輸出路徑
},
module: {
rules: [{
test: /\.css$/, // CSS loader
use: [
'style-loader',
'css-loader'
]
}, {
test: /\.(png|svg|jpg|gif)$/, // 圖片loader
use: [
{
loader: 'url-loader',
options: {
limit: 102400,
name: '[name]_[hash:20].[ext]',
outputPath: 'image/'
}
}
]
}]
},
plugins: [
new CleanWebpackPlugin(['dist']), // 清理dist文件件
new webpack.optimize.CommonsChunkPlugin({ // 打包公共模塊
names: ['vendor', 'common'],
minChunks: 2,
}),
new HtmlWebpackPlugin({ // 生成html入口
filename: 'index.html',
title: 'Webpack Sample Project',
chunks: ['vendor', 'common', 'index'],
}),
new HtmlWebpackPlugin({ // 生成html入口2
filename: 'index2.html',
title: 'Webpack Sample Project2',
chunks: ['vendor', 'common', 'index2'],
}),
]
};
此時使用命令npm run dist
打包西雀,可以查看dist
文件夾下的文件萨驶,src/Welcome.js
和src/Image.js
的確被打包到了commons_[chunkhash:20].js
文件中,而lodash
和moment
也被打包到了vender_[chunkhash:20].js
文件中艇肴,index_[chunkhash:20].js
和index2_[chunkhash:20].js
中并不含上面兩部分代碼腔呜。
雖然這樣公共代碼已經(jīng)能夠分離了,但是再悼,項目越來越大核畴,像lodash
和moment
這樣的三方庫也越來越多,每次打包都需要重新生成vender.js
文件冲九,特別是在開發(fā)中谤草,效率嚴重被拉低。
webpack提供了dll技術來解決這個問題莺奸。
添加webpack.config.dll.js
:
const path = require('path');
const webpack = require('webpack');
const CleanWebpackPlugin = require('clean-webpack-plugin');
const UglifyJSPlugin = require('uglifyjs-webpack-plugin');
module.exports = {
context: __dirname,
entry: {
vendor: ['lodash', 'moment'], // 三方庫
},
output: {
filename: '[name]_[hash:20].js', // 輸出帶20位hash的文件名
path: path.resolve(__dirname, 'dist/lib'), // 輸出路徑
library: '[name]_[hash:20]',
},
plugins: [
new CleanWebpackPlugin(['dist/lib']), // 清理dist文件件
new UglifyJSPlugin(),
new webpack.DllPlugin({
path: path.join(__dirname, 'dist/lib', '[name]_manifest.json'),
name: '[name]_[hash:20]',
})
]
};
此時可以使用webpack --config webpack.config.dll.js
命令進行dll打包了丑孩。接下來需要使用add-asset-html-webpack-plugin
插件來把上面配置文件生成的vendor_[hash:20].js
寫入到html-webpack-plugin
插件生成的index.html
中。
安裝add-asset-html-webpack-plugin
:
npm install --save-dev add-asset-html-webpack-plugin
修改webpack.config.js
如下:
const path = require('path');
const webpack = require('webpack');
const CleanWebpackPlugin = require('clean-webpack-plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const AddAssetHtmlPlugin = require('add-asset-html-webpack-plugin');
module.exports = {
entry: {
common: ['./src/Welcome.js', './src/Image.js'], // 項目中復用程度高的代碼
index: './src/index.js',
index2: './src/index2.js',
},
output: {
filename: '[name]_[chunkhash:20].js', // 輸出帶20位hash的文件名
path: path.resolve(__dirname, 'dist'), // 輸出路徑
},
module: {
rules: [{
test: /\.css$/, // CSS loader
use: [
'style-loader',
'css-loader'
]
}, {
test: /\.(png|svg|jpg|gif)$/, // 圖片loader
use: [
{
loader: 'url-loader',
options: {
limit: 102400,
name: '[name]_[hash:20].[ext]',
outputPath: 'image/'
}
}
]
}]
},
plugins: [
new CleanWebpackPlugin(['dist/*.*']), // 清理dist文件件
new webpack.DllReferencePlugin({ // webpack Dll
context: __dirname,
manifest: require(path.resolve(__dirname, 'dist/lib/vendor_manifest.json')),
}),
new webpack.optimize.CommonsChunkPlugin({ // 打包公共模塊
name: 'common',
minChunks: 2,
}),
new webpack.optimize.CommonsChunkPlugin({ // manifest
name: "manifest",
minChunks: Infinity
}),
new HtmlWebpackPlugin({ // 生成html入口
filename: 'index.html',
title: 'Webpack Sample Project',
chunks: ['manifest', 'common', 'index'],
}),
new AddAssetHtmlPlugin({ // 把dll生成的js文件拷貝到html
files: 'index.html',
includeSourcemap: false,
filepath: path.resolve(__dirname, './dist/lib/*.js'),
}),
new HtmlWebpackPlugin({ // 生成html入口2
filename: 'index2.html',
title: 'Webpack Sample Project2',
chunks: ['manifest', 'common', 'index2'],
}),
new AddAssetHtmlPlugin({ // 把dll生成的js文件拷貝到html
files: 'index2.html',
includeSourcemap: false,
filepath: path.resolve(__dirname, './dist/lib/*.js'),
}),
]
};
為了體現(xiàn)lodash
和moment
能夠正常使用:
修改src/Welcome.js
import moment from 'moment';
import _ from 'lodash';
export default function() {
var arr = [0, 1, 2];
_.fill(arr, 0);
arr[1] = [moment().format('YYYY-MM-DD')];
var welcome = document.createElement('div');
welcome.textContent = 'Hello webpack!' + arr.join(' <> ');
welcome.classList.add('welcome');
return welcome;
};
添加npm腳本:
"scripts": {
"dist": "webpack",
"build:dll": "webpack --config webpack.config.dll.js",
"build:dev": "webpack-dev-server --config webpack.config.dev.js --open",
"build:pord": "webpack --config webpack.config.pord.js"
},
這樣灭贷,第一次打包前運行npm run build:dll
生成vendor包温学,然后運行npm run build:dev
或npm run build:pord
進行開發(fā)打包或部署打包。
10.Demo09 - CSS分離
安裝add-asset-html-webpack-plugin
:
npm install --save-dev extract-text-webpack-plugin
修改webpack.config.js
如下:
...
const ExtractTextPlugin = require("extract-text-webpack-plugin");
module.exports = {
...
module: {
rules: [{
test: /\.css$/, // CSS loader
use: ExtractTextPlugin.extract({
fallback: "style-loader",
use: "css-loader"
})
}, {
test: /\.(png|svg|jpg|gif)$/, // 圖片loader
use: [
{
loader: 'url-loader',
options: {
publicPath: path.resolve(__dirname, 'dist'),// 這里需要修正image的publicPath
limit: 102400,
name: '[name]_[hash:20].[ext]',
outputPath: '/image/'
}
}
]
}]
},
plugins: [
new CleanWebpackPlugin([ // 清理dist文件件
'dist/*.*',
'dist/image/*.*',
'dist/style/*.*'
]),
new ExtractTextPlugin({ // CSS分離
filename: 'style/[name]_[contenthash:20].css',
}),
...
]
};
前往webpack.config.dev.js甚疟,把默認config中實例化的CleanWebpackPlugin給splice掉仗岖,就可以在npm run dist
、npm run build:dev
览妖、npm run build:pord
三個環(huán)境下進行打包了轧拄。