Skip to content

Commit

Permalink
Merge pull request #6793 from ronkorving/define-functions
Browse files Browse the repository at this point in the history
DefinePlugin: add ability to use function return values
  • Loading branch information
sokra committed Jun 28, 2018
2 parents e08399a + 8420c73 commit b181bc4
Show file tree
Hide file tree
Showing 11 changed files with 147 additions and 42 deletions.
110 changes: 69 additions & 41 deletions lib/DefinePlugin.js
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
10 changes: 9 additions & 1 deletion test/__snapshots__/StatsTestCases.test.js.snap
Expand Up @@ -693,7 +693,7 @@ exports[`StatsTestCases should print correct stats for concat-and-sideeffects 1`
`;

exports[`StatsTestCases should print correct stats for define-plugin 1`] = `
"Hash: cfe08d4450db77f81610f4228fcb997ec81e2aa6
"Hash: cfe08d4450db77f81610f4228fcb997ec81e2aa6bb43e7d151657ea2b793
Child
Hash: cfe08d4450db77f81610
Time: Xms
Expand All @@ -709,6 +709,14 @@ Child
Asset Size Chunks Chunk Names
main.js 3.6 KiB 0 [emitted] main
Entrypoint main = main.js
[0] ./index.js 24 bytes {0} [built]
Child
Hash: bb43e7d151657ea2b793
Time: Xms
Built at: Thu Jan 01 1970 00:00:00 GMT
Asset Size Chunks Chunk Names
main.js 3.6 KiB 0 [emitted] main
Entrypoint main = main.js
[0] ./index.js 24 bytes {0} [built]"
`;

Expand Down
1 change: 1 addition & 0 deletions test/statsCases/define-plugin/123.txt
@@ -0,0 +1 @@
123
1 change: 1 addition & 0 deletions test/statsCases/define-plugin/321.txt
@@ -0,0 +1 @@
321
24 changes: 24 additions & 0 deletions test/statsCases/define-plugin/webpack.config.js
@@ -1,4 +1,11 @@
var webpack = require("../../../");
var fs = require("fs");
var join = require("path").join;

function read(path) {
return JSON.stringify(fs.readFileSync(join(__dirname, path), "utf8"));
}

module.exports = [
{
mode: "production",
Expand All @@ -18,5 +25,22 @@ module.exports = [
VALUE: "321"
})
]
},

{
mode: "production",
entry: "./index",
plugins: [
new webpack.DefinePlugin({
VALUE: webpack.DefinePlugin.runtimeValue(() => read("123.txt"), [
"./123.txt"
])
}),
new webpack.DefinePlugin({
VALUE: webpack.DefinePlugin.runtimeValue(() => read("321.txt"), [
"./321.txt"
])
})
]
}
];
15 changes: 15 additions & 0 deletions test/watchCases/plugins/define-plugin/0/index.js
@@ -0,0 +1,15 @@
it("should be able to use dynamic defines in watch mode", function() {
const module = require("./module");
module.should.be.eql({
default: WATCH_STEP,
type: "string"
});
});

it("should not update a define when dependencies list is missing", function() {
const module2 = require("./module2");
module2.should.be.eql({
default: "0",
type: "string"
});
});
2 changes: 2 additions & 0 deletions test/watchCases/plugins/define-plugin/0/module.js
@@ -0,0 +1,2 @@
export default TEST_VALUE;
export const type = typeof TEST_VALUE;
2 changes: 2 additions & 0 deletions test/watchCases/plugins/define-plugin/0/module2.js
@@ -0,0 +1,2 @@
export default TEST_VALUE2;
export const type = typeof TEST_VALUE2;
1 change: 1 addition & 0 deletions test/watchCases/plugins/define-plugin/0/value.txt
@@ -0,0 +1 @@
0
1 change: 1 addition & 0 deletions test/watchCases/plugins/define-plugin/1/value.txt
@@ -0,0 +1 @@
1
22 changes: 22 additions & 0 deletions test/watchCases/plugins/define-plugin/webpack.config.js
@@ -0,0 +1,22 @@
const path = require("path");
const fs = require("fs");
const webpack = require("../../../../");
const valueFile = path.resolve(
__dirname,
"../../../js/watch-src/plugins/define-plugin/value.txt"
);
module.exports = {
plugins: [
new webpack.DefinePlugin({
TEST_VALUE: webpack.DefinePlugin.runtimeValue(
() => {
return JSON.stringify(fs.readFileSync(valueFile, "utf-8").trim());
},
[valueFile]
),
TEST_VALUE2: webpack.DefinePlugin.runtimeValue(() => {
return JSON.stringify(fs.readFileSync(valueFile, "utf-8").trim());
}, [])
})
]
};

0 comments on commit b181bc4

Please sign in to comment.