一耸棒、項(xiàng)目初始化及 webpack 安裝
mkdir webpack-demo && cd webpack-demo
npm init -y
npm install webpack webpack-cli --save-dev
初始化完成之后項(xiàng)目會(huì)有一個(gè) package.json
和一個(gè) node_modules
文件夾
二荒澡、基本使用
在根目錄下新建一個(gè) index.html
和 src
目錄,在 src
目錄下新建一個(gè) index.js
+ src
+ index.js
+ index.html
在 index.js
文件中寫入 js 代碼
const h = document.createElement('h1')
h.innerHTML = 'webpack'
document.body.appendChild(h)
然后執(zhí)行 webpack 打包
npx webpack
這時(shí)根目錄會(huì)生成一個(gè) dist
目錄
+ dist
+ main.js
把打包生成的 main.js
在 index.html
中引用
<script src="./dist/main.js"></script>
可以看到与殃,瀏覽器頁(yè)面上能夠正常顯示 index.js
執(zhí)行的結(jié)果
這是 webpack 的默認(rèn)配置单山,入口文件為
src/index.js
,輸出文件目錄為 dist/main.js
幅疼。webpack 也支持我們自定義配置米奸。
三、自定義配置
把剛剛打包生成的 dist
目錄刪掉爽篷,在根目錄下新建一個(gè) webpack.config.js
- dist
- main.js
+ webpack.config.js
首先修改入口文件和輸出目錄及文件名
const path = require('path')
module.exports = {
entry: './path/main.js',
output: {
// node.js拼接絕對(duì)路徑
path: path.resolve(__dirname, 'build'),
filename: 'bundle.js'
}
}
在根目錄下新增一個(gè) path/main.js
作為修改配置后的入口文件
+ path
+ main.js
重新寫入 js 代碼
import '../src'
const a = document.createElement('a')
a.innerHTML = '修改了入口文件和打包輸入目錄'
a.setAttribute('href', 'javascript:;')
document.body.appendChild(a)
此時(shí)再次執(zhí)行 npx webpack
打包悴晰,可以看到根目錄輸出的目錄不再是dist
,而是我們剛剛配置的 build
目錄
+ build
+ bundle.js
把 index.html
中 引入的 js 文件修改為 build/bundle.js
<script src="./build/bundle.js"></script>
此時(shí)再查看瀏覽器執(zhí)行結(jié)果逐工,已經(jīng)變成了 path/main.js
中的執(zhí)行結(jié)果
四铡溪、生產(chǎn)開(kāi)發(fā)多個(gè)配置文件
webpack 支持一個(gè)項(xiàng)目有多個(gè)配置文件,只不過(guò)默認(rèn)執(zhí)行的是 webpack.config.js
文件泪喊,但是我們也可以指定執(zhí)行其他的配置文件棕硫。
在根目錄下新建一個(gè) webpack.dev.config.js
,并刪掉剛剛打包生成的build
目錄
- build
- bundle.js
+ webpack.dev.config.js
在 webpack.dev.confi.js
寫入我們的另一套配置(后面我們的操作介紹基本都以這個(gè)文件的配置為例子)
const path = require('path')
module.exports = {
entry: './path/main.js',
output: {
path: path.resolve(__dirname, 'dev'),
filename: 'bundle.js'
}
}
此時(shí)再執(zhí)行 webpack 打包袒啼,需要我們手動(dòng)去指定執(zhí)行的配置文件
npx webpack --config webpack.dev.config.js
可以看到根目錄下輸出了一個(gè) dev
目錄
+ dev
+ bundle.js
把 dev/bundle.js
引入到 index.html
也可以看到 path/main.js
的正常執(zhí)行結(jié)果
六哈扮、配置 npm 腳本
如果每次執(zhí)行自定義的配置文件都要去手動(dòng)指定版本纬纪,這樣比較麻煩,我們可以在 package.json
文件中滑肉,添加一個(gè) npm script
腳本育八,就可以通過(guò) npm run
來(lái)執(zhí)行相應(yīng)的命令了。
"scripts": {
"dev": "webpack --config webpack.dev.config.js",
}
此時(shí)只需要執(zhí)行 npm 命令赦邻,就可以看到效果和原來(lái)的一致
npm run dev
但是髓棋,直到目前為止,我們都可以看到 webpack 一直給我們報(bào)了一個(gè) warning
這是因?yàn)閣ebpack需要我們提供一個(gè)
mode
配置選項(xiàng)來(lái)告知 webpack 使用相應(yīng)模式的內(nèi)置優(yōu)化惶洲。mode選項(xiàng)有兩個(gè)值development
和production
按声,顧名思義就是開(kāi)發(fā)模式和生產(chǎn)模式,它會(huì)將 process.env.NODE_ENV
設(shè)為相應(yīng)的值現(xiàn)在恬吕,我們?cè)?
webpack.dev.config.js
中加上 mode
配置
const path = require('path')
module.exports = {
mode: 'development'
}
然后再執(zhí)行 npm run dev
會(huì)發(fā)現(xiàn)签则,warning 已經(jīng)消失了
☆
mode
設(shè)為 production
打包后的 js 文件會(huì)被壓縮,一般在打上線版本的時(shí)候才會(huì)使用铐料,所以一般我們會(huì)有兩套 webpack 配置渐裂,一套針對(duì)開(kāi)發(fā)環(huán)境,一套針對(duì)生產(chǎn)環(huán)境
七钠惩、配置樣式 loader
在 webpack 中柒凉,任何文件模塊都是在 js 文件中導(dǎo)入的,否則就失去了使用 webpack 的意義篓跛,下面我們可以來(lái)看一下膝捞,傳統(tǒng)的樣式文件引入和使用了 webpack 之后有什么區(qū)別。
首先愧沟,在根目錄下新建一個(gè) style
目錄
+ style
+ index.css
在 index.css
中寫入樣式
h1 {
color: red;
}
然后在 path/main.js
中引入蔬咬,然后執(zhí)行打包,當(dāng)然這時(shí)候是不能打包成功的沐寺,webpack會(huì)報(bào)錯(cuò)提示你需要 loader
來(lái)解析 css 文件
所以我們需要先安裝解析 css 的 loader
npm install style-loader css-loader -D
☆ 因?yàn)?loader 只是在開(kāi)發(fā)時(shí)需要用到林艘,在打包后生成生產(chǎn)項(xiàng)目的時(shí)候已經(jīng)不需要用到了,所以只要 -D
就好混坞,而不需要 -S
安裝完成后狐援,需要在 webpack 的配置文件中進(jìn)行配置,這里我們就使用 webpack.dev.config.js
來(lái)配置
module.exports = {
module: {
rules: [
{
test: /\.css$/,
use: [
'style-loader',
'css-loader'
]
}
]
}
}
此時(shí)再 npm run dev
執(zhí)行打包拔第,可以看到咕村,webpack 已經(jīng)不報(bào)錯(cuò)了场钉,而且瀏覽器上也可以看到樣式生效了
那使用 webpack 和直接在 HTML 文件中引入 css 有什么區(qū)別呢蚊俺?
打開(kāi)瀏覽器的開(kāi)發(fā)者工具,可以看到
network
里面逛万,在使用了 webpack 之后泳猬,只請(qǐng)求了一個(gè) bundle.js 文件而直接在HTML文件中引入批钠,瀏覽器會(huì)單獨(dú)再去請(qǐng)求css文件,也就是說(shuō)得封,你引入幾個(gè)文件埋心,瀏覽器就會(huì)有幾個(gè)請(qǐng)求
八、配置 css 預(yù)處理器 loader
樣式處理 css忙上,還有常用的預(yù)處理器 less 和 sass拷呆,在 webpack 中要想使用這兩個(gè)預(yù)處理器,也需要配置相應(yīng)的 loader
首先分別安裝 less 和 sass 的loader
npm install less less-loader -D
npm install sass-loader node-sass -D
然后疫粥,分別在 webpack.dev.config.js
中進(jìn)行配置
module.exports = {
module: {
rules: [
{
test: /\.less$/,
use: [
'style-loader',
'css-loader',
'less-loader',
]
}, {
test: /\.s(a|c)ss$/,
use: [
'style-loader',
'css-loader',
'sass-loader'
]
}
]
}
}
分別在 style
目錄下新增一個(gè) index.less
茬斧、index.scss
、index.sass
+ style
+ index.css
+ index.less
+ index.scss
+ index.sass
然后分別寫入樣式打包驗(yàn)證一下
// index.less
h1 {
font-size: 50px;
}
// index.scss
a {
color: yellowgreen;
}
// index.sass
a
font-size: 20px
然后在 path/main.js
中引入梗逮,并執(zhí)行 npm run dev
打包
// path/main.js
import '../style/index.less'
import '../style/index.scss'
import '../style/index.sass'
可以看到项秉,樣式都已經(jīng)生效了
九、配置文件loader(字體圖標(biāo)和圖片文件)
安裝 file-loader
npm install file-loader -D
在 webpack.dev.config.js
配置
module.exports = {
module: {
rules: [
{
test: /\.(woff|woff2|eot|ttf|otf)$/,
use: 'file-loader'
}, {
test: /\.(png|svg|jpg|gif)$/,
use: 'file-loader'
}
]
}
}
在 style
目錄下新建一個(gè) iconfont.css
文件 (樣式可以在阿里圖庫(kù)下一份)
style
+ iconfont.css
然后在 path/main.js
中引入使用慷彤,并執(zhí)行 npm run dev
打包驗(yàn)證
// path/main.js
import '../style/iconfont.css'
const i = document.createElement('i')
i.classList.add('iconfont', 'icon-smile')
document.body.appendChild(i)
可以看到娄蔼,字體圖標(biāo)已經(jīng)可以在瀏覽器正常顯示
在
src
目錄下新增一個(gè) img
目錄,放入一張圖片
src
+ img
+ logo.svg
在 src/index.js
和 style/index.less
中分別寫入代碼
// src/index.js
import icon from './img/logo.svg'
const img = new Image()
img.src = icon
document.body.appendChild(img)
// style/index.less
h1 {
font-size: 50px;
background-image: url('../src/img/logo.svg');
}
img {
width: 200px;
}
執(zhí)行 npm run dev
打包后發(fā)現(xiàn) dev
目錄里生成了一個(gè) hash
命名的圖片文件底哗,但是瀏覽器上圖片卻無(wú)法正常顯示
這是因?yàn)榇虬蟮膱D片引用路徑岁诉,是以
index.html
所在的位置為根目錄的,但是實(shí)際上打包生成的圖片文件與 index.html
不在同一個(gè)目錄下跋选,導(dǎo)致引用路徑錯(cuò)誤唉侄,這意味著我們需要手動(dòng)把 index.html
移動(dòng)到打包后生成的 dev
目錄下,圖片才能正常顯示野建,這是一件非常麻煩的事情属划。正常來(lái)說(shuō),我們希望打包后的輸出目錄應(yīng)該是一個(gè)完整的可以上線使用的項(xiàng)目候生,而不是每次都要手動(dòng)去修改同眯。這里介紹一個(gè)插件
html-webpack-plugin
,它能在打包后自動(dòng)生成一個(gè) index.html
文件輸入到打包目錄唯鸭,并自動(dòng)幫我們引入 bundle.js
须蜗,下面來(lái)看一下怎么使用首先,安裝這個(gè)插件
npm install html-webpack-plugin -D
接著回到 webpack.dev.config.js
中配置
const HTMLWebpackPlugin = require('html-webpack-plugin')
module.exports = {
plugins: [
new HTMLWebpackPlugin({
// 輸出的文件名目溉,默認(rèn)就是 index.html
filename: 'index.html',
// 以指定的index.html為模板
template: './index.html'
})
]
}
此時(shí)再執(zhí)行 npm run dev
能夠看到明肮,在 dev
目錄下已經(jīng)生成了一個(gè) index.html
文件,并且會(huì)自動(dòng)引入 dev
下的 bundle.js
文件缭付,我們已經(jīng)不需要在原來(lái)的 index.html
手動(dòng)去引入了柿估。
在瀏覽器上打開(kāi) dev
下的 index.html
,可以看到圖片已經(jīng)能夠正常顯示了陷猫,并且還可以在樣式文件中作為背景圖片使用秫舌。
這個(gè)插件是不是很好用的妖?后面還會(huì)繼續(xù)用到,它的強(qiáng)大之處不只如此足陨。
十嫂粟、配置 url-loader
圖片除了上面介紹的 file-loader
之外,還有一個(gè)更為強(qiáng)大的 url-loader
墨缘,同樣的星虹,先安裝
npm install url-loader -D
然后回到 webpack.dev.config.js
中配置
module.exports = {
module: {
rules: [
{
test: /\.(png|svg|jpg|gif)$/,
use: {
loader: 'url-loader',
options: {
limit: 5 * 1024, // 小于5kb轉(zhuǎn)base64
outputPath: 'media/images', // 自定義打包后的圖片輸出路徑
name: '[name]-[hash:4].[ext]' // 自定義打包后的圖片名字,hash:n,n為hash長(zhǎng)度
}
}
}
]
}
}
使用這個(gè) loader 我們可以自定義圖片打包后的輸入路徑以及文件名,并且它默認(rèn)會(huì)將圖片轉(zhuǎn)為 base64
镊讼,但是我們也可以通過(guò) limit
屬性限制圖片小于某個(gè)特定值再轉(zhuǎn)為 base64搁凸,如果大于這個(gè)特定值,就沿用普通的 url狠毯。
☆ 如果是采用轉(zhuǎn)成 base64的方式护糖,則打包后不會(huì)有圖片目錄及文件輸出
執(zhí)行 npm run dev
驗(yàn)證一下,可以看到嚼松,dev
目錄下已經(jīng)生成了一個(gè) media
目錄嫡良,并且瀏覽器也還是可以正常顯示的
dev
+ media
+ images
+ logo-cd0b.svg
十一、使用自動(dòng)編譯的開(kāi)發(fā)工具
webpack 提供了幾個(gè)開(kāi)發(fā)工具献酗,可以幫助我們?cè)诖a發(fā)生變化后自動(dòng)編譯寝受,而不需要我們每次都手動(dòng)去執(zhí)行 打包命令。
觀察模式:watch
watch 相當(dāng)于是在代碼發(fā)生改變后自動(dòng)幫我們執(zhí)行打包命令罕偎,并輸出打包后的目錄及文件很澄。它有兩種啟動(dòng)方式,一種是直接通過(guò)命令
npx webpack --watch --config webpack.dev.config.js
我們同樣可以在 package.json
中把它配置成 npm script
腳本
"scripts": {
"watch": "webpack --watch --config webpack.dev.config.js",
}
這樣我們就可以直接通過(guò)執(zhí)行 npm run watch
來(lái)進(jìn)入觀察模式颜及。
另一種是直接在 webpack.dev.config.js
中配置
module.exports = {
watch: true
}
這樣其實(shí)我們只需要執(zhí)行 npm run dev
也能得到同樣的效果
webpack-dev-server
webpack-dev-server
其實(shí)就是提供了一個(gè)簡(jiǎn)單的 web 服務(wù)器甩苛,并且能夠?qū)崟r(shí)重新加載。這也是 webpack 最推薦的一種方式俏站。
首先需要先安裝 webpack-dev-server
npm install webpack-dev-server -D
同樣的讯蒲,它也可以通過(guò)命令和配置 webpack.dev.config.js
的方式來(lái)啟動(dòng)
npx webpack-dev-server
把它配置成 npm script
腳本
"scripts": {
"server": "webpack-dev-server --config webpack.dev.config.js"
}
執(zhí)行 npm run server
,可以看到 webpack-dev-server
為我們開(kāi)了一個(gè)端口為8080
的服務(wù)
在瀏覽器打開(kāi)本機(jī)
8080
端口服務(wù)肄扎,可以看到頁(yè)面正常顯示它與
watch
模式不同的是墨林,你可以先暫且簡(jiǎn)單地理解為它幫我們開(kāi)啟了一個(gè)服務(wù)器,并且把自動(dòng)打包后的文件映射到服務(wù)器的內(nèi)存根目錄上犯祠,而不是輸出到本地項(xiàng)目的根目錄旭等。這里其實(shí)也是借助了我們前面使用到的 html-webpack-plugin
插件。webpack-dev-server
還為我們提供了其他的配置項(xiàng)衡载,下面我們通過(guò)在 webpack.dev.config.js
里面配置看一下究竟
module.exports = {
devServer: {
// 自動(dòng)打開(kāi)瀏覽器
open: true,
// 熱更新
hot: true,
// 文件壓縮
compress: true,
// 自定義端口
port: 3000,
// 自定義映射的根目錄
contentBase: './src'
}
}
配置成 npm script
腳本
"scripts": {
"server": "webpack-dev-server --hot --compress --port 3000 --open --contentBase src --config webpack.dev.config.js"
}
webpack-dev-middleware
webpack-dev-middleware 相當(dāng)于一個(gè)內(nèi)存型的文件系統(tǒng)搔耕,它會(huì)把 webpack 處理后的文件輸出到服務(wù)器的內(nèi)存根目錄上。webpack 會(huì)根據(jù)我們的配置文件自動(dòng)梳理出 entry 和 output 模塊的關(guān)系脈絡(luò)月劈,然后 webpack-dev-middle 就在這個(gè)基礎(chǔ)上形成一個(gè)文件映射系統(tǒng)度迂,如果匹配到程序請(qǐng)求的文件,就會(huì)把內(nèi)存中緩存的對(duì)應(yīng)結(jié)果以文件的格式返回猜揪,否則就進(jìn)入下一個(gè)中間件惭墓。webpack-dev-server 的實(shí)現(xiàn)其實(shí)也是在內(nèi)部使用了它。
webpack-dev-server 實(shí)際上就相當(dāng)于啟用了一個(gè) express 的 http 服務(wù)器并調(diào)用 webpack-dev-middleware而姐。這個(gè) http 服務(wù)器和 client 使用了 websocket 通訊協(xié)議腊凶,當(dāng)源文件發(fā)生改變時(shí),webpack-dev-server 就使用 webpack 進(jìn)行實(shí)時(shí)編譯拴念,然后再用 webpack-dev-middleware 將 webpack 編譯后文件會(huì)輸出到內(nèi)存中钧萍。
總的來(lái)說(shuō),webpack-dev-server 是封裝好的政鼠,比較適合純前端項(xiàng)目风瘦;而 webpack-dev-middle 只是一個(gè)中間件,我們可以定制自己的后端服務(wù)進(jìn)行整合公般。所以對(duì)于我們前端人員來(lái)說(shuō)万搔,大多數(shù)場(chǎng)景下基本使用的都是 webpack-dev-server。
下面我們來(lái)看一下 webpack-dev-middle 具體怎么使用
首先官帘,安裝 express
和 webpack-dev-middleware
npm install --save-dev express webpack-dev-middleware
在 webpack.dev.config.js
中添加一個(gè) publicPath
module.exports = {
output: {
publicPath: '/'
}
}
在根目錄下新建一個(gè) server.js
const express = require('express');
const webpack = require('webpack');
const webpackDevMiddleware = require('webpack-dev-middleware');
const app = express();
const config = require('./webpack.dev.config.js');
const compiler = webpack(config);
app.use(webpackDevMiddleware(compiler, {
publicPath: config.output.publicPath
}));
app.listen(3000, function () {
console.log('Example app listening on port 3000!\n');
});
使用 node 執(zhí)行 server.js
node server.js
此時(shí)瞬雹,打開(kāi)瀏覽器對(duì)應(yīng)的端口,應(yīng)該可以看到程序已經(jīng)運(yùn)行了刽虹。
☆ 同樣的酗捌,使用 webpack-dev-middleware
必須使用 html-webpack-plugin
,否則 HTML 文件無(wú)法正確輸出到 express
服務(wù)器的根目錄涌哲。
十二胖缤、JS 高級(jí)語(yǔ)法兼容
盡管現(xiàn)在新版本的瀏覽器已經(jīng)能夠識(shí)別大部分的 es6
語(yǔ)法,但是大部分時(shí)候我們還是要考慮一些低版本瀏覽器的兼容性阀圾,況且 js 的語(yǔ)法也一直在更新草姻。所以為了讓我們能夠在項(xiàng)目中使用更多更高級(jí)的 js 語(yǔ)法,我們就需要有一個(gè)轉(zhuǎn)換器來(lái)把高版本語(yǔ)法轉(zhuǎn)為低版本語(yǔ)法稍刀,babel
就是這樣的一個(gè)轉(zhuǎn)換器撩独。
我們可以來(lái)測(cè)試一下
首先在 path/main.js
中寫入一段 es6 代碼
const fn = () => console.log('這是es6的箭頭函數(shù)')
fn()
class Person {
constructor(name) {
this.name = name
}
}
const name = new Person('林').name
console.log(name)
這時(shí),運(yùn)行 npm run dev
打包后發(fā)現(xiàn)谷歌瀏覽器是可以正常執(zhí)行的账月,沒(méi)有任何問(wèn)題综膀,我們打開(kāi) dev/bundle.js
,可以看到局齿,這兩段 es6 代碼還是原來(lái)的樣子剧劝,webpack 并沒(méi)有幫我們做任何轉(zhuǎn)換
class Dog {
name = 'Tom'
static color = 'yellow'
}
const dog = new Dog()
console.log(dog.name)
console.log(Dog.color)
再次運(yùn)行 npm run dev
刘急,這時(shí)你可以看到,webpack 已經(jīng)報(bào)錯(cuò)了,它并不能識(shí)別這一段代碼
接著魂务,我們來(lái)看一下配置 babel 之后是什么效果
首先,安裝
babel
npm install babel-loader @babel/core @babel/preset-env -D
npm install @babel/plugin-proposal-class-properties -D
然后在 webpack.dev.config.js
中添加配置
module.exports = {
module: {
rules: [
{
test: /\.js$/,
use: {
loader: 'babel-loader',
options: {
presets: ['@babel/env'],
plugins: [
'@babel/plugin-proposal-class-properties',
],
}
},
// 排除被轉(zhuǎn)換的目錄
exclude: /node_modules/
}
]
}
}
此時(shí)洞焙,再執(zhí)行 npm run dev
可以看到瀏覽器的運(yùn)行結(jié)果是一樣的猾愿,但是我們?cè)俅未蜷_(kāi) dev/bundle.js
可以看到我們剛剛添加的 es6 語(yǔ)法已經(jīng)被轉(zhuǎn)換成低版本的 js 語(yǔ)法
下面我們?cè)賮?lái)看一下這段代碼,
generator
語(yǔ)法
function *func() {
yield 1
yield 2
return 3
}
let newFn = func()
console.log(newFn.next())
console.log(newFn.next())
console.log(newFn.next())
console.log(newFn.next())
我們先把剛剛在 webpack.dev.config.js
中添加的 babel
配置注釋掉他巨,然后執(zhí)行 npm run dev
充坑,可以看到,程序不依賴 babel 也能夠正常執(zhí)行
但是當(dāng)我們把 babel 的配置加上染突,卻發(fā)現(xiàn)打包正常捻爷,但是程序執(zhí)行會(huì)報(bào)錯(cuò)
為了解決因?yàn)?babel 轉(zhuǎn)換導(dǎo)致的 generator 語(yǔ)法報(bào)錯(cuò),我們需要再安裝兩個(gè)插件
npm install @babel/plugin-transform-runtime -D
npm install @babel/runtime -S
并且在 webpack.dev.config.js
中添加配置
module.exports = {
module: {
rules: [
{
test: /\.js$/,
use: {
loader: 'babel-loader',
options: {
presets: ['@babel/env'],
plugins: [
'@babel/plugin-proposal-class-properties',
// 解決generator語(yǔ)法報(bào)錯(cuò)
'@babel/plugin-transform-runtime'
],
}
},
// 排除被轉(zhuǎn)換的目錄
exclude: /node_modules/
}
]
}
}
這樣就能在使用 babel 的同時(shí)使用 generator 語(yǔ)法份企,并且代碼也做了相應(yīng)的語(yǔ)法轉(zhuǎn)換
那對(duì)于高版本的原型方法也榄,babel 默認(rèn)會(huì)不會(huì)轉(zhuǎn)換呢?我們?cè)賮?lái)看一段
let arr = []
console.log(arr.includes('a'))
let str = '123'
console.log(str.includes('1'))
打包后可以看到司志,代碼并沒(méi)有被轉(zhuǎn)換
也就是說(shuō)手蝎,babel 默認(rèn)是不會(huì)對(duì)高版本的原型方法做轉(zhuǎn)換的,同樣的俐芯,babel 提供了另外一個(gè)插件
npm install @babel/polyfill -S
安裝后棵介,在需要用到高版本原型方法的地方引入即可
import '@babel/polyfill'
或者在 webpack.dev.config.js
中的入口做配置
module.exports = {
entry: ['@babel/polyfill', './path/main.js']
}
再次打包,可以看到代碼已經(jīng)被做了轉(zhuǎn)換處理吧史,這里轉(zhuǎn)換后的代碼比較長(zhǎng)邮辽,我就不貼了。簡(jiǎn)的來(lái)說(shuō)贸营,就是引入了 es6 的語(yǔ)法包吨述,然后再自定義了這個(gè)方法。
十三钞脂、source map的使用
在開(kāi)發(fā)的過(guò)程中揣云,我們經(jīng)常需要通過(guò)在控制臺(tái)查看報(bào)錯(cuò)信息或打印輸出日志來(lái)追蹤錯(cuò)誤和警告在源代碼中的原始位置,但是因?yàn)槲覀兪褂?babel 等轉(zhuǎn)換器把源代碼進(jìn)行了轉(zhuǎn)換冰啃,導(dǎo)致控制臺(tái)輸出的行數(shù)位置和我們實(shí)際的代碼不一致邓夕,造成了代碼調(diào)試的困難。source map
的作用就是為了解決這個(gè)問(wèn)題阎毅,并且使用非常簡(jiǎn)單焚刚,只需要在 webpack.dev.config.js
中添加一句配置就可以將編譯后的代碼映射回原始源代碼
module.exports = {
devtool: 'cheap-module-eval-source-map'
}
devtool
有很多的值(詳見(jiàn) webpack 官網(wǎng)),這些值怎么來(lái)選擇呢扇调?
1矿咕、我們需要的是映射原始源代碼的,而不是打包后的代碼
2、帶 cheap
關(guān)鍵字的表示開(kāi)銷很少的碳柱,也就是說(shuō)這種模式下打包出來(lái)的 source map 會(huì)很小捡絮,因?yàn)閟ource map 會(huì)占用額外的資源,所以我們要盡可能地減少它的開(kāi)支
3莲镣、帶 eval
關(guān)鍵字的更推薦使用福稳,因?yàn)?source map 的原理是額外生成一個(gè)映射文件,如果不帶 eval 的全部會(huì)單獨(dú)生成一個(gè)文件剥悟,而帶 eval 的不會(huì)生成額外的映射文件灵寺,而是通過(guò) eval 函數(shù)在代碼內(nèi)部來(lái)實(shí)現(xiàn)的曼库,也就是說(shuō) source map 的部分也會(huì)被打包進(jìn) bundle.js
中区岗。
我們來(lái)驗(yàn)證一下,分別使用 cheap-module-eval-source-map
和 cheap-module-source-map
兩種模式進(jìn)行打包
對(duì)比可以看到毁枯,使用
cheap-module-source-map
打包后額外生成了一個(gè) .map
文件慈缔,而 cheap-module-eval-source-map
卻沒(méi)有。但細(xì)心的朋友可以發(fā)現(xiàn)种玛,后者雖然沒(méi)有額外生成一個(gè)映射文件藐鹤,但是 bundle.js
卻比前者大了將近一倍多,那是不是意味著使用額外生成 .map
文件的反而比較好呢赂韵?其實(shí)不然娱节,對(duì)于生產(chǎn)環(huán)境來(lái)說(shuō)一倍的體積還是非常重要的,而 source map 更多是在開(kāi)發(fā)環(huán)境使用的祭示,開(kāi)發(fā)時(shí) 500kb 或是 1m 對(duì)于我們本地服務(wù)來(lái)說(shuō)影響可以說(shuō)是微乎其微的肄满,但是少了一個(gè)文件,卻可以減少一次額外的請(qǐng)求质涛。
綜上稠歉,推薦選擇使用 cheap-module-eval-source-map
☆ 注: 使用 source map 需要瀏覽器能夠支持 JavaScript 的 source map 功能,并且確保該功能開(kāi)啟
十四汇陆、插件
webpack 有很多的插件怒炸,主要是用來(lái)解決一些 loader 無(wú)法完美實(shí)現(xiàn)的一些其他的事情,以方便我們的開(kāi)發(fā)毡代。前面我們已經(jīng)用到了HTMLWebpackPlugin
這個(gè)插件阅羹, 這里再給大家介紹兩個(gè)比較常用的
clean-webpack-plugin
這個(gè)插件可以在我們執(zhí)行打包的時(shí)候自動(dòng)清除上一次打包后的目錄,然后再重新生成教寂。
首先安裝插件
npm install clean-webpack-plugin -D
然后在 webpack.dev.config.js
的 plugins
選項(xiàng)中配置即可
const { CleanWebpackPlugin } = require('clean-webpack-plugin')
module.exports = {
plugins: [
new CleanWebpackPlugin()
]
}
BannerPlugin
這個(gè)插件主要用于為每個(gè) chunk
文件頭部添加版權(quán)注釋信息灯蝴,這是 webpack 的內(nèi)置插件,所以不需要裝包就可以直接在 webpack.dev.config.js
中使用
module.exports = {
plugins: [
new webpack.BannerPlugin({
banner: '版權(quán)注釋信息'
})
// 或 new webpack.BannerPlugin('版權(quán)注釋信息')
]
}
CopyWebpackPlugin
這個(gè)插件主要是在我們項(xiàng)目中有不需要參與打包的靜態(tài)資源時(shí)孝宗,能夠?qū)㈧o態(tài)資源文件原封不動(dòng)的復(fù)制到打包輸出的指定目錄下穷躁,以保證靜態(tài)資源的正常引用
同樣地,先裝包
npm install copy-webpack-plugin -D
然后再 webpack.dev.config.js
的 plugins
選項(xiàng)中做配置
const path = require('path')
const CopyWebpackPlugin = require('copy-webpack-plugin')
module.exports = {
plugins: [
new new CopyWebpackPlugin([
{
// from: 源,從哪里拷貝问潭,可以是相對(duì)路徑或絕對(duì)路徑猿诸,推薦絕對(duì)路徑
from: path.join(__dirname, 'assets'),
// to: 目標(biāo),拷貝到哪里去狡忙,相對(duì)于 output 的路徑梳虽,同樣可以是相對(duì)路徑或絕對(duì)路徑,但是更推薦相對(duì)路徑
to: 'assets'
}
])
]
}
十五灾茁、HTML 中 img 標(biāo)簽的圖片資源處理 ??
前面我們介紹了 file-loader
和 url-loader
這兩種 loader 來(lái)處理圖片資源窜觉,但這些圖片是在 js 文件中引用的,但是還有一種情況北专,就是如果圖片資源直接在 index.html
中引用的話禀挫,前面這兩個(gè) loader 都是不會(huì)對(duì)我們引用的圖片進(jìn)行打包處理的,甚至是我們剛剛介紹的 CopyWebpackPlugin
插件也只是通過(guò)把圖片拷貝到打包目錄下來(lái)輔助我們的使用拓颓,并沒(méi)有真正通過(guò) webpack 進(jìn)行打包语婴。下面就來(lái)介紹一下在 webpack 中如何真正地處理在 html 中通過(guò) img 標(biāo)簽引用的圖片。
首先安裝 html-withimg-loader
npm install html-withimg-loader -D
然后在 webpack.dev.config.js
中配置即可
module.exports = {
module: {
rules: [
{
test: /\.(png|svg|jpg|gif)$/,
use: {
loader: 'file-loader',
options: {
esModule: false
}
}
},
{
test: /\.(htm|html)$/i,
use: 'html-withimg-loader'
}
]
}
}
☆ 需要注意的是驶睦,html-withimg-loader
同樣需要搭配 file-loader
或 url-loader
來(lái)使用砰左,并且需要把 options
選項(xiàng)里的 esModule
設(shè)為 false
執(zhí)行 npm run dev
,可以看到圖片已經(jīng)被打包场航,并且 index.html
里 img
標(biāo)簽的 src
路徑已經(jīng)被替換成打包后的圖片了
十六缠导、多頁(yè)應(yīng)用的打包
當(dāng)前 SPA 的開(kāi)發(fā)模式似乎已經(jīng)是前端應(yīng)用的一個(gè)主流,特別是三大框架的出現(xiàn)溉痢,讓前端開(kāi)發(fā)者可以非常方便實(shí)現(xiàn)一個(gè)單頁(yè)面應(yīng)用僻造,那么如何使用 webpack 來(lái)處理一個(gè)多頁(yè)應(yīng)用呢?下面我們就來(lái)簡(jiǎn)單看一下适室。
首先嫡意,我們?cè)?src
目錄下新建兩個(gè) html 和兩個(gè) js
src
+ index.html
+ other.html
+ index.js
+ other.js
然后修改 webpack.dev.config.js
的入口、出口和 HTMLWebpackPlugin
插件配置
module.exports = {
entry: {
index: './src/index.js',
other: './src/other.js'
},
output: {
path: path.resolve(__dirname, 'dev'),
// 多入口無(wú)法對(duì)應(yīng)一個(gè)固定的出口捣辆,所以按原始文件名輸出
filename: '[name].js',
},
plugins: [
new HTMLWebpackPlugin({
filename: 'index.html',
template: './src/index.html',
// 對(duì)應(yīng) entry 的入口命名蔬螟,指定打包后的 html 應(yīng)該引入哪些 js 文件
chunks: ['index']
}),
new HTMLWebpackPlugin({
filename: 'other.html',
template: './src/other.html',
chunks: ['other']
})
]
}
打包后可以看到,dev
目錄下已經(jīng)生成了多個(gè) html 文件和 js 文件汽畴,并且 html 文件也可以正確引入我們指定的 js 文件
打開(kāi)瀏覽器驗(yàn)證一下旧巾,可以看到已經(jīng)實(shí)現(xiàn)了多頁(yè)面的跳轉(zhuǎn)
十七、引入第三方庫(kù)
常用的第三方庫(kù)引用方式就是在每個(gè)需要用到的模塊通過(guò) import
或 require
進(jìn)行導(dǎo)入忍些,但是如果希望能夠在全局使用而不需要在每個(gè)模塊進(jìn)行單獨(dú)的導(dǎo)入呢鲁猩?這里介紹兩種比較方便的引入方式,一種通過(guò) expose-loader
進(jìn)行全局變量的注入罢坝,另一種是使用內(nèi)置插件 webpack.ProvidePlugin
對(duì)每個(gè)模塊的閉包空間廓握,注入一個(gè)變量,自動(dòng)加載模塊,這樣就不需要在每個(gè)模塊里面進(jìn)行 import 或 require
expose-loader 將庫(kù)引入到全局作用域
安裝 expose-loader
npm install expose-loader -D
在 webpack.dev.config.js
中配置
module.exports = {
module: {
rules: [
{
test: require.resolve('jquery'),
use: {
loader: 'expose-loader',
options: '$'
}
}
]
}
}
☆ require.resolve
用來(lái)獲取模塊的絕對(duì)路徑隙券,所以這里的 loader 只會(huì)作用于 jquery 模塊男应,并且只在 bundle 中使用到它時(shí),才進(jìn)行處理
webpack.ProvidePlugin 將庫(kù)自動(dòng)加載到每個(gè)模塊
在 webpack.dev.config.js
中創(chuàng)建插件對(duì)象娱仔,把變量指向?qū)?yīng)的 node 模塊
module.exports = {
plugins: [
new webpack.ProvidePlugin({
$: 'jquery',
jQuery: 'jquery'
})
]
}
十八沐飘、區(qū)分環(huán)境配置文件打包
前面我們也說(shuō)到過(guò),webpack 支持一個(gè)項(xiàng)目有多個(gè)配置文件牲迫。項(xiàng)目開(kāi)發(fā)時(shí)一般需要使用兩套配置文件耐朴,用于開(kāi)發(fā)階段打包(不壓縮代碼,不優(yōu)化代碼盹憎,增加效率)和上線階段打包(壓縮代碼筛峭、優(yōu)化代碼,打包后直接上線使用)脚乡,一般情況下蜒滩,我們會(huì)抽取成三個(gè)配置文件:webpack.base.js
滨达、webpack.prod.js
奶稠、webpack.dev.js
。
首先捡遍,將開(kāi)發(fā)環(huán)境和生產(chǎn)環(huán)境公用的配置放入 base 中锌订,不同的配置各自放入 prod 或 dev 文件中(例如:mode);然后画株,在 dev 和 prod 中使用 webpack-merge
把自己的配置與 base 的配置進(jìn)行合并后導(dǎo)出辆飘;最后,在 package.json
中配置 npm script
腳本谓传,通過(guò) --config
手動(dòng)指定特定的配置文件蜈项。
// ----------------------------------webpack.base.js----------------------------------
// 一般包括入口、出口续挟、插件以及l(fā)oader的配置
const path = require('path')
const HTMLWebpackPlugin = require('html-webpack-plugin')
const { CleanWebpackPlugin } = require('clean-webpack-plugin')
module.exports = {
entry: {
index: './src/index.js',
other: './src/other.js'
},
output: {
path: path.resolve(__dirname, 'dev'),
filename: '[name].js',
},
plugins: [
new HTMLWebpackPlugin({
filename: 'index.html',
template: './src/index.html',
chunks: ['index']
}),
new HTMLWebpackPlugin({
filename: 'other.html',
template: './src/other.html',
chunks: ['other']
}),
new CleanWebpackPlugin()
],
module: {
rules: [
{
test: /\.css$/,
use: [
'style-loader',
'css-loader'
]
}, {
test: /\.less$/,
use: [
'style-loader',
'css-loader',
'less-loader',
]
}, {
test: /\.s(a|c)ss$/,
use: [
'style-loader',
'css-loader',
'sass-loader'
]
}, {
test: /\.(png|svg|jpg|gif)$/,
use: {
loader: 'url-loader',
options: {
esModule: false,
limit: 2 * 1024, // 小于5kb轉(zhuǎn)base64
outputPath: 'media/images', // 自定義打包后的圖片輸出路徑
name: '[name]-[hash:4].[ext]' // 自定義打包后的圖片名字,hash:n,n為hash長(zhǎng)度
}
}
}, {
test: /\.(woff|woff2|eot|ttf|otf)$/,
use: 'file-loader'
}, {
test: /\.js$/,
use: {
loader: 'babel-loader',
options: {
presets: ['@babel/env'],
plugins: [
'@babel/plugin-proposal-class-properties',
'@babel/plugin-transform-runtime'
],
}
},
exclude: /node_modules/
}, {
test: /\.(htm|html)$/i,
use: 'html-withimg-loader'
}
]
}
}
// ----------------------------------webpack.dev.js ----------------------------------
// 一般包括 mode紧卒、source-map以及開(kāi)發(fā)服務(wù)器 dev-server 的配置
const merge = require('webpack-merge')
module.exports = merge(require('./webpack.base.js'), {
mode: 'development',
devtool: 'cheap-module-source-map',
devServer: {
// 自動(dòng)打開(kāi)瀏覽器
open: true,
// 熱更新
hot: true,
// 文件壓縮
compress: true,
// 自定義端口
port: 3001,
// 自定義根目錄
contentBase: './src'
}
})
// ----------------------------------webpack.prod.js----------------------------------
// 配置mode
const merge = require('webpack-merge')
module.exports = merge(require('./webpack.base'), {
mode: 'production'
})
配置兩個(gè) npm script
腳本
"scripts": {
"start": "webpack-dev-server --config webpack.dev.js",
"build": "webpack --config webpack.prod.js"
}
一般情況下,我們不會(huì)把所有配置文件放在根目錄下诗祸,而是把它們統(tǒng)一歸類到一個(gè) config
文件夾里
+ config
webpack.base.js
webpack.dev.js
webpack.prod.js
配置文件和 npm script
腳本也要做相應(yīng)的修改跑芳,否則執(zhí)行的時(shí)候會(huì)有文件路徑問(wèn)題
☆ 在 webpack 的配置文件中,相對(duì)路徑是一直以根目錄為基準(zhǔn)的直颅,但是絕對(duì)路徑的 __dirname
指的是當(dāng)前配置文件所在目錄
// ----------------------------------webpack.base.js----------------------------------
const path = require('path')
module.exports = {
output: {
// 如果使用了相對(duì)路徑則不需要修改博个,絕對(duì)路徑需要拼接正確的路徑,..代表往上一級(jí)目錄
path: path.resolve(__dirname, '..', 'dev'),
filename: '[name].js',
}
}
// ----------------------------------npm script 腳本----------------------------------
"scripts": {
"start": "webpack-dev-server --config ./config/webpack.dev.js",
"build": "webpack --config ./config/webpack.prod.js"
}
這樣我們就可以將配置文件統(tǒng)一歸類到 config
目錄下功偿,在開(kāi)發(fā)階段直接執(zhí)行 npm run start
打開(kāi) dev-server
服務(wù)器調(diào)試項(xiàng)目盆佣,在上線階段執(zhí)行 npm run build
進(jìn)行文件打包
十九、定義環(huán)境變量
某些情況下我們需要在業(yè)務(wù)代碼中區(qū)分當(dāng)前項(xiàng)目是處于開(kāi)發(fā)階段還是上線階段,比如當(dāng)后端在開(kāi)發(fā)環(huán)境和生產(chǎn)環(huán)境提供兩個(gè)不同的 API 地址時(shí)共耍。
webpack 提供了一個(gè)內(nèi)置插件 DefinePlugin
來(lái)讓我們定義一個(gè)環(huán)境變量投蝉,最終可以實(shí)現(xiàn)開(kāi)發(fā)階段與上線階段的 api 地址自動(dòng)切換。
// ----------------------------------webpack.dev.js----------------------------------
const webpack = require('webpack')
module.exports = {
plugins: [
new webpack.DefinePlugin({
NODE_ENV: '"dev"'
})
],
}
// ----------------------------------webpack.prod.js----------------------------------
const webpack = require('webpack')
module.exports = {
plugins: [
new webpack.DefinePlugin({
NODE_ENV: '"prod"'
})
],
}
這樣我們就可以在項(xiàng)目每個(gè)模塊的業(yè)務(wù)代碼中使用 NODE_ENV 變量征堪,需要注意的是定義的時(shí)候變量的值需要加引號(hào)瘩缆,引號(hào)里的內(nèi)容會(huì)被解析成表達(dá)式,當(dāng)成 JS 代碼執(zhí)行佃蚜,有點(diǎn)類似于 eval 函數(shù)
二十庸娱、http-proxy 解決跨域
前端開(kāi)發(fā)者都知道,由于瀏覽器的同源策略谐算,在向后端請(qǐng)求數(shù)據(jù)的時(shí)候熟尉,會(huì)遇到跨域的問(wèn)題。目前解決跨域的主要方案有:jsonp洲脂、cors斤儿、http-proxy
jsonp 是一種非官方推薦的解決方案,它其實(shí)就是創(chuàng)建了 script 標(biāo)簽恐锦,實(shí)現(xiàn)了去其他域獲取 js 腳本往果,請(qǐng)求的 js 腳本會(huì)返回一個(gè)函數(shù)調(diào)用,函數(shù)的實(shí)參就包含了需要從后臺(tái)獲取的數(shù)據(jù)一铅。說(shuō)白了就是利用瀏覽器的漏洞變相實(shí)現(xiàn)了跨域陕贮。由于它不是 ajax 請(qǐng)求,所以無(wú)法設(shè)置請(qǐng)求的方法潘飘,默認(rèn)只支持 get 請(qǐng)求肮之。jsonp 在早期還是比較常用的,但是慢慢地就被 cors 的方案給代替了卜录。
cors 即跨域資源共享戈擒,應(yīng)該是目前最主流的跨域解決方案了,就是在數(shù)據(jù)接口服務(wù)器在響應(yīng)數(shù)據(jù)的時(shí)候添加一個(gè)響應(yīng)頭信息去信任請(qǐng)求的客戶端艰毒,很明顯這是后端開(kāi)發(fā)者的工作筐高。那如果沒(méi)有后端配合呢?這時(shí)候就需要用到 http-proxy 了现喳。
http-proxy 其實(shí)就是 http 請(qǐng)求代理凯傲,原理很簡(jiǎn)單,就是客戶端瀏覽器直接訪問(wèn)本域服務(wù)器嗦篱,proxy 再將 ajax 請(qǐng)求轉(zhuǎn)發(fā)給數(shù)據(jù)接口服務(wù)器冰单。
這里要介紹的 devServer 解決跨域,其原理就是 http-proxy灸促。也就是將所有 ajax 請(qǐng)求發(fā)送給 devServer 服務(wù)器诫欠,再由 devServer 服務(wù)器做一次轉(zhuǎn)發(fā)涵卵,發(fā)送給數(shù)據(jù)接口服務(wù)器。由于 ajax 是發(fā)送給 devServer 服務(wù)器的荒叼,所以不存在跨域轿偎,而 devServer 是用 node 平臺(tái)發(fā)送的 http 請(qǐng)求,自然也不涉及到跨域問(wèn)題被廓。
使用 devServer
來(lái)配置 http 轉(zhuǎn)發(fā)只需要在開(kāi)發(fā)環(huán)境的配置文件中添加 proxy
// ----------------------------------webpack.dev.js ----------------------------------
const merge = require('webpack-merge')
module.exports = merge(require('./webpack.base.js'), {
devServer: {
// 自動(dòng)打開(kāi)瀏覽器
open: true,
// 熱更新
hot: true,
// 文件壓縮
compress: true,
// 自定義端口
port: 3001,
// 自定義根目錄
contentBase: './src',
// http 代理
proxy: {
// 當(dāng)前端請(qǐng)求本域以 /api 開(kāi)頭的地址時(shí)坏晦,會(huì)將請(qǐng)求轉(zhuǎn)發(fā)到 http://10.8.20.28:8080
'/api': 'http://10.8.20.28:8080'
}
}
})
在業(yè)務(wù)代碼中使用請(qǐng)求,注意接口地址不需要再拼接域名
axios.get('/api/getInfo').then(res => console.log(res))
如果后端 api 地址不是固定以 /api 開(kāi)頭的呢嫁乘?我們也同樣可以自定義一個(gè) /api昆婿,然后再把地址進(jìn)行重寫
// ----------------------------------webpack.dev.js ----------------------------------
const merge = require('webpack-merge')
module.exports = merge(require('./webpack.base.js'), {
devServer: {
// http 代理
proxy: {
// 此時(shí) /api 是前端自己加的,只是為了統(tǒng)一代理轉(zhuǎn)發(fā)
'/api': {
target: 'http://10.8.20.28:8080',
// 重寫地址蜓斧,把前端自己加的 /api 消除
pathRewrite: {
'^/api': ''
}
}
}
}
})
二十一仓蛆、HMR 的簡(jiǎn)單使用
HMR 即模塊熱替換,就是通過(guò) module.hot.accept
方法進(jìn)行文件監(jiān)視挎春,從而對(duì)某個(gè)模塊進(jìn)行熱更新看疙。只要模塊內(nèi)容發(fā)生變化,就會(huì)觸發(fā)回調(diào)函數(shù)直奋,從而可以重新讀取模塊內(nèi)容能庆,做對(duì)應(yīng)的操作
我們可以新建一個(gè) hotmodule.js
作為監(jiān)視的文件,然后在 index.js
中寫入下面的代碼對(duì) hotmodule.js
進(jìn)行監(jiān)聽(tīng)帮碰,當(dāng) hotmodule.js
被修改了相味,可以看到瀏覽器不會(huì)刷新拾积,但是控制臺(tái)會(huì)打印輸出最新的信息
if (module.hot) {
module.hot.accept('./hotmodule.js', () => {
console.log('hotmodule.js更新了')
let hot = require('./hotmodule.js')
console.log(hot)
})
}