前端基礎整理-webpack(配置思路)

前言

前端工程化主要解決 效率質量的問題;webpack是一種實現前端工程化的有效方案。
基本配置思路圖

基本概念

webpack相似一個加工處理器,經過靈活的配置文件實現複雜需求;主要配置有入口、出口、裝在器、插件
資料
  • entry/output: 出口/入口
  • loader/plugin: 裝載器/插件 加工處理
  • moudle/chunk/bundle:javascript

    • moudle: 源碼每一個文件可稱爲一個模塊
    • chunk: 經過webpack處理後的文件爲chunk代碼塊,通常由多個模塊構成
    • bundle: 最終產品,通常爲chunk處理後的代碼塊

主要任務

自動化處理js,css,html,圖片等靜態資源

腳本處理

主要讓javascript適應宿主環境,竟可能的壓縮體積和複用
es6

ployfill : 全局墊片,注入全局對象,污染全局
runtime : 局部墊片,按需局部注入,不會污染全局
presetscss

// .babelrc
{
  "presets": [
    ["@babel/preset-env", {
      "useBuiltIns": "usage|entry|false" // usage 按需加載polyfill
    }]
  ]
}

browerslist : 兼容到目標瀏覽器標準html

ts

ts / tsconfig前端

// ts.config.json
{  
  "compilerOptions": {  
    "module": "commonjs",  
    "target": "es5"  
  },  
  "exclude": \["./node\_modules"\]  
}

樣式處理

css處理

style-loader (合併style標籤,transform插入頁面前對其修改)
css-loader (css模塊化功能)(importLoaders解決css中@import未能處理loader的問題)
mini-css-extract-plugin(提取css至單獨文件)vue

css預處理

less-loader
sass-loader
stylus-loaderjava

css後處理

postcss
autoprefixer : 兼容瀏覽器樣式
postcss-preset-env : 可設置目標css規範
cssnano是一個強大的 PostCss 插件,在 CSS 壓縮優化中會常常被用到,它有別於常規的 CSS 壓縮工具只是去除空格註釋,還支持根據 CSS 語法解析結果智能壓縮代碼node

靜態資源處理

html處理

html-webpack-plugin : 打包並處理html文件至目標文件夾react

文件處理

file-loader : 容許js加載其餘資源;如, 圖片,字體,媒體 等
url-loader : 在file-loader基礎上封裝;如, 容許將圖片轉成base64格式webpack

tips: 注意3.0版本 esMoudle屬性
圖片

svg-url-loader 的工做原理相似於 url-loader,除了它利用 URL encoding 而不是 Base64 對文件編碼。
能夠藉助img-webpack-loader來對使用到的圖片進行優化。它支持 JPG、PNG、GIF 和 SVG 格式的圖片。git

// webpack.config.js
module.exports = {
    module: {
        rules: [
            {
                test: /\.(jpe?g|png|gif|svg)$/,
                loader: 'image-webpack-loader',
                // 這會應用該 loader,在其它以前
                enforce: 'pre'
            }
        ]
    }
};

經過enforce: 'pre'咱們提升了 img-webpack-loader 的優先級,保證在url-loadersvg-url-loader以前就完成了圖片的優化。
另外img-webpack-loader默認的配置就已經適用於平常開發圖片的壓縮優化需求了,可是若是你想更進一步去配置它,參考插件選項。要選擇指定選項,請查看國外牛人寫的一個圖像優化指南

字體/媒體
{
    // 文件解析
    test: /\.(eot|woff|ttf|woff2|appcache|mp4|pdf)(\?|$)/,
    loader: 'url-loader',
    query: {
        // 這麼多文件,ext不一樣,因此須要使用[ext]
        name: 'assets/[name].[hash:7].[ext]'
    }
},
數據

要導入 CSV,TSV 和 XML,可使用csv-loaderxml-loader

