webpack4 搭建企業級腳手架

前端模塊化

commjs使用同步的方式去加載模塊,使用module.export導出,require引入,commjs適合用於在服務端, 由於服務端讀取磁盤文件較快,commjs同步的特性讓其在編譯完成就可使用模塊了。commjs的加載機制是 require的值是module.export的值的拷貝, amd和cmd都是使用的異步的加載機制,主要用於瀏覽器端。 es6模塊是ECMA 提出的javascript模塊化的規範,將會成爲一種通用解決方案。 commjs的加載方式是運行時加載, import是編譯時加載, 編譯時加載能夠作指定輸入,編譯階段作ast語法樹解析能夠進行搖樹優化,還能夠作靜態優化的方案,而編譯時加載則不能,由於他是值的拷貝。javascript

項目構建工具

市面上有不少如rollup,webpack, glup還有Parcel。 rollup專一於處理es mudule的處理,若是要實現其餘模塊的打包會比較繁瑣和複雜,對一些splictChunk的方式支持的並不友好,不支持HMR, rollup更加精而美,如vue、react這些類庫的源碼都是經過rollup進行構建。相比較來講, rollup更適合類庫的搭建,而webpack適合全部項目的構建。 Parcel構建工具在必定意義上實現了零配置便可快速構建項目。配置過程依賴自動安裝,並且自帶多進程工做方式。可是仍是更加推薦webpack,由於webpack是目前爲止最好的構建工具, 在知名度,生態環境,插件市場,還有開發人員的熟知度。這每一點都無比的重要,而webpack是在每一個點上都作到首屈一指的水平,除了它的官方文檔之外。css

構建工具的做用

打包各類類型的資源, 將代碼中分散的js打包到bundle.js中,將一些新特性的語法轉換爲載體(瀏覽器)能夠識別的語法, 代碼轉換,文件優化,代碼分割, 模塊合併, 自動刷新, 代碼校驗, 自動發佈。 構建讓開發流程更加天然,合理的構建讓開發效率更加高效,提升效率和生產力, 目前的webpack還須要手動構建, 但願等到之後會出現一套集成的通用方案,接口暴露的更完全,更友好。 就像之前的java開發使用ssm開發項目,須要進行一大堆的配置,配置過程痛苦並且繁瑣,到了springboot的時代,就沒有那麼繁瑣的配置了。前端構建工具的發展也會隨着一系列的技術迭代而愈來愈友好。html

瞭解webpack

webpack4提供了webpack-cli 啓動webpack,而此時webpack也能夠進行零配置。會給一些必要的配置設置默認值。好比entry默認src/index.js, output默認build/main.js。前端

webpack的配置文件經過webpack.config.js進行修改,配置文件使用commonjs的方式導出配置數據。vue

mode模式

mode模式, develpoment 和 production webpack會根據開發模式的不一樣進行不一樣的項目優化, 而配置人員要針對不一樣的環境進行不一樣的構建流程, 開發環境須要添加一些輔助開發工具和開發打包的一些優化方案,而生產環境則更注重於體積的減少和壓縮模塊的拆分與公用。java

入口和出口

入口和出口,入口指定了從哪一個文件開始來進行構建,bundle是webpack將全部在代碼中零散的模塊進行統一打包,打包到一個bundle中,這個bundle的輸出位置和其餘輸出信息。而輸出位置信息必須是一個絕對路徑。node

打包後的模塊是一個自執行函數,傳入的參數是一個對象,key是打包文件的路徑,值是一個函數,使用eval函數執行其內部邏輯。react

  • webpack爲每個模塊會標識一個moduleId, 因此咱們重複引用的時候並不會進行重複打包,就是多執行一次__webpack_require__(moduleId)拿到installedModules這個中的module cache

context配置

context 配置,context的值是一個路徑, 該路徑是entry的相對路徑也是html-webpack-plugin的相對路徑, 也就是說當咱們在context上下文中配置了一個路徑,那麼entry與htmwebpack-plugin的路徑就是基於該路徑的,webpack

resolve配置

resolve配置 能夠用來配置別名,當在代碼中使用import 引入某個文件的時候可使用這個別名進行路徑的匹配。還能夠對引入文件的擴展名進行簡寫,好比引入import ../index.js能夠簡寫爲 import ../indexgit

resolve: {
    extensions: ['.js', '.vue', '.json'],
    alias: {
        'vue$': 'vue/dist/vue.esm.js',
        '@': resolve('src'),
    }
},
複製代碼

對媒體資源進行打包

對一些媒體資源進行打包,使用url-loader,也可使用file-loader,可是url-loader幾乎有file-loader的全部功能,還能夠配置超過必定大小轉換base64, 在url-loader的name屬性中有可讓咱們自定義文件輸出的路徑,能夠進行對打包後文件的分類和管理。name的值就是打包事後的文件路徑和文件名稱,文件名稱使用[name]佔位符進行替代,文件類型使用[ext]佔位符替代 這裏的媒體資源通常指

