tree-sharking
是 Webpack 2
後續版本的優化功能,顧名思義,就是將多餘的代碼給 「搖晃」 掉,在開發中咱們常常使用一些第三方庫,而這些第三方庫只使用了這個庫的一部門功能或代碼,未使用的代碼也要被打包進來,這樣出口文件會很是大,tree-sharking
幫咱們解決了這個問題,它能夠將各個模塊中沒有使用的方法過濾掉,只對有效代碼進行打包。html
假設咱們如今使用了 ElementUI 庫的兩個組件,一般會使用解構賦值來引入。vue
// 優化前 import { Button, Alert } from "element-ui";
這樣引用資源, Webpack 在打包的時候會找到 element-ui
並把裏面全部的代碼所有打包到出口文件,咱們只使用了兩個組件,所有打包不是咱們所但願的,tree-sharking
是經過在 Webpack 中配置 babel-plugin-import
插件來實現的,它能夠將解構的代碼轉換成下面的形式。node
// 優化後 import Button from "element-ui/lib/button"; import Alert from "element-ui/lib/Alert";
轉化後會去 node_modules
中的 element-ui
模塊找到 Button
和 Alert
兩個組件對應的文件,並打包到出口文件中。webpack
經過上面的轉換能夠看出,其實 tree-sharking
的實現原理是經過改變 AST 語法樹的結構來實現的,若是不瞭解抽象語法樹能夠參考 AST 抽象語法樹,咱們能夠經過在線轉換網站 http://esprima.org/demo/parse... 將 JS 代碼裝換成 AST 語法樹。git
優化前的 AST 語法樹:github
{ "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" }
優化後的 AST 語法樹:web
{ "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" }
從上面的語法樹對比,能夠看出在優化前 body
裏面只有一個對象,使用的組件信息存在 specifiers
裏,source
指向了 element-ui
,而在優化後,將兩個組件分別拆成了兩個對象存在 body
中,每一個對象的的 specifiers
只存儲一個組件,並在 source
裏面指向了當前組件對應的路徑。npm
既然咱們已經清楚要修改語法樹的位置,下面就使用 AST 來模擬 tree-sharking
功能,對語法樹的操做是依賴於 babel-core
和 babel-types
兩個核心模塊的,下面先安裝依賴。element-ui
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; // 判斷是不是默認導出,其中一個不是默認導出,則都不是默認導出 if (!types.isImportDefaultSpecifier(specifiers[0])) { // 若是不是默認導出,則須要轉換 specifiers = specifiers.map(specifier => { // 數組內容:當前默認導出的標識、從哪裏導入 return types.importDeclaration( [types.importDefaultSpecifier(specifier.local)], types.stringLiteral(`${source}/lib/${specifier.local.name.toLowerCase()}`) ); }); // 替換樹結構 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";
經過上面的代碼能夠發現咱們使用 babel-core
和 babel-types
兩個模塊的核心方法對語法書進行了遍歷、修改和替換,更詳細的 API 能夠查看 https://github.com/babel/babe...。json
前面只是驗證了 tree-sharking
中 JS 語法的轉換過程,接下來將上面的代碼轉換成插件配合 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; // 判斷是不是默認導出,其中一個不是默認導出,則都不是默認導出 if (!types.isImportDefaultSpecifier(specifiers[0])) { // 若是不是默認導出,則須要轉換 specifiers = specifiers.map(specifier => { // 數組內容:當前默認導出的標識、從哪裏導入 return types.importDeclaration( [types.importDefaultSpecifier(specifier.local)], types.stringLiteral(`${source}/lib/${specifier.local.name.toLowerCase()}`) ); }); // 替換樹解構 path.replaceWithMultiple(specifiers); } } } }; module.exports = importPlugin;
上面刪掉了多餘的測試代碼,將模塊中的 importPlugin
插件導出,並把 babel-plugin-my-import.js
移入了 node_modules
當中。
接下來安裝須要的依賴:
npm install webpack webpack-cli babel-loader babel-presets-envnpm install vue element-ui --save
安裝完依賴,寫一個要編譯的文件,使用 Webpack 進行打包,查看使用插件前和使用插件後出口文件的大小。
// 文件:import.js import Vue from "vue"; import { Button, Alert } from "element-ui";
下面來寫一個簡單的 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
相關的依賴升級 7.0
後出現一些問題致使 Webpack 沒法啓動,再此貼出 package.json
文件,按照對應版本下載依賴保證上面 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" } }
接下來分別在使用插件和不使用插件時執行打包命令,查看出口文件 bondle.js
的大小。
npx webpack
使用 babel-plugin-my-import
前:
使用 babel-plugin-my-import
後:
經過對比,能夠看到使用 tree-sharking
即咱們本身實現的 babel-plugin-my-import
插件後,打包的出口文件大大減少,其緣由是將引入第三方庫沒有使用的代碼全都過濾掉了,只打包了有效代碼。
上面對 Webpack 的 tree-sharking
進行了分析,並模擬 babel-plugin-import
簡易的實現了一版 tree-sharking
的優化插件,這個過程當中相信你們已經瞭解了 tree-sharking
的原理以及實現相似插件的思路,並已經具有了開發相似插件的基本條件,最後還有一點須要補充,tree-sharking
優化的方式是根據 ES6 語法 import
「靜態」 引入的特性實現的,若是要說 tree-sharking
很強大,還不如說 ES6 模塊化規範 「靜態」 引入的特性強大,正因爲是基於 「靜態」 引入,因此目前 tree-sharking
只支持遍歷一層 import
關鍵字。