Possible to skip inlined, named modules with r.js build? - javascript

In my application, I have a named module defined inline in index.html:
<script>
define('config', {
mode: '...',
environment: '...',
apiUrl: '...',
rootPath: '...',
baseImageUrl: '...'
});
</script>
I need to define this in my web page because I pull some stuff in using environment variables. So, in my modules, I can then include 'config':
define(['config', 'jquery'], function(config, $) { ... });
When I run a RequireJS build using Grunt (with grunt-require, which uses r.js), it's complaining that it's not finding config.js. My build configuration looks like this:
{
baseUrl: 'js',
main: 'app/Main',
dir: 'js/build',
out: null,
modules: { ... },
paths: { ... },
shim: { ... }
}
When I build, I get:
Error: Error: ENOENT, no such file or directory '/projects/projectname/js/build/config.js
How can I allow for use of named, inline-defined modules in conjunction with an r.js build?

Setting
paths: {
config : 'empty:'
}
in your build configuration file should exclude this module out of optimization process and, as a result, stop raising this error.
But this is a bit tricky way, because this option is commonly used to exclude third-party dependencies (for example, when developing some js library based on Backbone, it would be better to separate your lib from Backbone)

Related

RequireJS bundles fail in optimizer - "modules share the same URL"

tl;dr
RequireJS optimizer doesn't like me defining bundle definitions on a module, but also does not find the modules if I don't define the bundles.
long version
I am getting the following error when trying to use the requirejs optimizer:
Error: Module loading did not complete for: scripts/simulation.bundle, app_mini, testservice
The following modules share the same URL. This could be a misconfiguration if that URL only has one anonymous module in it:
.../web/dist/scripts/app.bundle.js: app_mini, testservice
I am actually using grunt-contrib-requirejs to optimize my js scripts for production. It was all working fine before adding the simulator.bundle
I have 3 bundles:
app.bundle (main bundle)
simulation.bundle
vendor.bundle
This is the modules option of the requirejs grunt tasks
[{
name: 'scripts/vendor.bundle',
exclude: [],
override: {
paths: {
angular: 'bower/angular/angular',
jquery: 'bower/jquery/dist/jquery',
ngRoute: "bower/angular-route/angular-route"
},
shim: {
angular: {
exports: 'angular',
deps: ['jquery'] // make jquery dependency - angular will replace jquery lite with full jquery
},
bundles: {
'scripts/app.bundle': ['app_mini', 'testservice'],
},
}
}
},
{
name: 'scripts/simulation.bundle',
exclude: [],
override: {
paths: {},
shim: {},
bundles: {
'scripts/vendor.bundle': ['angular', 'jquery'],
'scripts/app.bundle': ['app_mini', 'testservice']
}
}
},
{
name: 'scripts/app.bundle',
exclude: ['scripts/vendor.bundle'],
override: {
paths: {
app_mini: 'scripts/app.mini',
testservice: 'scripts/features/test.service'
},
shim: {},
bundles: {
'scripts/vendor.bundle': ['angular', 'jquery']
}
}
}
]
The bundles in simulation.bundle seem to be the problem. However, if I remove them, the files cannot be found:
>> Error: ENOENT: no such file or directory, open
>> '...\web\dist\app_mini.js'
>> In module tree:
>> scripts/simulation.bundle
The simulation.bundle is just a dummy module, loading angular and app_mini:
define(['app_mini', 'angular'], function(app_mini, angular) {
// nothing here
}
So either way, the optimizer cannot process the dependencies. How do I have to configure it to make it work?
Alright, once again I am posting the answer to my own question, and I hope some other people will benefit from my mistakes ;)
So what I found out is:
Bundle config is only for requireJS and not for the optimizer!
The bundles I defined in the config are leading to the error of modules sharing the same url.
The right way to do it, is to define ALL the paths for ALL the modules, and to specifically exclude the modules by name that should not be included in a module.
For example, app_mini should go into the app.bundle, but because it is required in the simulation.bundle it would get included there, because excluding app.bundle is not yet possible (it has not been optimized at this point), we need to exclude app_mini directly.
So a working config would look like this: (not tested)
paths: {
angular: 'bower/angular/angular',
jquery: 'bower/jquery/dist/jquery',
ngRoute: "bower/angular-route/angular-route"
app_mini: 'scripts/app.mini',
testservice: 'scripts/features/test.service'
},
shim: {
angular: {
exports: 'angular',
deps: ['jquery'] // make jquery dependency - angular will replace jquery lite with full jquery
}
},
modules: [
{
name: 'scripts/vendor.bundle',
exclude: [],
},
{
name: 'scripts/simulation.bundle',
exclude: [`app_mini`],
},
{
name: 'scripts/app.bundle',
exclude: ['scripts/vendor.bundle'],
}
}]

Using the RequireJS text plugin