{
    test: /\.(csv|tsv)$/,
    use: [
    'csv-loader'
    ]
},
{
    test: /\.xml$/,
    use: [
    'xml-loader'
    ]
}
CDN部署
通常靜態資源上線的時候都會放到 CDN,假設咱們的 CDN 域名和路徑爲: http://bd.xxx.com/img/,這時候只須要修改 output.publicPath便可
module.exports = {
    //..
    output: {
        publicPath: 'http://bd.xxx.com/img/'
    }
    //..
};

框架處理

vue

vue-loader / vue-style-loader

react

jsx
hot-module-replacement-plugin

// babelrc
presets: [
    // 添加 preset-react
    require.resolve('@babel/preset-react'),
    [require.resolve('@babel/preset-env'), {modules: false}]
],
// index.js
if (module.hot) {
    module.hot.accept(err => {
        if (err) {
            console.error('Cannot apply HMR update.', err);
        }
    });
}

輔助任務

本地開發調試

webapck-dev-server( 錯誤遮罩,代理請求,熱更新,重定向)
webpack.HotModuleReplacementPlugin 插件來開啓全局的 HMR 能力
// webpack.config.js
const path = require('path');
module.exports = {
    entry: './src/index.js',
    devServer: {
        contentBase: path.join(__dirname, 'dist'),
        port: 9000,
        // 開啓 hmr 支持
        hot: true,
        overlay: true, // 錯誤遮罩
        proxy: { // 接口代理
            ...
        }
    },
    plugins: [
        // 添加 hmr plugin
        new webpack.HotModuleReplacementPlugin()
    ]
};
// 在入口文件index.js最後添加以下代碼
if (module.hot) {
    // 通知 webpack 該模塊接受 hmr
    module.hot.accept(err => {
        if (err) {
            console.error('Cannot apply HMR update.', err);
        }
    });
}

錯誤定位

devtool開啓sourceMap方便定位錯誤 : evl-source-map(開發) / source-map(生產)
module.exports = {
 ...
 devtool: 'evl'
}

代碼風格檢查

{
    test: /\.js$/,
    loader: 'eslint-loader',
    enforce: 'pre',
    include: [path.resolve(__dirname, 'src')], // 指定檢查的目錄
    options: { // 這裏的配置項參數將會被傳遞到 eslint 的 CLIEngine
        formatter: require('eslint-friendly-formatter') // 指定錯誤報告的格式規範
    }
}
const StyleLintPlugin = require('stylelint-webpack-plugin');

module.exports = {
    // ...
    plugins: [new StyleLintPlugin(options)]
    // ...
};

優化方案

代碼質量方向

代碼分割

  • 多入口分割思路: 主業務代碼 + 公共依賴 + 第三方依賴 + webapck運行代碼
  • 單入口分割思路: 主業務代碼 + 異步依賴 + 第三方依賴 + webpack運行代碼
splitChunks

因爲 Webpack 作到了開箱即用,因此splitChunks是有默認配置的:

module.exports = {
    // ...
    optimization: {
        splitChunks: {
            chunks: 'async', // 三選一: "initial" | "all" | "async" (默認)
            minSize: 30000, // 最小尺寸,30K,development 下是10k,越大那麼單個文件越大,chunk 數就會變少(針對於提取公共 chunk 的時候,無論再大也不會把動態加載的模塊合併到初始化模塊中)當這個值很大的時候就不會作公共部分的抽取了
            maxSize: 0, // 文件的最大尺寸,0爲不限制,優先級:maxInitialRequest/maxAsyncRequests < maxSize < minSize
            minChunks: 1, // 默認1,被提取的一個模塊至少須要在幾個 chunk 中被引用,這個值越大,抽取出來的文件就越小
            maxAsyncRequests: 5, // 在作一次按需加載的時候最多有多少個異步請求,爲 1 的時候就不會抽取公共 chunk 了
            maxInitialRequests: 3, // 針對一個 entry 作初始化模塊分隔的時候的最大文件數,優先級高於 cacheGroup,因此爲 1 的時候就不會抽取 initial common 了
            automaticNameDelimiter: '~', // 打包文件名分隔符
            name: true, // 拆分出來文件的名字,默認爲 true,表示自動生成文件名,若是設置爲固定的字符串那麼全部的 chunk 都會被合併成一個
            /** =========核心配置cacheGroups======= **/
            cacheGroups: { 
                vendors: {
                    test: /[\\/]node_modules[\\/]/, // 正則規則,若是符合就提取 chunk
                    priority: -10 // 緩存組優先級,當一個模塊可能屬於多個 chunkGroup,這裏是優先級
                },
                default: {
                    minChunks: 2,
                    priority: -20, // 優先級
                    reuseExistingChunk: true // 若是該chunk包含的modules都已經另外一個被分割的chunk中存在,那麼直接引用已存在的chunk,不會再從新產生一個
                }
            }
        }
    }
};

