webpack總結篇
基於
webpack3
一、webpack簡介
1.1 版本更迭
image.png
大版本變化
image.png
1.2 功能進化
Webpack V1
- 編譯、打包
HMR
(模組熱更新)- 程式碼分割
- 檔案處理
Webpack V2
Tree Shaking
ES module
- 動態
Import
- 新的文件
Webpack V3
Scope Hoisting
(作用域提升)Magic Comments
(配合動態import
使用)
版本遷移
V1 -> V2
遷移指南 https://doc.webpack-china.org/guides/migrating/
V2 -> V3
更新升級即可
二、webpack核心概念
2.1 Entry
- 程式碼的入口
- 打包的入口
- 單個或多個
寫法建議使用鍵值對寫法
module.exports = {
entry: 'index.js'
}
module.exports = {
entry: '[index.js','vendor.js']
}
module.exports = { entry: { index:'index.js' } }
module.exports = {
entry: {
index:['index.js','app.js'],
vendor: 'vendor.js'
}
}
2.2 Output
- 打包成的檔案(
bundle
) - 一個或多個
- 自定義規則
- 配合
CDN
module.exports = {
entry: 'index.js',
output: {
filename: 'index.min.js'
}
}
module.exports = { entry: { index: 'index.js', vendor: 'vendor.js' }, output: { filename: '[name].min[hash:5].js' } }
2.3 Loaders
- 處理檔案
- 轉化為模組
module.exports = {
module: {
rules: [
{
test: /\.css$/,
use: 'css-loader'
}
]
}
}
2.3.1 常用Loader
編譯相關
babel-loader
ts-loader
樣式相關
style-loader
css-loader
less-loader
postcss-loader
檔案相關
file-loader
url-loader
2.4 Plugins
- 參與打包整個過程
- 打包優化和壓縮
- 配置編譯時的變數
- 極其靈活
module.exports = {
plugins: [
new webpack.optimize.UglifyJsPlugin()
]
}
2.4.1 常用plugins
優化相關
CommonsChunkPlugin
UglifyJsWebpackPlugin
功能相關
ExtractTextWebpackPlugin
提取cssHtmlWebpackPlugin
生成HTML模板HotModuleReplacementPlugin
熱模組替換CopyWebpackPlugin
拷貝檔案
2.5 名詞
Chunk
打包過程分割的程式碼塊Bundle
打包後的檔案Module
三、初探 webpack
3.1 使用babel打包es6
3.1.1 編譯 ES 6/7
Babel
npm install [email protected] @babel/core
npm install –save-dev babel-loader babel-core
Babel Presets
主要有幾種型別選擇
es2015
es2016
es2017
env
babel-preset-react
babel-preset-stage 0 - 3
npm install @babel/preset-env –save-dev
npm install babel-preset-env –save-dev
Babel Polyfill
針對一些不能處理的函式方法(
Generator
、Set
、Map
、Array.from...
)需要用到babel-Polyfill
處理
- 全域性墊片
- 為應用準備
npm install babel-polyfill –save
import “babel-polyfill”
Babel Runtime Transform
- 區域性墊片
- 為開發框架準備
npm install babel-plugin-transform-runtime –save-dev
npm install babel-runtime –save
例子
module.exports = {
entry: {
app: 'app.js'
},
output: {
filename: '[name].[hash:8].js'
},
module: {
rules:[
test: /\.js$/,
use: {
loader: 'babel-loader',
options: {
presets: [
'@babel/preset-env',{
//指定target為根據哪些語法編譯
targets: {
browsers: ['> 1%','last 2 versions']
}
}
]
}
},
exclude: '/node_modules'
]
}
}
對於
webpack
中babel
的配置可以單獨提取處理.babelrc
統一管理
{
"presets": [
["@babel/preset-env",{
targets: {
browsers: ['> 1%','last 2 versions']
}
}]
],
"plugins": [
"transform-runtime"
]
]
3.2 打包 Typescript
npm i typescipt ts-loader --save-dev
npm i typescipt awesome-typescript-loader --save-dev
配置
tsconfig.json
webpack.config.js
tsconfig
- 配置選項:官網
/docs/handbook/compiler-options.html
- 常用選項
compilerOptions
include
exclude
宣告檔案
用於編譯時檢查錯誤
以loadsh
為例,需要安裝@types/lodash
帶有宣告檔案的,而不是安裝lodash
npm install @types/lodash
npm install @types/vue
Typings
也可以這樣安裝帶有
type
的包
npm install typings
typings install lodash
例子
module.exports = {
entry: {
'app': 'app.js'
},
output: {
filename: '[name].bundle.js'
},
module: {
rules: [
test: /\.tsx?$/,
use: {
loader: 'ts-loader'
}
]
}
}
在專案根目錄建立
tsconfig.json
{
"compilerOptions": {
"module": "comonjs",
"target": "es5", //編譯後的檔案在哪個環境執行
"allowJs": true,//允許js語法
},
"include": [
//編譯路徑
"./src/*"
],
"exclude": [
//排除編譯檔案
"./node_modules"
]
}
3.3 提取 js 的公用程式碼
- 減少程式碼冗餘
- 提高載入速度
主要使用內建外掛實現
webpack.optimize.CommonsChunkPlugin
{
plugins: [
new webpack.optimize.CommonsChunkPlugin(option)
]
}
例子
module.exports = {
entry: {
"pageA": "./src/pageA",
"pageB": "./src/pageB",
"vendor": ['loash']//業務程式碼和第三方程式碼區分開,給loash單獨打一個包
},
output: {
path: path.resolve(__dirname, './dist'),
filename: '[name].bundle.js',
chunkFilename: '[name].chunk.js'
},
plugins: [
// 提取common
new webpack.optimize.CommonsChunkPlugin({
name: 'common',
minChunks:2,//出現兩次就打包成common程式碼
chunks: ['pageA','pageB']//指定範圍提取公共程式碼
})
// 提取vendor、取業務程式碼manifest
new webpack.optimize.CommonsChunkPlugin({
//把entry的vendor程式碼和這裡的common(webpack打包的)程式碼合併
names: ['vendor','manifest']
minChunks: Infinity
})
// ==== 提取業務程式碼manifest== 合併到上面names中
// 如果不想把webpack打包的程式碼和vendor程式碼合併 需要提取到manifest
//new webpack.optimize.CommonsChunkPlugin({
// webpack程式碼和vendordiam區分開
// name: 'manifest' //manifest即生成
// minChunks: Infinity
// })
]
}
image.png
3.4 程式碼分割和懶載入
第一種方式:通過wepack內建方法
require.ensure
動態載入一個模組,接收四個引數
[]:dependencies
初次並不會執行callback
的時候才會執行errorCallback
可省略chunkName
第二種方式:通過ES2015 Loader Spec
System.import()
後面演變為import()
來動態載入模組
import()
方式返回一個promise
在import
中傳入需要依賴的明,動態載入模組,就可以像使用Promise
一樣使用import().then()
程式碼分割場景
- 分離業務程式碼 和 第三方依賴
- 分離業務程式碼 和 業務公共程式碼 和 第三方依賴
- 分離首次載入 和 訪問後加載的程式碼
例子
module.exports = {
entry: {
"pageA": "./src/pageA"
},
output: {
path: path.resolve(__dirname, './dist'),
publicPath: './dist/',//動態載入路徑
filename: '[name].bundle.js',
chunkFilename: '[name].chunk.js'
}
}
目標是提取
pageA
、pageB
中公共的模組moduleA
//src/pageA.js
import './subPageA'
import './subPageB'
//ensure的時候程式碼不會執行 需要在下面載入一次
require.ensure(['lodash'],function(){
var _ = require('lodash')
_.join([1,2,3])
},'vendor')// 指定chunk名稱
export default 'pageA'
執行打包這時loadash
提取到vendor
中
image.png
這時候還不是我們想要的
//src/pageA.js
require.include('./moduleA')
if(page === 'subPageA'){
require.ensure(['./subPageA'],function(){
var subPageA = require('./subPageA')
},'subPageA')// 指定chunk名稱
}else{
require.ensure(['./subPageB'],function(){
var subPageB = require('./subPageB')
},'subPageB')// 指定chunk名稱
}
//ensure的時候程式碼不會執行 需要在下面載入一次
require.ensure(['lodash'],function(){
var _ = require('lodash')
_.join([1,2,3])
},'vendor')// 指定chunk名稱
export default 'pageA'
image.png
這時候這有
pageA
中才有moduleA
image.png
新建一個html驗證是否動態載入
<html>
<body>
<script src="./dist/pageA.bundle.js"></script>
</body>
</html>
image.png
import()動態載入的寫法
//src/pageA.js
require.include('./moduleA')
var page = 'subPageA'
if(page === 'subPageA'){
// 指定chunkName /** webpackChunkName: 'subPageA' **/
import(/** webpackChunkName: 'subPageA' **/,'./subPageA').then(function(subPageA){
console.log(subPageA)
})
}else{
import(/** webpackChunkName: 'subPageB' **/'./subPageB').then(function(subPageB){
console.log(subPageB)
})
}
// 非同步載入lodash
//ensure的時候程式碼不會執行 需要在下面載入一次
require.ensure(['lodash'],function(){
var _ = require('lodash')
_.join([1,2,3])
},'vendor')// 指定chunk名稱
export default 'pageA'
如果
/** webpackChunkName: 'subPageA' **/
相同,則會合並處理
合併了subPageA
和subPageB
image.png
來看看打包後的檔案,既有A、B
image.png
在webpack
程式碼分割中使用async
非同步載入
module.exports = {
entry: {
"pageA": "./src/pageA",
"pageB": "./src/pageB",
"vendor": ['loash']//業務程式碼和第三方程式碼區分開,給loash單獨打一個包
},
output: {
path: path.resolve(__dirname, './dist'),
filename: '[name].bundle.js',
chunkFilename: '[name].chunk.js'
},
plugins: [
// add 非同步模組
new webpack.optimize.CommonsChunkPlugin({
aysnc:'async-common',//非同步共同的東西
children: true,
names: ['vendor','manifest']
minChunks: Infinity
})
// 提取vendor、取業務程式碼manifest
new webpack.optimize.CommonsChunkPlugin({
//把entry的vendor程式碼和這裡的common(webpack打包的)程式碼合併
names: ['vendor','manifest']
minChunks: Infinity
})
]
}
//src/subPageA.js
import './moduleA'
console.log('this. is subPageA')
export default 'subPageA'
//src/subPageB.js
import './moduleA'
console.log('this. is subPageB')
export default 'subPageB'
//src/subPageB.js
import './moduleA'
console.log('this. is moduleA')
export default 'moduleA'
//src/pageA.js
// 非同步載入不能include 否則會和pageA打包到一起
// require.include('./moduleA')
import _ from 'lodash'
var page = 'subPageA'
if(page === 'subPageA'){
// 指定chunkName /** webpackChunkName: 'subPageA' **/
import(/** webpackChunkName: 'subPageA' **/,'./subPageA').then(function(subPageA){
console.log(subPageA)
})
}else{
import(/** webpackChunkName: 'subPageB' **/'./subPageB').then(function(subPageB){
console.log(subPageB)
})
}
// === lodash不在非同步載入
//ensure的時候程式碼不會執行 需要在下面載入一次
//require.ensure(['lodash'],function(){
// var _ = require('lodash')
// _.join([1,2,3])
//},'vendor')// 指定chunk名稱
export default 'pageA'
//src/pageB.js
import _ from 'lodash'
var page = 'subPageB'
if(page === 'subPageA'){
// 指定chunkName /** webpackChunkName: 'subPageA' **/
import(/** webpackChunkName: 'subPageA' **/,'./subPageA').then(function(subPageA){
console.log(subPageA)
})
}else{
import(/** webpackChunkName: 'subPageB' **/'./subPageB').then(function(subPageB){
console.log(subPageB)
})
}
// === lodash不在非同步載入
//ensure的時候程式碼不會執行 需要在下面載入一次
//require.ensure(['lodash'],function(){
// var _ = require('lodash')
// _.join([1,2,3])
//},'vendor')// 指定chunk名稱
export default 'pageB'
image.png
image.png
這樣就把
subpageA
和subPageB
共同依賴的moduleA
非同步提取出來
3.5 處理 CSS 和 CSS 模組化
引入css
需要兩個
loader
,style-loader
(建立標籤到文件流中)、css-loader
(可以import
一個樣式檔案,使得在js
中可以使用)
Style-Loader
style-loader
除了本身,還有這幾個loader
style-loader/url
可以注入link標籤到頁面style-loader/useable
控制樣式是否插入到頁面中
Style-Loader的options
insertAt
(插入位置)insertInto
(插入到dom
)singleton
(是否只使用一個style
標籤)transform
(轉化,瀏覽器環境下,插入頁面前)
例子
module.exports = {
entry: {
app: './src/app.js'
},
output: {
path: path.resolve(__dirname, 'dist'),
publicPath: './dist/', //指定從專案中哪裡引入資源
filename: '[name].bundle.js'
},
module: {
// loader解析從後往前處理
rules: [
{
test: /\.css$/,
use: [
{
//loader: 'style-loader',
loader: 'style-loader/url', //使用這個可以往頁面注入link標籤 而不是style,這個並不常用
},
{
//loader: 'css-loader',
loader: 'file-loader'//使用這個可以往頁面注入link標籤 而不是style 這個並不常用
}
]
}
]
}
}
CSS-Loader
options
alias
(解析的別名)importLoader
(@import
)Minimize
(是否壓縮)modules
(啟用css-modules
)
CSS-Modules
localIdentName: '[path][name]__[local]--[hash:base64:5]'
例子
module.exports = {
entry: {
app: './src/app.js'
},
output: {
path: path.resolve(__dirname, 'dist'),
publicPath: './dist/', //指定從專案中哪裡引入資源
filename: '[name].bundle.js'
},
module: {
// loader解析從後往前處理
rules: [
{
test: /\.css$/,
use: [
{
loader: 'style-loader',
options: {
//合併多個style為一個
singleton:true
}
},
{
loader: 'css-loader',
options: {
minimize:true,
modules: true,
// css模組化
localIdentName: '[path][name]_[local]_[hash:base64:5]'
}
}
]
}
]
}
}
配置Less / Sass
npm install less-loader less --save-dev
npm install sass-loader node-sass --save-dev
例子
module.exports = {
entry: {
app: './src/app.js'
},
output: {
path: path.resolve(__dirname, 'dist'),
publicPath: './dist/', //指定從專案中哪裡引入資源
filename: '[name].bundle.js'
},
module: {
// loader解析從後往前處理
rules: [
{
test: /\.(css|less)$/,
use: [
{
loader: 'style-loader',
options: {
//合併多個style為一個
singleton:true
}
},
{
loader: 'css-loader',
options: {
minimize:true,
modules: true,
// css模組化
localIdentName: '[path][name]_[local]_[hash:base64:5]'
}
},
{
loader: 'less-loader'
},
{
loader: 'sass-loader'
}
]
}
]
}
}
提取 CSS
extract-loader
ExtractTextWebpackPlugin
例子
module.exports = {
entry: {
app: './src/app.js'
},
output: {
path: path.resolve(__dirname, 'dist'),
publicPath: './dist/', //指定從專案中哪裡引入資源
filename: '[name].bundle.js'
},
module: {
// loader解析從後往前處理
rules: [
{
test: /\.(css|less)$/,
use:
ExtractTextWebpackPlugin.extra({
// 提取css並不會自動加入到文件中,需要在HTML手動加入css檔案
fallback: {
loader: 'style-loader',
options: {
//合併多個style為一個
singleton:true
}
},
// 處理css
use: [
{
loader: 'css-loader',
options: {
minimize:true,
modules: true,
// css模組化
localIdentName: '[path][name]_[local]_[hash:base64:5]'
}
},
{
loader: 'less-loader'
},
{
loader: 'sass-loader'
}
]
})
}
]
},
plugins: [
new ExtractTextWebpackPlugin({
filename: '[name].min.css',
allChunks: false //指定提取css範圍,true所有import進來的css
})
]
}
image.png
3.6 PostCSS in Webpack
安裝
postcss
postcss-loader
Autoprefixer
cssnano
postcss-cssnext
例子
module.exports = {
entry: {
app: './src/app.js'
},
output: {
path: path.resolve(__dirname, 'dist'),
publicPath: './dist/', //指定從專案中哪裡引入資源
filename: '[name].bundle.js'
},
module: {
// loader解析從後往前處理
rules: [
{
test: /\.(css|less)$/,
use:
ExtractTextWebpackPlugin.extra({
// 提取css並不會自動加入到文件中,需要在HTML手動加入css檔案
fallback: {
loader: 'style-loader',
options: {
//合併多個style為一個
singleton:true
}
},
// 處理css
use: [
{
loader: 'css-loader',
options: {
minimize:true,
modules: true,
// css模組化
localIdentName: '[path][name]_[local]_[hash:base64:5]'
}
},
{
loader: 'less-loader'
},
{
loader: 'sass-loader'
},
{
loader: 'postcss-loader',
options: {
ident: 'postcss',
plugins: [
require('antoprefixer')()
]
}
}
]
})
}
]
},
plugins: [
new ExtractTextWebpackPlugin({
filename: '[name].min.css',
allChunks: false //指定提取css範圍,true所有import進來的css
})
]
}
3.7 Tree shaking
3.7.1 JS Tree shaking
使用場景
- 常規優化
- 引入第三方庫的某一個功能
例子
module.exports = {
entry: {
app: './src/app.js'
},
output: {
path: path.resolve(__dirname, 'dist'),
publicPath: './dist/', //指定從專案中哪裡引入資源
filename: '[name].bundle.js'
},
module: {
rules: [
{
test: /\.js$/,
use: [
{
loader: 'babel-loader',
options: {
presets: ['env'],
plugins: [
// lodash Tree shaking
'lodash'
]
}
}
]
}
]
},
plugins: [
new ExtractTextWebpackPlugin({
filename: '[name].min.css',
allChunks: false //指定提取css範圍,true所有import進來的css
})
// Tree shaking
new webpack.optimize.UglifyJsPlugin({
})
]
}
有些庫不是es模組寫的,並不能
tree shaking
。需要藉助其他工具
npm i babel-loader babel-core babel-preset-env babel-plugin-lodash --save
lodash Tree不生效
lodash-es
--> nobabel-lugin-lodash
--->working
檢視模組是否Tree Shaking方式:去第三方庫index.js中看模組書寫方式是否是es
3.7.2 CSS Tree shaking
主要使用
purifycss-webpack
例子
const glob = require('glob-all')
module.exports = {
entry: {
app: './src/app.js'
},
output: {
path: path.resolve(__dirname, 'dist'),
publicPath: './dist/', //指定從專案中哪裡引入資源
filename: '[name].bundle.js'
},
module: {
rules: [
{
test: /\.js$/,
use: [
{
loader: 'babel-loader',
options: {
presets: ['env'],
plugins: [
// lodash Tree shaking
'lodash'
]
}
}
]
}
]
},
plugins: [
new ExtractTextWebpackPlugin({
filename: '[name].min.css',
allChunks: false //指定提取css範圍,true所有import進來的css
})
// 放到ExtractTextWebpackPlugin後面
new PurifyCss({
paths: glob.sync([
path.join(__dirname,'./*.html'),
path.join(__dirnname,'./src/*.js')
])
})
// Tree shaking
new webpack.optimize.UglifyJsPlugin({
})
]
}
四、由淺入深Webpack
4.1 檔案處理
4.1.1 圖片處理
css
中引入圖片- 自動合成雪碧圖
- 壓縮圖片
Base64
編碼
處理需要用到的
loader
file-loader
css
中引入圖片url-loader
base64
編碼img-loader
壓縮圖片postcss-sprites
合成雪碧圖
4.1.2 處理雪碧圖、base64、壓縮圖片
module.exports = {
module: {
rules: [
{
test: /\.(css|less)$/,
use:
extractLess.extract({
// 提取css並不會自動加入到文件中,需要在HTML手動加入css檔案
fallback: {
loader: 'style-loader',
options: {
//合併多個style為一個
singleton:true
}
},
// 處理css
use: [
{
loader: 'css-loader',
options: {
minimize:true,
modules: true,
// css模組化
localIdentName: '[path][name]_[local]_[hash:base64:5]'
}
},
{
loader: 'less-loader'
},
{
loader: 'sass-loader'
},
{
loader: 'postcss-loader',
options: {
ident: 'postcss',
plugins: [
// 合併雪碧圖
require('postcss-sprites')({
// 指定雪碧圖輸出路徑
spritePath: 'dist/assets/imgs/sprites',
retina: true // 處理蘋果高清retina 圖片命名需要 [email protected],對應的圖片的css大小設定也要減小一半
})
]
}
}
]
})
},
{
test: /\.(png|jpg|gif)$/,
use:[
//{
// loader: //'file-loader',//處理圖片
// options: {
// publicPath:'',// 使得圖片地址可以訪問
// outputPath: 'dist/'
// useRelativePath:true
//}
//}
// url-loader會把圖片轉成base64
{
loader: 'url-loader',
options: {
name: '[name].min.[ext]' //5kb內會轉成base64 ,否則輸出圖片路徑
limit: 1000,
publicPath:'',
outputPath: 'dist/',
useRelativePath:true
}
},
// 壓縮圖片
{
loader: 'img-loader',
options: {
pngquant: {
//圖片質量
quality:80
}
}
},
]
}
]
}
}
4.1.2 處理字型檔案
file-loader
url-loader
module.exports = {
module: {
rules: [
{
test: /\.(css|less)$/,
use:
extractLess.extract({
// 提取css並不會自動加入到文件中,需要在HTML手動加入css檔案
fallback: {
loader: 'style-loader',
options: {
//合併多個style為一個
singleton:true
}
},
// 處理css
use: [
{
loader: 'css-loader',
options: {
minimize:true,
modules: true,
// css模組化
localIdentName: '[path][name]_[local]_[hash:base64:5]'
}
},
{
loader: 'less-loader'
},
{
loader: 'sass-loader'
},
{
loader: 'postcss-loader',
options: {
ident: 'postcss',
plugins: [
// 合併雪碧圖
require('postcss-sprites')({
// 指定雪碧圖輸出路徑
spritePath: 'dist/assets/imgs/sprites',
retina: true // 處理蘋果高清retina 圖片命名需要 [email protected],對應的圖片的css大小設定也要減小一半
})
]
}
}
]
})
},
{
test: /\.(png|jpg|gif)$/,
use:[
//{
// loader: //'file-loader',//處理圖片
// options: {
// publicPath:'',// 使得圖片地址可以訪問
// outputPath: 'dist/'
// useRelativePath:true
//}
//}
// url-loader會把圖片轉成base64
{
loader: 'url-loader',
options: {
name: '[name].min.[ext]' //5kb內會轉成base64 ,否則輸出圖片路徑
limit: 1000,
publicPath:'',
outputPath: 'dist/',
useRelativePath:true
}
},
// 壓縮圖片
{
loader: 'img-loader',
options: {
pngquant: {
//圖片質量
quality:80
}
}
},
// 處理字型檔案
{
test: /\.(eot|woff2|woff|ttf|svg)$/,
use: [
{
loader:'url-loader',
options: {
name: '[name].min.[ext]' //5kb內會轉成base64 ,否則輸出圖片路徑
limit: 5000,
publicPath:'',
outputPath: 'dist/',
useRelativePath:true
}
}
]
}
]
}
]
}
}
4.1.3 處理第三方 JS 庫
處理第三方庫 用到
providePlugin
、imports-loader
1.providePlugin
以引入jQuery
為例
npm i jquery
module.exports = {
plugins: [
new webpack.ProvidePlugin({
// 在全域性注入jQuery變數
$:'jquery'
})
]
}
引入本地libs
中的jQuery
module.exports = {
resolve: {
alias: {
// $確切匹配 把jQuery這個關鍵字解析到某個目錄下
jquery$:path.resolve(__dirname,'src/libs/jquery.min.js')
}
},
plugins: [
new webpack.ProvidePlugin({
// 在全域性注入jQuery變數
$:'jquery'
})
]
}
2.imports-loader
module.exports = {
module:{
rules:[
{
test:path.resolve(__dirname,'src/app.js'),
use:[
{
loader: 'imports-loader',
options: {
$: 'jquery'
}
}
]
}
]
}
}
4.2 HTML in webpack(自動生成HTML)
自動生成
HTML
,把這個頁面需要的js
、css
插入到頁面中
4.2.1 生成 HTML
htmlWebpackPlugin
options
template
filename
minify
是否壓縮chunks
inject
是否讓css、js
通過標籤形式插入到頁面中
module.exports = {
entry: {
app: './src/app.js'
},
output: {
path: path.resolve(__dirname, 'dist'),
filename: '[name].bundle.js'
},
plugin:[
new htmlWebpackPlugin({
filename:'index.html', // 不傳預設index.html
template: './index.html',//傳入模板
inject:true,//控制js\css是否插入到頁面
minify: {
collapseWhitespace:true //壓縮空格
},
chunks:['app']//指定chunks會把跟這個入口相關的chunks插入到頁面中
})
]
}
4.2.2 HTML 中引入圖片
需要用到
html-loader
html-loader
options
attrs: [img:src]
module.exports = {
entry: {
app: './src/app.js'
},
output: {
path: path.resolve(__dirname, 'dist'),
filename: '[name].bundle.js',
publicPath:'/' //網站路徑為/ 圖片等資源引用不會發生錯誤
},
module:{
rules:[
{
loader: 'url-loader',
options: {
name: '[name].min.[ext]' //5kb內會轉成base64 ,否則輸出圖片路徑
limit: 1000,
publicPath:'',
outputPath: 'assets/imgs/',
//useRelativePath:true // 這裡不能使用這個 因為圖片路徑在HTML中、css中都存在,打包的時候圖片會放錯地方。需要用到絕對路徑,在output指定publicPath:'/'
}
},
// 處理HTML中的圖片引用路徑問題
{
test: /\.html$/,
use: [
{
loader: 'html-loader',
options: {
attrs: ['img:src','img:data-src']
}
}
]
}
]
}
}
require在HTML中引入圖片
<img src="${require('./public/imgs/xx.png)}" />
4.2.3 配合優化
提前載入webpack載入程式碼
inline-manifest-webpack-plugin
html-webpack-inline-chunk-plugin
建議使用 html-webpack-inline-chunk-plugin
npm i html-webpack-inline-chunk-plugin
var HTMLInlieChunk = require('html-webpack-inline-chunk-plugin')
module.exports = {
entry: {
app: './src/app.js'
},
output: {
path: path.resolve(__dirname, 'dist'),
filename: '[name].bundle.js',
publicPath:'/'
},
plugin:[
new htmlWebpackPlugin({
filename:'index.html', // 不傳預設index.html
template: './index.html',//傳入模板
inject:true,//控制js\css是否插入到頁面
minify: {
collapseWhitespace:true //壓縮空格
},
chunks:['app']//指定chunks會把跟這個入口相關的chunks插入到頁面中
})
new webpack.mdtimize.CommonsChunkPlugin({
name: 'manifest'
})
new HTMLInlieChunk({
inlineChunks: ['manifest'] //把webpack生成的manifest提取到HTML檔案script中,減少請求
})
]
}
五、Webpack 環境配置
5.1 Webpack Watch Mode
webpack --watch
// 簡寫
webpack -w
//webpack.config.js
var webpack = require('wepback')
var PurifyWebpack = require('pruifycss-webpack')
var ExtractTextWebpackPlugin = require('extract-text-webpack-plugin')
var HtmlWebpackPlugin = require('html-webpack-plugin')
var CleanWebpack = require('clean-webpacl-plugin')
var path = require('path')
var glob = rquire('glob-all')//處理多個路徑
var extractLess = new ExtractTextWebpackPlugin({
filename: 'css/[name]-bundle-[hash:5].css'
})
module.exports = {
entry: {
app: './src/app.js'
},
output: {
path: path.resolve(__dirname, 'dist'),
filename: '[name]-bundle-[hash:5].js'
},
module:{
rules: [
{
test: /\.(css|less)$/,
use:
extractLess.extract({
// 提取css並不會自動加入到文件中,需要在HTML手動加入css檔案
fallback: {
loader: 'style-loader',
options: {
//合併多個style為一個
singleton:true
}
},
// 處理css
use: [
{
loader: 'css-loader',
options: {
minimize:true,
modules: true,
// css模組化
localIdentName: '[path][name]_[local]_[hash:base64:5]'
}
},
{
loader: 'less-loader'
},
{
loader: 'sass-loader'
},
{
loader: 'postcss-loader',
options: {
ident: 'postcss',
plugins: [
// 合併雪碧圖
require('postcss-sprites')({
// 指定雪碧圖輸出路徑
spritePath: 'dist/assets/imgs/sprites',
retina: true // 處理蘋果高清retina 圖片命名需要 [email protected],對應的圖片的css大小設定也要減小一半
})
]
}
}
]
})
}
]
}
plugin: [
new CleanWebpack()
]
}
5.2 Webpack Dev Server
5.2.1 Dev Server
不能用來直接打包檔案,
Dev Server
搭建本地開發,檔案存在記憶體中
特性
live reloading
- 路徑重定向
- 支援
HTTPS
- 瀏覽器中顯示編譯錯誤
- 介面代理
- 模組熱更新
dev server
inline
contentBase
port
histApiFllback
https
proxy
hot
openpage
lazy
overlay
開啟錯誤遮罩
使用
"script"{
// 啟動
"server": "webpack-dev-server --open"
}
module.exports = {
entry: {
app: './src/app.js'
},
output: {
path: path.resolve(__dirname, 'dist'),
publicPath: '/',
filename: '[name].[hash:5].js'
},
devServer: {
port: 9001,
// 輸入任意路徑都不會出現404 都會重定向
// historyApiFallback: true
historyApiFallback: {
//從一個確定的url指向對應的檔案
//rewrites: [
// {
// from: '/pages/a',// 可以寫正則
// to: '/pages/a.html'
// }
rewrites: [
{
from: /^\/([a-zA-Z0-9]+\/?)([a-zA-Z0-9]+)/
to: function(context){
return '/' + context.match[1] + context.match[2] + '.html'
}
}
]
]
}
}
}
5.2.2 proxy代理遠端介面
- 代理遠端介面請求
http-proxy-middleware
devServer.proxy
module.exports = {
entry: {
app: './src/app.js'
},
output: {
path: path.resolve(__dirname, 'dist'),
publicPath: '/',
filename: '[name].[hash:5].js'
},
devServer: {
port: 9001,
// 輸入任意路徑都不會出現404 都會重定向
// historyApiFallback: true
historyApiFallback: {
//從一個確定的url指向對應的檔案
//rewrites: [
// {
// from: '/pages/a',// 可以寫正則
// to: '/pages/a.html'
// }
rewrites: [
{
from: /^\/([a-zA-Z0-9]+\/?)([a-zA-Z0-9]+)/
to: function(context){
return '/' + context.match[1] + context.match[2] + '.html'
}
}
]
]
},
proxy: {
'/api': {
target: 'https://blog.poetries.top',//代理到伺服器
changeOrigin:true,
logLevel: 'debug',
// pathRewite: { },
headers:{}// 請求頭
}
}
}
}
5.2.3 模組熱更新
-
保持應用的資料狀態
-
節省除錯時間
-
不需要重新整理
-
devServer.hot
-
webpack.HotModleReplacementPlugin
-
webpack.NamedModulesPlugin
看到模組更新的路徑
Module Hot Reloading
module.hot
module.hot.accept
module.exports = {
entry: {
app: './src/app.js'
},
output: {
path: path.resolve(__dirname, 'dist'),
publicPath: '/',
filename: '[name].[hash:5].js'
},
devServer: {
port: 9001,
// 輸入任意路徑都不會出現404 都會重定向
// historyApiFallback: true
historyApiFallback: {
//從一個確定的url指向對應的檔案
//rewrites: [
// {
// from: '/pages/a',// 可以寫正則
// to: '/pages/a.html'
// }
rewrites: [
{
from: /^\/([a-zA-Z0-9]+\/?)([a-zA-Z0-9]+)/
to: function(context){
return '/' + context.match[1] + context.match[2] + '.html'
}
}
]
]
},
hot:true,//開啟模組熱更新
hotOnly:true,
proxy: {
'/api': {
target: 'https://blog.poetries.top',//代理到伺服器
changeOrigin:true,
logLevel: 'debug',
// pathRewite: { },
headers:{}// 請求頭
}
}
},
plugin:[
// 模組熱更新外掛
new webpack.HotModuleReplacementPlugin()
// 輸出熱更新路徑
new webpack.NamedModulesPlugin()
]
}
模組熱更新配置
需要通過module.hot
f (module.hot) {
module.hot.accept('./library.js', function() {
// Do something with the updated library module...
})
}
5.2.4 開啟除錯SourceMap
Source Map除錯
把生成以後程式碼和之前的做一個對映
開啟Source Map方式
JS Source Map
設定
develpoment
eval
eval-source-map
cheap-eval-source-map
cheap-module-eval-source-map
推薦使用
cheap-module-source-map
production
source-map
hidden-source-map
nosource-source-map
推薦使用
source-map
CSS Source Map
設定
改變
loader
的options
選項
css-loader.options.soucemap
less-loader.options.soucemap
sass-loader.options.soucemap
module.exports = {
entry: {
app: './src/app.js'
},
output: {
path: path.resolve(__dirname, 'dist'),
publicPath: '/',
filename: '[name].[hash:5].js'
},
module: {
// 處理css的每個loader加上sourceMap: true 觀察css樣式 可以看到對應的行號
rules: [
test: /\.less/,
use: [
{
loader: 'style-loader',
options: {
// singleton: true,會導致css的sourceMap不生效
//singleton: true,
sourceMap: true
}
},
{
loader: 'css-loader',
options: {
importLoaders: 2,
sourceMap: true
}
},
{
loader: 'less-loader',
options: {
sourceMap: true
}
}
]
]
},
devtool: 'cheap-module-eval-source-map',//開啟sourcemap
devServer: {
port: 9001,
// 輸入任意路徑都不會出現404 都會重定向
// historyApiFallback: true
historyApiFallback: {
//從一個確定的url指向對應的檔案
//rewrites: [
// {
// from: '/pages/a',// 可以寫正則
// to: '/pages/a.html'
// }
rewrites: [
{
from: /^\/([a-zA-Z0-9]+\/?)([a-zA-Z0-9]+)/
to: function(context){
return '/' + context.match[1] + context.match[2] + '.html'
}
}
]
]
},
hot:true,//開啟模組熱更新
hotOnly:true,
overlay:true,//錯誤提示
proxy: {
'/api': {
target: 'https://blog.poetries.top',//代理到伺服器
changeOrigin:true,
logLevel: 'debug',
// pathRewite: { },
headers:{}// 請求頭
}
}
},
plugin:[
// 模組熱更新外掛
new webpack.HotModuleReplacementPlugin()
// 輸出熱更新路徑
new webpack.NamedModulesPlugin()
]
}
5.2.5 設定 ESLint 檢查程式碼格式
eslint
eslint-loader
esling-plugin-html
eslint-frindly-formatter
友好提示錯誤
配置eslint
wepback config
新增loader
.eslintrc
或者在package.json
的eslintConfig
中寫
配置eslint的規範,推薦使用JavaScript standard style(https://standardjs.com)
需要安裝以下外掛
eslint-config-standard
eslint-plugin-promise
eslint-plugin-standard
eslint-plugin-import
eslint-plugin-node
eslint-loader
options.failOnWarning
出現警告options.failOnError
options.formatter
options.outputReport
設定
devServer.overlay
在瀏覽器中看提示的錯誤
// .eslintrc
module.exports = {
root: true,
extends: 'standard',
plugins: [],
env: {
browsers: true,
node: true // node環境
},
rules: {
// 縮排
indent: ['error', 4],
//換行
"eol-last": ['error', 'never']
}
}
module.exports = {
entry: {
app: './src/app.js'
},
output: {
path: path.resolve(__dirname, 'dist'),
publicPath: '/',
filename: '[name].[hash:5].js'
},
module: {
// 處理css的每個loader加上sourceMap: true 觀察css樣式 可以看到對應的行號
rules: [
{
test: /\.js$/,
include: [path.resolve(__dirname,'src/')],
exclude: [path.resolve(__dirname,'src/libs')],
use: [
{
loader: 'babel-loader',
options: {
presets: ['env']
}
},
//eslint-loader需要在babel-loader之後處理
{
loader: 'eslint-loader',
options: {
formatter: require('eslint-frindly-formatter')
}
}
]
},
{
test: /\.less/,
use: [
{
loader: 'style-loader',
options: {
// singleton: true,會導致css的sourceMap不生效
//singleton: true,
sourceMap: true
}
},
{
loader: 'css-loader',
options: {
importLoaders: 2,
sourceMap: true
}
},
{
loader: 'less-loader',
options: {
sourceMap: true
}
}
]
}
]
},
devtool: 'cheap-module-eval-source-map',//開啟sourcemap
devServer: {
port: 9001,
// 輸入任意路徑都不會出現404 都會重定向
// historyApiFallback: true
historyApiFallback: {
//從一個確定的url指向對應的檔案
//rewrites: [
// {
// from: '/pages/a',// 可以寫正則
// to: '/pages/a.html'
// }
rewrites: [
{
from: /^\/([a-zA-Z0-9]+\/?)([a-zA-Z0-9]+)/
to: function(context){
return '/' + context.match[1] + context.match[2] + '.html'
}
}
]
]
},
hot:true,//開啟模組熱更新
hotOnly:true,
overlay:true,//錯誤提示
proxy: {
'/api': {
target: 'http://blog.poetries.top',//代理到伺服器
changeOrigin:true,
logLevel: 'debug',
// pathRewite: { },
headers:{}// 請求頭
}
}
},
plugin:[
// 模組熱更新外掛
new webpack.HotModuleReplacementPlugin()
// 輸出熱更新路徑
new webpack.NamedModulesPlugin()
]
}
5.2.6 區分開發環境 和 生產環境
開發環境
- 模組熱更新
sourceMap
- 介面代理
- 程式碼規範檢查
生產環境
- 提取公共程式碼
- 壓縮
- 檔案壓縮或
base64
編碼 - 去除無用的程式碼
共同點
- 入口一致
- loader處理
- 解析配置一致
使用
webpack-merge
合併公共配置
webpack.dev.conf.js
wepback.prod.conf.js
webpack.common.conf.js
"scripts":{
"server": "wepback-dev-server --env development --open --config build/webpack.common.config.js",
"build": "wepback --env production --open --config build/webpack.common.config.js"
}
公共配置
build/webpack.common.conf.js
const merge = require('webpack-merge')
const webpack = require('webpack')
const path = require('path')
const chalk = require('chalk')
const ExtractTextWebpackPlugin = require('extract-text-webpack-plugin')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const ProgressBarPlugin = require('progress-bar-webpack-plugin')
const developmentConfig = require('./webpack.dev.conf')
const productionConfig = require('./webpack.prod.conf')
// 根據環境變數生成配置
const generateConfig = env =>{
const extractLess = new ExtractTextWebpackPlugin({
filename: 'css/[name]-bundle-[hash:5].css'
})
const scriptLoader = [
'babel-loader'
].concat(env === 'produc