From e2385252139020946aca43c153a3d4a11ff36135 Mon Sep 17 00:00:00 2001 From: Guy Bedford Date: Thu, 17 Jan 2019 23:36:06 +0200 Subject: [PATCH] Refactor binding analysis --- src/loaders/relocate-loader.js | 568 +++++++++------------------------ src/utils/binary-locators.js | 83 +++++ src/utils/wrappers.js | 156 +++++++++ 3 files changed, 392 insertions(+), 415 deletions(-) create mode 100644 src/utils/binary-locators.js create mode 100644 src/utils/wrappers.js diff --git a/src/loaders/relocate-loader.js b/src/loaders/relocate-loader.js index 620dae003..39682d678 100644 --- a/src/loaders/relocate-loader.js +++ b/src/loaders/relocate-loader.js @@ -1,5 +1,5 @@ const path = require('path'); -const fs = require('graceful-fs'); +const { readFile, stat, statSync, existsSync } = require('graceful-fs'); const { walk } = require('estree-walker'); const MagicString = require('magic-string'); const { attachScopes } = require('rollup-pluginutils'); @@ -10,93 +10,11 @@ const getUniqueAssetName = require('../utils/dedupe-names'); const sharedlibEmit = require('../utils/sharedlib-emit'); const glob = require('glob'); const getPackageBase = require('../utils/get-package-base'); +const { pregyp, nbind } = require('../utils/binary-locators'); +const handleWrappers = require('../utils/wrappers'); -// binary support for inlining logic from - node-pre-gyp/lib/pre-binding.js -function isPregypId (id) { - return id === 'node-pre-gyp' || - id === 'node-pre-gyp/lib/pre-binding' || - id === 'node-pre-gyp/lib/pre-binding.js'; -} -const versioning = require('node-pre-gyp/lib/util/versioning.js'); -const napi = require('node-pre-gyp/lib/util/napi.js'); -const pregyp = { - find (package_json_path, opts) { - const package_json = JSON.parse(fs.readFileSync(package_json_path).toString()); - versioning.validate_config(package_json, opts); - var napi_build_version; - if (napi.get_napi_build_versions (package_json, opts)) { - napi_build_version = napi.get_best_napi_build_version(package_json, opts); - } - opts = opts || {}; - if (!opts.module_root) opts.module_root = path.dirname(package_json_path); - var meta = versioning.evaluate(package_json,opts,napi_build_version); - return meta.module; - } -}; - -function getNbind () { - // Adapted from nbind.js - function makeModulePathList(root, name) { - return ([ - [root, name], - [root, 'build', name], - [root, 'build', 'Debug', name], - [root, 'build', 'Release', name], - [root, 'out', 'Debug', name], - [root, 'Debug', name], - [root, 'out', 'Release', name], - [root, 'Release', name], - [root, 'build', 'default', name], - [ - root, - process.env['NODE_BINDINGS_COMPILED_DIR'] || 'compiled', - process.versions.node, - process.platform, - process.arch, - name - ] - ]); - } - function findCompiledModule(basePath, specList) { - var resolvedList = []; - var ext = path.extname(basePath); - for (var _i = 0, specList_1 = specList; _i < specList_1.length; _i++) { - var spec = specList_1[_i]; - if (ext == spec.ext) { - try { - spec.path = eval('require.resolve(basePath)'); - return spec; - } - catch (err) { - resolvedList.push(basePath); - } - } - } - for (var _a = 0, specList_2 = specList; _a < specList_2.length; _a++) { - var spec = specList_2[_a]; - for (var _b = 0, _c = makeModulePathList(basePath, spec.name); _b < _c.length; _b++) { - var pathParts = _c[_b]; - var resolvedPath = path.resolve.apply(path, pathParts); - try { - spec.path = eval('require.resolve(resolvedPath)'); - } - catch (err) { - resolvedList.push(resolvedPath); - continue; - } - return spec; - } - } - return null; - } - function find(basePath = process.cwd()) { - return findCompiledModule(basePath, [ - { ext: '.node', name: 'nbind.node', type: 'node' }, - { ext: '.js', name: 'nbind.js', type: 'emcc' } - ]); - } - return { init: find, find: find }; -} +const staticPath = Object.assign({ default: path }, path); +const staticFs = { default: { existsSync }, existsSync }; function isExpressionReference(node, parent) { if (parent.type === 'MemberExpression') return parent.computed || node === parent.object; @@ -116,161 +34,6 @@ function isExpressionReference(node, parent) { return true; } -// Wrapper detections for require extraction handles: -// -// 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); }) -// -// Browserify-style wrapper -// (function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.bugsnag = f()}})(function(){var define,module,exports;return (function(){function r(e,n,t){function o(i,f){if(!n[i]){if(!e[i]){var c="function"==typeof require&&require;if(!f&&c)return c(i,!0);if(u)return u(i,!0);var a=new Error("Cannot find module '"+i+"'");throw a.code="MODULE_NOT_FOUND",a}var p=n[i]={exports:{}};e[i][0].call(p.exports,function(r){var n=e[i][1][r];return o(n||r)},p,p.exports,r,e,n,t)}return n[i].exports}for(var u="function"==typeof require&&require,i=0;i -// (function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.bugsnag = f()}})(function(){var define,module,exports;return (function(){function r(e,n,t){function o(i,f){if(!n[i]){if(!e[i]){var c="function"==typeof require&&require;if(!f&&c)return c(i,!0);if(u)return u(i,!0);var a=new Error("Cannot find module '"+i+"'");throw a.code="MODULE_NOT_FOUND",a}var p=n[i]={exports:{}};e[i][0].call(p.exports,function(r){var n=e[i][1][r];return o(n||r)},p,p.exports,r,e,n,t)}return n[i].exports}for(var u="function"==typeof require&&require,i=0;i decl.init === null && decl.id.type === 'Identifier') && - arg.body.body[1].type === 'ReturnStatement' && - arg.body.body[1].argument.type === 'CallExpression' && - arg.body.body[1].argument.callee.type === 'CallExpression' && - arg.body.body[1].argument.arguments.length && - arg.body.body[1].argument.arguments.every(arg => arg.type === 'Literal' && typeof arg.value === 'number') && - arg.body.body[1].argument.callee.callee.type === 'CallExpression' && - arg.body.body[1].argument.callee.callee.callee.type === 'FunctionExpression' && - arg.body.body[1].argument.callee.callee.arguments.length === 0 && - // (dont go deeper into browserify loader internals than this) - arg.body.body[1].argument.callee.arguments.length === 3 && - arg.body.body[1].argument.callee.arguments[0].type === 'ObjectExpression' && - arg.body.body[1].argument.callee.arguments[1].type === 'ObjectExpression' && - arg.body.body[1].argument.callee.arguments[2].type === 'ArrayExpression') { - const modules = arg.body.body[1].argument.callee.arguments[0].properties; - - // verify modules is the expected data structure - // in the process, extract external requires - const externals = {}; - if (modules.every(m => { - if (m.type !== 'Property' || - m.computed !== false || - m.key.type !== 'Literal' || - typeof m.key.value !== 'number' || - m.value.type !== 'ArrayExpression' || - m.value.elements.length !== 2 || - m.value.elements[0].type !== 'FunctionExpression' || - m.value.elements[1].type !== 'ObjectExpression') - return false; - - // detect externals from undefined moduleMap values - const moduleMap = m.value.elements[1].properties; - for (const prop of moduleMap) { - if (prop.type !== 'Property' || - (prop.value.type !== 'Identifier' && prop.value.type !== 'Literal') || - prop.key.type !== 'Literal' || - typeof prop.key.value !== 'string' || - prop.computed) - return false; - if (prop.value.type === 'Identifier' && prop.value.name === 'undefined') - externals[prop.key.value] = true; - } - return true; - })) { - // if we have externals, inline them into the browserify cache for webpack to pick up - const externalIds = Object.keys(externals); - if (externalIds.length) { - const cache = arg.body.body[1].argument.callee.arguments[1]; - const renderedExternals = externalIds.map(ext => `"${ext}": { exports: require("${ext}") }`).join(',\n '); - magicString.appendRight(cache.end - 1, renderedExternals); - transformed = true; - } - } - } - } - return { ast, scope, transformed }; -} - const relocateRegEx = /_\_dirname|_\_filename|require\.main|node-pre-gyp|bindings|define/; module.exports = function (code) { @@ -282,6 +45,32 @@ module.exports = function (code) { if (id.endsWith('.json') || !code.match(relocateRegEx)) return this.callback(null, code); + // calculate the base-level package folder to load bindings from + const pkgBase = getPackageBase(id); + + const staticModules = Object.assign(Object.create(null), { + path: staticPath, + fs: staticFs, + 'node-pre-gyp': pregyp, + 'node-pre-gyp/lib/pre-binding': pregyp, + 'node-pre-gyp/lib/pre-binding.js': pregyp, + 'nbind': nbind + }); + + let staticBindingsInstance = false; + function createBindings () { + return (opts = {}) => { + if (typeof opts === 'string') + opts = { bindings: opts }; + if (!opts.path) { + opts.path = true; + staticBindingsInstance = true; + } + opts.module_root = pkgBase; + return bindings(opts); + }; + } + const emitAsset = (assetPath) => { // JS assets to support require(assetPath) and not fs-based handling // NB package.json is ambiguous here... @@ -306,10 +95,10 @@ module.exports = function (code) { assetEmissionPromises = assetEmissionPromises.then(async () => { const [source, permissions] = await Promise.all([ new Promise((resolve, reject) => - fs.readFile(assetPath, (err, source) => err ? reject(err) : resolve(source)) + readFile(assetPath, (err, source) => err ? reject(err) : resolve(source)) ), await new Promise((resolve, reject) => - fs.stat(assetPath, (err, stats) => err ? reject(err) : resolve(stats.mode)) + stat(assetPath, (err, stats) => err ? reject(err) : resolve(stats.mode)) ) ]); assetState.assetPermissions[name] = permissions; @@ -332,10 +121,10 @@ module.exports = function (code) { return; const [source, permissions] = await Promise.all([ new Promise((resolve, reject) => - fs.readFile(file, (err, source) => err ? reject(err) : resolve(source)) + readFile(file, (err, source) => err ? reject(err) : resolve(source)) ), await new Promise((resolve, reject) => - fs.stat(file, (err, stats) => err ? reject(err) : resolve(stats.mode)) + stat(file, (err, stats) => err ? reject(err) : resolve(stats.mode)) ) ]); assetState.assetPermissions[name + file.substr(assetDirPath.length)] = permissions; @@ -363,54 +152,63 @@ module.exports = function (code) { let scope = attachScopes(ast, 'scope'); - let pathId, pathImportIds = {}; - let fsId; - let pregypId, bindingsId, nbindId; + const knownBindings = Object.assign(Object.create(null), { + __dirname: { + shadowDepth: 0, + value: path.resolve(id, '..') + }, + __filename: { + shadowDepth: 0, + value: id + } + }); - const shadowDepths = Object.create(null); - shadowDepths.__filename = 0; - shadowDepths.__dirname = 0; - if (!isESM) { - shadowDepths.require = 0; + if (!isESM) + knownBindings.require = { + shadowDepth: 0 + }; + + function setKnownBinding (name, value) { + // require is somewhat special in that we shadow it but don't + // statically analyze it ("known unknown" of sorts) + if (name === 'require') return; + knownBindings[name] = { + shadowDepth: 0, + value: value + }; } - else { + function getKnownBinding (name) { + const binding = knownBindings[name]; + if (binding) { + if (binding.shadowDepth === 0) { + return binding.value; + } + } + } + + let nbindId, pregypId, bindingsId; + + if (isESM) { for (const decl of ast.body) { - // Detects: - // import * as path from 'path'; - // import path from 'path'; - // import { join } from 'path'; if (decl.type === 'ImportDeclaration') { const source = decl.source.value; - if (source === 'path') { - for (const impt of decl.specifiers) { - if (impt.type === 'ImportNamespaceSpecifier' || impt.type === 'ImportDefaultSpecifier') { - pathId = impt.local.name; - shadowDepths[pathId] = 0; - } - else if (impt.type === 'ImportSpecifier') { - pathImportIds[impt.local.name] = impt.imported.name; - shadowDepths[impt.local.name] = 0; - } - } - } - // import binary from 'node-pre-gyp'; - // import * as binary from 'node-pre-gyp'; - // import { find } from 'node-pre-gyp' not yet implemented - else if (isPregypId(source)) { + const staticModule = staticModules[source]; + if (staticModule) { for (const impt of decl.specifiers) { - if (impt.type === 'ImportNamespaceSpecifier' || impt.type === 'ImportDefaultSpecifier') { - pregypId = impt.local.name; - shadowDepths[pregypId] = 0; - } - } - } - // import bindings from 'bindings'; - else if (source === 'bindings') { - for (const impt of decl.specifiers) { - if (impt.type === 'ImportDefaultSpecifier') { - bindingsId = impt.local.name; - shadowDepths[bindingsId] = 0; - } + let bindingId; + if (impt.type === 'ImportNamespaceSpecifier') + setKnownBinding(bindingId = impt.local.name, staticModule); + else if (impt.type === 'ImportDefaultSpecifier' && 'default' in staticModule) + setKnownBinding(bindingId = impt.local.name, staticModule.default); + else if (impt.type === 'ImportSpecifier' && impt.imported.name in staticModule) + setKnownBinding(bindingId = impt.local.name, staticModule[impt.imported.name]); + + if (source === 'bindings') + bindingsId = bindingId; + else if (source === 'node-pre-gyp' || source === 'node-pre-gyp/lib/pre-binding' || source === 'node-pre-gyp/lib/pre-binding.js') + pregypId = bindingId; + else if (source === 'nbind') + nbindId = bindingId; } } } @@ -419,62 +217,18 @@ module.exports = function (code) { let transformed = false; - let staticBindingsInstance = false; - // calculate the base-level package folder to load bindings from - const pkgBase = getPackageBase(id); - function createBindings () { - return (opts = {}) => { - if (typeof opts === 'string') - opts = { bindings: opts }; - if (!opts.path) { - opts.path = true; - staticBindingsInstance = true; - } - opts.module_root = pkgBase; - return bindings(opts); - }; - } - function computeStaticValue (expr, bindingsReq) { + function computeStaticValue (expr) { staticBindingsInstance = false; // function expression analysis disabled due to static-eval locals bug if (expr.type === 'FunctionExpression') return; - const vars = {}; - if (shadowDepths.__dirname === 0) - vars.__dirname = path.resolve(id, '..'); - if (shadowDepths.__filename === 0) - vars.__filename = id; - if (pathId) { - if (shadowDepths[pathId] === 0) - vars[pathId] = path; - } - if (fsId) { - if (shadowDepths[fsId] === 0) - vars[fsId] = { - existsSync: fs.existsSync - }; - } - if (pregypId) { - if (shadowDepths[pregypId] === 0) - vars[pregypId] = pregyp; - } - if (bindingsId) { - if (shadowDepths[bindingsId] === 0) - vars[bindingsId] = createBindings(); - } - if (nbindId) { - if (shadowDepths[nbindId] === 0) - vars[nbindId] = getNbind(); - } - for (const pathFn of Object.keys(pathImportIds)) { - if (shadowDepths[pathFn] === 0) - vars[pathFn] = path[pathImportIds[pathFn]]; - } - if (bindingsReq && shadowDepths.require === 0) - vars.require = function (reqId) { - if (reqId === 'bindings') - return createBindings(); - }; + + const vars = Object.create(null); + Object.keys(knownBindings).forEach(name => { + const { shadowDepth, value } = knownBindings[name]; + if (shadowDepth === 0 && value !== undefined) + vars[name] = value; + }); // evaluate returns undefined for non-statically-analyzable return evaluate(expr, vars); @@ -490,7 +244,7 @@ module.exports = function (code) { node.type === 'CallExpression' && node.callee.type === 'Identifier' && node.callee.name === 'require' && - shadowDepths.require === 0 && + knownBindings.require.shadowDepth === 0 && node.arguments.length === 1 && node.arguments[0].type === 'Literal'; } @@ -502,8 +256,8 @@ module.exports = function (code) { if (node.scope) { scope = node.scope; for (const id in node.scope.declarations) { - if (id in shadowDepths) - shadowDepths[id]++; + if (id in knownBindings) + knownBindings[id].shadowDepth++; } } @@ -514,9 +268,10 @@ module.exports = function (code) { // __dirname, __filename, binary only currently as well as require('bindings')(...) // Can add require.resolve, import.meta.url, even path-like environment variables if (node.type === 'Identifier' && isExpressionReference(node, parent)) { - if (!shadowDepths[node.name]) { - if (node.name === '__dirname' || node.name === '__filename' || - node.name === pregypId || node.name === bindingsId) { + if (node.name === '__dirname' || node.name === '__filename' || + node.name === pregypId || node.name === bindingsId) { + const binding = getKnownBinding(node.name); + if (binding) { staticChildValue = computeStaticValue(node, false); // if it computes, then we start backtracking if (staticChildValue) { @@ -531,7 +286,7 @@ module.exports = function (code) { else if (node.type === 'CallExpression' && !isESM && isStaticRequire(node.callee) && node.callee.arguments[0].value === 'bindings') { - staticChildValue = computeStaticValue(node, true); + staticChildValue = createBindings()(computeStaticValue(node.arguments[0], true)); if (staticChildValue) { staticChildNode = node; staticChildValueBindingsInstance = staticBindingsInstance; @@ -558,88 +313,68 @@ module.exports = function (code) { else if (!isESM && node.type === 'MemberExpression' && node.object.type === 'Identifier' && node.object.name === 'require' && - !shadowDepths.require && + knownBindings.require.shadowDepth === 0 && node.property.type === 'Identifier' && node.property.name === 'main' && !node.computed) { magicString.overwrite(node.object.start, node.object.end, '__non_webpack_require__'); transformed = true; } - - // for now we only support top-level variable declarations - // so "var { join } = require('path')" will only detect in the top scope. - // Intermediate scope handling for these requires is straightforward, but - // would need nested shadow depth handling of the pathIds. - else if (parent === ast && node.type === 'VariableDeclaration') { + else if (node.type === 'VariableDeclaration') { for (const decl of node.declarations) { - // var path = require('path') - if (decl.id.type === 'Identifier' && - !isESM && isStaticRequire(decl.init)) { - if (decl.init.arguments[0].value === 'path') { - pathId = decl.id.name; - shadowDepths[pathId] = 0; - return this.skip(); - } - // var fs = require('fs') - else if (decl.init.arguments[0].value === 'fs') { - fsId = decl.id.name; - shadowDepths[fsId] = 0; - return this.skip(); - } - // var binary = require('node-pre-gyp') - else if (isPregypId(decl.init.arguments[0].value)) { - pregypId = decl.id.name; - shadowDepths[pregypId] = 0; - return this.skip(); - } - // var bindings = require('bindings') - else if (decl.init.arguments[0].value === 'bindings') { - bindingsId = decl.id.name; - shadowDepths[bindingsId] = 0; - return this.skip(); - } - // var nbind = require('nbind') - else if (decl.init.arguments[0].value === 'nbind') { - nbindId = decl.id.name; - shadowDepths[nbindId] = 0; + let binding; + if (!isESM && isStaticRequire(decl.init)) { + const source = decl.init.arguments[0].value; + const staticModule = staticModules[source]; + if (staticModule) { + // var known = require('known'); + if (decl.id.type === 'Identifier') { + setKnownBinding(decl.id.name, staticModule.default); + if (source === 'bindings') + bindingsId = decl.id.name; + else if (source === 'node-pre-gyp' || source === 'node-pre-gyp/lib/pre-binding' || source === 'node-pre-gyp/lib/pre-binding.js') + pregypId = decl.id.name; + else if (source === 'nbind') + nbindId = decl.id.name; + } + // var { known } = require('known); + else if (decl.id.type === 'ObjectPattern') { + for (const prop of decl.id.properties) { + if (prop.type !== 'Property' || + prop.key.type !== 'Identifier' || + prop.value.type !== 'Identifier' || + !(prop.key.name in staticModule)) + continue; + setKnownBinding(prop.value.name, staticModule[prop.key.name]); + } + } } } - // var { join } = path | require('path'); - else if (decl.id.type === 'ObjectPattern' && decl.init && - (decl.init.type === 'Identifier' && decl.init.name === pathId && shadowDepths[pathId] === 0) || - !isESM && isStaticRequire(decl.init) && decl.init.arguments[0].value === 'path') { - for (const prop of decl.id.properties) { - if (prop.type !== 'Property' || - prop.key.type !== 'Identifier' || - prop.value.type !== 'Identifier') - continue; - pathImportIds[prop.value.name] = prop.key.name; - shadowDepths[prop.key.name] = 0; - return this.skip(); - } + // var { knownProp } = known; + else if (decl.id.type === 'ObjectPattern' && + decl.init && decl.init.type === 'Identifier' && + (binding = getKnownBinding(decl.init.name)) !== undefined && + prop.key.name in binding) { + setKnownBinding(prop.value.name, binding[prop.key.name]); } - // var join = path.join + // var known = known.knownProp; else if (decl.id.type === 'Identifier' && - decl.init && - decl.init.type === 'MemberExpression' && - decl.init.object.type === 'Identifier' && - decl.init.object.name === pathId && - shadowDepths[decl.init.object.name] === 0 && - decl.init.computed === false && - decl.init.property.type === 'Identifier') { - pathImportIds[decl.init.property.name] = decl.id.name; - shadowDepths[decl.id.name] = 0; - return this.skip(); + decl.init && + decl.init.type === 'MemberExpression' && + decl.init.computed === false && + decl.init.object.type === 'Identifier' && + decl.init.property.type === 'Identifier' && + (binding = getKnownBinding(decl.init.object.name)) !== undefined && + decl.init.property.name in binding) { + setKnownBinding(decl.id.name, binding[decl.init.property.name]); } } } else if (node.type === 'AssignmentExpression') { // path = require('path') - if (isStaticRequire(node.right) && node.right.arguments[0].value === 'path' && + if (isStaticRequire(node.right) && node.right.arguments[0].value in staticModules && node.left.type === 'Identifier' && scope.declarations[node.left.name]) { - pathId = node.left.name; - shadowDepths[pathId] = 0; - return this.skip(); + setKnownBinding(node.left.name, staticModules[node.right.arguments[0].value]); } } }, @@ -647,8 +382,11 @@ module.exports = function (code) { if (node.scope) { scope = scope.parent; for (const id in node.scope.declarations) { - if (id in shadowDepths) { - shadowDepths[id]--; + if (id in knownBindings) { + if (knownBindings[id].shadowDepth > 0) + knownBindings[id].shadowDepth--; + else + delete knownBindings[id]; } } } @@ -668,7 +406,7 @@ module.exports = function (code) { let stats; if (typeof staticChildValue === 'string') { try { - stats = fs.statSync(staticChildValue); + stats = statSync(staticChildValue); } catch (e) {} } diff --git a/src/utils/binary-locators.js b/src/utils/binary-locators.js new file mode 100644 index 000000000..4ce1b834a --- /dev/null +++ b/src/utils/binary-locators.js @@ -0,0 +1,83 @@ +const path = require("path"); +const fs = require("fs"); + +// pregyp +const versioning = require("node-pre-gyp/lib/util/versioning.js"); +const napi = require("node-pre-gyp/lib/util/napi.js"); +const pregypFind = (package_json_path, opts) => { + const package_json = JSON.parse(fs.readFileSync(package_json_path).toString()); + versioning.validate_config(package_json, opts); + var napi_build_version; + if (napi.get_napi_build_versions (package_json, opts)) { + napi_build_version = napi.get_best_napi_build_version(package_json, opts); + } + opts = opts || {}; + if (!opts.module_root) opts.module_root = path.dirname(package_json_path); + var meta = versioning.evaluate(package_json,opts,napi_build_version); + return meta.module; +}; +exports.pregyp = { default: { find: pregypFind }, find: pregypFind }; + +// nbind +// Adapted from nbind.js +function makeModulePathList(root, name) { + return ([ + [root, name], + [root, "build", name], + [root, "build", "Debug", name], + [root, "build", "Release", name], + [root, "out", "Debug", name], + [root, "Debug", name], + [root, "out", "Release", name], + [root, "Release", name], + [root, "build", "default", name], + [ + root, + process.env["NODE_BINDINGS_COMPILED_DIR"] || "compiled", + process.versions.node, + process.platform, + process.arch, + name + ] + ]); +} +function findCompiledModule(basePath, specList) { + var resolvedList = []; + var ext = path.extname(basePath); + for (var _i = 0, specList_1 = specList; _i < specList_1.length; _i++) { + var spec = specList_1[_i]; + if (ext == spec.ext) { + try { + spec.path = eval("require.resolve(basePath)"); + return spec; + } + catch (err) { + resolvedList.push(basePath); + } + } + } + for (var _a = 0, specList_2 = specList; _a < specList_2.length; _a++) { + var spec = specList_2[_a]; + for (var _b = 0, _c = makeModulePathList(basePath, spec.name); _b < _c.length; _b++) { + var pathParts = _c[_b]; + var resolvedPath = path.resolve.apply(path, pathParts); + try { + spec.path = eval("require.resolve(resolvedPath)"); + } + catch (err) { + resolvedList.push(resolvedPath); + continue; + } + return spec; + } + } + return null; +} +function find(basePath = process.cwd()) { + return findCompiledModule(basePath, [ + { ext: ".node", name: "nbind.node", type: "node" }, + { ext: ".js", name: "nbind.js", type: "emcc" } + ]); +} +exports.nbind = { default: { init: find, find }, init: find, find }; + diff --git a/src/utils/wrappers.js b/src/utils/wrappers.js new file mode 100644 index 000000000..d87041715 --- /dev/null +++ b/src/utils/wrappers.js @@ -0,0 +1,156 @@ +// Wrapper detections for require extraction handles: +// +// 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); }) +// +// Browserify-style wrapper +// (function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.bugsnag = f()}})(function(){var define,module,exports;return (function(){function r(e,n,t){function o(i,f){if(!n[i]){if(!e[i]){var c="function"==typeof require&&require;if(!f&&c)return c(i,!0);if(u)return u(i,!0);var a=new Error("Cannot find module '"+i+"'");throw a.code="MODULE_NOT_FOUND",a}var p=n[i]={exports:{}};e[i][0].call(p.exports,function(r){var n=e[i][1][r];return o(n||r)},p,p.exports,r,e,n,t)}return n[i].exports}for(var u="function"==typeof require&&require,i=0;i +// (function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.bugsnag = f()}})(function(){var define,module,exports;return (function(){function r(e,n,t){function o(i,f){if(!n[i]){if(!e[i]){var c="function"==typeof require&&require;if(!f&&c)return c(i,!0);if(u)return u(i,!0);var a=new Error("Cannot find module '"+i+"'");throw a.code="MODULE_NOT_FOUND",a}var p=n[i]={exports:{}};e[i][0].call(p.exports,function(r){var n=e[i][1][r];return o(n||r)},p,p.exports,r,e,n,t)}return n[i].exports}for(var u="function"==typeof require&&require,i=0;i decl.init === null && decl.id.type === 'Identifier') && + arg.body.body[1].type === 'ReturnStatement' && + arg.body.body[1].argument.type === 'CallExpression' && + arg.body.body[1].argument.callee.type === 'CallExpression' && + arg.body.body[1].argument.arguments.length && + arg.body.body[1].argument.arguments.every(arg => arg.type === 'Literal' && typeof arg.value === 'number') && + arg.body.body[1].argument.callee.callee.type === 'CallExpression' && + arg.body.body[1].argument.callee.callee.callee.type === 'FunctionExpression' && + arg.body.body[1].argument.callee.callee.arguments.length === 0 && + // (dont go deeper into browserify loader internals than this) + arg.body.body[1].argument.callee.arguments.length === 3 && + arg.body.body[1].argument.callee.arguments[0].type === 'ObjectExpression' && + arg.body.body[1].argument.callee.arguments[1].type === 'ObjectExpression' && + arg.body.body[1].argument.callee.arguments[2].type === 'ArrayExpression') { + const modules = arg.body.body[1].argument.callee.arguments[0].properties; + + // verify modules is the expected data structure + // in the process, extract external requires + const externals = {}; + if (modules.every(m => { + if (m.type !== 'Property' || + m.computed !== false || + m.key.type !== 'Literal' || + typeof m.key.value !== 'number' || + m.value.type !== 'ArrayExpression' || + m.value.elements.length !== 2 || + m.value.elements[0].type !== 'FunctionExpression' || + m.value.elements[1].type !== 'ObjectExpression') + return false; + + // detect externals from undefined moduleMap values + const moduleMap = m.value.elements[1].properties; + for (const prop of moduleMap) { + if (prop.type !== 'Property' || + (prop.value.type !== 'Identifier' && prop.value.type !== 'Literal') || + prop.key.type !== 'Literal' || + typeof prop.key.value !== 'string' || + prop.computed) + return false; + if (prop.value.type === 'Identifier' && prop.value.name === 'undefined') + externals[prop.key.value] = true; + } + return true; + })) { + // if we have externals, inline them into the browserify cache for webpack to pick up + const externalIds = Object.keys(externals); + if (externalIds.length) { + const cache = arg.body.body[1].argument.callee.arguments[1]; + const renderedExternals = externalIds.map(ext => `"${ext}": { exports: require("${ext}") }`).join(',\n '); + magicString.appendRight(cache.end - 1, renderedExternals); + transformed = true; + } + } + } + } + return { ast, scope, transformed }; +} + +module.exports = handleWrappers;