mirror of
https://github.com/status-im/metro.git
synced 2025-02-27 10:10:40 +00:00
Initial commit
This commit is contained in:
commit
ddaccf41d8
45
blacklist.js
Normal file
45
blacklist.js
Normal file
@ -0,0 +1,45 @@
|
||||
/**
|
||||
* Copyright 2004-present Facebook. All Rights Reserved.
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
// Don't forget to everything listed here to `testConfig.json`
|
||||
// modulePathIgnorePatterns.
|
||||
var sharedBlacklist = [
|
||||
'node_modules/JSAppServer',
|
||||
'packager/react-packager',
|
||||
'node_modules/parse/node_modules/xmlhttprequest/lib/XMLHttpRequest.js',
|
||||
'node_modules/react-tools/src/utils/ImmutableObject.js',
|
||||
'node_modules/react-tools/src/core/ReactInstanceHandles.js',
|
||||
'node_modules/react-tools/src/event/EventPropagators.js',
|
||||
'node_modules/jest-cli',
|
||||
];
|
||||
|
||||
var webBlacklist = [
|
||||
'.ios.js'
|
||||
];
|
||||
|
||||
var iosBlacklist = [
|
||||
'node_modules/react-tools/src/browser/ui/React.js',
|
||||
'node_modules/react-tools/src/browser/eventPlugins/ResponderEventPlugin.js',
|
||||
'node_modules/react-tools/src/browser/ReactTextComponent.js',
|
||||
// 'node_modules/react-tools/src/vendor/core/ExecutionEnvironment.js',
|
||||
'.web.js',
|
||||
'.android.js',
|
||||
];
|
||||
|
||||
function escapeRegExp(str) {
|
||||
return str.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, '\\$&');
|
||||
}
|
||||
|
||||
function blacklist(isWeb) {
|
||||
return new RegExp('(' +
|
||||
sharedBlacklist
|
||||
.concat(isWeb ? webBlacklist : iosBlacklist)
|
||||
.map(escapeRegExp)
|
||||
.join('|') +
|
||||
')$'
|
||||
);
|
||||
}
|
||||
|
||||
module.exports = blacklist;
|
40
launchEditor.js
Normal file
40
launchEditor.js
Normal file
@ -0,0 +1,40 @@
|
||||
/**
|
||||
* Copyright 2004-present Facebook. All Rights Reserved.
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
var fs = require('fs');
|
||||
var spawn = require('child_process').spawn;
|
||||
|
||||
var firstLaunch = true;
|
||||
|
||||
function guessEditor() {
|
||||
if (firstLaunch) {
|
||||
console.log('When you see Red Box with stack trace, you can click any ' +
|
||||
'stack frame to jump to the source file. The packager will launch your ' +
|
||||
'editor of choice. It will first look at REACT_EDITOR environment ' +
|
||||
'variable, then at EDITOR. To set it up, you can add something like ' +
|
||||
'REACT_EDITOR=atom to your .bashrc.');
|
||||
firstLaunch = false;
|
||||
}
|
||||
|
||||
var editor = process.env.REACT_EDITOR || process.env.EDITOR || 'subl';
|
||||
return editor;
|
||||
}
|
||||
|
||||
function launchEditor(fileName, lineNumber) {
|
||||
if (!fs.existsSync(fileName)) {
|
||||
return;
|
||||
}
|
||||
|
||||
var argument = fileName;
|
||||
if (lineNumber) {
|
||||
argument += ':' + lineNumber;
|
||||
}
|
||||
|
||||
var editor = guessEditor();
|
||||
console.log('Opening ' + fileName + ' with ' + editor);
|
||||
spawn(editor, [argument], { stdio: ['pipe', 'pipe', process.stderr] });
|
||||
}
|
||||
|
||||
module.exports = launchEditor;
|
10
launchPackager.command
Executable file
10
launchPackager.command
Executable file
@ -0,0 +1,10 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Set terminal title
|
||||
echo -en "\033]0;React Packager\a"
|
||||
clear
|
||||
|
||||
THIS_DIR=$(dirname "$0")
|
||||
$THIS_DIR/packager.sh
|
||||
echo "Process terminated. Press <enter> to close the window"
|
||||
read
|
96
packager.js
Normal file
96
packager.js
Normal file
@ -0,0 +1,96 @@
|
||||
/**
|
||||
* Copyright 2004-present Facebook. All Rights Reserved.
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
var ReactPackager = require('./react-packager');
|
||||
var blacklist = require('./blacklist.js');
|
||||
var connect = require('connect');
|
||||
var http = require('http');
|
||||
var launchEditor = require('./launchEditor.js');
|
||||
var parseCommandLine = require('./parseCommandLine.js');
|
||||
var path = require('path');
|
||||
|
||||
var options = parseCommandLine([{
|
||||
command: 'port',
|
||||
default: 8081,
|
||||
}]);
|
||||
|
||||
if (!options.projectRoot) {
|
||||
options.projectRoot = path.resolve(__dirname, '..');
|
||||
}
|
||||
|
||||
console.log('\n' +
|
||||
' ===============================================================\n' +
|
||||
' | Running packager on port ' + options.port + '. \n' +
|
||||
' | Keep this packager running while developing on any JS \n' +
|
||||
' | projects. Feel free to close this tab and run your own \n' +
|
||||
' | packager instance if you prefer. \n' +
|
||||
' | \n' +
|
||||
' | https://github.com/facebook/react-native \n' +
|
||||
' | \n' +
|
||||
' ===============================================================\n'
|
||||
);
|
||||
|
||||
process.on('uncaughtException', function(e) {
|
||||
console.error(e);
|
||||
console.error(e.stack);
|
||||
console.error('\n >>> ERROR: could not create packager - please shut down ' +
|
||||
'any existing instances that are already running.\n\n');
|
||||
});
|
||||
|
||||
runServer(options, function() {
|
||||
console.log('\nReact packager ready.\n');
|
||||
});
|
||||
|
||||
function loadRawBody(req, res, next) {
|
||||
req.rawBody = '';
|
||||
req.setEncoding('utf8');
|
||||
|
||||
req.on('data', function(chunk) {
|
||||
req.rawBody += chunk;
|
||||
});
|
||||
|
||||
req.on('end', function() {
|
||||
next();
|
||||
});
|
||||
}
|
||||
|
||||
function openStackFrameInEditor(req, res, next) {
|
||||
if (req.url === '/open-stack-frame') {
|
||||
var frame = JSON.parse(req.rawBody);
|
||||
launchEditor(frame.file, frame.lineNumber);
|
||||
res.end('OK');
|
||||
} else {
|
||||
next();
|
||||
}
|
||||
}
|
||||
|
||||
function getAppMiddleware(options) {
|
||||
return ReactPackager.catalystMiddleware({
|
||||
dev: true,
|
||||
projectRoot: options.projectRoot,
|
||||
blacklistRE: blacklist(false),
|
||||
cacheVersion: '2',
|
||||
polyfillModuleNames: [
|
||||
path.resolve(__dirname, 'polyfill/console.js'),
|
||||
path.resolve(__dirname, 'polyfill/error-guard.js'),
|
||||
]
|
||||
});
|
||||
}
|
||||
|
||||
function runServer(
|
||||
options, /* {string projectRoot, bool web, bool dev} */
|
||||
readyCallback
|
||||
) {
|
||||
var app = connect()
|
||||
.use(loadRawBody)
|
||||
.use(openStackFrameInEditor)
|
||||
.use(getAppMiddleware(options))
|
||||
.use(connect.static(options.projectRoot))
|
||||
.use(connect.logger())
|
||||
.use(connect.compress())
|
||||
.use(connect.errorHandler());
|
||||
|
||||
return http.createServer(app).listen(options.port, readyCallback);
|
||||
}
|
6
packager.sh
Executable file
6
packager.sh
Executable file
@ -0,0 +1,6 @@
|
||||
#!/bin/bash
|
||||
|
||||
ulimit -n 4096
|
||||
|
||||
THIS_DIR=$(dirname "$0")
|
||||
node $THIS_DIR/packager.js
|
52
parseCommandLine.js
Normal file
52
parseCommandLine.js
Normal file
@ -0,0 +1,52 @@
|
||||
/**
|
||||
* Copyright 2004-present Facebook. All Rights Reserved.
|
||||
*
|
||||
* Wrapper on-top of `optimist` in order to properly support boolean flags
|
||||
* and have a slightly less akward API.
|
||||
*
|
||||
* Usage example:
|
||||
* var argv = parseCommandLine([{
|
||||
* command: 'web',
|
||||
* description: 'Run in a web browser instead of iOS',
|
||||
* default: true
|
||||
* }])
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
var optimist = require('optimist');
|
||||
|
||||
function parseCommandLine(config) {
|
||||
// optimist default API requires you to write the command name three time
|
||||
// This is a small wrapper to accept an object instead
|
||||
for (var i = 0; i < config.length; ++i) {
|
||||
optimist
|
||||
.boolean(config[i].command)
|
||||
.default(config[i].command, config[i].default)
|
||||
.describe(config[i].command, config[i].description);
|
||||
}
|
||||
var argv = optimist.argv;
|
||||
|
||||
// optimist doesn't have support for --dev=false, instead it returns 'false'
|
||||
for (var i = 0; i < config.length; ++i) {
|
||||
var command = config[i].command;
|
||||
if (argv[command] === undefined) {
|
||||
argv[command] = config[i].default;
|
||||
}
|
||||
if (argv[command] === 'true') {
|
||||
argv[command] = true;
|
||||
}
|
||||
if (argv[command] === 'false') {
|
||||
argv[command] = false;
|
||||
}
|
||||
}
|
||||
|
||||
// Show --help
|
||||
if (argv.help || argv.h) {
|
||||
optimist.showHelp();
|
||||
process.exit();
|
||||
}
|
||||
|
||||
return argv;
|
||||
}
|
||||
|
||||
module.exports = parseCommandLine;
|
141
polyfill/console.js
Normal file
141
polyfill/console.js
Normal file
@ -0,0 +1,141 @@
|
||||
/**
|
||||
* Copyright 2013 Facebook, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
* This pipes all of our console logging functions to native logging so that
|
||||
* JavaScript errors in required modules show up in Xcode via NSLog.
|
||||
*
|
||||
* @provides console
|
||||
* @polyfill
|
||||
*/
|
||||
|
||||
(function(global) {
|
||||
|
||||
var OBJECT_COLUMN_NAME = '(index)';
|
||||
|
||||
function setupConsole(global) {
|
||||
|
||||
if (!global.nativeLoggingHook) {
|
||||
return;
|
||||
}
|
||||
|
||||
function doNativeLog() {
|
||||
var str = Array.prototype.map.call(arguments, function(arg) {
|
||||
if (arg == null) {
|
||||
return arg === null ? 'null' : 'undefined';
|
||||
} else if (typeof arg === 'string') {
|
||||
return '"' + arg + '"';
|
||||
} else {
|
||||
// Perform a try catch, just in case the object has a circular
|
||||
// reference or stringify throws for some other reason.
|
||||
try {
|
||||
return JSON.stringify(arg);
|
||||
} catch (e) {
|
||||
if (typeof arg.toString === 'function') {
|
||||
try {
|
||||
return arg.toString();
|
||||
} catch (e) {
|
||||
return 'unknown';
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}).join(', ');
|
||||
global.nativeLoggingHook(str);
|
||||
};
|
||||
|
||||
var repeat = function(element, n) {
|
||||
return Array.apply(null, Array(n)).map(function() { return element; });
|
||||
};
|
||||
|
||||
function consoleTablePolyfill(rows) {
|
||||
// convert object -> array
|
||||
if (!Array.isArray(rows)) {
|
||||
var data = rows;
|
||||
rows = [];
|
||||
for (var key in data) {
|
||||
if (data.hasOwnProperty(key)) {
|
||||
var row = data[key];
|
||||
row[OBJECT_COLUMN_NAME] = key;
|
||||
rows.push(row);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (rows.length === 0) {
|
||||
global.nativeLoggingHook('');
|
||||
return;
|
||||
}
|
||||
|
||||
var columns = Object.keys(rows[0]).sort();
|
||||
var stringRows = [];
|
||||
var columnWidths = [];
|
||||
|
||||
// Convert each cell to a string. Also
|
||||
// figure out max cell width for each column
|
||||
columns.forEach(function(k, i) {
|
||||
columnWidths[i] = k.length;
|
||||
for (var j = 0; j < rows.length; j++) {
|
||||
var cellStr = rows[j][k].toString();
|
||||
stringRows[j] = stringRows[j] || [];
|
||||
stringRows[j][i] = cellStr;
|
||||
columnWidths[i] = Math.max(columnWidths[i], cellStr.length);
|
||||
}
|
||||
});
|
||||
|
||||
// Join all elements in the row into a single string with | separators
|
||||
// (appends extra spaces to each cell to make separators | alligned)
|
||||
var joinRow = function(row, space) {
|
||||
var cells = row.map(function(cell, i) {
|
||||
var extraSpaces = repeat(' ', columnWidths[i] - cell.length).join('');
|
||||
return cell + extraSpaces;
|
||||
});
|
||||
space = space || ' ';
|
||||
return cells.join(space + '|' + space);
|
||||
};
|
||||
|
||||
var separators = columnWidths.map(function(columnWidth) {
|
||||
return repeat('-', columnWidth).join('');
|
||||
});
|
||||
var separatorRow = joinRow(separators, '-');
|
||||
var header = joinRow(columns);
|
||||
var table = [header, separatorRow];
|
||||
|
||||
for (var i = 0; i < rows.length; i++) {
|
||||
table.push(joinRow(stringRows[i]));
|
||||
}
|
||||
|
||||
// Notice extra empty line at the beginning.
|
||||
// Native logging hook adds "RCTLog >" at the front of every
|
||||
// logged string, which would shift the header and screw up
|
||||
// the table
|
||||
global.nativeLoggingHook('\n' + table.join('\n'));
|
||||
};
|
||||
|
||||
global.console = {
|
||||
error: doNativeLog,
|
||||
info: doNativeLog,
|
||||
log: doNativeLog,
|
||||
warn: doNativeLog,
|
||||
table: consoleTablePolyfill
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
if (typeof module !== 'undefined') {
|
||||
module.exports = setupConsole;
|
||||
} else {
|
||||
setupConsole(global);
|
||||
}
|
||||
|
||||
})(this);
|
82
polyfill/error-guard.js
Normal file
82
polyfill/error-guard.js
Normal file
@ -0,0 +1,82 @@
|
||||
|
||||
/**
|
||||
* The particular require runtime that we are using looks for a global
|
||||
* `ErrorUtils` object and if it exists, then it requires modules with the
|
||||
* error handler specified via ErrorUtils.setGlobalHandler by calling the
|
||||
* require function with applyWithGuard. Since the require module is loaded
|
||||
* before any of the modules, this ErrorUtils must be defined (and the handler
|
||||
* set) globally before requiring anything.
|
||||
*/
|
||||
/* eslint global-strict:0 */
|
||||
(function(global) {
|
||||
var ErrorUtils = {
|
||||
_inGuard: 0,
|
||||
_globalHandler: null,
|
||||
setGlobalHandler: function(fun) {
|
||||
ErrorUtils._globalHandler = fun;
|
||||
},
|
||||
reportError: function(error) {
|
||||
Error._globalHandler && ErrorUtils._globalHandler(error);
|
||||
},
|
||||
applyWithGuard: function(fun, context, args) {
|
||||
try {
|
||||
ErrorUtils._inGuard++;
|
||||
return fun.apply(context, args);
|
||||
} catch (e) {
|
||||
ErrorUtils._globalHandler && ErrorUtils._globalHandler(e);
|
||||
} finally {
|
||||
ErrorUtils._inGuard--;
|
||||
}
|
||||
},
|
||||
applyWithGuardIfNeeded: function(fun, context, args) {
|
||||
if (ErrorUtils.inGuard()) {
|
||||
return fun.apply(context, args);
|
||||
} else {
|
||||
ErrorUtils.applyWithGuard(fun, context, args);
|
||||
}
|
||||
},
|
||||
inGuard: function() {
|
||||
return ErrorUtils._inGuard;
|
||||
},
|
||||
guard: function(fun, name, context) {
|
||||
if (typeof fun !== "function") {
|
||||
console.warn('A function must be passed to ErrorUtils.guard, got ', fun);
|
||||
return null;
|
||||
}
|
||||
name = name || fun.name || '<generated guard>';
|
||||
function guarded() {
|
||||
return (
|
||||
ErrorUtils.applyWithGuard(
|
||||
fun,
|
||||
context || this,
|
||||
arguments,
|
||||
null,
|
||||
name
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
return guarded;
|
||||
}
|
||||
};
|
||||
global.ErrorUtils = ErrorUtils;
|
||||
|
||||
/**
|
||||
* This is the error handler that is called when we encounter an exception
|
||||
* when loading a module.
|
||||
*/
|
||||
function setupErrorGuard() {
|
||||
var onError = function(e) {
|
||||
global.console.error(
|
||||
'Error: ' +
|
||||
'\n stack: ' + e.stack +
|
||||
'\n line: ' + e.line +
|
||||
'\n message: ' + e.message,
|
||||
e
|
||||
);
|
||||
};
|
||||
global.ErrorUtils.setGlobalHandler(onError);
|
||||
}
|
||||
|
||||
setupErrorGuard();
|
||||
})(this);
|
86
react-packager/.jshintrc
Normal file
86
react-packager/.jshintrc
Normal file
@ -0,0 +1,86 @@
|
||||
{
|
||||
"-W093": true,
|
||||
"asi": false,
|
||||
"bitwise": true,
|
||||
"boss": false,
|
||||
"browser": false,
|
||||
"camelcase": true,
|
||||
"couch": false,
|
||||
"curly": true,
|
||||
"debug": false,
|
||||
"devel": true,
|
||||
"dojo": false,
|
||||
"eqeqeq": true,
|
||||
"eqnull": true,
|
||||
"esnext": true,
|
||||
"evil": false,
|
||||
"expr": true,
|
||||
"forin": false,
|
||||
"freeze": true,
|
||||
"funcscope": true,
|
||||
"gcl": false,
|
||||
"globals": {
|
||||
"Promise": true,
|
||||
"React": true,
|
||||
"XMLHttpRequest": true,
|
||||
"document": true,
|
||||
"location": true,
|
||||
"window": true
|
||||
},
|
||||
"globalstrict": true,
|
||||
"immed": false,
|
||||
"indent": 2,
|
||||
"iterator": false,
|
||||
"jquery": false,
|
||||
"lastsemic": false,
|
||||
"latedef": false,
|
||||
"laxbreak": true,
|
||||
"laxcomma": false,
|
||||
"loopfunc": false,
|
||||
"maxcomplexity": false,
|
||||
"maxdepth": false,
|
||||
"maxerr": 50,
|
||||
"maxlen": 80,
|
||||
"maxparams": false,
|
||||
"maxstatements": false,
|
||||
"mootools": false,
|
||||
"moz": false,
|
||||
"multistr": false,
|
||||
"newcap": true,
|
||||
"noarg": true,
|
||||
"node": true,
|
||||
"noempty": false,
|
||||
"nonbsp": true,
|
||||
"nonew": true,
|
||||
"nonstandard": false,
|
||||
"notypeof": false,
|
||||
"noyield": false,
|
||||
"phantom": false,
|
||||
"plusplus": false,
|
||||
"predef": [
|
||||
"afterEach",
|
||||
"beforeEach",
|
||||
"describe",
|
||||
"expect",
|
||||
"it",
|
||||
"jest",
|
||||
"pit"
|
||||
],
|
||||
"proto": false,
|
||||
"prototypejs": false,
|
||||
"quotmark": true,
|
||||
"rhino": false,
|
||||
"scripturl": false,
|
||||
"shadow": false,
|
||||
"smarttabs": false,
|
||||
"strict": false,
|
||||
"sub": false,
|
||||
"supernew": false,
|
||||
"trailing": true,
|
||||
"undef": true,
|
||||
"unused": true,
|
||||
"validthis": false,
|
||||
"worker": false,
|
||||
"wsh": false,
|
||||
"yui": false
|
||||
}
|
8
react-packager/.npmignore
Normal file
8
react-packager/.npmignore
Normal file
@ -0,0 +1,8 @@
|
||||
*~
|
||||
*.swm
|
||||
*.swn
|
||||
*.swp
|
||||
*.DS_STORE
|
||||
npm-debug.log
|
||||
.cache
|
||||
node_modules
|
5
react-packager/__mocks__/debug.js
vendored
Normal file
5
react-packager/__mocks__/debug.js
vendored
Normal file
@ -0,0 +1,5 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = function() {
|
||||
return function() {};
|
||||
};
|
26
react-packager/__mocks__/net.js
vendored
Normal file
26
react-packager/__mocks__/net.js
vendored
Normal file
@ -0,0 +1,26 @@
|
||||
var EventEmitter = require('events').EventEmitter;
|
||||
var servers = {};
|
||||
exports.createServer = function(listener) {
|
||||
var server = {
|
||||
_listener: listener,
|
||||
|
||||
socket: new EventEmitter(),
|
||||
|
||||
listen: function(path) {
|
||||
listener(this.socket);
|
||||
servers[path] = this;
|
||||
}
|
||||
};
|
||||
|
||||
server.socket.setEncoding = function() {};
|
||||
server.socket.write = function(data) {
|
||||
this.emit('data', data);
|
||||
};
|
||||
|
||||
return server;
|
||||
};
|
||||
|
||||
exports.connect = function(options) {
|
||||
var server = servers[options.path || options.port];
|
||||
return server.socket;
|
||||
};
|
5
react-packager/example_project/bar.js
vendored
Normal file
5
react-packager/example_project/bar.js
vendored
Normal file
@ -0,0 +1,5 @@
|
||||
/**
|
||||
* @providesModule bar
|
||||
*/
|
||||
|
||||
module.exports = setInterval;
|
10
react-packager/example_project/config.json
Normal file
10
react-packager/example_project/config.json
Normal file
@ -0,0 +1,10 @@
|
||||
{
|
||||
"port": 3000,
|
||||
"devPort": 3001,
|
||||
"publicDir": "./public",
|
||||
"rootPath": "../example_project",
|
||||
"moduleOptions": {
|
||||
"format": "haste",
|
||||
"main": "index.js"
|
||||
}
|
||||
}
|
23
react-packager/example_project/foo/foo.js
vendored
Normal file
23
react-packager/example_project/foo/foo.js
vendored
Normal file
@ -0,0 +1,23 @@
|
||||
/**
|
||||
* @providesModule foo
|
||||
*/
|
||||
|
||||
|
||||
var bar = require('bar');
|
||||
|
||||
class Logger {
|
||||
log() {
|
||||
console.log('youll have to change me lol');
|
||||
}
|
||||
}
|
||||
|
||||
class SecretLogger extends Logger {
|
||||
log(secret) {
|
||||
console.log('logging ', secret);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = (secret) => {
|
||||
if (secret !== 'secret') throw new Error('wrong secret');
|
||||
bar(new SecretLogger().log.bind(SecretLogger, secret), 400);
|
||||
};
|
10
react-packager/example_project/index.js
vendored
Normal file
10
react-packager/example_project/index.js
vendored
Normal file
@ -0,0 +1,10 @@
|
||||
/**
|
||||
* @providesModule index
|
||||
* @jsx React.DOM
|
||||
*/
|
||||
|
||||
require('main');
|
||||
require('code');
|
||||
|
||||
var foo = require('foo');
|
||||
foo('secret');
|
46
react-packager/example_project/js/Channel.js
vendored
Normal file
46
react-packager/example_project/js/Channel.js
vendored
Normal file
@ -0,0 +1,46 @@
|
||||
/**
|
||||
* @providesModule Channel
|
||||
*/
|
||||
|
||||
var XHR = require('XHR');
|
||||
|
||||
/**
|
||||
* Client implementation of a server-push channel.
|
||||
*
|
||||
* @see Channel.js for full documentation
|
||||
*/
|
||||
var channel = null, at = null, delay = 0;
|
||||
var Channel = {};
|
||||
|
||||
Channel.connect = function() {
|
||||
var url = '/pull';
|
||||
if (channel) {
|
||||
url += '?channel=' + channel + '&at=' + at;
|
||||
}
|
||||
XHR.get(url, function(err, xhr) {
|
||||
if (err) {
|
||||
delay = Math.min(Math.max(1000, delay * 2), 30000);
|
||||
} else {
|
||||
var res = xhr.responseText;
|
||||
res = JSON.parse(res);
|
||||
|
||||
delay = 0;
|
||||
|
||||
// Cache channel state
|
||||
channel = res.channel;
|
||||
at = res.at;
|
||||
|
||||
var messages = res.messages;
|
||||
messages.forEach(function(message) {
|
||||
var ev = document.createEvent('CustomEvent');
|
||||
ev.initCustomEvent(message.event, true, true, message.detail);
|
||||
window.dispatchEvent(ev);
|
||||
});
|
||||
}
|
||||
|
||||
// Reconnect
|
||||
setTimeout(Channel.connect, delay);
|
||||
});
|
||||
};
|
||||
|
||||
module.exports = Channel;
|
22
react-packager/example_project/js/XHR.js
vendored
Normal file
22
react-packager/example_project/js/XHR.js
vendored
Normal file
@ -0,0 +1,22 @@
|
||||
/**
|
||||
* @providesModule XHR
|
||||
*/
|
||||
|
||||
function request(method, url, callback) {
|
||||
var xhr = new XMLHttpRequest();
|
||||
xhr.open(method, url);
|
||||
xhr.onreadystatechange = function() {
|
||||
if (xhr.readyState === 4) {
|
||||
if (xhr.status === 200) {
|
||||
callback(null, xhr);
|
||||
} else {
|
||||
callback(new Error('status = ' + xhr.status, xhr));
|
||||
}
|
||||
}
|
||||
};
|
||||
xhr.send();
|
||||
}
|
||||
|
||||
exports.get = function(url, callback) {
|
||||
request('GET', url, callback);
|
||||
};
|
51
react-packager/example_project/js/code.js
vendored
Normal file
51
react-packager/example_project/js/code.js
vendored
Normal file
@ -0,0 +1,51 @@
|
||||
/**
|
||||
* @providesModule code
|
||||
*/
|
||||
var XHR = require('XHR');
|
||||
|
||||
var $ = function(sel) {return document.querySelector(sel);};
|
||||
|
||||
function getListItems(files) {
|
||||
var items = [];
|
||||
files.forEach(function(file) {
|
||||
var displayName = file.name + (file.type == 1 ? '/' : '');
|
||||
items.push(
|
||||
React.DOM.li({
|
||||
className: 'type' + file.type,
|
||||
key: file.ino
|
||||
}, displayName)
|
||||
);
|
||||
if (file.type === 1) {
|
||||
items.push(getListItems(file.nodes));
|
||||
}
|
||||
});
|
||||
|
||||
return React.DOM.ol(null, items);
|
||||
}
|
||||
|
||||
var FileList = React.createClass({
|
||||
getInitialState: function() {
|
||||
return {files: []};
|
||||
},
|
||||
|
||||
componentDidMount: function() {
|
||||
XHR.get(
|
||||
this.props.source,
|
||||
function(err, xhr) {
|
||||
if (err) {throw err;}
|
||||
|
||||
var files = JSON.parse(xhr.responseText);
|
||||
this.setState({files: files});
|
||||
}.bind(this)
|
||||
);
|
||||
},
|
||||
|
||||
render: function() {
|
||||
return getListItems(this.state.files);
|
||||
}
|
||||
});
|
||||
|
||||
window.addEventListener('load', function() {
|
||||
React.render(React.createElement(FileList, {source: '/files'}),
|
||||
$('#code'));
|
||||
});
|
57
react-packager/example_project/js/main.js
vendored
Normal file
57
react-packager/example_project/js/main.js
vendored
Normal file
@ -0,0 +1,57 @@
|
||||
/**
|
||||
* @providesModule main
|
||||
*/
|
||||
var Channel = require('Channel');
|
||||
|
||||
function toArray(arr) {return Array.prototype.slice.apply(arr);}
|
||||
function $(sel) {return document.querySelector(sel);}
|
||||
function $$(sel) {return toArray(document.querySelectorAll(sel));}
|
||||
|
||||
window.addEventListener('load', function() {
|
||||
function channelLog() {
|
||||
var args = Array.prototype.slice.apply(arguments);
|
||||
var ts = new Date();
|
||||
var el = document.createElement('li');
|
||||
args.unshift(ts.getHours() + ':' +
|
||||
('0' + ts.getMinutes()).substr(0,2) + ':' +
|
||||
('0' + ts.getSeconds()).substr(0,2));
|
||||
el.className = 'console-entry';
|
||||
el.innerHTML = args.join(' ');
|
||||
$('#console').appendChild(el);
|
||||
el.scrollIntoView();
|
||||
}
|
||||
|
||||
global.addEventListener('ChannelInit', function(event) {
|
||||
$('#console').innerHTML = '';
|
||||
channelLog(event.type);
|
||||
});
|
||||
|
||||
global.addEventListener('ChannelLog', function(event) {
|
||||
channelLog.apply(null, event.detail);
|
||||
});
|
||||
|
||||
// Tab pane support
|
||||
function showTab(paneId) {
|
||||
paneId = paneId.replace(/\W/g, '');
|
||||
if (paneId) {
|
||||
$$('#nav-panes > div').forEach(function(pane) {
|
||||
pane.classList.toggle('active', pane.id === paneId);
|
||||
});
|
||||
$$('#nav-tabs li').forEach(function(tab) {
|
||||
tab.classList.toggle('active',
|
||||
tab.getAttribute('data-pane') === paneId);
|
||||
});
|
||||
global.history.replaceState(null, null, '#' + paneId);
|
||||
}
|
||||
}
|
||||
|
||||
$('#nav-tabs').onclick = function(e) {
|
||||
showTab(e.target.getAttribute('data-pane'));
|
||||
};
|
||||
|
||||
// Show current pane
|
||||
showTab(location.hash);
|
||||
|
||||
// Connect to server-push channel
|
||||
Channel.connect();
|
||||
});
|
94
react-packager/example_project/public/css/index.css
Normal file
94
react-packager/example_project/public/css/index.css
Normal file
@ -0,0 +1,94 @@
|
||||
html {
|
||||
font-family: sans-serif;
|
||||
}
|
||||
body {
|
||||
margin-right: 200px
|
||||
}
|
||||
|
||||
#nav-tabs {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
position: absolute;
|
||||
top: 0px;
|
||||
left: 0px;
|
||||
right: 0px;
|
||||
background-color: #eee;
|
||||
border-bottom: solid 1px black;
|
||||
font-size: 10pt;
|
||||
font-weight: bold;
|
||||
vertical-align: bottom;
|
||||
line-height: 20px;
|
||||
height: 29px;
|
||||
}
|
||||
#nav-tabs li {
|
||||
padding: 0 10px;
|
||||
margin: 0;
|
||||
border-bottom-width: 0;
|
||||
display:inline-block;
|
||||
cursor: pointer;
|
||||
line-height: 29px;
|
||||
}
|
||||
#nav-tabs li:first-child {
|
||||
color: #666;
|
||||
}
|
||||
#nav-tabs li.active {
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
#nav-panes {
|
||||
position: absolute;
|
||||
top: 30px;
|
||||
left: 0px;
|
||||
right: 0px;
|
||||
bottom: 0px;
|
||||
scroll: auto;
|
||||
overflow: auto;
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
#nav-panes .pane {
|
||||
display: none;
|
||||
}
|
||||
#nav-panes .active {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.pane {
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
#console {
|
||||
padding-left: 5px;
|
||||
}
|
||||
#console li {
|
||||
font-size: 10pt;
|
||||
font-family: monospace;
|
||||
white-space: nowrap;
|
||||
margin: 0;
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
#code > ol {
|
||||
font-size: 10pt;
|
||||
font-family: monospace;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
cursor: pointer;
|
||||
}
|
||||
#code ol ol {
|
||||
margin-left: 1em;
|
||||
padding-left: 1em;
|
||||
border-left: dashed 1px #ddd;
|
||||
}
|
||||
#code li {
|
||||
color: #000;
|
||||
font-weight: normal;
|
||||
list-style: none;
|
||||
line-height: 1.2em;
|
||||
}
|
||||
#code .type1 {
|
||||
color: #009;
|
||||
}
|
||||
#code .type2 {
|
||||
color: #909;
|
||||
}
|
30
react-packager/example_project/public/index.html
Normal file
30
react-packager/example_project/public/index.html
Normal file
@ -0,0 +1,30 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<link rel="stylesheet" type="text/css" href="css/index.css">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<ul id="nav-tabs">
|
||||
<li data-pane="main">JS App Server</li>
|
||||
<li data-pane="console">Console</li>
|
||||
<li data-pane="code">Code</li>
|
||||
<li data-pane="activity">Activity</li>
|
||||
</ul>
|
||||
|
||||
<div id="nav-panes">
|
||||
<div id="main" class="pane">
|
||||
<p>Welcome to the react packager project.</p>
|
||||
<a href="http://localhost:3000/resource?path=index.js">Get example index.js package</a>
|
||||
</div>
|
||||
|
||||
<div id="console" class="pane"></div>
|
||||
<div id="code" class="pane"></div>
|
||||
<div id="activity" class="pane">react packager tasks in progress</div>
|
||||
</div>
|
||||
|
||||
<script src="http://fb.me/react-0.12.0.js"></script>
|
||||
<script src="http://localhost:3000/resource?path=index.js"></script>
|
||||
<script type="text/javascript">require('index');</script>
|
||||
</body>
|
||||
</html>
|
100
react-packager/index.js
vendored
Normal file
100
react-packager/index.js
vendored
Normal file
@ -0,0 +1,100 @@
|
||||
var Packager = require('./src/Packager');
|
||||
var Activity = require('./src/Activity');
|
||||
var url = require('url');
|
||||
|
||||
exports.buildPackageFromUrl = function(options, reqUrl) {
|
||||
Activity.disable();
|
||||
var packager = createPackager(options);
|
||||
var params = getOptionsFromPath(url.parse(reqUrl).pathname);
|
||||
return packager.package(
|
||||
params.main,
|
||||
params.runModule,
|
||||
params.sourceMapUrl
|
||||
).then(function(p) {
|
||||
packager.kill();
|
||||
return p;
|
||||
});
|
||||
};
|
||||
|
||||
exports.catalystMiddleware = function(options) {
|
||||
var packager = createPackager(options);
|
||||
|
||||
return function(req, res, next) {
|
||||
var options;
|
||||
if (req.url.match(/\.bundle$/)) {
|
||||
options = getOptionsFromPath(url.parse(req.url).pathname);
|
||||
packager.package(
|
||||
options.main,
|
||||
options.runModule,
|
||||
options.sourceMapUrl
|
||||
).then(
|
||||
function(package) {
|
||||
res.end(package.getSource());
|
||||
},
|
||||
function(error) {
|
||||
handleError(res, error);
|
||||
}
|
||||
).done();
|
||||
} else if (req.url.match(/\.map$/)) {
|
||||
options = getOptionsFromPath(url.parse(req.url).pathname);
|
||||
packager.package(
|
||||
options.main,
|
||||
options.runModule,
|
||||
options.sourceMapUrl
|
||||
).then(
|
||||
function(package) {
|
||||
res.end(JSON.stringify(package.getSourceMap()));
|
||||
},
|
||||
function(error) {
|
||||
handleError(res, error);
|
||||
}
|
||||
).done();
|
||||
} else {
|
||||
next();
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
function getOptionsFromPath(pathname) {
|
||||
var parts = pathname.split('.');
|
||||
// Remove the leading slash.
|
||||
var main = parts[0].slice(1) + '.js';
|
||||
return {
|
||||
runModule: parts.slice(1).some(function(part) {
|
||||
return part === 'runModule';
|
||||
}),
|
||||
main: main,
|
||||
sourceMapUrl: parts.slice(0, -1).join('.') + '.map'
|
||||
};
|
||||
}
|
||||
|
||||
function handleError(res, error) {
|
||||
res.writeHead(500, {
|
||||
'Content-Type': 'application/json; charset=UTF-8',
|
||||
});
|
||||
|
||||
if (error.type === 'TransformError') {
|
||||
res.end(JSON.stringify(error));
|
||||
} else {
|
||||
console.error(error.stack || error);
|
||||
res.end(JSON.stringify({
|
||||
type: 'InternalError',
|
||||
message: 'React packager has encountered an internal error, ' +
|
||||
'please check your terminal error output for more details',
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
function createPackager(options) {
|
||||
return new Packager({
|
||||
projectRoot: options.projectRoot,
|
||||
blacklistRE: options.blacklistRE,
|
||||
polyfillModuleNames: options.polyfillModuleNames || [],
|
||||
runtimeCode: options.runtimeCode,
|
||||
cacheVersion: options.cacheVersion,
|
||||
resetCache: options.resetCache,
|
||||
dev: options.dev,
|
||||
});
|
||||
}
|
||||
|
||||
exports.kill = Packager.kill;
|
6
react-packager/package.json
Normal file
6
react-packager/package.json
Normal file
@ -0,0 +1,6 @@
|
||||
{
|
||||
"name": "react-packager",
|
||||
"version": "0.0.1",
|
||||
"description": "",
|
||||
"main": "index.js"
|
||||
}
|
79
react-packager/src/Activity/__tests__/Activity-test.js
vendored
Normal file
79
react-packager/src/Activity/__tests__/Activity-test.js
vendored
Normal file
@ -0,0 +1,79 @@
|
||||
jest.autoMockOff();
|
||||
|
||||
describe('Activity', function() {
|
||||
var Activity;
|
||||
|
||||
var origConsoleLog = console.log;
|
||||
|
||||
beforeEach(function() {
|
||||
console.log = jest.genMockFn();
|
||||
Activity = require('../');
|
||||
});
|
||||
|
||||
afterEach(function() {
|
||||
console.log = origConsoleLog;
|
||||
});
|
||||
|
||||
describe('startEvent', function() {
|
||||
it('writes a START event out to the console', function() {
|
||||
var EVENT_NAME = 'EVENT_NAME';
|
||||
var DATA = {someData: 42};
|
||||
|
||||
Activity.startEvent(EVENT_NAME, DATA);
|
||||
jest.runOnlyPendingTimers();
|
||||
|
||||
expect(console.log.mock.calls.length).toBe(1);
|
||||
var consoleMsg = console.log.mock.calls[0][0];
|
||||
expect(consoleMsg).toContain('START');
|
||||
expect(consoleMsg).toContain(EVENT_NAME);
|
||||
expect(consoleMsg).toContain(JSON.stringify(DATA));
|
||||
});
|
||||
});
|
||||
|
||||
describe('endEvent', function() {
|
||||
it('writes an END event out to the console', function() {
|
||||
var EVENT_NAME = 'EVENT_NAME';
|
||||
var DATA = {someData: 42};
|
||||
|
||||
var eventID = Activity.startEvent(EVENT_NAME, DATA);
|
||||
Activity.endEvent(eventID);
|
||||
jest.runOnlyPendingTimers();
|
||||
|
||||
expect(console.log.mock.calls.length).toBe(2);
|
||||
var consoleMsg = console.log.mock.calls[1][0];
|
||||
expect(consoleMsg).toContain('END');
|
||||
expect(consoleMsg).toContain(EVENT_NAME);
|
||||
expect(consoleMsg).toContain(JSON.stringify(DATA));
|
||||
});
|
||||
|
||||
it('throws when called with an invalid eventId', function() {
|
||||
expect(function() {
|
||||
Activity.endEvent(42);
|
||||
}).toThrow('event(42) is not a valid event id!');
|
||||
});
|
||||
|
||||
it('throws when called with an expired eventId', function() {
|
||||
var eid = Activity.startEvent('', '');
|
||||
Activity.endEvent(eid);
|
||||
|
||||
expect(function() {
|
||||
Activity.endEvent(eid);
|
||||
}).toThrow('event(1) has already ended!');
|
||||
});
|
||||
});
|
||||
|
||||
describe('signal', function() {
|
||||
it('writes a SIGNAL event out to the console', function() {
|
||||
var EVENT_NAME = 'EVENT_NAME';
|
||||
var DATA = {someData: 42};
|
||||
|
||||
Activity.signal(EVENT_NAME, DATA);
|
||||
jest.runOnlyPendingTimers();
|
||||
|
||||
expect(console.log.mock.calls.length).toBe(1);
|
||||
var consoleMsg = console.log.mock.calls[0][0];
|
||||
expect(consoleMsg).toContain(EVENT_NAME);
|
||||
expect(consoleMsg).toContain(JSON.stringify(DATA));
|
||||
});
|
||||
});
|
||||
});
|
160
react-packager/src/Activity/index.js
vendored
Normal file
160
react-packager/src/Activity/index.js
vendored
Normal file
@ -0,0 +1,160 @@
|
||||
var COLLECTION_PERIOD = 1000;
|
||||
|
||||
var _endedEvents = Object.create(null);
|
||||
var _eventStarts = Object.create(null);
|
||||
var _queuedActions = [];
|
||||
var _scheduledCollectionTimer = null;
|
||||
var _uuid = 1;
|
||||
var _enabled = true;
|
||||
|
||||
function endEvent(eventId) {
|
||||
var eventEndTime = Date.now();
|
||||
|
||||
if (!_eventStarts[eventId]) {
|
||||
_throw('event(' + eventId + ') is not a valid event id!');
|
||||
}
|
||||
|
||||
if (_endedEvents[eventId]) {
|
||||
_throw('event(' + eventId + ') has already ended!');
|
||||
}
|
||||
|
||||
_scheduleAction({
|
||||
action: 'endEvent',
|
||||
eventId: eventId,
|
||||
tstamp: eventEndTime
|
||||
});
|
||||
_endedEvents[eventId] = true;
|
||||
}
|
||||
|
||||
function signal(eventName, data) {
|
||||
var signalTime = Date.now();
|
||||
|
||||
if (eventName == null) {
|
||||
_throw('No event name specified');
|
||||
}
|
||||
|
||||
if (data == null) {
|
||||
data = null;
|
||||
}
|
||||
|
||||
_scheduleAction({
|
||||
action: 'signal',
|
||||
data: data,
|
||||
eventName: eventName,
|
||||
tstamp: signalTime
|
||||
});
|
||||
}
|
||||
|
||||
function startEvent(eventName, data) {
|
||||
var eventStartTime = Date.now();
|
||||
|
||||
if (eventName == null) {
|
||||
_throw('No event name specified');
|
||||
}
|
||||
|
||||
if (data == null) {
|
||||
data = null;
|
||||
}
|
||||
|
||||
var eventId = _uuid++;
|
||||
var action = {
|
||||
action: 'startEvent',
|
||||
data: data,
|
||||
eventId: eventId,
|
||||
eventName: eventName,
|
||||
tstamp: eventStartTime,
|
||||
};
|
||||
_scheduleAction(action);
|
||||
_eventStarts[eventId] = action;
|
||||
|
||||
return eventId;
|
||||
}
|
||||
|
||||
function disable() {
|
||||
_enabled = false;
|
||||
}
|
||||
|
||||
function _runCollection() {
|
||||
/* jshint -W084 */
|
||||
var action;
|
||||
while ((action = _queuedActions.shift())) {
|
||||
_writeAction(action);
|
||||
}
|
||||
|
||||
_scheduledCollectionTimer = null;
|
||||
}
|
||||
|
||||
function _scheduleAction(action) {
|
||||
_queuedActions.push(action);
|
||||
|
||||
if (_scheduledCollectionTimer === null) {
|
||||
_scheduledCollectionTimer = setTimeout(_runCollection, COLLECTION_PERIOD);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This a utility function that throws an error message.
|
||||
*
|
||||
* The only purpose of this utility is to make APIs like
|
||||
* startEvent/endEvent/signal inlineable in the JIT.
|
||||
*
|
||||
* (V8 can't inline functions that statically contain a `throw`, and probably
|
||||
* won't be adding such a non-trivial optimization anytime soon)
|
||||
*/
|
||||
function _throw(msg) {
|
||||
var err = new Error(msg);
|
||||
|
||||
// Strip off the call to _throw()
|
||||
var stack = err.stack.split('\n');
|
||||
stack.splice(1, 1);
|
||||
err.stack = stack.join('\n');
|
||||
|
||||
throw err;
|
||||
}
|
||||
|
||||
function _writeAction(action) {
|
||||
if (!_enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
var data = action.data ? ': ' + JSON.stringify(action.data) : '';
|
||||
var fmtTime = new Date(action.tstamp).toLocaleTimeString();
|
||||
|
||||
switch (action.action) {
|
||||
case 'startEvent':
|
||||
console.log(
|
||||
'[' + fmtTime + '] ' +
|
||||
'<START> ' + action.eventName +
|
||||
data
|
||||
);
|
||||
break;
|
||||
|
||||
case 'endEvent':
|
||||
var startAction = _eventStarts[action.eventId];
|
||||
console.log(
|
||||
'[' + fmtTime + '] ' +
|
||||
'<END> ' + startAction.eventName +
|
||||
'(' + (action.tstamp - startAction.tstamp) + 'ms)' +
|
||||
data
|
||||
);
|
||||
delete _eventStarts[action.eventId];
|
||||
break;
|
||||
|
||||
case 'signal':
|
||||
console.log(
|
||||
'[' + fmtTime + '] ' +
|
||||
' ' + action.eventName + '' +
|
||||
data
|
||||
);
|
||||
break;
|
||||
|
||||
default:
|
||||
_throw('Unexpected scheduled action type: ' + action.action);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
exports.endEvent = endEvent;
|
||||
exports.signal = signal;
|
||||
exports.startEvent = startEvent;
|
||||
exports.disable = disable;
|
34
react-packager/src/DependencyResolver/ModuleDescriptor.js
vendored
Normal file
34
react-packager/src/DependencyResolver/ModuleDescriptor.js
vendored
Normal file
@ -0,0 +1,34 @@
|
||||
function ModuleDescriptor(fields) {
|
||||
if (!fields.id) {
|
||||
throw new Error('Missing required fields id');
|
||||
}
|
||||
this.id = fields.id;
|
||||
|
||||
if (!fields.path) {
|
||||
throw new Error('Missing required fields path');
|
||||
}
|
||||
this.path = fields.path;
|
||||
|
||||
if (!fields.dependencies) {
|
||||
throw new Error('Missing required fields dependencies');
|
||||
}
|
||||
this.dependencies = fields.dependencies;
|
||||
|
||||
this.resolveDependency = fields.resolveDependency;
|
||||
|
||||
this.entry = fields.entry || false;
|
||||
|
||||
this.isPolyfill = fields.isPolyfill || false;
|
||||
|
||||
this._fields = fields;
|
||||
}
|
||||
|
||||
ModuleDescriptor.prototype.toJSON = function() {
|
||||
return {
|
||||
id: this.id,
|
||||
path: this.path,
|
||||
dependencies: this.dependencies
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = ModuleDescriptor;
|
101
react-packager/src/DependencyResolver/haste/DependencyGraph/__mocks__/fs.js
vendored
Normal file
101
react-packager/src/DependencyResolver/haste/DependencyGraph/__mocks__/fs.js
vendored
Normal file
@ -0,0 +1,101 @@
|
||||
'use strict';
|
||||
|
||||
var fs = jest.genMockFromModule('fs');
|
||||
|
||||
fs.realpath.mockImpl(function(filepath, callback) {
|
||||
var node;
|
||||
try {
|
||||
node = getToNode(filepath);
|
||||
} catch (e) {
|
||||
return callback(e);
|
||||
}
|
||||
if (node && typeof node === 'object' && node.SYMLINK != null) {
|
||||
return callback(null, node.SYMLINK);
|
||||
}
|
||||
callback(null, filepath);
|
||||
});
|
||||
|
||||
fs.readdir.mockImpl(function(filepath, callback) {
|
||||
var node;
|
||||
try {
|
||||
node = getToNode(filepath);
|
||||
if (node && typeof node === 'object' && node.SYMLINK != null) {
|
||||
node = getToNode(node.SYMLINK);
|
||||
}
|
||||
} catch (e) {
|
||||
return callback(e);
|
||||
}
|
||||
|
||||
if (!(node && typeof node === 'object' && node.SYMLINK == null)) {
|
||||
return callback(new Error(filepath + ' is not a directory.'));
|
||||
}
|
||||
|
||||
callback(null, Object.keys(node));
|
||||
});
|
||||
|
||||
fs.readFile.mockImpl(function(filepath, encoding, callback) {
|
||||
try {
|
||||
var node = getToNode(filepath);
|
||||
// dir check
|
||||
if (node && typeof node === 'object' && node.SYMLINK == null) {
|
||||
callback(new Error('Trying to read a dir, ESIDR, or whatever'));
|
||||
}
|
||||
return callback(null, node);
|
||||
} catch (e) {
|
||||
return callback(e);
|
||||
}
|
||||
});
|
||||
|
||||
fs.lstat.mockImpl(function(filepath, callback) {
|
||||
var node;
|
||||
try {
|
||||
node = getToNode(filepath);
|
||||
} catch (e) {
|
||||
return callback(e);
|
||||
}
|
||||
|
||||
if (node && typeof node === 'object' && node.SYMLINK == null) {
|
||||
callback(null, {
|
||||
isDirectory: function() {
|
||||
return true;
|
||||
},
|
||||
isSymbolicLink: function() {
|
||||
return false;
|
||||
}
|
||||
});
|
||||
} else {
|
||||
callback(null, {
|
||||
isDirectory: function() {
|
||||
return false;
|
||||
},
|
||||
isSymbolicLink: function() {
|
||||
if (typeof node === 'object' && node.SYMLINK) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
var filesystem;
|
||||
|
||||
fs.__setMockFilesystem = function(object) {
|
||||
filesystem = object;
|
||||
return filesystem;
|
||||
};
|
||||
|
||||
function getToNode(filepath) {
|
||||
var parts = filepath.split('/');
|
||||
if (parts[0] !== '') {
|
||||
throw new Error('Make sure all paths are absolute.');
|
||||
}
|
||||
var node = filesystem;
|
||||
parts.slice(1).forEach(function(part) {
|
||||
node = node[part];
|
||||
});
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
module.exports = fs;
|
713
react-packager/src/DependencyResolver/haste/DependencyGraph/__tests__/DependencyGraph-test.js
vendored
Normal file
713
react-packager/src/DependencyResolver/haste/DependencyGraph/__tests__/DependencyGraph-test.js
vendored
Normal file
@ -0,0 +1,713 @@
|
||||
'use strict';
|
||||
|
||||
jest
|
||||
.dontMock('../index')
|
||||
.dontMock('q')
|
||||
.dontMock('path')
|
||||
.dontMock('absolute-path')
|
||||
.dontMock('../../../../fb-path-utils')
|
||||
.dontMock('../docblock')
|
||||
.setMock('../../../ModuleDescriptor', function(data) {return data;});
|
||||
|
||||
var q = require('q');
|
||||
|
||||
describe('DependencyGraph', function() {
|
||||
var DependencyGraph;
|
||||
var fileWatcher;
|
||||
var fs;
|
||||
|
||||
beforeEach(function() {
|
||||
fs = require('fs');
|
||||
DependencyGraph = require('../index');
|
||||
|
||||
fileWatcher = {
|
||||
getWatcher: function() {
|
||||
return q({
|
||||
on: function() {
|
||||
return this;
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
describe('getOrderedDependencies', function() {
|
||||
pit('should get dependencies', function() {
|
||||
var root = '/root';
|
||||
fs.__setMockFilesystem({
|
||||
'root': {
|
||||
'index.js': [
|
||||
'/**',
|
||||
' * @providesModule index',
|
||||
' */',
|
||||
'require("a")'
|
||||
].join('\n'),
|
||||
'a.js': [
|
||||
'/**',
|
||||
' * @providesModule a',
|
||||
' */',
|
||||
].join('\n'),
|
||||
}
|
||||
});
|
||||
|
||||
var dgraph = new DependencyGraph({root: root, fileWatcher: fileWatcher});
|
||||
return dgraph.load().then(function() {
|
||||
expect(dgraph.getOrderedDependencies('/root/index.js'))
|
||||
.toEqual([
|
||||
{id: 'index', path: '/root/index.js', dependencies: ['a']},
|
||||
{id: 'a', path: '/root/a.js', dependencies: []},
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
pit('should get recursive dependencies', function() {
|
||||
var root = '/root';
|
||||
fs.__setMockFilesystem({
|
||||
'root': {
|
||||
'index.js': [
|
||||
'/**',
|
||||
' * @providesModule index',
|
||||
' */',
|
||||
'require("a")',
|
||||
].join('\n'),
|
||||
'a.js': [
|
||||
'/**',
|
||||
' * @providesModule a',
|
||||
' */',
|
||||
'require("index")',
|
||||
].join('\n'),
|
||||
}
|
||||
});
|
||||
|
||||
var dgraph = new DependencyGraph({root: root, fileWatcher: fileWatcher});
|
||||
return dgraph.load().then(function() {
|
||||
expect(dgraph.getOrderedDependencies('/root/index.js'))
|
||||
.toEqual([
|
||||
{id: 'index', path: '/root/index.js', dependencies: ['a']},
|
||||
{id: 'a', path: '/root/a.js', dependencies: ['index']},
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
pit('should work with packages', function() {
|
||||
var root = '/root';
|
||||
fs.__setMockFilesystem({
|
||||
'root': {
|
||||
'index.js': [
|
||||
'/**',
|
||||
' * @providesModule index',
|
||||
' */',
|
||||
'require("aPackage")',
|
||||
].join('\n'),
|
||||
'aPackage': {
|
||||
'package.json': JSON.stringify({
|
||||
name: 'aPackage',
|
||||
main: 'main.js'
|
||||
}),
|
||||
'main.js': 'lol'
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
var dgraph = new DependencyGraph({root: root, fileWatcher: fileWatcher});
|
||||
return dgraph.load().then(function() {
|
||||
expect(dgraph.getOrderedDependencies('/root/index.js'))
|
||||
.toEqual([
|
||||
{id: 'index', path: '/root/index.js', dependencies: ['aPackage']},
|
||||
{ id: 'aPackage/main',
|
||||
path: '/root/aPackage/main.js',
|
||||
dependencies: []
|
||||
},
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
pit('can have multiple modules with the same name', function() {
|
||||
var root = '/root';
|
||||
fs.__setMockFilesystem({
|
||||
'root': {
|
||||
'index.js': [
|
||||
'/**',
|
||||
' * @providesModule index',
|
||||
' */',
|
||||
'require("b")',
|
||||
].join('\n'),
|
||||
'b.js': [
|
||||
'/**',
|
||||
' * @providesModule b',
|
||||
' */',
|
||||
].join('\n'),
|
||||
'c.js': [
|
||||
'/**',
|
||||
' * @providesModule c',
|
||||
' */',
|
||||
].join('\n'),
|
||||
'somedir': {
|
||||
'somefile.js': [
|
||||
'/**',
|
||||
' * @providesModule index',
|
||||
' */',
|
||||
'require("c")',
|
||||
].join('\n')
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
var dgraph = new DependencyGraph({root: root, fileWatcher: fileWatcher});
|
||||
return dgraph.load().then(function() {
|
||||
expect(dgraph.getOrderedDependencies('/root/somedir/somefile.js'))
|
||||
.toEqual([
|
||||
{ id: 'index',
|
||||
path: '/root/somedir/somefile.js',
|
||||
dependencies: ['c']
|
||||
},
|
||||
{ id: 'c',
|
||||
path: '/root/c.js',
|
||||
dependencies: []
|
||||
},
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
pit('providesModule wins when conflict with package', function() {
|
||||
var root = '/root';
|
||||
fs.__setMockFilesystem({
|
||||
'root': {
|
||||
'index.js': [
|
||||
'/**',
|
||||
' * @providesModule index',
|
||||
' */',
|
||||
'require("aPackage")',
|
||||
].join('\n'),
|
||||
'b.js': [
|
||||
'/**',
|
||||
' * @providesModule aPackage',
|
||||
' */',
|
||||
].join('\n'),
|
||||
'aPackage': {
|
||||
'package.json': JSON.stringify({
|
||||
name: 'aPackage',
|
||||
main: 'main.js'
|
||||
}),
|
||||
'main.js': 'lol'
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
var dgraph = new DependencyGraph({root: root, fileWatcher: fileWatcher});
|
||||
return dgraph.load().then(function() {
|
||||
expect(dgraph.getOrderedDependencies('/root/index.js'))
|
||||
.toEqual([
|
||||
{ id: 'index',
|
||||
path: '/root/index.js',
|
||||
dependencies: ['aPackage']
|
||||
},
|
||||
{ id: 'aPackage',
|
||||
path: '/root/b.js',
|
||||
dependencies: []
|
||||
},
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
pit('should be forgiving with missing requires', function() {
|
||||
var root = '/root';
|
||||
fs.__setMockFilesystem({
|
||||
'root': {
|
||||
'index.js': [
|
||||
'/**',
|
||||
' * @providesModule index',
|
||||
' */',
|
||||
'require("lolomg")',
|
||||
].join('\n')
|
||||
}
|
||||
});
|
||||
|
||||
var dgraph = new DependencyGraph({root: root, fileWatcher: fileWatcher});
|
||||
return dgraph.load().then(function() {
|
||||
expect(dgraph.getOrderedDependencies('/root/index.js'))
|
||||
.toEqual([
|
||||
{ id: 'index',
|
||||
path: '/root/index.js',
|
||||
dependencies: ['lolomg']
|
||||
}
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
pit('should work with packages with subdirs', function() {
|
||||
var root = '/root';
|
||||
fs.__setMockFilesystem({
|
||||
'root': {
|
||||
'index.js': [
|
||||
'/**',
|
||||
' * @providesModule index',
|
||||
' */',
|
||||
'require("aPackage/subdir/lolynot")',
|
||||
].join('\n'),
|
||||
'aPackage': {
|
||||
'package.json': JSON.stringify({
|
||||
name: 'aPackage',
|
||||
main: 'main.js'
|
||||
}),
|
||||
'main.js': 'lol',
|
||||
'subdir': {
|
||||
'lolynot.js': 'lolynot'
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
var dgraph = new DependencyGraph({root: root, fileWatcher: fileWatcher});
|
||||
return dgraph.load().then(function() {
|
||||
expect(dgraph.getOrderedDependencies('/root/index.js'))
|
||||
.toEqual([
|
||||
{ id: 'index',
|
||||
path: '/root/index.js',
|
||||
dependencies: ['aPackage/subdir/lolynot']
|
||||
},
|
||||
{ id: 'aPackage/subdir/lolynot',
|
||||
path: '/root/aPackage/subdir/lolynot.js',
|
||||
dependencies: []
|
||||
},
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
pit('should work with packages with symlinked subdirs', function() {
|
||||
var root = '/root';
|
||||
fs.__setMockFilesystem({
|
||||
'symlinkedPackage': {
|
||||
'package.json': JSON.stringify({
|
||||
name: 'aPackage',
|
||||
main: 'main.js'
|
||||
}),
|
||||
'main.js': 'lol',
|
||||
'subdir': {
|
||||
'lolynot.js': 'lolynot'
|
||||
}
|
||||
},
|
||||
'root': {
|
||||
'index.js': [
|
||||
'/**',
|
||||
' * @providesModule index',
|
||||
' */',
|
||||
'require("aPackage/subdir/lolynot")',
|
||||
].join('\n'),
|
||||
'aPackage': { SYMLINK: '/symlinkedPackage' },
|
||||
}
|
||||
});
|
||||
|
||||
var dgraph = new DependencyGraph({root: root, fileWatcher: fileWatcher});
|
||||
return dgraph.load().then(function() {
|
||||
expect(dgraph.getOrderedDependencies('/root/index.js'))
|
||||
.toEqual([
|
||||
{ id: 'index',
|
||||
path: '/root/index.js',
|
||||
dependencies: ['aPackage/subdir/lolynot']
|
||||
},
|
||||
{ id: 'aPackage/subdir/lolynot',
|
||||
path: '/symlinkedPackage/subdir/lolynot.js',
|
||||
dependencies: []
|
||||
},
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
pit('should work with relative modules in packages', function() {
|
||||
var root = '/root';
|
||||
fs.__setMockFilesystem({
|
||||
'root': {
|
||||
'index.js': [
|
||||
'/**',
|
||||
' * @providesModule index',
|
||||
' */',
|
||||
'require("aPackage")',
|
||||
].join('\n'),
|
||||
'aPackage': {
|
||||
'package.json': JSON.stringify({
|
||||
name: 'aPackage',
|
||||
main: 'main.js'
|
||||
}),
|
||||
'main.js': 'require("./subdir/lolynot")',
|
||||
'subdir': {
|
||||
'lolynot.js': 'require("../other")'
|
||||
},
|
||||
'other.js': 'some code'
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
var dgraph = new DependencyGraph({root: root, fileWatcher: fileWatcher});
|
||||
return dgraph.load().then(function() {
|
||||
expect(dgraph.getOrderedDependencies('/root/index.js'))
|
||||
.toEqual([
|
||||
{ id: 'index',
|
||||
path: '/root/index.js',
|
||||
dependencies: ['aPackage']
|
||||
},
|
||||
{ id: 'aPackage/main',
|
||||
path: '/root/aPackage/main.js',
|
||||
dependencies: ['./subdir/lolynot']
|
||||
},
|
||||
{ id: 'aPackage/subdir/lolynot',
|
||||
path: '/root/aPackage/subdir/lolynot.js',
|
||||
dependencies: ['../other']
|
||||
},
|
||||
{ id: 'aPackage/other',
|
||||
path: '/root/aPackage/other.js',
|
||||
dependencies: []
|
||||
},
|
||||
]);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('file watch updating', function() {
|
||||
var fileWatcher;
|
||||
var triggerFileChange;
|
||||
|
||||
beforeEach(function() {
|
||||
fileWatcher = {
|
||||
getWatcher: function() {
|
||||
return q({
|
||||
on: function(eventType, callback) {
|
||||
if (eventType !== 'all') {
|
||||
throw new Error('Can only handle "all" event in watcher.');
|
||||
}
|
||||
triggerFileChange = callback;
|
||||
return this;
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
pit('updates module dependencies', function() {
|
||||
var root = '/root';
|
||||
var filesystem = fs.__setMockFilesystem({
|
||||
'root': {
|
||||
'index.js': [
|
||||
'/**',
|
||||
' * @providesModule index',
|
||||
' */',
|
||||
'require("aPackage")',
|
||||
'require("foo")'
|
||||
].join('\n'),
|
||||
'foo': [
|
||||
'/**',
|
||||
' * @providesModule foo',
|
||||
' */',
|
||||
'require("aPackage")'
|
||||
].join('\n'),
|
||||
'aPackage': {
|
||||
'package.json': JSON.stringify({
|
||||
name: 'aPackage',
|
||||
main: 'main.js'
|
||||
}),
|
||||
'main.js': 'main',
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
var dgraph = new DependencyGraph({root: root, fileWatcher: fileWatcher});
|
||||
return dgraph.load().then(function() {
|
||||
filesystem.root['index.js'] =
|
||||
filesystem.root['index.js'].replace('require("foo")', '');
|
||||
triggerFileChange('change', 'index.js');
|
||||
return dgraph.load().then(function() {
|
||||
expect(dgraph.getOrderedDependencies('/root/index.js'))
|
||||
.toEqual([
|
||||
{ id: 'index',
|
||||
path: '/root/index.js',
|
||||
dependencies: ['aPackage']
|
||||
},
|
||||
{ id: 'aPackage/main',
|
||||
path: '/root/aPackage/main.js',
|
||||
dependencies: []
|
||||
},
|
||||
]);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
pit('updates module dependencies on file change', function() {
|
||||
var root = '/root';
|
||||
var filesystem = fs.__setMockFilesystem({
|
||||
'root': {
|
||||
'index.js': [
|
||||
'/**',
|
||||
' * @providesModule index',
|
||||
' */',
|
||||
'require("aPackage")',
|
||||
'require("foo")'
|
||||
].join('\n'),
|
||||
'foo.js': [
|
||||
'/**',
|
||||
' * @providesModule foo',
|
||||
' */',
|
||||
'require("aPackage")'
|
||||
].join('\n'),
|
||||
'aPackage': {
|
||||
'package.json': JSON.stringify({
|
||||
name: 'aPackage',
|
||||
main: 'main.js'
|
||||
}),
|
||||
'main.js': 'main',
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
var dgraph = new DependencyGraph({root: root, fileWatcher: fileWatcher});
|
||||
return dgraph.load().then(function() {
|
||||
filesystem.root['index.js'] =
|
||||
filesystem.root['index.js'].replace('require("foo")', '');
|
||||
triggerFileChange('change', 'index.js');
|
||||
return dgraph.load().then(function() {
|
||||
expect(dgraph.getOrderedDependencies('/root/index.js'))
|
||||
.toEqual([
|
||||
{ id: 'index',
|
||||
path: '/root/index.js',
|
||||
dependencies: ['aPackage']
|
||||
},
|
||||
{ id: 'aPackage/main',
|
||||
path: '/root/aPackage/main.js',
|
||||
dependencies: []
|
||||
},
|
||||
]);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
pit('updates module dependencies on file delete', function() {
|
||||
var root = '/root';
|
||||
var filesystem = fs.__setMockFilesystem({
|
||||
'root': {
|
||||
'index.js': [
|
||||
'/**',
|
||||
' * @providesModule index',
|
||||
' */',
|
||||
'require("aPackage")',
|
||||
'require("foo")'
|
||||
].join('\n'),
|
||||
'foo.js': [
|
||||
'/**',
|
||||
' * @providesModule foo',
|
||||
' */',
|
||||
'require("aPackage")'
|
||||
].join('\n'),
|
||||
'aPackage': {
|
||||
'package.json': JSON.stringify({
|
||||
name: 'aPackage',
|
||||
main: 'main.js'
|
||||
}),
|
||||
'main.js': 'main',
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
var dgraph = new DependencyGraph({root: root, fileWatcher: fileWatcher});
|
||||
return dgraph.load().then(function() {
|
||||
delete filesystem.root.foo;
|
||||
triggerFileChange('delete', 'foo.js');
|
||||
return dgraph.load().then(function() {
|
||||
expect(dgraph.getOrderedDependencies('/root/index.js'))
|
||||
.toEqual([
|
||||
{ id: 'index',
|
||||
path: '/root/index.js',
|
||||
dependencies: ['aPackage', 'foo']
|
||||
},
|
||||
{ id: 'aPackage/main',
|
||||
path: '/root/aPackage/main.js',
|
||||
dependencies: []
|
||||
},
|
||||
]);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
pit('updates module dependencies on file add', function() {
|
||||
var root = '/root';
|
||||
var filesystem = fs.__setMockFilesystem({
|
||||
'root': {
|
||||
'index.js': [
|
||||
'/**',
|
||||
' * @providesModule index',
|
||||
' */',
|
||||
'require("aPackage")',
|
||||
'require("foo")'
|
||||
].join('\n'),
|
||||
'foo.js': [
|
||||
'/**',
|
||||
' * @providesModule foo',
|
||||
' */',
|
||||
'require("aPackage")'
|
||||
].join('\n'),
|
||||
'aPackage': {
|
||||
'package.json': JSON.stringify({
|
||||
name: 'aPackage',
|
||||
main: 'main.js'
|
||||
}),
|
||||
'main.js': 'main',
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
var dgraph = new DependencyGraph({root: root, fileWatcher: fileWatcher});
|
||||
return dgraph.load().then(function() {
|
||||
filesystem.root['bar.js'] = [
|
||||
'/**',
|
||||
' * @providesModule bar',
|
||||
' */',
|
||||
'require("foo")'
|
||||
].join('\n');
|
||||
triggerFileChange('add', 'bar.js');
|
||||
|
||||
filesystem.root.aPackage['main.js'] = 'require("bar")';
|
||||
triggerFileChange('change', 'aPackage/main.js');
|
||||
|
||||
return dgraph.load().then(function() {
|
||||
expect(dgraph.getOrderedDependencies('/root/index.js'))
|
||||
.toEqual([
|
||||
{ id: 'index',
|
||||
path: '/root/index.js',
|
||||
dependencies: ['aPackage', 'foo']
|
||||
},
|
||||
{ id: 'aPackage/main',
|
||||
path: '/root/aPackage/main.js',
|
||||
dependencies: ['bar']
|
||||
},
|
||||
{ id: 'bar',
|
||||
path: '/root/bar.js',
|
||||
dependencies: ['foo']
|
||||
},
|
||||
{ id: 'foo',
|
||||
path: '/root/foo.js',
|
||||
dependencies: ['aPackage']
|
||||
},
|
||||
]);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
pit('runs changes through ignore filter', function() {
|
||||
var root = '/root';
|
||||
var filesystem = fs.__setMockFilesystem({
|
||||
'root': {
|
||||
'index.js': [
|
||||
'/**',
|
||||
' * @providesModule index',
|
||||
' */',
|
||||
'require("aPackage")',
|
||||
'require("foo")'
|
||||
].join('\n'),
|
||||
'foo.js': [
|
||||
'/**',
|
||||
' * @providesModule foo',
|
||||
' */',
|
||||
'require("aPackage")'
|
||||
].join('\n'),
|
||||
'aPackage': {
|
||||
'package.json': JSON.stringify({
|
||||
name: 'aPackage',
|
||||
main: 'main.js'
|
||||
}),
|
||||
'main.js': 'main',
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
var dgraph = new DependencyGraph({
|
||||
root: root,
|
||||
fileWatcher: fileWatcher,
|
||||
ignoreFilePath: function(filePath) {
|
||||
if (filePath === '/root/bar.js') {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
});
|
||||
return dgraph.load().then(function() {
|
||||
filesystem.root['bar.js'] = [
|
||||
'/**',
|
||||
' * @providesModule bar',
|
||||
' */',
|
||||
'require("foo")'
|
||||
].join('\n');
|
||||
triggerFileChange('add', 'bar.js');
|
||||
|
||||
filesystem.root.aPackage['main.js'] = 'require("bar")';
|
||||
triggerFileChange('change', 'aPackage/main.js');
|
||||
|
||||
return dgraph.load().then(function() {
|
||||
expect(dgraph.getOrderedDependencies('/root/index.js'))
|
||||
.toEqual([
|
||||
{ id: 'index',
|
||||
path: '/root/index.js',
|
||||
dependencies: ['aPackage', 'foo']
|
||||
},
|
||||
{ id: 'aPackage/main',
|
||||
path: '/root/aPackage/main.js',
|
||||
dependencies: ['bar']
|
||||
},
|
||||
{ id: 'foo',
|
||||
path: '/root/foo.js',
|
||||
dependencies: ['aPackage']
|
||||
},
|
||||
]);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
pit('should ignore directory updates', function() {
|
||||
var root = '/root';
|
||||
var filesystem = fs.__setMockFilesystem({
|
||||
'root': {
|
||||
'index.js': [
|
||||
'/**',
|
||||
' * @providesModule index',
|
||||
' */',
|
||||
'require("aPackage")',
|
||||
'require("foo")'
|
||||
].join('\n'),
|
||||
'foo.js': [
|
||||
'/**',
|
||||
' * @providesModule foo',
|
||||
' */',
|
||||
'require("aPackage")'
|
||||
].join('\n'),
|
||||
'aPackage': {
|
||||
'package.json': JSON.stringify({
|
||||
name: 'aPackage',
|
||||
main: 'main.js'
|
||||
}),
|
||||
'main.js': 'main',
|
||||
}
|
||||
}
|
||||
});
|
||||
var dgraph = new DependencyGraph({root: root, fileWatcher: fileWatcher});
|
||||
return dgraph.load().then(function() {
|
||||
triggerFileChange('change', 'aPackage', '/root', {
|
||||
isDirectory: function(){ return true; }
|
||||
});
|
||||
return dgraph.load().then(function() {
|
||||
expect(dgraph.getOrderedDependencies('/root/index.js'))
|
||||
.toEqual([
|
||||
{ id: 'index',
|
||||
path: '/root/index.js',
|
||||
dependencies: ['aPackage', 'foo']
|
||||
},
|
||||
{ id: 'aPackage/main',
|
||||
path: '/root/aPackage/main.js',
|
||||
dependencies: []
|
||||
},
|
||||
{ id: 'foo',
|
||||
path: '/root/foo.js',
|
||||
dependencies: ['aPackage']
|
||||
},
|
||||
]);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
88
react-packager/src/DependencyResolver/haste/DependencyGraph/docblock.js
vendored
Normal file
88
react-packager/src/DependencyResolver/haste/DependencyGraph/docblock.js
vendored
Normal file
@ -0,0 +1,88 @@
|
||||
/**
|
||||
* Copyright 2013 Facebook, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
|
||||
var docblockRe = /^\s*(\/\*\*(.|\r?\n)*?\*\/)/;
|
||||
|
||||
var ltrimRe = /^\s*/;
|
||||
/**
|
||||
* @param {String} contents
|
||||
* @return {String}
|
||||
*/
|
||||
function extract(contents) {
|
||||
var match = contents.match(docblockRe);
|
||||
if (match) {
|
||||
return match[0].replace(ltrimRe, '') || '';
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
|
||||
var commentStartRe = /^\/\*\*?/;
|
||||
var commentEndRe = /\*\/$/;
|
||||
var wsRe = /[\t ]+/g;
|
||||
var stringStartRe = /(\r?\n|^) *\*/g;
|
||||
var multilineRe = /(?:^|\r?\n) *(@[^\r\n]*?) *\r?\n *([^@\r\n\s][^@\r\n]+?) *\r?\n/g;
|
||||
var propertyRe = /(?:^|\r?\n) *@(\S+) *([^\r\n]*)/g;
|
||||
|
||||
/**
|
||||
* @param {String} contents
|
||||
* @return {Array}
|
||||
*/
|
||||
function parse(docblock) {
|
||||
docblock = docblock
|
||||
.replace(commentStartRe, '')
|
||||
.replace(commentEndRe, '')
|
||||
.replace(wsRe, ' ')
|
||||
.replace(stringStartRe, '$1');
|
||||
|
||||
// Normalize multi-line directives
|
||||
var prev = '';
|
||||
while (prev != docblock) {
|
||||
prev = docblock;
|
||||
docblock = docblock.replace(multilineRe, "\n$1 $2\n");
|
||||
}
|
||||
docblock = docblock.trim();
|
||||
|
||||
var result = [];
|
||||
var match;
|
||||
while (match = propertyRe.exec(docblock)) {
|
||||
result.push([match[1], match[2]]);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Same as parse but returns an object of prop: value instead of array of paris
|
||||
* If a property appers more than once the last one will be returned
|
||||
*
|
||||
* @param {String} contents
|
||||
* @return {Object}
|
||||
*/
|
||||
function parseAsObject(docblock) {
|
||||
var pairs = parse(docblock);
|
||||
var result = {};
|
||||
for (var i = 0; i < pairs.length; i++) {
|
||||
result[pairs[i][0]] = pairs[i][1];
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
exports.extract = extract;
|
||||
exports.parse = parse;
|
||||
exports.parseAsObject = parseAsObject;
|
25
react-packager/src/DependencyResolver/haste/DependencyGraph/example.js
vendored
Normal file
25
react-packager/src/DependencyResolver/haste/DependencyGraph/example.js
vendored
Normal file
@ -0,0 +1,25 @@
|
||||
var path = require('path');
|
||||
var DependecyGraph = require('./');
|
||||
|
||||
var example_project = path.resolve(__dirname, '../../../../example_project');
|
||||
var watcher = new (require('../../../FileWatcher'))({projectRoot: example_project});
|
||||
var graph = new DependecyGraph({
|
||||
fileWatcher: watcher,
|
||||
root: example_project
|
||||
});
|
||||
|
||||
graph.load().then(function() {
|
||||
var index = path.join(example_project, 'index.js');
|
||||
console.log(graph.getOrderedDependencies(index));
|
||||
}).done();
|
||||
|
||||
watcher.getWatcher().then(function(watcher) {
|
||||
watcher.on('all', function() {
|
||||
setImmediate(function() {
|
||||
graph.load().then(function() {
|
||||
var index = path.join(example_project, 'index.js');
|
||||
console.log(graph.getOrderedDependencies(index));
|
||||
});
|
||||
})
|
||||
});
|
||||
});
|
450
react-packager/src/DependencyResolver/haste/DependencyGraph/index.js
vendored
Normal file
450
react-packager/src/DependencyResolver/haste/DependencyGraph/index.js
vendored
Normal file
@ -0,0 +1,450 @@
|
||||
'use strict';
|
||||
|
||||
var ModuleDescriptor = require('../../ModuleDescriptor');
|
||||
var q = require('q');
|
||||
var fs = require('fs');
|
||||
var docblock = require('./docblock');
|
||||
var path = require('path');
|
||||
var isAbsolutePath = require('absolute-path');
|
||||
var debug = require('debug')('DependecyGraph');
|
||||
|
||||
var readFile = q.nfbind(fs.readFile);
|
||||
var readDir = q.nfbind(fs.readdir);
|
||||
var lstat = q.nfbind(fs.lstat);
|
||||
var realpath = q.nfbind(fs.realpath);
|
||||
|
||||
function DependecyGraph(options) {
|
||||
this._root = options.root;
|
||||
this._ignoreFilePath = options.ignoreFilePath || function(){};
|
||||
this._loaded = false;
|
||||
this._queue = [this._root];
|
||||
this._graph = Object.create(null);
|
||||
this._packageByRoot = Object.create(null);
|
||||
this._packagesById = Object.create(null);
|
||||
this._moduleById = Object.create(null);
|
||||
this._fileWatcher = options.fileWatcher;
|
||||
|
||||
// Kick off the search process to precompute the dependency graph.
|
||||
this._init();
|
||||
}
|
||||
|
||||
DependecyGraph.prototype.load = function() {
|
||||
return this._loading || (this._loading = this._search());
|
||||
};
|
||||
|
||||
/**
|
||||
* Given an entry file return an array of all the dependent module descriptors.
|
||||
*/
|
||||
DependecyGraph.prototype.getOrderedDependencies = function(entryPath) {
|
||||
var absolutePath;
|
||||
if (!isAbsolutePath(entryPath)) {
|
||||
absolutePath = path.join(this._root, entryPath);
|
||||
} else {
|
||||
absolutePath = entryPath;
|
||||
}
|
||||
|
||||
var module = this._graph[absolutePath];
|
||||
if (module == null) {
|
||||
throw new Error('Module with path "' + absolutePath + '" is not in graph');
|
||||
}
|
||||
|
||||
var self = this;
|
||||
var deps = [];
|
||||
var visited = Object.create(null);
|
||||
|
||||
// Node haste sucks. Id's aren't unique. So to make sure our entry point
|
||||
// is the thing that ends up in our dependency list.
|
||||
var graphMap = Object.create(this._moduleById);
|
||||
graphMap[module.id] = module;
|
||||
|
||||
// Recursively collect the dependency list.
|
||||
function collect(module) {
|
||||
deps.push(module);
|
||||
|
||||
module.dependencies.forEach(function(name) {
|
||||
var id = sansExtJs(name);
|
||||
var dep = self.resolveDependency(module, id);
|
||||
|
||||
if (dep == null) {
|
||||
debug(
|
||||
'WARNING: Cannot find required module `%s` from module `%s`.',
|
||||
name,
|
||||
module.id
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!visited[dep.id]) {
|
||||
visited[dep.id] = true;
|
||||
collect(dep);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
visited[module.id] = true;
|
||||
collect(module);
|
||||
|
||||
return deps;
|
||||
};
|
||||
|
||||
/**
|
||||
* Given a module descriptor `fromModule` return the module descriptor for
|
||||
* the required module `depModuleId`. It could be top-level or relative,
|
||||
* or both.
|
||||
*/
|
||||
DependecyGraph.prototype.resolveDependency = function(
|
||||
fromModule,
|
||||
depModuleId
|
||||
) {
|
||||
var packageJson, modulePath, dep;
|
||||
|
||||
// Package relative modules starts with '.' or '..'.
|
||||
if (depModuleId[0] !== '.') {
|
||||
|
||||
// 1. `depModuleId` is simply a top-level `providesModule`.
|
||||
// 2. `depModuleId` is a package module but given the full path from the
|
||||
// package, i.e. package_name/module_name
|
||||
if (this._moduleById[sansExtJs(depModuleId)]) {
|
||||
return this._moduleById[sansExtJs(depModuleId)];
|
||||
}
|
||||
|
||||
// 3. `depModuleId` is a package and it's depending on the "main"
|
||||
// resolution.
|
||||
packageJson = this._packagesById[depModuleId];
|
||||
|
||||
// We are being forgiving here and raising an error because we could be
|
||||
// processing a file that uses it's own require system.
|
||||
if (packageJson == null) {
|
||||
debug(
|
||||
'WARNING: Cannot find required module `%s` from module `%s`.',
|
||||
depModuleId,
|
||||
fromModule.id
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
var main = packageJson.main || 'index';
|
||||
modulePath = withExtJs(path.join(packageJson._root, main));
|
||||
dep = this._graph[modulePath];
|
||||
if (dep == null) {
|
||||
throw new Error(
|
||||
'Cannot find package main file for pacakge: ' + packageJson._root
|
||||
);
|
||||
}
|
||||
return dep;
|
||||
} else {
|
||||
|
||||
// 4. `depModuleId` is a module defined in a package relative to
|
||||
// `fromModule`.
|
||||
packageJson = this._lookupPackage(fromModule.path);
|
||||
|
||||
if (packageJson == null) {
|
||||
throw new Error(
|
||||
'Expected relative module lookup from ' + fromModule.id + ' to ' +
|
||||
depModuleId + ' to be within a package but no package.json found.'
|
||||
);
|
||||
}
|
||||
|
||||
// Example: depModuleId: ../a/b
|
||||
// fromModule.path: /x/y/z
|
||||
// modulePath: /x/y/a/b
|
||||
var dir = path.dirname(fromModule.path);
|
||||
modulePath = withExtJs(path.join(dir, depModuleId));
|
||||
|
||||
dep = this._graph[modulePath];
|
||||
if (dep == null) {
|
||||
debug(
|
||||
'WARNING: Cannot find required module `%s` from module `%s`.' +
|
||||
' Inferred required module path is %s',
|
||||
depModuleId,
|
||||
fromModule.id,
|
||||
modulePath
|
||||
);
|
||||
return null;
|
||||
}
|
||||
|
||||
return dep;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Intiates the filewatcher and kicks off the search process.
|
||||
*/
|
||||
DependecyGraph.prototype._init = function() {
|
||||
var processChange = this._processFileChange.bind(this);
|
||||
var loadingWatcher = this._fileWatcher.getWatcher();
|
||||
|
||||
this._loading = this.load()
|
||||
.then(function() {
|
||||
return loadingWatcher;
|
||||
})
|
||||
.then(function(watcher) {
|
||||
watcher.on('all', processChange);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Implements a DFS over the file system looking for modules and packages.
|
||||
*/
|
||||
DependecyGraph.prototype._search = function() {
|
||||
var self = this;
|
||||
var dir = this._queue.shift();
|
||||
|
||||
if (dir == null) {
|
||||
return q.Promise.resolve(this._graph);
|
||||
}
|
||||
|
||||
// Steps:
|
||||
// 1. Read a dir and stat all the entries.
|
||||
// 2. Filter the files and queue up the directories.
|
||||
// 3. Process any package.json in the files
|
||||
// 4. recur.
|
||||
return readDir(dir)
|
||||
.then(function(files){
|
||||
return q.all(files.map(function(filePath) {
|
||||
return realpath(path.join(dir, filePath));
|
||||
}));
|
||||
})
|
||||
.then(function(filePaths) {
|
||||
filePaths = filePaths.filter(function(filePath) {
|
||||
return !self._ignoreFilePath(filePath);
|
||||
});
|
||||
|
||||
var statsP = filePaths.map(function(filePath) {
|
||||
return lstat(filePath);
|
||||
});
|
||||
|
||||
return [
|
||||
filePaths,
|
||||
q.all(statsP)
|
||||
];
|
||||
})
|
||||
.spread(function(files, stats) {
|
||||
var modulePaths = files.filter(function(filePath, i) {
|
||||
if (stats[i].isDirectory()) {
|
||||
self._queue.push(filePath);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (stats[i].isSymbolicLink()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return filePath.match(/\.js$/);
|
||||
});
|
||||
|
||||
var processing = self._findAndProcessPackage(files, dir)
|
||||
.then(function() {
|
||||
return q.all(modulePaths.map(self._processModule.bind(self)));
|
||||
});
|
||||
|
||||
return q.all([
|
||||
processing,
|
||||
self._search()
|
||||
]);
|
||||
})
|
||||
.then(function() {
|
||||
return self;
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Given a list of files find a `package.json` file, and if found parse it
|
||||
* and update indices.
|
||||
*/
|
||||
DependecyGraph.prototype._findAndProcessPackage = function(files, root) {
|
||||
var self = this;
|
||||
|
||||
var packagePath;
|
||||
for (var i = 0; i < files.length ; i++) {
|
||||
var file = files[i];
|
||||
if (path.basename(file) === 'package.json') {
|
||||
packagePath = file;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (packagePath != null) {
|
||||
return readFile(packagePath, 'utf8')
|
||||
.then(function(content) {
|
||||
var packageJson = JSON.parse(content);
|
||||
if (packageJson.name == null) {
|
||||
|
||||
debug(
|
||||
'WARNING: package.json `%s` is missing a name field',
|
||||
packagePath
|
||||
);
|
||||
return q();
|
||||
}
|
||||
|
||||
packageJson._root = root;
|
||||
self._packageByRoot[root] = packageJson;
|
||||
self._packagesById[packageJson.name] = packageJson;
|
||||
|
||||
return packageJson;
|
||||
});
|
||||
} else {
|
||||
return q();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Parse a module and update indices.
|
||||
*/
|
||||
DependecyGraph.prototype._processModule = function(modulePath) {
|
||||
var self = this;
|
||||
return readFile(modulePath, 'utf8')
|
||||
.then(function(content) {
|
||||
var moduleDocBlock = docblock.parseAsObject(content);
|
||||
var moduleData = { path: path.resolve(modulePath) };
|
||||
if (moduleDocBlock.providesModule || moduleDocBlock.provides) {
|
||||
moduleData.id =
|
||||
moduleDocBlock.providesModule || moduleDocBlock.provides;
|
||||
} else {
|
||||
moduleData.id = self._lookupName(modulePath);
|
||||
}
|
||||
moduleData.dependencies = extractRequires(content);
|
||||
|
||||
var module = new ModuleDescriptor(moduleData);
|
||||
self._updateGraphWithModule(module);
|
||||
return module;
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Compute the name of module relative to a package it may belong to.
|
||||
*/
|
||||
DependecyGraph.prototype._lookupName = function(modulePath) {
|
||||
var packageJson = this._lookupPackage(modulePath);
|
||||
if (packageJson == null) {
|
||||
return path.resolve(modulePath);
|
||||
} else {
|
||||
var relativePath =
|
||||
sansExtJs(path.relative(packageJson._root, modulePath));
|
||||
return path.join(packageJson.name, relativePath);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Update the graph and idices with the module.
|
||||
*/
|
||||
DependecyGraph.prototype._updateGraphWithModule = function(module) {
|
||||
this._graph[module.path] = module;
|
||||
|
||||
if (this._moduleById[module.id]) {
|
||||
debug(
|
||||
'WARNING: Top-level module name conflict `%s`.\n' +
|
||||
'module with path `%s` will replace `%s`',
|
||||
module.id,
|
||||
module.path,
|
||||
this._moduleById[module.id].path
|
||||
);
|
||||
}
|
||||
|
||||
this._moduleById[module.id] = module;
|
||||
};
|
||||
|
||||
/**
|
||||
* Find the nearest package to a module.
|
||||
*/
|
||||
DependecyGraph.prototype._lookupPackage = function(modulePath) {
|
||||
var root = this._root;
|
||||
var packageByRoot = this._packageByRoot;
|
||||
|
||||
/**
|
||||
* Auxiliary function to recursively lookup a package.
|
||||
*/
|
||||
function lookupPackage(currDir) {
|
||||
// ideally we stop once we're outside root and this can be a simple child
|
||||
// dir check. However, we have to support modules that was symlinked inside
|
||||
// our project root.
|
||||
if (!path.relative(root, currDir) === '' || currDir === '.'
|
||||
|| currDir === '/') {
|
||||
return null;
|
||||
} else {
|
||||
var packageJson = packageByRoot[currDir];
|
||||
if (packageJson) {
|
||||
return packageJson;
|
||||
} else {
|
||||
return lookupPackage(path.dirname(currDir));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return lookupPackage(path.dirname(modulePath));
|
||||
};
|
||||
|
||||
/**
|
||||
* Process a filewatcher change event.
|
||||
*/
|
||||
DependecyGraph.prototype._processFileChange = function(eventType, filePath, root, stat) {
|
||||
var absPath = path.join(this._root, filePath);
|
||||
if (this._ignoreFilePath(absPath)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (eventType === 'delete') {
|
||||
var module = this._graph[absPath];
|
||||
if (module == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
delete this._graph[module];
|
||||
|
||||
// Others may keep a reference so we mark it as deleted.
|
||||
module.deleted = true;
|
||||
|
||||
// Modules may have same id.
|
||||
if (this._moduleById[module.id] === module) {
|
||||
delete this._moduleById[module.id];
|
||||
}
|
||||
} else if (!(stat && stat.isDirectory())) {
|
||||
var self = this;
|
||||
this._loading = this._loading.then(function() {
|
||||
return self._processModule(absPath);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Extract all required modules from a `code` string.
|
||||
*/
|
||||
var requireRe = /\brequire\s*\(\s*[\'"]([^"\']+)["\']\s*\)/g;
|
||||
var blockCommentRe = /\/\*(.|\n)*?\*\//g;
|
||||
var lineCommentRe = /\/\/.+(\n|$)/g;
|
||||
function extractRequires(code) {
|
||||
var deps = [];
|
||||
|
||||
code
|
||||
.replace(blockCommentRe, '')
|
||||
.replace(lineCommentRe, '')
|
||||
.replace(requireRe, function(match, dep) {
|
||||
deps.push(dep);
|
||||
});
|
||||
|
||||
return deps;
|
||||
}
|
||||
|
||||
/**
|
||||
* `file` without the .js extension.
|
||||
*/
|
||||
function sansExtJs(file) {
|
||||
if (file.match(/\.js$/)) {
|
||||
return file.slice(0, -3);
|
||||
} else {
|
||||
return file;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* `file` with the .js extension.
|
||||
*/
|
||||
function withExtJs(file) {
|
||||
if (file.match(/\.js$/)) {
|
||||
return file;
|
||||
} else {
|
||||
return file + '.js';
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = DependecyGraph;
|
152
react-packager/src/DependencyResolver/haste/__tests__/HasteDependencyResolver-test.js
vendored
Normal file
152
react-packager/src/DependencyResolver/haste/__tests__/HasteDependencyResolver-test.js
vendored
Normal file
@ -0,0 +1,152 @@
|
||||
|
||||
jest.dontMock('../')
|
||||
.dontMock('q')
|
||||
.setMock('../../ModuleDescriptor', function(data) {return data;});
|
||||
|
||||
var q = require('q');
|
||||
|
||||
describe('HasteDependencyResolver', function() {
|
||||
var HasteDependencyResolver;
|
||||
var DependencyGraph;
|
||||
|
||||
beforeEach(function() {
|
||||
// For the polyfillDeps
|
||||
require('path').join.mockImpl(function(a, b) {
|
||||
return b;
|
||||
});
|
||||
HasteDependencyResolver = require('../');
|
||||
DependencyGraph = require('../DependencyGraph');
|
||||
});
|
||||
|
||||
describe('getDependencies', function() {
|
||||
pit('should get dependencies with polyfills', function() {
|
||||
var module = {id: 'index', path: '/root/index.js', dependencies: ['a']};
|
||||
var deps = [module];
|
||||
|
||||
var depResolver = new HasteDependencyResolver({
|
||||
projectRoot: '/root'
|
||||
});
|
||||
|
||||
// Is there a better way? How can I mock the prototype instead?
|
||||
var depGraph = depResolver._depGraph;
|
||||
depGraph.getOrderedDependencies.mockImpl(function() {
|
||||
return deps;
|
||||
});
|
||||
depGraph.load.mockImpl(function() {
|
||||
return q();
|
||||
});
|
||||
|
||||
return depResolver.getDependencies('/root/index.js')
|
||||
.then(function(result) {
|
||||
expect(result.mainModuleId).toEqual('index');
|
||||
expect(result.dependencies).toEqual([
|
||||
{ path: 'polyfills/prelude.js',
|
||||
id: 'polyfills/prelude.js',
|
||||
isPolyfill: true,
|
||||
dependencies: []
|
||||
},
|
||||
{ path: 'polyfills/require.js',
|
||||
id: 'polyfills/require.js',
|
||||
isPolyfill: true,
|
||||
dependencies: ['polyfills/prelude.js']
|
||||
},
|
||||
{ path: 'polyfills/polyfills.js',
|
||||
id: 'polyfills/polyfills.js',
|
||||
isPolyfill: true,
|
||||
dependencies: ['polyfills/prelude.js', 'polyfills/require.js']
|
||||
},
|
||||
module
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
pit('should pass in more polyfills', function() {
|
||||
var module = {id: 'index', path: '/root/index.js', dependencies: ['a']};
|
||||
var deps = [module];
|
||||
|
||||
var depResolver = new HasteDependencyResolver({
|
||||
projectRoot: '/root',
|
||||
polyfillModuleNames: ['some module']
|
||||
});
|
||||
|
||||
// Is there a better way? How can I mock the prototype instead?
|
||||
var depGraph = depResolver._depGraph;
|
||||
depGraph.getOrderedDependencies.mockImpl(function() {
|
||||
return deps;
|
||||
});
|
||||
depGraph.load.mockImpl(function() {
|
||||
return q();
|
||||
});
|
||||
|
||||
return depResolver.getDependencies('/root/index.js')
|
||||
.then(function(result) {
|
||||
expect(result.mainModuleId).toEqual('index');
|
||||
expect(result.dependencies).toEqual([
|
||||
{ path: 'polyfills/prelude.js',
|
||||
id: 'polyfills/prelude.js',
|
||||
isPolyfill: true,
|
||||
dependencies: []
|
||||
},
|
||||
{ path: 'polyfills/require.js',
|
||||
id: 'polyfills/require.js',
|
||||
isPolyfill: true,
|
||||
dependencies: ['polyfills/prelude.js']
|
||||
},
|
||||
{ path: 'polyfills/polyfills.js',
|
||||
id: 'polyfills/polyfills.js',
|
||||
isPolyfill: true,
|
||||
dependencies: ['polyfills/prelude.js', 'polyfills/require.js']
|
||||
},
|
||||
{ path: 'some module',
|
||||
id: 'some module',
|
||||
isPolyfill: true,
|
||||
dependencies: [ 'polyfills/prelude.js', 'polyfills/require.js',
|
||||
'polyfills/polyfills.js']
|
||||
},
|
||||
module
|
||||
]);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('wrapModule', function() {
|
||||
it('should ', function() {
|
||||
var depResolver = new HasteDependencyResolver({
|
||||
projectRoot: '/root'
|
||||
});
|
||||
|
||||
var depGraph = depResolver._depGraph;
|
||||
var dependencies = ['x', 'y', 'z']
|
||||
var code = [
|
||||
'require("x")',
|
||||
'require("y")',
|
||||
'require("z")',
|
||||
].join('\n');
|
||||
|
||||
depGraph.resolveDependency.mockImpl(function(fromModule, toModuleName) {
|
||||
if (toModuleName === 'x') {
|
||||
return {
|
||||
id: 'changed'
|
||||
};
|
||||
} else if (toModuleName === 'y') {
|
||||
return { id: 'y' };
|
||||
}
|
||||
return null;
|
||||
});
|
||||
|
||||
var processedCode = depResolver.wrapModule({
|
||||
id: 'test module',
|
||||
path: '/root/test.js',
|
||||
dependencies: dependencies
|
||||
}, code);
|
||||
|
||||
expect(processedCode).toEqual([
|
||||
"__d('test module',[\"changed\",\"y\"],function(global," +
|
||||
" require, requireDynamic, requireLazy, module, exports) {" +
|
||||
" require('changed')",
|
||||
"require('y')",
|
||||
'require("z")});',
|
||||
].join('\n'));
|
||||
});
|
||||
});
|
||||
});
|
121
react-packager/src/DependencyResolver/haste/index.js
vendored
Normal file
121
react-packager/src/DependencyResolver/haste/index.js
vendored
Normal file
@ -0,0 +1,121 @@
|
||||
'use strict';
|
||||
|
||||
var path = require('path');
|
||||
var FileWatcher = require('../../FileWatcher');
|
||||
var DependencyGraph = require('./DependencyGraph');
|
||||
var ModuleDescriptor = require('../ModuleDescriptor');
|
||||
|
||||
var DEFAULT_POLYFILLS = [
|
||||
|
||||
];
|
||||
|
||||
var DEFINE_MODULE_CODE =
|
||||
'__d(' +
|
||||
'\'_moduleName_\',' +
|
||||
'_deps_,' +
|
||||
'function(global, require, requireDynamic, requireLazy, module, exports) {'+
|
||||
' _code_' +
|
||||
'}' +
|
||||
');';
|
||||
|
||||
var DEFINE_MODULE_REPLACE_RE = /_moduleName_|_code_|_deps_/g;
|
||||
|
||||
var REL_REQUIRE_STMT = /require\(['"]([\.\/0-9A-Z_$\-]*)['"]\)/gi;
|
||||
|
||||
function HasteDependencyResolver(config) {
|
||||
this._depGraph = new DependencyGraph({
|
||||
root: config.projectRoot,
|
||||
ignoreFilePath: function(filepath) {
|
||||
return filepath.indexOf('__tests__') !== -1 ||
|
||||
(config.blacklistRE && config.blacklistRE.test(filepath));
|
||||
},
|
||||
fileWatcher: new FileWatcher(config)
|
||||
});
|
||||
|
||||
this._polyfillModuleNames = [
|
||||
config.dev
|
||||
? path.join(__dirname, 'polyfills/prelude_dev.js')
|
||||
: path.join(__dirname, 'polyfills/prelude.js'),
|
||||
path.join(__dirname, 'polyfills/require.js'),
|
||||
path.join(__dirname, 'polyfills/polyfills.js'),
|
||||
].concat(
|
||||
config.polyfillModuleNames || []
|
||||
);
|
||||
}
|
||||
|
||||
HasteDependencyResolver.prototype.getDependencies = function(main) {
|
||||
var depGraph = this._depGraph;
|
||||
var self = this;
|
||||
|
||||
return depGraph.load()
|
||||
.then(function() {
|
||||
var dependencies = depGraph.getOrderedDependencies(main);
|
||||
var mainModuleId = dependencies[0].id;
|
||||
|
||||
self._prependPolyfillDependencies(dependencies);
|
||||
|
||||
return {
|
||||
mainModuleId: mainModuleId,
|
||||
dependencies: dependencies
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
HasteDependencyResolver.prototype._prependPolyfillDependencies = function(
|
||||
dependencies
|
||||
) {
|
||||
var polyfillModuleNames = this._polyfillModuleNames;
|
||||
if (polyfillModuleNames.length > 0) {
|
||||
var polyfillModules = polyfillModuleNames.map(
|
||||
function(polyfillModuleName, idx) {
|
||||
return new ModuleDescriptor({
|
||||
path: polyfillModuleName,
|
||||
id: polyfillModuleName,
|
||||
dependencies: polyfillModuleNames.slice(0, idx),
|
||||
isPolyfill: true
|
||||
});
|
||||
}
|
||||
);
|
||||
dependencies.unshift.apply(dependencies, polyfillModules);
|
||||
}
|
||||
};
|
||||
|
||||
HasteDependencyResolver.prototype.wrapModule = function(module, code) {
|
||||
if (module.isPolyfill) {
|
||||
return code;
|
||||
}
|
||||
|
||||
var depGraph = this._depGraph;
|
||||
var resolvedDeps = Object.create(null);
|
||||
var resolvedDepsArr = [];
|
||||
|
||||
for (var i = 0; i < module.dependencies.length; i++) {
|
||||
var depName = module.dependencies[i];
|
||||
var dep = this._depGraph.resolveDependency(module, depName);
|
||||
if (dep) {
|
||||
resolvedDeps[depName] = dep.id;
|
||||
resolvedDepsArr.push(dep.id);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
var relativizedCode =
|
||||
code.replace(REL_REQUIRE_STMT, function(codeMatch, depName) {
|
||||
var dep = resolvedDeps[depName];
|
||||
if (dep != null) {
|
||||
return 'require(\'' + dep + '\')';
|
||||
} else {
|
||||
return codeMatch;
|
||||
}
|
||||
});
|
||||
|
||||
return DEFINE_MODULE_CODE.replace(DEFINE_MODULE_REPLACE_RE, function(key) {
|
||||
return {
|
||||
'_moduleName_': module.id,
|
||||
'_code_': relativizedCode,
|
||||
'_deps_': JSON.stringify(resolvedDepsArr),
|
||||
}[key];
|
||||
});
|
||||
};
|
||||
|
||||
module.exports = HasteDependencyResolver;
|
75
react-packager/src/DependencyResolver/haste/polyfills/polyfills.js
vendored
Normal file
75
react-packager/src/DependencyResolver/haste/polyfills/polyfills.js
vendored
Normal file
@ -0,0 +1,75 @@
|
||||
/**
|
||||
* Copyright 2013 Facebook, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
* This pipes all of our console logging functions to native logging so that
|
||||
* JavaScript errors in required modules show up in Xcode via NSLog.
|
||||
*
|
||||
* @provides Object.es6
|
||||
* @polyfill
|
||||
*/
|
||||
|
||||
// WARNING: This is an optimized version that fails on hasOwnProperty checks
|
||||
// and non objects. It's not spec-compliant. It's a perf optimization.
|
||||
|
||||
Object.assign = function(target, sources) {
|
||||
if (__DEV__) {
|
||||
if (target == null) {
|
||||
throw new TypeError('Object.assign target cannot be null or undefined');
|
||||
}
|
||||
if (typeof target !== 'object' && typeof target !== 'function') {
|
||||
throw new TypeError(
|
||||
'In this environment the target of assign MUST be an object.' +
|
||||
'This error is a performance optimization and not spec compliant.'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
for (var nextIndex = 1; nextIndex < arguments.length; nextIndex++) {
|
||||
var nextSource = arguments[nextIndex];
|
||||
if (nextSource == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (__DEV__) {
|
||||
if (typeof nextSource !== 'object' &&
|
||||
typeof nextSource !== 'function') {
|
||||
throw new TypeError(
|
||||
'In this environment the target of assign MUST be an object.' +
|
||||
'This error is a performance optimization and not spec compliant.'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// We don't currently support accessors nor proxies. Therefore this
|
||||
// copy cannot throw. If we ever supported this then we must handle
|
||||
// exceptions and side-effects.
|
||||
|
||||
for (var key in nextSource) {
|
||||
if (__DEV__) {
|
||||
var hasOwnProperty = Object.prototype.hasOwnProperty;
|
||||
if (!hasOwnProperty.call(nextSource, key)) {
|
||||
throw new TypeError(
|
||||
'One of the sources to assign has an enumerable key on the ' +
|
||||
'prototype chain. This is an edge case that we do not support. ' +
|
||||
'This error is a performance optimization and not spec compliant.'
|
||||
);
|
||||
}
|
||||
}
|
||||
target[key] = nextSource[key];
|
||||
}
|
||||
}
|
||||
|
||||
return target;
|
||||
};
|
1
react-packager/src/DependencyResolver/haste/polyfills/prelude.js
vendored
Normal file
1
react-packager/src/DependencyResolver/haste/polyfills/prelude.js
vendored
Normal file
@ -0,0 +1 @@
|
||||
__DEV__ = false;
|
1
react-packager/src/DependencyResolver/haste/polyfills/prelude_dev.js
vendored
Normal file
1
react-packager/src/DependencyResolver/haste/polyfills/prelude_dev.js
vendored
Normal file
@ -0,0 +1 @@
|
||||
__DEV__ = true;
|
626
react-packager/src/DependencyResolver/haste/polyfills/require.js
vendored
Normal file
626
react-packager/src/DependencyResolver/haste/polyfills/require.js
vendored
Normal file
@ -0,0 +1,626 @@
|
||||
(function(global) {
|
||||
|
||||
// avoid redefining require()
|
||||
if (global.require) {
|
||||
return;
|
||||
}
|
||||
|
||||
var __DEV__ = global.__DEV__;
|
||||
|
||||
var toString = Object.prototype.toString;
|
||||
|
||||
/**
|
||||
* module index: {
|
||||
* mod1: {
|
||||
* exports: { ... },
|
||||
* id: 'mod1',
|
||||
* dependencies: ['mod1', 'mod2'],
|
||||
* factory: function() { ... },
|
||||
* waitingMap: { mod1: 1, mod3: 1, mod4: 1 },
|
||||
* waiting: 2
|
||||
* }
|
||||
* }
|
||||
*/
|
||||
var modulesMap = {},
|
||||
/**
|
||||
* inverse index: {
|
||||
* mod1: [modules, waiting for mod1],
|
||||
* mod2: [modules, waiting for mod2]
|
||||
* }
|
||||
*/
|
||||
dependencyMap = {},
|
||||
/**
|
||||
* modules whose reference counts are set out of order
|
||||
*/
|
||||
predefinedRefCounts = {},
|
||||
|
||||
_counter = 0,
|
||||
|
||||
REQUIRE_WHEN_READY = 1,
|
||||
USED_AS_TRANSPORT = 2,
|
||||
|
||||
hop = Object.prototype.hasOwnProperty;
|
||||
|
||||
function _debugUnresolvedDependencies(names) {
|
||||
var unresolved = Array.prototype.slice.call(names);
|
||||
var visited = {};
|
||||
var ii, name, module, dependency;
|
||||
|
||||
while (unresolved.length) {
|
||||
name = unresolved.shift();
|
||||
if (visited[name]) {
|
||||
continue;
|
||||
}
|
||||
visited[name] = true;
|
||||
|
||||
module = modulesMap[name];
|
||||
if (!module || !module.waiting) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (ii = 0; ii < module.dependencies.length; ii++) {
|
||||
dependency = module.dependencies[ii];
|
||||
if (!modulesMap[dependency] || modulesMap[dependency].waiting) {
|
||||
unresolved.push(dependency);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (name in visited) if (hop.call(visited, name)) {
|
||||
unresolved.push(name);
|
||||
}
|
||||
|
||||
var messages = [];
|
||||
for (ii = 0; ii < unresolved.length; ii++) {
|
||||
name = unresolved[ii];
|
||||
var message = name;
|
||||
module = modulesMap[name];
|
||||
if (!module) {
|
||||
message += ' is not defined';
|
||||
} else if (!module.waiting) {
|
||||
message += ' is ready';
|
||||
} else {
|
||||
var unresolvedDependencies = [];
|
||||
for (var jj = 0; jj < module.dependencies.length; jj++) {
|
||||
dependency = module.dependencies[jj];
|
||||
if (!modulesMap[dependency] || modulesMap[dependency].waiting) {
|
||||
unresolvedDependencies.push(dependency);
|
||||
}
|
||||
}
|
||||
message += ' is waiting for ' + unresolvedDependencies.join(', ');
|
||||
}
|
||||
messages.push(message);
|
||||
}
|
||||
return messages.join('\n');
|
||||
}
|
||||
|
||||
/**
|
||||
* This is mainly for logging in ModuleErrorLogger.
|
||||
*/
|
||||
function ModuleError(msg) {
|
||||
this.name = 'ModuleError';
|
||||
this.message = msg;
|
||||
this.stack = Error(msg).stack;
|
||||
this.framesToPop = 2;
|
||||
}
|
||||
ModuleError.prototype = Object.create(Error.prototype);
|
||||
ModuleError.prototype.constructor = ModuleError;
|
||||
|
||||
var _performance =
|
||||
global.performance ||
|
||||
global.msPerformance ||
|
||||
global.webkitPerformance || {};
|
||||
|
||||
if (!_performance.now) {
|
||||
_performance = global.Date;
|
||||
}
|
||||
|
||||
var _now = _performance ?
|
||||
_performance.now.bind(_performance) : function(){return 0;};
|
||||
|
||||
var _factoryStackCount = 0;
|
||||
var _factoryTime = 0;
|
||||
var _totalFactories = 0;
|
||||
|
||||
/**
|
||||
* The require function conforming to CommonJS spec:
|
||||
* http://wiki.commonjs.org/wiki/Modules/1.1.1
|
||||
*
|
||||
* To define a CommonJS-compliant module add the providesModule
|
||||
* Haste header to your file instead of @provides. Your file is going
|
||||
* to be executed in a separate context. Every variable/function you
|
||||
* define will be local (private) to that module. To export local members
|
||||
* use "exports" variable or return the exported value at the end of your
|
||||
* file. Your code will have access to the "module" object.
|
||||
* The "module" object will have an "id" property that is the id of your
|
||||
* current module. "module" object will also have "exports" property that
|
||||
* is the same as "exports" variable passed into your module context.
|
||||
* You can require other modules using their ids.
|
||||
*
|
||||
* Haste will automatically pick dependencies from require() calls. So
|
||||
* you don't have to manually specify @requires in your header.
|
||||
*
|
||||
* You cannot require() modules from non-CommonJS files. Write a legacy stub
|
||||
* (@providesLegacy) and use @requires instead.
|
||||
*
|
||||
* @example
|
||||
*
|
||||
* / **
|
||||
* * @providesModule math
|
||||
* * /
|
||||
* exports.add = function() {
|
||||
* var sum = 0, i = 0, args = arguments, l = args.length;
|
||||
* while (i < l) {
|
||||
* sum += args[i++];
|
||||
* }
|
||||
* return sum;
|
||||
* };
|
||||
*
|
||||
* / **
|
||||
* * @providesModule increment
|
||||
* * /
|
||||
* var add = require('math').add;
|
||||
* return function(val) {
|
||||
* return add(val, 1);
|
||||
* };
|
||||
*
|
||||
* / **
|
||||
* * @providesModule program
|
||||
* * /
|
||||
* var inc = require('increment');
|
||||
* var a = 1;
|
||||
* inc(a); // 2
|
||||
*
|
||||
* module.id == "program";
|
||||
*
|
||||
*
|
||||
* @param {String} id
|
||||
* @throws when module is not loaded or not ready to be required
|
||||
*/
|
||||
function require(id) {
|
||||
var module = modulesMap[id], dep, i, msg;
|
||||
if (module && module.exports) {
|
||||
// If ref count is 1, this was the last call, so undefine the module.
|
||||
// The ref count can be null or undefined, but those are never === 1.
|
||||
if (module.refcount-- === 1) {
|
||||
delete modulesMap[id];
|
||||
}
|
||||
return module.exports;
|
||||
}
|
||||
|
||||
if (global.ErrorUtils && !global.ErrorUtils.inGuard()) {
|
||||
return ErrorUtils.applyWithGuard(require, this, arguments);
|
||||
}
|
||||
|
||||
if (!module) {
|
||||
msg = 'Requiring unknown module "' + id + '"';
|
||||
if (__DEV__) {
|
||||
msg += '. It may not be loaded yet. Did you forget to run arc build?';
|
||||
}
|
||||
throw new ModuleError(msg);
|
||||
}
|
||||
|
||||
if (module.hasError) {
|
||||
throw new ModuleError(
|
||||
'Requiring module "' + id + '" which threw an exception'
|
||||
);
|
||||
}
|
||||
|
||||
if (module.waiting) {
|
||||
throw new ModuleError(
|
||||
'Requiring module "' + id + '" with unresolved dependencies: ' +
|
||||
_debugUnresolvedDependencies([id])
|
||||
);
|
||||
}
|
||||
|
||||
var exports = module.exports = {};
|
||||
var factory = module.factory;
|
||||
if (toString.call(factory) === '[object Function]') {
|
||||
var args = [],
|
||||
dependencies = module.dependencies,
|
||||
length = dependencies.length,
|
||||
ret;
|
||||
if (module.special & USED_AS_TRANSPORT) {
|
||||
length = Math.min(length, factory.length);
|
||||
}
|
||||
try {
|
||||
for (i = 0; args.length < length; i++) {
|
||||
dep = dependencies[i];
|
||||
if (!module.inlineRequires[dep]) {
|
||||
args.push(dep === 'module' ? module :
|
||||
(dep === 'exports' ? exports :
|
||||
require.call(null, dep)));
|
||||
}
|
||||
}
|
||||
|
||||
++_totalFactories;
|
||||
if (_factoryStackCount++ === 0) {
|
||||
_factoryTime -= _now();
|
||||
}
|
||||
try {
|
||||
ret = factory.apply(module.context || global, args);
|
||||
} catch (e) {
|
||||
if (modulesMap.ex && modulesMap.erx) {
|
||||
// when ErrorUtils is ready, ex and erx are ready. otherwise, we
|
||||
// don't append module id to the error message but still throw it
|
||||
var ex = require.call(null, 'ex');
|
||||
var erx = require.call(null, 'erx');
|
||||
var messageWithParams = erx(e.message);
|
||||
if (messageWithParams[0].indexOf(' from module "%s"') < 0) {
|
||||
messageWithParams[0] += ' from module "%s"';
|
||||
messageWithParams[messageWithParams.length] = id;
|
||||
}
|
||||
e.message = ex.apply(null, messageWithParams);
|
||||
}
|
||||
throw e;
|
||||
} finally {
|
||||
if (--_factoryStackCount === 0) {
|
||||
_factoryTime += _now();
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
module.hasError = true;
|
||||
module.exports = null;
|
||||
throw e;
|
||||
}
|
||||
if (ret) {
|
||||
if (__DEV__) {
|
||||
if (typeof ret != 'object' && typeof ret != 'function') {
|
||||
throw new ModuleError(
|
||||
'Factory for module "' + id + '" returned ' +
|
||||
'an invalid value "' + ret + '". ' +
|
||||
'Returned value should be either a function or an object.'
|
||||
);
|
||||
}
|
||||
}
|
||||
module.exports = ret;
|
||||
}
|
||||
} else {
|
||||
module.exports = factory;
|
||||
}
|
||||
|
||||
// If ref count is 1, this was the last call, so undefine the module.
|
||||
// The ref count can be null or undefined, but those are never === 1.
|
||||
if (module.refcount-- === 1) {
|
||||
delete modulesMap[id];
|
||||
}
|
||||
return module.exports;
|
||||
}
|
||||
|
||||
require.__getFactoryTime = function() {
|
||||
return (_factoryStackCount ? _now() : 0) + _factoryTime;
|
||||
};
|
||||
|
||||
require.__getTotalFactories = function() {
|
||||
return _totalFactories;
|
||||
};
|
||||
|
||||
/**
|
||||
* The define function conforming to CommonJS proposal:
|
||||
* http://wiki.commonjs.org/wiki/Modules/AsynchronousDefinition
|
||||
*
|
||||
* define() allows you to explicitly state dependencies of your module
|
||||
* in javascript. It's most useful in non-CommonJS files.
|
||||
*
|
||||
* define() is used internally by haste as a transport for CommonJS
|
||||
* modules. So there's no need to use define() if you use providesModule
|
||||
*
|
||||
* @example
|
||||
* / **
|
||||
* * @provides alpha
|
||||
* * /
|
||||
*
|
||||
* // Sets up the module with ID of "alpha", that uses require,
|
||||
* // exports and the module with ID of "beta":
|
||||
* define("alpha", ["require", "exports", "beta"],
|
||||
* function (require, exports, beta) {
|
||||
* exports.verb = function() {
|
||||
* return beta.verb();
|
||||
* //Or:
|
||||
* return require("beta").verb();
|
||||
* }
|
||||
* });
|
||||
*
|
||||
* / **
|
||||
* * @provides alpha
|
||||
* * /
|
||||
* // An anonymous module could be defined (module id derived from filename)
|
||||
* // that returns an object literal:
|
||||
*
|
||||
* define(["alpha"], function (alpha) {
|
||||
* return {
|
||||
* verb: function(){
|
||||
* return alpha.verb() + 2;
|
||||
* }
|
||||
* };
|
||||
* });
|
||||
*
|
||||
* / **
|
||||
* * @provides alpha
|
||||
* * /
|
||||
* // A dependency-free module can define a direct object literal:
|
||||
*
|
||||
* define({
|
||||
* add: function(x, y){
|
||||
* return x + y;
|
||||
* }
|
||||
* });
|
||||
*
|
||||
* @param {String} id optional
|
||||
* @param {Array} dependencies optional
|
||||
* @param {Object|Function} factory
|
||||
*/
|
||||
function define(id, dependencies, factory,
|
||||
_special, _context, _refCount, _inlineRequires) {
|
||||
if (dependencies === undefined) {
|
||||
dependencies = [];
|
||||
factory = id;
|
||||
id = _uid();
|
||||
} else if (factory === undefined) {
|
||||
factory = dependencies;
|
||||
if (toString.call(id) === '[object Array]') {
|
||||
dependencies = id;
|
||||
id = _uid();
|
||||
} else {
|
||||
dependencies = [];
|
||||
}
|
||||
}
|
||||
|
||||
// Non-standard: we allow modules to be undefined. This is designed for
|
||||
// temporary modules.
|
||||
var canceler = { cancel: _undefine.bind(this, id) };
|
||||
|
||||
var record = modulesMap[id];
|
||||
|
||||
// Nonstandard hack: we call define with null deps and factory, but a
|
||||
// non-null reference count (e.g. define('name', null, null, 0, null, 4))
|
||||
// when this module is defined elsewhere and we just need to update the
|
||||
// reference count. We use this hack to avoid having to expose another
|
||||
// global function to increment ref counts.
|
||||
if (record) {
|
||||
if (_refCount) {
|
||||
record.refcount += _refCount;
|
||||
}
|
||||
// Calling define() on a pre-existing module does not redefine it
|
||||
return canceler;
|
||||
} else if (!dependencies && !factory && _refCount) {
|
||||
// If this module hasn't been defined yet, store the ref count. We'll use
|
||||
// it when the module is defined later.
|
||||
predefinedRefCounts[id] = (predefinedRefCounts[id] || 0) + _refCount;
|
||||
return canceler;
|
||||
} else {
|
||||
// Defining a new module
|
||||
record = { id: id };
|
||||
record.refcount = (predefinedRefCounts[id] || 0) + (_refCount || 0);
|
||||
delete predefinedRefCounts[id];
|
||||
}
|
||||
|
||||
if (__DEV__) {
|
||||
if (
|
||||
!factory ||
|
||||
(typeof factory != 'object' && typeof factory != 'function' &&
|
||||
typeof factory != 'string')) {
|
||||
throw new ModuleError(
|
||||
'Invalid factory "' + factory + '" for module "' + id + '". ' +
|
||||
'Factory should be either a function or an object.'
|
||||
);
|
||||
}
|
||||
|
||||
if (toString.call(dependencies) !== '[object Array]') {
|
||||
throw new ModuleError(
|
||||
'Invalid dependencies for module "' + id + '". ' +
|
||||
'Dependencies must be passed as an array.'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
record.factory = factory;
|
||||
record.dependencies = dependencies;
|
||||
record.context = _context;
|
||||
record.special = _special;
|
||||
record.inlineRequires = _inlineRequires || {};
|
||||
record.waitingMap = {};
|
||||
record.waiting = 0;
|
||||
record.hasError = false;
|
||||
modulesMap[id] = record;
|
||||
_initDependencies(id);
|
||||
|
||||
return canceler;
|
||||
}
|
||||
|
||||
function _undefine(id) {
|
||||
if (!modulesMap[id]) {
|
||||
return;
|
||||
}
|
||||
|
||||
var module = modulesMap[id];
|
||||
delete modulesMap[id];
|
||||
|
||||
for (var dep in module.waitingMap) {
|
||||
if (module.waitingMap[dep]) {
|
||||
delete dependencyMap[dep][id];
|
||||
}
|
||||
}
|
||||
|
||||
for (var ii = 0; ii < module.dependencies.length; ii++) {
|
||||
dep = module.dependencies[ii];
|
||||
if (modulesMap[dep]) {
|
||||
if (modulesMap[dep].refcount-- === 1) {
|
||||
_undefine(dep);
|
||||
}
|
||||
} else if (predefinedRefCounts[dep]) {
|
||||
predefinedRefCounts[dep]--;
|
||||
}
|
||||
// Subtle: we won't account for this one fewer reference if we don't have
|
||||
// the dependency's definition or reference count yet.
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Special version of define that executes the factory as soon as all
|
||||
* dependencies are met.
|
||||
*
|
||||
* define() does just that, defines a module. Module's factory will not be
|
||||
* called until required by other module. This makes sense for most of our
|
||||
* library modules: we do not want to execute the factory unless it's being
|
||||
* used by someone.
|
||||
*
|
||||
* On the other hand there are modules, that you can call "entrance points".
|
||||
* You want to run the "factory" method for them as soon as all dependencies
|
||||
* are met.
|
||||
*
|
||||
* @example
|
||||
*
|
||||
* define('BaseClass', [], function() { return ... });
|
||||
* // ^^ factory for BaseClass was just stored in modulesMap
|
||||
*
|
||||
* define('SubClass', ['BaseClass'], function() { ... });
|
||||
* // SubClass module is marked as ready (waiting == 0), factory is just
|
||||
* // stored
|
||||
*
|
||||
* define('OtherClass, ['BaseClass'], function() { ... });
|
||||
* // OtherClass module is marked as ready (waiting == 0), factory is just
|
||||
* // stored
|
||||
*
|
||||
* requireLazy(['SubClass', 'ChatConfig'],
|
||||
* function() { ... });
|
||||
* // ChatRunner is waiting for ChatConfig to come
|
||||
*
|
||||
* define('ChatConfig', [], { foo: 'bar' });
|
||||
* // at this point ChatRunner is marked as ready, and its factory
|
||||
* // executed + all dependent factories are executed too: BaseClass,
|
||||
* // SubClass, ChatConfig notice that OtherClass's factory won't be
|
||||
* // executed unless explicitly required by someone
|
||||
*
|
||||
* @param {Array} dependencies
|
||||
* @param {Object|Function} factory
|
||||
*/
|
||||
function requireLazy(dependencies, factory, context) {
|
||||
return define(
|
||||
dependencies,
|
||||
factory,
|
||||
undefined,
|
||||
REQUIRE_WHEN_READY,
|
||||
context,
|
||||
1
|
||||
);
|
||||
}
|
||||
|
||||
function _uid() {
|
||||
return '__mod__' + _counter++;
|
||||
}
|
||||
|
||||
function _addDependency(module, dep) {
|
||||
// do not add duplicate dependencies and circ deps
|
||||
if (!module.waitingMap[dep] && module.id !== dep) {
|
||||
module.waiting++;
|
||||
module.waitingMap[dep] = 1;
|
||||
dependencyMap[dep] || (dependencyMap[dep] = {});
|
||||
dependencyMap[dep][module.id] = 1;
|
||||
}
|
||||
}
|
||||
|
||||
function _initDependencies(id) {
|
||||
var modulesToRequire = [];
|
||||
var module = modulesMap[id];
|
||||
var dep, i, subdep;
|
||||
|
||||
// initialize id's waitingMap
|
||||
for (i = 0; i < module.dependencies.length; i++) {
|
||||
dep = module.dependencies[i];
|
||||
if (!modulesMap[dep]) {
|
||||
_addDependency(module, dep);
|
||||
} else if (modulesMap[dep].waiting) {
|
||||
for (subdep in modulesMap[dep].waitingMap) {
|
||||
if (modulesMap[dep].waitingMap[subdep]) {
|
||||
_addDependency(module, subdep);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (module.waiting === 0 && module.special & REQUIRE_WHEN_READY) {
|
||||
modulesToRequire.push(id);
|
||||
}
|
||||
|
||||
// update modules depending on id
|
||||
if (dependencyMap[id]) {
|
||||
var deps = dependencyMap[id];
|
||||
var submodule;
|
||||
dependencyMap[id] = undefined;
|
||||
for (dep in deps) {
|
||||
submodule = modulesMap[dep];
|
||||
|
||||
// add all deps of id
|
||||
for (subdep in module.waitingMap) {
|
||||
if (module.waitingMap[subdep]) {
|
||||
_addDependency(submodule, subdep);
|
||||
}
|
||||
}
|
||||
// remove id itself
|
||||
if (submodule.waitingMap[id]) {
|
||||
submodule.waitingMap[id] = undefined;
|
||||
submodule.waiting--;
|
||||
}
|
||||
if (submodule.waiting === 0 &&
|
||||
submodule.special & REQUIRE_WHEN_READY) {
|
||||
modulesToRequire.push(dep);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// run everything that's ready
|
||||
for (i = 0; i < modulesToRequire.length; i++) {
|
||||
require.call(null, modulesToRequire[i]);
|
||||
}
|
||||
}
|
||||
|
||||
function _register(id, exports) {
|
||||
var module = modulesMap[id] = { id: id };
|
||||
module.exports = exports;
|
||||
module.refcount = 0;
|
||||
}
|
||||
|
||||
// pseudo name used in common-require
|
||||
// see require() function for more info
|
||||
_register('module', 0);
|
||||
_register('exports', 0);
|
||||
|
||||
_register('define', define);
|
||||
_register('global', global);
|
||||
_register('require', require);
|
||||
_register('requireDynamic', require);
|
||||
_register('requireLazy', requireLazy);
|
||||
|
||||
define.amd = {};
|
||||
|
||||
global.define = define;
|
||||
global.require = require;
|
||||
global.requireDynamic = require;
|
||||
global.requireLazy = requireLazy;
|
||||
|
||||
require.__debug = {
|
||||
modules: modulesMap,
|
||||
deps: dependencyMap,
|
||||
printDependencyInfo: function() {
|
||||
if (!global.console) {
|
||||
return;
|
||||
}
|
||||
var names = Object.keys(require.__debug.deps);
|
||||
global.console.log(_debugUnresolvedDependencies(names));
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* All @providesModule files are wrapped by this function by makehaste. It
|
||||
* is a convenience function around define() that prepends a bunch of required
|
||||
* modules (global, require, module, etc) so that we don't have to spit that
|
||||
* out for every module which would be a lot of extra bytes.
|
||||
*/
|
||||
global.__d = function(id, deps, factory, _special, _inlineRequires) {
|
||||
var defaultDeps = ['global', 'require', 'requireDynamic', 'requireLazy',
|
||||
'module', 'exports'];
|
||||
define(id, defaultDeps.concat(deps), factory, _special || USED_AS_TRANSPORT,
|
||||
null, null, _inlineRequires);
|
||||
};
|
||||
|
||||
})(this);
|
12
react-packager/src/DependencyResolver/index.js
vendored
Normal file
12
react-packager/src/DependencyResolver/index.js
vendored
Normal file
@ -0,0 +1,12 @@
|
||||
var HasteDependencyResolver = require('./haste');
|
||||
var NodeDependencyResolver = require('./node');
|
||||
|
||||
module.exports = function createDependencyResolver(options) {
|
||||
if (options.moduleFormat === 'haste') {
|
||||
return new HasteDependencyResolver(options);
|
||||
} else if (options.moduleFormat === 'node') {
|
||||
return new NodeDependencyResolver(options);
|
||||
} else {
|
||||
throw new Error('unsupported');
|
||||
}
|
||||
};
|
48
react-packager/src/DependencyResolver/node/index.js
vendored
Normal file
48
react-packager/src/DependencyResolver/node/index.js
vendored
Normal file
@ -0,0 +1,48 @@
|
||||
var Promise = require('q').Promise;
|
||||
var ModuleDescriptor = require('../ModuleDescriptor');
|
||||
|
||||
var mdeps = require('module-deps');
|
||||
var path = require('path');
|
||||
var fs = require('fs');
|
||||
|
||||
// var REQUIRE_RUNTIME = fs.readFileSync(
|
||||
// path.join(__dirname, 'require.js')
|
||||
// ).toString();
|
||||
|
||||
exports.getRuntimeCode = function() {
|
||||
return REQUIRE_RUNTIME;
|
||||
};
|
||||
|
||||
exports.wrapModule = function(id, source) {
|
||||
return Promise.resolve(
|
||||
'define(' + JSON.stringify(id) + ',' + ' function(exports, module) {\n'
|
||||
+ source + '\n});'
|
||||
);
|
||||
};
|
||||
|
||||
exports.getDependencies = function(root, fileEntryPath) {
|
||||
return new Promise(function(resolve, reject) {
|
||||
fileEntryPath = path.join(process.cwd(), root, fileEntryPath);
|
||||
|
||||
var md = mdeps();
|
||||
|
||||
md.end({file: fileEntryPath});
|
||||
|
||||
var deps = [];
|
||||
|
||||
md.on('data', function(data) {
|
||||
deps.push(
|
||||
new ModuleDescriptor({
|
||||
id: data.id,
|
||||
deps: data.deps,
|
||||
path: data.file,
|
||||
entry: data.entry
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
md.on('end', function() {
|
||||
resolve(deps);
|
||||
});
|
||||
});
|
||||
};
|
37
react-packager/src/FileWatcher/__tests__/FileWatcher-test.js
vendored
Normal file
37
react-packager/src/FileWatcher/__tests__/FileWatcher-test.js
vendored
Normal file
@ -0,0 +1,37 @@
|
||||
'use strict';
|
||||
|
||||
jest.dontMock('../')
|
||||
.dontMock('q')
|
||||
.setMock('child_process', { exec: function(cmd, cb) { cb(null, '/usr/bin/watchman') } });
|
||||
|
||||
describe('FileWatcher', function() {
|
||||
var FileWatcher;
|
||||
var Watcher;
|
||||
|
||||
beforeEach(function() {
|
||||
FileWatcher = require('../');
|
||||
Watcher = require('sane').WatchmanWatcher;
|
||||
Watcher.prototype.once.mockImplementation(function(type, callback) {
|
||||
callback();
|
||||
});
|
||||
});
|
||||
|
||||
pit('it should get the watcher instance when ready', function() {
|
||||
var fileWatcher = new FileWatcher({projectRoot: 'rootDir'});
|
||||
return fileWatcher.getWatcher().then(function(watcher) {
|
||||
expect(watcher instanceof Watcher).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
pit('it should end the watcher', function() {
|
||||
var fileWatcher = new FileWatcher({projectRoot: 'rootDir'});
|
||||
Watcher.prototype.close.mockImplementation(function(callback) {
|
||||
callback();
|
||||
});
|
||||
|
||||
return fileWatcher.end().then(function() {
|
||||
expect(Watcher.prototype.close).toBeCalled();
|
||||
});
|
||||
});
|
||||
|
||||
});
|
56
react-packager/src/FileWatcher/index.js
vendored
Normal file
56
react-packager/src/FileWatcher/index.js
vendored
Normal file
@ -0,0 +1,56 @@
|
||||
'use strict';
|
||||
|
||||
var sane = require('sane');
|
||||
var q = require('q');
|
||||
var exec = require('child_process').exec;
|
||||
|
||||
var Promise = q.Promise;
|
||||
|
||||
var detectingWatcherClass = new Promise(function(resolve, reject) {
|
||||
exec('which watchman', function(err, out) {
|
||||
if (err || out.length === 0) {
|
||||
resolve(sane.NodeWatcher);
|
||||
} else {
|
||||
resolve(sane.WatchmanWatcher);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
module.exports = FileWatcher;
|
||||
|
||||
var MAX_WAIT_TIME = 3000;
|
||||
|
||||
var memoizedInstances = Object.create(null);
|
||||
|
||||
function FileWatcher(projectConfig) {
|
||||
if (memoizedInstances[projectConfig.projectRoot]) {
|
||||
return memoizedInstances[projectConfig.projectRoot];
|
||||
} else {
|
||||
memoizedInstances[projectConfig.projectRoot] = this;
|
||||
}
|
||||
|
||||
this._loadingWatcher = detectingWatcherClass.then(function(Watcher) {
|
||||
var watcher = new Watcher(projectConfig.projectRoot, {glob: '**/*.js'});
|
||||
|
||||
return new Promise(function(resolve, reject) {
|
||||
var rejectTimeout = setTimeout(function() {
|
||||
reject(new Error('Watcher took too long to load.'));
|
||||
}, MAX_WAIT_TIME);
|
||||
|
||||
watcher.once('ready', function() {
|
||||
clearTimeout(rejectTimeout);
|
||||
resolve(watcher);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
FileWatcher.prototype.getWatcher = function() {
|
||||
return this._loadingWatcher;
|
||||
};
|
||||
|
||||
FileWatcher.prototype.end = function() {
|
||||
return this._loadingWatcher.then(function(watcher) {
|
||||
return q.ninvoke(watcher, 'close');
|
||||
});
|
||||
};
|
128
react-packager/src/JSTransformer/Cache.js
vendored
Normal file
128
react-packager/src/JSTransformer/Cache.js
vendored
Normal file
@ -0,0 +1,128 @@
|
||||
'use strict';
|
||||
|
||||
var path = require('path');
|
||||
var version = require('../../package.json').version;
|
||||
var tmpdir = require('os').tmpDir();
|
||||
var pathUtils = require('../fb-path-utils');
|
||||
var FileWatcher = require('../FileWatcher');
|
||||
var fs = require('fs');
|
||||
var _ = require('underscore');
|
||||
var q = require('q');
|
||||
|
||||
var Promise = q.Promise;
|
||||
|
||||
module.exports = Cache;
|
||||
|
||||
function Cache(projectConfig) {
|
||||
this._cacheFilePath = path.join(
|
||||
tmpdir,
|
||||
'React-Packager-JSTransformer-' + version + '-' +
|
||||
projectConfig.projectRoot.split(path.sep).join('-') +
|
||||
'-' + (projectConfig.cacheVersion || '0')
|
||||
);
|
||||
|
||||
var data;
|
||||
if (!projectConfig.resetCache) {
|
||||
data = loadCacheSync(this._cacheFilePath);
|
||||
} else {
|
||||
data = Object.create(null);
|
||||
}
|
||||
this._data = data;
|
||||
|
||||
this._has = Object.prototype.hasOwnProperty.bind(data);
|
||||
this._persistEventually = _.debounce(
|
||||
this._persistCache.bind(this),
|
||||
2000
|
||||
);
|
||||
|
||||
this._fileWatcher = new FileWatcher(projectConfig);
|
||||
this._fileWatcher
|
||||
.getWatcher()
|
||||
.done(function(watcher) {
|
||||
watcher.on('all', function(type, filepath) {
|
||||
var absPath = path.join(projectConfig.projectRoot, filepath);
|
||||
delete data[absPath];
|
||||
});
|
||||
}.bind(this));
|
||||
}
|
||||
|
||||
Cache.prototype.get = function(filepath, loaderCb) {
|
||||
if (!pathUtils.isAbsolutePath(filepath)) {
|
||||
throw new Error('Use absolute paths');
|
||||
}
|
||||
|
||||
var recordP = this._has(filepath)
|
||||
? this._data[filepath]
|
||||
: this._set(filepath, loaderCb(filepath));
|
||||
|
||||
return recordP.then(function(record) {
|
||||
return record.data;
|
||||
});
|
||||
};
|
||||
|
||||
Cache.prototype._set = function(filepath, loaderPromise) {
|
||||
return this._data[filepath] = loaderPromise.then(function(data) {
|
||||
return [
|
||||
data,
|
||||
q.nfbind(fs.stat)(filepath)
|
||||
];
|
||||
}).spread(function(data, stat) {
|
||||
this._persistEventually();
|
||||
return {
|
||||
data: data,
|
||||
mtime: stat.mtime.getTime(),
|
||||
};
|
||||
}.bind(this));
|
||||
};
|
||||
|
||||
Cache.prototype.end = function() {
|
||||
return q.all([
|
||||
this._persistCache(),
|
||||
this._fileWatcher.end()
|
||||
]);
|
||||
};
|
||||
|
||||
Cache.prototype._persistCache = function() {
|
||||
if (this._persisting != null) {
|
||||
return this._persisting;
|
||||
}
|
||||
|
||||
var data = this._data;
|
||||
var cacheFilepath = this._cacheFilePath;
|
||||
|
||||
return this._persisting = q.all(_.values(data))
|
||||
.then(function(values) {
|
||||
var json = Object.create(null);
|
||||
Object.keys(data).forEach(function(key, i) {
|
||||
json[key] = values[i];
|
||||
});
|
||||
return q.nfbind(fs.writeFile)(cacheFilepath, JSON.stringify(json));
|
||||
})
|
||||
.then(function() {
|
||||
this._persisting = null;
|
||||
return true;
|
||||
}.bind(this));
|
||||
};
|
||||
|
||||
function loadCacheSync(cacheFilepath) {
|
||||
var ret = Object.create(null);
|
||||
if (!fs.existsSync(cacheFilepath)) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
var cacheOnDisk = JSON.parse(fs.readFileSync(cacheFilepath));
|
||||
|
||||
// Filter outdated cache and convert to promises.
|
||||
Object.keys(cacheOnDisk).forEach(function(key) {
|
||||
if (!fs.existsSync(key)) {
|
||||
return;
|
||||
}
|
||||
var value = cacheOnDisk[key];
|
||||
var stat = fs.statSync(key);
|
||||
if (stat.mtime.getTime() === value.mtime) {
|
||||
ret[key] = Promise.resolve(value);
|
||||
}
|
||||
});
|
||||
|
||||
return ret;
|
||||
}
|
0
react-packager/src/JSTransformer/README.md
Normal file
0
react-packager/src/JSTransformer/README.md
Normal file
244
react-packager/src/JSTransformer/__tests__/Cache-test.js
vendored
Normal file
244
react-packager/src/JSTransformer/__tests__/Cache-test.js
vendored
Normal file
@ -0,0 +1,244 @@
|
||||
'use strict';
|
||||
|
||||
jest
|
||||
.dontMock('underscore')
|
||||
.dontMock('path')
|
||||
.dontMock('q')
|
||||
.dontMock('absolute-path')
|
||||
.dontMock('../../fb-path-utils')
|
||||
.dontMock('../Cache');
|
||||
|
||||
var q = require('q');
|
||||
|
||||
describe('JSTransformer Cache', function() {
|
||||
var Cache;
|
||||
|
||||
beforeEach(function() {
|
||||
require('os').tmpDir.mockImpl(function() {
|
||||
return 'tmpDir';
|
||||
});
|
||||
|
||||
Cache = require('../Cache');
|
||||
|
||||
var FileWatcher = require('../../FileWatcher');
|
||||
FileWatcher.prototype.getWatcher = function() {
|
||||
return q({
|
||||
on: function() {}
|
||||
});
|
||||
};
|
||||
});
|
||||
|
||||
describe('getting/settig', function() {
|
||||
it('calls loader callback for uncached file', function() {
|
||||
var cache = new Cache({projectRoot: '/rootDir'});
|
||||
var loaderCb = jest.genMockFn().mockImpl(function() {
|
||||
return q();
|
||||
});
|
||||
cache.get('/rootDir/someFile', loaderCb);
|
||||
expect(loaderCb).toBeCalledWith('/rootDir/someFile');
|
||||
});
|
||||
|
||||
pit('gets the value from the loader callback', function() {
|
||||
require('fs').stat.mockImpl(function(file, callback) {
|
||||
callback(null, {
|
||||
mtime: {
|
||||
getTime: function() {}
|
||||
}
|
||||
});
|
||||
});
|
||||
var cache = new Cache({projectRoot: '/rootDir'});
|
||||
var loaderCb = jest.genMockFn().mockImpl(function() {
|
||||
return q('lol');
|
||||
});
|
||||
return cache.get('/rootDir/someFile', loaderCb).then(function(value) {
|
||||
expect(value).toBe('lol');
|
||||
});
|
||||
});
|
||||
|
||||
pit('caches the value after the first call', function() {
|
||||
require('fs').stat.mockImpl(function(file, callback) {
|
||||
callback(null, {
|
||||
mtime: {
|
||||
getTime: function() {}
|
||||
}
|
||||
});
|
||||
});
|
||||
var cache = new Cache({projectRoot: '/rootDir'});
|
||||
var loaderCb = jest.genMockFn().mockImpl(function() {
|
||||
return q('lol');
|
||||
});
|
||||
return cache.get('/rootDir/someFile', loaderCb).then(function() {
|
||||
var shouldNotBeCalled = jest.genMockFn();
|
||||
return cache.get('/rootDir/someFile', shouldNotBeCalled)
|
||||
.then(function(value) {
|
||||
expect(shouldNotBeCalled).not.toBeCalled();
|
||||
expect(value).toBe('lol');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
pit('it invalidates cache after a file has changed', function() {
|
||||
require('fs').stat.mockImpl(function(file, callback) {
|
||||
callback(null, {
|
||||
mtime: {
|
||||
getTime: function() {}
|
||||
}
|
||||
});
|
||||
});
|
||||
var FileWatcher = require('../../FileWatcher');
|
||||
var triggerChangeFile;
|
||||
FileWatcher.prototype.getWatcher = function() {
|
||||
return q({
|
||||
on: function(type, callback) {
|
||||
triggerChangeFile = callback;
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
var cache = new Cache({projectRoot: '/rootDir'});
|
||||
var loaderCb = jest.genMockFn().mockImpl(function() {
|
||||
return q('lol');
|
||||
});
|
||||
|
||||
return cache.get('/rootDir/someFile', loaderCb).then(function(value) {
|
||||
expect(value).toBe('lol');
|
||||
triggerChangeFile('change', 'someFile');
|
||||
var loaderCb2 = jest.genMockFn().mockImpl(function() {
|
||||
return q('lol2');
|
||||
});
|
||||
return cache.get('/rootDir/someFile', loaderCb2).then(function(value2) {
|
||||
expect(value2).toBe('lol2');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('loading cache from disk', function() {
|
||||
var fileStats;
|
||||
|
||||
beforeEach(function() {
|
||||
fileStats = {
|
||||
'/rootDir/someFile': {
|
||||
mtime: {
|
||||
getTime: function() {
|
||||
return 22;
|
||||
}
|
||||
}
|
||||
},
|
||||
'/rootDir/foo': {
|
||||
mtime: {
|
||||
getTime: function() {
|
||||
return 11;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
var fs = require('fs');
|
||||
|
||||
fs.existsSync.mockImpl(function() {
|
||||
return true;
|
||||
});
|
||||
|
||||
fs.statSync.mockImpl(function(filePath) {
|
||||
return fileStats[filePath];
|
||||
});
|
||||
|
||||
fs.readFileSync.mockImpl(function() {
|
||||
return JSON.stringify({
|
||||
'/rootDir/someFile': {
|
||||
mtime: 22,
|
||||
data: 'oh hai'
|
||||
},
|
||||
'/rootDir/foo': {
|
||||
mtime: 11,
|
||||
data: 'lol wat'
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
pit('should load cache from disk', function() {
|
||||
var cache = new Cache({projectRoot: '/rootDir'});
|
||||
var loaderCb = jest.genMockFn();
|
||||
return cache.get('/rootDir/someFile', loaderCb).then(function(value) {
|
||||
expect(loaderCb).not.toBeCalled();
|
||||
expect(value).toBe('oh hai');
|
||||
|
||||
return cache.get('/rootDir/foo', loaderCb).then(function(value) {
|
||||
expect(loaderCb).not.toBeCalled();
|
||||
expect(value).toBe('lol wat');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
pit('should not load outdated cache', function() {
|
||||
require('fs').stat.mockImpl(function(file, callback) {
|
||||
callback(null, {
|
||||
mtime: {
|
||||
getTime: function() {}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
fileStats['/rootDir/foo'].mtime.getTime = function() {
|
||||
return 123;
|
||||
};
|
||||
|
||||
var cache = new Cache({projectRoot: '/rootDir'});
|
||||
var loaderCb = jest.genMockFn().mockImpl(function() {
|
||||
return q('new value');
|
||||
});
|
||||
|
||||
return cache.get('/rootDir/someFile', loaderCb).then(function(value) {
|
||||
expect(loaderCb).not.toBeCalled();
|
||||
expect(value).toBe('oh hai');
|
||||
|
||||
return cache.get('/rootDir/foo', loaderCb).then(function(value) {
|
||||
expect(loaderCb).toBeCalled();
|
||||
expect(value).toBe('new value');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('writing cache to disk', function() {
|
||||
it('should write cache to disk', function() {
|
||||
var index = 0;
|
||||
var mtimes = [10, 20, 30];
|
||||
var debounceIndex = 0;
|
||||
require('underscore').debounce = function(callback) {
|
||||
return function () {
|
||||
if (++debounceIndex === 3) {
|
||||
callback();
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
var fs = require('fs');
|
||||
fs.stat.mockImpl(function(file, callback) {
|
||||
callback(null, {
|
||||
mtime: {
|
||||
getTime: function() {
|
||||
return mtimes[index++];
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
var cache = new Cache({projectRoot: '/rootDir'});
|
||||
cache.get('/rootDir/bar', function() {
|
||||
return q('bar value');
|
||||
});
|
||||
cache.get('/rootDir/foo', function() {
|
||||
return q('foo value');
|
||||
});
|
||||
cache.get('/rootDir/baz', function() {
|
||||
return q('baz value');
|
||||
});
|
||||
|
||||
jest.runAllTimers();
|
||||
expect(fs.writeFile).toBeCalled();
|
||||
});
|
||||
});
|
||||
});
|
67
react-packager/src/JSTransformer/__tests__/Transformer-test.js
vendored
Normal file
67
react-packager/src/JSTransformer/__tests__/Transformer-test.js
vendored
Normal file
@ -0,0 +1,67 @@
|
||||
'use strict';
|
||||
|
||||
jest
|
||||
.dontMock('worker-farm')
|
||||
.dontMock('q')
|
||||
.dontMock('os')
|
||||
.dontMock('../index');
|
||||
|
||||
describe('Transformer', function() {
|
||||
var Transformer;
|
||||
var workers;
|
||||
|
||||
beforeEach(function() {
|
||||
workers = jest.genMockFn();
|
||||
jest.setMock('worker-farm', jest.genMockFn().mockImpl(function() {
|
||||
return workers;
|
||||
}));
|
||||
require('../Cache').prototype.get.mockImpl(function(filePath, callback) {
|
||||
return callback();
|
||||
});
|
||||
require('fs').readFile.mockImpl(function(file, callback) {
|
||||
callback(null, 'content');
|
||||
});
|
||||
Transformer = require('../');
|
||||
});
|
||||
|
||||
pit('should loadFileAndTransform', function() {
|
||||
workers.mockImpl(function(data, callback) {
|
||||
callback(null, { code: 'transformed' });
|
||||
});
|
||||
require('fs').readFile.mockImpl(function(file, callback) {
|
||||
callback(null, 'content');
|
||||
});
|
||||
|
||||
return new Transformer({}).loadFileAndTransform([], 'file', {})
|
||||
.then(function(data) {
|
||||
expect(data).toEqual({
|
||||
code: 'transformed',
|
||||
sourcePath: 'file',
|
||||
sourceCode: 'content'
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
pit('should add file info to parse errors', function() {
|
||||
require('fs').readFile.mockImpl(function(file, callback) {
|
||||
callback(null, 'var x;\nvar answer = 1 = x;');
|
||||
});
|
||||
|
||||
workers.mockImpl(function(data, callback) {
|
||||
var esprimaError = new Error('Error: Line 2: Invalid left-hand side in assignment');
|
||||
esprimaError.description = 'Invalid left-hand side in assignment';
|
||||
esprimaError.lineNumber = 2;
|
||||
esprimaError.column = 15;
|
||||
callback(null, {error: esprimaError});
|
||||
});
|
||||
|
||||
return new Transformer({}).loadFileAndTransform([], 'foo-file.js', {})
|
||||
.catch(function(error) {
|
||||
expect(error.type).toEqual('TransformError');
|
||||
expect(error.snippet).toEqual([
|
||||
'var answer = 1 = x;',
|
||||
' ^',
|
||||
].join('\n'));
|
||||
});
|
||||
});
|
||||
});
|
100
react-packager/src/JSTransformer/index.js
vendored
Normal file
100
react-packager/src/JSTransformer/index.js
vendored
Normal file
@ -0,0 +1,100 @@
|
||||
|
||||
'use strict';
|
||||
|
||||
var os = require('os');
|
||||
var fs = require('fs');
|
||||
var q = require('q');
|
||||
var Cache = require('./Cache');
|
||||
var _ = require('underscore');
|
||||
var workerFarm = require('worker-farm');
|
||||
|
||||
var workers = workerFarm(require.resolve('./worker'));
|
||||
warmupWorkers();
|
||||
|
||||
var readFile = q.nfbind(fs.readFile);
|
||||
|
||||
module.exports = Transformer;
|
||||
Transformer.TransformError = TransformError;
|
||||
|
||||
function Transformer(projectConfig) {
|
||||
this._cache = new Cache(projectConfig);
|
||||
}
|
||||
|
||||
Transformer.prototype.kill = function() {
|
||||
workerFarm.end(workers);
|
||||
return this._cache.end();
|
||||
};
|
||||
|
||||
Transformer.prototype.loadFileAndTransform = function(
|
||||
transformSets,
|
||||
filePath,
|
||||
options
|
||||
) {
|
||||
return this._cache.get(filePath, function() {
|
||||
return readFile(filePath)
|
||||
.then(function(buffer) {
|
||||
var sourceCode = buffer.toString();
|
||||
var opts = _.extend({}, options, {filename: filePath});
|
||||
return q.nfbind(workers)({
|
||||
transformSets: transformSets,
|
||||
sourceCode: sourceCode,
|
||||
options: opts,
|
||||
}).then(
|
||||
function(res) {
|
||||
if (res.error) {
|
||||
throw formatEsprimaError(res.error, filePath, sourceCode);
|
||||
}
|
||||
|
||||
return {
|
||||
code: res.code,
|
||||
sourcePath: filePath,
|
||||
sourceCode: sourceCode
|
||||
};
|
||||
}
|
||||
);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
// worker-farm module starts workers lazily. But we want them to take time
|
||||
// to initialize so we send a dummy request.
|
||||
// see https://github.com/rvagg/node-worker-farm/issues/23
|
||||
function warmupWorkers() {
|
||||
os.cpus().forEach(function() {
|
||||
workers({
|
||||
transformSets: ['es6'],
|
||||
sourceCode: '\n',
|
||||
options: {}
|
||||
}, function() {});
|
||||
});
|
||||
}
|
||||
|
||||
function TransformError() {}
|
||||
TransformError.__proto__ = SyntaxError.prototype;
|
||||
|
||||
function formatEsprimaError(err, filename, source) {
|
||||
if (!(err.lineNumber && err.column)) {
|
||||
return err;
|
||||
}
|
||||
|
||||
var stack = err.stack.split('\n');
|
||||
stack.shift();
|
||||
|
||||
var msg = 'TransformError: ' + err.description + ' ' + filename + ':' +
|
||||
err.lineNumber + ':' + err.column;
|
||||
var sourceLine = source.split('\n')[err.lineNumber - 1];
|
||||
var snippet = sourceLine + '\n' + new Array(err.column - 1).join(' ') + '^';
|
||||
|
||||
stack.unshift(msg);
|
||||
|
||||
var error = new TransformError();
|
||||
error.message = msg;
|
||||
error.type = 'TransformError';
|
||||
error.stack = stack.join('\n');
|
||||
error.snippet = snippet;
|
||||
error.filename = filename;
|
||||
error.lineNumber = err.lineNumber;
|
||||
error.column = err.column;
|
||||
error.description = err.description;
|
||||
return error;
|
||||
}
|
33
react-packager/src/JSTransformer/transformer.js
vendored
Normal file
33
react-packager/src/JSTransformer/transformer.js
vendored
Normal file
@ -0,0 +1,33 @@
|
||||
/**
|
||||
* Copyright 2004-present Facebook. All Rights Reserved.
|
||||
*
|
||||
* Note: This is a fork of the fb-specific transform.js
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
var jstransform = require('jstransform').transform;
|
||||
|
||||
var reactVisitors =
|
||||
require('react-tools/vendor/fbtransform/visitors').getAllVisitors();
|
||||
var staticTypeSyntax =
|
||||
require('jstransform/visitors/type-syntax').visitorList;
|
||||
// Note that reactVisitors now handles ES6 classes, rest parameters, arrow
|
||||
// functions, template strings, and object short notation.
|
||||
var visitorList = reactVisitors;
|
||||
|
||||
|
||||
function transform(transformSets, srcTxt, options) {
|
||||
options = options || {};
|
||||
|
||||
// These tranforms mostly just erase type annotations and static typing
|
||||
// related statements, but they were conflicting with other tranforms.
|
||||
// Running them first solves that problem
|
||||
var staticTypeSyntaxResult = jstransform(
|
||||
staticTypeSyntax,
|
||||
srcTxt
|
||||
);
|
||||
|
||||
return jstransform(visitorList, staticTypeSyntaxResult.code);
|
||||
}
|
||||
|
||||
exports.transform = transform;
|
26
react-packager/src/JSTransformer/worker.js
vendored
Normal file
26
react-packager/src/JSTransformer/worker.js
vendored
Normal file
@ -0,0 +1,26 @@
|
||||
'use strict';
|
||||
|
||||
var transformer = require('./transformer');
|
||||
|
||||
module.exports = function (data, callback) {
|
||||
var result;
|
||||
try {
|
||||
result = transformer.transform(
|
||||
data.transformSets,
|
||||
data.sourceCode,
|
||||
data.options
|
||||
);
|
||||
} catch (e) {
|
||||
return callback(null, {
|
||||
error: {
|
||||
lineNumber: e.lineNumber,
|
||||
column: e.column,
|
||||
message: e.message,
|
||||
stack: e.stack,
|
||||
description: e.description
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
callback(null, result);
|
||||
};
|
108
react-packager/src/Packager/Package.js
vendored
Normal file
108
react-packager/src/Packager/Package.js
vendored
Normal file
@ -0,0 +1,108 @@
|
||||
'use strict';
|
||||
|
||||
var _ = require('underscore');
|
||||
var SourceMapGenerator = require('source-map').SourceMapGenerator;
|
||||
var base64VLQ = require('./base64-vlq');
|
||||
|
||||
module.exports = Package;
|
||||
|
||||
function Package(sourceMapUrl) {
|
||||
this._modules = [];
|
||||
this._sourceMapUrl = sourceMapUrl;
|
||||
}
|
||||
|
||||
Package.prototype.setMainModuleId = function(moduleId) {
|
||||
this._mainModuleId = moduleId;
|
||||
};
|
||||
|
||||
Package.prototype.addModule = function(
|
||||
transformedCode,
|
||||
sourceCode,
|
||||
sourcePath
|
||||
) {
|
||||
this._modules.push({
|
||||
transformedCode: transformedCode,
|
||||
sourceCode: sourceCode,
|
||||
sourcePath: sourcePath
|
||||
});
|
||||
};
|
||||
|
||||
Package.prototype.finalize = function(options) {
|
||||
if (options.runMainModule) {
|
||||
var runCode = ';require("' + this._mainModuleId + '");';
|
||||
this.addModule(
|
||||
runCode,
|
||||
runCode,
|
||||
'RunMainModule.js'
|
||||
);
|
||||
}
|
||||
|
||||
Object.freeze(this._modules);
|
||||
Object.seal(this._modules);
|
||||
};
|
||||
|
||||
Package.prototype.getSource = function() {
|
||||
return _.pluck(this._modules, 'transformedCode').join('\n') + '\n' +
|
||||
'RAW_SOURCE_MAP = ' + JSON.stringify(this.getSourceMap({excludeSource: true})) + ';\n' +
|
||||
'\/\/@ sourceMappingURL=' + this._sourceMapUrl;
|
||||
};
|
||||
|
||||
Package.prototype.getSourceMap = function(options) {
|
||||
options = options || {};
|
||||
var mappings = this._getMappings();
|
||||
var map = {
|
||||
file: 'bundle.js',
|
||||
sources: _.pluck(this._modules, 'sourcePath'),
|
||||
version: 3,
|
||||
names: [],
|
||||
mappings: mappings,
|
||||
sourcesContent: options.excludeSource
|
||||
? [] : _.pluck(this._modules, 'sourceCode')
|
||||
};
|
||||
return map;
|
||||
};
|
||||
|
||||
|
||||
Package.prototype._getMappings = function() {
|
||||
var modules = this._modules;
|
||||
|
||||
// The first line mapping in our package is basically the base64vlq code for
|
||||
// zeros (A).
|
||||
var firstLine = 'AAAA';
|
||||
|
||||
// Most other lines in our mappings are all zeros (for module, column etc)
|
||||
// except for the lineno mappinp: curLineno - prevLineno = 1; Which is C.
|
||||
var line = 'AACA';
|
||||
|
||||
var mappings = '';
|
||||
for (var i = 0; i < modules.length; i++) {
|
||||
var module = modules[i];
|
||||
var transformedCode = module.transformedCode;
|
||||
var lastCharNewLine = false;
|
||||
module.lines = 0;
|
||||
for (var t = 0; t < transformedCode.length; t++) {
|
||||
if (t === 0 && i === 0) {
|
||||
mappings += firstLine;
|
||||
} else if (t === 0) {
|
||||
mappings += 'AC';
|
||||
|
||||
// This is the only place were we actually don't know the mapping ahead
|
||||
// of time. When it's a new module (and not the first) the lineno
|
||||
// mapping is 0 (current) - number of lines in prev module.
|
||||
mappings += base64VLQ.encode(0 - modules[i - 1].lines);
|
||||
mappings += 'A';
|
||||
} else if (lastCharNewLine) {
|
||||
module.lines++;
|
||||
mappings += line;
|
||||
}
|
||||
lastCharNewLine = transformedCode[t] === '\n';
|
||||
if (lastCharNewLine) {
|
||||
mappings += ';';
|
||||
}
|
||||
}
|
||||
if (i != modules.length - 1) {
|
||||
mappings += ';';
|
||||
}
|
||||
}
|
||||
return mappings;
|
||||
};
|
5
react-packager/src/Packager/__mocks__/source-map.js
vendored
Normal file
5
react-packager/src/Packager/__mocks__/source-map.js
vendored
Normal file
@ -0,0 +1,5 @@
|
||||
var SourceMapGenerator = jest.genMockFn();
|
||||
SourceMapGenerator.prototype.addMapping = jest.genMockFn();
|
||||
SourceMapGenerator.prototype.setSourceContent = jest.genMockFn();
|
||||
SourceMapGenerator.prototype.toJSON = jest.genMockFn();
|
||||
exports.SourceMapGenerator = SourceMapGenerator;
|
95
react-packager/src/Packager/__tests__/Package-test.js
vendored
Normal file
95
react-packager/src/Packager/__tests__/Package-test.js
vendored
Normal file
@ -0,0 +1,95 @@
|
||||
'use strict';
|
||||
|
||||
jest
|
||||
.dontMock('underscore')
|
||||
.dontMock('../base64-vlq')
|
||||
.dontMock('source-map')
|
||||
.dontMock('../Package');
|
||||
|
||||
var SourceMapGenerator = require('source-map').SourceMapGenerator;
|
||||
|
||||
describe('Package', function() {
|
||||
var Package;
|
||||
var ppackage;
|
||||
|
||||
beforeEach(function() {
|
||||
Package = require('../Package');
|
||||
ppackage = new Package('test_url');
|
||||
ppackage.getSourceMap = jest.genMockFn().mockImpl(function() {
|
||||
return 'test-source-map';
|
||||
});
|
||||
});
|
||||
|
||||
describe('source package', function() {
|
||||
it('should create a package and get the source', function() {
|
||||
ppackage.addModule('transformed foo;', 'source foo', 'foo path');
|
||||
ppackage.addModule('transformed bar;', 'source bar', 'bar path');
|
||||
ppackage.finalize({});
|
||||
expect(ppackage.getSource()).toBe([
|
||||
'transformed foo;',
|
||||
'transformed bar;',
|
||||
'RAW_SOURCE_MAP = "test-source-map";',
|
||||
'\/\/@ sourceMappingURL=test_url',
|
||||
].join('\n'));
|
||||
});
|
||||
|
||||
it('should create a package and add run module code', function() {
|
||||
ppackage.addModule('transformed foo;', 'source foo', 'foo path');
|
||||
ppackage.addModule('transformed bar;', 'source bar', 'bar path');
|
||||
ppackage.setMainModuleId('foo');
|
||||
ppackage.finalize({runMainModule: true});
|
||||
expect(ppackage.getSource()).toBe([
|
||||
'transformed foo;',
|
||||
'transformed bar;',
|
||||
';require("foo");',
|
||||
'RAW_SOURCE_MAP = "test-source-map";',
|
||||
'\/\/@ sourceMappingURL=test_url',
|
||||
].join('\n'));
|
||||
});
|
||||
});
|
||||
|
||||
describe('sourcemap package', function() {
|
||||
it('should create sourcemap', function() {
|
||||
var ppackage = new Package('test_url');
|
||||
ppackage.addModule('transformed foo;\n', 'source foo', 'foo path');
|
||||
ppackage.addModule('transformed bar;\n', 'source bar', 'bar path');
|
||||
ppackage.setMainModuleId('foo');
|
||||
ppackage.finalize({runMainModule: true});
|
||||
var s = ppackage.getSourceMap();
|
||||
expect(s).toEqual(genSourceMap(ppackage._modules));
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
function genSourceMap(modules) {
|
||||
var sourceMapGen = new SourceMapGenerator({file: 'bundle.js', version: 3});
|
||||
var packageLineNo = 0;
|
||||
for (var i = 0; i < modules.length; i++) {
|
||||
var module = modules[i];
|
||||
var transformedCode = module.transformedCode;
|
||||
var sourcePath = module.sourcePath;
|
||||
var sourceCode = module.sourceCode;
|
||||
var transformedLineCount = 0;
|
||||
var lastCharNewLine = false;
|
||||
for (var t = 0; t < transformedCode.length; t++) {
|
||||
if (t === 0 || lastCharNewLine) {
|
||||
sourceMapGen.addMapping({
|
||||
generated: {line: packageLineNo + 1, column: 0},
|
||||
original: {line: transformedLineCount + 1, column: 0},
|
||||
source: sourcePath
|
||||
});
|
||||
}
|
||||
lastCharNewLine = transformedCode[t] === '\n';
|
||||
if (lastCharNewLine) {
|
||||
transformedLineCount++;
|
||||
packageLineNo++;
|
||||
}
|
||||
}
|
||||
packageLineNo++;
|
||||
sourceMapGen.setSourceContent(
|
||||
sourcePath,
|
||||
sourceCode
|
||||
);
|
||||
}
|
||||
return sourceMapGen.toJSON();
|
||||
};
|
83
react-packager/src/Packager/__tests__/Packager-test.js
vendored
Normal file
83
react-packager/src/Packager/__tests__/Packager-test.js
vendored
Normal file
@ -0,0 +1,83 @@
|
||||
'use strict';
|
||||
|
||||
jest
|
||||
.setMock('worker-farm', function() { return function() {};})
|
||||
.dontMock('path')
|
||||
.dontMock('q')
|
||||
.dontMock('os')
|
||||
.dontMock('underscore')
|
||||
.dontMock('../');
|
||||
|
||||
var q = require('q');
|
||||
|
||||
describe('Packager', function() {
|
||||
var getDependencies;
|
||||
var wrapModule;
|
||||
var Packager;
|
||||
|
||||
beforeEach(function() {
|
||||
getDependencies = jest.genMockFn();
|
||||
wrapModule = jest.genMockFn();
|
||||
require('../../DependencyResolver').mockImpl(function() {
|
||||
return {
|
||||
getDependencies: getDependencies,
|
||||
wrapModule: wrapModule,
|
||||
};
|
||||
});
|
||||
|
||||
Packager = require('../');
|
||||
});
|
||||
|
||||
pit('create a package', function() {
|
||||
require('fs').statSync.mockImpl(function() {
|
||||
return {
|
||||
isDirectory: function() {return true;}
|
||||
};
|
||||
});
|
||||
|
||||
var packager = new Packager({});
|
||||
var modules = [
|
||||
{id: 'foo', path: '/root/foo.js', dependencies: []},
|
||||
{id: 'bar', path: '/root/bar.js', dependencies: []},
|
||||
];
|
||||
|
||||
getDependencies.mockImpl(function() {
|
||||
return q({
|
||||
mainModuleId: 'foo',
|
||||
dependencies: modules
|
||||
});
|
||||
});
|
||||
|
||||
require('../../JSTransformer').prototype.loadFileAndTransform
|
||||
.mockImpl(function(tsets, path) {
|
||||
return q({
|
||||
code: 'transformed ' + path,
|
||||
sourceCode: 'source ' + path,
|
||||
sourcePath: path
|
||||
});
|
||||
});
|
||||
|
||||
wrapModule.mockImpl(function(module, code) {
|
||||
return 'lol ' + code + ' lol';
|
||||
});
|
||||
|
||||
return packager.package('/root/foo.js', true, 'source_map_url')
|
||||
.then(function(p) {
|
||||
expect(p.addModule.mock.calls[0]).toEqual([
|
||||
'lol transformed /root/foo.js lol',
|
||||
'source /root/foo.js',
|
||||
'/root/foo.js'
|
||||
]);
|
||||
expect(p.addModule.mock.calls[1]).toEqual([
|
||||
'lol transformed /root/bar.js lol',
|
||||
'source /root/bar.js',
|
||||
'/root/bar.js'
|
||||
]);
|
||||
|
||||
expect(p.finalize.mock.calls[0]).toEqual([
|
||||
{runMainModule: true}
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
});
|
168
react-packager/src/Packager/base64-vlq.js
vendored
Normal file
168
react-packager/src/Packager/base64-vlq.js
vendored
Normal file
@ -0,0 +1,168 @@
|
||||
/* -*- Mode: js; js-indent-level: 2; -*- */
|
||||
/*
|
||||
* Copyright 2011 Mozilla Foundation and contributors
|
||||
* Licensed under the New BSD license. See LICENSE or:
|
||||
* http://opensource.org/licenses/BSD-3-Clause
|
||||
*
|
||||
* Based on the Base 64 VLQ implementation in Closure Compiler:
|
||||
* https://code.google.com/p/closure-compiler/source/browse/trunk/src/com/google/debugging/sourcemap/Base64VLQ.java
|
||||
*
|
||||
* Copyright 2011 The Closure Compiler Authors. All rights reserved.
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are
|
||||
* met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above
|
||||
* copyright notice, this list of conditions and the following
|
||||
* disclaimer in the documentation and/or other materials provided
|
||||
* with the distribution.
|
||||
* * Neither the name of Google Inc. nor the names of its
|
||||
* contributors may be used to endorse or promote products derived
|
||||
* from this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
var charToIntMap = {};
|
||||
var intToCharMap = {};
|
||||
|
||||
'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'
|
||||
.split('')
|
||||
.forEach(function (ch, index) {
|
||||
charToIntMap[ch] = index;
|
||||
intToCharMap[index] = ch;
|
||||
});
|
||||
|
||||
var base64 = {};
|
||||
/**
|
||||
* Encode an integer in the range of 0 to 63 to a single base 64 digit.
|
||||
*/
|
||||
base64.encode = function base64_encode(aNumber) {
|
||||
if (aNumber in intToCharMap) {
|
||||
return intToCharMap[aNumber];
|
||||
}
|
||||
throw new TypeError("Must be between 0 and 63: " + aNumber);
|
||||
};
|
||||
|
||||
/**
|
||||
* Decode a single base 64 digit to an integer.
|
||||
*/
|
||||
base64.decode = function base64_decode(aChar) {
|
||||
if (aChar in charToIntMap) {
|
||||
return charToIntMap[aChar];
|
||||
}
|
||||
throw new TypeError("Not a valid base 64 digit: " + aChar);
|
||||
};
|
||||
|
||||
|
||||
|
||||
// A single base 64 digit can contain 6 bits of data. For the base 64 variable
|
||||
// length quantities we use in the source map spec, the first bit is the sign,
|
||||
// the next four bits are the actual value, and the 6th bit is the
|
||||
// continuation bit. The continuation bit tells us whether there are more
|
||||
// digits in this value following this digit.
|
||||
//
|
||||
// Continuation
|
||||
// | Sign
|
||||
// | |
|
||||
// V V
|
||||
// 101011
|
||||
|
||||
var VLQ_BASE_SHIFT = 5;
|
||||
|
||||
// binary: 100000
|
||||
var VLQ_BASE = 1 << VLQ_BASE_SHIFT;
|
||||
|
||||
// binary: 011111
|
||||
var VLQ_BASE_MASK = VLQ_BASE - 1;
|
||||
|
||||
// binary: 100000
|
||||
var VLQ_CONTINUATION_BIT = VLQ_BASE;
|
||||
|
||||
/**
|
||||
* Converts from a two-complement value to a value where the sign bit is
|
||||
* placed in the least significant bit. For example, as decimals:
|
||||
* 1 becomes 2 (10 binary), -1 becomes 3 (11 binary)
|
||||
* 2 becomes 4 (100 binary), -2 becomes 5 (101 binary)
|
||||
*/
|
||||
function toVLQSigned(aValue) {
|
||||
return aValue < 0
|
||||
? ((-aValue) << 1) + 1
|
||||
: (aValue << 1) + 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts to a two-complement value from a value where the sign bit is
|
||||
* placed in the least significant bit. For example, as decimals:
|
||||
* 2 (10 binary) becomes 1, 3 (11 binary) becomes -1
|
||||
* 4 (100 binary) becomes 2, 5 (101 binary) becomes -2
|
||||
*/
|
||||
function fromVLQSigned(aValue) {
|
||||
var isNegative = (aValue & 1) === 1;
|
||||
var shifted = aValue >> 1;
|
||||
return isNegative
|
||||
? -shifted
|
||||
: shifted;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the base 64 VLQ encoded value.
|
||||
*/
|
||||
exports.encode = function base64VLQ_encode(aValue) {
|
||||
var encoded = "";
|
||||
var digit;
|
||||
|
||||
var vlq = toVLQSigned(aValue);
|
||||
|
||||
do {
|
||||
digit = vlq & VLQ_BASE_MASK;
|
||||
vlq >>>= VLQ_BASE_SHIFT;
|
||||
if (vlq > 0) {
|
||||
// There are still more digits in this value, so we must make sure the
|
||||
// continuation bit is marked.
|
||||
digit |= VLQ_CONTINUATION_BIT;
|
||||
}
|
||||
encoded += base64.encode(digit);
|
||||
} while (vlq > 0);
|
||||
|
||||
return encoded;
|
||||
};
|
||||
|
||||
/**
|
||||
* Decodes the next base 64 VLQ value from the given string and returns the
|
||||
* value and the rest of the string via the out parameter.
|
||||
*/
|
||||
exports.decode = function base64VLQ_decode(aStr, aOutParam) {
|
||||
var i = 0;
|
||||
var strLen = aStr.length;
|
||||
var result = 0;
|
||||
var shift = 0;
|
||||
var continuation, digit;
|
||||
|
||||
do {
|
||||
if (i >= strLen) {
|
||||
throw new Error("Expected more digits in base 64 VLQ value.");
|
||||
}
|
||||
digit = base64.decode(aStr.charAt(i++));
|
||||
continuation = !!(digit & VLQ_CONTINUATION_BIT);
|
||||
digit &= VLQ_BASE_MASK;
|
||||
result = result + (digit << shift);
|
||||
shift += VLQ_BASE_SHIFT;
|
||||
} while (continuation);
|
||||
|
||||
aOutParam.value = fromVLQSigned(result);
|
||||
aOutParam.rest = aStr.slice(i);
|
||||
};
|
||||
|
115
react-packager/src/Packager/index.js
vendored
Normal file
115
react-packager/src/Packager/index.js
vendored
Normal file
@ -0,0 +1,115 @@
|
||||
'use strict';
|
||||
|
||||
var assert = require('assert');
|
||||
var fs = require('fs');
|
||||
var path = require('path');
|
||||
var Promise = require('q').Promise;
|
||||
var Transformer = require('../JSTransformer');
|
||||
var DependencyResolver = require('../DependencyResolver');
|
||||
var _ = require('underscore');
|
||||
var Package = require('./Package');
|
||||
var Activity = require('../Activity');
|
||||
|
||||
var DEFAULT_CONFIG = {
|
||||
/**
|
||||
* RegExp used to ignore paths when scanning the filesystem to calculate the
|
||||
* dependency graph.
|
||||
*/
|
||||
blacklistRE: null,
|
||||
|
||||
/**
|
||||
* The kind of module system/transport wrapper to use for the modules bundled
|
||||
* in the package.
|
||||
*/
|
||||
moduleFormat: 'haste',
|
||||
|
||||
/**
|
||||
* An ordered list of module names that should be considered as dependencies
|
||||
* of every module in the system. The list is ordered because every item in
|
||||
* the list will have an implicit dependency on all items before it.
|
||||
*
|
||||
* (This ordering is necessary to build, for example, polyfills that build on
|
||||
* each other)
|
||||
*/
|
||||
polyfillModuleNames: [],
|
||||
|
||||
/**
|
||||
* DEPRECATED
|
||||
*
|
||||
* A string of code to be appended to the top of a package.
|
||||
*
|
||||
* TODO: THIS RUINS SOURCE MAPS. THIS OPTION SHOULD BE REMOVED ONCE WE GET
|
||||
* config.polyfillModuleNames WORKING!
|
||||
*/
|
||||
runtimeCode: ''
|
||||
};
|
||||
|
||||
function Packager(projectConfig) {
|
||||
// Verify that the root exists.
|
||||
var root = projectConfig.projectRoot;
|
||||
assert(fs.statSync(root).isDirectory(), 'Root has to be a valid directory');
|
||||
this._rootPath = root;
|
||||
|
||||
this._config = Object.create(DEFAULT_CONFIG);
|
||||
for (var key in projectConfig) {
|
||||
this._config[key] = projectConfig[key];
|
||||
}
|
||||
|
||||
this._resolver = new DependencyResolver(this._config);
|
||||
|
||||
this._transformer = new Transformer(projectConfig);
|
||||
}
|
||||
|
||||
Packager.prototype.kill = function() {
|
||||
return this._transformer.kill();
|
||||
};
|
||||
|
||||
Packager.prototype.package = function(main, runModule, sourceMapUrl) {
|
||||
var transformModule = this._transformModule.bind(this);
|
||||
var ppackage = new Package(sourceMapUrl);
|
||||
|
||||
var findEventId = Activity.startEvent('find dependencies');
|
||||
var transformEventId;
|
||||
|
||||
return this._resolver.getDependencies(main)
|
||||
.then(function(result) {
|
||||
Activity.endEvent(findEventId);
|
||||
transformEventId = Activity.startEvent('transform');
|
||||
|
||||
ppackage.setMainModuleId(result.mainModuleId);
|
||||
return Promise.all(
|
||||
result.dependencies.map(transformModule)
|
||||
);
|
||||
})
|
||||
.then(function(transformedModules) {
|
||||
Activity.endEvent(transformEventId);
|
||||
|
||||
transformedModules.forEach(function(transformed) {
|
||||
ppackage.addModule(
|
||||
transformed.code,
|
||||
transformed.sourceCode,
|
||||
transformed.sourcePath
|
||||
);
|
||||
});
|
||||
|
||||
ppackage.finalize({ runMainModule: runModule });
|
||||
return ppackage;
|
||||
});
|
||||
};
|
||||
|
||||
Packager.prototype._transformModule = function(module) {
|
||||
var resolver = this._resolver;
|
||||
return this._transformer.loadFileAndTransform(
|
||||
['es6'],
|
||||
path.resolve(module.path),
|
||||
this._config.transformer || {}
|
||||
).then(function(transformed) {
|
||||
return _.extend(
|
||||
{},
|
||||
transformed,
|
||||
{code: resolver.wrapModule(module, transformed.code)}
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
module.exports = Packager;
|
14
react-packager/src/fb-path-utils/index.js
vendored
Normal file
14
react-packager/src/fb-path-utils/index.js
vendored
Normal file
@ -0,0 +1,14 @@
|
||||
var absolutePath = require('absolute-path');
|
||||
var path = require('path');
|
||||
var pathIsInside = require('path-is-inside');
|
||||
|
||||
function isAbsolutePath(pathStr) {
|
||||
return absolutePath(pathStr);
|
||||
}
|
||||
|
||||
function isChildPath(parentPath, childPath) {
|
||||
return pathIsInside(parentPath, childPath);
|
||||
}
|
||||
|
||||
exports.isAbsolutePath = isAbsolutePath;
|
||||
exports.isChildPath = isChildPath;
|
Loading…
x
Reference in New Issue
Block a user