Skip to content

Commit

Permalink
Merge pull request #7056 from webpack/feature/preload
Browse files Browse the repository at this point in the history
add support for link preload/prefetch
  • Loading branch information
sokra committed Apr 17, 2018
2 parents 0ff2901 + 8e2e19b commit ec4ec8e
Show file tree
Hide file tree
Showing 76 changed files with 795 additions and 154 deletions.
19 changes: 16 additions & 3 deletions lib/AsyncDependenciesBlock.js
Expand Up @@ -6,15 +6,28 @@
const DependenciesBlock = require("./DependenciesBlock");

module.exports = class AsyncDependenciesBlock extends DependenciesBlock {
constructor(name, module, loc, request) {
constructor(groupOptions, module, loc, request) {
super();
this.chunkName = name;
if (typeof groupOptions === "string") {
groupOptions = { name: groupOptions };
} else if (!groupOptions) {
groupOptions = { name: undefined };
}
this.groupOptions = groupOptions;
this.chunkGroup = undefined;
this.module = module;
this.loc = loc;
this.request = request;
}

get chunkName() {
return this.groupOptions.name;
}

set chunkName(value) {
this.groupOptions.name = value;
}

get chunks() {
throw new Error("Moved to AsyncDependenciesBlock.chunkGroup");
}
Expand All @@ -24,7 +37,7 @@ module.exports = class AsyncDependenciesBlock extends DependenciesBlock {
}

updateHash(hash) {
hash.update(this.chunkName || "");
hash.update(JSON.stringify(this.groupOptions));
hash.update(
(this.chunkGroup &&
this.chunkGroup.chunks
Expand Down
60 changes: 58 additions & 2 deletions lib/Chunk.js
Expand Up @@ -170,8 +170,8 @@ class Chunk {
if (aItem.done) return 0;
const aModuleIdentifier = aItem.value.identifier();
const bModuleIdentifier = bItem.value.identifier();
if (aModuleIdentifier > bModuleIdentifier) return -1;
if (aModuleIdentifier < bModuleIdentifier) return 1;
if (aModuleIdentifier < bModuleIdentifier) return -1;
if (aModuleIdentifier > bModuleIdentifier) return 1;
}
}

Expand Down Expand Up @@ -361,6 +361,62 @@ class Chunk {
};
}

getChildIdsByOrders() {
const lists = new Map();
for (const group of this.groupsIterable) {
if (group.chunks[group.chunks.length - 1] === this) {
for (const childGroup of group.childrenIterable) {
// TODO webpack 5 remove this check for options
if (typeof childGroup.options === "object") {
for (const key of Object.keys(childGroup.options)) {
if (key.endsWith("Order")) {
const name = key.substr(0, key.length - "Order".length);
let list = lists.get(name);
if (list === undefined) lists.set(name, (list = []));
list.push({
order: childGroup.options[key],
group: childGroup
});
}
}
}
}
}
}
const result = Object.create(null);
for (const [name, list] of lists) {
list.sort((a, b) => {
const cmp = b.order - a.order;
if (cmp !== 0) return cmp;
// TOOD webpack 5 remove this check of compareTo
if (a.group.compareTo) return a.group.compareTo(b.group);
return 0;
});
result[name] = Array.from(
list.reduce((set, item) => {
for (const chunk of item.group.chunks) set.add(chunk.id);
return set;
}, new Set())
);
}
return result;
}

getChildIdsByOrdersMap() {
const chunkMaps = Object.create(null);

for (const chunk of this.getAllAsyncChunks()) {
const data = chunk.getChildIdsByOrders();
for (const key of Object.keys(data)) {
let chunkMap = chunkMaps[key];
if (chunkMap === undefined)
chunkMaps[key] = chunkMap = Object.create(null);
chunkMap[chunk.id] = data[key];
}
}
return chunkMaps;
}

getChunkModuleMaps(filterFn) {
const chunkModuleIdMap = Object.create(null);
const chunkModuleHashMap = Object.create(null);
Expand Down
92 changes: 90 additions & 2 deletions lib/ChunkGroup.js
Expand Up @@ -26,16 +26,45 @@ const sortOrigin = (a, b) => {
};

class ChunkGroup {
constructor(name) {
constructor(options) {
if (typeof options === "string") {
options = { name: options };
} else if (!options) {
options = { name: undefined };
}
this.groupDebugId = debugId++;
this.name = name;
this.options = options;
this._children = new SortableSet(undefined, sortById);
this._parents = new SortableSet(undefined, sortById);
this._blocks = new SortableSet();
this.chunks = [];
this.origins = [];
}

addOptions(options) {
for (const key of Object.keys(options)) {
if (this.options[key] === undefined) {
this.options[key] = options[key];
} else if (this.options[key] !== options[key]) {
if (key.endsWith("Order")) {
this.options[key] = Math.max(this.options[key], options[key]);
} else {
throw new Error(
`ChunkGroup.addOptions: No option merge strategy for ${key}`
);
}
}
}
}

get name() {
return this.options.name;
}

set name(value) {
this.options.name = value;
}

/* istanbul ignore next */
get debugId() {
return Array.from(this.chunks, x => x.debugId).join("+");
Expand Down Expand Up @@ -222,6 +251,18 @@ class ChunkGroup {
return false;
}

getFiles() {
const files = new Set();

for (const chunk of this.chunks) {
for (const file of chunk.files) {
files.add(file);
}
}

return Array.from(files);
}

remove(reason) {
// cleanup parents
for (const parentChunkGroup of this._parents) {
Expand Down Expand Up @@ -269,6 +310,53 @@ class ChunkGroup {
this._children.sort();
}

compareTo(otherGroup) {
if (this.chunks.length > otherGroup.chunks.length) return -1;
if (this.chunks.length < otherGroup.chunks.length) return 1;
const a = this.chunks[Symbol.iterator]();
const b = otherGroup.chunks[Symbol.iterator]();
// eslint-disable-next-line
while (true) {
const aItem = a.next();
const bItem = b.next();
if (aItem.done) return 0;
const cmp = aItem.value.compareTo(bItem.value);
if (cmp !== 0) return cmp;
}
}

getChildrenByOrders() {
const lists = new Map();
for (const childGroup of this._children) {
// TODO webpack 5 remove this check for options
if (typeof childGroup.options === "object") {
for (const key of Object.keys(childGroup.options)) {
if (key.endsWith("Order")) {
const name = key.substr(0, key.length - "Order".length);
let list = lists.get(name);
if (list === undefined) lists.set(name, (list = []));
list.push({
order: childGroup.options[key],
group: childGroup
});
}
}
}
}
const result = Object.create(null);
for (const [name, list] of lists) {
list.sort((a, b) => {
const cmp = b.order - a.order;
if (cmp !== 0) return cmp;
// TOOD webpack 5 remove this check of compareTo
if (a.group.compareTo) return a.group.compareTo(b.group);
return 0;
});
result[name] = list.map(i => i.group);
}
return result;
}

checkConstraints() {
const chunk = this;
for (const child of chunk._children) {
Expand Down
18 changes: 15 additions & 3 deletions lib/Compilation.js
Expand Up @@ -1006,17 +1006,22 @@ class Compilation extends Tapable {
}
}

addChunkInGroup(name, module, loc, request) {
addChunkInGroup(groupOptions, module, loc, request) {
if (typeof groupOptions === "string") {
groupOptions = { name: groupOptions };
}
const name = groupOptions.name;
if (name) {
const chunkGroup = this.namedChunkGroups.get(name);
if (chunkGroup !== undefined) {
chunkGroup.addOptions(groupOptions);
if (module) {
chunkGroup.addOrigin(module, loc, request);
}
return chunkGroup;
}
}
const chunkGroup = new ChunkGroup(name);
const chunkGroup = new ChunkGroup(groupOptions);
if (module) chunkGroup.addOrigin(module, loc, request);
const chunk = this.addChunk(name);

Expand Down Expand Up @@ -1190,11 +1195,18 @@ class Compilation extends Tapable {
);
c = chunkGroup;
} else {
c = this.addChunkInGroup(b.chunkName, module, b.loc, b.request);
c = this.addChunkInGroup(
b.groupOptions || b.chunkName,
module,
b.loc,
b.request
);
blockChunkGroups.set(b, c);
allCreatedChunkGroups.add(c);
}
} else {
// TODO webpack 5 remove addOptions check
if (c.addOptions) c.addOptions(b.groupOptions);
c.addOrigin(module, b.loc, b.request);
}

Expand Down
20 changes: 17 additions & 3 deletions lib/ContextModule.js
Expand Up @@ -12,7 +12,7 @@ const Template = require("./Template");

class ContextModule extends Module {
// type ContextMode = "sync" | "eager" | "weak" | "async-weak" | "lazy" | "lazy-once"
// type ContextOptions = { resource: string, recursive: boolean, regExp: RegExp, addon?: string, mode?: ContextMode, chunkName?: string, include?: RegExp, exclude?: RegExp }
// type ContextOptions = { resource: string, recursive: boolean, regExp: RegExp, addon?: string, mode?: ContextMode, chunkName?: string, include?: RegExp, exclude?: RegExp, groupOptions?: Object }
// resolveDependencies: (fs: FS, options: ContextOptions, (err: Error?, dependencies: Dependency[]) => void) => void
// options: ContextOptions
constructor(resolveDependencies, options) {
Expand Down Expand Up @@ -81,6 +81,11 @@ class ContextModule extends Module {
if (this.options.regExp) identifier += ` ${this.options.regExp}`;
if (this.options.include) identifier += ` include: ${this.options.include}`;
if (this.options.exclude) identifier += ` exclude: ${this.options.exclude}`;
if (this.options.groupOptions) {
identifier += ` groupOptions: ${JSON.stringify(
this.options.groupOptions
)}`;
}
if (this.options.namespaceObject === "strict")
identifier += " strict namespace object";
else if (this.options.namespaceObject) identifier += " namespace object";
Expand All @@ -106,6 +111,11 @@ class ContextModule extends Module {
identifier += ` include: ${this.prettyRegExp(this.options.include + "")}`;
if (this.options.exclude)
identifier += ` exclude: ${this.prettyRegExp(this.options.exclude + "")}`;
if (this.options.groupOptions) {
const groupOptions = this.options.groupOptions;
for (const key of Object.keys(groupOptions))
identifier += ` ${key}: ${groupOptions[key]}`;
}
if (this.options.namespaceObject === "strict")
identifier += " strict namespace object";
else if (this.options.namespaceObject) identifier += " namespace object";
Expand Down Expand Up @@ -170,7 +180,9 @@ class ContextModule extends Module {
// and add that block to this context
if (dependencies.length > 0) {
const block = new AsyncDependenciesBlock(
this.options.chunkName,
Object.assign({}, this.options.groupOptions, {
name: this.options.chunkName
}),
this
);
for (const dep of dependencies) {
Expand Down Expand Up @@ -202,7 +214,9 @@ class ContextModule extends Module {
);
}
const block = new AsyncDependenciesBlock(
chunkName,
Object.assign({}, this.options.groupOptions, {
name: chunkName
}),
dep.module,
dep.loc,
dep.userRequest
Expand Down
12 changes: 0 additions & 12 deletions lib/Entrypoint.js
Expand Up @@ -16,18 +16,6 @@ class Entrypoint extends ChunkGroup {
return true;
}

getFiles() {
const files = new Set();

for (const chunk of this.chunks) {
for (const file of chunk.files) {
files.add(file);
}
}

return Array.from(files);
}

setRuntimeChunk(chunk) {
this.runtimeChunk = chunk;
}
Expand Down

0 comments on commit ec4ec8e

Please sign in to comment.