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
关键字。