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的總結,希望對大家的學習有所幫助