【譯文】四種使用webpack提升Vue應用的方式

前端深夜告解室發表於2019-03-04

作者:孫輝,美團金融前端團隊成員。15年畢業加入美團,相信技術,更相信技術只是大千世界裡知識的一種,個人部落格: sunyuhui.com

譯者注:本篇文章所提到的幾個措施大家可能都曾經在專案裡用過,但是就如作者所言:你只是在用,並不知道為什麼用,本文最大的價值在於提供了系統的優化方案並解釋了原因,原文:4 Ways To Boost Your Vue.js App With Webpack

webpack是開發Vue單頁應用必不可少的工具,它能管理複雜的構建步驟,並且優化你的應用大小和效能, 使你的開發工作流更加簡單。

在這篇文章中,我將解釋使用webpack提升你的Vue應用的4種方式,包括:

  1. 單檔案元件
  2. 優化Vue構建過程
  3. 瀏覽器快取管理
  4. 程式碼分離

關於vue-cli

如果你在使用 vue-cli 提供的模板來構建你的應用,那麼webpack的相關配置已經提供好了,這些配置已經經過很好地優化,我也給不了你其他的優化建議了。

不過因為配置是開箱即用的,所以你很有可能並不知道這些配置真正在做什麼,由於 vue-cli 裡提供了我將要討論的優化措施,所以,你可以把這篇文章當做模板裡webpack配置的概述。

1.單檔案元件

Vue的特點之一就是使用了基於HTML的模板元件,這帶來了一個本質問題:要麼HTML標記使用醜陋的JavaScript字串,要麼將模板內容和元件定義寫在不同的檔案中。這帶來了一些困難。

Vue提供了一種叫做Single File Components(SFCs)的方式來解決這個問題,將模板、元件定義、CSS寫在一個 .vue 檔案裡。

MyComponent.vue

<template>
  <div id="my-component">...</div>
</template>
<script>
  export default {...}
</script>
<style>
  #my-component {...}
</style>複製程式碼

Vue-loader 使得 SFCs成為可能,這個 webpack loader 將SFCs分隔成不同語言塊,然後輸出到合適的loader,例如 script塊 輸出到babel-loader, 模板塊輸出到Vue自己的vue-template-loader,該loader能夠將模板轉換成JavaScript的render函式。

vue-loader的最終輸出是一個將要包含在 Webpack bundle 中的JavaScript模組。

典型的vue-loader配置如下所示:

module: {
  rules: [
    {
      test: /\.vue$/,
      loader: 'vue-loader',
      options: {
        loaders: {
          // Override the default loaders
        }
      }
    },
  ]
}複製程式碼

2.優化Vue構建

執行時構建

如果在你的應用中僅僅使用 render函式,不需要HTML模板,那你不需要使用Vue的模板編譯器,通過去掉模板編譯器,可以讓你在Webpack構建過程中,減少bundle的體積。

記住單檔案元件在開發模式中使用了預編譯

Vue的執行時構建版本包含了Vue的所有特點,除了模板編譯器,被稱為vue.runtime.js,體積比全功能版本小了20KB,所以這值得你嘗試一下。

預設情況下使用的是執行時構建版本,所以當你使用 import vue from 'vue' 來引用Vue的時候,你得到的是執行時構建版本,不過你能通過 alias 配置項來改變。

resolve: {
  alias: {
    'vue$': 'vue/dist/vue.esm.js' // Use the full build
  }
}複製程式碼

譯者注:在Vue模組中包含8個檔案,各檔案的區別可以參考:官方文件,在需要使用完整版時使用了執行時版本會報warn:

You are using the runtime-only build of Vue where the template option is not available. Either pre-compile the templates into render functions, or use the compiler-included build.

production環境中去掉 warn 和 error 資訊

另外一個減少Vue構建體積的方式是在 production環境中去掉所有 error 和 warn資訊,這些不必要的程式碼導致你打包後的體積膨脹,而且增加了執行時耗時,你最好避免這些消耗。

如果你除錯Vue的原始碼,你會發現這些提示資訊都是通過環境變數 process.env.NODE_ENV 的值來判斷的,例如:

if (process.env.NODE_ENV !== 'production') {
  warn(("Error in " + info + ": \"" + (err.toString()) + "\""), vm);
}複製程式碼

如果 process.env.NODE_ENV 的值設定成 production ,那麼提示資訊在構架過程中就會自動被剔出去。

可以通過使用 DefinePlugin 去設定 process.env.NODE_ENV 的值,同時使用 UglifyJsPlugin 去減小程式碼體積並且去掉不需要的程式碼塊。

if (process.env.NODE_ENV === 'production') {
  module.exports.plugins = (module.exports.plugins || []).concat([
    new webpack.DefinePlugin({
      'process.env': {
        NODE_ENV: '"production"'
      }
    }),
    new webpack.optimize.UglifyJsPlugin()
  ])
}複製程式碼

譯者注:實際專案中使用的更多的方式是 production環境 和 其他環境 使用不同的webpack配置檔案.

3.瀏覽器快取管理

瀏覽器能夠快取你的站點檔案,只有在你本地沒有副本時或者副本已經過期時才會重新下載。

如果你所有的程式碼都在一個檔案裡,那一個微小的改動也會導致整個檔案的下載,理想情況下,你想要你的使用者儘可能少的下載檔案,所以將頻繁改動的程式碼和不怎麼改動的程式碼分開是非常明智的。

Vendor file

