Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: webpack 5 wrapper handling #186

Merged
merged 4 commits into from
Apr 14, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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"
]