wip: bundling work

This commit is contained in:
calebboyd
2017-07-05 23:18:08 -05:00
parent ac64063916
commit ce2c4cefd0
7 changed files with 135 additions and 56 deletions
+1 -1
View File
@@ -2,7 +2,7 @@
"presets": [
["env", {
"targets": {
"node": "6.5"
"node": "4.0"
}
}]
]
+1
View File
@@ -2,6 +2,7 @@
temp/
coverage
node_modules
examples
*.log
*.exe
.idea
+3 -3
View File
@@ -16,7 +16,7 @@
## Motivation and Features
- Supports production ready ([secure](#security)) builds
- Supports production ready, ([securable](#security)) builds
- Ability to run multiple applications with *different* node.js runtimes.
- Distribute binaries without needing node / npm.
- Idempotent builds
@@ -53,7 +53,7 @@ Additional resources can be added to the binary by passing `-r glob/pattern/**/*
By default `nexe` will attempt to download a pre-built executable. However, some users may want to customize the way node is built, either by changing the flags, providing a different icon, or different executable details. These options are supported out of the box, and subsequent builds with compatible options will result in instant build times.
Nexe also exposes its patching pipeline to the user. This allows limitless and simple patching of node sources prior to compilation.
Nexe also exposes its patching pipeline to the user. This allows the application of simple patching of node sources prior to compilation.
## Node.js API
@@ -178,7 +178,7 @@ For examples, see the built in patches: [src/patches](src/patches)
#### `SourceFile`
- `contents: string`
- `filename: string`
Any modifications made to `SourceFile#contents` will be maintained in the cache _without_ the need to explicitly write them back out, e.g. using `NexeCompiler#setFileContentsAsync`.
## Security
+1 -6
View File
@@ -35,18 +35,13 @@
"chalk": "^1.1.3",
"download": "^6.2.0",
"globby": "^6.1.0",
"html-loader": "^0.4.5",
"json-loader": "^0.5.4",
"memory-fs": "^0.4.1",
"minimist": "^1.2.0",
"mkdirp": "^0.5.1",
"nigel": "^2.0.2",
"ora": "^1.2.0",
"raw-loader": "^0.5.1",
"rimraf": "^2.6.1",
"tar": "^3.1.3",
"webpack": "^2.3.3",
"xbin-loader": "^1.0.1"
"tar": "^3.1.3"
},
"devDependencies": {
"babel-cli": "^6.24.1",
+8 -46
View File
@@ -1,53 +1,15 @@
import Mfs from 'memory-fs'
import webpack from 'webpack'
import {
FuseBox,
CSSPlugin,
JSONPlugin,
HTMLPlugin } from 'fuse-box'
import { fromCallback } from 'bluebird'
import { resolve, join, relative } from 'path'
const isObject = (x) => typeof x === 'object'
const isString = (x) => x instanceof String || typeof x === 'string'
import * as path from 'path'
export default async function bundle (compiler, next) {
let bundleConfig = compiler.options.bundle
if (!bundleConfig) {
let options = compiler.options.bundle
if (!options) {
return next()
}
const inputFilePath = compiler.options.input || require.resolve(process.cwd())
const mfs = new Mfs()
const path = resolve('nexe')
const filename = 'virtual-bundle.js'
if (isString(bundleConfig)) {
bundleConfig = require(relative(process.cwd(), bundleConfig))
} else if (!isObject(bundleConfig)) {
bundleConfig = {
entry: resolve(inputFilePath),
target: 'node',
module: {
rules: [
{ test: /\.html$/, use: 'html-loader' },
{ test: /\.json$/, use: 'json-loader' },
{ test: /\.node$/, use: 'xbin-loader' },
{ test: /license$|\.md$/, use: 'raw-loader' }
]
}
}
}
bundleConfig.output = { path, filename }
const bundler = webpack(bundleConfig)
bundler.outputFileSystem = mfs
const stats = await fromCallback(cb => bundler.run(cb))
if (stats.hasErrors()) {
// compiler.log.error(stats.toString())
return null
}
compiler.input = mfs.readFileSync(
join(bundleConfig.output.path, bundleConfig.output.filename)
)
return next()
}
+91
View File
@@ -0,0 +1,91 @@
import * as fs from 'fs'
import * as path from 'path'
import * as child from 'child_process'
function findNativeModulePath (filePath, bindingsArg) {
const dirname = path.dirname(filePath)
const tempFile = Math.random() * 100 + '.js'
const tempFilePath = path.join(dirname, tempFile)
fs.writeFileSync(tempFilePath, `
var bindings = require('bindings');
var Module = require('module');
var originalRequire = Module.prototype.require;
Module.prototype.require = function(path) {
const mod = originalRequire.apply(this, arguments);
process.stdout.write(path)
return mod
};
bindings('${bindingsArg}')
`)
//using exec because it can be done syncronously
const nativeFileName = child.execSync('node ' + tempFile, { cwd: dirname }).toString()
fs.unlinkSync(tempFilePath)
const relativePath = './' + path.relative(dirname, nativeFileName).replace(/\\/g, '/')
return relativePath
}
/**
* Traverse all nodes in a file and evaluate usages of the bindings module
*/
export class BindingsRewrite {
constructor () {
this.bindingsIdNodes = []
this.nativeModulePaths = []
this.rewrite = false
}
isRequireBindings (node) {
return node.callee.name === 'require' && node.arguments[0].value === 'bindings'
}
onNode (absolutePath, node, parent) {
if (node.type === 'CallExpression') {
if (this.isRequireBindings(node) && parent.type === 'VariableDeclarator') {
/**
* const loadBindings = require('bindings');
* -> const loadBindings = String('');
*/
this.bindingsIdNodes.push(parent.id)
node.callee.name = 'String'
node.arguments[0].value = ''
this.rewrite = true
return
}
if (this.isRequireBindings(node) && parent.type === 'CallExpression') {
/**
*const bindings = require('bindings')('native-module')....
* -> const bindings = require('./path/to/native/module.node').....
*/
const bindingsArgNode = parent.arguments[0]
if (bindingsArgNode.type === 'Literal') {
parent.callee = { type: 'Identifier', name: 'require' }
bindingsArgNode.value = findNativeModulePath(absolutePath, bindingsArgNode.value)
this.nativeModulePaths.push(bindingsArgNode.value)
this.rewrite = true
return
}
}
const bindingsInvocationIdx = this.bindingsIdNodes.findIndex(x => node.callee.name === x.name)
if (this.bindingsIdNodes[bindingsInvocationIdx]) {
/**
* const bindings = loadBindings('native-module')
* -> const bindings = require('./path/to/native/module.node')
*/
const bindingsIdNode = this.bindingsIdNodes[bindingsInvocationIdx]
const bindingsArgNode = node.arguments[0]
this.bindingsIdNodes.splice(bindingsInvocationIdx, 1)
if (bindingsArgNode.type === 'Literal') {
node.callee.name = 'require'
bindingsArgNode.value = findNativeModulePath(absolutePath, bindingsArgNode.value)
this.nativeModulePaths.push(bindingsArgNode.value)
this.rewrite = true
return
}
}
}
}
}
+30
View File
@@ -0,0 +1,30 @@
import { BindingsRewrite } from './bindings-rewrite'
export class NativeModulePlugin {
constructor (...moduleNames) {
this.test = new RegExp(`node_modules\/(${moduleNames.join('|')}).*\.js`)
this.limit2project = false
}
transform (file) {
const bindingsRewrite = new BindingsRewrite()
file.loadContents()
file.makeAnalysis(null, {
plugins: [{
onNode(file, node, parent) {
bindingsRewrite.onNode(file.info.absPath, node, parent)
},
onEnd(file) {
if (bindingsRewrite.rewrite) {
const index = file.analysis.dependencies.indexOf('bindings')
if (~index) {
file.analysis.dependencies.splice(index, 1)
}
file.analysis.dependencies.push(...bindingsRewrite.nativeModulePaths)
file.analysis.requiresRegeneration = true
}
}
}]
})
}
}