I am using Karma to run tests against some code.
Both the tests and the code are transpiled (ES6 => ES5 using babel) before being run by Karma.
This works great and the tests run fine.
But if I try and use the text! plugin from any of the files being tested...
import template from 'text!./template.html';
...I get:
There is no timestamp for /base/src/text.js!
Uncaught Error: Script error for "text", needed by: text!app/template.html_unnormalized2
http://requirejs.org/docs/errors.html#scripterror
Uncaught Error: Load timeout for modules: text!app/template.html_unnormalized2
Does anyone have any ideas why this might be?
The built artifact in the dist folder (i.e. the item under test) contains the successfully encoded text RequireJS items eg:
define('text!app/template.html',[],function () { return '<div>foo</div>';});
Additional Info
test-main.js
var TEST_REGEXP = /(spec|test)\.js$/i;
var allTestFiles = [];
Object.keys(window.__karma__.files).forEach(function(file) {
if (TEST_REGEXP.test(file)) {
var normalizedTestModule = file.replace(/^\/base\/|\.js$/g, '');
allTestFiles.push(normalizedTestModule);
}
});
require.config({
baseUrl: '/base/src',
paths: {},
shim: {},
deps: allTestFiles,
callback: window.__karma__.start
});
karma.conf.js
module.exports = function(config) {
'use strict';
var path = require('path');
var cdn = 'http://localhost:55635/modules/';
var basePath = path.dirname(__filename);
config.set({
basePath: '../../..',
frameworks: [
'requirejs',
'jasmine'
],
files: [
{
pattern: path.join(basePath, 'test-transpiled', '*-spec.js'),
included: false
},
path.join(basePath, 'dist', 'artifacts', 'app.js'),
path.join(basePath, 'test', 'unit', 'test-main.js')
],
proxies: {
'/cdn/': cdn
},
exclude: [],
preprocessors: {},
reporters: ['dots'],
colors: true,
autoWatch: false,
singleRun: false,
browsers: ['Chrome'],
});
};
Edit:
I have added the following to karma.conf.js:
files: [
{
pattern: path.join(basePath, 'node_modules/require-plugins/text/text.js'),
included: false
},
// ...
],
I continue to get an error when running the tests:
There is no timestamp for /base/src/text.js!
Presumably because I need to add "text" to the paths section of test-main.js?
require.config({
baseUrl: '/base/src',
paths: {
'text': '../node_modules/require-plugins/text/text'
},
// ...
});
But I have tried various combinations of baseUrl and the path in the text path and I cannot get it to stop 404-ing.
Your files option in karma.conf.js does not include the text plugin, which is why you get the error that there's no timestamp for it.
Add an item to your files list that hits the text plugin on your file system, and make sure that you have included: false for it. RequireJS plugins are like other modules: RequireJS must be able to load them to use them.
You may need to also set paths in test-main.js depending on where you put your plugin. RequireJS already is looking for it at /base/src/text.js. If you locate it so that the plugin is served at this URL, then there's no need to set paths. If you put it somewhere else, then you do need to set paths. Something like:
paths: {
text: 'path/to/text',
}
Remember that the paths in paths are interpreted relative to your baseUrl setting.
I tried using the require.js text! plugin, and also found that it conflicts with the baseUrl defined for the rest of the project.
The requirejs.config sets the baseUrl to the parent directory of the JS files, while my templates are defined in a sibling directory to the js.
There was no way to tell requirejs to load templates from /base/templates and js from base/js.
My solution was to change the text.js plugin, and add a hook to change the url just before the ajax call is made to fetch the HTML file. You can take my version of text.js from here.

Downloading Script Dependencies in Parallel RequireJS

Following this RequireJS example I'm trying to have a single file for all vendor js assets like jquery and foundation, whilst loading page specific code in other modules.
While I can build and copy the js successfully (using grunt requirejs optimiser) into a build folder, the baseUrl in the require.config is obviously now wrong.
baseUrl: 'js' points to public/js instead of public/build/js and so all paths are incorrect.
Is there a way of dynamically updating the baseUrl when running the optimiser? So it points to public/build/js?
Here's my grunt task:
requirejs: {
dist: {
options: {
baseUrl: '<%=pkg.paths.js%>',
dir:'project/public/build/js',
mainConfigFile: '<%=pkg.paths.js%>main.js',
optimize: 'none',
removeCombined: true,
uglify2: {
no_mangle: true,
compress: {
drop_console: true
}
},
modules: [
{
name: 'vendorCommon'
},
{
name: 'dashboard',
exclude: ['vendorCommon']
}
]
}
}
}
Vendor Common
define(['jquery', 'foundation'],
function () {
//Just an empty function, this is a place holder
//module that will be optimized to include the
//common depenendencies listed in this module's dependency array.
});
Require Config
require.config({
baseUrl: '/js',
priority: ['vendorCommon'],
paths: {
'vendorCommon':'vendor/vendor-common',
'jquery':'../bower_components/jquery/dist/jquery.min',
'foundation':'../bower_components/foundation/js/foundation/foundation',
'dashboard':'views/dashboard'
},
shim: {
'foundation': ['jquery']
}
});
I've used the optimizer's onBuildWrite setting to modify some modules when they are optimized. If your configuration is included in your optimized bundle then you could use onBuildWrite to patch it:
onBuildWrite: function (moduleName, path, contents) {
if (moduleName === '<%=pkg.paths.js%>main') {
return contents.replace("baseUrl: '/js'", "baseUrl: '/build/js'");
}
return contents;
}
Disclaimer: I'm writing this off the top of my head. Beware of typos.
Another possibility would be to override baseUrl at runtime. RequireJS is able to combine multiple configurations into a single configuration. A new value of baseUrl in a later call would override the earlier value. So if you have a way to set up the optimized version of your app (for instance, through different HTML served by your server) to call require.config({baseUrl: '/build/js'}); after the call you show in your question but before any code that needs the correct baseUrl, this could also be an option.

