diff --git a/Libraries/Sample/Sample.android.js b/Libraries/Sample/Sample.android.js new file mode 100644 index 00000000000000..57cd7240019c5f --- /dev/null +++ b/Libraries/Sample/Sample.android.js @@ -0,0 +1,17 @@ +/** + * Stub of Sample for Android. + * + * @providesModule Sample + * @flow + */ +'use strict'; + +var warning = require('warning'); + +var Sample = { + test: function() { + warning("Not yet implemented for Android."); + } +}; + +module.exports = Sample; diff --git a/Libraries/Sample/Sample.h b/Libraries/Sample/Sample.h new file mode 100644 index 00000000000000..6a13417ad8dbb6 --- /dev/null +++ b/Libraries/Sample/Sample.h @@ -0,0 +1,5 @@ +#import "RCTBridgeModule.h" + +@interface Sample : NSObject + +@end diff --git a/Libraries/Sample/Sample.ios.js b/Libraries/Sample/Sample.ios.js new file mode 100644 index 00000000000000..884e5305c0298c --- /dev/null +++ b/Libraries/Sample/Sample.ios.js @@ -0,0 +1,20 @@ +/** + * @providesModule Sample + * @flow + */ +'use strict'; + +var NativeSample = require('NativeModules').Sample; +var invariant = require('invariant'); + +/** + * High-level docs for the Sample iOS API can be written here. + */ + +var Sample = { + test: function() { + NativeSample.test(); + } +}; + +module.exports = Sample; diff --git a/Libraries/Sample/Sample.m b/Libraries/Sample/Sample.m new file mode 100644 index 00000000000000..d3094111251982 --- /dev/null +++ b/Libraries/Sample/Sample.m @@ -0,0 +1,12 @@ +#import "Sample.h" + +@implementation Sample + +RCT_EXPORT_MODULE() + +RCT_EXPORT_METHOD(test) +{ + // Your implementation here +} + +@end diff --git a/Libraries/Sample/Sample.xcodeproj/project.pbxproj b/Libraries/Sample/Sample.xcodeproj/project.pbxproj new file mode 100644 index 00000000000000..f00a2fd1863b09 --- /dev/null +++ b/Libraries/Sample/Sample.xcodeproj/project.pbxproj @@ -0,0 +1,251 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 46; + objects = { + +/* Begin PBXBuildFile section */ + 13BE3DEE1AC21097009241FE /* Sample.m in Sources */ = {isa = PBXBuildFile; fileRef = 13BE3DED1AC21097009241FE /* Sample.m */; }; +/* End PBXBuildFile section */ + +/* Begin PBXCopyFilesBuildPhase section */ + 58B511D91A9E6C8500147676 /* CopyFiles */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = "include/$(PRODUCT_NAME)"; + dstSubfolderSpec = 16; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + +/* Begin PBXFileReference section */ + 134814201AA4EA6300B7C361 /* libSample.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libSample.a; sourceTree = BUILT_PRODUCTS_DIR; }; + 13BE3DEC1AC21097009241FE /* Sample.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Sample.h; sourceTree = ""; }; + 13BE3DED1AC21097009241FE /* Sample.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = Sample.m; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 58B511D81A9E6C8500147676 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 134814211AA4EA7D00B7C361 /* Products */ = { + isa = PBXGroup; + children = ( + 134814201AA4EA6300B7C361 /* libSample.a */, + ); + name = Products; + sourceTree = ""; + }; + 58B511D21A9E6C8500147676 = { + isa = PBXGroup; + children = ( + 13BE3DEC1AC21097009241FE /* Sample.h */, + 13BE3DED1AC21097009241FE /* Sample.m */, + 134814211AA4EA7D00B7C361 /* Products */, + ); + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 58B511DA1A9E6C8500147676 /* Sample */ = { + isa = PBXNativeTarget; + buildConfigurationList = 58B511EF1A9E6C8500147676 /* Build configuration list for PBXNativeTarget "Sample" */; + buildPhases = ( + 58B511D71A9E6C8500147676 /* Sources */, + 58B511D81A9E6C8500147676 /* Frameworks */, + 58B511D91A9E6C8500147676 /* CopyFiles */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = Sample; + productName = RCTDataManager; + productReference = 134814201AA4EA6300B7C361 /* libSample.a */; + productType = "com.apple.product-type.library.static"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 58B511D31A9E6C8500147676 /* Project object */ = { + isa = PBXProject; + attributes = { + LastUpgradeCheck = 0610; + ORGANIZATIONNAME = Facebook; + TargetAttributes = { + 58B511DA1A9E6C8500147676 = { + CreatedOnToolsVersion = 6.1.1; + }; + }; + }; + buildConfigurationList = 58B511D61A9E6C8500147676 /* Build configuration list for PBXProject "Sample" */; + compatibilityVersion = "Xcode 3.2"; + developmentRegion = English; + hasScannedForEncodings = 0; + knownRegions = ( + en, + ); + mainGroup = 58B511D21A9E6C8500147676; + productRefGroup = 58B511D21A9E6C8500147676; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 58B511DA1A9E6C8500147676 /* Sample */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXSourcesBuildPhase section */ + 58B511D71A9E6C8500147676 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 13BE3DEE1AC21097009241FE /* Sample.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin XCBuildConfiguration section */ + 58B511ED1A9E6C8500147676 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_DYNAMIC_NO_PIC = NO; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_SYMBOLS_PRIVATE_EXTERN = NO; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 7.0; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + }; + name = Debug; + }; + 58B511EE1A9E6C8500147676 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = YES; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 7.0; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + 58B511F01A9E6C8500147676 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + HEADER_SEARCH_PATHS = ( + "$(inherited)", + /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include, + "$(SRCROOT)/../../React/**", + "$(SRCROOT)/../../node_modules/react-native/React/**", + ); + LIBRARY_SEARCH_PATHS = "$(inherited)"; + OTHER_LDFLAGS = "-ObjC"; + PRODUCT_NAME = Sample; + SKIP_INSTALL = YES; + }; + name = Debug; + }; + 58B511F11A9E6C8500147676 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + HEADER_SEARCH_PATHS = ( + "$(inherited)", + /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include, + "$(SRCROOT)/../../React/**", + ); + LIBRARY_SEARCH_PATHS = "$(inherited)"; + OTHER_LDFLAGS = "-ObjC"; + PRODUCT_NAME = Sample; + SKIP_INSTALL = YES; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 58B511D61A9E6C8500147676 /* Build configuration list for PBXProject "Sample" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 58B511ED1A9E6C8500147676 /* Debug */, + 58B511EE1A9E6C8500147676 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 58B511EF1A9E6C8500147676 /* Build configuration list for PBXNativeTarget "Sample" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 58B511F01A9E6C8500147676 /* Debug */, + 58B511F11A9E6C8500147676 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 58B511D31A9E6C8500147676 /* Project object */; +} diff --git a/Libraries/Sample/package.json b/Libraries/Sample/package.json new file mode 100644 index 00000000000000..3649dfb7c3737d --- /dev/null +++ b/Libraries/Sample/package.json @@ -0,0 +1,5 @@ +{ + "name": "Sample", + "version": "0.0.1", + "keywords": "react-native" +} diff --git a/local-cli/cli.js b/local-cli/cli.js index f76b7986d43412..4f75e83db36d44 100644 --- a/local-cli/cli.js +++ b/local-cli/cli.js @@ -10,6 +10,7 @@ var path = require('path'); var init = require('./init.js'); var install = require('./install.js'); var bundle = require('./bundle.js'); +var newLibrary = require('./new-library.js'); function printUsage() { console.log([ @@ -18,7 +19,8 @@ function printUsage() { 'Commands:', ' start: starts the webserver', ' install: installs npm react components', - ' bundle: builds the javascript bundle for offline use' + ' bundle: builds the javascript bundle for offline use', + ' new-library: generates a native library bridge' ].join('\n')); process.exit(1); } @@ -51,6 +53,9 @@ function run() { case 'bundle': bundle.init(args); break; + case 'new-library': + newLibrary.init(args); + break; case 'init': printInitWarning(); break; diff --git a/local-cli/generator-utils.js b/local-cli/generator-utils.js new file mode 100644 index 00000000000000..449e000ae021d5 --- /dev/null +++ b/local-cli/generator-utils.js @@ -0,0 +1,47 @@ +'use strict'; + +var path = require('path'); +var fs = require('fs'); + +function copyAndReplace(src, dest, replacements) { + if (fs.lstatSync(src).isDirectory()) { + if (!fs.existsSync(dest)) { + fs.mkdirSync(dest); + } + } else { + var content = fs.readFileSync(src, 'utf8'); + Object.keys(replacements).forEach(function(regex) { + content = content.replace(new RegExp(regex, 'g'), replacements[regex]); + }); + fs.writeFileSync(dest, content); + } +} + +function walk(current) { + if (!fs.lstatSync(current).isDirectory()) { + return [current]; + } + + var files = fs.readdirSync(current).map(function(child) { + child = path.join(current, child); + return walk(child); + }); + return [].concat.apply([current], files); +} + +function validatePackageName(name) { + if (!name.match(/^[$A-Z_][0-9A-Z_$]*$/i)) { + console.error( + '"%s" is not a valid name for a project. Please use a valid identifier ' + + 'name (alphanumeric).', + name + ); + process.exit(1); + } +} + +module.exports = { + copyAndReplace: copyAndReplace, + walk: walk, + validatePackageName: validatePackageName +}; diff --git a/local-cli/init.js b/local-cli/init.js index 42309c515eff0a..6441edd705a2e7 100755 --- a/local-cli/init.js +++ b/local-cli/init.js @@ -1,15 +1,15 @@ 'use strict'; var path = require('path'); -var fs = require('fs'); +var utils = require('./generator-utils'); function init(projectDir, appName) { console.log('Setting up new React Native app in ' + projectDir); var source = path.resolve(__dirname, '..', 'Examples/SampleApp'); - walk(source).forEach(function(f) { + utils.walk(source).forEach(function(f) { f = f.replace(source + '/', ''); // Strip off absolute path - if (f === 'project.xcworkspace' || f === 'xcuserdata') { + if (f === 'project.xcworkspace' || f.indexOf('.xcodeproj/xcuserdata') !== -1) { return; } @@ -21,7 +21,7 @@ function init(projectDir, appName) { }; var dest = f.replace(/SampleApp/g, appName).replace(/^_/, '.'); - copyAndReplace( + utils.copyAndReplace( path.resolve(source, f), path.resolve(projectDir, dest), replacements @@ -34,30 +34,4 @@ function init(projectDir, appName) { console.log(''); } -function copyAndReplace(src, dest, replacements) { - if (fs.lstatSync(src).isDirectory()) { - if (!fs.existsSync(dest)) { - fs.mkdirSync(dest); - } - } else { - var content = fs.readFileSync(src, 'utf8'); - Object.keys(replacements).forEach(function(regex) { - content = content.replace(new RegExp(regex, 'g'), replacements[regex]); - }); - fs.writeFileSync(dest, content); - } -} - -function walk(current) { - if (!fs.lstatSync(current).isDirectory()) { - return [current]; - } - - var files = fs.readdirSync(current).map(function(child) { - child = path.join(current, child); - return walk(child); - }); - return [].concat.apply([current], files); -} - module.exports = init; diff --git a/local-cli/new-library.js b/local-cli/new-library.js new file mode 100644 index 00000000000000..533594aca592ec --- /dev/null +++ b/local-cli/new-library.js @@ -0,0 +1,61 @@ +'use strict'; + +var path = require('path'); +var fs = require('fs'); +var utils = require('./generator-utils'); + +function showHelp() { + console.log([ + 'Usage: react-native new-library ', + '' + ].join('\n')); + process.exit(1); +} + +function newLibrary(libraryName) { + var root = process.cwd(); + var libraries = path.resolve(root, 'Libraries'); + var libraryDest = path.resolve(libraries, libraryName); + var source = path.resolve('node_modules', 'react-native', 'Libraries', 'Sample') + '/'; + + if (!fs.existsSync(libraries)) { + fs.mkdir(libraries); + } + + if (fs.existsSync(libraryDest)) { + console.log('Library already exists in', libraryDest); + process.exit(1); + } + + utils.walk(source).forEach(function(f) { + f = f.replace(source, ''); // Strip off absolute path + if (f === 'project.xcworkspace' || f.indexOf('.xcodeproj/xcuserdata') !== -1) { + return; + } + + var dest = f.replace(/Sample/g, libraryName).replace(/^_/, '.'); + utils.copyAndReplace( + path.resolve(source, f), + path.resolve(libraryDest, dest), + { 'Sample': libraryName } + ); + }); + + console.log('Created library in', libraryDest); + console.log('Next Steps:'); + console.log(' Link your library in Xcode:'); + console.log(' https://facebook.github.io/react-native/docs/linking-libraries.html#content'); + console.log(''); +} + +module.exports = { + init: function(args) { + var libraryName = args[1]; + if (!libraryName) { + showHelp(); + } + utils.validatePackageName(libraryName); + + newLibrary(libraryName); + } +}; diff --git a/react-native-cli/index.js b/react-native-cli/index.js index 7bd237e021df44..91a1a7469dc28d 100755 --- a/react-native-cli/index.js +++ b/react-native-cli/index.js @@ -55,7 +55,7 @@ if (cli) { } } -function init(name) { +function validatePackageName(name) { if (!name.match(/^[$A-Z_][0-9A-Z_$]*$/i)) { console.error( '"%s" is not a valid name for a project. Please use a valid identifier ' + @@ -64,6 +64,10 @@ function init(name) { ); process.exit(1); } +} + +function init(name) { + validatePackageName(name); var root = path.resolve(name); var projectName = path.basename(root);