webpack性能優化(下)

在webpack性能優化(上)中,咱們從 代碼分離,Loader, webpack解析(resolve), webpack 外部擴展(Externals) ,Dlls 優化構建速度,等方面分析了優化手段,這篇文章讓咱們接着來擼。javascript

圖片等靜態文件 dev prod

一般來講,咱們會經過使用file-loader,url-loader等loader來處理項目中的靜態文件,如圖片字體等文件css

//這樣最終dist文件中就會生成font文件夾存放字體文件
{
    test: /\.(woff|svg|eot|ttf)\??.*$/,
    loader: "url-loader",
    options: {
        limit: 8192,
        name: "font/[name].[hash:6].[ext]"
    }
}

複製代碼

limit屬性是在文件大小超出limit的值纔會單獨打包,不然使用base64 的方式引用一般適用於小圖片,這就是咱們一般的文件處理方式。html

使用base64引入圖片的好處是減小http請求數,但相應的問題是base64佔用的空間比普通的圖片文件大一點。java

固然咱們還有另一種方案,具體作法是將項目的中靜態文件統一存放在static文件夾下,最後使用 CopyWebpackPlugin將static文件夾拷貝到dist目錄下node

new CopyWebpackPlugin([
    {
        from: path.resolve(SRC_PATH, 'img'),
        to: 'img'
    }
]),
複製代碼

這樣作的好處是咱們的靜態資源不通過webpack的處理,能夠提高構建速度,但問題也是很明顯的,那就是維護的成本增大並可能出現一些意外的狀況,好比:react

這樣處理的問題是可能開發環境引用路徑和打包文件訪問圖片路徑不一致問題,這裏能夠經過output.publicPath屬性來配置解決webpack

output: {
    //打包文件中經過相對路徑引用的資源都會被配置的路徑所替換
    publicPath: '/assets/'
}

//對於這種結構的項目固然不合適使用這種方法
|- /static
|– /components
|  |– /my-component
|  |  |– index.jsx
|  |  |– index.css
|  |  |– icon.svg
|  |  |– img.png
複製代碼

固然從咱們實際項目的測試效果來看,我只能說這種處理方式並不算是很優秀,僅供參考。web

source map dev

在開發環境中,咱們比較關注調試的方便程度,而原始webpack打包後的bundle文件中可能包含來自多個文件的內容,對於程序的報錯信息每每簡單的指向這個bundle文件: express

image.png
而source map是爲了幫助咱們定位程序出現的錯誤對應的源代碼的位置。使用sourceMap報錯信息正確的指向了源碼的錯誤位置。
image.png

//1 使用devtool選項配置,有多個選項可選
module.exports = {
    devtool: 'inline-source-map',
};

//2 使用plugins方式進行更細粒度的配置
module.exports = {
    plugins: [
        new webpack.SourceMapDevToolPlugin({
            filename: '[name].js.map',
            exclude: ['vendor.js']
        })
    ]
};

//在使用uglifyjs-webpack-plugin時 須要開啓sourceMap選項

複製代碼

devtool文檔json

UglifyJsPlugin 配合 tree shaking prod

對於js壓縮 在webpack <= 3.x的版本中:

//1 使用 -p(production)標記來壓縮js
//2 使用內置 plugin(webpack.optimize.UglifyJsPlugin)
//3 使用外部引入plugin(uglifyjs-webpack-plugin)
module.exports = {
    plugins: [
        new webpack.optimize.UglifyJsPlugin({
            compress: {
                warnings: false
            },
            output: {
                //remove all comments
                comments: false
            }
        }),
    ]
};

複製代碼

在webpack4中

const UglifyJsPlugin = require('uglifyjs-webpack-plugin');
module.exports = {
    //1 設置 mode
    mode: ""production
    //2 minimize
    optimization:{
	minimize: true,
        //3 或者指定其餘插件
        minimizer: [
          new UglifyJsPlugin({
            sourceMap: true
          })
        ]
    },
    //3 若須要sourceMap 須要設置 devtool的值
    devtool: 'source-map', 
};

//可選的壓縮插件
UglifyJSPlugin, ClosureCompilerPlugin
BabelMinifyWebpackPlugin,

複製代碼

此處須要注意。如果在使用了UglifyJSPlugin且開啓sourceMap後,須要同時給devtool設置值。一樣的如果設置了devtool的值,則UglifyJSPlugin也須要開啓sourceMap。不然不會生成.map的源代碼對應文件。

在開啓js的壓縮後 咱們的tree shaking登場了,tree shaking是什麼?爲何須要使用?

tree shaking是一個術語,用於描述移除js中未引用的代碼。 使用它能優化輸出。 未開啓tree shaking的實例:

//tool.js
export function square(x) {
  return x * x;
}

export function cube(x) {
  return x * x * x;
}

//index.js
import { square } from "./tool.js"

//最終輸出 在關閉 UglifyJSPlugin插件後測試結果
複製代碼

image.png

咱們能夠看到 cube這個咱們並無引用的模塊也被打包進源碼了。

使用tree shaking 來優化輸出,在package.json中:

//webpack4
//1 將文件標記爲無反作用 
{
  "name": "web",
  "version": "1.0.0",
  //如果整個項目都無反作用 直接設置爲false
  "sideEffects": false
  //如果部分文件確實有反作用
  "sideEffects": [file_path1, file_path2]
}

//2 開啓js壓縮 使用上述方法開啓便可

//webpack2/3
//1 須要配置 .babelrc modules false
{
  "presets": [
    [
      "env", 
      {
        "modules": false
      }
    ]
  ]
}

//2 開啓js壓縮

複製代碼

