Webpack之tree-starking 解析
tree-sharking 簡(jiǎn)介
tree-sharking 是 Webpack 2 后續(xù)版本的優(yōu)化功能,顧名思義,就是將多余的代碼給 “搖晃” 掉,在開發(fā)中我們經(jīng)常使用一些第三方庫,而這些第三方庫只使用了這個(gè)庫的一部門功能或代碼,未使用的代碼也要被打包進(jìn)來,這樣出口文件會(huì)非常大,tree-sharking 幫我們解決了這個(gè)問題,它可以將各個(gè)模塊中沒有使用的方法過濾掉,只對(duì)有效代碼進(jìn)行打包。
AST 語法樹分析
假設(shè)我們現(xiàn)在使用了 ElementUI 庫的兩個(gè)組件,通常會(huì)使用解構(gòu)賦值來引入。
優(yōu)化前
import { Button, Alert } from "element-ui";
這樣引用資源, Webpack 在打包的時(shí)候會(huì)找到 element-ui 并把里面所有的代碼全部打包到出口文件,我們只使用了兩個(gè)組件,全部打包不是我們所希望的,tree-sharking 是通過在 Webpack 中配置 babel-plugin-import 插件來實(shí)現(xiàn)的,它可以將解構(gòu)的代碼轉(zhuǎn)換成下面的形式。
優(yōu)化后
import Button from "element-ui/lib/button"; import Alert from "element-ui/lib/Alert";
轉(zhuǎn)化后會(huì)去 node_modules 中的 element-ui 模塊找到 Button 和 Alert 兩個(gè)組件對(duì)應(yīng)的文件,并打包到出口文件中。
通過上面的轉(zhuǎn)換可以看出,其實(shí) tree-sharking 的實(shí)現(xiàn)原理是通過改變 AST 語法樹的結(jié)構(gòu)來實(shí)現(xiàn)的,我們可以通過在線轉(zhuǎn)換網(wǎng)站 http://esprima.org/demo/parse.html 將 JS 代碼裝換成 AST 語法樹。
優(yōu)化前的 AST 語法樹
{
"type": "Program",
"body": [
{
"type": "ImportDeclaration",
"specifiers": [
{
"type": "ImportSpecifier",
"local": {
"type": "Identifier",
"name": "Button"
},
"imported": {
"type": "Identifier",
"name": "Button"
}
},
{
"type": "ImportSpecifier",
"local": {
"type": "Identifier",
"name": "Alert"
},
"imported": {
"type": "Identifier",
"name": "Alert"
}
}
],
"source": {
"type": "Literal",
"value": "element-ui",
"raw": "\"element-ui\""
}
}
],
"sourceType": "module"
}
優(yōu)化后的 AST 語法樹
{
"type": "Program",
"body": [
{
"type": "ImportDeclaration",
"specifiers": [
{
"type": "ImportDefaultSpecifier",
"local": {
"type": "Identifier",
"name": "Button"
}
}
],
"source": {
"type": "Literal",
"value": "element-ui/lib/button",
"raw": "\"element-ui/lib/button\""
}
},
{
"type": "ImportDeclaration",
"specifiers": [
{
"type": "ImportDefaultSpecifier",
"local": {
"type": "Identifier",
"name": "Alert"
}
}
],
"source": {
"type": "Literal",
"value": "element-ui/lib/Alert",
"raw": "\"element-ui/lib/Alert\""
}
}
],
"sourceType": "module"
}
從上面的語法樹對(duì)比,可以看出在優(yōu)化前 body 里面只有一個(gè)對(duì)象,使用的組件信息存在 specifiers 里,source 指向了 element-ui,而在優(yōu)化后,將兩個(gè)組件分別拆成了兩個(gè)對(duì)象存在 body 中,每個(gè)對(duì)象的的 specifiers 只存儲(chǔ)一個(gè)組件,并在 source 里面指向了當(dāng)前組件對(duì)應(yīng)的路徑。
模擬 tree-starking
既然我們已經(jīng)清楚要修改語法樹的位置,下面就使用 AST 來模擬 tree-sharking 功能,對(duì)語法樹的操作是依賴于 babel-core 和 babel-types 兩個(gè)核心模塊的,下面先安裝依賴。
npm install babel-core babel-types
文件:babel-plugin-my-import.js
const babel = require("babel-core");
const types = require("babel-types");
let code = `import { Button, Alert } from "element-ui"`;
let importPlugin = {
visitor: {
ImportDeclaration(path) {
let node = path.node;
let source = node.source.value;
let specifiers = node.specifiers;
// 判斷是否是默認(rèn)導(dǎo)出,其中一個(gè)不是默認(rèn)導(dǎo)出,則都不是默認(rèn)導(dǎo)出
if (!types.isImportDefaultSpecifier(specifiers[0])) {
// 如果不是默認(rèn)導(dǎo)出,則需要轉(zhuǎn)換
specifiers = specifiers.map(specifier => {
// 數(shù)組內(nèi)容:當(dāng)前默認(rèn)導(dǎo)出的標(biāo)識(shí)、從哪里導(dǎo)入
return types.importDeclaration(
[types.importDefaultSpecifier(specifier.local)],
types.stringLiteral(`${source}/lib/${specifier.local.name.toLowerCase()}`)
);
});
// 替換樹結(jié)構(gòu)
path.replaceWithMultiple(specifiers);
}
}
}
};
let result = babel.transform(code, {
plugins: [importPlugin]
});
console.log(result.code);
// import Button from "element-ui/lib/button";
// import Alert from "element-ui/lib/alert";
通過上面的代碼可以發(fā)現(xiàn)我們使用 babel-core 和 babel-types 兩個(gè)模塊的核心方法對(duì)語法書進(jìn)行了遍歷、修改和替換,更詳細(xì)的 API 可以查看 https://github.com/babel/babel/tree/6.x/packages/babel-types。
結(jié)合 Webpack 使用插件
前面只是驗(yàn)證了 tree-sharking 中 JS 語法的轉(zhuǎn)換過程,接下來將上面的代碼轉(zhuǎn)換成插件配合 Webpack 使用,來徹底感受 tree-sharking 的工作過程。
文件:~node_modules/babel-plugin-my-import.js
const babel = require("babel-core");
const types = require("babel-types");
let importPlugin = {
visitor: {
ImportDeclaration(path) {
let node = path.node;
let source = node.source.value;
let specifiers = node.specifiers;
// 判斷是否是默認(rèn)導(dǎo)出,其中一個(gè)不是默認(rèn)導(dǎo)出,則都不是默認(rèn)導(dǎo)出
if (!types.isImportDefaultSpecifier(specifiers[0])) {
// 如果不是默認(rèn)導(dǎo)出,則需要轉(zhuǎn)換
specifiers = specifiers.map(specifier => {
// 數(shù)組內(nèi)容:當(dāng)前默認(rèn)導(dǎo)出的標(biāo)識(shí)、從哪里導(dǎo)入
return types.importDeclaration(
[types.importDefaultSpecifier(specifier.local)],
types.stringLiteral(`${source}/lib/${specifier.local.name.toLowerCase()}`)
);
});
// 替換樹解構(gòu)
path.replaceWithMultiple(specifiers);
}
}
}
};
module.exports = importPlugin;
上面刪掉了多余的測(cè)試代碼,將模塊中的 importPlugin 插件導(dǎo)出,并把 babel-plugin-my-import.js 移入了 node_modules 當(dāng)中。
接下來安裝需要的依賴:
npm install webpack webpack-cli babel-loader babel-presets-env
npm install vue element-ui --save
安裝完依賴,寫一個(gè)要編譯的文件,使用 Webpack 進(jìn)行打包,查看使用插件前和使用插件后出口文件的大小。
文件:import.js
import Vue from "vue";
import { Button, Alert } from "element-ui";
下面來寫一個(gè)簡(jiǎn)單的 Webpack 配置文件。
文件:webpcak.config.js
module.exports = {
mode: "development",
entry: "import.js",
output: {
filename: "bundle.js",
path: __dirname
},
module: {
rules: [{
test: /\.js$/,
use: {
loader: "babel-loader",
options: {
presets: [
"env",
],
plugins: [
// 插件:不使用插件打包注釋掉該行即可
["my-import", { libararyName: "element-ui" }]
]
}
},
exclude: /node_modules/
}]
}
};
為了防止 babel 相關(guān)的依賴升級(jí) 7.0 后出現(xiàn)一些問題導(dǎo)致 Webpack 無法啟動(dòng),再此貼出 package.json 文件,按照對(duì)應(yīng)版本下載依賴保證上面 Webpack 配置生效。
文件:package.json
{
"name": "ast-lesson",
"version": "1.0.0",
"description": "tree-starking",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"babel-core": "^6.26.3",
"babel-loader": "^7.1.5",
"babel-preset-env": "^1.7.0",
"babel-types": "^6.26.0",
"escodegen": "^1.10.0",
"esprima": "^4.0.0",
"estraverse": "^4.2.0",
"webpack": "^4.16.0",
"webpack-cli": "^3.0.8"
},
"devDependencies": {
"vue": "^2.5.17",
"element-ui": "^2.4.6"
}
}
對(duì)比使用插件前后的出口文件
接下來分別在使用插件和不使用插件時(shí)執(zhí)行打包命令,查看出口文件 bondle.js 的大小。
npx webpack
使用 babel-plugin-my-import 前:

