一、準備
nodejs安裝教程:https://blog.csdn.net/FED_AF/article/details/105747632)
二、安裝
(1)全局安裝
:: 初始化npm
C:\Users\Administrator> npm init -y
package name: (administrator) webpack_test
::後面全部回車默認
::全局安裝(webpack版本4.43.0,webpack-cli版本3.3.11)
C:\Users\Administrator> npm i webpack webpack-cli -g
::提供sw代碼運行的服務器環境
C:\Users\Administrator> npm i serve -g
(2)項目安裝
:: 初始化npm
項目根目錄> npm init -y
package name: (webpack_test) 名稱自己取,只要不和全局的一樣就行
::後面全部回車默認
::安裝webpack和webpack-cli
項目根目錄> npm i webpack webpack-cli -D
(3)模塊和插件安裝
::安裝webpack-dev-server
項目根目錄> npm i webpack-dev-server -D
::用來打包css
項目根目錄> npm i css-loader style-loader -D
::用來打包sass(這裏是需要裝css-loader和style-loader的,但前面已經裝過就不再裝)
項目根目錄> npm i sass sass-loader -D
::用來打包html
項目根目錄> npm i html-webpack-plugin -D
::用來打包css引入的圖片
項目根目錄> npm i url-loader file-loader -D
::用來打包html的img元素引入的圖片
項目根目錄> npm i html-loader -D
::用來提取css成單獨文件
項目根目錄> npm i mini-css-extract-plugin -D
::用來處理css兼容性
項目根目錄> npm i postcss-loader postcss-preset-env -D
::用來壓縮css代碼
項目根目錄> npm i optimize-css-assets-webpack-plugin -D
::用來檢查規範js代碼(airbnb風格)
項目根目錄> npm i eslint-loader eslint eslint-config-airbnb-base eslint-plugin-import -D
::用來處理js代碼的兼容性
項目根目錄> npm i babel-loader @babel/preset-env @babel/core @babel/polyfill core-js -D
::用來實現PWA技術
項目根目錄> npm i workbox-webpack-plugin serve -D
::用來多進程打包
項目根目錄> npm i thread-loader -D
::用來配合dll使用,功能是將某個文件打包輸出去,並在html中自動引入
項目根目錄> npm i add-asset-html-webpack-plugin -D
::優化生產環境代碼壓縮
項目根目錄> npm i terser-webpack-plugin -D
(4)第三方庫安裝
::jquery
項目根目錄> npm i jquery -D
三、結構
項目根目錄
源代碼目錄src
構建後代碼目錄build
webpack.config.js
四、配置
-
項目根目錄下的webpack.dll.js(初始化動態鏈接庫):
/*
使用dll技術,對某些庫(第三方庫:jquery、vue)進行單獨打包
*/
const {resolve} = require('path');
const webpack = require('webpack');
module.exports = {
entry: {
// 最終打包生成的[name]
// ['jquery']要打包的庫是jquery
jquery: ['jquery']
},
output: {
filename: '[name].js',
path: resolve(__dirname, 'dll'),
// 打包的庫向外暴露出去的內容叫什麼名字
library: '[name]_[hash]',
},
plugins: [
// 打包生成一個manifest.json,提供和jquery的映射關係
new webpack.DllPlugin({
// 映射庫的暴露的內容名稱
name: '[name]_[hash]',
// 輸出文件路徑
path: resolve(__dirname, 'dll/manifest.json')
})
],
mode: 'production'
};
-
項目根目錄下的webpack.dev.js(開發環境):
// resolve用來拼接絕對路徑的方法
const { resolve } = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const webpack = require('webpack');
const AddAssetHtmlWebpackPlugin = require('add-asset-html-webpack-plugin');
/*
樣式文件可以使用HMR功能,因爲style-loader內部實現了
js文件默認不能使用HMR功能
解決:需要修改js代碼,添加支持HMR功能的代碼,只能處理非入口js文件的其他文件
html文件默認不能使用HMR功能(html不存在引用問題,沒有優化的空間),同時會導致修改html文件後頁面刷新無果
解決:修改entry入口,將html文件引入
*/
// 設置node環境變量
process.env.NODE_ENV = 'development';
module.exports = { // webpack配置
// 入口起點
entry: ['./src/index.js', './src/index.html'],
// 輸出
output: {
// 輸出文件名
filename: 'built.js',
// 輸出路徑
// __dirname是nodejs的變量,代表當前文件的目錄絕對路徑
path: resolve(__dirname, 'build')
},
// loader的配置
module: {
rules: [
// 詳細loader配置
oneOf: [
{
// 匹配哪些文件
test: /\.css$/,
// 使用哪些loader進行處理,多個loader用use數組
use: [
// use數組中loader的執行順序:從右到左,從下到上,依次執行
// 創建style標籤,將js中的樣式資源插入進去,添加到head中生效
'style-loader',
// 將css文件變成commonjs模塊加載到js中,裏面的內容是樣式字符串
'css-loader'
]
},
{
test: /\.sass$/,
use: [
'style-loader',
'css-loader',
// 將sass文件編譯成css文件
'sass-loader',
'sass'
]
},
{
// 處理圖片資源
test: /\.(jpg|png|gif)$/,
// 使用一個loader,直接用loader
// 需要下載url-loader和file-loader
loader: 'url-loader',
options: {
// 當發現圖片大小小於8kb,就會被base64處理,轉化爲字符串
// 優點:能夠減少請求數量,減輕服務器壓力
// 缺點:圖片體積會更大,文件請求速度更慢
limit: 8 * 1024,
// 問題:因爲url-loader默認使用es6模塊化解析,而html-loader引入圖片是commonjs
// 解析時會出現問題:[object Module]
// 解決:關閉url-loader的es6模塊化,使用commonjs解析
esModule: false
}
},
{
// 處理圖片資源
test: /\.html$/,
// 處理html文件的img圖片(負責引入img,從而能被url-loader處理)
// 需要下載html-loader
loader: 'html-loader'
},
{
// 處理其他資源
exclude: /\.(css|js|html|jpg|png|gif|sass|json)$/,
loader: 'file-loader',
options: {
// 限制輸出的文件名哈希值長度爲10位,在加上本來的擴展名
name: '[hash:10].[ext]'
}
}
]
]
},
// plugins的配置
plugins: [
// 詳細plugins的配置
// html-webpack-plugin
// 功能:默認會創建一個空的HTML,自動引入打包輸出的所有資源(JS/CSS)
// 需求:需要有結構的HTML文件
new HtmlWebpackPlugin({
// 將 './src/icons/icon.ico'文件作爲網頁標籤的圖標引入
// favicon: './src/icons/icon.ico',
// 複製 './src/index.html'文件,並自動引入打包輸出的所有資源
// 多個html需要多個HtmlWebpackPlugin,通過filename指定構建後的html路徑和文件名
template: './src/index.html'
}),
new HtmlWebpackPlugin({
template: './src/inde.html',
filename: './build/c/inde.html'
}),
// 告訴webpack哪些庫不參與打包,同時使用時的名稱也得改
new webpack.DllReferencePlugin({
manifest: resolve(__dirname, 'dll/manifest.json')
}),
// 將某個文件打包輸出去,並在html中自動引入
new AddAssetHtmlWebpackPlugin({
filepath: resolve(__dirname, 'dll/jquery.js')
})
],
// 模式
// mode: 'production'
mode: 'development',
// 開發服務器devServer:用來自動化(自動編譯,自動打開瀏覽器,自動刷新瀏覽器)
// 特點:只會在內存中編譯打包,不會有任何輸出
devServer: {
contentBase: resolve(__dirname, 'build'),
// 啓動gzip壓縮
compress: true,
// 自動打開瀏覽器
// open: true,
// 按如下配置即可在其他終端訪問
host: '0.0.0.0',
// 開啓熱模塊替換(HMR功能),省去不必要刷新
hot: true,
// 端口號
port: 3000
},
devtool: "eval-source-map"
};
/*
source-map是一種提供源代碼到構建後代碼映射的技術。用法如下:
[inline-|hidden-|eval-][nosources-][cheap-[module-]]source-map
source-map:外部
會提示錯誤代碼準確信息和源代碼的錯誤位置
inline-source-map:內聯,將source-map嵌入到入口js中,構建速度更快
會提示錯誤代碼準確信息和源代碼的錯誤位置
hidden-source-map:外部,將source-map輸出到一個map文件中
會提示錯誤代碼錯誤原因但是沒有錯誤位置,而且只能提示構建後代碼的錯誤位置
eval-source-map:內聯,每一個文件都生成對應的source-map
會提示錯誤代碼準確信息和源代碼的錯誤位置(只多了一個js文件的hash值)
nosources-source-map:外部
會提示錯誤代碼準確信息,但是沒有任何源代碼信息
cheap-source-map:外部
會提示錯誤代碼準確信息,但只能定位到行
cheap-module-source-map:外部
會提示錯誤代碼準確信息,但只能定位到行
速度快:eval-cheap-source-map>eval-source-map>inline-source-map>cheap-source-map>...
調試友好:source-map>cheap-module-source-map>cheap-source-map>...
開發環境通常選擇eval-source-map或eval-cheap-module-source-map
生產環境通常選擇source-map或cheap-module-source-map
*/
-
項目根目錄下的webpack.pro.js(生產環境):
const { resolve } = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const OptimizeCssAssetsWebpackPlugin = require('optimize-css-assets-webpack-plugin');
const WorkboxWebpackPlugin = require('workbox-webpack-plugin');
const webpack = require('webpack');
const AddAssetHtmlWebpackPlugin = require('add-asset-html-webpack-plugin');
/*
緩存:
babel緩存:讓第二次打包構建速度更快
文件資源緩存:讓代碼上線運行更好使用。
每次webpack構建時會生成一個唯一的hash值加入到文件名中,以此保證當文件修改時刷新緩存
使用contenthash保證只對更改過的文件重新生成hash值
*/
/*
tree shaking: 去除無用代碼
前提: 1.必須使用ES6模塊化 2.開啓production環境
作用:減少代碼體積
在packaage.json中配置"sideEffects": ["*.css"]表示除css文件外所有代碼都進行tree shaking
*/
/*
code split: 代碼分割
將較大的js文件分割成多個較小的js文件,從而實現並行加載,速度更快
*/
/*
PWA:漸進式網絡開發應用程序(離線可訪問)
通過workbox實現PWA
*/
// 複用loader
const commonCssLoader = [
// 提取js中的css成單獨文件
MiniCssExtractPlugin.loader,
'css-loader',
// css兼容性處理:postcss --> postcss-loader postcss-preset-env
// 幫postcss找到package.json中browserslist裏面的配置,通過配置加載指定的css兼容性樣式
/* "browserslist": {
// 開發環境 --> 設置node環境變量:process.env.NODE_ENV = development
"development": [
"last 1 chrome version",//兼容谷歌瀏覽器最近的一個版本
"last 1 firefox version",
"last 1 safari version"
],
// 生產環境:默認是生產環境
"production": [
">0.2%",//考慮99.8%的瀏覽器的兼容性
"not dead",//不考慮不再使用的瀏覽器的兼容性
"not op_mini all"//不考慮op_mini瀏覽器的全部版本的兼容性
]
}*/
// 使用loader的默認配置
// 'postcss-loader',
// 修改loader的默認配置
{
loader: 'postcss-loader',
options: {
ident: 'postcss',
plugins: () => [
// postcss的插件
require('postcss-preset-env')()
]
}
}
];
// 設置node環境變量
// process.env.NODE_ENV = 'development';
module.exports = {
entry: './src/index.js',
output: {
filename: 'built.[contenthash:10].js',
path: resolve(__dirname, 'build')
},
module: {
rules: [
/*{
// 用eslint做js語法檢查
// 只檢查自己寫的源代碼,第三方的庫是不用檢查的
// 在package.json中的eslintConfig設置檢查規則:
// 使用airbnb風格
test: /\.js$/,
// 排除node模塊
exclude: /node_modules/,
// 優先執行
enforce: 'pre',
loader: 'eslint-loader',
options: {
// 自動規範源js的語法
fix: true
}
},*/
{
// 以下loader只會匹配一個,造成衝突的可以放到oneOf外面
oneOf: [
{
test: /\.css$/,
use: [...commonCssLoader]
},
{
test: /\.sass$/,
use: [...commonCssLoader, 'sass-loader', 'sass']
},
{
test: /\.(jpg|png|gif)$/,
loader: 'url-loader',
options: {
limit: 8 * 1024,
name: '[hash:10].[ext]',
outputPage: 'images',
esModule: false
}
},
{
test: /\.html$/,
loader: 'html-loader'
},
{
exclude: /\.(css|js|html|jpg|png|gif|sass|json|ico)$/,
loader: 'file-loader',
options: {
outputPage: 'media',
name: '[hash:10].[ext]'
}
},
{
// 用babel做js兼容性處理
test: /\.js$/,
exclude: /node_modules/,
use: [
// 開啓多進程打包。
// 進程啓動時間大概爲600ms,進程通信也有開銷。
// 只有工作消耗時間比較長,才需要多進程打包
{
loader: 'thread-loader',
options: {
workers: 2 // 進程2個
}
},
{
loader: 'babel-loader',
options: {
// 預設:指示babel做怎麼樣的兼容性處理
// @babel/preset-env:基本兼容性處理:只能轉換基本語法,不能轉換promise等
// @babel/polyfill:全部兼容性處理(在入口js文件中引入即可:import '@babel/polyfill';):所有兼容性代碼全部引入,代碼體積太大
// core-js:按需處理兼容性
presets: [
[
'@babel/preset-env',
{
// 按需加載
useBuiltIns: 'usage',
corejs: {
// 指定core-js的版本
version: 3
},
// 指定兼容性做到哪個版本瀏覽器
targets: {
chrome: '60',
firefox: '60',
ie: '8',
safari: '10',
edge: '17'
}
}
]
],
// 開啓babel緩存
// 第二次構建時,會讀取之前的緩存
cacheDirectory: true
}
}
]
}
]
}
]
},
plugins: [
new HtmlWebpackPlugin({
// favicon: './src/icons/icon.ico',
template: './src/index.html',
// 壓縮html代碼
minify: {
// 移除空格
collapseWhitespace: true,
// 移除註釋
removeComments: true
}
}),
new MiniCssExtractPlugin({
// 對輸出的文件重命名
filename: 'css/main.[contenthash:10].css'
}),
// 壓縮css
new OptimizeCssAssetsWebpackPlugin(),
// 壓縮css
new WorkboxWebpackPlugin.GenerateSW({
// 幫助serviceworker快速啓動
// 刪除舊的serviceworker
// 生成一個serviceworker配置文件
clientsClaim: true,
skipWaiting: true,
}),
// 告訴webpack哪些庫不參與打包,同時使用時的名稱也得改
new webpack.DllReferencePlugin({
manifest: resolve(__dirname, 'dll/manifest.json')
}),
// 將某個文件打包輸出去,並在html中自動引入
new AddAssetHtmlWebpackPlugin({
filepath: resolve(__dirname, 'dll/jquery.js')
})
],
// 對於單頁面應用,可以將node_module中代碼單獨打包一個chunk最終輸出
// 對於多頁面應用,可以自動分析多入口trunk中有沒有公共的文件。如果有會打包成單獨一個chunk
optimization: {
splitChunks: {
chunks: 'all'
}
},
mode: 'production',
// 下面這一行不是註釋,是忽略eslint報的no-console警告
// eslint-disable-next-line
// mode: 'development',
devtool: "source-map",
externals: {
// 忽略庫名 -- npm包名
jquery: 'jQuery'
}
};
-
項目根目錄下的package.json:
{
"name": "webpack_code",
"version": "1.0.0",
"main": "webpack.config.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"license": "ISC",
"description": "",
"devDependencies": {
"@babel/core": "^7.9.6",
"@babel/polyfill": "^7.8.7",
"@babel/preset-env": "^7.9.6",
"add-asset-html-webpack-plugin": "^3.1.3",
"babel-loader": "^8.1.0",
"core-js": "^3.6.5",
"css-loader": "^3.5.3",
"eslint": "^7.0.0",
"eslint-config-airbnb-base": "^14.1.0",
"eslint-loader": "^4.0.2",
"eslint-plugin-import": "^2.20.2",
"file-loader": "^6.0.0",
"html-loader": "^1.1.0",
"html-webpack-plugin": "^4.3.0",
"jquery": "^3.5.1",
"mini-css-extract-plugin": "^0.9.0",
"optimize-css-assets-webpack-plugin": "^5.0.3",
"postcss-loader": "^3.0.0",
"postcss-preset-env": "^6.7.0",
"sass": "^1.26.5",
"sass-loader": "^8.0.2",
"serve": "^11.3.0",
"style-loader": "^1.2.1",
"thread-loader": "^2.1.3",
"url-loader": "^4.1.0",
"webpack": "^4.43.0",
"webpack-cli": "^3.3.11",
"webpack-dev-server": "^3.11.0",
"workbox-webpack-plugin": "^5.1.3"
},
"browserslist": {
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
],
"production": [
">0.2%",
"not dead",
"not op_mini all"
]
},
"eslintConfig": {
"extends": "airbnb-base",
"env": {
"browser": true
}
},
"sideEffects": [
"*.css",
"*.sass"
]
}
-
項目根目錄下的server.js:
/*
服務器代碼
啓動服務器指令:node server.js
*/
const express = require('express');
const app = express();
// 緩存保留時長
app.use(express.static('build', {maxAge: 1000 * 3600}));
// 監聽3000端口
app.listen(3000);
-
項目入口js:
...
import './print.js';
...
//熱模塊替換
if (module.hot) { // 用於開發環境
// 一旦module.hot爲true,說明開啓了HMR功能
/*
1、accept方法會監聽print.js的變化,一旦發生變化,默認不會重新打包構建其他js,而是會執行後面的回調函數
2、每引入一個(自己寫的)js,就要在if裏面加一個用來監聽的accept函數
*/
module.hot.accept('./print.js', function() {})
}
//代碼分割、懶加載、預加載
用於生產環境,通過import動態導入語法能將某個文件單獨打包成一個chunk
//通過/* webpackChunkName: 'test' */將輸出的chunk設置爲固定的名字
// 正常加載是並行加載(同時加載多個文件,文件越多加載速度越慢)
// 懶加載:當文件需要使用時才加載
// 預加載 prefetch:等其他資源加載完畢,瀏覽器空閒了,再偷偷加載資源
// 懶加載或預加載都建立在代碼分割的基礎上
// 但是下面的語法eslint會報錯,提示你import語句只能放在頂部,解決的方法,要麼打包時關掉eslint,要麼使用webpack.pro.js中的optimization實現按需加載
import(/* webpackChunkName: 'test', webpackPrefetch: true */'./test').then(({ mul }) => {
console.log(mul(4, 5));
});
/*
eslint不認識window、navigator全局變量
解決:需要修改package.json中eslintConfig配置
"env": {
"browser": true // 支持瀏覽器端全局變量
}
sw代碼必須運行在服務器上
npm i serve -g
serve -s build 啓動服務器,將build目錄下所有資源作爲靜態資源暴露出去
sw只能用https訪問,本地除外
*/
// 註冊serviceWorker
// 處理兼容性問題
if ('serviceWorker' in navigator) {
window.addEventListener('load', () => {
navigator.serviceWorker.register('/service-worker.js').then(() => {
console.log('註冊成功');
}).catch(() => {
console.log('註冊失敗');
});
});
}
五、性能優化
1、開發環境性能優化
側重:
(1)優化打包構建速度
- HMR
- oneOf
(2)優化代碼調試
- source-map
2、生產環境性能優化
側重:
(1)優化打包構建速度
- oneOf
- babel緩存
- 多進程打包
(2)優化代碼運行的性能
- 文件資源緩存(hash-chunkhash-contenthash)
- tree shaking
- code split
- 懶加載/預加載
- pwa
- externals
- dll
六、使用
-
手動打包
進入項目根目錄,執行webpack命令,默認會將webpack.config.js作爲配置文件,開發環境中,可以將webpack.dev,js複製爲webpack.config.js。生產環境同理,並不是不能直接指定或修改配置文件,只是這樣來得快。對於某些第三方庫,可以先使用webpack.dll.js打包,以後每次打包再用webpack.pro.js
-
開發環境實時監聽打包
devServer配置:見webpack.dev.js
進入項目根目錄,執行npx webpack-dev-server命令
-
生產環境監聽
服務器配置:見server.js
進入項目根目錄,執行node server.js命令(只是創建了服務器,並不能自動打包和刷新頁面)
運行servicework的服務器啓動命令:serve -s build