2 * Copyright (c) 2015 Samsung Electronics Co., Ltd.
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
19 #include "module-loader.h"
30 ModuleLoader::ModuleLoader()
34 ModuleLoader::~ModuleLoader()
36 VectorBase::SizeType count = mModules.Count();
37 for( VectorBase::SizeType i = 0; i < count ; ++i)
39 Module* module = mModules[i];
45 bool ModuleLoader::CompileAndRun(v8::Isolate* isolate,
46 const std::string& sourceCode,
47 const std::string& sourceFileName )
50 v8::HandleScope handleScope( isolate );
51 v8::TryCatch tryCatch;
53 // convert from string to v8 string
54 v8::Local<v8::String> source = v8::String::NewFromUtf8( isolate, sourceCode.c_str());
55 v8::Local<v8::String> file = v8::String::NewFromUtf8( isolate, sourceFileName.c_str());
56 v8::ScriptOrigin origin(file);
59 v8::Local<v8::Script> script = v8::Script::Compile( source, &origin);
61 // See if an exception has been thrown
62 if( tryCatch.HasCaught() || script.IsEmpty() )
64 // Print errors that happened during compilation.
65 V8Utils::ReportException( isolate, &tryCatch );
70 v8::Local<v8::Value> result = script->Run();
72 // in V8 test code ( they check for an exception and empty return object )
73 if( tryCatch.HasCaught() || result.IsEmpty() )
75 // Print errors that happened during execution.
76 V8Utils::ReportException( isolate, &tryCatch);
83 bool ModuleLoader::ExecuteScript( v8::Isolate* isolate,
84 const std::string& sourceCode,
85 const std::string& sourceFileName )
87 StoreScriptInfo( sourceFileName );
89 return CompileAndRun(isolate, sourceCode, sourceFileName );
92 bool ModuleLoader::ExecuteScriptFromFile( v8::Isolate* isolate,
93 const std::string& fileName )
97 V8Utils::GetFileContents( fileName, contents );
99 if( contents.empty() )
104 return ExecuteScript( isolate, contents, fileName );
108 * ### var module = require("moduleName");
111 * There is no standard for modules or the 'require' keyword in JavaScript.<br />
112 * However CommonJS have this: http://wiki.commonjs.org/wiki/Modules/1.1.1 ( used by Node.js).
115 * The concept behind 'require' keyword is simple, it allows you to include another
116 * JavaScript file, which exports an API / function / constructor / singleton.
119 * // example_module.js
120 * exports.hello = function() { return "hello world" }
125 * var example = require( "example_module.js");
127 * log( example.hello() );
131 * ### Module writers guide:
134 * #### Exporting as a namespace
136 * Example of using a namespace to export functions / objects.
138 * // filesystem-helper.js
139 * exports.version = "FileSystem helper 1.0";
140 * exports.open = function() { }
141 * exports.close = function() { }
142 * exports.read = function() { }
143 * exports.write = function() { ... }
144 * exports.fileSize = function() {...}
149 * var fs = require( "filesystem-helper.js");
153 * var file = fs.open("myfile.txt");
154 * var data = fs.read( file );
158 * #### Exporting as a function
160 * In this example we are using module.exports directly to change it
161 * from an object literal with name-value pairs (exports object) to a function.
163 * // my_first_module.js
164 * module.exports = function() { log("helloWorld"); }
169 * var func = require("my_first_module.js");
170 * func(); // prints out hello-world
173 * #### Exporting as a constructor
177 * function ImageActor( position, orientation, image, name )
179 * this = new dali.ImageActor( image );
180 * this.position = position;
181 * this.orientation = orientation;
184 * module.exports = ImageActor;
190 * var ImageActor = require(" ImageActor.js");
192 * var imageActor = new ImageActor( position, orientation, image, "my first image actor");
194 * #### Exporting as a singleton
196 * By exporting a singleton you have an object which has shared state between
197 * any modules using it.
201 * // image-database.js
203 * function ImageDatabase( )
205 * this.addImage = function() { ... };
206 * this.removeImage = function() { ... };
207 * this.getImage = function() { ...};
208 * this.getImageCount = function() { ... };
211 * module.exports = new ImageDatabase();
218 * var database = require('image-database.js');
220 * database.addImage( myImage );
224 * // another-module.js
225 * var database = require('image-database.js');
227 * // gets the same database object as main.js
230 * The first call to require('image-database.js') will create the image database.
231 * Further calls, will return the same instance, because require caches module.exports.
232 * Otherwise it would have to recompile and run the module every time require is called.
236 * #### Automatic wrapping of a module by DALi:
238 * The module is automatically wrapped in a function by DALi before being executed ( similar technique to Node.js). </br>
239 * This is to prevent any functions / variables declared by the module entering the global namespace. </br>
240 * Currently the module will have access to all DALi global functions, like log, require and the DALi API ( actors / stage etc).</br>
243 * // Parameters passed to the internally generated function
244 * // module = reference to current module
245 * // module.exports = defines what the module exports
246 * // exports = reference to module.exports
247 * // __filename = module filename
248 * // __dirname = module directory
250 * function createModuleXYZ( exports ( === module.exports), module, __filename, __dirname )
253 * // Module code automatically inserted here.
255 * log(" my first module ");
256 * var version = "1.3"; // this won't pollute global namespace
257 * exports.version = version;
258 * exports.logActorPosition = function( actorName )
260 * var actor = dali.stage.getRootLayer().findChildByName(actorName );
261 * log( actor.x + "," + actor.y + "," + actor.z );
267 * return module.exports;
271 * Initially module.exports is an object literal with name-value pairs ( exports object).
272 * However it can be re-assigned to a constructor / function / singleton object as shown
273 * in the examples above.
276 * ### Circular dependencies:
278 * DALi JS supports circular dependencies as required by the CommonJS specification.
283 * export.version = "1.3"
284 * export.loaded = false;
285 * var bModule = require('b.js')
286 * export.loaded = true;
290 * var aModule = require('a.js')
291 * log( "aModule version = " + aModule.version + ", aModule loaded = " + aModule.loaded );
293 * //prints aModule = 1.3, aModule loaded = false
297 * var aModule = require("a.js");
300 * When b.js requires a.js, it is given everything that is exported from a.js, up to the point
301 * b.js is required by a.js.
303 * ### 'require' background
305 * There is alternative to module spec in CommonJS called RequireJs ( http://requirejs.org/docs/node.html) <br />
306 * DALi JS tries to follows the CommonJS specification (used by Node.js) as it
307 * is supposed to be better suited to server side development. <br /><br />
313 void ModuleLoader::Require(const v8::FunctionCallbackInfo< v8::Value >& args )
315 v8::Isolate* isolate = args.GetIsolate();
316 v8::HandleScope handleScope( isolate );
319 std::string fileName = V8Utils::GetStringParameter( PARAMETER_0, found, isolate , args );
322 DALI_SCRIPT_EXCEPTION( isolate, "require missing module name");
326 // strip off any path / .js
327 std::string moduleName;
328 V8Utils::GetModuleName( fileName, moduleName );
330 // see if the module already exists
331 const Module* existingModule = FindModule( moduleName );
334 // printf(" using existing module %s \n",moduleName.c_str() );
335 args.GetReturnValue().Set( existingModule->mExportsObject );
339 std::string path = mCurrentScriptPath; // path of top level script being executed
340 std::string contents;
341 V8Utils::GetFileContents(path + fileName, contents);
343 // wrap the module in a function to protect global namespace.
344 // the create function itself is global so we make it unique for each module
345 // For reference nodeJs does this as an anonymous function, but we're calling it from native side
346 // so need to pass parameters / get a name for it.
347 std::string functionName ="__createModule" + moduleName;
348 std::string source = "function " + functionName + "( exports, module, __filename, __directory) { ";
350 source+=" \n };"; // close the function
352 CompileAndRun( isolate, source, fileName );
354 // We need to create module object, so that the module can read / write properties to it
356 v8::Local<v8::Object> moduleObject = v8::Object::New( isolate );
357 v8::Local<v8::Object> exportsObject = v8::Object::New( isolate );
358 moduleObject->Set( v8::String::NewFromUtf8( isolate, "exports"), exportsObject );
359 moduleObject->Set( v8::String::NewFromUtf8( isolate, "id"), v8::String::NewFromUtf8( isolate ,moduleName.c_str() ) );
361 // store the module exports object now, this is to allow for circular dependencies.
362 // If this-module requires another module, which then requires this module ( creating a circle), it will be given an export object
363 // which contains everything exported so far.
364 Module* module = StoreModule( path, fileName, moduleName, isolate, exportsObject );
366 v8::Local<v8::Context> currentContext = isolate->GetCurrentContext();
368 // get the CreateModule function
369 v8::Local<v8::Function> createModule = v8::Local<v8::Function>::Cast(currentContext->Global()->Get(v8::String::NewFromUtf8( isolate, functionName.c_str() )));
372 std::vector< v8::Local<v8::Value> > arguments;
373 arguments.push_back( exportsObject );
374 arguments.push_back( moduleObject );
375 arguments.push_back( v8::String::NewFromUtf8( isolate, fileName.c_str() ));
376 arguments.push_back( v8::String::NewFromUtf8( isolate, path.c_str() ));
379 // call the CreateModule function
380 createModule->Call( createModule, arguments.size(), &arguments[0]); //[0]
382 // get the module.export object, the module writer may have re-assinged module.exports, so the exports object
383 // no longer references it.
384 v8::Local<v8::Value> moduleExportsValue = moduleObject->Get( v8::String::NewFromUtf8( isolate, "exports"));
385 v8::Local<v8::Object> moduleExports = moduleExportsValue->ToObject();
387 // Re-store the export ( possible nothing happens, because exports hasn't been re-assigned).
388 module->mExportsObject.Reset( isolate, moduleExports);
390 args.GetReturnValue().Set( moduleExports );
394 void ModuleLoader::StorePreBuiltModule( v8::Isolate* isolate, v8::Local<v8::Object>& exportObject, const std::string& name )
396 StoreModule( "", name, name, isolate, exportObject );
399 void ModuleLoader::StoreScriptInfo( const std::string& sourceFileName )
401 V8Utils::GetFileDirectory( sourceFileName, mCurrentScriptPath);
404 Module* ModuleLoader::StoreModule( const std::string& path,
405 const std::string& fileName,
406 const std::string& moduleName,
408 v8::Isolate* isolate,
409 v8::Local<v8::Object>& moduleExportsObject )
411 Module* module = new Module( path, fileName, moduleName, isolate, moduleExportsObject );
412 mModules.PushBack( module );
417 const Module* ModuleLoader::FindModule( const std::string& moduleName )
419 VectorBase::SizeType count = mModules.Count();
420 for( VectorBase::SizeType i = 0; i < count ; ++i)
422 const Module* module = mModules[i];
423 if (module->mModuleName == moduleName )