Skip to content
This repository was archived by the owner on May 29, 2019. It is now read-only.

Commit 652d27e

Browse files
committed
Initial prototype of chunk tree based optimizing
1 parent a10bb9b commit 652d27e

File tree

12 files changed

+182
-52
lines changed

12 files changed

+182
-52
lines changed

ExtractedModule.js

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
/*
2+
MIT License http://www.opensource.org/licenses/mit-license.php
3+
Author Tobias Koppers @sokra
4+
*/
5+
var SourceMapSource = require("webpack/lib/SourceMapSource");
6+
var RawSource = require("webpack/lib/RawSource");
7+
8+
function ExtractedModule(identifier, source, sourceMap, addtitionalInformation) {
9+
this._identifier = identifier;
10+
this._source = source;
11+
this._sourceMap = sourceMap;
12+
this.addtitionalInformation = addtitionalInformation;
13+
this.chunks = [];
14+
}
15+
module.exports = ExtractedModule;
16+
17+
ExtractedModule.prototype.addChunk = function(chunk) {
18+
var idx = this.chunks.indexOf(chunk);
19+
if(idx < 0)
20+
this.chunks.push(chunk);
21+
};
22+
23+
ExtractedModule.prototype._removeAndDo = require("webpack/lib/removeAndDo");
24+
25+
ExtractedModule.prototype.removeChunk = function(chunk) {
26+
return this._removeAndDo("chunks", chunk, "removeModule");
27+
};
28+
29+
ExtractedModule.prototype.rewriteChunkInReasons = function(oldChunk, newChunks) { };
30+
31+
ExtractedModule.prototype.identifier = function() {
32+
return this._identifier;
33+
};
34+
35+
ExtractedModule.prototype.source = function() {
36+
if(this._sourceMap)
37+
return new SourceMapSource(this._source, null, this._sourceMap);
38+
else
39+
return new RawSource(this._source);
40+
};

example/base.css

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
body {
22
border: 1px solid black;
3+
correct: c;
34
}

example/common.css

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
body {
2+
height: 100%;
3+
correct: c;
4+
}

example/entry.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
1+
require("./common.css");
12
require("./style.css");
23
require("./dep");

example/entry2.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
require("./common.css");
2+
require("./style4.css");

example/style.css

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
@import "base.css";
22
body {
33
background: url(image.png);
4+
correct: a;
45
}

example/style2.css

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
@import "base.css";
22
body {
33
color: red;
4+
correct: a;
45
}

example/style3.css

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
.xyz {
22
color: url(image.png);
3+
correct: a;
34
}

example/style4.css

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
@import "base.css";
2+
.secondary {
3+
color: url(image.png);
4+
correct: b;
5+
}

example/webpack.config.js

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,30 @@
1+
var webpack = require("webpack");
12
var ExtractTextPlugin = require("../");
23
module.exports = {
3-
entry: "./entry.js",
4+
entry: {
5+
a: "./entry.js",
6+
b: "./entry2.js"
7+
},
48
output: {
5-
filename: "bundle.js",
9+
filename: "[name].js",
10+
chunkFilename: "[name].js",
611
path: __dirname + "/assets",
712
publicPath: "/assets/"
813
},
914
module: {
1015
loaders: [
11-
{ test: /\.css$/, loader: ExtractTextPlugin.extract("style-loader", "css-loader?sourceMap")},
16+
{ test: /\.css$/, loader: ExtractTextPlugin.extract(
17+
"style-loader",
18+
"css-loader?sourceMap"
19+
)},
1220
{ test: /\.png$/, loader: "file-loader" }
1321
]
1422
},
1523
plugins: [
16-
new ExtractTextPlugin("styles.css?[hash]-[chunkhash]-[name]")
24+
new ExtractTextPlugin("[name].css?[hash]-[chunkhash]-[name]", {
25+
disable: false,
26+
allChunks: true
27+
}),
28+
new webpack.optimize.CommonsChunkPlugin("c", "c.js")
1729
]
1830
};

index.js

Lines changed: 109 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,14 @@
22
MIT License http://www.opensource.org/licenses/mit-license.php
33
Author Tobias Koppers @sokra
44
*/
5-
var SourceMapSource = require("webpack/lib/SourceMapSource");
5+
var ConcatSource = require("webpack/lib/ConcatSource");
66
var Template = require("webpack/lib/Template");
77
var async = require("async");
88
var SourceNode = require("source-map").SourceNode;
99
var SourceMapConsumer = require("source-map").SourceMapConsumer;
1010
var ModuleFilenameHelpers = require("webpack/lib/ModuleFilenameHelpers");
11+
var ExtractedModule = require("./ExtractedModule");
12+
var Chunk = require("webpack/lib/Chunk");
1113

