Цель встряхивания дерева
Приложения часто имеют файл входа, который эквивалентен стволу дерева, а файл входа имеет много зависимых модулей, которые эквивалентны ветвям. На практике, хотя это зависит от определенного модуля, на самом деле он использует только некоторые из его функций. При встряхивании дерева неиспользуемые модули встряхиваются, чтобы достичь цели удаления бесполезного кода.
модуль
Модули CommonJSrequire
modules.exports
,exports
var my_lib;
if (Math.random()) {
my_lib = require('foo');
} else {
my_lib = require('bar');
}
module.exports = xx
Модули для ES2015 (ES6)import
,export
// lib.js
export function foo() {}
export function bar() {}
// main.js
import { foo } from './lib.js';
foo();
Принцип встряхивания дерева
О принципе встряхивания дерева, вПрактика оптимизации производительности Tree Shaking — принципыСказано более ясно, просто.
Tree shaking的本质是消除无用的JavaScript代码。
因为ES6模块的出现,ES6模块依赖关系是确定的,`和运行时的状态无关`,可以进行可靠的静态分析,
这就是Tree shaking的基础。
Инструменты для поддержки встряхивания деревьев
- Webpack/UglifyJS
- rollup
- Google closure compiler
Сегодня давайте посмотрим, что делает встряхивание дерева в Webpack.
Webpack Tree shaking
Что именно может сделать встряхивание дерева?
1. Встряхивание Webpack Tree запускает анализ с модулей верхнего уровня ES6 и может очищать неиспользуемые модули.
отПример официального сайтаприходите посмотретькод:
//App.js
import { cube } from './utils.js';
cube(2);
//utils.js
export function square(x) {
console.log('square');
return x * x;
}
export function cube(x) {
console.log('cube');
return x * x * x;
}
result: Код квадрата удален.
function(e, t, r) {
"use strict";
r.r(t), console.log("cube")
}
2. Встряхивание дерева Webpack рефакторит модули, вызываемые в несколько слоев, извлекает код в них и упрощает структуру вызова функций.
//App.js
import { getEntry } from './utils'
console.log(getEntry());
//utils.js
import entry1 from './entry.js'
export function getEntry() {
return entry1();
}
//entry.js
export default function entry1() {
return 'entry1'
}
result: Упрощенный код выглядит следующим образом
//摘录核心代码
function(e, t, r) {
"use strict";
r.r(t), console.log("entry1")
}
3. Встряхивание дерева Webpack не очистит IIFE (немедленно вызовите выражение функции)
Что такое ИИФЭ? ?IIFE in MDN
//App.js
import { cube } from './utils.js';
console.log(cube(2));
//utils.js
var square = function(x) {
console.log('square');
}();
export function cube(x) {
console.log('cube');
return x * x * x;
}
result: И квадрат, и cude существуют
function(e, t, n) {
"use strict";
n.r(t);
console.log("square");
console.log(function(e) {
return console.log("cube"), e * e * e
}(2))
}
Вопрос здесь будет в том, почему IIFE не очищается? существуетВаш Tree-Shaking бесполезенБыл анализ, и в нем есть пример, см. ниже
причина проста:因为IIFE比较特殊,它在被翻译时(JS并非编译型的语言)就会被执行,Webpack不做程序流分析,它不知道IIFE会做什么特别的事情,所以不会删除这部分代码
Например:
var V8Engine = (function () {
function V8Engine () {}
V8Engine.prototype.toString = function () { return 'V8' }
return V8Engine
}())
var V6Engine = (function () {
function V6Engine () {}
V6Engine.prototype = V8Engine.prototype // <---- side effect
V6Engine.prototype.toString = function () { return 'V6' }
return V6Engine
}())
console.log(new V8Engine().toString())
result:
输出V6,而并不是V8
Если в IIFE V6 есть еще какие-то объявления глобальных переменных, то, конечно, их нельзя удалить.
4. Встряхивание Webpack Tree для функции возврата IIFE, если оно не используется, оно будет очищено
Конечно, Webpack не так уж и глуп, если вы обнаружите, что функцию возврата IIFE неоткуда вызывать, ее все равно можно удалить.
//App.js
import { cube } from './utils.js';
console.log(cube(2));
//utils.js
var square = function(x) {
console.log('square');
return x * x;
}();
function getSquare() {
console.log('getSquare');
square();
}
export function cube(x) {
console.log('cube');
return x * x * x;
}
result: Результат выглядит следующим образом
function(e, t, n) {
"use strict";
n.r(t);
console.log("square"); <= square这个IIFE内部的代码还在
console.log(function(e) {
return console.log("cube"), e * e * e <= square这个IIFEreturn的方法因为getSquare未被调用而被删除
}(2))
}
5. Встряхивание Webpack Tree в сочетании со сторонними пакетами
//App.js
import { getLast } from './utils.js';
console.log(getLast('abcdefg'));
//utils.js
import _ from 'lodash'; <=这里的引用方式不同,会造成bundle的不同结果
export function getLast(string) {
console.log('getLast');
return _.last(string);
}
result: Результат выглядит следующим образом
import _ from 'lodash';
Asset Size
bundle.js 70.5 KiB
import { last } from 'lodash';
Asset Size
bundle.js 70.5 KiB
import last from 'lodash/last'; <=这种引用方式明显降低了打包后的大小
Asset Size
bundle.js 1.14 KiB
Чего не может встряхивание дерева Webpack
существуетНа 80% меньше объема! Раскройте истинный потенциал встряхивания дерева веб-пакетовКак упоминалось в статье, хотя тряска Webpack Tree очень мощная, у нее все же есть недостатки.
//App.js
import { Add } from './utils'
Add(1 + 2);
//utils.js
import { isArray } from 'lodash-es';
export function array(array) {
console.log('isArray');
return isArray(array);
}
export function Add(a, b) {
console.log('Add');
return a + b
}
result: код, который не следует импортировать
这个`array`函数未被使用,但是lodash-es这个包的部分代码还是会被build到bundle.js中
Вы можете использовать этот плагинwebpack-deep-scope-analysis-pluginрешить
резюме
Если вы хотите更好
Чтобы использовать встряхивание Webpack Tree, выполните следующие действия:
- Модули, использующие ES2015 (ES6)
- Избегайте использования IIFE
- Если вы используете сторонний модуль, то можете попробовать использовать его прямо из пути к файлу (это не лучший способ)
import { fn } from 'module';
=>
import fn from 'module/XX';
Проблема 1 с Babel — преобразование синтаксиса (Babel6)
以上的所有示例都没有使用Babel进行处理
, но мы понимаем, что в реальных проектах нам все же нужен Babel. Так в чем проблема с использованием Babel? (Далее обсуждение основано наBabel6
на основе)
мы видимкод:
//App.js
import { Apple } from './components'
const appleModel = new Apple({ <==仅调用了Apple
model: 'IphoneX'
}).getModel()
console.log(appleModel)
//components.js
export class Person {
constructor ({ name, age, sex }) {
this.className = 'Person'
this.name = name
this.age = age
this.sex = sex
}
getName () {
return this.name
}
}
export class Apple {
constructor ({ model }) {
this.className = 'Apple'
this.model = model
}
getModel () {
return this.model
}
}
//webpack.config.js
const path = require('path');
module.exports = {
entry: [
'./App.js'
],
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, './build'),
},
module: {},
mode: 'production'
};
result: Результат выглядит следующим образом
function(e, t, n) {
"use strict";
n.r(t);
const r = new class {
constructor({ model: e }) {
this.className = "Apple", this.model = e
}
getModel() {
return this.model
}
}({ model: "IphoneX" }).getModel();
console.log(r)
}
//仅有Apple的类,没有Person的类(Tree shaking成功)
//class还是class,并没有经过语法转换(没有经过Babel的处理)
А как насчет добавления обработки Babel (babel-loader)?
//App.js和component.js保持不变
//webpack.config.js
const path = require('path');
module.exports = {
entry: [
'./App.js'
],
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, './buildBabel'),
},
module: {
rules: [
{
test: /\.js$/,
use: {
loader: 'babel-loader',
options: {
presets: ['env']
}
}
}
]
},
mode: 'production'
};
результат:Результат выглядит следующим образом
function(e, n, t) {
"use strict";
Object.defineProperty(n, "__esModule", { value: !0 });
var r = function() {
function e(e, n) {
for(var t = 0; t < n.length; t++) {
var r = n[t];
r.enumerable = r.enumerable || !1, r.configurable = !0, "value" in r && (r.writable = !0), Object.defineProperty(e, r.key, r)
}
}
return function(n, t, r) {
return t && e(n.prototype, t), r && e(n, r), n
}
}();
function o(e, n) {
if(!(e instanceof n)) throw new TypeError("Cannot call a class as a function")
}
n.Person = function() {
function e(n) {
var t = n.name, r = n.age, u = n.sex;
o(this, e), this.className = "Person", this.name = t, this.age = r, this.sex = u
}
return r(e, [{
key: "getName", value: function() {
return this.name
}
}]), e
}(), n.Apple = function() {
function e(n) {
var t = n.model;
o(this, e), this.className = "Apple", this.model = t
}
return r(e, [{
key: "getModel", value: function() {
return this.model
}
}]), e
}()
}
//这次不仅Apple类在,Person类也存在(Tree shaking失败了)
//class已经被babel处理转换了
Вывод: Tree Shaking Webpack позволяет удалять экспортированные, но неиспользуемые блоки кода, но при использовании с Babel(6) возникают проблемы.
Итак, давайте посмотрим, что сделал Бабель,Это код, обработанный Babel6
'use strict';
Object.defineProperty(exports, "__esModule", {
value: true
});
//_createClass本质上也是一个IIFE
var _createClass = function() {
function defineProperties(target, props) {
for(var i = 0; i < props.length; i++) {
var descriptor = props[i];
descriptor.enumerable = descriptor.enumerable || false;
descriptor.configurable = true;
if("value" in descriptor) descriptor.writable = true;
Object.defineProperty(target, descriptor.key, descriptor);
}
}
return function(Constructor, protoProps, staticProps) {
if(protoProps) defineProperties(Constructor.prototype, protoProps);
if(staticProps) defineProperties(Constructor, staticProps);
return Constructor;
};
}();
function _classCallCheck(instance, Constructor) {
if(!(instance instanceof Constructor)) {
throw new TypeError("Cannot call a class as a function");
}
}
//Person本质上也是一个IIFE
var Person = exports.Person = function () {
function Person(_ref) {
var name = _ref.name,
age = _ref.age,
sex = _ref.sex;
_classCallCheck(this, Person);
this.className = 'Person';
this.name = name;
this.age = age;
this.sex = sex;
}
_createClass(Person, [{ <==这里调用了另一个IIFE
key: 'getName',
value: function getName() {
return this.name;
}
}]);
return Person;
}();
С самого начала мы знали, что встряхивание дерева Webpack не обрабатывает IIFE, поэтому даже если здесь не вызывается класс Person, код для класса Person существует в комплекте.
Мы можем установить с помощьюloose: true
чтобы Babel использовал свободный режим при конвертации, но это только удаляет_createClass
, сам Человек все еще существует
//webpack.config.js
{
loader: 'babel-loader',
options: {
presets: [["env", { loose: true }]]
}
}
result: Результат выглядит следующим образом
function(e, t, n) {
"use strict";
function r(e, t) {
if(!(e instanceof t)) throw new TypeError("Cannot call a class as a function")
}
t.__esModule = !0;
t.Person = function() {
function e(t) {
var n = t.name, o = t.age, u = t.sex;
r(this, e), this.className = "Person", this.name = n, this.age = o, this.sex = u
}
return e.prototype.getName = function() {
return this.name
}, e
}(), t.Apple = function() {
function e(t) {
var n = t.model;
r(this, e), this.className = "Apple", this.model = n
}
return e.prototype.getModel = function() {
return this.model
}, e
}()
}
Обсуждение Babel6
Class declaration in IIFE considered as side effect
Видеть:GitHub.com/Секретарь, о, о, УГ в…
Суммировать:
- Uglify не выполняет анализ хода выполнения программы, но выполняет сборку (Uglify не выполняет анализ хода выполнения программы, но выполняет сводка)
- Присвоение переменной может вызвать побочный эффект
- Add some
/*#__PURE__*/
аннотация могла бы с этим помочь (можно попробовать добавить аннотацию/*#__PURE__*/
способ объявить функцию без побочных эффектов, чтобы Webpack мог отфильтровать эту часть кода при анализе и обработке)
Что касается третьего пункта: добавление/*#__PURE__*/
, который также является Вавилоном7
поведение при исполнении,Это код, обработанный Babel7
var Person =
/*#__PURE__*/ <=这里添加了注释
function() {
function Person(_ref) {
var name = _ref.name,
age = _ref.age,
sex = _ref.sex;
_classCallCheck(this, Person);
this.className = 'Person';
this.name = name;
this.age = age;
this.sex = sex;
}
_createClass(Person, [{
key: "getName",
value: function getName() {
return this.name;
}
}]);
return Person;
}();
exports.Person = Person;
Поэтому в работающей среде Babel7 этот неиспользуемый класс Person может быть отфильтрован после обработки Webpack.
Проблема 2 с Babel — Преобразование модуля (Babel6/7)
Мы уже знаем, что модули CommonJS отличаются от модулей ES6, Babel преобразует все модули вexports
комбинироватьrequire
В форме , мы также знаем, что Webpack основан на модулях ES6 для достижения максимального сотрясения дерева, поэтому, когда мы используем Babel, мы должны отключить это поведение Babel следующим образом:
//babel.rc
presets: [["env",
{ module: false }
]]
Но вот вопрос: когда мы должны отключить это преобразование?
Если мы все в App, закрывать этот модуль бессмысленно, потому что если он закрыт, то упакованный пакет не может быть запущен в браузере (импорт не поддерживается). Итак, здесь мы должны установить его, когда библиотека функций, от которой зависит приложение, упакована.
Например: нравитсяlodash/lodash-es
,redux
,react-redux
,styled-component
Эти библиотеки существуют как в версиях ES5, так и в версиях ES6.
- redux
- dist
- es
- lib
- src
...
В то же время установка конфигурации записи в packages.json позволяет Webpack сначала читать файлы ES6. например:Запись Redux ES
//package.json
"main": "lib/redux.js",
"unpkg": "dist/redux.js",
"module": "es/redux.js",
"typings": "./index.d.ts",
Webpack Tree shaking - Side Effect
В официальной документации упоминаетсяsideEffects, но в документации мало подробностей о том, что делает этот тег, даже при запускеЧиновник привел пример, в последней версии Webpack не может получить результаты, которые он интерпретирует, поэтому больше сомнений в использовании этого тега. закончить чтениеКак использовать побочные эффекты в Webpack?Что даст эта встреча? как использовать? Имея базовое понимание, мы можем копнуть глубже
Что именно делает встряхивание дерева?
Демо1:
//App.js
import { a } from 'tree-shaking-npm-module-demo'
console.log(a);
//index.js
export { a } from "./a";
export { b } from "./b";
export { c } from "./c";
//a.js
export var a = "a";
//b.js
export var b = "b";
//c.js
export var c = "c";
результат: остался только код a
function(e, t, r) {
"use strict";
r.r(t);
console.log("a")
}
Демо2:
//App.js
import { a } from 'tree-shaking-npm-module-demo'
console.log(a);
//index.js
export { a } from "./a";
export { b } from "./b";
export { c } from "./c";
//a.js
export var a = "a";
//b.js
(function fun() {
console.log('fun');
})()
window.name = 'name'
export var b = "b";
//c.js
export var c = "c";
результат: Код a оставлен, а также присутствует код в b
function(e, n, t) {
"use strict";
t.r(n);
console.log("fun"), window.name = "name";
console.log("a")
}
Demo3: добавить тег sideEffects
//package.json
{
"sideEffects": false,
}
результат: оставлен только код модуля a, а все побочные эффекты модуля b удалены
function(e, t, r) {
"use strict";
r.r(t);
console.log("a")
}
Вкратце: ссылкаWhat Does Webpack 4 Expect From A Package With sideEffects: falseсередина@asdfasdfads(那个目前只有三个赞)
ответ
Фактически:
The consensus is that "has no sideEffects" phrase can be decyphered as "doesn't talk to things external to the module at the top level".
译为:
"没有副作用"这个短语可以被解释为"不与顶层模块以外的东西进行交互"。
В Demo3 мы добавили"sideEffects": false
Это значит:
1. Хотя в модуле b есть некоторый код с побочными эффектами (IIFE и операции, которые изменяют глобальные переменные/свойства), мы не думаем, что опасно его удалять
2. Модуль есть
引用过
(Импортируется или реэкспортируется другими модулями)
情况A
//b.js
(function fun() {
console.log('fun');
})()
window.name = 'name'
export var b = "b";
//index.js
import { b } from "./b";
分析:
b模块一旦被import,那么其中的代码会在翻译时执行
情况B
//b.js
(function fun() {
console.log('fun');
})()
window.name = 'name'
export var b = "b";
//index.js
export { b } from "./b";
分析:
According to the ECMA Module Spec, whenever a module reexports all exports (regardless if used or unused) need to be evaluated and executed in the case that one of those exports created a side-effect with another.
b模块一旦被重新re-export,根据ECMA模块规范,每当模块重新导出所有导出(无论使用或未使用)时,都需要对其中一个导出与另一个导出产生副作用的情况进行评估和执行
情况C
//b.js
(function fun() {
console.log('fun');
})()
window.name = 'name'
export var b = "b";
//index.js
//没有import也没有export
分析:
没用的当然没有什么影响
Пока выполняются два вышеуказанных пункта: мы можем безопасно добавить этот флаг в зависимости от ситуации, чтобы уведомить Webpack о том, что этот бесполезный код можно безопасно удалить. Конечно, если ваш код имеет некоторые побочные эффекты, вместо этого вы можете предоставить массив:
"sideEffects": [
"./src/some-side-effectful-file.js"
]
Суммировать:
Если вы хотите воспользоваться встряхиванием дерева Webpack, вам нужно внести некоторые изменения в свой проект. предложение:
1. Для сторонних библиотек:
- Поддержание команды: плюс по мере необходимости
sideEffects
тег, при изменении конфигурации Babel для экспортаES6模块
- Сторонние: попробуйте использовать версию с модулями ES.
2. Инструменты:
- Обновите Webpack до 4.x
- Обновите Babel до 7.x
Ссылаться на
- Ваш Tree-Shaking бесполезен:nuggets.capable/post/684490…
- Практика оптимизации производительности Tree-Shaking — принципы:nuggets.capable/post/684490…
- Практика оптимизации производительности Tree-Shaking - Практика:nuggets.capable/post/684490…
- сегмент fault.com/ah/119000001…
- Использование Tree-Shaking:Woohoo. Объявление Hubei.com/wiki/WebCrawler…
- На 80% меньше объема! Раскройте истинный потенциал встряхивания дерева веб-пакетов:nuggets.capable/post/684490…
- Как webpack устраняет мертвый код с помощью анализа области:Винсент Свойства.GitHub.IO/2018/05/bet… (GitHub.com/Винсент Опрос…)
- Вы обновили Webpack2 сегодня?www.aliued.com/?p=4060
- Веб-сайт на китайском языке:Woohoo. Веб-пакет JS.com/guides/prod…
- в вебпаке
sideEffects
Как мне его использовать?nuggets.capable/post/684490…