// 注意這裏打包圖片的時候,要設置esModule: false,是版本問題,否則會報錯。 
        png|jpe?g|gif|svg
        mp4|webm|ogg|mp3|wav|flac|aac
        // 注意若是你使用了elementui的話,這裏是woff, 而不是woff2, 由於element-ui/lib/theme-chalk內是沒有woff2文件的。
        woff|eot|ttf|otf
        
複製代碼

例如:

{
    test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
    loader: 'url-loader',
    exclude: [resolve('src/icons')],
    include: [resolve('src')],
    options: {
        esModule: false, // 版本問題
        limit: 8192,
        // 自定義目錄和名稱
        name: utils.assetsPath('img/[name].[hash:7].[ext]') // 打包後的文件會輸出到指定目錄的img/[name].[hash:7].[ext]
    }
},
複製代碼

對svg的打包使用svg-sprite-loader

devServer的配置

在development模式中的配置,devServer是很是重要的一個輔助開發工具,提供HMR, gzip壓縮,使用http服務運行,自動打開瀏覽器,跨域代理,對控制檯的輸出控制,靜態資源的路徑一系列的功能, 使用devServer開啓的服務並無產出任何文件,devServer將打包後的結果存放到內存中,在內存中進行對文件的讀取, 極大的提升了效率。devServer內部也是內置了一個express服務器, devServer的contentBase放置沒有被webpack打包的文件,可是若是也須要進行訪問的靜態資源目錄,並且使用服務器路由進行指向,不須要太多文件的輸入和輸出操做。

devServer: {
    clientLogLevel: 'warning',
    historyApiFallback: {
        rewrites: [
            { from: /.*/, to: path.posix.join(config.dev.assetsPublicPath, 'index.html') },
        ],
    },
    hot: true,
    contentBase: false, // since we use CopyWebpackPlugin.
    compress: true,
    host: HOST || config.dev.host,
    port: PORT || config.dev.port,
    // open: config.dev.autoOpenBrowser,
    overlay: config.dev.errorOverlay
        ? { warnings: false, errors: true }
        : false,
    publicPath: config.dev.assetsPublicPath,
    proxy: config.dev.proxyTable,
    quiet: true, // necessary for FriendlyErrorsPlugin
    watchOptions: {
        poll: config.dev.poll,
    }
},
複製代碼

對樣式文件進行打包

webpack對於loader的處理是自下而上的,在處理.css文件時,使用了兩個loader css-loader和vue-style-loader(style-loader),這裏用的是vue項目,而要解析.vue文件中的style樣式,要使用vue-style-loader進行處理。 若是對於預處理樣式文件的話,好比sass文件, 在加上sass-loader處理一遍再給css-loader....。

關於cache-loader的坑

在後面優化的時候,使用了cache-loader進行優化,cache-loader的用法就是寫在loader的最前面,而後進行cache-loader解析,若是沒有改變的,直接從磁盤上拿文件,極大提高了效率,可是解析和讀取文件的過程也須要時間,因此若是是性能開銷極大的loader選擇cache-loader進行處理的話,會顯著的提升打包效率。可是若是遇到了mini-css-extract-plugin.loader的話,仍是將cache-loader放在mini-css-extract-plugin.loader的前面,那麼根本就不會去打包樣式,去cache-loader的issues中找到了這樣一個issue github.com/webpack-con… 你要將cache-loader寫在mini-css-extract-plugin.loader的後面。

  • ps

若是插入太多的style樣式會致使頁面阻塞,因此使用mini-css-extract-plugin對樣式進行提取。可是在開發環境中,我使用mini-css-extract-plugin沒法進行css熱更新,網上找了幾種辦法未解決,使用了style標籤的形式能夠進行熱更新。在生產環境中使用mini-css-extract-plugin。

對js文件進行打包

webpack默認只能處理js和json, 而對於js的處理又是重中之重,咱們須要把js轉換成es2015, 轉換jsx語法,對一些類的靜態方法須要進行polyfile, 對於generator語法的處理,js的分包,懶加載機制都是js文件須要作的,經過babel他們變的簡單了。像一些經過@babel/ployfile不只體積很大,並且會污染全局的沙盒環境,很不推薦。這裏使用core-js進行對類的方法進行polyfile

{
      "presets": [
        [
          "@babel/preset-env",
          {
            "useBuiltIns": "usage", // or "usage"
            "corejs": 3
          }
        ]
      ],
      "plugins": [
        "transform-vue-jsx",
        "@babel/plugin-transform-runtime",
        "@babel/plugin-syntax-dynamic-import"
      ]
    }