使用 babel-plugin-my-import 后:

通過對(duì)比,可以看到使用 tree-sharking 即我們自己實(shí)現(xiàn)的 babel-plugin-my-import 插件后,打包的出口文件大大減小,其原因是將引入第三方庫沒有使用的代碼全都過濾掉了,只打包了有效代碼。
總結(jié)
上面對(duì) Webpack 的 tree-sharking 進(jìn)行了分析,并模擬 babel-plugin-import 簡(jiǎn)易的實(shí)現(xiàn)了一版 tree-sharking 的優(yōu)化插件,這個(gè)過程中相信大家已經(jīng)了解了 tree-sharking 的原理以及實(shí)現(xiàn)類似插件的思路,并已經(jīng)具備了開發(fā)類似插件的基本條件,最后還有一點(diǎn)需要補(bǔ)充,tree-sharking 優(yōu)化的方式是根據(jù) ES6 語法 import “靜態(tài)” 引入的特性實(shí)現(xiàn)的,如果要說 tree-sharking 很強(qiáng)大,還不如說 ES6 模塊化規(guī)范 “靜態(tài)” 引入的特性強(qiáng)大,正由于是基于 “靜態(tài)” 引入,所以目前 tree-sharking 只支持遍歷一層 import 關(guān)鍵字。
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
第七篇Bootstrap表單布局實(shí)例代碼詳解(三種表單布局)
Bootstrap提供了三種表單布局:垂直表單,內(nèi)聯(lián)表單和水平表單。接下來通過本文給大家介紹Bootstrap表單布局實(shí)例代碼詳解,非常不錯(cuò),具有參考借鑒價(jià)值,感興趣的朋友一起學(xué)習(xí)吧2016-06-06
js如何準(zhǔn)確獲取當(dāng)前頁面url網(wǎng)址信息
這篇文章主要為大家介紹了js準(zhǔn)確獲取當(dāng)前頁面url網(wǎng)址信息的多種方法,包括正則法、split拆分法等,需要的朋友可以參考下2016-04-04
利用Ext Js生成動(dòng)態(tài)樹實(shí)例代碼
今天在公司幫同事寫了個(gè)用Ext Js生成動(dòng)態(tài)樹的Demo,在這里分享一下,也好供以后自己查閱。2008-09-09
Javascript筆記一 js以及json基礎(chǔ)使用說明
JavaScript中的數(shù)據(jù)很簡(jiǎn)潔的。簡(jiǎn)單數(shù)據(jù)只有 undefined, null, boolean, number和string這五種,而復(fù)雜數(shù)據(jù)只有一種,即object。2010-05-05
在web中js實(shí)現(xiàn)類似excel的表格控件
這篇文章主要介紹了如何在web中實(shí)現(xiàn)類似excel的表格控件,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2016-09-09
詳細(xì)總結(jié)Javascript中的焦點(diǎn)管理
相信大家都知道焦點(diǎn)作為javascript中的一個(gè)重要功能,基本上和頁面交互都離不開焦點(diǎn)。但卻少有人對(duì)焦點(diǎn)管理系統(tǒng)地做總結(jié)歸納。本文就javascript中的焦點(diǎn)管理作詳細(xì)介紹,有需要的朋友們可以參考借鑒。2016-09-09

