当前位置: 首页 > 后端技术 > Node.js

Treeshaking原理及应用

时间:2023-04-03 16:47:53 Node.js

Treeshaking原理及应用概念Treeshaking字面意思是“摇树”。摇一摇树,树上枯黄的叶子就会抖落。在项目开发中,我们会将代码按模块组织起来,treeshaking的作用就是抖掉项目中所有不需要的代码,消除引用,删除没有被调用的无用模块代码。这种优化最终实现了代码量的减少,这也是项目性能优化的一部分。部分。treeshaking早期是通过rollup实现的,后来webpack2也加入了treeshaking的功能。在webpack中,treeshaking指的是按需加载,也就是没有被引用的模块不会被打包进来,减少我们的包体积,缩短应用的加载时间,呈现更好的用户体验。原理TreeShaking在去除代码冗余的过程中,程序从入口文件开始扫描模块的所有模块依赖和子依赖,然后将它们链接起来形成一个“抽象语法树”(AST)。然后,运行所有代码,查看哪些代码被使用并标记。最后,将“抽象语法树”中不用的代码“抖掉”。经过这样的过程后,未使用的代码将被删除。TreeShaking的实现关键在于ESModule模块的静态解析(Staticmoduleresolution)功能。那么什么是静态分析呢?静态分析在ESModule中,我们可以将模块的加载分为两个阶段:静态分析和编译执行;所谓静态分析,就是在代码执行之前,可以分析读取整体的代码依赖和调用关系;再来看看ESModule的特点:只能作为模块顶层语句出现(不能嵌套在条件语句中)import的模块名只能是字符串常量(字符串只对文件读取)import和export语句没有动态部分(不允许变量等)在ES6中,实现了完全静态的导入语法:import,也就是下面的导入是不可行的://报错,不能嵌套在条件语句中//中的代码静态分析阶段未编译执行,无法获取变量f'+'oo'}来自'module1';只能通过Importallpackages然后进行条件获取,如下:importfoofrom"foo";importbarfrom"bar";if(condition){//foo.xxxx}else{//bar.xxx}ESModuleimportsyntax使用treeshaking非常完美,因为不需要运行代码就可以分析不需要的代码。与ESModule不同,CommonJS支持模块的动态加载。加载前无法判断模块是否被调用过,所以不支持treeshaking。if(condition){myDynamicModule=require("foo");}else{myDynamicModule=require("bar");}这些ES6模块的设计虽然不如CommonJS的require灵活,但它们保证了ES6模块的依赖是确定性(deterministic),与运行时状态无关,从而保证ES6模块可以可靠地进行静态分析。我们知道webpack本身就是一个插件集合,那么有哪些插件实现了treeshaking功能呢?目前集成treeshaking功能的插件有以下几种:UglifyJSwebpack-rollup-loaderBabelMinifyWebpackPluginapplication看官方文档解释:webpack2正式版内置了对ES2015模块的支持(也称为和谐模块)和未使用的模块检测功能。新的正式版webpack4扩展了这个检测能力,使用package.json的“sideEffects”属性作为标记,给编译器提供提示,指出项目中哪些文件是“纯(pureES2015modules)”,所以它可以安全地删除文件中未使用的部分。配置方法:1.在sideEffects副作用pakeage.json文件中添加sideEffects配置,排除非es模块类型的模块,避免副作用;如果所有代码都不包含副作用,我们可以简单地将此属性标记为false以通知webpack,它可以安全地删除未使用的导出。sideEffects更有效,因为它允许跳过整个模块/文件和文件的整个子树。{"name":"webpack-app","version":"1.0.0","description":"webpackappdescription","main":"","sideEffects":["*.scss"]}2.用babel编译.babelrc文件时,模块默认会编译成CommonJS,modules会设置为false,避免ESModule模块类型转换{"presets":[["es2015",{"modules":false}]]}运行示例下面是webpack+babel实现treeshaking的例子://helpers.jsexportfunctionfoo(){return'foo';}exportfunctionbar(){return'bar';}//main.jsimport{foo}from'./helpers';letelem=document.getElementById('output');elem.innerHTML=`Output:${foo()}`;未配置使用treeshaking:babeles2015,包含插件transform-es2015-modules-commonjs,其作用是将es2015代码转为commonjs,从而失去静态分析的基本条件//.babelrcfile{presets:['es2015'],}编译后输出helpersModulecode:function(module,exports){'usestrict';Object.defineProperty(exports,"__esModule",{值:真});exports.foo=foo;exports.bar=bar;函数foo(){返回'foo';}functionbar(){return'bar';}}如上代码,我们可以看到exports同时导出了foo和bar;configuretreeshaking:编译后输出helpers模块代码,只导出了foo模块,但是还有一个bar方法:function(module,exports,__webpack_require__){/*harmonyexport*/exports["foo"]=富;/*未使用的和声导出栏*/;函数foo(){返回'foo';}functionbar(){return'bar';实现了无用代码的剔除:function(t,n,r){functione(){return"foo"}n.foo=e}总结通过上面的解释,我们了解到为了在项目中实现treeshaking,我们需要满足以下条件:使用ES2015模块语法(即import和export)确保没有编译器将ES2015模块语法转换为CommonJS(顺便说一句,这是@babel/preset-env的默认行为,即现在常用)。在项目的package.json文件中,添加“sideEffects”属性。使用“production”配置选项启用更多优化,包括代码压缩和treeshaking。目前的treeshaking技术还不是很成熟。在处理冗余代码时,往往因为副作用而不能很好地简化代码。在日常的代码编写中,我们还是需要减少冗余,提高代码质量。