1. 程式人生 > >webpack 如何優雅的使用tree-shaking(搖樹優化)

webpack 如何優雅的使用tree-shaking(搖樹優化)

webpack 如何優雅的使用tree-shaking

1.什麼是tree-shaking

webpack 2 的到來帶來的最棒的新特性之一就是tree-shaking 。tree-shaking源自於rollup.js,先如今,webpack 2也有類似的做法。

webpack 裡的tree-shaking的到來不得不歸功於es6規範的模組。為什麼這麼說,如今的前端模組規範很多,比較出流行的比如commonJS , AMD , es6 ,我簡單的說一下commonJS和es6模組的區別。

commonJS 模組

commonJS的模組規範在Node中發揚光大,總的來說,它的特性有這幾個:

1.動態載入模組
commonJS和es6的最大區別大概就在於此了吧,commonJS模組的動態載入能夠很輕鬆的實現懶載入,優化使用者體驗。

2.載入整個模組
commonJS模組中,匯出的是整個模組。

3.每個模組皆為物件
commonJS模組都被視作一個物件。

4.值拷貝
commonJS的模組輸出和 函式的值傳遞相似,都是值的拷貝

es6 模組

1.靜態解析
即在解析階段就確定輸出的模組,所以es6模組的import一般寫在被引入檔案的開頭。

2.模組不是物件
在es6裡,每個模組並不會當做一個物件看待

3.載入的不是整個模組
在es6模組中經常會看見一個模組中有好幾個export 匯出

4.模組的引用
es6模組中,匯出的並不是模組的值拷貝,而是這個模組的引用

在結合es6模組和commonJS模組的區別之後,我們知道es6的特點是靜態解析,而commonJS模組的特點是動態解析的,因此,借於es6模組的靜態解析,tree-shaking的實現才能成為可能。
在webpack中,tree-shaking指的就是按需載入,即沒有被引用的模組不會被打包進來,減少我們的包大小,縮小應用的載入時間,呈現給使用者更佳的體驗。

2.怎麼使用tree-shaking

說了這麼多那到底如何使用tree-shaking呢?
webpack預設es6規範編寫的模組都能使用tree-shaking。這是什麼意思呢?下面來看個例子。
首先奉上我的demo目錄如下:

├─dist
    └─index.html
├─node_modules
    └─...
├─src
    ├─scripts
    ├─assets
├─webpack.config.js
└─package.json

dist用來存放打包好的程式碼
src相反的用來存放原始檔
src裡的scripts目錄用來存放js指令碼檔案,assets用來存放靜態資原始檔

以下幾條命令過後開始我們的tree-shaking之旅

npm install --save-dev webpack webpack-dev-server

webpack.config.js

const webpack = require('webpack')
const path = require('path')

module.exports = {
    entry:'./src/scripts/main.js',
    output:{
        path:path.resolve(__dirname,'dist/'),
        filename:'main.bundle.js'
    },
    plugins:[
        new webpack.HotModuleReplacementPlugin()
    ],
    devServer:{
        port:4200,
        contentBase:path.resolve(__dirname,'dist/'),
        historyApiFallback:true,
        hot:true
    }
}

接下來是main.js,直接引入了sayHello

import { sayHello } from './greeter.ts';

sayHello();

相應的main.js的依賴greeter.js

export function sayHello(){
    alert('hello')
}

export function sayWorld(){
    alert('world')
}

在dist目錄下有個index.html 用來引入打包後的bundle

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Document</title>
</head>
<body>
    <script type="text/javascript" src="./main.bundle.js"></script>
</body>
</html>

以上就是整個demo的程式碼,接下來的事情我們直接webpack打包試試看

webpack
[
/* 0 */
/***/ (function(module, __webpack_exports__, __webpack_require__) {

"use strict";
Object.defineProperty(__webpack_exports__, "__esModule", { value: true });
/* harmony import */ var __WEBPACK_IMPORTED_MODULE_0__person__ = __webpack_require__(1);


Object(__WEBPACK_IMPORTED_MODULE_0__person__["a" /* sayHello */])();

/***/ }),
/* 1 */
/***/ (function(module, __webpack_exports__, __webpack_require__) {

"use strict";
/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "a", function() { return sayHello; });
/* unused harmony export sayWorld */

    function sayHello(){
        alert('hello');
    }
    function sayWorld(){
        alert('world');
    }



/***/ })
/******/ ]

我們關注這一行

/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "a", function() { return sayHello; });

