生物医药网站建设,大型网站开发人员,网站的构建是怎样的,网站已改版大家好#xff0c;我是若川。我持续组织了近一年的源码共读活动#xff0c;感兴趣的可以 点此扫码加我微信 lxchuan12 参与#xff0c;每周大家一起学习200行左右的源码#xff0c;共同进步。同时极力推荐订阅我写的《学习源码整体架构系列》 包含20余篇源码文章。历史面试…大家好我是若川。我持续组织了近一年的源码共读活动感兴趣的可以 点此扫码加我微信 lxchuan12 参与每周大家一起学习200行左右的源码共同进步。同时极力推荐订阅我写的《学习源码整体架构系列》 包含20余篇源码文章。历史面试系列。另外目前建有江西|湖南|湖北籍前端群可加我微信进群。Rollup 与 Webpack 的 Tree-shakinghttp://zoo.zhengcaiyun.cn/blog/article/tree-shakingRollup 和 Webpack 是目前项目中使用较为广泛的两种打包工具去年发布的 Vite 中打包所依赖的也是 Rollup在对界面加载效率要求越来越高的今天打包工具最终产出的包体积也影响着开发人员对工具的选择所以对 Tree-shaking 的支持程度和配置的便捷性、有效性就尤为重要了。本文就来简单分析下两者 Tree-shaking 的流程和效果差异。Tree-shaking 的目的Tree-shaking 的目标只有一个去除无用代码缩小最终的包体积至于什么算是无用代码呢主要分为三类代码不会被执行不可到达代码执行的结果不会被用到代码只会影响死变量只写不读 Tree-shaking 的目的就是将这三类代码在最终包中剔除做到按需引入。为什么 Tree-shaking 需要依赖 ES6 moduleES6 module 特点只能作为模块顶层的语句出现import 的模块名只能是字符串常量import 之后是不可修改的 例如在使用 CommonJS 时必须导入完整的工具 (tool) 或库 (library) 对象且可带有条件判断来决定是否导入。// 使用 CommonJS 导入完整的 utils 对象
if (hasRequest) {const utils require( utils );
}但是在使用 ES6 模块时无需导入整个 utils 对象我们可以只导入我们所需使用的 request 函数但此处的 import 是不能在任何条件语句下进行的否则就会报错。// 使用 ES6 import 语句导入 request 函数
import { request } from utils;ES6 模块依赖关系是确定的和运行时的状态无关因此可以进行可靠的静态分析这就是 Tree-shaking 的基础。静态分析就是不执行代码直接对代码进行分析在 ES6 之前的模块化比如上面提到的 CommonJS 我们可以动态 require 一个模块只有执行后才知道引用的什么模块这就使得我们不能直接静态的进行分析。Wepack5.x Tree-shaking 机制Webpack 2 正式版本内置支持 ES2015 模块也叫做 harmony modules和未使用模块检测能力。Webpack 4 正式版本扩展了此检测能力通过 package.json 的 sideEffects 属性作为标记向 compiler 提供提示表明项目中的哪些文件是 pure (纯正 ES2015 模块)由此可以安全地删除文件中未使用的部分。Webpack 5 中内置了 terser-webpack-plugin 插件用于 JS 代码压缩相较于 Webpack 4 来说无需再额外下载安装但如果开发者需要增加自定义配置项那还是需要安装。Wepack 自身在编译过程中会根据模块的 import 与 export 依赖分析对代码块进行打标。/*** param {Context} context context* returns {string|Source} the source code that will be included as initialization code*/getContent({ runtimeTemplate, runtimeRequirements }) {runtimeRequirements.add(RuntimeGlobals.exports);runtimeRequirements.add(RuntimeGlobals.definePropertyGetters);// 未使用的模块, 在代码块前增加 unused harmony exports 注释标记const unusedPart this.unusedExports.size 1? /* unused harmony exports ${joinIterableWithComma(this.unusedExports)} */\n: this.unusedExports.size 0? /* unused harmony export ${first(this.unusedExports)} */\n: ;const definitions [];const orderedExportMap Array.from(this.exportMap).sort(([a], [b]) a b ? -1 : 1);// 对 harmony export 进行打标for (const [key, value] of orderedExportMap) {definitions.push(\n/* harmony export */ ${JSON.stringify(key)}: ${runtimeTemplate.returningFunction(value)});}// 对 harmony export 进行打标const definePart this.exportMap.size 0? /* harmony export */ ${RuntimeGlobals.definePropertyGetters}(${this.exportsArgument}, {${definitions.join(,)}\n/* harmony export */ });\n: ;return ${definePart}${unusedPart};}上面是从 Webpack 中截取的打标代码可以看到主要会有两类标记harmony export 和 unused harmony export 分别代表了有用与无用。标记完成后打包时 Teser 会将无用的模块去除。Rollup Tree-shaking 机制以下是 rollup 2.77.2 版本的 package.json 文件我们可以看下它的主要依赖{name: rollup,version: 2.77.2,description: Next-generation ES module bundler,main: dist/rollup.js,module: dist/es/rollup.js,typings: dist/rollup.d.ts,bin: {rollup: dist/bin/rollup},devDependencies: {rollup/plugin-alias: ^3.1.9,rollup/plugin-buble: ^0.21.3,rollup/plugin-commonjs: ^22.0.1,rollup/plugin-json: ^4.1.0,rollup/plugin-node-resolve: ^13.3.0,rollup/plugin-replace: ^4.0.0,rollup/plugin-typescript: ^8.3.3,rollup/pluginutils: ^4.2.1,acorn: ^8.7.1, // 生成 AST 语法树acorn-jsx: ^5.3.2, // 针对 jsx 语法分析acorn-walk: ^8.2.0, // 递归生成对象magic-string: ^0.26.2, // 语句的替换......,},
......
}想要详细了解AcornA tiny, fast JavaScript parser, written completely in JavaScript.可查看(https://github.com/acornjs/acorn)Magic-string可查看(https://github.com/rich-harris/magic-string#readme) 。rollup源码中各个模块的执行顺序大致如下图这也基本表明了它的分析流程。与 Webpack 不同的是Rollup 不仅仅针对模块进行依赖分析它的分析流程如下从入口文件开始组织依赖关系并按文件生成 Module生成抽象语法树Acorn建立语句间的关联关系为每个节点打标标记是否被使用生成代码MagicString position去除无用代码Rollup 的优势它支持导出 ES 模块的包。它支持程序流分析能更加正确的判断项目本身的代码是否有副作用。两个 Case案例1Import 但未调用不可消除import pkgjson from ../package.json;export function getMeta (version: string) {return {lver: version || pkgjson.version,}
}编译后整个 package.json 都被打了进来代码块如下var name zcy/xxxxx-sdk;
var version$1 0.0.1-beta;
var description ;
var main lib/index.es.js;
var module$1 lib/index.cjs.js;
var browser lib/index.umd.js;
var types lib/index.d.ts;
var scripts {test: jest --color --coveragetrue,doc: rm -rf doc typedoc --out doc ./src,.....
};
var repository {type: git,url: ......
};
var author ;
var license ISC;
var devDependencies {babel/core: ^7.15.5,babel/preset-env: ^7.15.4,babel/runtime-corejs3: ^7.11.2,types/jest: ^24.9.1,typescript-eslint/eslint-plugin: ^2.34.0,typescript-eslint/parser: ^2.34.0,babel-loader: ^8.2.2,eslint: ^6.8.0,eslint-config-alloy: ^3.7.2,jest: ^24.9.0,lodash.camelcase: ^4.3.0,path: ^0.12.7,prettier: ^1.19.1,rollup: ^1.32.1,...
.
};
var dependencies {babel/plugin-transform-runtime: ^7.10.5,rollup/plugin-json: ^4.1.0,core-js: ^3.6.5
};
var sideEffects false;
var pkgjson {name: name,version: version$1,description: description,main: main,module: module$1,browser: browser,types: types,scripts: scripts,repository: repository,author: author,license: license,devDependencies: devDependencies,dependencies: dependencies,sideEffects: sideEffects,
};未 import 的部分可消除import { version } from ../package.json;export function getMeta (ver: string) {return {lver: ver || version,}
}编译后可以发现version 作为一个常量被单独打包进来代码块如下var version$1 0.0.1-beta;案例2: 变量影响了全局变量window.utm a.b.c;即使 utm 没有任何地方被使用到在编译打包的过程中上述代码也不能被去除。因此我们可以得出结论在 import 三方工具库、组件库时不要全量 import。设置或改动全局变量需谨慎。Vue3 针对 Tree-shaking 所做的优化在 Vue2.x 中你一定见过以下引入方式import Vue from vueVue.nextTick(() {// 一些和 DOM 有关的东西
})很可惜的是像 Vue.nextTick() 这样的全局 API 是不支持 Tree-shaking 的因为它并没有被单独 export无论 nextTick 方法是否被实际调用都会被包含在最终的打包产物中。但在 Vue3针对全局和内部 API 进行了改造。如果你想更详细的了解 Vue3.x 全局 API Tree-shaking 带来的改动可以查看这里里面详细列出了不再兼容的 API以及在内部帮助器及插件中的使用变化。有了这些能力之后我们可以不再过于关注框架总体的体积了因为按需打包使得我们只需要关注那些我们已经使用到的功能和代码。最终效果对比先分别来看下两种打包工具的配置webpack.config.js :const webpack require(webpack);
const path require(path);
// 删除 const UglifyJsPlugin require(uglifyjs-webpack-plugin);module.exports {entry: path.join(__dirname, src/index.ts),output: {filename: webpack.bundle.js},module: {rules: [{test: /\.(js|ts|tsx)$/,exclude: /(node_modules|bower_components|lib)/,use: {loader: babel-loader,options: {presets: [babel/preset-env]}}},{test: /\.tsx?$/,use: ts-loader,exclude: /(node_modules|lib)/,},]},resolve: {extensions: [.tsx, .ts, .js],},optimization: { // tree-shaking 优化配置usedExports: true,},plugins: [new webpack.optimize.ModuleConcatenationPlugin()]
}rollup.config.js :import resolve from rollup-plugin-node-resolve;
import commonjs from rollup-plugin-commonjs;
import typescript from rollup-plugin-typescript2;
import babel from rollup-plugin-babel;
import json from rollup-plugin-json;
import { uglify } from rollup-plugin-uglifyexport default {input: src/index.ts,output: [{ file: lib/index.cjs.js, format: cjs },],treeshake: true, // treeshake 开关plugins: [json(),typescript(),resolve(),commonjs(),babel({exclude: node_modules/**,runtimeHelpers: true,sourceMap: true,extensions: [.js, .jsx, .es6, .es, .mjs, .ts, .json],}),uglify(),],
};最后来看下打包结果的对比。结果发现本项目在配置 sideEffectsfalse 前后时长和体积没有明显变化。对比Tree-Shaking 前体积Tree-Shaking 后体积打包时长webpack5.52.046kb44kb4.8srollup1.32.124kb18kb3.7s另上述打包效果中的项目是 sdk 工具包。结束语你如果想了解 Rollup 会打包更快的原因可以查看我之前发布的文章《Vite 特性和部分源码解析 》(https://www.zoo.team/article/about-vite。关于 Tree-shaking 的问题也欢迎你在下面留言讨论。推荐阅读《Rollup源码解析》(https://juejin.cn/post/7021115814870810660)Rollup Tree-shaking 机制 (https://www.rollupjs.com/guide/introduction#tree-shaking)················· 若川简介 ·················你好我是若川毕业于江西高校。现在是一名前端开发“工程师”。写有《学习源码整体架构系列》20余篇在知乎、掘金收获超百万阅读。从2014年起每年都会写一篇年度总结已经坚持写了8年点击查看年度总结。同时最近组织了源码共读活动帮助5000前端人学会看源码。公众号愿景帮助5年内前端人走向前列。扫码加我微信 lxchuan12、拉你进源码共读群今日话题目前建有江西|湖南|湖北 籍 前端群想进群的可以加我微信 lxchuan12 进群。分享、收藏、点赞、在看我的文章就是对我最大的支持~