Skip to content

Commit

Permalink
Webpack wrappers - string ids and namespaces (#119)
Browse files Browse the repository at this point in the history
  • Loading branch information
guybedford committed Jun 9, 2020
1 parent 7b00c66 commit c7f25ef
Show file tree
Hide file tree
Showing 5 changed files with 223 additions and 19 deletions.
2 changes: 1 addition & 1 deletion src/analyze.js
Expand Up @@ -394,8 +394,8 @@ module.exports = async function (id, code, job) {
}
}

handleWrappers(ast);
let scope = attachScopes(ast, 'scope');
handleWrappers(ast);
({ ast = ast, scope = scope } = handleSpecialCases({ id, ast, scope, emitAsset: path => assets.add(path), emitAssetDirectory, job }) || {});

function backtrack (self, parent) {
Expand Down
97 changes: 79 additions & 18 deletions src/utils/wrappers.js
Expand Up @@ -21,7 +21,7 @@ function handleWrappers (ast) {
wrapper = ast.body[0].expression;
else if (ast.body.length === 1 &&
ast.body[0].type === 'ExpressionStatement' &&
ast.body[0].expression.type === 'AssgnmentExpression' &&
ast.body[0].expression.type === 'AssignmentExpression' &&
ast.body[0].expression.left.type === 'MemberExpression' &&
ast.body[0].expression.left.object.type === 'Identifier' &&
ast.body[0].expression.left.object.name === 'module' &&
Expand Down Expand Up @@ -89,6 +89,7 @@ function handleWrappers (ast) {
body[0].expression.arguments[0].params.length === 1 &&
body[0].expression.arguments[0].params[0].type === 'Identifier' &&
body[0].expression.arguments[0].params[0].name === 'require') {
delete body[0].expression.arguments[0].scope.declarations.require;
body[0].expression.arguments[0].params = [];
}
}
Expand Down Expand Up @@ -265,13 +266,14 @@ function handleWrappers (ast) {
callSite.arguments[0].name === 'require' &&
callSite.arguments[1].type === 'Identifier' &&
callSite.arguments[1].name === 'exports') {
delete wrapper.arguments[0].scope.declarations.require;
delete wrapper.arguments[0].scope.declarations.exports;
wrapper.arguments[0].params = [];
}
}
}
// Webpack wrapper
//
// or !(function (){})() | (function () {})() variants
// module.exports = (function(e) {
// var t = {};
// function r(n) { /*...*/ }
Expand All @@ -281,6 +283,8 @@ function handleWrappers (ast) {
// },
// function(e, t, r) {
// const n = r(0);
// const ns = r.n(n);
// ns.a.export;
// }
// ]);
// ->
Expand All @@ -293,8 +297,12 @@ function handleWrappers (ast) {
// },
// function(e, t, r) {
// const n = require("fs");
// const ns = { a: require("fs") };
// }
// ]);
//
// OR !(function (){})() | (function () {})() variants
// OR { 0: function..., 'some-id': function () ... } registry variants
else if (wrapper.callee.type === 'FunctionExpression' &&
wrapper.callee.params.length === 1 &&
wrapper.callee.body.body.length > 2 &&
Expand All @@ -306,13 +314,21 @@ function handleWrappers (ast) {
wrapper.callee.body.body[0].declarations[0].init.properties.length === 0 &&
wrapper.callee.body.body[1].type === 'FunctionDeclaration' &&
wrapper.callee.body.body[1].params.length === 1 &&
wrapper.callee.body.body[1].body.body.length === 3 &&
wrapper.arguments[0].type === 'ArrayExpression' &&
wrapper.arguments[0].elements.length > 0 &&
wrapper.arguments[0].elements.every(el => el.type === 'FunctionExpression')) {
wrapper.callee.body.body[1].body.body.length >= 3 && (
wrapper.arguments[0].type === 'ArrayExpression' &&
wrapper.arguments[0].elements.length > 0 &&
wrapper.arguments[0].elements.every(el => el.type === 'FunctionExpression') ||
wrapper.arguments[0].type === 'ObjectExpression' &&
wrapper.arguments[0].properties.length > 0 &&
wrapper.arguments[0].properties.every(prop => prop.key.type === 'Literal' && prop.value.type === 'FunctionExpression')
)) {
const externalMap = new Map();
for (let i = 0; i < wrapper.arguments[0].elements.length; i++) {
const m = wrapper.arguments[0].elements[i];
let modules;
if (wrapper.arguments[0].type === 'ArrayExpression')
modules = wrapper.arguments[0].elements.map((el, i) => [i, el]);
else
modules = wrapper.arguments[0].properties.map(prop => [prop.key.value, prop.value]);
for (const [k, m] of modules) {
if (m.body.body.length === 1 &&
m.body.body[0].type === 'ExpressionStatement' &&
m.body.body[0].expression.type === 'AssignmentExpression' &&
Expand All @@ -327,13 +343,13 @@ function handleWrappers (ast) {
m.body.body[0].expression.right.callee.name === 'require' &&
m.body.body[0].expression.right.arguments.length === 1 &&
m.body.body[0].expression.right.arguments[0].type === 'Literal') {
externalMap.set(i, m.body.body[0].expression.right.arguments[0].value);
externalMap.set(k, m.body.body[0].expression.right.arguments[0].value);
}
}
for (let i = 0; i < wrapper.arguments[0].elements.length; i++) {
const m = wrapper.arguments[0].elements[i];
for (const [, m] of modules) {
if (m.params.length === 3 && m.params[2].type === 'Identifier') {
walk(m.body.body, {
const assignedVars = new Map();
walk(m.body, {
enter (node, parent) {
if (node.type === 'FunctionExpression' ||
node.type === 'FunctionDeclaration' ||
Expand Down Expand Up @@ -361,18 +377,63 @@ function handleWrappers (ast) {
value: externalId
}]
};
if (parent.right === node)
if (parent.right === node) {
parent.right = replacement;
else if (parent.left === node)
}
else if (parent.left === node) {
parent.left = replacement;
else if (parent.object === node)
}
else if (parent.object === node) {
parent.object = replacement;
else if (parent.callee === node)
}
else if (parent.callee === node) {
parent.callee = replacement;
else if (parent.arguments && parent.arguments.some(arg => arg === node))
}
else if (parent.arguments && parent.arguments.some(arg => arg === node)) {
delete parent.scope.declarations[node.id];
parent.arguments = parent.arguments.map(arg => arg === node ? replacement : arg);
else if (parent.init === node)
}
else if (parent.init === node) {
if (parent.type === 'VariableDeclarator' && parent.id.type === 'Identifier')
assignedVars.set(parent.id.name, externalId);
parent.init = replacement;
}
}
}
else if (node.type === 'CallExpression' &&
node.callee.type === 'MemberExpression' &&
node.callee.object.type === 'Identifier' &&
node.callee.object.name === m.params[2].name &&
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)) {
if (parent.init === node) {
parent.init = {
type: 'ObjectExpression',
properties: [{
type: 'ObjectProperty',
method: false,
computed: false,
shorthand: false,
key: {
type: 'Identifier',
name: 'a'
},
value: {
type: 'CallExpression',
callee: {
type: 'Identifier',
name: 'require'
},
arguments: [{
type: 'Literal',
value: assignedVars.get(node.arguments[0].name)
}]
}
}]
};
}
}
}
Expand Down
@@ -0,0 +1 @@
{ "word": "word" }
138 changes: 138 additions & 0 deletions test/unit/webpack-wrapper-strs-namespaces/input.js
@@ -0,0 +1,138 @@
module.exports =
/******/ (function(modules) { // webpackBootstrap
/******/ // The module cache
/******/ var installedModules = {};
/******/
/******/ // The require function
/******/ function __webpack_require__(moduleId) {
/******/
/******/ // Check if module is in cache
/******/ if(installedModules[moduleId]) {
/******/ return installedModules[moduleId].exports;
/******/ }
/******/ // Create a new module (and put it into the cache)
/******/ var module = installedModules[moduleId] = {
/******/ i: moduleId,
/******/ l: false,
/******/ exports: {}
/******/ };
/******/
/******/ // Execute the module function
/******/ var threw = true;
/******/ try {
/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
/******/ threw = false;
/******/ } finally {
/******/ if(threw) delete installedModules[moduleId];
/******/ }
/******/
/******/ // Flag the module as loaded
/******/ module.l = true;
/******/
/******/ // Return the exports of the module
/******/ return module.exports;
/******/ }
/******/
/******/
/******/ // expose the modules object (__webpack_modules__)
/******/ __webpack_require__.m = modules;
/******/
/******/ // expose the module cache
/******/ __webpack_require__.c = installedModules;
/******/
/******/ // define getter function for harmony exports
/******/ __webpack_require__.d = function(exports, name, getter) {
/******/ if(!__webpack_require__.o(exports, name)) {
/******/ Object.defineProperty(exports, name, { enumerable: true, get: getter });
/******/ }
/******/ };
/******/
/******/ // define __esModule on exports
/******/ __webpack_require__.r = function(exports) {
/******/ if(typeof Symbol !== 'undefined' && Symbol.toStringTag) {
/******/ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
/******/ }
/******/ Object.defineProperty(exports, '__esModule', { value: true });
/******/ };
/******/
/******/ // create a fake namespace object
/******/ // mode & 1: value is a module id, require it
/******/ // mode & 2: merge all properties of value into the ns
/******/ // mode & 4: return value when already ns object
/******/ // mode & 8|1: behave like require
/******/ __webpack_require__.t = function(value, mode) {
/******/ if(mode & 1) value = __webpack_require__(value);
/******/ if(mode & 8) return value;
/******/ if((mode & 4) && typeof value === 'object' && value && value.__esModule) return value;
/******/ var ns = Object.create(null);
/******/ __webpack_require__.r(ns);
/******/ Object.defineProperty(ns, 'default', { enumerable: true, value: value });
/******/ if(mode & 2 && typeof value != 'string') for(var key in value) __webpack_require__.d(ns, key, function(key) { return value[key]; }.bind(null, key));
/******/ return ns;
/******/ };
/******/
/******/ // getDefaultExport function for compatibility with non-harmony modules
/******/ __webpack_require__.n = function(module) {
/******/ var getter = module && module.__esModule ?
/******/ function getDefault() { return module['default']; } :
/******/ function getModuleExports() { return module; };
/******/ __webpack_require__.d(getter, 'a', getter);
/******/ return getter;
/******/ };
/******/
/******/ // Object.prototype.hasOwnProperty.call
/******/ __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };
/******/
/******/ // __webpack_public_path__
/******/ __webpack_require__.p = "";
/******/
/******/
/******/ // Load entry module and return exports
/******/ return __webpack_require__(__webpack_require__.s = 0);
/******/ })
/************************************************************************/
/******/ ({

/***/ 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__ = __webpack_require__("oyvS");
/* 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__ = __webpack_require__("mw/K");
/* harmony import */ var fs__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(fs__WEBPACK_IMPORTED_MODULE_1__);


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

/***/ }),

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

module.exports = require("fs");

/***/ }),

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

module.exports = require("path");

/***/ })

/******/ });
4 changes: 4 additions & 0 deletions test/unit/webpack-wrapper-strs-namespaces/output.js
@@ -0,0 +1,4 @@
[
"test/unit/webpack-wrapper-strs-namespaces/assets/dictionary.json",
"test/unit/webpack-wrapper-strs-namespaces/input.js"
]

0 comments on commit c7f25ef

Please sign in to comment.