實際上只return了一個sayHello。
因此我們現在只需要壓縮一下整個Js程式碼,就能把沒引用的sayWorld剔除。

鍵入以下命令進行壓縮

webpack --optimize-minimize

由於壓縮後的程式碼只有一行了,我們移步尾部:

function(e,n,r){"use strict";function t(){alert("hello")}r.d(n,"a",function(){return t})}]);

可以看到sayWorld函式已經被成功剔除。

我們啟動webpack-dev-server

webpack-dev-server

在瀏覽器中輸入

http://localhost:4200

這裡寫圖片描述

每次都需要在命令列裡輸入引數,豈不是很麻煩,還有沒有其他更好的辦法呢?

(1)我們可以把這串命令放入package.json的scripts欄位,然後通過npm start來自動執行

(2)其實–optimize-minimize的底層實現是一個外掛UglifyJsPlugin,因此,我們可以直接在webpack.config.js裡配置它

在webpack.config.js裡配置外掛

const path = require('path');
const webpack = require('webpack');

module.exports = {
    entry:'./src/scripts/main.js',
    output:{
        filename:'main.bundle.js',
        path:path.join(__dirname,'dist')
    },
    plugins:[
        new webpack.optimize.UglifyJsPlugin(), // <----------- 壓縮js
        new webpack.HotModuleReplacementPlugin()
    ],
    devServer:{
        port:4200,
        historyApiFallback:true,
        hot:true,
        contentBase:path.join(__dirname,"dist/")
    }
}

然後我們webpack打包

webpack

即看到同樣的效果

function(e,n,r){"use strict";function t(){alert("hello")}r.d(n,"a",function(){return t})}]);

在tree-shaking觸發打包後,僅僅是撇開了模組的引用,但還是要結合壓縮工具來進行,這才是完整的一次tree-shaking

那如果是typescript該怎麼使用tree-shaking呢?

3.如何在typescript裡使用tree-shaking

要在webpack裡使用ts,首先我們必須安裝tsc

npm install --save-dev typescript

之後我們需要解析ts檔案的loader

npm install --save-dev ts-loader

然後在webpack.config.js進行配置

const webpack = require('webpack')
const path = require('path')

module.exports = {
    entry:'./src/scripts/main.ts',
    output:{
        path:path.resolve(__dirname,'dist/'),
        filename:'main.bundle.js'
    },
    module:{
        rules:[
            {
                test:/\.ts$/,
                use:['ts-loader']
            }
        ]
    },
    plugins:[
        new webpack.optimize.UglifyJsPlugin(),
        new webpack.HotModuleReplacementPlugin()
    ],
    devServer:{
        port:4200,
        contentBase:path.resolve(__dirname,'dist/'),
        historyApiFallback:true,
        hot:true
    }
}

獻上我的兩份檔案main.ts , greeter.ts (這兩份檔案除了字尾名基本沒有改動)

main.ts

import { sayHello } from './greeter.ts';

sayHello();

greeter.ts

export var sayHello = function(){
    alert('hello')
}

export var sayWorld = function(){
    alert('world')
}

之後我們需要做的是,建立一個tsconfig.json的配置檔案供tsc解析,這時,坑來了。

下面是我的tsconfig.json檔案

{
    "compilerOptions":{
        "target":"es5",
        "sourceMap":true
    },
    "exclude":[
        "./node_modules"
    ]
}

好像沒有什麼不對

接著我們webpack

webpack

看下打包壓縮後的程式碼的最後一部分:

"use strict";Object.defineProperty(n,"__esModule",{value:!0}),n.sayHello=function(){alert("hello")},n.sayWorld=function(){alert("world")}}]);

sayWorld居然還是存在!!!怎麼回事,為什麼沒有被觸發tree-shaking優化?

這是因為tsc編譯後的程式碼為es5 ,而正因如此,tsc預設使用了commonJS的規範來載入模組,因此並沒有觸發tree-shaking,那我們要怎麼做?

修改一下tsconfig.json,把target改為es6即可!

{
    "compilerOptions":{
        "target":"es6",
        "sourceMap":true
    },
    "exclude":[
        "./node_modules"
    ]
}

再次打包

webpack

看一下打包後的bundle

function(e,n,r){"use strict";r.d(n,"a",function(){return t});var t=function({alert("hello")}}]);

果然是觸發了tree-shaking

開啟webpack-dev-server

webpack-dev-server

可以看到成功列印hello

這裡寫圖片描述

以上就是我對webpack tree-shaking的總結,希望對大家的學習有所幫助