splitChunks默認配置對應的就是 chunk 生成的第二種狀況:經過寫代碼時主動使用import()或者require.ensure來動態加載。

Tips:除了 JavaScript, splitChunks也適用於使用 mini-css-extract-plugin插件的 css 配置。
runtimeChunk
Webpack 打包時,除了模塊代碼以外,Webpack 的 bundle 中還包含了 Runtime(運行時),這部分代碼是一小段用來管理模塊執行和加載的代碼。
// webpack.config.js
module.exports = {
    optimization: {
        runtimeChunk: true
    }
};
// 分離webapck 運行時代碼
打包分析

體積優化

js壓縮

mode=production下,Webpack 會自動壓縮代碼,咱們能夠自定義本身的壓縮工具,這裏推薦 terser-webpack-plugin,它是 Webpack 官方維護的插件

const TerserPlugin = require('terser-webpack-plugin');

module.exports = {
    optimization: {
        minimizer: [new TerserPlugin({
            // 使用 cache,加快二次構建速度
            cache: true,
            terserOptions: {
                comments: false,
                compress: {
                    // 刪除無用的代碼
                    unused: true,
                    // 刪掉 debugger
                    drop_debugger: true, // eslint-disable-line
                    // 移除 console
                    drop_console: true, // eslint-disable-line
                    // 移除無用的代碼
                    dead_code: true, // eslint-disable-line
                    parallel: true   // 多線程
                }
            }
        })]
    }
};

做用域提高(Scope Hoisting)是指 webpack 經過 ES6 語法的靜態分析,分析出模塊之間的依賴關係,儘量地把模塊放到同一個函數中。

Tips:其實 webpack 4 中,在 production 模式下已經根據大多數項目的優化經驗作了通用的配置,相似 Tree-Shaking、Scope Hoisting 都是默認開啓的,並且最新版本的 Webpack 使用的壓縮工具就是 terser-webpack-plugin。
tree-shaking

在 Webpack 中,Tree-Shaking 是須要配合mode=production來使用的,這是由於 Webpack 的 Tree-Shaking 實際分了兩步來實現

  1. Webpack 本身來分析 ES6 Modules 的引入和使用狀況,去除不使用的import引入;
  2. 藉助工具(如 uglifyjs-webpack-pluginterser-webpack-plugin)進行刪除,這些工具只在mode=production中會被使用。
// babelrc
presets: [
    [require.resolve('@babel/preset-env'), {modules: false}] // 防止babel轉義模塊,致使tree-shaking實效
],
css壓縮

首先咱們的 CSS 文件應該是導出到單獨的 CSS 文件中,而不要直接打包到 JavaScript 文件中,而後經過style-loaderaddStyles方法添加進去,導出 CSS 文件就須要使用mini-css-extract-plugin這個插件

const MiniCssExtractPlugin = require('mini-css-extract-plugin');
module.exports = {
    plugins: [
        new MiniCssExtractPlugin({
            filename: '[name].css',
            chunkFilename: '[name].[contenthash:8].css'
        })
    ],
    module: {
        rules: [
            {
                test: /\.css$/,
                use: [
                    {
                        loader: MiniCssExtractPlugin.loader,
                        options: {
                            publicPath: '../',
                            hmr: process.env.NODE_ENV === 'development'
                        }
                    },
                    'css-loader'
                ]
            }
        ]
    }
};

