整合營銷服務商

          電腦端+手機端+微信端=數據同步管理

          免費咨詢熱線:

          吃透 Vue 項目開發實踐|16個方面深入前端工程化

          吃透 Vue 項目開發實踐|16個方面深入前端工程化開發技巧「下」



          前面兩篇文章總結了 Vue 開發的大部分技巧和內容,最后一篇文章來對它進行一個收尾

          這篇文章我們來談談一些 Vue 理解和實踐要求高一點的問題

          首先是生命周期這一塊內容,隨著實踐越多它的意義越大,理解也越深刻

          mixin 功能強大,對代碼復用組織都有很高的要求,算是 Vue 后期發力的高級技巧

          服務端渲染可能是學習 Vue 最后一塊陣地了,對于 SPA 框架的一個里程碑

          最后,總結一下我在使用 Vue 中使用的技巧和經驗

          常規操作,先點贊后觀看哦!你的點贊是我創作的動力之一!

          前情提要

          我將從 16 個方面來論述 Vue 開發過程中的一些技巧和原理。如果你還未觀看上節文章,可以移步至

          • 吃透 Vue 項目開發實踐|16個方面深入前端工程化開發技巧【上】
          • 吃透 Vue 項目開發實踐|16個方面深入前端工程化開發技巧【中】

          本篇概覽

          Vue 生命周期

          什么是 Vue 生命周期?

          Vue 生命周期大概就是:一個從 Vue 實例的創建到組件銷毀的一個的過程。

          具體情況下,我們分為幾個核心的階段,并且每個階段都有一套鉤子函數來執行我們需要的代碼。

          生命周期階段與鉤子

          我們整理分類一下這些生命周期鉤子,為了記憶方便分為 4 大核心階段:

          方便讀者記憶,這里盡量使用圖示:

          可以看到每一個階段中的鉤子命名都很好記憶,階段開始前使用 beforeXxx,階段后結束后使用xxxed

          除這 8 個核心鉤子,另外還有 3 個新增功能型鉤子,目前總共是 11 個鉤子 順帶提一下這 3 個鉤子的功能

          1. 組件緩存,activated 與 deactivated,這兩個鉤子也是一對的,分別表示被 keep-alive 緩存的組件激活和停用時調用。
          2. 組件錯誤捕獲,errorCaptured,對組件中出現對異常錯誤進行處理,使用較少。

          圖解生命周期

          我們看看官方的圖解,在 Vue 教程實例這節 官方教程直接跳轉

          官方圖解

          官方圖解并沒有詳細解釋這張圖。我猜一方面原因是這個圖里面涉及的細節都是在 vue 源碼里面體現,要真正解釋起來也沒那么簡單。另一方面是希望我們多在實踐中去理解里面的意義。

          Vue 源碼基本流程

          對于上面那張圖的理解,我們需要對 Vue 源碼進行梳理,才能真正的理解。大概根據現有的源碼,我梳理了一下大致的流程:

          我們可以清楚地看到,從 Vue 實例創建、組件掛載、渲染的一些過程中,有著明顯的周期節點。

          簡化文字圖解

          結合上面源碼的流程和相關實踐,簡化每一個階段做了哪些時期,每一個鉤子里面是組件處于什么狀態。

          實踐驗證一下生命周期

          提出問題

          下面我們提出一些問題:

          1. 什么時期創建 el ?
          2. 什么時期掛載 data ?
          3. 什么時期可以訪問 dom ?
          4. 什么情況下組件會更新?更新是同步更新還是異步更新?
          5. 什么情況下組件會被銷毀?
          6. 銷毀組件后,還可以訪問哪些內容?

          編寫代碼

          1. 首先寫一個小 demo,打印關鍵組件信息
          <template>
            <div>
              <div class="message">
                {{message}}
              </div>
            </div>
          </template>
          
          <script>export default {
            data() {
              return {
                message: '1'
              }
            },
            methods: {
              printComponentInfo(lifeName) {
                console.log(lifeName)
                console.log('el', this.$el)
                console.log('data', this.$data)
                console.log('watch', this.$watch)
              }
            }
          }</script>
          復制代碼
          1. 增加核心的 8 個生命周期鉤子,分別調用打印方法
            // ...
            beforeCreate() {
              this.printComponentInfo('beforeCreate')
            },
            created() {
              this.printComponentInfo('created')
            },
            beforeMount() {
              this.printComponentInfo('beforeMount')
            },
            mounted() {
              this.printComponentInfo('mounted')
            },
            beforeUpdate() {
              this.printComponentInfo('beforeUpdate')
            },
            updated() {
              this.printComponentInfo('updated')
            },
            beforeDestroy() {
              this.printComponentInfo('beforeDestroy')
            },
            destroyed() {
              this.printComponentInfo('destroyed')
            },
            // ...
          復制代碼

          創建階段

          beforeCreate 中methods中方法直接報錯無法訪問,直接訪問 el 和 data 后

          發現只能訪問到 watch, el 和 data 均不能訪問到

          created 時期 el 無法訪問到,但是可以訪問到 data 了

          掛載階段

          beforeMount 中可以訪問 data 但是仍然訪問不到 el

          mounted 中可以訪問到 el 了

          首次加載頁面,更新階段和銷毀階段到鉤子都未觸發

          更新階段

          我們增加一行代碼

          this.message=this.message + 1
          復制代碼

          如果增加在 created 階段,發現 update鉤子仍然未觸發,但是 el 和 data 的值都變成了 2

          如果增加在 mounted 階段,發現 update鉤子此時觸發了

          銷毀階段

          怎樣觸發銷毀的鉤子呢? 大概有這幾種方法

          • 手動調用 $destory
          • v-if 與 v-for 指令,(v-show 不行)
          • 路由切換和關閉或刷新瀏覽器 我們在 mounted 鉤子里面增加一行代碼手動銷毀當前組件,或者跳轉路由
          this.$destory('lifecycle')
          復制代碼

          發現beforeDestory 和 destoryed 都觸發了,而且el、data都一樣還是可以訪問到

          生命周期鉤子常見使用的場景

          beforeCreate 謹慎操作 this

          beforeCreate 無法訪問到 this 中的 data、method

          // 錯誤實例
          beforeCreate() {
              // 允許
              console.log('ok')
              // 不允許
              this.print() // 報錯找不到
              this.message=1 // 報錯找不到
          
          }
          復制代碼

          請求應放在 created 鉤子中

          created 可以訪問 this,但無法訪問 dom,dom 未掛載

          created() {
              // 允許并推薦
              this.$http.get(xxx).then(res=> {
                  this.data=res.data
              })
              // 不允許
              this.$el
              this.$ref.demo
              const a=document.getElementById('demo')
          }
          復制代碼

          操作 DOM 代碼應放在 mounted 鉤子中

          mounted 已經掛載 dom,可以訪問 this

          mounted() {
              // 允許
              this.$el
              this.$ref.demo
              let a=document.getElementById('')
          }
          復制代碼

          生命周期相關demo 代碼見github-lifecycle-demo

          理解并合理使用 mixin

          什么是 mixin(混入)

          當組件使用混入對象時,所有混入對象的選項將被“混合”進入該組件本身的選項 大致原理就是將外來的組件、方法以某種方式進行合并。合并的規則有點像繼承和擴展。

          當組件和混入對象含有同名選項時,這些選項將以恰當的方式進行“合并”

          我們看一下一個組件里面有哪些東西是可以合并的

          // mixins/demo
          export default {
              data() {
                  return {}
              },
              mounted() {},
              methods: {},
              computed: {},
              components: {},
              directives: {}
          }
          復制代碼

          data、methods、computed、directives、components 生命周期鉤子

          沒錯這些都可以混入

          import demoMixin form '@/mixins/demo'
          export default {
              mixins: [demoMixin]
          }
          復制代碼

          這樣看來,很多頁面重復的代碼我們都可以直接抽取出來

          或者是封裝成一個公共的 mixin

          比如我們做 H5 頁面,里面很多短信驗證的邏輯固有邏輯,但是需要訪問到 this。使用工具函數肯定不行。

          這時候就可以考慮使用 mixin,封裝成一個具有響應式的模塊。供需要的地方進行引入。

          mixin 規則

          首先是優先級的問題,當重名選項時選擇哪一個為最后的結果

          默認規則我這里分為 3 類

          1. data 混入: 以當前組件值為最后的值
          2. 生命周期鉤子: 保留所有鉤子,先執行 mixins 的,后執行當前組件的
          3. methods、computed、directives、components 這種健值對形式,同名key,統統以當前組件為準

          當然如果想改變規則,也可以通過配置來改變規則

          Vue.config.optionMergeStrategies.myOption=function (toVal, fromVal) {
            // 返回合并后的值
          }
          復制代碼

          mixin 的好處

          我們知道 Vue 最能復用代碼的就是組件。一般情況,我們通過 props 來控制組件的,將原有組件封裝成 HOC 高階組件。而控制 props 的生成不一樣的功能的代碼還是寫在基礎組件里。

          <template lang="pug">
            div.dib
              van-button.btn(
                @click="$emit('click')"
                :class="getClass" v-bind="$attrs"
                :style="{'width': size==='large' ? '345px': '', 'backgroundColor': getBgColor, borderColor: getBgColor, color: getBgColor}")
                slot
          </template>
          
          <script>import { Button } from 'vant'
          import Vue from 'vue'
          import { getColor } from '@/utils'
          Vue.use(Button)
          
          export default {
            name: 'app-button',
            props: {
              type: {
                type: String,
                default: 'primary'
              },
              theme: {
                type: String,
                default: 'blue'
              },
              size: {
                type: String,
                default: ''
              }
            }
          }復制代碼

          以這個組件為例,我們還是通過公共組件內部的邏輯,來改變組件的行為

          但是,使用 mixin 提供了另一個思路。我們寫好公共的 mixin,每一個需要使用 mixin 的地方。我們進行擴展合并,不同與公共 mixin 的選項我們在當前組件中進行自定義,也就是擴展。我們新的邏輯是寫在當前組件里面的,而非公共 mixins 里

          畫個圖理解一下:

          最后總結一下 mixin 的優點

          • 復用代碼,公共邏輯抽離
          • 可以訪問 this, 可以操作響應式代碼
          • mixins 相對與組件來說,更像擴展

          說了這么多那 mixins 有什么壞處呢?

          第一,千萬不能濫用全局 mixins 因為會影響所有多子組件

          第二,由于 mixins 的合并策略固有影響,可能在一些極端情況達不到你想要的效果

          比如:我已經存在一個 mixins,我的頁面里面也有一些方法,我要引入mixins 就要做很多改動,來保證我的代碼按我的需要運行。

          頁面 data 有一個 message 值,mixins 里面同樣有一個。

          按照默認規則,mixins 里面的 message 會被頁面里面 message 覆蓋。但是這兩個 message 可能代表的意義不一樣,都需要存在。那么我就需要改掉其中的一個,如果業務太深的話,可能這個 message 沒那么好改。

          有時候需要考慮這些問題,導致使用 mixins 都會增加一些開發負擔。當然也是這些問題可以使用規范來規避。

          理解并使用 SSR

          SSR 是 Serve Side Render 的縮寫,翻譯過來就是我們常說的服務端渲染

          簡單歸納 SSR 存在的原因

          1. SPA 框架 SEO 的解決方案
          2. 提升首屏加載速度

          但是它還存在以下問題

          • 有些生命周期鉤子無法使用(之前提到的 activated 和 deactivated 等等)
          • 額外很多的配置
          • 服務器資源的需求

          總得來說,SSR 是必要的但不是充分的,SPA 的 SEO 現在沒有更好的方案,有這方面強烈需求的網站來說,SSR 確實很有必要

          SSR 原理

          通過上面圖我們可以得大致幾點內容

          • 通用業務代碼只寫一套。
          • 通過暴露客戶端和服務端兩個入口,對 Vue 實例進行訪問。
          • 通過構建工具打成兩個包,服務端包通過 node 服務器渲染給瀏覽器訪問。客戶端和原來一樣通過訪問資源獲取。

          實現SSR 有 3 種方式

          1. 根據官網教程搭建,一步一步搭建SSR, 這里有具體教程,略微有點麻煩,但是能夠體驗搭建的過程,更加深入細節。使用于練習,和現有項目的改造

          2. 使用demo改造,開源 demo,方便省時,適用代碼參考學習 vue-ssr-demo

          3. 使用nuxt,預設了 SSR 和預渲染,適用于需要 SSR 的新項目。

          分別嘗試用這 3 種方式搭建 SSR

          五步簡單理解SSR(根據官網教程,具體完操作查看整教程和demo)

          這里主要加深理解,vue-cli3+ 實現基本 SSR

          第一步:安裝依賴

          • vue-server-renderer (核心依賴,版本必須與 vue 版本一致)
          • webpack-merge(用于webpack配置合并)
          • webpack-node-externals (用于webpack配置更改)
          • express (用于服務端渲染)

          第二步:建立入口,并改造改造

          分為 2 個入口,將 main.js 定為通用入口, 并額外增加entry-client.js 和 entry-serve.js 兩個

          1.改造主要入口,創建工廠函數

          // main.js
          import Vue from 'vue'
          import App from './App.vue'
          import { createRouter } from "./router"
          // app、router
          export function createApp () {
            const router=createRouter()
            const app=new Vue({
              router,
              render: h=> h(App)
            })
            return { app, router }
          }
          復制代碼

          2.客戶端入口

          // client.js
          import { createApp } from './main'
          // 客戶端特定引導邏輯
          const { app }=createApp()
          app.$mount('#app')
          復制代碼

          3.服務端入口

          // serve.js
          import { createApp } from "./main";
          export default context=> {
            // 因為有可能會是異步路由鉤子函數或組件,所以我們將返回一個 Promise
            return new Promise((resolve, reject)=> {
              const { app, router }=createApp();
              // 設置服務器端 router 的位置
              router.push(context.url);
              // 等到 router 將可能的異步組件和鉤子函數解析完
              router.onReady(()=> {
                const matchedComponents=router.getMatchedComponents();
                // 匹配不到的路由,執行 reject 函數
                if (!matchedComponents.length) {
                  return reject({
                    code: 404
                  });
                }
                // Promise 應該 resolve 應用程序實例,以便它可以渲染
                resolve(app);
              }, reject);
            });
          };
          復制代碼

          第三步:改造 vue.config 配置

          const VueSSRServerPlugin=require("vue-server-renderer/server-plugin");
          const VueSSRClientPlugin=require("vue-server-renderer/client-plugin");
          const nodeExternals=require("webpack-node-externals");
          const merge=require("webpack-merge");
          const TARGET_NODE=process.env.WEBPACK_TARGET==="node";
          const target=TARGET_NODE ? "server" : "client";
          module.exports={
            configureWebpack: ()=> ({
              entry: `./src/entry-${target}.js`,
              devtool: 'source-map',
              target: TARGET_NODE ? "node" : "web",
              node: TARGET_NODE ? undefined : false,
              output: {
                libraryTarget: TARGET_NODE ? "commonjs2" : undefined
              },
              externals: TARGET_NODE
                ? nodeExternals({
                    whitelist: [/\.css$/]
                  })
                : undefined,
              optimization: {
                    splitChunks: undefined
              },
              plugins: [TARGET_NODE ? new VueSSRServerPlugin() : new VueSSRClientPlugin()]
            }),
            //...
          };
          復制代碼

          第四步:對路由 router 改造

          export function createRouter(){
            return new Router({
              mode: 'history',
              routes: [
              //...
              ]
            })
          }
          復制代碼

          第五步:使用 express 運行服務端代碼

          這一步主要是讓 node 服務端響應 HTML 給瀏覽器訪問

          const Vue=require('vue')
          const server=require('express')()
          const renderer=require('vue-server-renderer').createRenderer()
          server.get('*', (req, res)=> {
            const app=new Vue({
              data: {
                url: req.url
              },
              template: `<div>訪問的 URL 是: {{ url }}</div>`
            })
            renderer.renderToString(app, (err, html)=> {
              if (err) {
                res.status(500).end('Internal Server Error')
                return
              }
              res.end(`
                <!DOCTYPE html>
                <html lang="en">
                  <head><title>Hello</title></head>
                  <body>${html}</body>
                </html>
              `)
            })
          })
          server.listen(8080)
          復制代碼

          nuxt 體驗

          簡單幾步體驗下 nuxt

          安裝后

          簡單看了一下源碼,nuxt 把我們之前提到的重要的改造,全部封裝到 .nuxt 文件夾里面了

          跑一下 dev 發現有兩個端,一個 clinet 端,一個 server 端

          最后查看一下效果,整個過程挺絲滑的。目錄結構也比較符合我的風格,新項目需要 SSR 會考慮使用 nuxt

          使用 webpack 插件預渲染

          解決 SEO 問題是不是只有 SSR 呢?其實預渲染也能做到,首先

          區別使用 SSR 和預渲染

          • 服務端渲染解決的問題,不僅只是把 HTML 頁面給瀏覽器,更重要的是處理動態邏輯和 JS 代碼后,將渲染后完整的 HTML 給瀏覽器,渲染的過程在服務端。
          • 預渲染,是利用構建工具在 webpack 中生成靜態的 HTML,直接給瀏覽器,渲染的過程在本地。
          • 預渲染插件里面提到兩種不能使用:大量路由、動態內容

          使用 prerender-spa-plugin 插件進行簡單預渲染

          1. 安裝 prerender-spa-plugin
          yarn prerender-spa-plugin
          復制代碼
          1. 修改 webpack 配置,比較簡單就能完成配置
          const path=require('path')
          const PrerenderSPAPlugin=require('prerender-spa-plugin')
          const Renderer=PrerenderSPAPlugin.PuppeteerRenderer
          module.exports={
            plugins: [
              //...
              new PrerenderSPAPlugin({
                staticDir: path.join(__dirname, 'dist'),
                outputDir: path.join(__dirname, 'prerendered'),
                indexPath: path.join(__dirname, 'dist', 'index.html'),
                routes: [ '/', '/about', '/some/deep/nested/route' ],
                postProcess (renderedRoute) {
                  renderedRoute.route=renderedRoute.originalPath
                  renderedRoute.html=renderedRoute.html.split(/>[\s]+</gmi).join('><')
                  if (renderedRoute.route.endsWith('.html')) {
                    renderedRoute.outputPath=path.join(__dirname, 'dist', renderedRoute.route)
                  }
                  return renderedRoute
                },
                minify: {
                  collapseBooleanAttributes: true,
                  collapseWhitespace: true,
                  decodeEntities: true,
                  keepClosingSlash: true,
                  sortAttributes: true
                },
                renderer: new Renderer({
                  inject: {
                    foo: 'bar'
                  },
                  maxConcurrentRoutes: 4
            ]
          }
          復制代碼

          Vue 開發技巧總結

          從 5 個大的角度來提升開發效率體驗代碼美觀代碼質量用戶體驗

          5 大角度提升

          代碼復用

          • 組件化開發,代碼效率 * n
          • 使用 mixins 抽離公共邏輯,代碼效率 * n
          • 工具化函數、使用 filter 編碼效率+
          • sass 復用 css,編碼體驗、效率+

          代碼質量

          • 代碼靜態檢查 eslint + prettier,代碼風格+、基礎語法錯誤-
          • 數據類型控制 typescript,代碼質量+
          • 前端測試 test,代碼質量+

          代碼優化

          • 合理使用 vue,渲染性能+
          • 合理使用 vuex 減少請求,使用圖片懶加載,加載性能+
          • 合理使用函數組件,組件性能+
          • 合理骨架屏、路由過渡,用戶體驗+

          開發效率

          • 使用更新的腳手架 vue-cli4,webpack 配置效率+
          • 使用配置好的腳手架模版 vue-h5-template,vue 配置效率+
          • 使用更簡潔的模版 pug,HTML 編寫效率+
          • 使用更強大的 css 編寫 sass,CSS 編寫效率+
          • 使用模擬數據 mock,脫離后端開發效率+
          • 開源組件封裝 HOC,組件開發,頁面編寫效率+

          瓶頸解決

          • 路由history使用,服務端配置相關,URL美觀+
          • 解決SEO與首屏加載、服務端渲染 SSR 基本解決

          后記

          作者面臨 “失業” 和 “禁足” 在家里的雙重打擊下,仍然堅持完成了這個系列的最后一篇文章。

          如果能對你有幫助,便是它最大的價值。都看到這里還不點贊,太過不去啦!

          由于技術水平有限,文章中如有錯誤地方,請在評論區指出,感謝!


          在網絡安全領域,暗鏈接(或隱藏鏈接)是一種常見的網絡欺詐手段。它們通常隱藏在網頁代碼中,對普通用戶不可見,但可能指向惡意網站或用于執行不安全的操作。因此,從網頁源代碼中檢測并識別這些暗鏈接變得尤為重要。本文將探討如何從提取的HTML源代碼中檢測暗鏈接,并將這些鏈接進行輸出。

          一、理解暗鏈接

          什么是暗鏈接:

          暗鏈接通常指的是隱藏在網頁中,對用戶不明顯,但可能含有惡意內容的鏈接。

          暗鏈接的常見形式:

          完全隱藏的鏈接,如使用CSS將鏈接設為透明或大小為零。

          使用微小的文本或圖像作為鏈接。

          將鏈接嵌入到不相關元素中。

          二、HTML源代碼的檢測方法

          分析HTML結構:

          介紹如何查看和理解網頁的HTML源代碼。

          解釋如何識別HTML中的鏈接元素(通常是<a>標簽)。

          檢測暗鏈接的技術:

          使用HTML和CSS屬性來識別隱藏的鏈接,如display: none;、opacity: 0;或width: 0px; height: 0px;等。

          檢查鏈接的文本內容和尺寸,判斷是否可疑。

          三、實現暗鏈接檢測

          工具和技術選擇:

          介紹可用于解析HTML和CSS的工具,如Python的BeautifulSoup庫。

          代碼實現:

          提供一個基本的腳本示例,展示如何從HTML中提取鏈接并檢查其屬性以識別暗鏈接。

          四、案例分析

          實際網頁案例:

          選取一個包含暗鏈接的網頁作為案例,展示如何使用工具檢測這些鏈接。

          結果展示和分析:

          展示檢測到的暗鏈接,并對其可能的風險進行分析。

          五、總結與預防建議

          總結:

          強調檢測暗鏈接在網絡安全中的重要性。

          預防建議:

          提供一些基本的網絡安全建議,幫助用戶識別和避免訪問暗鏈接。

          小結

          通過本文的討論,讀者可以了解如何從HTML源代碼中檢測暗鏈接,并了解這些鏈接可能帶來的風險。雖然這需要一定的技術知識,但通過合適的工具和方法,可以有效地識別和防范這種網絡安全威脅。

          示例代碼(Python使用BeautifulSoup檢測暗鏈接)

          python

          from bs4 import BeautifulSoup

          import requests

          # 加載網頁內容

          url="https://example.com"

          html_content=requests.get(url).text

          soup=BeautifulSoup(html_content, 'html.parser')

          # 檢測暗鏈接

          dark_links=[]

          for link in soup.find_all('a'):

          if link.get('style'):

          styles=link.get('style').split(';')

          if any(s in styles for s in ['display:none', 'opacity:0', 'width:0px', 'height:0px']):

          dark_links.append(link.get('href'))

          # 輸出檢測到的暗鏈接

          print("Detected Dark Links:", dark_links)

          這段代碼演示了如何使用BeautifulSoup庫來解析HTML,并檢查每個鏈接的CSS樣式以識別是否為暗鏈接。這只是一個基本的示例,實際應用中可能需要更復雜的邏輯來處理各種隱藏技術。

          說之前也是對 chunk 這個概念有些模糊,并且很多時候網上的文章大部分介紹重點在將代碼分離動態加載之類的。寫這篇文章的目的也是想讓其他那些跟我一樣曾經對這個概念不是很清楚的童鞋有個清晰的認識。廢話不多說,開始干!

          Let's Dive in!

          Webpack 文件分離包括兩個部分,一個是 Bundle 的分離,一個是 Code 代碼的分離:

          • Bundle splitting: 實際上就是創建多個更小的文件,并行加載,以獲得更好的緩存效果;主要的作用就是使瀏覽器并行下載,提高下載速度。并且運用瀏覽器緩存,只有代碼被修改,文件名中的哈希值改變了才會去再次加載。
          • Code splitting: 只加載用戶最需要的部分,其余的代碼都遵從懶加載的策略;主要的作用就是加快頁面加載速度,不加載不必要加載的東西。

          準備工作

          在進行文件分離之前的準備工作,我們先寫一些代碼:

          入口文件 src/index.js:

          const { getData }=require('./main')
          const { findMaxIndex }=require('./math')
          let arr=[1,2,123,21,3,21,321,1]
          findMaxIndex(arr)
          getData('./index.html')
          

          兩個依賴模塊:

          src/main.js:

          const axios=require('axios')
          const getData=url=> {
           axios.get(url).then(d=> {
           console.log(d.status)
           console.log(d.data.length)
           })
          }
          module.exports={
           getData
          }
          

          src/math.js:

          const _=require('lodash')
          const findMaxIndex=arr=> {
           let x=_.max(arr)
           let r=Array.prototype.indexOf.call(arr, x)
           console.log(r);
          }
          module.exports={
           findMaxIndex
          }
          

          增加一個 webpack 配置文件 webpack.config.js:

          const path=require('path')
          module.exports={
           mode: 'development',
           entry: path.resolve(__dirname, 'src/index.js'),
           output: {
           path: path.resolve(__dirname, 'dist'),
           filename: '[name].[contenthash].js'
           },
          }
          

          文件分離之前打包效果

          在 bundle split 和 code split 操作之前,我們先看一下當前默認打包的效果:



          全部依賴都被打包到 main.xxx.js 中去,大小是 609k

          開始分離操作

          Bundle Split

          Bundle Split 的主要任務是將多個引用的包和模塊進行分離,避免全部依賴打包到一個文件下

          基本用法

          Webpack 4 中需要使用到 optimization.splitChunks 的配置:

          const path=require('path')
          module.exports={
           mode: 'development',
           entry: path.resolve(__dirname, 'src/index.js'),
           output: {
           path: path.resolve(__dirname, 'dist'),
           filename: '[name].[contenthash].js'
           },
           optimization: {
           splitChunks: {
           chunks: 'all'
           }
           }
          }
          
          optimization.splitChunks 的意思是將所有來自 node_modules 中的依賴全部打包分離出來,這個時候我們再看打包的文件是個什么樣子:



          增加了 splitChunks 的配置,我們第三方模塊都被打包到了 vendors~main.xxx.js 中去了,這個文件大小有 604k,而入口文件 main.xxx.js 則只有 7k

          雖然說這樣將第三方模塊單獨打包出去能夠減小入口文件的大小,但這樣仍然是個不小的文件;這個小的測試項目中我們使用到了 axios 和 lodash 這兩個第三方模塊,因此我們希望的應該是將這兩個模塊單獨分離出來兩個文件,而不是全部放到一個 vendors 中去,那么我們繼續配置 webpack.config.js:

          將每個 npm 包單獨分離出來

          這里我們需要使用到 webpack.HashedModuleIdsPlugin 這個插件

          參考官方文檔(https://webpack.js.org/plugins/split-chunks-plugin/#optimization-splitchunks)

          直接上代碼:

          const path=require('path')
          const webpack=require('webpack')
          module.exports={
           mode: 'development',
           entry: path.resolve(__dirname, 'src/index.js'),
           plugins: [
           new webpack.HashedModuleIdsPlugin() // 根據模塊的相對路徑生成 HASH 作為模塊 ID
           ],
           output: {
           path: path.resolve(__dirname, 'dist'),
           filename: '[name].[contenthash].js'
           },
           optimization: {
           runtimeChunk: 'single',
           splitChunks: {
           chunks: 'all', // 默認 async 可選值 all 和 initial
           maxInitialRequests: Infinity, // 一個入口最大的并行請求數
           minSize: 0, // 避免模塊體積過小而被忽略
           minChunks: 1, // 默認也是一表示最小引用次數
           cacheGroups: {
           vendor: {
           test: /[\\/]node_modules[\\/]/, // 如果需要的依賴特別小,可以直接設置成需要打包的依賴名稱
           name(module, chunks, chcheGroupKey) { // 可提供布爾值、字符串和函數,如果是函數,可編寫自定義返回值
           const packageName=module.context.match(/[\\/]node_modules[\\/](.*?)([\\/]|$)/)[1] // 獲取模塊名稱
           return `npm.${packageName.replace('@', '')}` // 可選,一般情況下不需要將模塊名稱 @ 符號去除
           }
           }
           }
           }
           }
          }
          

          這里我們主要做了幾件事:

          為了避免每次打包的文件哈希變化,我們可以使用 webpack 內置的 HashedModuleIdsPlugin,這樣可以避免每次打包的文件哈希值變化首先增加 maxInitialRequests 并設置成 Infinity,指定這個入口文件最大并行請求數然后將 minSize 和 minChunks 分別設置成 0 和 1,即使模塊非常小也將其提取出來,并且這個模塊的引用次數只有 1 也要提取最后配置匹配的依賴以及分離出的文件名格式另外,我們還將運行時代碼分離出來,這塊代碼還可以配合 InlineManifestWebpackPlugin 直接插入到 HTML 文件中。這里我們將這個配置設置成 single,即將所有chunk的運行代碼打包到一個文件中

          這樣 Bundle Split 的操作基本就完成了,讓我們看看效果如何:



          所依賴的幾個模塊都被分離出去了。

          使用 HtmlWebpackPlugin 這個插件將 js 代碼注入 html 文件中。

          npm i -D html-webpack-plugin

          修改 webpack.config.js 文件:

          // 配置文件引入這個插件
          var HtmlWebpackPlugin=require('html-webpack-plugin');
          // ...
          module.exports={
           // ...
           plugins: [
           new HtmlWebpackPlugin(),
           new webpack.HashedModuleIdsPlugin() // 根據模塊的相對路徑生成 HASH 作為模塊 ID
           ],
           // ...
          }
          

          安裝 http-server 或使用 vscode 的插件 Live Server 將代碼放入一個本地服務器中,打開瀏覽器的調試窗口進入到 Network 面板:



          可以看到我們將模塊單獨分離出來并行加載,這樣比單獨加載一個龐大的包要快不少,接下來我們還要進行代碼分離,將不必要加載的模塊延遲加載

          Code Split

          代碼分離實際上就是只加載用戶需要使用到的部分代碼,不是必須的就暫時不加載。

          這里我們要用到 require.ensure 這個方法去獲取依賴,這樣 webpack 打包之后將會增加運行時代碼,在設定好的條件下才會觸發獲取這個依賴。

          在 ES6 中我們可以用 Dynamic Imports 來替代上述方案,如果使用 ES6 語法那么需要使用到 babel 以及 babel 的插件 plugin-syntax-dynamic-import(https://babeljs.io/docs/en/babel-plugin-syntax-dynamic-import/#installation),在瀏覽器中為了保證兼容性,還需要安裝 promise 的 polyfill,用法大同小異,可直接觀摩 webpack 的官方文檔(https://webpack.js.org/guides/code-splitting/#dynamic-imports)

          修改我們的代碼:

          const { getData }=require('./main')
          let arr=[1,2,123,21,3,21,321,1]
          getData('./index.html')
          setTimeout(()=> {
           require.ensure(['./math'], function(require) {
           const { findMaxIndex }=require('./math')
           console.log(findMaxIndex(arr))
           })
          }, 3000)
          
          我們設定了一個定時器,只有在 3000 毫秒以后,瀏覽器才會去請求這個 math 模塊

          編譯之后,打開調試面板刷新瀏覽器:



          在頁面剛加載完畢后,瀏覽器會嘗試獲取上述這么幾個模塊,因為模塊都很小很快就加載完成了



          在 3500ms 左右,瀏覽器才會去獲取 requie.ensure 方法定義的 math 模塊,因 math 模塊又包含依賴 lodash,因此這個 lodash 第三方模塊也會被按需加載。

          這樣我們就完成了代碼分離的操作,這樣做的優勢就是不需要第一時間加載的模塊,可以推遲加載,以頁面的加載速度。當然,上面的 timeout 定時器完全可以換成其他諸如按鈕點擊之類的事件來觸發。


          主站蜘蛛池模板: 国产精品美女一区二区视频| 天堂Av无码Av一区二区三区| 久久久久久免费一区二区三区| 夜夜精品无码一区二区三区| 肥臀熟女一区二区三区| 久久国产香蕉一区精品| 久久精品免费一区二区三区| 亚洲福利视频一区二区三区| 人妻精品无码一区二区三区| 精品3d动漫视频一区在线观看| 国产乱码伦精品一区二区三区麻豆 | 美女一区二区三区| 国产精品高清一区二区人妖| 精品国产一区二区三区av片| 中文字幕一区二区三区精华液 | 无码一区二区三区AV免费| 国产成人高清亚洲一区久久| 成人日韩熟女高清视频一区| 亚洲性无码一区二区三区| 亚洲国产一区二区三区| 奇米精品视频一区二区三区| 在线精品自拍亚洲第一区| 激情一区二区三区| 在线不卡一区二区三区日韩| 国产自产V一区二区三区C| 亚洲日韩AV无码一区二区三区人 | 麻豆aⅴ精品无码一区二区| 日本一区二区三区在线看| 国产在线观看一区二区三区精品 | 国产传媒一区二区三区呀| 亚洲爆乳精品无码一区二区三区| 日本精品一区二区三本中文| 国99精品无码一区二区三区 | 亚洲国产欧美国产综合一区| 国产亚洲情侣一区二区无| 在线观看精品视频一区二区三区| 国产拳头交一区二区| 国产高清在线精品一区| 东京热人妻无码一区二区av| 日本精品一区二区三区视频| 国产乱码一区二区三区四|