
- 使用最新版4.31版本
webpack
-
用了會上瘾,它也是突破你技術瓶頸的好方向,現在基本上任何東西都離不開webpack
,webpack
用得好,什麼webpack
随便上手(本人體會很深),本人參考了next nuxt
腳手架,京東的Vue
優化方案,以及本人的其他方面優化,着重在webpack
下的建構速度優化提升非常明顯(當然開發環境下也是~),性能提升很明顯生産模式
-
- 本配置完成功能:
- 識别
檔案和.Vue
template模闆
-
搖樹優化 删除掉無用代碼tree shaking
- 引入
并且按需加載,識别一切代碼babel polifill
- 識别
和 箭頭函數async / await
-
功能,熱重新整理,安裝後立即接管浏覽器 離線後仍讓可以通路網站 還可以在手機上添加網站到桌面使用PWA
-
預加載資源preload
按需請求資源 ,這裡除了prefetch
預解析外,建議其他的使用按需加載元件,順便代碼分割,這也是京東的優化方案dns
- 配置
,攔截非預期請求(京東的方案)nginx
-
子產品化,不怕命名沖突CSS
- 小圖檔的
處理base64
- 檔案字尾省掉j
等sx js json
- 實作
路由懶加載,按需加載 , 代碼分割 指定多個路由同個VueRouter
并且打包到同個chunkName
中 實作代碼精确分割chunk
- 支援
等預處理less sass stylus
-
優化首屏加載時間 不讓一個檔案體積過大code spliting
- 提取公共代碼,打包成一個chunk
- 每個chunk有對應的
,每個檔案有對應的chunkhash
,友善浏覽器差別緩存contenthash
- 圖檔壓縮
-
壓縮CSS
- 增加
字首 相容各種浏覽器CSS
- 對于各種不同檔案打包輸出指定檔案夾下
- 緩存babel的編譯結果,加快編譯速度
- 每個入口檔案,對應一個chunk,打包出來後對應一個檔案 也是
code spliting
- 删除HTML檔案的注釋等無用内容
- 每次編譯删除舊的打包代碼
- 将
檔案單獨抽取出來CSS
- 讓babel不僅緩存編譯結果,還在第一次編譯後開啟多線程編譯,極大加快建構速度
- 性能優化沒有盡頭,本人僅表達自己目前掌握的知識點,士别三日,刮目相看:
每隔三天,技術就會
正式開始吧,假設你已經懂什麼是 entry output loader plugin
webpack常見配置
// 入口檔案
entry: {
app: './src/js/index.js',
},
// 輸出檔案
output: {
filename: '[name].bundle.js',
path: path.resolve(__dirname, 'dist'),
publicPath: '/' //確定檔案資源能夠在 http://localhost:3000 下正确通路
},
// 開發者工具 source-map
devtool: 'inline-source-map',
// 建立開發者伺服器
devServer: {
contentBase: './dist',
hot: true // 熱更新
},
plugins: [
// 删除dist目錄
new CleanWebpackPlugin(['dist']),
// 重新穿件html檔案
new HtmlWebpackPlugin({
title: 'Output Management'
}),
// 以便更容易檢視要修補(patch)的依賴
new webpack.NamedModulesPlugin(),
// 熱更新子產品
new webpack.HotModuleReplacementPlugin()
],
// 環境
mode: "development",
// loader配置
module: {
rules: [
{
test: /\.css$/,
use: [
'style-loader',
'css-loader'
]
},
{
test: /\.(png|svg|jpg|gif)$/,
use: [
'file-loader'
]
}
]
}
這裡面我們重點關注和
module
屬性,因為今天的重點是編寫
plugins
和
loader
,需要配置這兩個屬性。
plugin
-
啟動後,在讀取配置的過程中會先執行webpack
初始化一個new MyPlugin(options)
獲得其執行個體。在初始化MyPlugin
對象後,再調用compiler
給插件執行個體傳入myPlugin.apply(compiler)
對象。compiler
插件執行個體在擷取到
compiler
對象後,就可以通過
compiler.plugin
(事件名稱, 回調函數) 監聽到
Webpack
廣播出來的事件。
并且可以通過 compiler 對象去操作 webpack。
-
對象包含了Compiler
環境所有的的配置資訊,包含Webpack
這些資訊,這個對象在options,loaders,plugins
啟動時候被執行個體化,它是全局唯一的,可以簡單地把它了解為Webpack
執行個體;Webpack
-
對Compilation
Compilation象包含了目前的子產品資源、編譯生成資源、變化的檔案等。當 Webpack 以開發模式運作時,每當檢測到一個檔案變化,一次新的
Compilation将被建立。
Compilation對象也提供了很多事件回調供插件做擴充。通過
Compiler` 對象。也能讀取到
-
的差別在于:Compiler 和 Compilation
-
代表了整個Compiler
從啟動到關閉的生命周期,而Webpack
隻是代表了一次新的編譯。Compilation
- 事件流
-
通過webpack
來組織這條複雜的生産線。Tapable
-
的事件流機制保證了插件的有序性,使得整個系統擴充性很好。webpack
-
的事件流機制應用了觀察者模式,和webpack
非常相似。Node.js 中的 EventEmitter
-
1.2 打包原理
- 識别入口檔案
- 通過逐層識别子產品依賴。(
或者Commonjs、amd
的es6
都會對其進行分析。來擷取代碼的依賴)import,webpack
-
做的就是分析代碼。轉換代碼,編譯代碼,輸出代碼webpack
- 最終形成打包後的代碼
- 這些都是
的一些基礎知識,對于了解webpack的工作機制很有幫助。webpack
腳手架一般都是遵循了 commonjs
子產品化方案,如果你不是很懂,那麼看起來很費勁,我寫的腳手架,就不使用子產品化方案了,簡單 粗
暴
commonjs
粗
- 開始開發環境配置
- 包管理器 使用
不解釋 就用yarn
yarn
- 配置
開發模式下的配置webpack.dev.js
-
yarn init -y
-
(yarn add webpack webpack-cli
會自動添加依賴是線上依賴還是開發環境的依賴)yarn
配置入口
entry: path.resolve(__dirname, '../src/main.js')}
配置輸出目錄
output: {
filename: 'js/[name].[hash:5].js',
path: path.resolve(__dirname, '../dist'),
},
引入
Vue
腳手架裡基本配置的
loader
,後面的
loader
都是往
rules
數組裡加就行了
module: {
rules: [
{
test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
use: [{
loader: 'url-loader',
options: {
limit: 10000,
name: 'img/[name]-[hash:5].[ext]',
}
}
]
},
{
test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/,
loader: 'url-loader',
options: {
limit: 10000,
name: 'fonts/[name]-[hash:5].[ext]',
}
},
{
test: /\.(mp4|webm|ogg|mp3|wav|flac|aac)(\?.*)?$/,
use: [
{
loader: 'url-loader',
options: {
limit: 4096,
name: 'media/[name]-[hash:5].[ext]',
}
}
]
}
]
},
有人會問 這麼多我怎麼看啊 别急 第一個是處理
url-loader
圖檔的,讓低于
base64
大小的檔案以
limit
形式使用,後面兩個一樣的套路,隻是換了檔案類型而已 ,不會的話,先複制過去跑一把?
base64
配置識别
.vue
檔案和
tempalte
模闆 ,
yarn add vue vue-loader vue-template-compiler
加入loader
{
test:/\.vue$/,
loader:"vue-loader"
}
加入plugin
const vueplugin = require('vue-loader/lib/plugin')
在webpack的plugin中
new vueplugin()即可
入口指定
babel-polifill
,
vendor
代碼分割公共子產品,打包後這些代碼都會在一個公共子產品
app: ['babel-polyfill', './src/index.js', './src/pages/home/index.js', './src/pages/home/categorys/index.jsx'],
vendor: ['vuex', 'better-scroll', 'mint-ui', 'element-ui']
指定
html
檔案為模闆打包輸出,自動引入打包後的
js
檔案
const HtmlWebpackPlugin = require('html-webpack-plugin');
plugins: [
new HtmlWebpackPlugin({
template: path.resolve(__dirname,'../index.html'),
filename: 'index.html'
}),
]
省掉
.vue
的字尾 ,直接配置在
module.exports
對象中,跟
entry
同級
resolve: {
extensions: ['.js','.json','.vue'],
}
加入識别
html
檔案的
loader
{
test: /\.(html)$/,
loader: 'html-loader'
}
開啟多線程編譯
const os = require('os')
{
loader: 'thread-loader',
options: {
workers: os.cpus().length
}
}
加入
babel-loader
加入 babel-loader 還有 解析JSX ES6文法的 babel preset
@babel/preset-env解析es6文法
@babel/plugin-syntax-dynamic-import解析vue的 import按需加載,附帶code spliting功能
{
loader: 'babel-loader',
options: { //jsx文法
presets: ["@babel/preset-react",
//tree shaking 按需加載babel-polifill
["@babel/preset-env", { "modules": false, "useBuiltIns": "false", "corejs": 2 }]],
plugins: [
//支援import 懶加載
"@babel/plugin-syntax-dynamic-import",
//andt-mobile按需加載 true是less,如果不用less style的值可以寫'css'
["import", { libraryName: "antd-mobile", style: true }],
//識别class元件
["@babel/plugin-proposal-class-properties", { "loose": true }],
],
cacheDirectory: true
},
}
在使用上面的
babel
配置後 我們躺着就可以用
vueRouter
的路由懶加載了
路由懶加載
- 當打包建構應用時,JavaScript 包會變得非常大,影響頁面加載。如果我們能把不同路由對應的元件分割成不同的代碼塊,然後當路由被通路的時候才加載對應元件,這樣就更加高效了。
- 結合 Vue 的異步元件和 Webpack 的代碼分割功能,輕松實作路由元件的懶加載。
- 首先,可以将異步元件定義為傳回一個 Promise 的工廠函數 (該函數傳回的 Promise 應該 resolve 元件本身):
- const Foo = () => Promise.resolve({ / 元件定義對象 / })
- 第二,在 Webpack 中,我們可以使用動态 import文法來定義代碼分塊點 (split point):
-
import('./Foo.vue') // 傳回 Promise
-
注意
- 如果您使用的是 Babel,你将需要添加 syntax-dynamic-import 插件,才能使 Babel 可以正确地解析文法。
- 結合這兩者,這就是如何定義一個能夠被 Webpack 自動代碼分割的異步元件。
const Foo = () => import('./Foo.vue')
在路由配置中什麼都不需要改變,隻需要像往常一樣使用 Foo:
const router = new VueRouter({
routes: [
{ path: '/foo', component: Foo }
]
})
# 把元件按組分塊
有時候我們想把某個路由下的所有元件都打包在同個異步塊 (chunk) 中。隻需要使用 命名 chunk,一個特殊的注釋文法來提供 chunk name (需要 Webpack > 2.4)。
const Foo = () => import(/* webpackChunkName: "group-foo" */ './Foo.vue')
const Bar = () => import(/* webpackChunkName: "group-foo" */ './Bar.vue')
const Baz = () => import(/* webpackChunkName: "group-foo" */ './Baz.vue')
Webpack 會将任何一個異步子產品與相同的塊名稱組合到相同的異步塊中。
加入插件 熱更新plugin和html-webpack-plugin
const HtmlWebpackPlugin = require('html-webpack-plugin')
const webpack = require('webpack')
new HtmlWebpackPlugin({
template: './src/index.html'
}),
new webpack.HotModuleReplacementPlugin(),
devServer: {
contentBase: '../build',
open: true,
port: 5000,
hot: true
},
加入
less-css
識别的子產品
{
test: /\.(less|css)$/,
use: [
{ loader: 'style-loader' },
{
loader: 'css-loader'
, options: {
modules: false, //不建議開啟css子產品化,某些ui元件庫可能會按需加載失敗
localIdentName: '[local]--[hash:base64:5]'
}
},
{
loader: 'less-loader',
options: { javascriptEnabled: true }
}
]
},
下面正式開始生産環境
踩坑是好事 為什麼這次不放完整的源碼 因為不去踩坑 永遠提升不了技術
html
殺掉無效的代碼
new HtmlWebpackPlugin({
template: './src/index.html',
minify: {
removeComments: true,
collapseWhitespace: true,
removeRedundantAttributes: true,
useShortDoctype: true,
removeEmptyAttributes: true,
removeStyleLinkTypeAttributes: true,
keepClosingSlash: true,
minifyJS: true,
minifyCSS: true,
minifyURLs: true,
}
}),
加入圖檔壓縮 性能優化很大
{
test: /\.(jpg|jpeg|bmp|svg|png|webp|gif)$/,
use:[
{loader: 'url-loader',
options: {
limit: 8 * 1024,
name: '[name].[hash:8].[ext]',
outputPath:'/img'
}},
{
loader: 'img-loader',
options: {
plugins: [
require('imagemin-gifsicle')({
interlaced: false
}),
require('imagemin-mozjpeg')({
progressive: true,
arithmetic: false
}),
require('imagemin-pngquant')({
floyd: 0.5,
speed: 2
}),
require('imagemin-svgo')({
plugins: [
{ removeTitle: true },
{ convertPathData: false }
]
})
]
}
}
]
}
加入file-loader 把一些檔案打包輸出到固定的目錄下
{
exclude: /\.(js|json|less|css|jsx)$/,
loader: 'file-loader',
options: {
outputPath: 'media/',
name: '[name].[contenthash:8].[ext]'
}
}
加入壓縮css的插件
const OptimizeCssAssetsWebpackPlugin = require('optimize-css-assets-webpack-plugin')
new OptimizeCssAssetsWebpackPlugin({
cssProcessPluginOptions:{
preset:['default',{discardComments: {removeAll:true} }]
}
}),
加入code spliting代碼分割
vue
腳手架是同步異步分開割,我是直接一起割
optimization: {
runtimeChunk:true, //設定為 true, 一個chunk打包後就是一個檔案,一個chunk對應`一些js css 圖檔`等
splitChunks: {
chunks: 'all' // 預設 entry 的 chunk 不會被拆分, 配置成 all, 就可以了拆分了,一個入口`JS`,
//打包後就生成一個單獨的檔案
}
}
加入 WorkboxPlugin , PWA的插件
pwa這個技術其實要想真正用好,還是需要下點功夫,它有它的生命周期,以及它在浏覽器中熱更新帶來的副作用等,需要認真研究。可以參考百度的lavas架構發展曆史~
const WorkboxPlugin = require('workbox-webpack-plugin')
new WorkboxPlugin.GenerateSW({
clientsClaim: true, //讓浏覽器立即servece worker被接管
skipWaiting: true, // 更新sw檔案後,立即插隊到最前面
importWorkboxFrom: 'local',
include: [/\.js$/, /\.css$/, /\.html$/,/\.jpg/,/\.jpeg/,/\.svg/,/\.webp/,/\.png/],
}),
單頁面應用的優化核心 :
- 最重要的是路由懶加載 代碼分割
- 部分渲染在服務端完成 極大加快首屏渲染速度
首選VUE
架構,也可以使用它的腳手架nuxt
- 圖檔壓縮和圖檔懶加載是對頁面層次最大的優化之一
- 後面繼續書寫
和next nuxt
的使用~pwa
腳手架的搭建過程很多坑,但是卻能大大提升你的技術天花闆
福利,各種教程資源,應有盡有
《Java基礎、入門、精通、架構師全套資源》