1214
var nextId = 0;
1315

@@ -21,6 +23,7 @@ function ExtractTextPlugin(id, filename, options) {
2123
this.filename = filename;
2224
this.options = options;
2325
this.id = id;
26+
this.modulesByIdentifier = {};
2427
}
2528
module.exports = ExtractTextPlugin;
2629

@@ -44,15 +47,15 @@ ExtractTextPlugin.extract = function(before, loader) {
4447
}
4548
};
4649

47-
ExtractTextPlugin.prototype.applyAdditionalInformation = function(node, info) {
48-
if(info.length === 1 && info[0]) {
49-
node = new SourceNode(null, null, null, [
50+
ExtractTextPlugin.prototype.applyAdditionalInformation = function(source, info) {
51+
if(info) {
52+
return new ConcatSource(
5053
"@media " + info[0] + " {",
51-
node,
54+
source,
5255
"}"
53-
]);
56+
);
5457
}
55-
return node;
58+
return source;
5659
};
5760

5861
ExtractTextPlugin.prototype.loader = function(options) {
@@ -64,7 +67,7 @@ ExtractTextPlugin.prototype.loader = function(options) {
6467
ExtractTextPlugin.prototype.extract = function(before, loader) {
6568
if(loader) {
6669
return [
67-
this.loader({move: before.split("!").length, extract: true, remove: true}),
70+
this.loader({omit: before.split("!").length, extract: true, remove: true}),
6871
before,
6972
loader
7073
].join("!");
@@ -82,6 +85,8 @@ ExtractTextPlugin.prototype.apply = function(compiler) {
8285
compiler.plugin("this-compilation", function(compilation) {
8386
compilation.plugin("normal-module-loader", function(loaderContext, module) {
8487
loaderContext[__dirname] = function(content, opt) {
88+
if(options.disable)
89+
return false;
8590
if(!Array.isArray(content) && content !== null)
8691
throw new Error("Exported value is not a string.");
8792
module.meta[__dirname] = {
@@ -94,11 +99,38 @@ ExtractTextPlugin.prototype.apply = function(compiler) {
9499
var contents;
95100
var filename = this.filename;
96101
var id = this.id;
102+
var extractedChunks, entryChunks;
103+
compilation.plugin("optimize", function() {
104+
entryChunks = compilation.chunks.filter(function(c) {
105+
return c.entry;
106+
});
107+
}.bind(this));
97108
compilation.plugin("optimize-tree", function(chunks, modules, callback) {
98109
contents = [];
110+
extractedChunks = chunks.map(function(chunk) {
111+
return new Chunk();
112+
});
113+
chunks.forEach(function(chunk, i) {
114+
var extractedChunk = extractedChunks[i];
115+
extractedChunk.index = i;
116+
extractedChunk.originalChunk = chunk;
117+
extractedChunk.name = chunk.name;
118+
chunk.chunks.forEach(function(c) {
119+
extractedChunk.addChunk(extractedChunks[chunks.indexOf(c)]);
120+
});
121+
chunk.parents.forEach(function(c) {
122+
extractedChunk.addParent(extractedChunks[chunks.indexOf(c)]);
123+
});
124+
});
125+
entryChunks.forEach(function(chunk) {
126+
var idx = chunks.indexOf(chunk);
127+
if(idx < 0) return;
128+
var extractedChunk = extractedChunks[idx];
129+
extractedChunk.entry = extractedChunk.initial = true;
130+
});
99131
async.forEach(chunks, function(chunk, callback) {
132+
var extractedChunk = extractedChunks[chunks.indexOf(chunk)];
100133
var shouldExtract = !!(options.allChunks || chunk.initial);
101-
var content = [];
102134
async.forEach(chunk.modules.slice(), function(module, callback) {
103135
var meta = module.meta && module.meta[__dirname];
104136
if(meta) {
@@ -116,59 +148,89 @@ ExtractTextPlugin.prototype.apply = function(compiler) {
116148
compilation.errors.push(err);
117149
return callback();
118150
}
119-
if(meta.content) content.push(meta.content);
151+
if(meta.content)
152+
this.addResultToChunk(module.identifier(), meta.content, extractedChunk);
120153
callback();
121-
});
154+
}.bind(this));
122155
} else {
123-
if(meta.content) content.push(meta.content);
156+
if(meta.content)
157+
this.addResultToChunk(module.identifier(), meta.content, extractedChunk);
124158
callback();
125159
}
126160
} else callback();
127-
}, function(err) {
161+
}.bind(this), function(err) {
128162
if(err) return callback(err);
129-
if(content.length > 0) {
130-
contents.push({
131-
chunk: chunk,
132-
content: content
133-
});
134-
}
135163
callback();
136164
}.bind(this));
137165
}.bind(this), function(err) {
138166
if(err) return callback(err);
167+
extractedChunks.forEach(function(extractedChunk) {
168+
if(extractedChunk.initial)
169+
this.mergeNonInitialChunks(extractedChunk);
170+
}, this);
171+
compilation.applyPlugins("optimize-extracted-chunks", extractedChunks);
139172
callback();
140173
}.bind(this));
141-
});
174+
}.bind(this));
142175
compilation.plugin("additional-assets", function(callback) {
143176
var assetContents = {};
144-
contents.forEach(function(item) {
145-
var chunk = item.chunk;
146-
var file = compilation.getPath(filename, {
147-
chunk: chunk
148-
});
149-
assetContents[file] = (assetContents[file] || []).concat(item.content);
150-
chunk.files.push(file);
151-
});
152-
Object.keys(assetContents).forEach(function(file) {
153-
var contained = {};
154-
var content = assetContents[file].reduce(function(arr, items) {
155-
return arr.concat(items);
156-
}, []).filter(function(item) {
157-
if(contained[item[0]]) return false;
158-
contained[item[0]] = true;
159-
return true;
160-
}).map(function(item) {
161-
var css = item[1];
162-
var contents = item.slice(1).filter(function(i) { return typeof i === "string"; });
163-
var sourceMap = typeof item[item.length-1] === "object" ? item[item.length-1] : undefined;
164-
var text = contents.shift();
165-
var node = sourceMap ? SourceNode.fromStringWithSourceMap(text, new SourceMapConsumer(sourceMap)) : new SourceNode(null, null, null, text);
166-
return this.applyAdditionalInformation(node, contents);
167-
}.bind(this));
168-
var strAndMap = new SourceNode(null, null, null, content).toStringWithSourceMap();
169-
compilation.assets[file] = new SourceMapSource(strAndMap.code, file, strAndMap.map.toJSON());
170-
}.bind(this));
177+
extractedChunks.forEach(function(extractedChunk) {
178+
if(extractedChunk.modules.length) {
179+
var chunk = extractedChunk.originalChunk;
180+
var file = compilation.getPath(filename, {
181+
chunk: chunk
182+
});
183+
compilation.assets[file] = this.renderExtractedChunk(extractedChunk);
184+
chunk.files.push(file);
185+
}
186+
}, this);
171187
callback();
172188
}.bind(this));
173189
}.bind(this));
174190
};
191+
192+
ExtractTextPlugin.prototype.mergeNonInitialChunks = function(chunk, intoChunk, checkedChunks) {
193+
if(!intoChunk) {
194+
checkedChunks = [];
195+
chunk.chunks.forEach(function(c) {
196+
if(c.initial) return;
197+
this.mergeNonInitialChunks(c, chunk, checkedChunks);
198+
}, this);
199+
} else if(checkedChunks.indexOf(chunk) < 0) {
200+
checkedChunks.push(chunk);
201+
chunk.modules.slice().forEach(function(module) {
202+
chunk.removeModule(module);
203+
intoChunk.addModule(module);
204+
module.addChunk(intoChunk);
205+
});
206+
chunk.chunks.forEach(function(c) {
207+
if(c.initial) return;
208+
this.mergeNonInitialChunks(c, intoChunk, checkedChunks);
209+
}, this);
210+
}
211+
};
212+
213+
ExtractTextPlugin.prototype.addModule = function(identifier, source, sourceMap, additionalInformation) {
214+
if(!this.modulesByIdentifier[identifier])
215+
return this.modulesByIdentifier[identifier] = new ExtractedModule(identifier, source, sourceMap, additionalInformation);
216+
return this.modulesByIdentifier[identifier];
217+
};
218+
219+
ExtractTextPlugin.prototype.addResultToChunk = function(identifier, result, extractedChunk) {
220+
if(!Array.isArray(result)) {
221+
result = [[identifier, result]];
222+
}
223+
result.forEach(function(item) {
224+
var module = this.addModule.apply(this, item);
225+
extractedChunk.addModule(module);
226+
module.addChunk(extractedChunk);
227+
}, this);
228+
};
229+
230+
ExtractTextPlugin.prototype.renderExtractedChunk = function(chunk) {
231+
var source = new ConcatSource();
232+
chunk.modules.forEach(function(module) {
233+
source.add(this.applyAdditionalInformation(module.source(), module.additionalInformation));
234+
}, this);
235+
return source;
236+
};

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
"author": "Tobias Koppers @sokra",
55
"description": "Extract text from bundle into a file.",
66
"peerDependencies": {
7-
"webpack": "^1.4"
7+
"webpack": "^1.4.2"
88
},
99
"dependencies": {
1010
"async": "~0.2.10",

0 commit comments

Comments
 (0)