概念
本質(zhì)上公罕,webpack 是一個(gè)現(xiàn)代 JavaScript 應(yīng)用程序的靜態(tài)模塊打包器(module bundler)楼眷。當(dāng) webpack 處理應(yīng)用程序時(shí)熊尉,它會(huì)遞歸地構(gòu)建一個(gè)依賴關(guān)系圖(dependency graph)狰住,其中包含應(yīng)用程序需要的每個(gè)模塊催植,然后將所有這些模塊打包成一個(gè)或多個(gè) bundle创南。
從 webpack v4.0.0 開始稿辙,可以不用引入一個(gè)配置文件邻储。然而笔刹,webpack 仍然還是高度可配置的舌菜。在開始前你需要先理解四個(gè)核心概念:
- 入口(entry)
- 輸出(output)
- loader
- 插件(plugins)
入口(entry)
入口起點(diǎn)(entry point)指示 webpack 應(yīng)該使用哪個(gè)模塊日月,來作為構(gòu)建其內(nèi)部依賴圖的開始爱咬。進(jìn)入入口起點(diǎn)后精拟,webpack 會(huì)找出有哪些模塊和庫是入口起點(diǎn)(直接和間接)依賴的蜂绎∈υ妫可以通過在 webpack 配置中配置 entry 屬性践美,來指定一個(gè)入口起點(diǎn)(或多個(gè)入口起點(diǎn))陨倡。默認(rèn)值為 ./src。
接下來我們看一個(gè) entry 配置的最簡單例子:
webpack.config.js
module.exports = {
entry: './path/to/my/entry/file.js'
};
根據(jù)應(yīng)用程序的特定需求矛缨,可以以多種方式配置 entry 屬性
出口(output)
output 屬性告訴 webpack 在哪里輸出它所創(chuàng)建的 bundles箕昭,以及如何命名這些文件落竹,默認(rèn)值為 ./dist述召』基本上夺刑,整個(gè)應(yīng)用程序結(jié)構(gòu)遍愿,都會(huì)被編譯到你指定的輸出路徑的文件夾中。你可以通過在配置中指定一個(gè) output 字段耘斩,來配置這些處理過程:
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 的名稱括授,以及我們想要 bundle 生成(emit)到哪里坞笙。可能你想要了解在代碼最上面導(dǎo)入的 path 模塊是什么荚虚,它是一個(gè) Node.js 核心模塊薛夜,用于操作文件路徑曲管。
loader
loader 讓 webpack 能夠去處理那些非 JavaScript 文件(webpack 自身只理解 JavaScript)却邓。loader 可以將所有類型的文件轉(zhuǎn)換為 webpack 能夠處理的有效模塊,然后你就可以利用 webpack 的打包能力院水,對(duì)它們進(jìn)行處理腊徙。
本質(zhì)上简十,webpack loader 將所有類型的文件,轉(zhuǎn)換為應(yīng)用程序的依賴圖(和最終的 bundle)可以直接引用的模塊撬腾。
注意螟蝙,loader 能夠 import 導(dǎo)入任何類型的模塊(例如 .css 文件),這是 webpack 特有的功能民傻,其他打包程序或任務(wù)執(zhí)行器的可能并不支持胰默。我們認(rèn)為這種語言擴(kuò)展是有很必要的,因?yàn)檫@可以使開發(fā)人員創(chuàng)建出更準(zhǔn)確的依賴關(guān)系圖漓踢。
在更高層面牵署,在 webpack 的配置中 loader 有兩個(gè)目標(biāo):
- test 屬性,用于標(biāo)識(shí)出應(yīng)該被對(duì)應(yīng)的 loader 進(jìn)行轉(zhuǎn)換的某個(gè)或某些文件喧半。
- use 屬性奴迅,表示進(jìn)行轉(zhuǎn)換時(shí),應(yīng)該使用哪個(gè) loader挺据。
webpack.config.js
const path = require('path');
const config = {
output: {
filename: 'my-first-webpack.bundle.js'
},
module: {
rules: [
{ test: /\.txt$/, use: 'raw-loader' }
]
}
};
module.exports = config;
以上配置中取具,對(duì)一個(gè)單獨(dú)的 module 對(duì)象定義了 rules 屬性,里面包含兩個(gè)必須屬性:test 和 use扁耐。
插件(plugins)
loader 被用于轉(zhuǎn)換某些類型的模塊暇检,而插件則可以用于執(zhí)行范圍更廣的任務(wù)。插件的范圍包括婉称,從打包優(yōu)化和壓縮块仆,一直到重新定義環(huán)境中的變量。插件接口功能極其強(qiáng)大酿矢,可以用來處理各種各樣的任務(wù)榨乎。
想要使用一個(gè)插件,你只需要 require() 它瘫筐,然后把它添加到 plugins 數(shù)組中。多數(shù)插件可以通過選項(xiàng)(option)自定義铐姚。你也可以在一個(gè)配置文件中因?yàn)椴煌康亩啻问褂猛粋€(gè)插件策肝,這時(shí)需要通過使用 new 操作符來創(chuàng)建它的一個(gè)實(shí)例。
webpack.config.js
const HtmlWebpackPlugin = require('html-webpack-plugin'); // 通過 npm 安裝
const webpack = require('webpack'); // 用于訪問內(nèi)置插件
const config = {
module: {
rules: [
{ test: /\.txt$/, use: 'raw-loader' }
]
},
plugins: [
new webpack.optimize.UglifyJsPlugin(),
new HtmlWebpackPlugin({template: './src/index.html'})
]
};
module.exports = config;
模式
通過選擇 development 或 production 之中的一個(gè)隐绵,來設(shè)置 mode 參數(shù)之众,你可以啟用相應(yīng)模式下的 webpack 內(nèi)置的優(yōu)化
module.exports = {
mode: 'production'
};
Vue項(xiàng)目webpack配置參數(shù)
在項(xiàng)目目錄下新建一個(gè)名為build目錄,里面用于存放各種配置文件依许,涉及到基礎(chǔ)配置棺禾、開發(fā)和生產(chǎn)環(huán)境、靜態(tài)服務(wù)器以及熱加載峭跳,詳細(xì)的內(nèi)容請(qǐng)看下面的代碼:
- webpack.conf.js(基本配置文件)
// 引入依賴模塊
var path = require('path');
var webpack = require('webpack');
var ExtractTextPlugin = require('extract-text-webpack-plugin');
module.exports = {
// 入口文件膘婶,路徑相對(duì)于本文件所在的位置缺前,可以寫成字符串、數(shù)組悬襟、對(duì)象
entry: {
// path.resolve([from ...], to) 將to參數(shù)解析為絕對(duì)路徑
index:path.resolve(__dirname, '../src/entry/index.js'),
// 需要被提取為公共模塊的群組
vendors:['vue','vue-router','jquery'],
},
// 輸出配置
output: {
// 輸出文件衅码,路徑相對(duì)于本文件所在的位置
path: path.resolve(__dirname, '../output/static/js/'),
// 設(shè)置publicPath這個(gè)屬性會(huì)出現(xiàn)很多問題:
// 1.可以看成輸出文件的另一種路徑,差別路徑是相對(duì)于生成的html文件脊岳;
// 2.也可以看成網(wǎng)站運(yùn)行時(shí)的訪問路徑逝段;
// 3.該屬性的好處在于當(dāng)你配置了圖片CDN的地址,本地開發(fā)時(shí)引用本地的圖片資源割捅,上線打包時(shí)就將資源全部指向CDN了奶躯,如果沒有確定的發(fā)布地址不建議配置該屬性,特別是在打包圖片時(shí)亿驾,路徑很容易出現(xiàn)混亂巫糙,如果沒有設(shè)置,則默認(rèn)從站點(diǎn)根目錄加載
// publicPath: '../static/js/',
// 基于文件的md5生成Hash名稱的script來防止緩存
filename: '[name].[hash].js',
// 非主入口的文件名颊乘,即未被列在entry中参淹,卻又需要被打包出來的文件命名配置
chunkFilename: '[id].[chunkhash].js'
},
// 其他解決方案
resolve: {
// require時(shí)省略的擴(kuò)展名,遇到.vue結(jié)尾的也要去加載
extensions: ['','.js', '.vue'],
// 模塊別名地址乏悄,方便后續(xù)直接引用別名浙值,無須寫長長的地址,注意如果后續(xù)不能識(shí)別該別名檩小,需要先設(shè)置root
alias:{}
},
// 不進(jìn)行打包的模塊
externals:{},
// 模塊加載器
module: {
// loader相當(dāng)于gulp里的task开呐,用來處理在入口文件中require的和其他方式引用進(jìn)來的文件,test是正則表達(dá)式规求,匹配要處理的文件筐付;loader匹配要使用的loader,"-loader"可以省略阻肿;include把要處理的目錄包括進(jìn)來瓦戚,exclude排除不處理的目錄
loaders: [
// 使用vue-loader 加載 .vue 結(jié)尾的文件
{
test: /\.vue$/,
loader: 'vue-loader',
exclude: /node_modules/
},
// 使用babel 加載 .js 結(jié)尾的文件
{
test: /\.js$/,
loader: 'babel',
exclude: /node_modules/,
query:{
presets: ['es2015', 'stage-0'],
plugins: ['transform-runtime']
}
},
// 使用css-loader和style-loader 加載 .css 結(jié)尾的文件
{
test: /\.css$/,
// 將樣式抽取出來為獨(dú)立的文件
loader: ExtractTextPlugin.extract("style-loader", "css-loader!autoprefixer-loader"),
exclude: /node_modules/
},
// 使用less-loader、css-loader和style-loade 加載 .less 結(jié)尾的文件
{
test: /\.less$/,
// 將樣式抽取出來為獨(dú)立的文件
loader: ExtractTextPlugin.extract("style-loader", "css-loader!autoprefixer-loader!less-loader"),
exclude: /node_modules/
},
// 加載圖片
{
test: /\.(png|jpg|gif)$/,
loader: 'url-loader',
query: {
// 把較小的圖片轉(zhuǎn)換成base64的字符串內(nèi)嵌在生成的js文件里
limit: 10000,
// 路徑要與當(dāng)前配置文件下的publicPath相結(jié)合
name:'../img/[name].[ext]?[hash:7]'
}
},
// 加載圖標(biāo)
{
test: /\.(eot|woff|woff2|svg|ttf)([\?]?.*)$/,
loader: 'file-loader',
query: {
// 把較小的圖標(biāo)轉(zhuǎn)換成base64的字符串內(nèi)嵌在生成的js文件里
limit: 10000,
name:'../fonts/[name].[ext]?[hash:7]',
prefix:'font'
}
},
]
},
// 配置插件項(xiàng)
plugins: []
}
- webpack.dev.config.js(開發(fā)環(huán)境下的配置文件)
// 引入依賴模塊
var path = require('path');
var webpack = require('webpack');
var HtmlWebpackPlugin = require('html-webpack-plugin');
var ExtractTextPlugin = require('extract-text-webpack-plugin');
// 引入基本配置
var config = require('./webpack.config.js');
// 必須修改原配置中網(wǎng)站運(yùn)行時(shí)的訪問路徑丛塌,相當(dāng)于絕對(duì)路徑较解,修改完之后,當(dāng)前配置文件下的很多相對(duì)路徑都是相對(duì)于這個(gè)來設(shè)定赴邻;
// 注意:webpack-dev-server會(huì)實(shí)時(shí)的編譯印衔,但是最后的編譯的文件并沒有輸出到目標(biāo)文件夾,而是保存到了內(nèi)存當(dāng)中
config.output.publicPath = '/';
// 重新配置模塊加載器
config.module= {
// test是正則表達(dá)式姥敛,匹配要處理的文件奸焙;loader匹配要使用的loader,"-loader"可以省略;include把要處理的目錄包括進(jìn)來与帆,exclude排除不處理的目錄
loaders: [
// 使用vue-loader 加載 .vue 結(jié)尾的文件
{
test: /\.vue$/,
loader: 'vue-loader',
exclude: /node_modules/
},
// 使用babel 加載 .js 結(jié)尾的文件
{
test: /\.js$/,
loader: 'babel',
exclude: /node_modules/,
query:{
presets: ['es2015', 'stage-0'],
plugins: ['transform-runtime']
}
},
// 使用css-loader了赌、autoprefixer-loader和style-loader 加載 .css 結(jié)尾的文件
{
test: /\.css$/,
// 將樣式抽取出來為獨(dú)立的文件
loader: ExtractTextPlugin.extract("style-loader", "css-loader!autoprefixer-loader"),
exclude: /node_modules/
},
// 使用less-loader、autoprefixer-loader鲤桥、css-loader和style-loade 加載 .less 結(jié)尾的文件
{
test: /\.less$/,
// 將樣式抽取出來為獨(dú)立的文件
loader: ExtractTextPlugin.extract("style-loader", "css-loader!autoprefixer-loader!less-loader"),
exclude: /node_modules/
},
// 加載圖片
{
test: /\.(png|jpg|gif)$/,
loader: 'url-loader',
query: {
// 把較小的圖片轉(zhuǎn)換成base64的字符串內(nèi)嵌在生成的js文件里
limit: 10000,
// 路徑和生產(chǎn)環(huán)境下的不同揍拆,要與修改后的publickPath相結(jié)合
name: 'img/[name].[ext]?[hash:7]'
}
},
// 加載圖標(biāo)
{
test: /\.(eot|woff|woff2|svg|ttf)([\?]?.*)$/,
loader: 'file-loader',
query: {
limit: 10000,
// 路徑和生產(chǎn)環(huán)境下的不同,要與修改后的publickPath相結(jié)合
name:'fonts/[name].[ext]?[hash:7]',
prefix:'font'
}
},
]
};
// 重新配置插件項(xiàng)
config.plugins = [
// 位于開發(fā)環(huán)境下
new webpack.DefinePlugin({
'process.env': {
NODE_ENV: '"development"'
}
}),
// 自動(dòng)生成html插件茶凳,如果創(chuàng)建多個(gè)HtmlWebpackPlugin的實(shí)例嫂拴,就會(huì)生成多個(gè)頁面
new HtmlWebpackPlugin({
// 生成html文件的名字,路徑和生產(chǎn)環(huán)境下的不同贮喧,要與修改后的publickPath相結(jié)合筒狠,否則開啟服務(wù)器后頁面空白
filename: 'src/pages/index.html',
// 源文件,路徑相對(duì)于本文件所在的位置
template: path.resolve(__dirname, '../src/pages/index.html'),
// 需要引入entry里面的哪幾個(gè)入口箱沦,如果entry里有公共模塊辩恼,記住一定要引入
chunks: ['vendors','index'],
// 要把<script>標(biāo)簽插入到頁面哪個(gè)標(biāo)簽里(body|true|head|false)
inject: 'body',
// 生成html文件的標(biāo)題
title:''
// hash如果為true,將添加hash到所有包含的腳本和css文件谓形,對(duì)于解除cache很有用
// minify用于壓縮html文件灶伊,其中的removeComments:true用于移除html中的注釋,collapseWhitespace:true用于刪除空白符與換行符
}),
// 提取css單文件的名字寒跳,路徑和生產(chǎn)環(huán)境下的不同聘萨,要與修改后的publickPath相結(jié)合
new ExtractTextPlugin("[name].[contenthash].css"),
// 提取入口文件里面的公共模塊
new webpack.optimize.CommonsChunkPlugin({
name: 'vendors',
filename: 'vendors.js',
}),
// 為組件分配ID,通過這個(gè)插件webpack可以分析和優(yōu)先考慮使用最多的模塊童太,并為它們分配最小的ID
new webpack.optimize.OccurenceOrderPlugin(),
// 模塊熱替換插件
new webpack.HotModuleReplacementPlugin(),
// 允許錯(cuò)誤不打斷程序
new webpack.NoErrorsPlugin(),
// 全局掛載插件
new webpack.ProvidePlugin({
$:"jquery",
jQuery:"jquery",
"window.jQuery":"jquery"
})
];
// vue里的css也要單獨(dú)提取出來
config.vue = {
loaders: {
css: ExtractTextPlugin.extract("css")
}
};
// 啟用source-map米辐,開發(fā)環(huán)境下推薦使用cheap-module-eval-source-map
config.devtool='cheap-module-eval-source-map';
// 為了實(shí)現(xiàn)熱加載,需要?jiǎng)討B(tài)向入口配置中注入 webpack-hot-middleware/client 书释,路徑相對(duì)于本文件所在的位置
// var devClient = 'webpack-hot-middleware/client';
// 為了修改html文件也能實(shí)現(xiàn)熱加載翘贮,需要修改上面的devClient變量,引入同級(jí)目錄下的dev-client.js文件
var devClient = './build/dev-client';
// Object.keys()返回對(duì)象的可枚舉屬性和方法的名稱
Object.keys(config.entry).forEach(function (name, i) {
var extras = [devClient];
config.entry[name] = extras.concat(config.entry[name]);
})
module.exports = config;
- webpack.prod.config.js(生產(chǎn)環(huán)境下的配置文件)
// 引入依賴模塊
var path = require('path');
var webpack = require('webpack');
var HtmlWebpackPlugin = require('html-webpack-plugin');
var ExtractTextPlugin = require('extract-text-webpack-plugin');
// 引入基本配置
var config = require('./webpack.config');
// 重新配置插件項(xiàng)
config.plugins = [
// 位于生產(chǎn)環(huán)境下
new webpack.DefinePlugin({
'process.env': {
NODE_ENV: '"production"'
}
}),
// 自動(dòng)生成html插件爆惧,如果創(chuàng)建多個(gè)HtmlWebpackPlugin的實(shí)例狸页,就會(huì)生成多個(gè)頁面
new HtmlWebpackPlugin({
// 生成html文件的名字,路徑相對(duì)于輸出文件所在的位置
filename: '../../html/index.html',
// 源文件检激,路徑相對(duì)于本文件所在的位置
template: path.resolve(__dirname, '../src/pages/index.html'),
// 需要引入entry里面的哪幾個(gè)入口肴捉,如果entry里有公共模塊,記住一定要引入
chunks: ['vendors','special','index'],
// 要把<script>標(biāo)簽插入到頁面哪個(gè)標(biāo)簽里(body|true|head|false)
inject: 'body',
// 生成html文件的標(biāo)題
title:'',
// hash如果為true叔收,將添加hash到所有包含的腳本和css文件,對(duì)于解除cache很有用
// minify用于壓縮html文件傲隶,其中的removeComments:true用于移除html中的注釋饺律,collapseWhitespace:true用于刪除空白符與換行符
}),
// 提取css單文件的名字,路徑相對(duì)于輸出文件所在的位置
new ExtractTextPlugin("../css/[name].[contenthash].css"),
// 提取入口文件里面的公共模塊
new webpack.optimize.CommonsChunkPlugin({
name: 'vendors',
filename: 'vendors.js',
}),
// 壓縮js代碼
new webpack.optimize.UglifyJsPlugin({
compress: {
warnings: false
},
// 排除關(guān)鍵字跺株,不能混淆
except:['$','exports','require']
}),
// 為組件分配ID复濒,通過這個(gè)插件webpack可以分析和優(yōu)先考慮使用最多的模塊脖卖,并為它們分配最小的ID
new webpack.optimize.OccurenceOrderPlugin(),
// 全局掛載插件,當(dāng)模塊使用這些變量的時(shí)候巧颈,wepback會(huì)自動(dòng)加載畦木,區(qū)別于window掛載
new webpack.ProvidePlugin({
$:"jquery",
jQuery:"jquery",
"window.jQuery":"jquery"
})
];
// vue里的css也要單獨(dú)提取出來
config.vue = {
loaders: {
css: ExtractTextPlugin.extract("css")
}
};
// 開啟source-map,生產(chǎn)環(huán)境下推薦使用cheap-source-map或source-map砸泛,后者得到的.map文件體積比較大十籍,但是能夠完全還原以前的js代碼
config.devtool='source-map';
// 關(guān)閉source-map
// config.devtool=false;
module.exports = config;
- dev-server.js(服務(wù)器配置文件)
// 引入依賴模塊
var express = require('express');
var webpack = require('webpack');
var config = require('./webpack.dev.config.js');
// 創(chuàng)建一個(gè)express實(shí)例
var app = express();
// 對(duì)網(wǎng)站首頁的訪問返回 "Hello World!" 字樣
app.get('/', function (req, res) {
res.send('Hello World!');
});
// 調(diào)用webpack并把配置傳遞過去
var compiler = webpack(config);
// 使用 webpack-dev-middleware 中間件,搭建服務(wù)器
var devMiddleware = require('webpack-dev-middleware')(compiler, {
publicPath: config.output.publicPath,
stats: {
colors: true,
chunks: false
}
})
// 使用 webpack-hot-middleware 中間件唇礁,實(shí)現(xiàn)熱加載
var hotMiddleware = require('webpack-hot-middleware')(compiler);
// 為了修改html文件也能實(shí)現(xiàn)熱加載勾栗,使用webpack插件來監(jiān)聽html源文件改變事件
compiler.plugin('compilation', function (compilation) {
compilation.plugin('html-webpack-plugin-after-emit', function (data, cb) {
// 發(fā)布事件
hotMiddleware.publish({ action: 'reload' });
cb();
})
});
// 注冊(cè)中間件
app.use(devMiddleware);
app.use(hotMiddleware);
// 監(jiān)聽 8888 端口,開啟服務(wù)器
app.listen(8888, function (err) {
if (err) {
console.log(err);
return;
}
console.log('Listening at http://localhost:8888');
})
- dev-client.js(配合dev-server.js監(jiān)聽html文件改動(dòng)也能夠觸發(fā)自動(dòng)刷新)
// 引入 webpack-hot-middleware/client
var hotClient = require('webpack-hot-middleware/client');
// 訂閱事件盏筐,當(dāng) event.action === 'reload' 時(shí)執(zhí)行頁面刷新
hotClient.subscribe(function (event) {
if (event.action === 'reload') {
window.location.reload();
}
})
- 為了不必每次構(gòu)建項(xiàng)目都要輸入webpack --display-modules --display-chunks --config build/webpack.config.js這條長命令围俘,我們?cè)趐ackage.js文件中修改“scripts”項(xiàng):
"scripts": {
"build":"webpack --display-modules --display-chunks --config build/webpack.config.js",
"dev":"node ./build/dev-server.js"
}