Avoid duplication of "paths" configuration in RequireJS main file and r.js build file?

Here is (part of) my folder structure:
node-test
bower_components
build
public
main.js
build.js
Running the optimizer with r.js -o build.js and the following configuration works fine:
// main.js file
requirejs.config({
baseUrl: '../bower_components',
paths: {
'domready': 'domready/ready',
'jquery': 'jquery/jquery',
}
});
requirejs(['domready', 'jquery'], function (domReady, $) {
domReady(function () {
});
});
// build.js file
({
baseUrl: "bower_components",
name: "./almond/almond",
include: "./../public/main",
out: "build/main.js",
paths: {
'domready': 'domready/ready',
'jquery': 'jquery/jquery',
},
preserveLicenseComments: false
})
However, if I remove paths configuration in build.js it doesn't work anymore:
Tracing dependencies for: ./almond/almond Error: ENOENT, no such file
or directory
'C:\Users\Marco\Documents\Progetti\nodejs-opt\bower_components\domready.js'
In module tree:
../public/main
Error: Error: ENOENT, no such file or directory
'C:\Users\Marco\Documents\Progetti\nodejs-opt\bower_components\domready.js'
In module tree:
../public/main
at Object.fs.openSync (fs.js:427:18)
I'd like to be DRY, avoiding adding a dependency twice. Is this possible?
If you want to use same configuration from your runtime code to find the location of your libraries, you can use the mainConfigFile option:
...if you prefer the "main" JS file configuration to be read for the build so that you do not have to duplicate the values in a separate configuration, set this property to the location of that main JS file. The first requirejs({}), require({}), requirejs.config({}), or require.config({}) call found in that file will be used.
Something like this:
({
baseUrl: "bower_components",
mainConfigFile: '/some/path/main.js', // adjust path as needed
name: "./almond/almond",
include: "./../public/main",
out: "build/main.js",
preserveLicenseComments: false
})

Require.js + Backbone optimization

Good afternoon,
I'm trying to optimize a source code based on Require.js and Backbone using r.js but I'm getting the following error during the compilation :
Tracing dependencies for: main
Cannot optimize network URL, skipping: empty:.js
TypeError: Cannot read property 'normalize' of undefined
In module tree:
main
app
router
views/main_panel/event_details
helpers/template_manager
My template_manager module does not try to access any 'normalize' property so I don't really understand what is that supposed to mean.
Here's the entry point to my application as well as the require.js configuration.
require.config({
paths: {
order: 'libs/requirejs-plugins/order',
text: 'libs/requirejs-plugins/text',
jQuery: 'libs/jquery/jquery',
Underscore: 'libs/underscore/underscore',
Backbone: 'libs/backbone/backbone',
templates: '../templates',
Sync: 'helpers/sync'
}
});
require([
'app',
'event_manager',
'order!https://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js',
'order!libs/underscore/underscore-min',
'order!libs/backbone/backbone-min',
'helpers/objects_extension',
'helpers/date_extension',
'helpers/assets'
], function(App){
App.initialize();
});
The application itself more or less follows what's in this tutorial.
My app.build.js file is as follow
({
appDir: "../",
baseUrl: "js",
dir: "../app-build",
modules: [
{
name: "main"
}
],
paths: {
order: 'empty:',
text: 'empty:',
jQuery: 'empty:',
Underscore: 'empty:',
Backbone: 'empty:',
templates: '../templates',
Sync: 'helpers/sync'
}
})
Thank you for your help.
James Burke says:
The text plugin needs be loaded by the loader for it to process text! depenencies. Loader plugins are executed as part of a build to resolve their resources.
So it should be enough to remove the text: "empty:" from the paths config, and just leave the excludes: in so it is not included in the final build result. This assumes you have text.js available locally to be read by the optimizer.
https://github.com/jrburke/r.js/issues/221

Categories

Resources