Skip to content

Commit

Permalink
fix: webpack 5 wrapper handling (#186)
Browse files Browse the repository at this point in the history
  • Loading branch information
guybedford authored Apr 14, 2021
1 parent 9c848ca commit bd87d04
Show file tree
Hide file tree
Showing 6 changed files with 172 additions and 37 deletions.
9 changes: 6 additions & 3 deletions src/analyze.ts
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,9 @@ const globalBindings: any = {
__importStar: normalizeWildcardRequire,
MONGOOSE_DRIVER_PATH: undefined,
URL: URL,
Object: {
assign: Object.assign
}
};
globalBindings.global = globalBindings.GLOBAL = globalBindings.globalThis = globalBindings;

Expand Down Expand Up @@ -345,12 +348,12 @@ export default async function analyze(id: string, code: string, job: Job): Promi

function computePureStaticValue (expr: Node, computeBranches = true) {
const vars = Object.create(null);
Object.keys(knownBindings).forEach(name => {
vars[name] = getKnownBinding(name);
});
Object.keys(globalBindings).forEach(name => {
vars[name] = { value: globalBindings[name] };
});
Object.keys(knownBindings).forEach(name => {
vars[name] = getKnownBinding(name);
});
vars['import.meta'] = { url: importMetaUrl };
// evaluate returns undefined for non-statically-analyzable
const result = evaluate(expr, vars, computeBranches);
Expand Down
18 changes: 16 additions & 2 deletions src/utils/static-eval.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,20 @@ const visitors: Record<string, (this: State, node: Node, walk: Walk) => Evaluate
}
return { value: arr };
},
'ArrowFunctionExpression': function (this: State, node: Node, walk: Walk) {
// () => val support only
if (node.params.length === 0 && !node.generator && !node.async && node.expression) {
const innerValue = walk(node.body);
if (!innerValue || !('value' in innerValue))
return;
return {
value: {
[FUNCTION]: () => innerValue.value
}
};
}
return undefined;
},
'BinaryExpression': function BinaryExpression(this: State, node: Node, walk: Walk) {
const op = node.operator;

Expand Down Expand Up @@ -160,7 +174,8 @@ const visitors: Record<string, (this: State, node: Node, walk: Walk) => Evaluate
},
'CallExpression': function CallExpression(this: State, node: Node, walk: Walk) {
const callee = walk(node.callee);
if (!callee || 'test' in callee) return;
if (!callee || 'test' in callee)
return;
let fn: any = callee.value;
if (typeof fn === 'object' && fn !== null) fn = fn[FUNCTION];
if (typeof fn !== 'function') return;
Expand Down Expand Up @@ -263,7 +278,6 @@ const visitors: Record<string, (this: State, node: Node, walk: Walk) => Evaluate
},
'MemberExpression': function MemberExpression(this: State, node: Node, walk: Walk) {
const obj = walk(node.object);
// do not allow access to methods on Function
if (!obj || 'test' in obj || typeof obj.value === 'function') {
return undefined;
}
Expand Down
116 changes: 84 additions & 32 deletions src/utils/wrappers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ export function handleWrappers(ast: Node) {
ast.body[0].type === 'ExpressionStatement' &&
ast.body[0].expression.type === 'CallExpression' &&
ast.body[0].expression.callee.type === 'FunctionExpression' &&
ast.body[0].expression.arguments.length === 1)
(ast.body[0].expression.arguments.length === 1 || ast.body[0].expression.arguments.length === 0))
wrapper = ast.body[0].expression;
else if (ast.body.length === 1 &&
ast.body[0].type === 'ExpressionStatement' &&
Expand All @@ -34,15 +34,15 @@ export function handleWrappers(ast: Node) {
ast.body[0].expression.right.callee.type === 'FunctionExpression' &&
ast.body[0].expression.right.arguments.length === 1)
wrapper = ast.body[0].expression.right;

if (wrapper) {
// When.js-style AMD wrapper:
// (function (define) { 'use strict' define(function (require) { ... }) })
// (typeof define === 'function' && define.amd ? define : function (factory) { module.exports = factory(require); })
// ->
// (function (define) { 'use strict' define(function () { ... }) })
// (typeof define === 'function' && define.amd ? define : function (factory) { module.exports = factory(require); })
if (wrapper.arguments[0].type === 'ConditionalExpression' &&
if (wrapper.arguments[0] && wrapper.arguments[0].type === 'ConditionalExpression' &&
wrapper.arguments[0].test.type === 'LogicalExpression' &&
wrapper.arguments[0].test.operator === '&&' &&
wrapper.arguments[0].test.left.type === 'BinaryExpression' &&
Expand Down Expand Up @@ -112,7 +112,7 @@ export function handleWrappers(ast: Node) {
// "external": { exports: require('external') }
// },[24])(24)
// });
else if (wrapper.arguments[0].type === 'FunctionExpression' &&
else if (wrapper.arguments[0] && wrapper.arguments[0].type === 'FunctionExpression' &&
wrapper.arguments[0].params.length === 0 &&
(wrapper.arguments[0].body.body.length === 1 ||
wrapper.arguments[0].body.body.length === 2 &&
Expand Down Expand Up @@ -236,7 +236,7 @@ export function handleWrappers(ast: Node) {
// })(function () {
// // ...
// }
else if (wrapper.arguments[0].type === 'FunctionExpression' &&
else if (wrapper.arguments[0] && wrapper.arguments[0].type === 'FunctionExpression' &&
wrapper.arguments[0].params.length === 2 &&
wrapper.arguments[0].params[0].type === 'Identifier' &&
wrapper.arguments[0].params[1].type === 'Identifier' &&
Expand Down Expand Up @@ -316,12 +316,23 @@ export function handleWrappers(ast: Node) {
// },
// function(e, t, r) {
// const n = require("fs");
// const ns = { a: require("fs") };
// const ns = Object.assign(a => n, { a: n });
// }
// ]);
//
// OR !(function (){})() | (function () {})() variants
// OR { 0: function..., 'some-id': function () ... } registry variants
// OR Webpack 5 non-runtime variant:
//
// (function() {
// var exports = {};
// exports.id = 223;
// exports.ids = [223];
// exports.modules = { ... };
// var __webpack_require__ = require("../../webpack-runtime.js");
// ...
// })()
//
else if (wrapper.callee.type === 'FunctionExpression' &&
wrapper.callee.params.length === 1 &&
wrapper.callee.body.body.length > 2 &&
Expand Down Expand Up @@ -350,13 +361,44 @@ export function handleWrappers(ast: Node) {
wrapper.arguments[0].properties &&
wrapper.arguments[0].properties.length > 0 &&
wrapper.arguments[0].properties.every((prop: any) => prop && prop.key && prop.key.type === 'Literal' && prop.value && prop.value.type === 'FunctionExpression')
)) {
) ||
wrapper.arguments.length === 0 &&
wrapper.callee.type === 'FunctionExpression' &&
wrapper.callee.params.length === 0 &&
wrapper.callee.body.type === 'BlockStatement' &&
wrapper.callee.body.body.length > 5 &&
wrapper.callee.body.body[0].type === 'VariableDeclaration' &&
wrapper.callee.body.body[0].declarations.length === 1 &&
wrapper.callee.body.body[0].declarations[0].id.type === 'Identifier' &&
wrapper.callee.body.body[1].type === 'ExpressionStatement' &&
wrapper.callee.body.body[1].expression.type === 'AssignmentExpression' &&
wrapper.callee.body.body[2].type === 'ExpressionStatement' &&
wrapper.callee.body.body[2].expression.type === 'AssignmentExpression' &&
wrapper.callee.body.body[3].type === 'ExpressionStatement' &&
wrapper.callee.body.body[3].expression.type === 'AssignmentExpression' &&
wrapper.callee.body.body[3].expression.left.type === 'MemberExpression' &&
wrapper.callee.body.body[3].expression.left.object.type === 'Identifier' &&
wrapper.callee.body.body[3].expression.left.object.name === wrapper.callee.body.body[0].declarations[0].id.name &&
wrapper.callee.body.body[3].expression.left.property.type === 'Identifier' &&
wrapper.callee.body.body[3].expression.left.property.name === 'modules' &&
wrapper.callee.body.body[3].expression.right.type === 'ObjectExpression' &&
(wrapper.callee.body.body[4].type === 'VariableDeclaration' &&
wrapper.callee.body.body[4].declarations.length === 1 &&
wrapper.callee.body.body[4].declarations[0].init.type === 'CallExpression' &&
wrapper.callee.body.body[4].declarations[0].init.callee.type === 'Identifier' &&
wrapper.callee.body.body[4].declarations[0].init.callee.name === 'require' ||
wrapper.callee.body.body[5].type === 'VariableDeclaration' &&
wrapper.callee.body.body[5].declarations.length === 1 &&
wrapper.callee.body.body[5].declarations[0].init.type === 'CallExpression' &&
wrapper.callee.body.body[5].declarations[0].init.callee.type === 'Identifier' &&
wrapper.callee.body.body[5].declarations[0].init.callee.name === 'require')) {
const externalMap = new Map<number, any>();
const moduleObj = wrapper.callee.params.length ? wrapper.arguments[0] : wrapper.callee.body.body[3].expression.right;
let modules: [number, any][];
if (wrapper.arguments[0].type === 'ArrayExpression')
modules = wrapper.arguments[0].elements.map((el: any, i: number) => [i, el]);
if (moduleObj.type === 'ArrayExpression')
modules = moduleObj.elements.map((el: any, i: number) => [i, el]);
else
modules = wrapper.arguments[0].properties.map((prop: any) => [prop.key.value, prop.value]);
modules = moduleObj.properties.map((prop: any) => [prop.key.value, prop.value]);
for (const [k, m] of modules) {
if (m.body.body.length === 1 &&
m.body.body[0].type === 'ExpressionStatement' &&
Expand All @@ -375,7 +417,6 @@ export function handleWrappers(ast: Node) {
externalMap.set(k, m.body.body[0].expression.right.arguments[0].value);
}
}
if (externalMap.size)
for (const [, m] of modules) {
if (m.params.length === 3 && m.params[2].type === 'Identifier') {
const assignedVars = new Map();
Expand Down Expand Up @@ -429,32 +470,44 @@ export function handleWrappers(ast: Node) {
node.callee.property.type === 'Identifier' &&
node.callee.property.name === 'n' &&
node.arguments.length === 1 &&
node.arguments[0].type === 'Identifier' &&
assignedVars.get(node.arguments[0].name)) {
node.arguments[0].type === 'Identifier') {
if (maybeParent && maybeParent.init === node) {
const req = node.arguments[0];
maybeParent.init = {
type: 'ObjectExpression',
properties: [{
type: 'ObjectProperty',
method: false,
computed: false,
shorthand: false,
key: {
type: 'CallExpression',
callee: {
type: 'MemberExpression',
object: {
type: 'Identifier',
name: 'a'
name: 'Object'
},
value: {
type: 'CallExpression',
callee: {
type: 'Identifier',
name: 'require'
},
arguments: [{
type: 'Literal',
value: assignedVars.get(node.arguments[0].name)
property: {
type: 'Identifier',
name: 'assign'
}
},
arguments: [
{
type: 'ArrowFunctionExpression',
expression: true,
params: [],
body: req
},
{
type: 'ObjectExpression',
properties: [{
type: 'ObjectProperty',
method: false,
computed: false,
shorthand: false,
key: {
type: 'Identifier',
name: 'a'
},
value: req
}]
}
}]
]
};
}
}
Expand All @@ -465,4 +518,3 @@ export function handleWrappers(ast: Node) {
}
}
}

Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{ "word": "word" }
60 changes: 60 additions & 0 deletions test/unit/webpack-5-wrapper-namespace/input.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
(function() {
var exports = {};
exports.id = 223;
exports.ids = [223];
exports.modules = {

/***/ 0:
/***/ (function(module, exports, __webpack_require__) {

module.exports = __webpack_require__("PicC");


/***/ }),

/***/ "PicC":
/***/ (function(module, __webpack_exports__, __webpack_require__) {

"use strict";
__webpack_require__.r(__webpack_exports__);
/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "default", function() { return handler; });
/* harmony import */ var path__WEBPACK_IMPORTED_MODULE_0__ = require('path');
/* harmony import */ var path__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(path__WEBPACK_IMPORTED_MODULE_0__);
/* harmony import */ var fs__WEBPACK_IMPORTED_MODULE_1__ = require('fs');
/* harmony import */ var fs__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(fs__WEBPACK_IMPORTED_MODULE_1__);

f(__webpack_require__("oyvS"));


function handler(req, res) {
const dictionaryPath = path__WEBPACK_IMPORTED_MODULE_0___default().join(process.cwd(), "assets", "dictionary.json");
const content = fs__WEBPACK_IMPORTED_MODULE_1___default().readFileSync(dictionaryPath, "utf-8");
res.json(content);
}

/***/ }),

/***/ "mw/K":
/***/ (function(module, exports) {

module.exports = require("fs");

/***/ }),

/***/ "oyvS":
/***/ (function(module, exports) {

module.exports = require("path");

/***/ })

/******/ };

// load runtime
var __webpack_require__ = require("../../webpack-runtime.js");
__webpack_require__.C(exports);
var __webpack_exec__ = function(moduleId) { return __webpack_require__(__webpack_require__.s = moduleId); }
var __webpack_exports__ = (__webpack_exec__(277));
module.exports = __webpack_exports__;

})();
5 changes: 5 additions & 0 deletions test/unit/webpack-5-wrapper-namespace/output.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
[
"package.json",
"test/unit/webpack-5-wrapper-namespace/assets/dictionary.json",
"test/unit/webpack-5-wrapper-namespace/input.js"
]

0 comments on commit bd87d04

Please sign in to comment.