Skip to content

Commit

Permalink
Merge branch 'master' into feature/type-compiler-compilation-save
Browse files Browse the repository at this point in the history
  • Loading branch information
sokra committed Jun 29, 2018
2 parents db63d9f + 190cf7b commit 6e68f96
Show file tree
Hide file tree
Showing 65 changed files with 700 additions and 111 deletions.
6 changes: 6 additions & 0 deletions _SETUP.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,12 @@ or in watch mode
yarn test:unit --watch
```

### To update Jest snapshots use

```bash
yarn test:update-snapshots
```

### To run code formatter (prettier) run

```bash
Expand Down
2 changes: 1 addition & 1 deletion examples/dll-app-and-vendor/0-vendor/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ It's built separately from the app part. The vendors dll is only built when the

The DllPlugin in combination with the `output.library` option exposes the internal require function as global variable in the target environment.

A manifest is creates which includes mappings from module names to internal ids.
A manifest is created which includes mappings from module names to internal ids.

### webpack.config.js

Expand Down
2 changes: 1 addition & 1 deletion examples/dll-app-and-vendor/0-vendor/template.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ It's built separately from the app part. The vendors dll is only built when the

The DllPlugin in combination with the `output.library` option exposes the internal require function as global variable in the target environment.

A manifest is creates which includes mappings from module names to internal ids.
A manifest is created which includes mappings from module names to internal ids.

### webpack.config.js

Expand Down
60 changes: 57 additions & 3 deletions lib/Compilation.js
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,10 @@ const ModuleDependency = require("./dependencies/ModuleDependency");
/** @typedef {import("./dependencies/SingleEntryDependency")} SingleEntryDependency */
/** @typedef {import("./dependencies/MultiEntryDependency")} MultiEntryDependency */
/** @typedef {import("./dependencies/DllEntryDependency")} DllEntryDependency */
/** @typedef {import("./dependencies/DependencyReference")} DependencyReference */
/** @typedef {import("./DependenciesBlock")} DependenciesBlock */
/** @typedef {import("./AsyncDependenciesBlock")} AsyncDependenciesBlock */
/** @typedef {import("./Dependency")} Dependency */
/** @typedef {import("./Dependency").DependencyLocation} DependencyLocation */
/** @typedef {import("./Dependency").DependencyTemplate} DependencyTemplate */
/** @typedef {import("./util/createHash").Hash} Hash */
Expand Down Expand Up @@ -213,7 +215,15 @@ class Compilation extends Tapable {
failedModule: new SyncHook(["module", "error"]),
/** @type {SyncHook<Module>} */
succeedModule: new SyncHook(["module"]),
/** @type {SyncHook<Module[]>} */

/** @type {SyncWaterfallHook<DependencyReference, Dependency, Module>} */
dependencyReference: new SyncWaterfallHook([
"dependencyReference",
"dependency",
"module"
]),

/** @type {SyncHook<Module[]>} */
finishModules: new SyncHook(["modules"]),
/** @type {SyncHook<Module>} */
finishRebuildingModule: new SyncHook(["module"]),
Expand Down Expand Up @@ -1429,6 +1439,19 @@ class Compilation extends Tapable {
}
}

/**
* @param {Module} module the module containing the dependency
* @param {Dependency} dependency the dependency
* @returns {DependencyReference} a reference for the dependency
*/
getDependencyReference(module, dependency) {
// TODO remove dep.getReference existance check in webpack 5
if (typeof dependency.getReference !== "function") return null;
const ref = dependency.getReference();
if (!ref) return null;
return this.hooks.dependencyReference.call(ref, dependency, module);
}