「反作用」的定義是,在導入時會執行特殊行爲的代碼,而不是僅僅暴露一個 export 或多個 export。舉例說明,例如 polyfill,它影響全局做用域,而且一般不提供 export。

對於開啓後的壓縮代碼中,咱們搜索"*"號 只獲得一個結果,測試成功。

image.png

最後咱們簡單解釋下設置modules false的做用。tree shaking自己是依賴於ES6的靜態導入,也就是咱們經常使用的import export。ES6模塊化中一個文件可以輸出多個模塊,而咱們能夠只導入須要的模塊。對比commonjs的動態導入模塊化標準,一個文件只有一個輸出,所以不難發現,tree shaking在commonjs模塊化的系統中是發揮不了做用的。

而modules的意義是啓用將ES6模塊語法轉換爲另外一種模塊類型,默認值'commonjs',將該設置爲false即不轉化,也就是ES6模塊語法,因此在此咱們須要將modules設置爲false。

modules的取值有 'amd' | 'umd' | 'systemjs' | 'commonjs' | false。 在webpack4中已經能夠不用此方法來檢測重複模塊了

Split CSS prod

一個web項目中css是關鍵的一環,若沒有額外配置css最終會被打包進入js文件中,但熟悉瀏覽器渲染的開發者應該會清楚,瀏覽器在渲染頁面時會解析DOM樹和CSS樹,最後將之對應合併呈現渲染好的頁面。將css放在js中引入勢必會延緩css樹的計算。 因此將css從js中分離,打包成單獨的css文件,而後和js並行加載是咱們項目的一個提高點,這樣能夠加快界面渲染速度,也能夠單獨作緩存。

//使用插件 extract-text-webpack-plugin
{
    test: /\.css$/,
    use: ExtractTextPlugin.extract({
        //用於css未被提取(allChunks: false)
        fallback: "style-loader",
        use: 'css-loader'
    })
}

new ExtractTextPlugin({
    filename: 'common.[chunkhash].css',
    allchunk: true
})

複製代碼

固然webpack-dev-server是不支持extract-text-webpack-plugin抽離的css熱替換的,因此此插件不建議再dev環境中使用,若是非要使用能夠考慮css-hot-loader。

清理 /dist 文件夾 prod

webpack將打包的文件放在dist文件夾中,如果使用了hash文件名,則每次文件變更後從新打包生成的文件名都會不一樣,這會形成dist目錄愈來愈混亂,好的作法是每次打包前先清理dist文件夾:

new CleanWebpackPlugin(pathsToClean, cleanOptions)
複製代碼

在內存中編譯 dev

webpack-dev-server你們都不陌生,開發環境必備,webpack內部依賴了webpack-hot-middleware,webpack-dev-middleware兩個插件。

webpack-dev-middleware提供了在內存中編譯功能,它在文件更改後自動編譯文件並保存在內存中,具體表現爲,刷新瀏覽器便可看到咱們的更改。

webpack-hot-middleware提供了服務端推送功能,一般和webpack-dev-middleware配合使用,當文件更改並自動編譯完成後,服務端經過SSE(服務端發送事件)將更改信息推送到客戶端,客戶端會接收到一個json文件,其中包含了更改了的文件的一些信息,客戶端會根據這些信息主動向服務端獲取最新的文件。

若無文件更改webpack-hot-middleware也會在必定時間間隔後遍歷內存文件檢測是否更改,而後經過事件流的方式向客戶端發送消息。

webpack-dev-middleware和webpack-hot-middleware都是express的標準插件

我相信各位項目中這兩個功能都是已經開啓的我就再也不具體說他們的配置了,這裏咱們主要說下在node服務端怎麼使用這兩個插件達到熱更新的目的。

咱們以koa爲例,如何在koa中開啓熱更新調試咱們的項目呢?

//新建 app.js做爲koa服務端入口 app.js

import Koa from "koa";
import views from "koa-views";
import webpack from "webpack";
import webpack_config from "../webpack/webpack.config.js";
import { devMiddleware, hotMiddleware } from 'koa-webpack-middleware'

var app = new Koa()
var compiler = webpack(webpack_config)

app.use(views("./template", {map: {html: "ejs"}}));

app.use(devMiddleware(compiler,{
    publicPath:"/"
}));

app.use(hotMiddleware(compiler))

複製代碼

koa-webpack-middleware 將express的中間件(webpack-dev-middleware和webpack-hot-middleware)進行封裝,將咱們koa中間件的next方法傳遞到express的第三個參數中進行封裝。

最簡單的配置如上。但這種配置會有一個問題就是刷新404的問題。

hotMiddleware會在匹配到項目跟路由時直接返回內存中的html文件給客戶端。可是其餘的路由如react的路由時,它不會去匹配,最終會返回一個404

//會返回template/index.html 但這時是空的
//也就是沒有導入js的html
await ctx.render("index");
複製代碼

解決,當用戶訪問時在webpack編譯輸出的最後階段獲取到文件信息,取出獲取到的html文件寫入template下的index.html文件,最後返回它,具體操做以下:

compiler.plugin("emit",(comilation,callback) => {
    const assets = comilation.assets;
    let file, data;
    Object.keys(assets).forEach(key => {
        if(key.match(/\.html$/)){
            file = path.resolve(__dirname,"./template/index.html");
            data = assets[key].source();
            fs.writeFileSync(file,data);
        }
    });

    callback();
})
複製代碼

固然上述方法略顯笨重,且須要理解的東西較多,不太推薦,這裏有一個插件能解決上述問題 connect-history-api-fallback,你們本身學習下便可。

個人博客地址

相關文章
相關標籤/搜索