之前都是gulp之類的,現在都什麼時代了!是時候學習webpack,現在都webpack4.x了,沒辦法,我這種弱雞就只能webpack2開始。
webpack官網文件
前提
- 安裝node,安裝成功,
npm -v
cmd命令來檢查是否安裝成功。 - 安裝webpack,
npm install [email protected]
局部安裝,全局安裝使用npm install [email protected] -g
,如果忘記版本號,npm view webpack versions --json
查看所有版本號。 - 下載淘寶鏡像源,國內npm下載很慢,而且有些包下下來會有問題,比如node-sass。所以需要切換爲淘寶鏡像源,
npm install -g cnpm --registry=https://registry.npm.taobao.org
cls
清空命令提示符窗口內容
webpack(我的系統是win10)
處理html
- 在根目錄下生成package.json文件: npm init -y
- 安裝webapck到開發模式 :cnpm install webpack --save-dev
- 創建webpack.config.js文件: echo > webpack.config.js
var path = require("path")
module.exports={
<!-- 要打包的文件 -->
entry:"./index.js",
output:{
<!-- 指定打包後的文件名字 -->
filename:"bundle.js",
<!-- 打包後的文件路徑 -->
path:path.resolve(__dirname,"dist")
}
}
- 創建src目錄,並在src目錄下創建index.html,index.js文件並隨便輸入一點東西
window.onload = function(){
alert(1)
}
- 執行 webpack 命令,可以發現webpack幫我們在dist下生成了一個main.js文件,打開main.js並拖到最下面你會發現index.js的內容就在裏面。
打包完之後,我們在dist生成了js文件,但是我們的index.html在src下面,你可以手動的複製src下的html文件到dist目錄下,並且將打包後的js文件引入。不過像我們這麼懶的人,還是希望webpack能夠幫我們在dist下也生成index.html,要是能自動引入打包後的js文件,那就再好不過了。這時候,是時候來一發插件了。 - cnpm install html-webpack-plugin --save-dev
修改webpack.config.js
var HtmlWebpackPlugin = require('html-webpack-plugin');
var path = require("path")
module.exports = {
entry:"./index.js",
output: {
path: path.resolve(__dirname, 'dist'),
filename:"bundle.js",
},
plugins: [new HtmlWebpackPlugin({
title: "測試"
})]
}
重新執行命令 webpack ,你會發現在dist下多生成了一個index.html文件,打開發現還有一個script的標籤引用着我們打包後的文件,nice。
不過問題又來了,html文件很簡陋,就是emmet幫我們生成的Html5文件,你可能希望還帶有更多的 meta標籤,像這樣的
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
<meta name="format-detection" content="telephone=no, email=no"/>
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<title></title>
</head>
<body>
<header></header>
<nav></nav>
<div id="app"></div>
<footer></footer>
</body>
— 此時,你可以自己寫一個模板,只需要告訴html-webpack-plugin插件文件的位置就可以了。
修改webpack.config.js
plugins: [
new htmlWebpackPlugin({
title:"首頁",
<!-- 指定模板位置 -->
template:'./src/index.html',
<!-- 指定打包後的文件名字 -->
filename: 'index.html'
})
]
對於多疑症的你,打包多次後你可能會懷疑文件修改生效了沒有,此時你可以安裝clean-webpack-plugin
插件,在每次打包時,先刪除原來的dist
目錄,再重新打包,這樣就可以放心的睡覺,不用擔心門沒關了。
11. cnpm install clean-webpack-plugin --save-dev
plugins:[
new htmlWebpackPlugin({
title:"首頁",
template:'./src/index.html',
filename: 'index.html'
}),
<!-- 每次打包前先刪除dist目錄 -->
new CleanWebpackPlugin(['dist'])
]
處理css
以往我們寫css都是寫好後手動通過link引入到html,使用webpack後,你將不再需要手動做這些操作,只需要在js文件中引入,webpack就能幫你搞定,不過需要一些loader和plugin的支持。
cnpm install --save-dev css-loade style-loader
修改webpack.config.js
###爲了處理css文件我們需要多配置一個module參數,並使用css-loader來將css文件打包到成“字符串”到js文件中,並使用style-loader將打包後的字符串提取出來並append<style>標籤到head下面
var path=require("path");
var htmlWebpackPlugin=require('html-webpack-plugin');
var CleanWebpackPlugin = require('clean-webpack-plugin');
module.exports={
entry:{
main:'./src/index.js'
},
output:{
filename:"bundle.js",
path:path.resolve(__dirname,'dist')
},
module:{
rules:[
<!-- test檢測到以xxx結尾的東西use對應的loader -->
{
test: /\.css$/,
use: [ 'style-loader', 'css-loader' ]
}
]
},
plugins:[
new htmlWebpackPlugin({
title:"首頁",
template:'./src/index.html',
filename:"index.html"
}),
new CleanWebpackPlugin(['dist'])
]
}
哦,聽說你想用sass 預處理 器,那麼只需要在use里加多一個sass-loader,並安裝依賴
cnpm install --save-dev sass-loader node-sass
rules:[
{
test: /\.scss$/,
use: [ 'style-loader', 'css-loader',"sass-loader" ]
}
]
什麼,想要自動補全瀏覽器後綴autoprefixer?沒問題
rules:[
{
test: /\.s?css$/,
use: [
{
loader: "style-loader"
},
{
loader: "css-loader"
},
{
loader: 'postcss-loader',
options: {
plugins: [
require('autoprefixer')({
browsers: ['last 10 versions']
})
]
}
},
{
loader: "sass-loader"
}
]
}
]
這裏需要注意,use裏面的執行順序是倒序的 ,,webpack會以倒敘的方式處理並將處理後的結果返回給上一個loader,最後通過style-loader將文件的內容append到head下面的style標籤裏。
我還想要自動刷新呢?
** 當你修改一點文件後,你需要重執行命令新編譯文件,然後刷新頁面。如果你使用過gulp的自動刷新比如live-reload,那你也一定希望webpack也有同樣的功能,webpack-dev-server滿足你的需求並且能夠給你更多**
cnpm install --save-dev webpack-dev-server
<!-- 在package.json中加入: -->
"scripts": {
"dev": "webpack-dev-server"
}
通過npm run dev
即可執行創建一個服務器,打開localhsot:8080
此時再修改文件,你會發現頁面自動刷新了並且修改生效了,不過你看不到重新編譯後的文件在哪裏,應爲webpack-dev-server
將文件編譯到了內存中 ,比起重新生成文件效率會更高,當然只適用於開發階段。
啓動服務後,如果你 還想讓他自己 打開Localhost,還想 使用模塊熱重載 ,可以加多一個配置
devServer:{
open:true,
hot: true,// 告訴 dev-server我們想用HMR模式
}
開發的時候我們並不在意 style這種形式,但是我們希望在生產環境下 css能從js文件宏分離出來,我們希望能css能跟js並行加載,而且可以避免因爲Js文件過大,加載慢而產生的flash of unstyle(無樣式頁面閃爍)。
**使用“extract-text-webpack-plugin”插件來分離css代碼。 **
cnpm install --save-dev extract-text-webpack-plugin
修改webpack.config.js
var path=require("path");
var webpack=require('webpack');
var htmlWebpackPlugin=require('html-webpack-plugin');
var CleanWebpackPlugin = require('clean-webpack-plugin');
var ExtractTextPlugin = require("extract-text-webpack-plugin");
var extractSass = new ExtractTextPlugin({
filename: "[name].[contenthash].css",
disable: process.env.NODE_ENV === "development"
});
//通過設置環境變量來告訴webpack我們在development模式下不需要提取css到單獨在文件,只有當不是development下(也即是production)才需要提取文件。
module.exports={
entry:{
main:'./src/index.js'
},
output:{
filename:"bundle.js",
path:path.resolve(__dirname,'dist')
},
devServer:{
open:true,
hot: true, <!-- 告訴 dev-server我們想用HMR模式 -->
},
module:{
rules:[
{
test: /\.s?css$/,
use: extractSass.extract({
use: [
{
loader: "css-loader"
},
{
loader: 'postcss-loader',
options: {
plugins: [
require('autoprefixer')
]
}
},
{
loader: "sass-loader"
}],
fallback: "style-loader"
})
}
]
},
plugins:[
<!-- 使用此插件來支持熱重載 -->
new webpack.HotModuleReplacementPlugin(),
new htmlWebpackPlugin({
title:"首頁",
template:'./src/index.html',
filename:"index.html"
}),
new CleanWebpackPlugin(['dist']),
extractSass
]
}
<!-- package.json -->
"scripts": {
"dev": "set NODE_ENV=development&&webpack-dev-server",
"build":"webpack -p"
}
分別執行npm run dev以及npm run build,你會發先npm run build時css文件被提取到一個單獨的文件了。
處理js
爲了使用promise,async等es6,7語法,同時兼容低版本瀏覽器,你還需要將js轉碼爲es5。
**
cnpm install --save-dev
"babel-core": "^6.25.0",
"babel-loader": "^7.0.0",
"babel-plugin-transform-object-rest-spread": "^6.23.0",
"babel-plugin-transform-runtime": "^6.23.0",
"babel-preset-env": "^1.5.2",
**
webpack.config.js修改
{
test: /\.js$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader',
options: {
presets: ['env'],
plugins: ['transform-runtime']
}
}
}
寫一點async或者prmise代碼,打包後發現promise變成了_promise2
代理
前後端分離開發時經常還會遇到跨域的問題,還可以利用devServer來進行代理
devServer:{
open:true,
hot: true,
proxy: {
'/api/': {
target: 'http://baidu.com',
secure: false,
changeOrigin: true
}
}
$.ajax({
url:'/api/',
success:function(res){
console.log(res)
},
error:function(res){
console.log(res)
}
})
圖片,字體
{
test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
loader: 'url-loader',
query: {
limit: 8000,
<!-- 小於8k的轉化爲Base64 -->
name: '[name].[hash:7].[ext]'
}
},
{
test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/,
loader: 'url-loader',
query: {
limit: 8000,
name: 'font/[name].[hash:7].[ext]'
}
}
你可能想要把所有img或者font文件分別放到一個img或者font文件夾下,可以這麼寫:name: 'img/[name].[hash:7].[ext]'
如果你不想下載字體文件下來,可已上傳到阿里字體庫並使用阿里的在線字體圖標,並複製自己的文件的在線地址。
在css裏面像這麼用:@import url("//at.alicdn.com/t/font_s0zqwb6by3lhm2t9.css");
打包多文件
var path=require("path");
var webpack=require('webpack');
var htmlWebpackPlugin=require('html-webpack-plugin');
var CleanWebpackPlugin = require('clean-webpack-plugin');
var ExtractTextPlugin = require("extract-text-webpack-plugin");
var extractSass = new ExtractTextPlugin({
filename: "[name].[contenthash].css",
disable: process.env.NODE_ENV === "development"
});
module.exports={
entry:{
"main":'./src/js/index.js',
"car":"./src/js/car.js",
"goods":"./src/js/goods.js"
},
output:{
filename:"[name].[hash].js",
path:path.resolve(__dirname,'dist')
},
devServer:{
open:true,
hot: true,
proxy: {
'/api/': {
target: 'http://baidu.com',
secure: false,
changeOrigin: true
}
}
},
module:{
rules:[
{
test: /\.s?css$/,
use: extractSass.extract({
use: [
{
loader: "css-loader"
},
{
loader: 'postcss-loader',
options: {
plugins: [
require('autoprefixer')({
browsers: ['last 10 versions']
})
]
}
},
{
loader: "sass-loader"
}],
fallback: "style-loader"
})
},
{
test: /\.js$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader',
options: {
presets: ['env'],
plugins: ['transform-runtime']
}
}
},
{
test: /\.html$/,
use: ['html-withimg-loader']
},
{
test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
loader: 'url-loader',
query: {
limit: 8000,
name: 'img/[name].[hash:7].[ext]'
}
},
{
test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/,
loader: 'url-loader',
query: {
limit: 8000,
name: 'font/[name].[hash:7].[ext]'
}
}
]
},
plugins:[
new webpack.HotModuleReplacementPlugin(),
new htmlWebpackPlugin({
title:"首頁",
template:'./src/index.html',
filename:"index.html",
chunks:["page/main/main"]
}),
new htmlWebpackPlugin({
title:"購物車",
template:'./src/index.html',
filename:"car.html",
chunks:["page/car/car"]
}),
new htmlWebpackPlugin({
title:"商品",
template:'./src/index.html',
filename:"goods.html",
chunks:["page/goods/goods"]
}),
new CleanWebpackPlugin(['dist']),
extractSass,
new webpack.ProvidePlugin({
$:"jquery",
jQuery:"jquery"
})
]
}
上面這樣寫打包後文件都放在一個目錄下,目錄很亂,不方便管理,想讓每個頁面的js,css文件放在對應目錄下,可以按下面這麼寫
entry:{
"page/main/main":'./src/js/index.js',
"page/car/car":"./src/js/car.js",
"page/goods/goods":"./src/js/goods.js"
}
如果多個文件都引用了一些其他庫,比如Jquery,vue,你可能想把所有的公共庫提取出來,利用common-chunk
插件可以解決。即使你做的是spa單頁面應用,你也可以將公共庫從js文件中提取出來,每次修改時只修改業務邏輯而不重新打包庫,從而可以緩存庫文件。
entry:{
"main/main":'./src/js/index.js',
"car/car":"./src/js/car.js",
"goods/goods":"./src/js/goods.js"
"vendor":["jquery","vue"]
},
plugins:[
new htmlWebpackPlugin({
...同上
chunks:["page/main/main","vendor","mainfest"]
}),
new htmlWebpackPlugin({
...
chunks:["page/car/car","vendor","mainfest"]
}),
new htmlWebpackPlugin({
...
chunks:["page/goods/goods","vendor","mainfest"]
}),
new webpack.optimize.CommonsChunkPlugin({
name:["vendor","mainfest"]
})
]
可以發現,我們在entry中手動引入了多個文件,在plugins中,我們又手動的多次new htmlWebpackPlugin()。雖然能夠正常的運行,然而存在很多限制。首先存在大量重複的代碼,現在只有3個頁面,假如現有10個頁面,那麼你就得手動的new10次,在enrty中輸入10個入口。其次,當增加新的頁面時,又得過來webpack的配置裏面手動的再加入到入口文件中。明顯的,對於這些重複的而且長得很像的,如果能自動獲取到需要配置的文件,並且在一個循環裏面調用,那就方便多了。
使用node.js的golb模塊來獲取文件,並且使用webpack-merge來合併對象
//引入glob
var glob= require('glob');
//同步讀取src目錄下所有的html文件
var files = glob.sync('./src/*.html');
var entry={};
var plugins=[];
//循環將文件
files.forEach(function(item,i){
//item類似:./src/index.html
var htmlName=item.slice(item.lastIndexOf("/")+1);
//最後生成的文件名只需要最後面的名字index.html
var name=htmlName.split(".")[0];
//添加到entry入口,並制定生成文件的目錄
entry["page/"+name+"/"+name]='./src/js/'+name+'.js'
//生成htmlWebpackPlugin實例
plugins.push(
new htmlWebpackPlugin({
template:item,
filename:htmlName,
chunks:["page/"+name+"/"+name/*]
})
)
});
module.exports={
entry:entry,
output:{
filename:"[name].[chunkhash].js",
path:path.resolve(__dirname,'dist'),
},
module:{
rules:[
{
test: /.js$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader'
}
},
]
},
plugins:plugins
}
可以發現,使用glob.sync來讀取文件並進行循環操作,原本需要手動操作的部分,循環已經幫我們處理好了。原本需要很多new new htmlWebpackPlugin()已經不再存在,而且即使後續需要添加新的頁面,也不需要我們手動去添加,glob.sync會自己去讀取所有的*.html文件,接着循環會幫我們處理。這裏唯一需要注意的地方就是,index.html對應的js文件必須爲index.js,ceshi.html文件對應的文件必須爲ceshi.js,至於js裏面需要引入其他庫,或者自己寫的工具函數,哪個頁面只需引入即可,webpack會自己處理好。
提取相同的庫文件到單獨的文件中
當頁面存在引用相同的庫時,最好還是將這些庫文件提取到單獨的文件,頁面只保留業務邏輯的js代碼。爲了提取文件,有2種處理方案:
- 使用CommonsChunkPlugin來提取在上面的代碼上增加:(以下這部分代碼只在使用CommonsChunkPlugin時需要)
entry.vendor=[
//舉例如下:
"vue","vuex","axios","jquery","vue-router","moment","lodash"
];
plugins=[
new webpack.optimize.CommonsChunkPlugin({
name:["vendor","manifest"]
})
]
//修改循環中的chunks, 增加vendor以及manifest:
files.forEach(function(item,i){
plugins.push(
new htmlWebpackPlugin({
template:item,
filename:htmlName,
chunks:["page/"+name+"/"+name,"vendor","mainfest"]
})
)
});
再執行webpack,可以發現公共的庫已經被提取到單獨的vendor文件。
2. 使用DLL
對於 CommonsChunkPlugin,webpack 每次打包實際還是需要去處理這些第三方庫,只是打包完之後,能把第三方庫和我們自己的代碼分開。而DLLPlugin 則是能把第三方代碼完全分離開,即每次只打包項目自身的代碼。
這裏我使用的是autodll-webpack-plugin。
npm install --save-dev autodll-webpack-plugin
//引入模塊
var AutoDllPlugin = require('autodll-webpack-plugin');
//在plugins中添加插件:(需要放在循環之前,防止數組被重新賦值)
var plugins=[
new AutoDllPlugin({
//文件名
filename: '[name].[hash].js',
//打包後的文件路徑
path: './page/common/',
//是否將生成的Js文件注入到html文件中
inject:true,
//需要分離的庫
entry: {
vendor: [
"vue","vuex","axios","jquery","vue-router","moment","lodash","vue-touch","vue-lazyload"
]
},
//壓縮代碼(注意這裏是壓縮分離出來的庫文件代碼,跟外面的要區分開來,如果外面的需要壓縮,外面的plugins中也需要new一個壓縮實例)
plugins: [
new webpack.optimize.UglifyJsPlugin()
]
})
]
執行webpack命令,可以發現公共的庫文件也被打包到單獨的文件了。autodll-webpack-plugin在內存中緩存了文件,所以當你第二此執行webpack命令進行打包時,可以明顯發現打包時間少了很多。而CommonsChunkPlugin每次都全部重新打包再進行分離,所以當需要多次修改文件時,autodll-webpack-plugin可以明顯降低打包的時間,尤其是依賴很多外部庫的時候。
使用webpack-merge區分生成環境和開發環境:
很多時候,我們都需要針對不同的環境進行不用的操作。
//比如在生成環境下分離css到單獨文件:
var extractSass = new ExtractTextPlugin({
filename: "[name].[contenthash].css",
disable: process.env.NODE_ENV === "development"
});
//比如在生成環境下要壓縮代碼:
new UglifyJSPlugin();
//在開發環境下使用代理
devServer:{
proxy: {
'api': {
target: 'http://api.douban.com/v2/movie/',
secure: false,
changeOrigin: true
}
}
通常來說會用process.env.NODE_ENV === "development"
,並且在package.json
中設置環境變量來進行判斷,不過當文件大了或者頁面需要判斷的地方多了之後,配置文件就會充斥着大量三元表達式。可以考慮用webpack-merge
將配置文件拆分爲3個文件,一個是webpack.common.js
,即不管是生產環境還是開發環境都會用到的部分,以及webpack.product.js
和webpack.dev.js
。
//webpack.common.js
var path = require('path');
var HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
entry: {
app: './src/index.js'
},
output: {
filename: '[name].bundle.js',
path: path.resolve(__dirname, 'dist')
},
module: {
rules:[
{
test: /.js$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader'
}
},
]
},
plugins: [
new HtmlWebpackPlugin({
title: 'test'
})
],
}
//開發環境webpack.dev.js
var merge = require('webpack-merge');
var common = require('./webpack.common.js');
module.exports = merge(common, {
module:{
rules:[
{
test: /.css$/,
use:["style-loader","css-loader"]
}
]
},
devtool: 'inline-source-map',
devServer:{
open:true,
hot: true,
proxy: {
'/api/': {
target: 'http://baidu.com',
secure: false,
changeOrigin: true
}
}
},
})
//生產環境webpack.product.js
var merge = require('webpack-merge');
var UglifyJSPlugin = require('uglifyjs-webpack-plugin');
var common = require('./webpack.common.js');
var ExtractTextPlugin = require("extract-text-webpack-plugin");
var cleanPlugin = require("clean-webpack-plugin");
var extractSass = new ExtractTextPlugin({
filename: "[name].[contenthash].css",
});
module.exports = merge(common, {
module: {
rules: [{
test: /.css$/,
use: extractSass.extract({
use: [{
loader: "css-loader"
}, ],
fallback: "style-loader"
})
}]
},
devtool: 'source-map',
plugins: [
new cleanPlugin(["dist"]),
new UglifyJSPlugin(),
extractSass,
]
});
在package.json中修改webpack默認配置文件
"scripts": {
"dev": "webpack-dev-server --config webpack.dev.js",
"build": "webpack --config webpack.product.js"
},
通過npm run dev來開發,及npm run build來打包。