In my app there are dynamic parts that are loaded from database as string that looks like:
"define(['dependency1', 'dependency2'], function(){"+
" // function body" +
"})"
which is just a simple requireJS module, as a string. I want to lazy load the script above using async require call. So, my main requireJS script looks like:
require(["jquery"], function($){
$(document).ready(function(){
// logic to load specific script from database
var scriptString = functionToLoadTheStringAbove();
// ideally i would like to call it like this
require([scriptString], function(){
// scriptString, dependency1, dependency2 are loaded
}
});
});
How do I load those string in requireJS? I know about text plugin, but it only allow loading from files. I tried eval but it doesn't resolve dependencies correctly.
This is quite late, but I just post my solution here in case anyone needs.
So I ended up asking in requireJS forum and examining the source of text! plugin and json! plugin. The cleanest way to load module from String in RequireJS is by making your own plugin to load the String, and then use onLoad.fromText() that will eval your String and resolve all dependencies.
Example of my plugin (let's call it db! plugin):
define([], function(){
var db = new Database(); // string is loaded from LocalStorage
return {
load: function(name, req, onLoad, reqConfig){
db.get(name, function(err, scriptString){
if (err) onLoad(err);
else onLoad.fromText(scriptString);
});
}
}
});
You can then use the plugin like:
require(["jquery", "db!myScript"], function($, myScript){
// jQuery, myScript and its dependencies are loaded from database
});
Note:
There's no way to require() from String without eval. This is what onLoad.fromText() does internally. Since eval is evil, you should only use it if you know what String you're going to eval(). If you're using it in browser extension, you might want to relax the CSP policy.
To name your String module, you can use explicit naming syntax. This way, your module will always have the same absolute name.
To answer the question a little more directly, create a plugin like so:
define("load-string",[], function(){
var strings=[],
re_package_name = /^string_module_(\d+)$/;
return {
normalize: function(name, _){
if(re_package_name.test(name)){
return name
}
var nml = "string_module_" + (strings.push(name)-1);
return nml;
},
load: function(name, _, onLoad, config){
if(re_package_name.test(name)){
onLoad.fromText(strings[name.match(re_package_name)[1]]);
}else{
onLoad.error("Invalid package name: ",name);
}
}
}
});
and use it like so:
var world_module = "define([],function(){return 'world!'})";
require(["load-string!" + world_module],
function(x){
console.log("Hello "+x);
})
You should be able to do :
require(["jquery"], function($){
$(document).ready(function(){
// logic to load specific script from database
var scriptString = functionToLoadTheStringAbove();
var olddefine = define; // require js define
var runme; // capture function
define = function (args,func){
runme = func;
}
eval(scriptString);
runme(); // run the function in current scope
define = olddefine; // restore requirejs function
// dependency1, dependency2 are loaded
});
});
Related
Ghost Blog has limited functionality when it comes to outputting content to a post and it's typically done through the {{content}} helper. I am trying to add more nuanced capabilities to the content helper by creating my own handlebars helpers to output blocks of content from within the ghost {{content}} helper.
I've been making use of these two resources to create my own solutions to the problem https://www.neoito.com/ghost-cms-on-steroids-with-custom-block-helpers/
https://github.com/TryGhost/Ghost/wiki/Apps-Getting-Started-for-Ghost-Devs
Everything works fine for the most part, but I've hit a snag when trying to port html from the post {{content}} to the handlebars helper I've registered. I managed to make an ajax call using jquery from the back-end by npm installing it within the registered helper folder. The folder is set up via the method described in the second link (creating an App within the ghost content folder).
in the index file, the helper is stored in a separate function and called when the app activates. My problem is getting the helper function to accept the ajax call and extract the html from the returned value.
I'm not set on this method and there is a way that it has been done in the first link (however, it was created to solve the problem with an older versions of ghost - pre 1.0 and I'm making use of Ghost 1.2.0 so I'm aware of some "breaking changes" have been made).
I need a way to extract the post html from the server side of things (if its even possible). The internal api does not work for me unless it is called inside the activate function but I cant seem to get it to work in the helper function which is outside of the activate functions scope... I'd love some help on this.
Here my index file to give some context to this. If you need any more information, let me know and I'll post (right now I cant think of anything else you might need)
const $ = require('jquery');
var App = require('ghost-app'),
hbs = require('express-hbs'),
ghost_api = require('ghost/core/server/public/ghost-sdk'),
proxy = require('ghost/core/server/helpers/proxy'),
helpers_briefcase;
helpers_briefcase = App.extend({
// content: function () {$.get(ghost.url.api('posts', {formats:["html"]})).done(function (data){
// console.log('posts', data.posts["html"]);
// return ('posts', data.posts["html"])
// console.log('it worked');
// debug('it worked');
// }).fail(function (err){
// console.log(err);
// });
// },
//Filter handling
filters: {
ghost_head: 'handleGhostHead',
ghost_foot: 'handleGhostFoot'
},
handleGhostHead: function () {},
handleGhostFoot: function () {},
install: function () {
},
uninstall: function () {
},
//Register Handlebars Helpers on activate
activate: function (posts) {
//Test for getting post content
this.ghost.api.posts.read(0).then(function (post) {
console.log(post.title);
return post.title
});
this.ghost.helpers.register('content_block', this.content_block_helper);
this.ghost.helpers.register('if_eq', this.if_eq);
},
deactivate: function () {
},
content_block_helper: function(node, posts) {
var content = post.data.root.post.html;
var regexstring = '<content_block'+ node +'>[\\s\\S]*?<\/content_block'+
node + '>'
var regexp = new RegExp(regexstring);
if(content.match(regexp)){
var match = content.match(regexp)
match = match.replace('<content_block'+ node + '>', '');
match = match.replace('</content_block'+ node + '>', '');
return new hbs.SafeString(match)
} else {
return('My first Handlebars Helper');
}
},
if_eq: function(a, b, opts) {
if (a == b) {
return opts.fn(this);
} else {
return opts.inverse(this);
}
}
});
module.exports = helpers_briefcase;
Thanks in advance.
Disclaimer: I consider myself something between a dedicated hobbyist and an expert, so before you go pointing out what javascript sins I've committed, please keep this in mind (go easy with pointing out errors outside of the problem I'm trying to solve) *thanks S.O Community.
In a "normal" require.js function module, the module is considered "loaded" as soon as the module function returns:
define(function() {
// As soon as this function returns, the module is "loaded"
});
But I have a module that needs to do some asynchronous script loading (specifically including some Google Javascript API-s) and I don't want my module to be considered "loaded" until I say it is.
When creating a loader plugin for require.js, you are supplied with an "onload" function that you can call when the plugin is done loading. This would be perfect for my case, but I don't want my Google API wrapper to be a plugin, I want it to appear to be a "normal" module. Plugins are treated differently by the optimizer and I don't want that headache. Also plugins must be required using special syntax, and I'd like to avoid having to remember that every time I use it.
I have combed through the API several times without finding a way to accomplish what I'm trying to do. Is there an undocumented (or poorly documented) method of defining a module, where the module itself gets to decide when it should be considered "loaded"?
As an example, an implementation like this would be awesome, if it existed:
define(["onload"], function(onload) {
setTimeout(onload, 5000);
});
The first time this module was required, it should take 5 seconds to "load".
We bootstrap a lot of stuff using the convention below which is based on early releases (0.3.x) of the MEAN stack and uses the awesome async library.
Using your example, it might look something like this:
// bootstrap.js
// Array of tasks to pass to async to execute.
var tasks = [];
// Require path
var thePath = __dirname + '/directoryContainingRequires';
// Build array of tasks to be executed in parallel.
fs.readdirSync(thePath).forEach(function (file) {
if (~file.indexOf('.js')) {
var filePath = thePath + '/' + file;
tasks.push(function(callback) {
require(filePath)(callback);
});
}
});
// Execute parallel methods.
async.parallel(
tasks,
function(err) {
if(err) console.error(err);
console.log('All modules loaded!');
process.exit(0);
}
);
The file being required looks similar to this:
// yourModule.js
module.exports = function(moduleCallback) {
setTimeout(function() {
console.log('5 seconds elapsed');
moduleCallback(null);
}, 5000);
};
I'm trying to use a library -- Google's libphonenumber -- in my require application that is not AMD. What is the best way to consume this? I know I can create a module like this:
define(['module'], function (module) {
// insert and return library code here.
});
But that doesn't seem great. It seems like I would have to refactor some of their code to get that working (e.g., turn it all into an object and return that object). I see a lot of libraries using a different pattern where they use an immediately invoked function that defines the module on the window object and returns it.
(function() {
var phoneformat = {};
window.phoneformat = phoneformat;
if (typeof window.define === "function" && window.define.amd) {
window.define("phoneformat", [], function() {
return window.phoneformat;
});
}
})();
** UPDATE **
Is there any reason not to just do this?
define(['lib/phoneformatter'], function(phoneformatter) {
});
I get access to all of my methods but now it seems they are global because I did not wrap the library in a define...
Use RequireJS's shim. It'll look something like this
requirejs.config({
shim: {
'libphonenumber': {
exports: 'libphonenumber' // Might not apply for this library
}
}
});
This will load libphonenumber and put its variables in the global scope
This ended up working for me:
define(['module'], function (module) {
// insert and return library code here.
});
I am not entirely sure why 'module' was necessary. But it doesn't work without it. Also, I just returned an object and attached functions to it like so:
return {
countryForE164Number: countryForE164Number,
nextFunction: nextFunction,
// more functions as needed.
}
There is not much in the way of documentation for using 'module' but from what I can ascertain: Module is a special dependency that is processed by requireJS core. It gives you information about the module ID and location of the current module. So it is entirely possible that I messed up the paths in config.
I have an external API which provides a script file with javascript callback function in it. It assumes the function is implemented in my code.
eg a JS file with the following content:
aCallback({json:'stuff'});
I would like to wrap this in a requireJS module but am not too sure how to go about it.
I have tried the following shim:
define("my-wrapper", [], function () {
return function(data){ console.log(data); }
}
);
var require = {
shim: {
"my-wrapper": {exports: "aCallback"},
"http://api.con/service": ["my-wrapper"]
}
};
require(["http://api.con/service"], function (service) {});
but it says when it try's to load the service that aCallback is undefined. What have I got wrong? Is there a better way to wrap this kind of script?
You might want to try requiring the callback as a dependency instead of using shim so that it does exist in the same scope.
require(["http://api.con/service", "my-wrapper"], function (service, aCallback) {
});
It does depend on how the service expects to consume the callback though.
Is there a preferred way to pass server data in a RequireJS module? Our current implementation looks like the following code snippets; using a 'page' object to hold any server/dynamic data and passing that to the main bootstrap. (We don't want to use ajax to populate any dependencies at this time)
From a server page :
<script data-main="scripts/main" src="scripts/require-jquery.js"></script>
<script type="text/javascript">
define("page", function () {
return { guid: "<%=Guid.NewGuid() %>" };
});
</script>
main.js
require(["jquery", "jquery.alpha", "page"], function ($, alpha, page) {
alpha.initialize(page);
});
jquery.apha.js
define(["jquery", "page"], function ($, page) {
return {
initialize: function () {
console.log(page.guid);
//logs guid as expected
}
}
});
I usually do something like this (using PHP on the back-end but anything works):
<script src="scripts/require-jquery.js"></script>
<script>
require(['scripts/main'], function(App) {
var myApp = new App({
param1: <?=json_encode($param1);?>,
param2: <?=json_encode($param2);?>
});
});
</script>
And then define my module as something that takes a config:
define(['jquery'], function($) {
var App = function(options) {
this.options = options;
//blabla
}
// add some stuff to App.prototype maybe
// and finally...
return App;
});
RequireJS says nothing about how to deal with server data, as it is a means to modularize your javascript. So in that regard there is no defacto standard and you can combine RequireJS with json, ajax, php, embedded xml etc however you want.
Two Approaches
There generally are two ways to go about this.
Model a 'dao' or 'service' module that gets the required data from the server and
makes it accessible to its users (similar to your current approach, see code sample below)
Define a global object to which all modules have access
The first approach adds parameters to your functions.
The second provides global access. This also requires your own initialization code to start fetching data.
It comes down to personal preference and how many of these 'dao's' you have. If you have more than one it might become poluting as you need a new parameter for each dao module. In that case making them global seems cleaner.
A problem with your approach
There is a problem with your current approach though, where you have the Page module as a definition (using define() instead of require()), because a define module is created for each object that depends on it. This potentially means multiple calls within the same page. Instead use:
// in seperate file page.js:
require([], function () {
return { guid: "<%=Guid.NewGuid() %>" };
});
This way RequireJS recognizes page as a module because it is a seperate file and it will go to your server only once per page.
If you have a JSON object, make an AJAX call like #yves mentioned in the comments.
There are other options if you don't want to do that. You could put the guid as a data attribute on the script tag. Also, you could try making the loader js file dynamic so the config is set in that.
Honestly though, I'd just make an AJAX call.
I just started today with RequireJS and prior to this I was used to just call the function I wanted to execute on page load like this:
<script>
my_method(<?php echo json_encode( array('opt1'=>true, 'opt2'=>false) );?>);
</script>
As #ziad-saab I've found that the most similar thing I can do is not using the data-main attribute and just define an inline module:
<script src="path/to/require.js"></script>
<script>
require(['my/module'],function(module){
module.my_method(<?php echo json_encode( array('opt1'=>true, 'opt2'=>false) );?>);
});
</script>
The data-main attribute instructs RequireJS to execute the module as soon as require.js and all module dependecies are loaded. Omitting it (the module) and just defining it as an inline module I'm able to throw in PHP variables.
This way I don't need to handle with modules that hold my configurations and the transition to use requirejs is easier in my environment.
I have found some of the answers confusing, so here are the exact steps you need to follow to make it work for you:
In my case I am doing this like so:
index.php
<script src="/js/crm/lib/require.js"></script>
<script>
// this is so called "named define"
define('dto', {
page: JSON.parse('{{ pageDTO | json_encode }}'),
flashMessages: JSON.parse('{{ this.flashSession.getMessages() | json_encode }}')
});
// note we are using relative path to public dir here
// order is also important, we need to define our dto module before bootstraping the application
require(['/js/crm/app.js']);
</script>
app.js
"use strict";
require.config({
// ...
baseUrl: '/js/crm/lib',
paths: { app: '../app' }
});
require(['app/bootstrap']);
some-module.js
(in this case layout.js that is required in app/bootstrap)
"use strict";
define([
'dto',
'jquery',
'lodash'
], function (dto, $, _) {
console.log(dto);
});
Note using data-main to bootstrap the application, without explicit call to require might work, but due to race condition. If defining dto for some reason would take more than it takes requirejs to call main module script will crash. We don't want to rely on that, so we do everything ourselves :)
So this would not work (sometimes):
<script data-main="/js/crm/app.js" src="/js/crm/lib/require.js"></script>
<script>
// this is so called "named define"
define('dto', {
page: JSON.parse('{{ pageDTO | json_encode }}'),
flashMessages: JSON.parse('{{ this.flashSession.getMessages() | json_encode }}')
});
</script>
Use window global variable to transfer server data into js application:
<script type="text/javascript">
window.server_data=parseJSON(<?php echo json_encode(array ("server_data"=>"it works!"));?>);
</script>
<script data-main="js/application" src="js/lib/require.js"></script>
in application.js:
requirejs(["app/main"],function (MyApp){
console.dir(window.server_data); //all our application need this global variable
var myApp=new MyApp();
myApp.init(window.server_data); //and your application now has server data
});