複製代碼
由於js模塊較多,就採用了多線程打包的方案,對js文件進行多線程打包,這裏視狀況而定,有些項目不必定須要多線程打包,由於分配線程須要消耗必定的資源,
若是電腦性能好,開的進程少,那麼打包的速度就會提升,反之會下降。
happypack的使用很簡單,匹配到js文件,使用happypack進行打包,指定id,該id會去找plugin中的HappyPack中的id,

對於js使用cache-loader,發現會很大程度上提升打包的速度,配合happypack大概提升8s左右的時間,
這裏的時間不是固定的,是根據項目的大小和複雜程度來的,在必定程度上成正比。
複製代碼
const happyThreadPool = HappyPack.ThreadPool({ size: os.cpus().length });
new HappyPack({
    //用id來標識 happypack處理那裏類文件
    id: 'js',
    //如何處理 用法和loader 的配置同樣
    loaders: [
        {
            loader: 'cache-loader'
        },
        {
            loader: 'babel-loader?cacheDirectory=true',
        }
    ],
    //共享進程池
    threadPool: happyThreadPool,
    //容許 HappyPack 輸出日誌
    verbose: true,
}),
複製代碼
關於happypack對vue文件的多線程打包
vue-loader 15如今不支持happypack 由於happypack內部使用了虛擬的compiler和loaderContext,不建議使用happypack了,
複製代碼

熱模替換

永遠不要再生產環境下使用HMR, 因此咱們只在開發環境中配置。

  • 所謂的熱模替換,即對模塊進行熱插拔,當更新的時候只對變化的模塊進行更新,而不會干擾到程序的總體運行流程和所有刷新,咱們知道刷新意味着內存中數據丟失,須要從新獲取資源,在不少場景中,HMR都很重要。
// devserver中配置
 // 開啓 HMR 特性,若是資源不支持 HMR 會 fallback 到 live reloading
 hot: true,
 // 表示不會回退到 使用刷新的方式進行重載
 // hotOnly: true
 
 // 配置hmr所須要的插件
 new webpack.HotModuleReplacementPlugin(),
複製代碼
  • 對於通常的css和img,hmr開箱即用,可是對於js,hmr卻沒法進行通用方案的支持。由於js導出模塊的不肯定性和引用的不肯定性,hmr沒法對其進行通用的處理方案。若是要對js進行進行熱模替換的話,則須要HotModuleReplacementPlugin提供的api進行手動的處理須要進行hmr的模塊。可是所幸咱們使用的是框架,無論是vue仍是react都集成了hmr的方案,他們會讓這些js模塊進行統一處理,加上moudle.hot方法進行判斷。咱們只須要開箱即用。在vue中 vue-loade 內部使用的 vue-hot-reload-api會集成進去熱重載方案,若是使用原生js的話,那麼就須要咱們進行手動的對js進行熱莫替換, 框架作了一套集成的方案,一樣對於模塊的導出方式和數據結構也是規定好的,這就是爲何框架這麼方便的集成一套通用的熱模替換方案。

配套插件

// 重載以後在控制檯顯示的file name 是更新的文件的名字,默認的是文件的id
new webpack.NamedModulesPlugin(), 
複製代碼

sourcemap

  • 在webpack中 sourcemap的模式的要使用devtool進行開啓, 這裏官網上已經說的很詳細了,

    補充幾個小點:eval模式放在一個臨時的虛擬機中運行,VM154:1 // 
    複製代碼

  • 每段代碼都會經過sourceURL進行聲明

  • eval模式,構建速度很快,能夠獲得錯誤在源代碼中的行列,可是沒法查看具體的行列信息, 由於它沒法查看源代碼,只能查看轉換後的代碼。

  • source-map模式 , 會產生.map文件,將打包好的文件經過.map文件能夠映射到源文件,

  • eval-source-map 能夠定位文件,並且能夠定位具體的行列信息(由於能夠生成source-map映射文件),

  • cheap-eval-source-map 能夠定位文件,可是沒法定位到具體的列,只能定位到具體的行,並且是loader轉換以後的代碼,可是構建速度獲得提升

  • cheap-module-eval-source-map 和 cheap-eval-source-map 相似,可是是loader轉換以前的源文件代碼。

在開發環境中devtool通常狀況下項目的最適合的選擇的是cheap-module-eval-source-map 他的打包和構建速度能夠,並且會產品源代碼,能夠進行調試,生產環境使用false,構建和打包速度最快,不會產生源代碼和其餘額外文件。若是在生產環境中出現了bug,而其餘環境中沒有這個bug,想要定位並且還不想暴露源代碼的話,可使用nosources-source-map

開發環境優化