cssnano是基於 postcss 的一款功能強大的插件包,它集成了 30 多個插件,只須要執行一個命令,就能夠對咱們的 CSS 作多方面不一樣類型的優化,
在 Webapck 中,css-loader 已經集成了 cssnano,咱們還可使用optimize-css-assets-webpack-plugin來自定義 cssnano 的規則。optimize-css-assets-webpack-plugin 是一個 CSS 的壓縮插件,默認的壓縮引擎就是 cssnano。咱們來看下怎麼在 Webpack 中使用這個插件:

// webpack.config.js
const OptimizeCssAssetsPlugin = require('optimize-css-assets-webpack-plugin');
module.exports = {
    plugins: [
        new OptimizeCssAssetsPlugin({
            assetNameRegExp: /\.optimize\.css$/g,
            cssProcessor: require('cssnano'), // 這裏制定了引擎,不指定默認也是 cssnano
            cssProcessorPluginOptions: {
                preset: ['default', {discardComments: {removeAll: true}}]
            },
            canPrint: true
        })
    ]
};
圖片資源壓縮

若是咱們的項目中小圖片特別多,例若有不少 icon 類的圖標,這時候則推薦使用雪碧圖(CSS Sprite)來合併這些小圖到一張大圖中,而後使用background-position來設置圖片的位置,經過這樣的方式能夠節省屢次小圖片的請求。

// postcss.config.js
const postcssSprites = require('postcss-sprites');

module.exports = {
    plugins: [
        postcssSprites({
            // 在這裏制定了從哪裏加載的圖片被主動使用css sprite
            // 能夠約定好一個目錄名稱規範,防止所有圖片都被處理
            spritePath: './src/assets/img/'
        })
    ]
};
// tips: 雪碧圖的位置時根據圖片原圖的位置而肯定的,在多端適配問題上可能會有問題

對於大圖片來講,可使用image-webpack-loader來壓縮圖片,image-webpack-loader 它支持 JPG、PNG、GIF 和 SVG 格式的圖片,所以咱們在碰到全部這些類型的圖片都會使用它。

緩存優化

webpack.mode介紹
瀏覽器只會在文件名發生改變(或者瀏覽器緩存策略失效)時纔會請求網絡。在使用 Webpack 構建項目的時候,一樣能夠作到自動更新,但 Webpack 使用的不是版本號,而是指定哈希值(hash),Webpack 的 hash 值有三種:

  • hash:每次編譯 Compilation 對象的 hash,全局一致,跟單次編譯有關,跟單個文件無關,不推薦使用
  • chunkhash:chunk 的 hash,根據不一樣的 chunk 及其包含的模塊計算出來的 hash,chunk 中包含的任意模塊發生變化,則 chunkhash 發生變化,推薦使用
  • contenthash:CSS 文件特有的 hash 值,是根據 CSS 文件內容計算出來的,CSS 發生變化則其值發生變化,推薦 CSS 導出中使用

Javascript運行時優化

const PrepackWebpackPlugin = require('prepack-webpack-plugin').default;
 
const configuration = {};
 
module.exports = {
  // ...
  plugins: [
    new PrepackWebpackPlugin(configuration)
  ]
};

開發效率方向

項目自己相關

  • 減小依賴嵌套深度
  • 合理使用 polyfill,防止多餘的代碼
  • 使用 ES6 語法,儘可能不使用具備反作用的代碼,以增強 Tree-Shaking 的效果

Webpack相關

減小查找過程

使用 resolve.alias減小查找過程
使用 resolve.extensions 優先查找
合理配置 rule 的查找範圍,設置inclue,exclude範圍

