webpack 是什么?
本質(zhì)上,webpack 是一個(gè)現(xiàn)代 JavaScript 應(yīng)用程序的靜態(tài)模塊打包器(module bundler)。當(dāng) webpack 處理應(yīng)用程序時(shí),它會遞歸地構(gòu)建一個(gè)依賴關(guān)系圖(dependency graph),其中包含應(yīng)用程序需要的每個(gè)模塊,然后將所有這些模塊打包成一個(gè)或多個(gè) bundle。
快速了解幾個(gè)基本的概念
mode 開發(fā)模式
webpack 提供 mode 配置選項(xiàng),配置 webpack 相應(yīng)模式的內(nèi)置優(yōu)化。
// webpack.production.config.js
module.exports = {
+ mode: 'production',
}
入口文件(entry)
入口文件,類似于其他語言的起始文件。比如:c 語言的 main 函數(shù)所在的文件。
入口起點(diǎn)(entry point)指示 webpack 應(yīng)該使用哪個(gè)模塊,來作為構(gòu)建其內(nèi)部依賴圖的開始。進(jìn)入入口起點(diǎn)后,webpack 會找出有哪些模塊和庫是入口起點(diǎn)(直接和間接)依賴的。
可以在 webpack 的配置文件中配置入口,配置節(jié)點(diǎn)為: entry
,當(dāng)然可以配置一個(gè)入口,也可以配置多個(gè)。
輸出(output)
output 屬性告訴 webpack 在哪里輸出它所創(chuàng)建的 bundles,以及如何命名這些文件。
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'
}
};
loader
loader 讓 webpack 能夠去處理那些非 JavaScript 文件(webpack 自身只理解 JavaScript)。loader 可以將所有類型的文件轉(zhuǎn)換為 webpack 能夠處理的有效模塊,然后你就可以利用 webpack 的打包能力,對它們進(jìn)行處理。
插件(plugins)
loader 被用于轉(zhuǎn)換某些類型的模塊,而插件則可以用于執(zhí)行范圍更廣的任務(wù)。插件的范圍包括,從打包優(yōu)化和壓縮,一直到重新定義環(huán)境中的變量。插件接口功能極其強(qiáng)大,可以用來處理各種各樣的任務(wù)。
webpack 的安裝
請確保安裝了 Node.js
的最新版本。而且已經(jīng)在您的項(xiàng)目根目錄下已經(jīng)初始化好了最基本的package.json
文件
本地安裝 webpack
$ npm install --save-dev webpack
# 如果你使用 webpack 4+ 版本,你還需要安裝 CLI。
npm install --save-dev webpack-cli
安裝完成后,可以添加npm
的script
腳本
// package.json
"scripts": {
"start": "webpack --config webpack.config.js"
}
全局安裝 webpack(不推薦)
將使 webpack 在全局環(huán)境下可用:
npm install --global webpack
注意:不推薦全局安裝 webpack。這會將你項(xiàng)目中的 webpack 鎖定到指定版本,并且在使用不同的 webpack 版本的項(xiàng)目中,可能會導(dǎo)致構(gòu)建失敗。
快速入門完整 demo
- 第一步:創(chuàng)建項(xiàng)目結(jié)構(gòu)
首先我們創(chuàng)建一個(gè)目錄,初始化 npm,然后 在本地安裝 webpack,接著安裝 webpack-cli(此工具用于在命令行中運(yùn)行 webpack):
mkdir webpack-demo && cd webpack-demo
npm init -y
npm install webpack webpack-cli --save-dev
項(xiàng)目結(jié)構(gòu)
webpack-demo
+ |- package.json
+ |- /dist
+ |- index.html
+ |- /src
+ |- index.js
- 第二步:安裝 loadash 依賴和編寫 js 文件
npm install --save lodash
編寫:src/index.js 文件
import _ from 'lodash';
function createDomElement() {
let dom = document.createElement('div');
dom.innerHTML = _.join(['aicoder', '.com', ' wow'], '');
return dom;
}
document.body.appendChild(createDomElement());
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>起步</title>
</head>
<body>
<script src="./main.js"></script>
</body>
</html>
- 第三步:編寫 webpack 配置文件
根目錄下添加 webpack.config.js
文件。
webpack-demo
|- package.json
+ |- webpack.config.js
|- /dist
|- index.html
|- /src
|- index.js
webpack.config.js 內(nèi)容如下:
const path = require('path');
module.exports = {
mode: 'development',
entry: './src/index.js',
output: {
filename: 'main.js',
path: path.resolve(__dirname, './dist')
}
};
- 執(zhí)行構(gòu)建任務(wù)
直接執(zhí)行構(gòu)建任務(wù):
npx webpack
打開: dist/index.html 可以查看到頁面的結(jié)果。
加載非 js 文件
webpack 最出色的功能之一就是,除了 JavaScript,還可以通過 loader 引入任何其他類型的文件
加載 CSS 文件
- 第一步: 安裝 css 和 style 模塊解析的依賴
style-loader
和css-loader
npm install --save-dev style-loader css-loader
- 第二步: 添加 css 解析的 loader
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']
}
]
}
};
-
css-loader
: 輔助解析 js 中的import './main.css'
-
style-loader
: 把 js 中引入的 css 內(nèi)容 注入到 html 標(biāo)簽中,并添加 style 標(biāo)簽.依賴css-loader
你可以在依賴于此樣式的 js 文件中 導(dǎo)入樣式文件,比如:import './style.css'。現(xiàn)在,當(dāng)該 js 模塊運(yùn)行時(shí),含有 CSS 字符串的
<style>
標(biāo)簽,將被插入到 html 文件的<head>
中。
- 第三步: 編寫 css 文件和修改 js 文件
在 src 目錄中添加 style.css
文件
webpack-demo
|- package.json
|- webpack.config.js
|- /dist
|- bundle.js
|- index.html
|- /src
+ |- style.css
|- index.js
|- /node_modules
src/style.css
.hello {
color: red;
}
修改 js 文件
import _ from 'lodash';
+ import './style.css';
function createDomElement() {
let dom = document.createElement('div');
dom.innerHTML = _.join(['aicoder', '.com', ' wow'], '');
+ dom.className = 'hello';
return dom;
}
document.body.appendChild(createDomElement());
最后重新打開 dist 目錄下的 index.html 看一下文字是否變成了紅色的了。
module 配置補(bǔ)充
模塊(module): 這些選項(xiàng)決定了如何處理項(xiàng)目中的不同類型的模塊。
webpack 模塊可以支持如下:
- ES2015 import 語句
- CommonJS require() 語句
- AMD define 和 require 語句
- css/sass/less 文件中的 @import 語句。
- 樣式
(url(...))
或 HTML 文件(<img src=...>)
中的圖片鏈接(image url)
module.noParse
值的類型: RegExp | [RegExp] | function
防止 webpack 解析那些任何與給定正則表達(dá)式相匹配的文件。忽略的文件中不應(yīng)該含有 import, require, define 的調(diào)用,或任何其他導(dǎo)入機(jī)制。忽略大型的 library 可以提高構(gòu)建性能。
module.exports = {
mode: 'devleopment',
entry: './src/index.js',
...
module: {
noParse: /jquery|lodash/,
// 從 webpack 3.0.0 開始,可以使用函數(shù),如下所示
// noParse: function(content) {
// return /jquery|lodash/.test(content);
// }
}
...
};
module.rules
創(chuàng)建模塊時(shí),匹配請求的規(guī)則數(shù)組。這些規(guī)則能夠修改模塊的創(chuàng)建方式。這些規(guī)則能夠?qū)δK(module)應(yīng)用 loader,或者修改解析器(parser)。
module.exports = {
...
module: {
noParse: /jquery|lodash/,
rules: [
{
test: /\.css$/,
use: ['style-loader', 'css-loader']
}
]
}
...
};
module Rule
- Rule 條件詳解
- 字符串:匹配輸入必須以提供的字符串開始。是的。目錄絕對路徑或文件絕對路徑。
- 正則表達(dá)式:test 輸入值。
- 函數(shù):調(diào)用輸入的函數(shù),必須返回一個(gè)真值(truthy value)以匹配。
- 條件數(shù)組:至少一個(gè)匹配條件。
- 對象:匹配所有屬性。每個(gè)屬性都有一個(gè)定義行為。
Rule.test
- { test: Condition }:匹配特定條件。一般是提供一個(gè)正則表達(dá)式或正則表達(dá)式的數(shù)組,但這不是強(qiáng)制的。
module.exports = {
...
module: {
rules: [
{
test: /\.css$/,
use: ['style-loader', 'css-loader']
}
]
}
...
};
其他的條件比如:
-
{ include: Condition }
:匹配特定條件。一般是提供一個(gè)字符串或者字符串?dāng)?shù)組,但這不是強(qiáng)制的。 -
{ exclude: Condition }
:排除特定條件。一般是提供一個(gè)字符串或字符串?dāng)?shù)組,但這不是強(qiáng)制的。 -
{ and: [Condition] }
:必須匹配數(shù)組中的所有條件 -
{ or: [Condition] }
:匹配數(shù)組中任何一個(gè)條件 -
{ not: [Condition] }
:必須排除這個(gè)條件
module.exports = {
...
module: {
rules: [
{
test: /\.css$/,
include: [
path.resolve(__dirname, "app/styles"),
path.resolve(__dirname, "vendor/styles")
],
use: ['style-loader', 'css-loader']
}
]
}
...
};
Rule.use
應(yīng)用于模塊指定使用一個(gè) loader。
Loaders can be chained by passing multiple loaders, which will be applied from right to left (last to first configured).
加載器可以鏈?zhǔn)絺鬟f,從右向左進(jìn)行應(yīng)用到模塊上。
use: [
'style-loader',
{
loader: 'css-loader'
},
{
loader: 'less-loader',
options: {
noIeCompat: true
}
}
];
傳遞字符串(如:use: [ "style-loader" ])是 loader 屬性的簡寫方式(如:use: [ { loader: "style-loader "} ])。
加載 Sass 文件
加載 Sass 需要sass-loader
。
安裝
npm install sass-loader node-sass webpack --save-dev
使用:
// webpack.config.js
module.exports = {
...
module: {
rules: [{
test: /\.scss$/,
use: [{
loader: "style-loader"
}, {
loader: "css-loader"
}, {
loader: "sass-loader"
}]
}]
}
};
為 sass 文件注入內(nèi)容:
如果你要將 Sass 代碼放在實(shí)際的入口文件(entry file)之前,可以設(shè)置 data 選項(xiàng)。此時(shí) sass-loader 不會覆蓋 data 選項(xiàng),只會將它拼接在入口文件的內(nèi)容之前。
{
loader: "sass-loader",
options: {
data: "$env: " + process.env.NODE_ENV + ";"
}
}
注意:由于代碼注入, 會破壞整個(gè)入口文件的 source map。 通常一個(gè)簡單的解決方案是,多個(gè) Sass 文件入口。
創(chuàng)建 Source Map
css-loader
和sass-loader
都可以通過該 options 設(shè)置啟用 sourcemap。
// webpack.config.js
module.exports = {
...
module: {
rules: [{
test: /\.scss$/,
use: [{
loader: "style-loader"
}, {
loader: "css-loader",
options: {
sourceMap: true
}
}, {
loader: "sass-loader",
options: {
sourceMap: true
}
}]
}]
}
};
PostCSS 處理 loader(附帶:添加 css3 前綴)
PostCSS是一個(gè) CSS 的預(yù)處理工具,可以幫助我們:給 CSS3 的屬性添加前綴,樣式格式校驗(yàn)(stylelint),提前使用 css 的新特性比如:表格布局,更重要的是可以實(shí)現(xiàn) CSS 的模塊化,防止 CSS 樣式?jīng)_突。
我們常用的就是使用 PostCSS 進(jìn)行添加前綴,以此為例:
安裝
npm i -D postcss-loader
npm install autoprefixer --save-dev
# 以下可以不用安裝
# cssnext可以讓你寫CSS4的語言,并能配合autoprefixer進(jìn)行瀏覽器兼容的不全,而且還支持嵌套語法
$ npm install postcss-cssnext --save-dev
# 類似scss的語法,實(shí)際上如果只是想用嵌套的話有cssnext就夠了
$ npm install precss --save-dev
# 在@import css文件的時(shí)候讓webpack監(jiān)聽并編譯
$ npm install postcss-import --save-dev
const path = require('path');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
module.exports = {
mode: 'development',
entry: './src/index.js',
output: {
filename: 'main.js',
path: path.resolve(__dirname, './dist')
},
module: {
rules: [
{
test: /\.(sa|sc|c)ss$/,
use: [
'style-loader',
{
loader: 'css-loader',
options: {
sourceMap: true
}
},
{
loader: 'postcss-loader',
options: {
ident: 'postcss',
sourceMap: true,
plugins: loader => [
require('autoprefixer')({ browsers: ['> 0.15% in CN'] }) // 添加前綴
]
}
},
{
loader: 'sass-loader',
options: {
sourceMap: true
}
}
]
}
]
}
};
樣式表抽離成專門的單獨(dú)文件并且設(shè)置版本號
首先以下的 css 的處理我們都把 mode 設(shè)置為 production
。
webpack4 開始使用: mini-css-extract-plugin
插件, 1-3 的版本可以用: extract-text-webpack-plugin
抽取了樣式,就不能再用
style-loader
注入到 html 中了。
npm install --save-dev mini-css-extract-plugin
const path = require('path');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const devMode = process.env.NODE_ENV !== 'production'; // 判斷當(dāng)前環(huán)境是開發(fā)環(huán)境還是 部署環(huán)境,主要是 mode屬性的設(shè)置值。
module.exports = {
mode: 'development',
entry: './src/index.js',
output: {
filename: 'main.js',
path: path.resolve(__dirname, './dist')
},
module: {
rules: [
{
test: /\.(sa|sc|c)ss$/,
use: [
MiniCssExtractPlugin.loader,
'css-loader',
'postcss-loader',
'sass-loader'
]
}
]
},
plugins: [
new MiniCssExtractPlugin({
filename: devMode ? '[name].css' : '[name].[hash].css', // 設(shè)置最終輸出的文件名
chunkFilename: devMode ? '[id].css' : '[id].[hash].css'
})
]
};
再次運(yùn)行打包:
在 dist 目錄中已經(jīng)把 css 抽取到單獨(dú)的一個(gè) css 文件中了。修改 html,引入此 css 就能看到結(jié)果了。
壓縮 CSS
webpack5 貌似會內(nèi)置 css 的壓縮,webpack4 可以自己設(shè)置一個(gè)插件即可。
壓縮 css 插件:optimize-css-assets-webpack-plugin
安裝
npm i -D optimize-css-assets-webpack-plugin
const path = require('path');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin');
const autoprefixer = require('autoprefixer');
module.exports = {
mode: 'production',
entry: './src/index.js',
output: {
filename: 'main.[hash].js',
path: path.resolve(__dirname, './dist')
},
module: {
rules: [
{
test: /\.(sa|sc|c)ss$/,
use: [
MiniCssExtractPlugin.loader,
{
loader: 'css-loader'
},
{
loader: 'postcss-loader',
options: {
ident: 'postcss',
plugins: loader => [autoprefixer({ browsers: ['> 0.15% in CN'] })]
}
},
{
loader: 'sass-loader'
}
]
}
]
},
plugins: [
new MiniCssExtractPlugin({
filename: '[name][hash].css',
chunkFilename: '[id][hash].css'
})
],
optimization: {
minimizer: [new OptimizeCSSAssetsPlugin({})]
}
};
JS 壓縮
壓縮需要一個(gè)插件: uglifyjs-webpack-plugin
, 此插件需要一個(gè)前提就是:mode: 'production'
.
安裝
npm i -D uglifyjs-webpack-plugin
const path = require('path');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const UglifyJsPlugin = require('uglifyjs-webpack-plugin');
const OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin');
const autoprefixer = require('autoprefixer');
module.exports = {
mode: 'production',
entry: './src/index.js',
output: {
filename: 'main.[hash].js',
path: path.resolve(__dirname, './dist')
},
module: {
rules: [
{
test: /\.(sa|sc|c)ss$/,
use: [
MiniCssExtractPlugin.loader,
{
loader: 'css-loader'
},
{
loader: 'postcss-loader',
options: {
ident: 'postcss',
plugins: loader => [autoprefixer({ browsers: ['> 0.15% in CN'] })]
}
},
{
loader: 'sass-loader'
}
]
}
]
},
plugins: [
new MiniCssExtractPlugin({
filename: '[name][hash].css',
chunkFilename: '[id][hash].css'
})
],
optimization: {
minimizer: [
new UglifyJsPlugin({
cache: true,
parallel: true,
sourceMap: true // set to true if you want JS source maps
}),
new OptimizeCSSAssetsPlugin({})
]
}
};
解決 CSS 文件或者 JS 文件名字哈希變化的問題
HtmlWebpackPlugin
插件,可以把打包后的 CSS 或者 JS 文件引用直接注入到 HTML 模板中,這樣就不用每次手動修改文件引用了。
安裝
npm install --save-dev html-webpack-plugin
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const UglifyJsPlugin = require('uglifyjs-webpack-plugin');
const OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin');
const autoprefixer = require('autoprefixer');
module.exports = {
mode: 'production',
entry: './src/index.js',
output: {
filename: 'main.[hash].js',
path: path.resolve(__dirname, './dist')
},
module: {
rules: [
{
test: /\.(sa|sc|c)ss$/,
use: [
MiniCssExtractPlugin.loader,
{
loader: 'css-loader'
},
{
loader: 'postcss-loader',
options: {
ident: 'postcss',
plugins: loader => [autoprefixer({ browsers: ['> 0.15% in CN'] })]
}
},
{
loader: 'sass-loader'
}
]
}
]
},
plugins: [
new MiniCssExtractPlugin({
filename: '[name][hash].css',
chunkFilename: '[id][hash].css'
}),
new HtmlWebpackPlugin({
title: 'AICODER 全棧線下實(shí)習(xí)', // 默認(rèn)值:Webpack App
filename: 'main.html', // 默認(rèn)值: 'index.html'
template: path.resolve(__dirname, 'src/index.html'),
minify: {
collapseWhitespace: true,
removeComments: true,
removeAttributeQuotes: true // 移除屬性的引號
}
})
],
optimization: {
minimizer: [
new UglifyJsPlugin({
cache: true,
parallel: true,
sourceMap: true // set to true if you want JS source maps
}),
new OptimizeCSSAssetsPlugin({})
]
}
};
清理 dist 目錄
每次構(gòu)建,我們的 /dist 文件夾都會保存生成的文件,然后就會非常雜亂。
通常,在每次構(gòu)建前清理 /dist 文件夾,是比較推薦的做法
clean-webpack-plugin
是一個(gè)比較普及的管理插件,讓我們安裝和配置下。
npm install clean-webpack-plugin --save-dev
webpack.config.js
const path = require('path');
....
+ const CleanWebpackPlugin = require('clean-webpack-plugin');
module.exports = {
entry: {
app: './src/index.js',
print: './src/print.js'
},
plugins: [
+ new CleanWebpackPlugin(['dist'])
...
],
output: {
filename: '[name].bundle.js',
path: path.resolve(__dirname, 'dist')
}
...
};
現(xiàn)在執(zhí)行 npm run build,再檢查 /dist 文件夾。如果一切順利,你現(xiàn)在應(yīng)該不會再看到舊的文件,只有構(gòu)建后生成的文件!
加載圖片與圖片優(yōu)化
在 css 文件或者 sass 文件中添加如下代碼
$red: #900;
$size: 20px;
.box {
height: 30px*2;
font-size: $size;
transform: translate3d( 0, 0, 0 );
+ background: url('../static/1.jpeg')
}
運(yùn)行打包發(fā)現(xiàn)如下錯(cuò)誤:
ERROR in ./src/static/1.jpeg 1:0
Module parse failed: Unexpected character '?' (1:0)
You may need an appropriate loader to handle this file type.
解決方案:file-loader
處理文件的導(dǎo)入
npm install --save-dev file-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'
]
},
+ {
+ test: /\.(png|svg|jpg|gif)$/,
+ use: [
+ 'file-loader'
+ ]
+ }
]
}
};
此時(shí)運(yùn)行打包,發(fā)現(xiàn) dist 目錄多了一個(gè)圖片文件,另外報(bào)錯(cuò)不再出現(xiàn)。
那更進(jìn)一步,圖片如何進(jìn)行優(yōu)化呢?
image-webpack-loader
可以幫助我們對圖片進(jìn)行壓縮和優(yōu)化。
npm install image-webpack-loader --save-dev
使用: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'
]
},
{
test: /\.(png|svg|jpg|gif|jpeg|ico)$/,
use: [
'file-loader',
+ {
+ loader: 'image-webpack-loader',
+ options: {
+ mozjpeg: {
+ progressive: true,
+ quality: 65
+ },
+ optipng: {
+ enabled: false,
+ },
+ pngquant: {
+ quality: '65-90',
+ speed: 4
+ },
+ gifsicle: {
+ interlaced: false,
+ },
+ webp: {
+ quality: 75
+ }
+ }
+ },
]
}
]
}
};
此時(shí)在運(yùn)行 webpack,發(fā)現(xiàn)會 生成的圖片的大小會被壓縮很多。
更進(jìn)一步處理圖片成 base64
url-loader
功能類似于 file-loader,可以把 url 地址對應(yīng)的文件,打包成 base64 的 DataURL,提高訪問的效率。
如何使用:
npm install --save-dev url-loader
webpack.config.js
module.exports = {
module: {
rules: [
{
test: /\.(png|svg|jpg|gif|jpeg|ico|woff|woff2|eot|ttf|otf)$/,
use: [
{
loader: 'url-loader', // 根據(jù)圖片大小,把圖片優(yōu)化成base64
options: {
limit: 10000
}
},
{
loader: 'image-webpack-loader', // 先進(jìn)行圖片優(yōu)化
options: {
mozjpeg: {
progressive: true,
quality: 65
},
optipng: {
enabled: false
},
pngquant: {
quality: '65-90',
speed: 4
},
gifsicle: {
interlaced: false
},
webp: {
quality: 75
}
}
}
]
}
]
}
};
字體的處理(同圖片)
由于 css 中可能引用到自定義的字體,處理也是跟圖片一致。
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'
]
},
{
test: /\.(png|svg|jpg|gif)$/,
use: [
'file-loader'
]
},
+ {
+ test: /\.(woff|woff2|eot|ttf|otf)$/,
+ use: [
+ 'file-loader'
+ ]
+ }
]
}
};
開發(fā)相關(guān)輔助
js 使用 source map
當(dāng) webpack 打包源代碼時(shí),可能會很難追蹤到錯(cuò)誤和警告在源代碼中的原始位置。例如,如果將三個(gè)源文件(a.js, b.js 和 c.js)打包到一個(gè) bundle(bundle.js)中,而其中一個(gè)源文件包含一個(gè)錯(cuò)誤,那么堆棧跟蹤就會簡單地指向到 bundle.js。
使用 inline-source-map
選項(xiàng),這有助于解釋說明 js 原始出錯(cuò)的位置。(不要用于生產(chǎn)環(huán)境):
webpack.config.js
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const CleanWebpackPlugin = require('clean-webpack-plugin');
module.exports = {
entry: {
app: './src/index.js',
print: './src/print.js'
},
+ devtool: 'inline-source-map',
plugins: [
new CleanWebpackPlugin(['dist']),
new HtmlWebpackPlugin({
title: 'Development'
})
],
output: {
filename: '[name].bundle.js',
path: path.resolve(__dirname, 'dist')
}
};
監(jiān)控文件變化,自動編譯。使用觀察模式
每次修改完畢后,都手動編譯異常痛苦。最簡單解決的辦法就是啟動watch
。
npx webpack --watch
當(dāng)然可以添加到 npm 的 script 中
package.json
{
"name": "development",
"version": "1.0.0",
"description": "",
"main": "webpack.config.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
+ "watch": "npx webpack --watch",
"build": "npx webpack"
},
"devDependencies": {
"clean-webpack-plugin": "^0.1.16",
"css-loader": "^0.28.4",
"csv-loader": "^2.1.1",
"file-loader": "^0.11.2",
"html-webpack-plugin": "^2.29.0",
"style-loader": "^0.18.2",
"webpack": "^3.0.0",
"xml-loader": "^1.2.1"
}
}
但是有個(gè) bug,就是每次我們修改 js 或者 css 文件后,要看到修改后的 html 的變化,需要我自己重新刷新頁面。
如何能不刷新頁面,自動更新變化呢?
使用 webpack-dev-server 和熱更新
webpack-dev-server 為你提供了一個(gè)簡單的 web 服務(wù)器,并且能夠?qū)崟r(shí)重新加載(live reloading)。
安裝
npm install --save-dev webpack-dev-server
webpack.config.js
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const CleanWebpackPlugin = require('clean-webpack-plugin');
module.exports = {
entry: {
app: './src/index.js',
print: './src/print.js'
},
devtool: 'inline-source-map',
+ devServer: {
+ contentBase: './dist'
+ },
plugins: [
new CleanWebpackPlugin(['dist']),
new HtmlWebpackPlugin({
title: 'Development'
})
],
output: {
filename: '[name].bundle.js',
path: path.resolve(__dirname, 'dist')
}
};
啟動此 webserver:
webpack-dev-server --open
devServer: {
clientLogLevel: 'warning', // 可能的值有 none, error, warning 或者 info(默認(rèn)值)
hot: true, // 啟用 webpack 的模塊熱替換特性, 這個(gè)需要配合: webpack.HotModuleReplacementPlugin插件
contentBase: path.join(__dirname, "dist"), // 告訴服務(wù)器從哪里提供內(nèi)容, 默認(rèn)情況下,將使用當(dāng)前工作目錄作為提供內(nèi)容的目錄
compress: true, // 一切服務(wù)都啟用gzip 壓縮
host: '0.0.0.0', // 指定使用一個(gè) host。默認(rèn)是 localhost。如果你希望服務(wù)器外部可訪問 0.0.0.0
port: 8080, // 端口
open: true, // 是否打開瀏覽器
overlay: { // 出現(xiàn)錯(cuò)誤或者警告的時(shí)候,是否覆蓋頁面線上錯(cuò)誤消息。
warnings: true,
errors: true
},
publicPath: '/', // 此路徑下的打包文件可在瀏覽器中訪問。
proxy: { // 設(shè)置代理
"/api": { // 訪問api開頭的請求,會跳轉(zhuǎn)到 下面的target配置
target: "http://192.168.0.102:8080",
pathRewrite: {"^/api" : "/mockjsdata/5/api"}
}
},
quiet: true, // necessary for FriendlyErrorsPlugin. 啟用 quiet 后,除了初始啟動信息之外的任何內(nèi)容都不會被打印到控制臺。這也意味著來自 webpack 的錯(cuò)誤或警告在控制臺不可見。
watchOptions: { // 監(jiān)視文件相關(guān)的控制選項(xiàng)
poll: true, // webpack 使用文件系統(tǒng)(file system)獲取文件改動的通知。在某些情況下,不會正常工作。例如,當(dāng)使用 Network File System (NFS) 時(shí)。Vagrant 也有很多問題。在這些情況下,請使用輪詢. poll: true。當(dāng)然 poll也可以設(shè)置成毫秒數(shù),比如: poll: 1000
ignored: /node_modules/, // 忽略監(jiān)控的文件夾,正則
aggregateTimeout: 300 // 默認(rèn)值,當(dāng)?shù)谝粋€(gè)文件更改,會在重新構(gòu)建前增加延遲
}
}
如何啟用熱更新呢?
webpack.config.js
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const CleanWebpackPlugin = require('clean-webpack-plugin');
+ const webpack = require('webpack');
module.exports = {
entry: {
app: './src/index.js'
},
devtool: 'inline-source-map',
devServer: {
contentBase: './dist',
+ hot: true
},
plugins: [
new CleanWebpackPlugin(['dist']),
new HtmlWebpackPlugin({
title: 'Hot Module Replacement'
}),
+ new webpack.NamedModulesPlugin(), // 更容易查看(patch)的依賴
+ new webpack.HotModuleReplacementPlugin() // 替換插件
],
output: {
filename: '[name].bundle.js',
path: path.resolve(__dirname, 'dist')
}
};
JS啟用babel轉(zhuǎn)碼
雖然現(xiàn)代的瀏覽器已經(jīng)兼容了96%以上的ES6的語法了,但是為了兼容老式的瀏覽器(IE8、9)我們需要把最新的ES6的語法轉(zhuǎn)成ES5的。那么babel
的loader就出場了。
安裝
npm i -D babel-loader babel-core babel-preset-env
用法
在webpack的配置文件中,添加js的處理模塊。
module: {
rules: [
{
test: /\.js$/,
exclude: /(node_modules)/, // 加快編譯速度,不包含node_modules文件夾內(nèi)容
use: {
loader: 'babel-loader'
}
}
]
}
然后,在項(xiàng)目根目錄下,添加babel的配置文件 .babelrc
.
.babelrc
文件如下:
{
"presets": ["env"]
}
最后,在入口js文件中,添加ES6的?新語法:
class Temp {
show() {
console.log('this.Age :', this.Age);
}
get Age() {
return this._age;
}
set Age(val) {
this._age = val + 1;
}
}
let t = new Temp();
t.Age = 19;
t.show();
最后打包:
npx webpack
最終打包后的js代碼:
var a = 1,
b = 3,
c = 9;
console.log('a :', a);
console.log('b :', b);
console.log('c :', c);
var Temp = function () {
function Temp() {
_classCallCheck(this, Temp);
}
_createClass(Temp, [{
key: 'show',
value: function show() {
console.log('this.Age :', this.Age);
}
}, {
key: 'Age',
get: function get() {
return this._age;
},
set: function set(val) {
this._age = val + 1;
}
}]);
return Temp;
}();
var t = new Temp();
t.Age = 19;
t.show();
Babel優(yōu)化
babel-loader可以配置如下幾個(gè)options:
cacheDirectory
:默認(rèn)值為 false。當(dāng)有設(shè)置時(shí),指定的目錄將用來緩存 loader 的執(zhí)行結(jié)果。之后的 webpack 構(gòu)建,將會嘗試讀取緩存,來避免在每次執(zhí)行時(shí),可能產(chǎn)生的、高性能消耗的 Babel 重新編譯過程(recompilation process)。如果設(shè)置了一個(gè)空值 (loader: 'babel-loader?cacheDirectory') 或者 true (loader: babel-loader?cacheDirectory=true),loader 將使用默認(rèn)的緩存目錄 node_modules/.cache/babel-loader,如果在任何根目錄下都沒有找到 node_modules 目錄,將會降級回退到操作系統(tǒng)默認(rèn)的臨時(shí)文件目錄。cacheIdentifier
:默認(rèn)是一個(gè)由 babel-core 版本號,babel-loader 版本號,.babelrc 文件內(nèi)容(存在的情況下),環(huán)境變量 BABEL_ENV 的值(沒有時(shí)降級到 NODE_ENV)組成的字符串。可以設(shè)置為一個(gè)自定義的值,在 identifier 改變后,強(qiáng)制緩存失效。forceEnv
:默認(rèn)將解析 BABEL_ENV 然后是 NODE_ENV。允許你在 loader 級別上覆蓋 BABEL_ENV/NODE_ENV。對有不同 babel 配置的,客戶端和服務(wù)端同構(gòu)應(yīng)用非常有用。
注意:sourceMap 選項(xiàng)是被忽略的。當(dāng) webpack 配置了 sourceMap 時(shí)(通過 devtool 配置選項(xiàng)),將會自動生成 sourceMap。
babel 在每個(gè)文件都插入了輔助代碼,使代碼體積過大.babel 對一些公共方法使用了非常小的輔助代碼,比如 _extend。 默認(rèn)情況下會被添加到每一個(gè)需要它的文件中。你可以引入 babel runtime
作為一個(gè)獨(dú)立模塊,來避免重復(fù)引入。
安裝:
npm install babel-plugin-transform-runtime --save-dev
npm install babel-runtime --save
配置:
webpack.config.js
rules: [
// 'transform-runtime' 插件告訴 babel 要引用 runtime 來代替注入。
{
test: /\.js$/,
exclude: /(node_modules|bower_components)/,
use: {
loader: 'babel-loader',
}
}
]
修改.babelrc
{
"presets": ["env"],
"plugins": [
["transform-runtime", {
"helpers": true,
"polyfill": true,
"regenerator": true,
"moduleName": "babel-runtime"
}]
]
}
此時(shí),webpack打包的時(shí)候,會自動優(yōu)化重復(fù)引入公共方法的問題。
ESLint校驗(yàn)代碼格式規(guī)范
安裝
npm install eslint --save-dev
npm install eslint-loader --save-dev
# 以下是用到的額外的需要安裝的eslint的解釋器、校驗(yàn)規(guī)則等
npm i -D babel-eslint standard
使用
// webpack.config.js
module.exports = {
// ...
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
loader: "eslint-loader",
options: {
// eslint options (if necessary)
fix: true
}
},
],
},
// ...
}
eslint配置可以直接放到webpack的配置文件中,也可以直接放到項(xiàng)目根目錄的 .eslintrc
中文檔。
// .eslintrc.js
// https://eslint.org/docs/user-guide/configuring
module.exports = {
root: true,
parserOptions: {
parser: 'babel-eslint'
},
env: {
browser: true
},
extends: [
// https://github.com/standard/standard/blob/master/docs/RULES-en.md
'standard'
],
globals: {
NODE_ENV: false
},
rules: {
// allow async-await
'generator-star-spacing': 'off',
// allow debugger during development
'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off',
// 添加,分號必須
semi: ['error', 'always'],
'no-unexpected-multiline': 'off',
'space-before-function-paren': ['error', 'never'],
// 'quotes': ["error", "double", { "avoidEscape": true }]
quotes: [
'error',
'single',
{
avoidEscape: true
}
]
}
};
此時(shí)eslint的配置就結(jié)束了。
到此為止,一個(gè)完整的開發(fā)階段的webpack的配置文件
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const CleanWebpackPlugin = require('clean-webpack-plugin');
const autoprefixer = require('autoprefixer');
const webpack = require('webpack');
module.exports = {
mode: 'development',
entry: './src/index.js',
output: {
filename: 'main.js',
path: path.resolve(__dirname, './dist')
},
devtool: 'inline-source-map',
devServer: {
clientLogLevel: 'warning', // 可能的值有 none, error, warning 或者 info(默認(rèn)值)
hot: true, // 啟用 webpack 的模塊熱替換特性, 這個(gè)需要配合: webpack.HotModuleReplacementPlugin插件
contentBase: path.join(__dirname, "dist"), // 告訴服務(wù)器從哪里提供內(nèi)容, 默認(rèn)情況下,將使用當(dāng)前工作目錄作為提供內(nèi)容的目錄
compress: true, // 一切服務(wù)都啟用gzip 壓縮
host: '0.0.0.0', // 指定使用一個(gè) host。默認(rèn)是 localhost。如果你希望服務(wù)器外部可訪問 0.0.0.0
port: 8085, // 端口
open: true, // 是否打開瀏覽器
overlay: { // 出現(xiàn)錯(cuò)誤或者警告的時(shí)候,是否覆蓋頁面線上錯(cuò)誤消息。
warnings: true,
errors: true
},
publicPath: '/', // 此路徑下的打包文件可在瀏覽器中訪問。
proxy: { // 設(shè)置代理
"/api": { // 訪問api開頭的請求,會跳轉(zhuǎn)到 下面的target配置
target: "http://192.168.0.102:8080",
pathRewrite: {
"^/api": "/mockjsdata/5/api"
}
}
},
quiet: true, // necessary for FriendlyErrorsPlugin. 啟用 quiet 后,除了初始啟動信息之外的任何內(nèi)容都不會被打印到控制臺。這也意味著來自 webpack 的錯(cuò)誤或警告在控制臺不可見。
watchOptions: { // 監(jiān)視文件相關(guān)的控制選項(xiàng)
poll: true, // webpack 使用文件系統(tǒng)(file system)獲取文件改動的通知。在某些情況下,不會正常工作。例如,當(dāng)使用 Network File System (NFS) 時(shí)。Vagrant 也有很多問題。在這些情況下,請使用輪詢. poll: true。當(dāng)然 poll也可以設(shè)置成毫秒數(shù),比如: poll: 1000
ignored: /node_modules/, // 忽略監(jiān)控的文件夾,正則
aggregateTimeout: 300 // 默認(rèn)值,當(dāng)?shù)谝粋€(gè)文件更改,會在重新構(gòu)建前增加延遲
}
},
module: {
rules: [
{
test: /\.js$/,
exclude: /(node_modules)/, // 加快編譯速度,不包含node_modules文件夾內(nèi)容
use: [{
loader: 'babel-loader'
},{
loader: 'eslint-loader',
options: {
fix: true
}
}]
},
{
test: /\.(sa|sc|c)ss$/,
use: [
'style-loader', {
loader: 'css-loader',
options: {
sourceMap: true
}
}, {
loader: 'postcss-loader',
options: {
ident: 'postcss',
sourceMap: true,
plugins: (loader) => [autoprefixer({browsers: ['> 0.15% in CN']})]
}
}, {
loader: 'sass-loader',
options: {
sourceMap: true
}
}
]
}, {
test: /\.(woff|woff2|eot|ttf|otf)$/,
use: [
{
loader: 'url-loader',
options: {
limit: 10000
}
}
]
}, {
test: /\.(png|svg|jpg|gif|jpeg|ico)$/,
use: [
{
loader: 'url-loader',
options: {
limit: 10000
}
}, {
loader: 'image-webpack-loader',
options: {
mozjpeg: {
progressive: true,
quality: 65
},
optipng: {
enabled: false
},
pngquant: {
quality: '65-90',
speed: 4
},
gifsicle: {
interlaced: false
},
webp: {
quality: 75
}
}
}
]
}
]
},
plugins: [
new MiniCssExtractPlugin({filename: '[name].css', chunkFilename: '[id].css'}),
new CleanWebpackPlugin(['dist']),
new webpack.NamedModulesPlugin(), // 更容易查看(patch)的依賴
new webpack.HotModuleReplacementPlugin(), // 替換插件
new HtmlWebpackPlugin({
title: 'AICODER 全棧線下實(shí)習(xí)', // 默認(rèn)值:Webpack App
filename: 'index.html', // 默認(rèn)值: 'index.html'
minify: {
collapseWhitespace: true,
removeComments: true,
removeAttributeQuotes: true, // 移除屬性的引號
},
template: path.resolve(__dirname, 'src/index.html')
})
],
optimization: {}
};
用于生產(chǎn)環(huán)境的配置
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const UglifyJsPlugin = require("uglifyjs-webpack-plugin");
const OptimizeCSSAssetsPlugin = require("optimize-css-assets-webpack-plugin");
const CleanWebpackPlugin = require('clean-webpack-plugin');
const autoprefixer = require('autoprefixer');
module.exports = {
mode: 'production',
entry: './src/index.js',
output: {
filename: 'main.[hash].js',
path: path.resolve(__dirname, './dist')
},
module: {
rules: [
{
test: /\.js$/,
exclude: /(node_modules)/, // 加快編譯速度,不包含node_modules文件夾內(nèi)容
use: [{
loader: 'babel-loader'
},{
loader: 'eslint-loader',
options: {
fix: true
}
}]
},
{
test: /\.(sa|sc|c)ss$/,
use: [
MiniCssExtractPlugin.loader, {
loader: 'css-loader'
}, {
loader: 'postcss-loader',
options: {
ident: 'postcss',
plugins: (loader) => [autoprefixer({browsers: ['> 0.15% in CN']})]
}
}, {
loader: 'sass-loader'
}
]
}, {
test: /\.(woff|woff2|eot|ttf|otf)$/,
use: [
{
loader: 'url-loader',
options: {
limit: 10000
}
}
]
}, {
test: /\.(png|svg|jpg|gif|jpeg|ico)$/,
use: [
'file-loader', {
loader: 'image-webpack-loader',
options: {
mozjpeg: {
progressive: true,
quality: 65
},
optipng: {
enabled: false
},
pngquant: {
quality: '65-90',
speed: 4
},
gifsicle: {
interlaced: false
},
webp: {
quality: 75
}
}
}
]
}
]
},
plugins: [
new MiniCssExtractPlugin({filename: '[name][hash].css', chunkFilename: '[id][hash].css'}),
new CleanWebpackPlugin(['dist']),
new HtmlWebpackPlugin({
title: 'AICODER 全棧線下實(shí)習(xí)', // 默認(rèn)值:Webpack App
filename: 'index.html', // 默認(rèn)值: 'index.html'
template: path.resolve(__dirname, 'src/index.html'),
minify: {
collapseWhitespace: true,
removeComments: true,
removeAttributeQuotes: true, // 移除屬性的引號
}
})
],
optimization: {
minimizer: [
new UglifyJsPlugin({
cache: true, parallel: true, sourceMap: true // set to true if you want JS source maps
}),
new OptimizeCSSAssetsPlugin({})
]
}
};
相關(guān)的loader列表
webpack
可以使用 loader 來預(yù)處理文件。這允許你打包除 JavaScript 之外的任何靜態(tài)資源。你可以使用 Node.js 來很簡單地編寫自己的 loader。
文件
-
raw-loader
加載文件原始內(nèi)容(utf-8) -
val-loader
將代碼作為模塊執(zhí)行,并將 exports 轉(zhuǎn)為 JS 代碼 -
url-loader
像 file loader 一樣工作,但如果文件小于限制,可以返回 data URL -
file-loader
將文件發(fā)送到輸出文件夾,并返回(相對)URL
JSON
-
json-loader
加載 JSON 文件(默認(rèn)包含) -
json5-loader
加載和轉(zhuǎn)譯 JSON 5 文件 -
cson-loader
加載和轉(zhuǎn)譯 CSON 文件
轉(zhuǎn)換編譯(Transpiling)
-
script-loader
在全局上下文中執(zhí)行一次 JavaScript 文件(如在 script 標(biāo)簽),不需要解析 -
babel-loader
加載 ES2015+ 代碼,然后使用 Babel 轉(zhuǎn)譯為 ES5 -
buble-loader
使用 Bublé 加載 ES2015+ 代碼,并且將代碼轉(zhuǎn)譯為 ES5 -
traceur-loader
加載 ES2015+ 代碼,然后使用 Traceur 轉(zhuǎn)譯為 ES5 -
ts-loader
或awesome-typescript-loader
像 JavaScript 一樣加載 TypeScript 2.0+ -
coffee-loader
像 JavaScript 一樣加載 CoffeeScript
模板(Templating)
-
html-loader
導(dǎo)出 HTML 為字符串,需要引用靜態(tài)資源 -
pug-loader
加載 Pug 模板并返回一個(gè)函數(shù) -
jade-loader
加載 Jade 模板并返回一個(gè)函數(shù) -
markdown-loader
將 Markdown 轉(zhuǎn)譯為 HTML -
react-markdown-loader
使用 markdown-parse parser(解析器) 將 Markdown 編譯為 React 組件 -
posthtml-loader
使用 PostHTML 加載并轉(zhuǎn)換 HTML 文件 -
handlebars-loader
將 Handlebars 轉(zhuǎn)移為 HTML -
markup-inline-loader
將內(nèi)聯(lián)的 SVG/MathML 文件轉(zhuǎn)換為 HTML。在應(yīng)用于圖標(biāo)字體,或?qū)?CSS 動畫應(yīng)用于 SVG 時(shí)非常有用。
樣式
-
style-loader
將模塊的導(dǎo)出作為樣式添加到 DOM 中 -
css-loader
解析 CSS 文件后,使用 import 加載,并且返回 CSS 代碼 -
less-loader
加載和轉(zhuǎn)譯 LESS 文件 -
sass-loader
加載和轉(zhuǎn)譯 SASS/SCSS 文件 -
postcss-loader
使用 PostCSS 加載和轉(zhuǎn)譯 CSS/SSS 文件 -
stylus-loader
加載和轉(zhuǎn)譯 Stylus 文件
清理和測試(Linting && Testing)
-
mocha-loader
使用 mocha 測試(瀏覽器/NodeJS) -
eslint-loader
PreLoader,使用 ESLint 清理代碼 -
jshint-loader
PreLoader,使用 JSHint 清理代碼 -
jscs-loader
PreLoader,使用 JSCS 檢查代碼樣式 -
coverjs-loader
PreLoader,使用 CoverJS 確定測試覆蓋率
框架(Frameworks)
-
vue-loader
加載和轉(zhuǎn)譯 Vue 組件 -
polymer-loader
使用選擇預(yù)處理器(preprocessor)處理,并且require()
類似一等模塊(first-class)的 Web 組件 -
angular2-template-loader
加載和轉(zhuǎn)譯 Angular 組件 - Awesome 更多第三方 loader,查看 awesome-webpack 列表。
other
webpack還是有很多其他需要學(xué)習(xí)的內(nèi)容。 請參考官網(wǎng),或者研究一下vue-cli
的生成的webpack的相關(guān)配置,也很值得學(xué)習(xí)。
另外其他腳手架生成的相關(guān)配置都可以研究一下比如:create-react-app
、yo
等