dll優化: 在項目中,咱們一般會使用到一些第三方包,並且這些第三方包版本固定,不會迭代更新,而咱們每次打包都要這些第三方包進行打包,很浪費時間,因此開發環境咱們使用dll連接庫進行對一些固定的文件進行先行打包,而後使用manifest進行引入,咱們先新建dll_webpack配置文件(配置development模式), 而後在開發環境須要的webpack配置文件中使用webpack自帶的DllReferencePlugin插件進行橋接已經打包好的第三方資源。 咱們須要在index.html中手動引入已經打包好的dll.js文件,咱們能夠經過AddAssetHtmlPlugin插件將打包好的js文件動態的導入到index.html中

// webpack.dll.config.js
const webpack = require('webpack')
const utils = require('./utils')
const config = require('../config')
const path = require('path')

module.exports = {
  mode: 'development',
  entry: {
    hk: ['vue/dist/vue.esm.js', 'vuex', 'echarts', 'vue-router', 'element-ui']
  },
  output: {
    filename: '_dll_[name].js',
    // path: path.resolve(__dirname, 'dll'),
    path: path.resolve(__dirname, 'dll'),
    library: '_dll_[name]',
    // libraryTarget: 'var'
  },
  plugins: [
    new webpack.DllPlugin({
      name: '_dll_[name]',
      // path: path.resolve(__dirname, 'dll', 'manifest.json')
      path: path.resolve(__dirname, 'dll', 'manifest.json'),
    })
  ]
}
// webpack.config.js
// 將dll連接庫的內容放到html中
new AddAssetHtmlPlugin({ filepath: require.resolve('./dll/_dll_hk.js') }),
new webpack.DllReferencePlugin({
    manifest: path.resolve(__dirname, 'dll', 'manifest.json')
}),
複製代碼

生產環境優化

使用mini-css-extract-plugin時壓縮代碼

當使用mini-css-extract-plugin進行提取css文件時, css文件不會被打包, 咱們可使用在minimizer中使用optimize-css-assets-webpack-plugin 進行壓縮css代碼,可是當使用minimizer屬性的時候,webpack就會認爲你再也不使用默認的壓縮方式了,而是使用自定義的壓縮方式。因此還要手動的添加webpack內置的壓縮js的插件terser-webpack-plugin
複製代碼
optimization: {
    minimize: true, // production模式會自動開啓搖樹優化 usedExports 和 minimize 和 sideEffects 
    splitChunks: {
        chunks: "all",
        minSize: 16000, // 模塊的最小體積
        minChunks: 1, // 模塊的最小被引用次數
        maxAsyncRequests: 5, // 按需加載的最大並行請求數
        maxInitialRequests: 3, // 一個入口最大並行請求數
        automaticNameDelimiter: '~', // 文件名的鏈接符
        name: true,
        cacheGroups: { // 緩存組
            vendors: {  //第三方
                test: /[\\/]node_modules[\\/]/,
                priority: -10//權重
            },
            default: {
                minChunks: 2,
                priority: -20,
                reuseExistingChunk: true
            }
        }
    },
    minimizer: [
        new TerserJSPlugin({
            cache: true, // 是否緩存
            parallel: true, // 是否並行打包
            sourceMap: false // 是否開始源碼映射
        }),
        new OptimizeCSSPlugin({})
    ],
},
複製代碼
  1. tree-shaking 搖樹優化 搖樹也就是將晃動將樹上的枯黃的樹葉讓其掉落下來,造成一顆新樹。 而webpack會將那些未被引用的代碼經過搖樹優化進行去除。tree-shaking配合optimization 選項開啓minimize會自動壓縮掉未被使用的代碼。 對於webpack來說,並非全部的模塊代碼均可以被tree-shaking,一個較典型的就是loadsh,若是你引入了loadsh,只用了其中一個方法,webpack仍然會將loadsh其餘的方法所有打包,tree-shaking失效了,由於loadsh默認的導出方式是commonjs,而tree-shaking的前提就是模塊是esModule。
  2. scope hoisting 若是沒有scope hoisting的話,打包以後在一個自執行函數中會存在一些變量,這些變量的做用就是通過一系列的計算對另一個結果產生影響。而這些東西運行在瀏覽器端會消耗內存進行存儲,有可能還會產生大量的閉包(須要規定函數做用域)形成內存消耗,而scope hoisting能夠將這些靜態放在一個做用域中,進行合併計算。減小了打包的體積和減小了內存的開銷。
  3. splitChunks 對代碼進行分包, splitchunk會將咱們項目中的資源模塊按照設計的規則打包到不一樣的bundle中。 在項目中主要用到的就是與ESModules 的動態導入特性相結合,按需加載模塊。並且能對代碼公共部分進行提取。對第三方vendors和業務代碼進行提取
相關文章
相關標籤/搜索