Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve chunking algorithm for dynamic imports #3354

Merged
merged 49 commits into from Jan 27, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
49 commits
Select commit Hold shift + click to select a range
096f31c
give Set an array
tjenkinson Dec 25, 2019
4d5c88f
update chunkColouring to support already loaded modules
tjenkinson Dec 25, 2019
0eff2dc
put back updating handledEntryPoints
tjenkinson Dec 25, 2019
429366b
update 'dynamic-import' test
tjenkinson Dec 31, 2019
7b5886f
update 'dynamic-import-chained' test
tjenkinson Dec 31, 2019
e607d28
update 'dynamic-import-chunking' test
tjenkinson Dec 31, 2019
5c36759
update 'dynamic-import-facade' test
tjenkinson Dec 31, 2019
8622aef
update 'dynamic-import-inline-colouring' test
tjenkinson Dec 31, 2019
89079d2
update 'dynamic-import-statically-imported' test
tjenkinson Dec 31, 2019
18647be
update 'dynamic-import-statically-imported-2' test
tjenkinson Dec 31, 2019
d8a8a10
update 'resolve-dynamic-import' test
tjenkinson Dec 31, 2019
80bcec9
update 'nested-dynamic-imports' test
tjenkinson Dec 31, 2019
5dc21c7
update 'sanitize-internal-imports' test
tjenkinson Dec 31, 2019
9ea97d6
update 'import-meta-url' test
tjenkinson Dec 31, 2019
b78e1e4
update 'reference-files' test
tjenkinson Dec 31, 2019
5dd7bcb
update 'emit-asset' test
tjenkinson Dec 31, 2019
f6f84a7
update 'aliasing-extensions' test
tjenkinson Dec 31, 2019
07f86a5
update bundle-information test
tjenkinson Dec 31, 2019
a01c145
update hooks test
tjenkinson Dec 31, 2019
8a94ab9
disable 'updates the right hashes on dependency changes' test for now
tjenkinson Dec 31, 2019
d841a51
update 'updates the right hashes on dependency changes' test to updat…
tjenkinson Dec 31, 2019
55ca332
Add some first specific tests
lukastaegert Jan 10, 2020
fa7dfa3
use correct key for dynamicDependentEntryPointsByDynamicEntry
tjenkinson Jan 10, 2020
14375fb
try a different approach
tjenkinson Jan 11, 2020
1856149
coloursToRoot
tjenkinson Jan 12, 2020
fb33f27
sequential colouring
tjenkinson Jan 12, 2020
3fd60a8
just check if colour is already loaded
tjenkinson Jan 12, 2020
eb88128
refactor
tjenkinson Jan 12, 2020
a94daf8
going through tests
tjenkinson Jan 12, 2020
b464d2c
manual chunks
tjenkinson Jan 12, 2020
3cf8458
propogate manualChunkAlias
tjenkinson Jan 12, 2020
193b66f
simplify
tjenkinson Jan 15, 2020
46fc382
working through tests
tjenkinson Jan 15, 2020
c986dc4
bring xor back
tjenkinson Jan 16, 2020
a417cf2
update fixtures
tjenkinson Jan 16, 2020
8425325
revert bundle-information test changes
tjenkinson Jan 16, 2020
921dff6
reset chunk order test
tjenkinson Jan 16, 2020
b1b7abd
revert chunk order test changes
tjenkinson Jan 16, 2020
c10e59d
make all the tests pass
tjenkinson Jan 18, 2020
317cc69
tidy up
tjenkinson Jan 18, 2020
6c128b9
Merge branch 'master' into improve-chunking-algo-dynamic-imports
tjenkinson Jan 18, 2020
5230071
fix test description
tjenkinson Jan 19, 2020
7e94887
remove unneeded options
tjenkinson Jan 19, 2020
4ac0406
no need for _
tjenkinson Jan 19, 2020
b19ef0d
Add test case when multiple entry points have a shared dependency tha…
lukastaegert Jan 22, 2020
529a9a8
handle new test case
tjenkinson Jan 25, 2020
ddd643d
Merge branch 'master' into improve-chunking-algo-dynamic-imports
tjenkinson Jan 25, 2020
a0ed839
don't reprocess entry point if previously processed with same loaded …
tjenkinson Jan 26, 2020
20813c0
Use new, faster chunking algorithm
lukastaegert Jan 27, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
197 changes: 149 additions & 48 deletions src/utils/chunkColouring.ts
Expand Up @@ -2,74 +2,175 @@ import ExternalModule from '../ExternalModule';
import Module from '../Module';
import { randomUint8Array, Uint8ArrayXor } from './entryHashing';

