前言:都2020年了,感覺是時候該學一波webpack了,趁著最近有時間,就學了一下,等把官網上的webpack結構和模塊大概看了一遍之後,就感覺可以開始搭個項目實戰一下了,從0開始,一步步記錄下來 使用版本: webpack4.x 1.包含插件和loader * html: html-webpac ...
前言:都2020年了,感覺是時候該學一波webpack了,趁著最近有時間,就學了一下,等把官網上的webpack結構和模塊大概看了一遍之後,就感覺可以開始搭個項目實戰一下了,從0開始,一步步記錄下來
使用版本: webpack4.x
1.包含插件和loader
* html:
html-webpack-plugin clean-webpack-plugin
* css:
style-loader css-loader sass-loader node-sass postcss-loader autoprefixer
* js:
babel-loader @babel/preset-env @babel/core @babel/polyfill core-js@2 @babel/plugin-transform-runtime
@babel/runtime @babel/runtime-corejs2
* vue:
vue-loader vue-template-compiler vue-style-loader vue vue-router axios vuex
* webpack:
file-loader url-loader webpack-dev-server webpack-merge copy-webpack-plugin happypack HardSourceWebpackPlugin
webpack-bundle-analyzer optimize-css-assets-webpack-plugin portfinder FriendlyErrorsPlugin
另外要註意 :光理論是不夠的。在此贈送2020最新企業級 Vue3.0/Js/ES6/TS/React/node等實戰視頻教程,想學的可進裙 519293536 免費獲取,小白勿進哦
2.webpack功能
-- 生成hmtl模板
-- 刪除上一次的dist文件
-- 自動添加瀏覽器首碼
-- 使用sass預編譯器
-- 轉換ES6,7,8,9語法為ES5
-- 大於10k文件打包到dist,小於10k轉換為base64
-- 相容vue-- 拷貝靜態文件
-- 熱更新
-- 區分當前環境
-- 多線程打包
-- 緩存未改變模塊
-- g-zip壓縮
-- 獲取本機ip
-- 打包大小分析
-- 壓縮css
-- 檢查埠是否存在複製代碼
3.實現步驟
1. 初體驗
1. 新建一個文件 取名為webpack-vue
2. cd webpack-vue npm init -y npm i -D webpack webpack-cli
3. 新建 src/main.js ,裡面隨便寫點 console.log('hello,webpack')
4. 修改 package.json - >scripts ,添加 "build":"webpack src/main.js"
5. 然後 npm run build 如果多了一個dist文件,那麼初次打包就成功了複製代碼
2. 配置
- 新建一個 build 文件夾,新建一個 webpack.config.js
- 寫入以下內容
const path=require('path')
module.exports = {
mode:'development',
entry:path.resolve(__dirname,'../src/main.js'), //入口文件
output:{
filename:'[name].[hash:8].js', //打包後的名字 生成8位數的hash
path.resolve(__dirname,'../dist') //打包的路徑
}
}
然後修改 package.json ->scripts,修改為: "build":"webpack --config build/webpack.config.js"
然後npm run build複製代碼
3. 我們生成了main.js之後,不可能每次都手動在index.html裡面引入,所以我們需要這個插件來幫我們自動引入
先安裝插件:
npm i -D html-webpack-plugin複製代碼
根目錄新建一個 public/index.html
修改webpack.config.js:
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin') //這裡引入插件
module.exports = {
mode:'development', // 開發模式
entry: path.resolve(__dirname,'../src/main.js'), // 入口文件
output: {
filename: '[name].[hash].js', // 打包後的文件名稱
path: path.resolve(__dirname,'../dist') // 打包後的目錄
},
//插件註入
plugins:[
new HtmlWebpackPlugin({
template:path.resolve(__dirname,'../public/index.html')
})
]
}複製代碼
然後npm run build 就會發現dist裡面多了index.html,並且已經自動幫我們引入了main.js
4. 由於hash每次生成的不同,導致每次打包都會將新的main.js打包到dist文件夾,所以我們需要一個插件來打包前刪除dist文件
先安裝插件:
npm i -D clean-webpack-plugin複製代碼
webpack.config.js
const {CleanWebpackPlugin} = require('clean-webpack-plugin') //引入
plugins:[
new HtmlWebpackPlugin({
template:path.resolve(__dirname,'../public/index.html')
}),
new CleanWebpackPlugin()
]
複製代碼
5.我們一般把不需要打包的靜態文件放在public裡面,這個地方不會被打包到dist,所以我們需要使用插件來把這些文件複製過去
先安裝插件:
npm i -D copy-webpack-plugin複製代碼
webpack.config.js
const CopyWebpackPlugin = require('copy-webpack-plugin') // 複製文件
plugins: [
new CopyWebpackPlugin({
patterns: [
{
from: path.resolve(__dirname, '../public'),
to: path.resolve(__dirname, '../dist')
}
]
})
]複製代碼
6. 為了讓webpack識別css,我們需要安裝loader,並將解析後的css插入到index.html裡面的style
先安裝:
npm i -D style-loader css-loader複製代碼
webpack.config.js
module.exports = {
module:{
rules:[{
test:/\.css$/,
use:['style-loader','css-loader'] // 從右向左解析原則
}]
}
}複製代碼
7.我們這裡可以使用預編譯器更好的處理css,我這裡使用的是sass
先安裝:
npm install -D sass-loader node-sass
webpack.config.js
module:{
rules: [{
test: /\.scss$/,
use: ['style-loader', 'css-loader', 'sass-loader'] // 從右向左解析原則
}]
}複製代碼
8. 自動添加瀏覽器首碼
先安裝:
npm i -D postcss-loader autoprefixer
webpakc.config.js
module: {
rules: [
{
test: /\.scss$/,
use: ['style-loader', 'css-loader', 'postcss-loader', 'sass-loader'] // 從右向左解析原則 sass-loader必須寫後面
}]
}
在根目錄新建 .browserslistrc
這個裡面配置相容的瀏覽器
這裡貼下我的預設配置,可以根據實際情況自己去配置
> 1%
last 2 versions
not ie <= 8
再新建一個postcss.config.js
module.exports = {
plugins: [require('autoprefixer')] // 引用該插件即可了
}
這樣打包後就自動幫你添加瀏覽器首碼了複製代碼
9.之前的style-loader只是把css打包到index.html裡面的style-loader裡面,如果css特別多這種辦法肯定不行,所以我們需要單獨抽離提取css
先安裝:
npm i -D mini-css-extract-plugin
webpack.config.js
const MiniCssExtractPlugin = require("mini-css-extract-plugin") //提取css
module: {
rules: [{
test: /\.scss$/,
use: [MiniCssExtractPlugin.loader, 'css-loader', 'postcss-loader', 'sass-loader'] // 從右向左解析原則
}]
}
plugins: [
new MiniCssExtractPlugin({
filename: "css/[name].[hash:8].css",
chunkFilename: "[id].css",
})
]
這樣打包後就將css打包到css文件下裡面了複製代碼
10. 我們為了減少圖片字體等打包的大小,我們可以使用url-loader將少於指定大小的文件轉換為base64,使用file-loader將大於指定大小的文件 移動到指定的位置
先安裝:
npm i -D file-loader url-loader
webpack.config.js
module:{
rules:[
{
test:/\.(jpe?g|png|gif|svg)(\?.*)?$/ //圖片
use:[{
loader:'url-loader',
options:{
limit:10240,
fallback:{
loader:'file-loader',
options:{
name:'img/[name].[hash:8].[ext]',
publicPath: '../' //為了防止圖片路徑錯誤
}
}
}
}]
},{
test: /\.(mp4|webm|ogg|mp3|wav|flac|aac)(\?.*)?$/,
use: [{
loader: 'url-loader',
options: {
limit: 10240,
fallback: {
loader: 'file-loader',
options: {
name: 'media/[name].[hash:8].[ext]',
publicPath: '../'
}
}
}
}]
},{
test:/\.(woff2?|eot|ttf|otf)(\?.*)$/,
loader:'url-loader',
options:{
limit:10240,
fallback:{
loader:'file-loader,
options:{
name:'fonts/[name].[hash:8].[ext]',
publicPath: '../'
}
}
}
}
]
}
webpack只會轉換和移動項目中使用了的圖片
打包後發現圖片字體等也打包進去了複製代碼
11.為了相容瀏覽器,我們需要將ES6,7,8,9轉換為Es5
先安裝:
npm install -D babel-loader @babel/preset-env @babel/core複製代碼
webpack.config.js
module: {
rules: [{
test: /\.js$/,
use: ['babel-loader']
}]
}
根目錄新建 .babelrc
{
"presets": [
"@babel/preset-env"
]
}
一些新的api如Promise,set,Maps等還不支持轉換,所以我們需要另一個插件babel/polyfill,但是這個插件會將所有的poly都打包到
mian.js裡面,所以我們需要另一個插件 core-js@2 來按需載入
`npm i -s @babel/polyfill core-js@2`
修改.babelrc
{
"presets": [
[
"@babel/preset-env",
{
"modules": false,
"useBuiltIns": "usage",
"corejs": 2,
"targets": {
"browsers": [
"last 2 versions",
"ie >= 10"
]
}
}
]
]
}
還有一個問題,會影響全局依賴,所以我們需要另一個插件來解決這個問題
npm i @babel/plugin-transform-runtime -D
npm i --save @babel/runtime
npm i --save @babel/runtime-corejs2
修改.babelrc
{
"presets": [
"@babel/preset-env"
],
"plugins": [
[
"@babel/plugin-transform-runtime",
{
"helpers": true,
"regenerator": true,
"useESModules": false,
"corejs": 2
}
]
]
}
這樣就完美解決ES6新api的相容問題了複製代碼
12.相容vue
先安裝:
npm i -D vue-loader vue-template-compiler vue-style-loader
npm i -S vue 複製代碼
webpack.config.js
const vueLoaderPlugin=require('vue-loader/lib/plugin')
function resolve(dir) {
return path.join(__dirname, '..', dir)
}
module:{
rules:[{
test:/\.vue$/,
use:[vue-loader]
}]
},
resolve:{
alias:{
'vue$':'vue/dist/vue.runtime.esm.js',
'@':path.resolve(__dirname,'../src')
},
extensions: ['.js', '.vue', '.json'],
},
plugins:[
new vueLoaderPlugin()
]
這樣配置完成之後就可以webpack就識別了vue文件複製代碼
13.熱更新
先安裝:
npm i -D webpack-dev-server複製代碼
wepack.config.js
const webpack = require('webpack')
devServer: {
compress: true,
port: 8989,
hot: true,
inline: true,
hotOnly: true, //當編譯失敗時,不刷新頁面
overlay: true, //用來在編譯出錯的時候,在瀏覽器頁面上顯示錯誤
publicPath: '/', //一定要加
open: true,
watchOptions: {
// 不監聽的文件或文件夾,支持正則匹配
ignored: /node_modules/,
// 監聽到變化後等1s再去執行動作
aggregateTimeout: 1000,
// 預設每秒詢問1000次
poll: 1000
}
},
plugins: [
new webpack.HotModuleReplacementPlugin(),
new webpack.NamedModulesPlugin(),
new webpack.NoEmitOnErrorsPlugin()
]
在package.json裡面配置: "dev":"webpack-dev-server --config build/webpack.config.js"
在main.js裡面
import Vue from "vue"
import App from "./App"
new Vue({
render:h=>h(App)
}).$mount('#app')
src文件夾內新建一個APP.vue,內容自定義
然後npm run dev 頁面就運行起來了複製代碼
14.區分開發環境和生產環境
build文件夾內新建 webpack.dev.js webpack.prod.js複製代碼
開發環境:
1.不需要壓縮代碼
2.熱更新
3.完整的soureMap
...
生產環境:
1.壓縮代碼
2.提取css文件
3.去除soureMap (根據個人需要)
...複製代碼
我們這裡需要安裝 webpack-merge 來合併配置項
先安裝:
npm i -D webpack-merge 複製代碼
webpack.dev.js
const webpackConfig = require('./webpack.config')
const merge = require('webpack-merge')
const webpack = require('webpack')
module.exports = merge(webpackConfig, {
mode: 'development',
devtool: 'cheap-module-eval-source-map',
devServer: {
compress: true,
port: 8989,
hot: true,
inline: true,
hotOnly: true, //當編譯失敗時,不刷新頁面
overlay: true, //用來在編譯出錯的時候,在瀏覽器頁面上顯示錯誤
publicPath: '/', //一定要加
open: true,
watchOptions: {
// 不監聽的文件或文件夾,支持正則匹配
ignored: /node_modules/,
// 監聽到變化後等1s再去執行動作
aggregateTimeout: 1000,
// 預設每秒詢問1000次
poll: 1000
}
},
module: {
rules: [
{
test: /\.js$/,
use: ['babel-loader']
},
{
test: /\.css$/,
use: ['vue-style-loader', 'css-loader', 'postcss-loader'],
},
{
test: /\.scss$/,
use: ['vue-style-loader', 'css-loader', 'postcss-loader', 'sass-loader'],
exclude: /node_modules/
}
]
},
plugins: [
new webpack.HotModuleReplacementPlugin(),
new webpack.NamedModulesPlugin(),
new webpack.NoEmitOnErrorsPlugin()
]
})複製代碼
webpack.prod.js
const webpackConfig = require('./webpack.config')
const merge = require('webpack-merge')
const path = require('path')
const { CleanWebpackPlugin } = require('clean-webpack-plugin') //清除dist
const MiniCssExtractPlugin = require("mini-css-extract-plugin") //提取css
function resolve(dir) {
return path.join(__dirname, '..', dir)
}
module.exports = merge(webpackConfig, {
mode: "production",
devtool: 'none',
optimization: {
splitChunks: {
chunks: 'all',
cacheGroups: {
vendors: {
name: 'vendors',
test: /[\\\/]node_modules[\\\/]/,
priority: -10,
chunks: 'initial'
},
common: {
name: 'chunk-common',
minChunks: 2,
priority: -20,
chunks: 'initial',
reuseExistingChunk: true
}
}
}
},
module: {
rules: [
{
test: /\.js$/,
use: ['babel-loader']
exclude: /node_modules/,
include: [resolve('src'), resolve('node_modules/webpack-dev-server/client')]
},
{
test: /\.css$/,
use: [
{
loader: MiniCssExtractPlugin.loader,
options: {
publicPath: '../',
}
}, 'css-loader', 'postcss-loader'],
},
{
test: /\.scss$/,
use: [
{
loader: MiniCssExtractPlugin.loader,
options: {
publicPath: '../',
}
}, 'css-loader', 'postcss-loader', 'sass-loader'],
exclude: /node_modules/
}
]
},
plugins: [
new CleanWebpackPlugin(),
new MiniCssExtractPlugin({
filename: 'css/[name].[hash].css',
chunkFilename: 'css/[name].[hash].css',
})
]
})
複製代碼
webpack.config.js
const path = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin') //這裡引入插件
const vueLoaderPlugin = require('vue-loader/lib/plugin')
const CopyWebpackPlugin = require('copy-webpack-plugin') // 複製文件
function resolve(dir) {
return path.join(__dirname, '..', dir)
}
module.exports = {
mode: 'development',
entry: path.resolve(__dirname, '../src/main.js'),
output: {
filename: 'js/[name].[hash:8].js',
path: path.resolve(__dirname, '../dist'),
chunkFilename: 'js/[name].[hash:8].js', //非同步載入模塊
publicPath: './'
},
externals: {},
module: {
noParse: /jquery/,
rules: [
{
test: /\.vue$/,
use: [{
loader: 'vue-loader',
options: {
compilerOptions: {
preserveWhitespace: false
}
}
}]
},
{
test: /\.(jpe?g|png|gif)$/i, //圖片文件
use: [
{
loader: 'url-loader',
options: {
limit: 10240,
fallback: {
loader: 'file-loader',
options: {
name: 'img/[name].[hash:8].[ext]',
publicPath: '../'
}
}
}
}
]
},
{
test: /\.(mp4|webm|ogg|mp3|wav|flac|aac)(\?.*)?$/, //媒體文件
use: [
{
loader: 'url-loader',
options: {
limit: 10240,
fallback: {
loader: 'file-loader',
options: {
name: 'media/[name].[hash:8].[ext]',
publicPath: '../'
}
}
}
}
]
},
{
test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/i, // 字體
use: [
{
loader: 'url-loader',
options: {
limit: 10240,
fallback: {
loader: 'file-loader',
options: {
name: 'font/[name].[hash:8].[ext]',
publicPath: '../'
}
}
}
}
]
}
]
},
resolve: {
alias: {
'vue$': 'vue/dist/vue.esm.js',
'@': resolve('src'),
},
extensions: ['.js', '.vue', '.json'],
},
//插件註入
plugins: [
new HtmlWebpackPlugin({
template: path.resolve(__dirname, '../public/index.html')
}),
new vueLoaderPlugin(),
new CopyWebpackPlugin({
patterns: [
{
from: path.resolve(__dirname, '../public'),
to: path.resolve(__dirname, '../dist')
}
]
})
]
}複製代碼
3.webpack配置優化
1.設置mode
預設為production,webpack4.x預設會壓縮代碼和去除無用的代碼
可選參數:production, development
ps:之前我認為只需要設置mode為production,就不用使用壓縮css和js的插件,但結果發現我錯了,仔細比較了下,還是要安裝的
先安裝打包css的:
npm i -D optimize-css-assets-webpack-plugin複製代碼
webpack.prod.js
const optimizeCss = require('optimize-css-assets-webpack-plugin');
plugins:[
new optimizeCss({
cssProcessor: require('cssnano'), //引入cssnano配置壓縮選項
cssProcessorOptions: {
discardComments: { removeAll: true }
},
canPrint: true //是否將插件信息列印到控制台
})
]
複製代碼
壓縮js和js打包多線程的暫時沒有添加,在網上搜有的說不用添加,有的說還是要安裝插件,等實際項目中我用完之後再來添加
2.縮小搜索範圍
alias 可以告訴webpack去指定文件夾去尋找,儘量多使用
include exclude 包括和過濾
noParse 當我們代碼中使用到import jq from 'jquery'時,webpack會去解析jq這個庫是否有依賴其他的包。這個可以告訴webpack不必解析
extensions 使用頻率高的寫在前面
複製代碼
3.單線程轉多線程
webpack處理文本,圖片,css的時候,由於js單線程的特性,只能一個一個文件的處理,HappyPack可以將這個任務 分解到多個子線程裡面,子線程完畢後會把結果發送到主線程,從而加快打包速度
先安裝:
npm i -D happypack
webpack.prod.js
const HappyPack = require('happypack') //單進程轉多進程
const os = require('os')
const happyThreadPool = HappyPack.ThreadPool({ size: os.cpus().length })
module: {
rules: [{
test: /\.js$/,
use: ['happypack/loader?id=happyBabel'],
exclude: /node_modules/,
include: [resolve('src'), resolve('node_modules/webpack-dev-server/client')]
}]
}
plugins:[
new HappyPack({
id: 'happyBabel',
loaders: ['babel-loader?cacheDirectory'],
threadPool: happyThreadPool
})
]
複製代碼
4.第三方模塊優化
將不怎麼改變的第三方依賴,我們可以用DllPlugin DllReferencePlugin將它從依賴中抽離出來,這樣每一次打包就不用打包這些文件,加快了打包的速度;
但是webpack4.x的性能已經很好了,參考vue-cli也沒有使用dll抽離,所以我們這裡也不使用了,這裡我們使用 另一個插件:hard-source-webpack-plugin ,這個插件會去對比修改了哪些配置,只去打包修改過了的配置 第一次打包速度正常,第二次打包速度能提升 50%+
npm i -D hard-source-webpack-plugin
webpack.prod.js
const HardSourceWebpackPlugin = require('hard-source-webpack-plugin') //緩存第三方模塊
plugins: [
new HardSourceWebpackPlugin()
]
複製代碼
5.externals
通過cdn載入的依賴,可以在這裡設置,就不會通過webpack編譯
6.g-zip壓縮
g-zip壓縮可以將已經壓縮過的js,css再次壓縮一遍,減少了打包大小,需要nginx配置
npm i -D compression-webpack-plugin
webpack.prod.js
const CompressionWebpackPlugin = require('compression-webpack-plugin')
const productionGzipExtensions = ["js", "css"];
plugins:[
new CompressionWebpackPlugin({
filename: '[path].gz[query]',
algorithm: 'gzip',
test: new RegExp("\\.(" + productionGzipExtensions.join("|") + ")$"),
threshold: 10240, // 只有大小大於10k的資源會被處理
minRatio: 0.6 // 壓縮比例,值為0 ~ 1
})
]
複製代碼
7.自動獲取本地Ip,通過本地ip地址啟動項目
webpack.dev.js
const os = require('os')
devServer:{
host:()=>{
var netWork = os.networkInterfaces()
var ip = ''
for (var dev in netWork) {
netWork[dev].forEach(function (details) {
if (ip === '' && details.family === 'IPv4' && !details.internal) {
ip = details.address
return;
}
})
}
return ip || 'localhost'
}
}
複製代碼
8.抽離一些公共配置
根目錄新建vue.config.js 裡面配置一些公共的配置如:
const os = require('os')
module.exports = {
dev: {
host: getNetworkIp(), //埠號
port: 8999,
autoOpen: true, //自動打開
},
build: {
productionGzipExtensions: ["js", "css"], //需要開啟g-zip的文件尾碼
productionGzip: false //是否開啟g-zip壓縮
}
}
//獲取本地ip地址
function getNetworkIp() {
var netWork = os.networkInterfaces()
var ip = ''
for (var dev in netWork) {
netWork[dev].forEach(function (details) {
if (ip === '' && details.family === 'IPv4' && !details.internal) {
ip = details.address
return;
}
})
}
return ip || 'localhost'
}
然後webpack.dev.js webpack.prod.js引入這個文件,獲取其中的配置就好了
複製代碼
9.打包大小分析
先安裝:
npm i -D webpack-bundle-analyzer
webpack.prod.js
if (process.env.npm_config_report) {
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin
prodConfig.plugins.push(
new BundleAnalyzerPlugin()
)
}
然後 npm run build --report就會彈出一個頁面,裡面就是打包大小分析
複製代碼
10.完整的vue項目(vue-router axios vuex等)
先安裝:
npm i -S vue-router axios vuex
然後在src裡面新建 -> router文件夾 ->新建index.js
index.js
import Vue from "vue"
import Router from "vue-router"
Vue.use(Router)
export default new Router({
mode: 'hash',
routes: [
{
path: '/',
name: 'home',
component: () => import(/* webpackChunkName: "home" */ "@/views/home"),
},
]
})
main.js
import router from "./router"
new Vue({
router,
render: h => h(App)
}).$mount('#app')
新建views -> Home.vue
隨便寫點東西,然後npm run dev 這樣就完成了一個路由了
複製代碼
到這裡webpack搭建vue項目就搭建完成,還有沒懂的嗎?光理論是不夠的。在此贈送2020最新企業級 Vue3.0/Js/ES6/TS/React/node等實戰視頻教程,想學的可進裙 519293536 免費獲取,小白勿進哦!
本文的文字及圖片來源於網路加上自己的想法,僅供學習、交流使用,不具有任何商業用途,版權歸原作者所有,如有問題請及時聯繫我們以作處理