/**
* This method creates the Chunk graph from the Module graph
* @private
Expand Down Expand Up @@ -1458,7 +1481,7 @@ class Compilation extends Tapable {
*/
const iteratorDependency = d => {
// We skip Dependencies without Reference
const ref = d.getReference();
const ref = this.getDependencyReference(currentModule, d);
if (!ref) {
return;
}
Expand All @@ -1484,6 +1507,8 @@ class Compilation extends Tapable {
blockQueue.push(b);
};

/** @type {Module} */
let currentModule;
/** @type {DependenciesBlock} */
let block;
/** @type {DependenciesBlock[]} */
Expand All @@ -1495,6 +1520,7 @@ class Compilation extends Tapable {

for (const module of this.modules) {
blockQueue = [module];
currentModule = module;
while (blockQueue.length > 0) {
block = blockQueue.pop();
blockInfoModules = new Set();
Expand Down Expand Up @@ -2246,6 +2272,8 @@ class Compilation extends Tapable {
createChunkAssets() {
const outputOptions = this.outputOptions;
const cachedSourceMap = new Map();
/** @type {Map<string, {hash: string, source: Source, chunk: Chunk}>} */
const alreadyWrittenFiles = new Map();
for (let i = 0; i < this.chunks.length; i++) {
const chunk = this.chunks[i];
chunk.files = [];
Expand All @@ -2268,6 +2296,28 @@ class Compilation extends Tapable {
const cacheName = fileManifest.identifier;
const usedHash = fileManifest.hash;
filenameTemplate = fileManifest.filenameTemplate;
file = this.getPath(filenameTemplate, fileManifest.pathOptions);

// check if the same filename was already written by another chunk
const alreadyWritten = alreadyWrittenFiles.get(file);
if (alreadyWritten !== undefined) {
if (alreadyWritten.hash === usedHash) {
if (this.cache) {
this.cache[cacheName] = {
hash: usedHash,
source: alreadyWritten.source
};
}
chunk.files.push(file);
this.hooks.chunkAsset.call(chunk, file);
continue;
} else {
throw new Error(
`Conflict: Multiple chunks emit assets to the same filename ${file}` +
` (chunks ${alreadyWritten.chunk.id} and ${chunk.id})`
);
}
}
if (
this.cache &&
this.cache[cacheName] &&
Expand All @@ -2294,7 +2344,6 @@ class Compilation extends Tapable {
};
}
}
file = this.getPath(filenameTemplate, fileManifest.pathOptions);
if (this.assets[file] && this.assets[file] !== source) {
throw new Error(
`Conflict: Multiple assets emit to the same filename ${file}`
Expand All @@ -2303,6 +2352,11 @@ class Compilation extends Tapable {
this.assets[file] = source;
chunk.files.push(file);
this.hooks.chunkAsset.call(chunk, file);
alreadyWrittenFiles.set(file, {
hash: usedHash,
source,
chunk
});
}
} catch (err) {
this.errors.push(
Expand Down
110 changes: 69 additions & 41 deletions lib/DefinePlugin.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,34 +9,52 @@ const BasicEvaluatedExpression = require("./BasicEvaluatedExpression");
const ParserHelpers = require("./ParserHelpers");
const NullFactory = require("./NullFactory");

const stringifyObj = obj => {
class RuntimeValue {
constructor(fn, fileDependencies) {
this.fn = fn;
this.fileDependencies = fileDependencies || [];
}

exec(parser) {
for (const fileDependency of this.fileDependencies) {
parser.state.module.buildInfo.fileDependencies.add(fileDependency);
}

return this.fn();
}
}

const stringifyObj = (obj, parser) => {
return (
"Object({" +
Object.keys(obj)
.map(key => {
const code = obj[key];
return JSON.stringify(key) + ":" + toCode(code);
return JSON.stringify(key) + ":" + toCode(code, parser);
})
.join(",") +
"})"
);
};

const toCode = code => {
const toCode = (code, parser) => {
if (code === null) {
return "null";
}
if (code === undefined) {
return "undefined";
}
if (code instanceof RuntimeValue) {
return toCode(code.exec(parser), parser);
}
if (code instanceof RegExp && code.toString) {
return code.toString();
}
if (typeof code === "function" && code.toString) {
return "(" + code.toString() + ")";
}
if (typeof code === "object") {
return stringifyObj(code);
return stringifyObj(code, parser);
}
return code + "";
};
Expand All @@ -46,6 +64,10 @@ class DefinePlugin {
this.definitions = definitions;
}

static runtimeValue(fn, fileDependencies) {
return new RuntimeValue(fn, fileDependencies);
}

apply(compiler) {
const definitions = this.definitions;
compiler.hooks.compilation.tap(
Expand All @@ -64,6 +86,7 @@ class DefinePlugin {
if (
code &&
typeof code === "object" &&
!(code instanceof RuntimeValue) &&
!(code instanceof RegExp)
) {
walkDefinitions(code, prefix + key + ".");
Expand All @@ -90,7 +113,6 @@ class DefinePlugin {
if (isTypeof) key = key.replace(/^typeof\s+/, "");
let recurse = false;
let recurseTypeof = false;
code = toCode(code);
if (!isTypeof) {
parser.hooks.canRename
.for(key)
Expand All @@ -108,24 +130,25 @@ class DefinePlugin {
*/
if (recurse) return;
recurse = true;
const res = parser.evaluate(code);
const res = parser.evaluate(toCode(code, parser));
recurse = false;
res.setRange(expr.range);
return res;
});
parser.hooks.expression
.for(key)
.tap(
"DefinePlugin",
/__webpack_require__/.test(code)
? ParserHelpers.toConstantDependencyWithWebpackRequire(
parser,
code
)
: ParserHelpers.toConstantDependency(parser, code)
);
parser.hooks.expression.for(key).tap("DefinePlugin", expr => {
const strCode = toCode(code, parser);
if (/__webpack_require__/.test(strCode)) {
return ParserHelpers.toConstantDependencyWithWebpackRequire(
parser,
strCode
)(expr);
} else {
return ParserHelpers.toConstantDependency(parser, strCode)(
expr
);
}
});
}
const typeofCode = isTypeof ? code : "typeof (" + code + ")";
parser.hooks.evaluateTypeof.for(key).tap("DefinePlugin", expr => {
/**
* this is needed in case there is a recursion in the DefinePlugin
Expand All @@ -137,12 +160,18 @@ class DefinePlugin {
*/
if (recurseTypeof) return;
recurseTypeof = true;
const typeofCode = isTypeof
? toCode(code, parser)
: "typeof (" + toCode(code, parser) + ")";
const res = parser.evaluate(typeofCode);
recurseTypeof = false;
res.setRange(expr.range);
return res;
});
parser.hooks.typeof.for(key).tap("DefinePlugin", expr => {
const typeofCode = isTypeof
? toCode(code, parser)
: "typeof (" + toCode(code, parser) + ")";
const res = parser.evaluate(typeofCode);
if (!res.isString()) return;
return ParserHelpers.toConstantDependency(
Expand All @@ -153,7 +182,6 @@ class DefinePlugin {
};

const applyObjectDefine = (key, obj) => {
const code = stringifyObj(obj);
parser.hooks.canRename
.for(key)
.tap("DefinePlugin", ParserHelpers.approve);
Expand All @@ -162,29 +190,29 @@ class DefinePlugin {
.tap("DefinePlugin", expr =>
new BasicEvaluatedExpression().setTruthy().setRange(expr.range)
);
parser.hooks.evaluateTypeof
.for(key)
.tap("DefinePlugin", ParserHelpers.evaluateToString("object"));
parser.hooks.expression
.for(key)
.tap(
"DefinePlugin",
/__webpack_require__/.test(code)
? ParserHelpers.toConstantDependencyWithWebpackRequire(
parser,
code
)
: ParserHelpers.toConstantDependency(parser, code)
);
parser.hooks.typeof
.for(key)
.tap(
"DefinePlugin",
ParserHelpers.toConstantDependency(
parser.hooks.evaluateTypeof.for(key).tap("DefinePlugin", expr => {
return ParserHelpers.evaluateToString("object")(expr);
});
parser.hooks.expression.for(key).tap("DefinePlugin", expr => {
const strCode = stringifyObj(obj, parser);

if (/__webpack_require__/.test(strCode)) {
return ParserHelpers.toConstantDependencyWithWebpackRequire(
parser,
JSON.stringify("object")
)
);
strCode
)(expr);
} else {
return ParserHelpers.toConstantDependency(parser, strCode)(
expr
);
}
});
parser.hooks.typeof.for(key).tap("DefinePlugin", expr => {
return ParserHelpers.toConstantDependency(
parser,
JSON.stringify("object")
)(expr);
});
};

walkDefinitions(definitions, "");
Expand Down
6 changes: 2 additions & 4 deletions lib/DependenciesBlockVariable.js
Original file line number Diff line number Diff line change
Expand Up @@ -63,11 +63,9 @@ class DependenciesBlockVariable {

hasDependencies(filter) {
if (filter) {
if (this.dependencies.some(filter)) return true;
} else {
if (this.dependencies.length > 0) return true;
return this.dependencies.some(filter);
}
return false;
return this.dependencies.length > 0;
}
}

Expand Down
Loading

0 comments on commit 6e68f96

Please sign in to comment.