9102年:從0開始手寫一個Vue.js優化版腳手架

前言:在看本文前,建議你看下,下面這兩篇文章 順便給個贊和github的贊哦~javascript

  • 若是你對webpack不是很瞭解,請你關注我以前的文章,都是百星以上star的高質量文css

    • 文章內容都會不按期更新 記得必定要收藏
    • webpack用了會上癮,它也是突破你技術瓶頸的好方向,如今基本上任何東西都離不開webpack,webpack用得好,什麼next nuxt隨便上手(本人體會很深),本人蔘考了Vue腳手架,京東的webpack優化方案,以及本人的其餘方面優化,着重在生產模式下的構建速度優化提高很是明顯(固然開發環境下也是~),性能提高很明顯哦~
    • 本配置完成功能:
    • 識別.Vue文件和template模板
    • tree shaking 搖樹優化 刪除掉無用代碼
    • 識別 async / await和 箭頭函數
    • PWA功能,熱刷新,安裝後當即接管瀏覽器 離線後仍讓能夠訪問網站 還能夠在手機上添加網站到桌面使用
    • preload 預加載資源prefetch按需請求資源
    • CSS模塊化,不怕命名衝突
    • 小圖片的base64處理
    • 文件後綴省掉jsx 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,若是不懂,看我上面的文章哦~html

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'
        ]
      }
    ]
  }

複製代碼

這裏面咱們重點關注 moduleplugins屬性,由於今天的重點是編寫loaderplugin,須要配置這兩個屬性。vue

  • webpack 啓動後,在讀取配置的過程當中會先執行 new MyPlugin(options) 初始化一個MyPlugin得到其實例。在初始化 compiler對象後,再調用 myPlugin.apply(compiler) 給插件實例傳入 compiler對象。 插件實例在獲取到 compiler對象後,就能夠經過 compiler.plugin(事件名稱, 回調函數) 監聽到 Webpack廣播出來的事件。 而且能夠經過 compiler 對象去操做 webpack。java

  • Compiler 對象包含了 Webpack 環境全部的的配置信息,包含options,loaders,plugins 這些信息,這個對象在Webpack啓動時候被實例化,它是全局惟一的,能夠簡單地把它理解爲Webpack 實例;webpack

  • Compilation象包含了當前的模塊資源、編譯生成資源、變化的文件等。當 Webpack 以開發模式運行時,每當檢測到一個文件變化,一次新的Compilation 將被建立。Compilation 對象也提供了不少事件回調供插件作擴展。經過Compilation 也能讀取到 Compiler` 對象。git

  • Compiler 和 Compilation的區別在於:es6

  • Compiler 表明了整個Webpack 從啓動到關閉的生命週期,而 Compilation 只是表明了一次新的編譯。github

  • 事件流web

  • webpack經過 Tapable來組織這條複雜的生產線。

  • webpack的事件流機制保證了插件的有序性,使得整個系統擴展性很好。

  • webpack 的事件流機制應用了觀察者模式,和 Node.js 中的 EventEmitter很是類似。

1.2 打包原理

  • 識別入口文件

  • 經過逐層識別模塊依賴。(Commonjs、amd或者es6import,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功能
   

{
        test: /\.(js|jsx)$/,
        use:
        {
            loader: 'babel-loader',
            options: {
                presets:   ["@babel/preset-env", { "modules": false }] ,//附帶`tree shaking`
                plugins: ["@babel/plugin-syntax-dynamic-import"]
                     },
                cacheDirectory: true//開啓babel編譯緩存
        }
   },
複製代碼

在使用上面的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`的使用~ 
> 腳手架的搭建過程不少坑,可是卻能大大提高你的技術天花板,跟着做者一塊兒踩坑吧,別忘了來個人`github`點贊哦~ [倉庫源碼地址~歡迎star][4]




  [1]: /img/bVbsKJj
  [2]: https://segmentfault.com/a/1190000019126657
  [3]: https://segmentfault.com/a/1190000018827395
  [4]: https://github.com/JinJieTan/React-webpack複製代碼
相關文章
相關標籤/搜索