type DependentModuleMap = Map<Module, Set<Module>>;

export function assignChunkColouringHashes(
entryModules: Module[],
manualChunkModules: Record<string, Module[]>
) {
let currentEntry: Module, currentEntryHash: Uint8Array;
let modulesVisitedForCurrentEntry: Set<string>;
const handledEntryPoints: Set<string> = new Set();
const dynamicImports: Module[] = [];
const { dependentEntryPointsByModule, dynamicImportersByModule } = analyzeModuleGraph(
entryModules
);
const dynamicDependentEntryPointsByDynamicEntry: DependentModuleMap = getDynamicDependentEntryPoints(
dependentEntryPointsByModule,
dynamicImportersByModule
);

const addCurrentEntryColourToModule = (module: Module) => {
if (currentEntry.manualChunkAlias) {
module.manualChunkAlias = currentEntry.manualChunkAlias;
module.entryPointsHash = currentEntryHash;
} else {
Uint8ArrayXor(module.entryPointsHash, currentEntryHash);
}
if (manualChunkModules) {
for (const chunkName of Object.keys(manualChunkModules)) {
const entryHash = randomUint8Array(10);

for (const dependency of module.dependencies) {
if (
dependency instanceof ExternalModule ||
modulesVisitedForCurrentEntry.has(dependency.id)
) {
continue;
}
modulesVisitedForCurrentEntry.add(dependency.id);
if (!handledEntryPoints.has(dependency.id) && !dependency.manualChunkAlias) {
addCurrentEntryColourToModule(dependency);
for (const entry of manualChunkModules[chunkName]) {
addColourToModuleDependencies(
entry,
entryHash,
null,
dependentEntryPointsByModule,
dynamicDependentEntryPointsByDynamicEntry
);
}
}
}

for (const { resolution } of module.dynamicImports) {
if (
resolution instanceof Module &&
resolution.dynamicallyImportedBy.length > 0 &&
!resolution.manualChunkAlias
) {
dynamicImports.push(resolution);
}
for (const entry of entryModules) {
if (!entry.manualChunkAlias) {
const entryHash = randomUint8Array(10);
addColourToModuleDependencies(
entry,
entryHash,
null,
dependentEntryPointsByModule,
dynamicDependentEntryPointsByDynamicEntry
);
}
};
}

if (manualChunkModules) {
for (const chunkName of Object.keys(manualChunkModules)) {
currentEntryHash = randomUint8Array(10);
for (const entry of dynamicImportersByModule.keys()) {
if (!entry.manualChunkAlias) {
const entryHash = randomUint8Array(10);
addColourToModuleDependencies(
entry,
entryHash,
dynamicDependentEntryPointsByDynamicEntry.get(entry)!,
dependentEntryPointsByModule,
dynamicDependentEntryPointsByDynamicEntry
);
}
}
}

for (currentEntry of manualChunkModules[chunkName]) {
modulesVisitedForCurrentEntry = new Set(currentEntry.id);
addCurrentEntryColourToModule(currentEntry);
function analyzeModuleGraph(
entryModules: Module[]
): {
dependentEntryPointsByModule: DependentModuleMap;
dynamicImportersByModule: DependentModuleMap;
} {
const dynamicImportersByModule: DependentModuleMap = new Map();
const dependentEntryPointsByModule: DependentModuleMap = new Map();
const entriesToHandle = new Set(entryModules);
for (const currentEntry of entriesToHandle) {
const modulesToHandle = new Set<Module>([currentEntry]);
for (const module of modulesToHandle) {
getDependentModules(dependentEntryPointsByModule, module).add(currentEntry);
for (const dependency of module.dependencies) {
if (!(dependency instanceof ExternalModule)) {
modulesToHandle.add(dependency);
}
}
for (const { resolution } of module.dynamicImports) {
if (
resolution instanceof Module &&
resolution.dynamicallyImportedBy.length > 0 &&
!resolution.manualChunkAlias
) {
getDependentModules(dynamicImportersByModule, resolution).add(module);
entriesToHandle.add(resolution);
}
}
}
}
return { dependentEntryPointsByModule, dynamicImportersByModule };
}

for (currentEntry of entryModules) {
handledEntryPoints.add(currentEntry.id);
currentEntryHash = randomUint8Array(10);
modulesVisitedForCurrentEntry = new Set(currentEntry.id);
if (!currentEntry.manualChunkAlias) {
addCurrentEntryColourToModule(currentEntry);
function getDependentModules(moduleMap: DependentModuleMap, module: Module): Set<Module> {
const dependentModules = moduleMap.get(module) || new Set();
moduleMap.set(module, dependentModules);
return dependentModules;
}

function getDynamicDependentEntryPoints(
dependentEntryPointsByModule: DependentModuleMap,
dynamicImportersByModule: DependentModuleMap
): DependentModuleMap {
const dynamicDependentEntryPointsByDynamicEntry: DependentModuleMap = new Map();
for (const [dynamicEntry, importers] of dynamicImportersByModule.entries()) {
const dynamicDependentEntryPoints = getDependentModules(
dynamicDependentEntryPointsByDynamicEntry,
dynamicEntry
);
for (const importer of importers) {
for (const entryPoint of dependentEntryPointsByModule.get(importer)!) {
dynamicDependentEntryPoints.add(entryPoint);
}
}
}
return dynamicDependentEntryPointsByDynamicEntry;
}

for (currentEntry of dynamicImports) {
if (handledEntryPoints.has(currentEntry.id)) {
function addColourToModuleDependencies(
entry: Module,
colour: Uint8Array,
dynamicDependentEntryPoints: Set<Module> | null,
dependentEntryPointsByModule: DependentModuleMap,
dynamicDependentEntryPointsByDynamicEntry: DependentModuleMap
) {
const manualChunkAlias = entry.manualChunkAlias;
const modulesToHandle = new Set([entry]);
for (const module of modulesToHandle) {
if (manualChunkAlias) {
module.manualChunkAlias = manualChunkAlias;
module.entryPointsHash = colour;
} else if (
dynamicDependentEntryPoints &&
areEntryPointsContainedOrDynamicallyDependent(
dynamicDependentEntryPoints,
dependentEntryPointsByModule.get(module)!,
dynamicDependentEntryPointsByDynamicEntry
)
) {
continue;
} else {
Uint8ArrayXor(module.entryPointsHash, colour);
}
for (const dependency of module.dependencies) {
if (!(dependency instanceof ExternalModule || dependency.manualChunkAlias)) {
modulesToHandle.add(dependency);
}
}
}
}

function areEntryPointsContainedOrDynamicallyDependent(
entryPoints: Set<Module>,
superSet: Set<Module>,
dynamicDependentEntryPointsByDynamicEntry: DependentModuleMap
): boolean {
for (const module of entryPoints) {
if (!superSet.has(module)) {
const dynamicDependentEntryPoints = dynamicDependentEntryPointsByDynamicEntry.get(module);
if (
!(
dynamicDependentEntryPoints &&
areEntryPointsContainedOrDynamicallyDependent(
dynamicDependentEntryPoints,
superSet,
dynamicDependentEntryPointsByDynamicEntry
)
)
) {
return false;
}
}
handledEntryPoints.add(currentEntry.id);
currentEntryHash = randomUint8Array(10);
modulesVisitedForCurrentEntry = new Set(currentEntry.id);
addCurrentEntryColourToModule(currentEntry);
}
return true;
}
21 changes: 7 additions & 14 deletions src/utils/entryHashing.ts
@@ -1,12 +1,12 @@
const CHAR_CODE_A = 97;
const CHAR_CODE_0 = 48;

function intToHex(num: number) {
function intToHex(num: number): string {
if (num < 10) return String.fromCharCode(CHAR_CODE_0 + num);
else return String.fromCharCode(CHAR_CODE_A + (num - 10));
}

export function Uint8ArrayToHexString(buffer: Uint8Array) {
export function Uint8ArrayToHexString(buffer: Uint8Array): string {
let str = '';
// hex conversion - 2 chars per 8 bit component
for (let i = 0; i < buffer.length; i++) {
Expand All @@ -18,24 +18,17 @@ export function Uint8ArrayToHexString(buffer: Uint8Array) {
return str;
}

export function Uint8ArrayXor(to: Uint8Array, from: Uint8Array) {
for (let i = 0; i < to.length; i++) to[i] = to[i] ^ from[i];
return to;
}

export function randomUint8Array(len: number) {
export function randomUint8Array(len: number): Uint8Array {
const buffer = new Uint8Array(len);
for (let i = 0; i < buffer.length; i++) buffer[i] = Math.random() * (2 << 8);
return buffer;
}

export function Uint8ArrayEqual(bufferA: Uint8Array, bufferB: Uint8Array) {
for (let i = 0; i < bufferA.length; i++) {
if (bufferA[i] !== bufferB[i]) return false;
}
return true;
export function Uint8ArrayXor(to: Uint8Array, from: Uint8Array): Uint8Array {
for (let i = 0; i < to.length; i++) to[i] = to[i] ^ from[i];
return to;
}

export function randomHexString(len: number) {
export function randomHexString(len: number): string {
return Uint8ArrayToHexString(randomUint8Array(Math.floor(len / 2)));
}
@@ -0,0 +1,26 @@
define(['exports'], function (exports) { 'use strict';

class C {
fn (num) {
console.log(num - p$1);
}
}

var p = 43;

new C().fn(p);

class C$1 {
fn (num) {
console.log(num - p);
}
}

var p$1 = 42;

new C$1().fn(p$1);

exports.p = p;
exports.p$1 = p$1;

});
@@ -1,17 +1,9 @@
define(['exports', './main2'], function (exports, main2) { 'use strict';
define(['exports', './generated-main1'], function (exports, main2) { 'use strict';

class C {
fn (num) {
console.log(num - main2.p);
}
}

var p = 42;

new C().fn(p);
exports.p = main2.p$1;

exports.p = p;

Object.defineProperty(exports, '__esModule', { value: true });
Object.defineProperty(exports, '__esModule', { value: true });

});
@@ -1,17 +1,9 @@
define(['exports', './main1'], function (exports, main1) { 'use strict';
define(['exports', './generated-main1'], function (exports, main2) { 'use strict';

class C {
fn (num) {
console.log(num - main1.p);
}
}

var p = 43;

new C().fn(p);
exports.p = main2.p;

exports.p = p;

Object.defineProperty(exports, '__esModule', { value: true });
Object.defineProperty(exports, '__esModule', { value: true });

});
@@ -0,0 +1,24 @@
'use strict';

class C {
fn (num) {
console.log(num - p$1);
}
}

var p = 43;

new C().fn(p);

class C$1 {
fn (num) {
console.log(num - p);
}
}

var p$1 = 42;

new C$1().fn(p$1);

exports.p = p;
exports.p$1 = p$1;
Expand Up @@ -2,16 +2,8 @@

Object.defineProperty(exports, '__esModule', { value: true });

var main2 = require('./main2.js');
var main2 = require('./generated-main1.js');

class C {
fn (num) {
console.log(num - main2.p);
}
}

var p = 42;

new C().fn(p);

exports.p = p;
exports.p = main2.p$1;