天天看點

2019年!手寫一個Vue的腳手架 【極緻優化版】!注意

2019年!手寫一個Vue的腳手架 【極緻優化版】!注意
  • 使用最新版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

       對

      象包含了目前的子產品資源、編譯生成資源、變化的檔案等。當 Webpack 以開發模式運作時,每當檢測到一個檔案變化,一次新的 

      Compilation 

      将被建立。

      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

子產品化方案,如果你不是很懂,那麼看起來很費勁,我寫的腳手架,就不使用子產品化方案了,簡單

  • 開始開發環境配置
  • 包管理器 使用

    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基礎、入門、精通、架構師全套資源》