Common Chunks外掛能把你的Vendor程式碼(例如Vue.js這些不會經常改動的依賴包)和應用程式碼(每次開發過程中都會改動的程式碼)分離開。

你能配置外掛檢查一個依賴是否來自於node_modules,如果是,那就打包到vendor.js檔案。

new webpack.optimize.CommonsChunkPlugin({
  name: 'vendor',
  minChunks: function (module) {
    return module.context && module.context.indexOf('node_modules') !== -1;
  }
})複製程式碼

這麼做之後,在構建後的輸出檔案中,將有兩個獨立的檔案,能夠分別被瀏覽器快取。

<script src="vendor.js" charset="utf-8"></script>
<script src="app.js" charset="utf-8"></script>複製程式碼

指紋

當構建後的檔案改動了,我們該怎麼丟棄快取呢?

預設情況下,只有當一個快取檔案過期,或者使用者手動清除快取,瀏覽器才會重新從伺服器請求檔案,如果伺服器提示檔案已經改動,那檔案才會重新被下載(如果返回304則不會)。

為了避免不必要的請求,我們可以在每次檔案內容改動時,改變檔案的名字,從而強制瀏覽器重新下載,通過在檔名稱後面新增一個"指紋":hash,我們可以非常容易達到這個目的。

cache control
cache control

Common Chunks外掛生成"chunkhash",能隨著檔案的改動而更新,Webpack能在輸出時,將這個hash值新增到檔名稱末尾。

output: {
  filename: '[name].[chunkhash].js'
},複製程式碼

這樣做的話,你會發現輸出的檔名稱類似於app.3b80b7c17398c31e4705.js

譯者注:chunkhash並不需要Common Chunks生成,而是webpack自動生成的,而且據官方文件Common Chunks是無法生成chunkhash的,作者在這裡這麼寫讓我摸不著頭腦, 另外,還有一種新增hash值的方式是使用

output: {
  filename: '[name].[hash].js'
},複製程式碼

其中的區別主要在於chunkhash是基於內容生成的hash值,而hash值是基於模組標識(webpack打包時每個模組都有一個唯一標識),hash值的主要問題在於任何一個檔案更新之後都會更新hash值,導致那些內容沒有更新的檔案的檔名也更新了,需要重新下載。具體區別可參考:webpack配置:快取

自動插入構建檔案

當然了,增加了hash值之後,你就必須要在index檔案裡更新你的引用,否則瀏覽器是不會知道的。

<script src="app.3b80b7c17398c31e4705.js"></script>複製程式碼

手動去做這件事將是一件非常沉悶無聊的事情,可以使用HTML Webpack Plugin來做這件事。這個外掛能在構建過程中自動在你的HTML檔案裡插入對構建檔案的引用。

先來把構建檔案中的引用去掉

index.html

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <title>test-6</title>
  </head>
  <body>
    <div id="app"></div>
    <!-- built files should go here, but will be auto injected -->
  </body>
</html>複製程式碼

然後在Webpack配置裡增加HTML Webpack Plugin的配置。

new HtmlWebpackPlugin({
  filename: 'index.html'
  template: 'index.html',
  inject: true,
  chunksSortMode: 'dependency'
})複製程式碼

現在帶有hash值的構建檔案將會自動增加到index檔案,同時,你的index.html檔案也會被包含在輸出檔案裡,所以你可能需要將這一點告訴伺服器。

4.程式碼分離

預設情況下,Webpack將把你的所有應用程式碼輸出到一個大的構建檔案中,但是如果你有多個頁面,分隔程式碼將每個獨立的頁面輸出到不同的檔案中,只有在需要的時候才去載入。

Webpack有個功能 "code splitting",可以用來做這件事,在Vue.js中需要使用async components,配合Vue Router使用更簡單。

Async componets

相比以一個定義物件作為第二個引數,Async components第二個引數是一個Promise函式,該函式將一個物件resolve,例如:

Vue.component('async-component', function (resolve, reject) {
  setTimeout(() => {
    resolve({
      // Component definition including props, methods etc.
    });
  }, 1000)
})複製程式碼

Vue只有在這個元件需要被渲染時,才會呼叫這個函式,同時也會為以後的預渲染快取返回結果。

如果我們將每個頁面都當做一個元件,並且將定義物件放在伺服器端,那在程式碼分離的路上,我們已經走了一半了。

require

為了從伺服器上載入你的非同步元件程式碼。使用Webpack的require語法,這將通知Webpack將async-component打包到一個分隔的bundle,更妙的是,Webpack將使用AJAX處理bundle的載入,所以你的程式碼可以像下面這樣簡單:

Vue.component('async-component', function (resolve) {
  require(['./AsyncComponent.vue'], resolve)
});複製程式碼

懶載入

在Vue.js應用中,vue-router是一個典型模組,你可以用它來將SPA轉換成多頁應用,懶載入是利用Vue和Webpack實現程式碼分離的官方推薦方式。

const HomePage = resolve => require(['./HomePage.vue'], resolve);

const rounter = new VueRouter({
  routes: [
    {
      path: '/',
      name: 'HomePage',
      component: HomePage
    }
  ]
})複製程式碼

最後,團隊為了招聘方便,整了個公眾號,主要是一些招聘資訊,團隊資訊,所有的技術文章在公眾號裡也可以看到,對了,如果你想去美團其他團隊,我們也可以幫你內推哦 ~

二維碼
二維碼

done

相關文章