module.exports = {
    resolve: {  
      // 順序從前到後查找
      extensions: \['.js', '.jsx', '.ts', '.tsx', '.vue'\],  
      // 使用 alias 把導入 react 的語句換成直接使用單獨完整的 react.min.js 文件,
      // 減小耗時的遞歸解析操做
      alias: {  
         react: path.resolve(__dirname, './node_modules/react/dist/react.min.js'),
        '@assets': resolve('./src/assets')  
      }  
    },
    // 排除不須要解析的模塊
    module: {
        noParse: /node_modules\/(jquey\.js)/;
    }
};
利用多線程提高構建速度

thread-loader : 是針對 loader 進行優化的,它會將 loader 放置在一個 worker 池裏面運行,以達到多線程構建

// webpack.config.js

module.exports = {
    module: {
        rules: [
            {
                test: /\.js$/,
                include: path.resolve('src'),
                use: [
                    'thread-loader'
                    // 你的高開銷的loader放置在此 (e.g babel-loader)
                ]
            }
        ]
    }
};

happypack : 利用多線程模型來提升構建速度 / 支持列表

// webpack.config.js
const os = require('os');
const HappyPack = require('happypack');
// 根據 cpu 數量建立線程池
const happyThreadPool = HappyPack.ThreadPool({size: os.cpus().length});
module.exports = {
    module: {
        rules: [
            {
                test: /\.js$/,
                use: 'happypack/loader?id=jsx'
            },

            {
                test: /\.less$/,
                use: 'happypack/loader?id=styles'
            }
        ]
    },
    plugins: [
        new HappyPack({
            id: 'jsx',
            // 多少個線程
            threads: happyThreadPool,
            loaders: ['babel-loader']
        }),

        new HappyPack({
            id: 'styles',
            // 自定義線程數量
            threads: 2,
            loaders: ['style-loader', 'css-loader', 'less-loader']
        })
    ]
};
預編譯處理

預先編譯和打包不會變更存在的文件,在業務代碼中直接引入,加快 Webpack 編譯打包的速度,可是並不能減小最後生成的代碼體積。
wepack.DllPlugin
add-asset-html-webpack-plugin可將dll處理文件自動插入html中

// webpack.config.dll.js
const webpack = require('webpack');
// 這裏是第三方依賴庫
const vendors = ['react', 'react-dom'];

module.exports = {
   mode: 'production',
   entry: {
       // 定義程序中打包公共文件的入口文件vendor.js
       vendor: vendors
   },
   output: {
       filename: '[name].[chunkhash].js',
       // 這裏是使用將 verdor 做爲 library 導出,而且指定全局變量名字是[name]_[chunkhash]
       library: '[name]_[chunkhash]'
   },
   plugins: [
       new webpack.DllPlugin({
           // 這裏是設置 mainifest.json 路徑
           path: 'manifest.json',
           name: '[name]_[chunkhash]',
           context: __dirname
       })
   ]
};
// webpack.config.js
const webpack = require('webpack');

module.exports = {
   output: {
       filename: '[name].[chunkhash].js'
   },
   entry: {
       app: './src/index.js'
   },
   plugins: [
       new webpack.DllReferencePlugin({
           context: __dirname,
           // 這裏導入 manifest配置內容
           manifest: require('./manifest.json')
       })
   ]
};
壓縮優化

terser-webpack-plugin : 開啓多線程(parallel)和緩存(cache)

const TerserPlugin = require('terser-webpack-plugin');

module.exports = {
    optimization: {
        minimizer: [
            new TerserPlugin({
                cache: true, // 開啓緩存
                parallel: true // 多線程
            })
        ]
    }
};
緩存優化
如babel-loader中cacheDirectory
rules: [
    {
        test: /\.js$/,
        loader: 'babel-loader',
        options: {
            cacheDirectory: true
        },
        // 排除路徑
        exclude: /node_modules/,
        // 查找路徑
        include: [path.resolve('.src')]
    }
];
其餘優化
適當選擇sourceMap的devtool值
切換一些 loader 或者插件,好比: fast-sass-loader能夠並行處理 sass 文件,要比 sass-loader 快 5~10 倍;

參考

相關文章
相關標籤/搜索