Using RequireJS successfully for a single page application for quite some time. Finally adding JS unit tests to exercise model classes. I have a single config for src modules and another for test modules. (I want to keep test files out of the shipping distribution)
I can load src from index.html and test from LocationTest.html only if both files are in the same top-level folder. I would like to move LocationTest.html inside the test folder, but no change to data-main or the test config file seems to work.
Here is the file layout for the happy path...
/www
- index.html
- LocationTest.html
- js
- src
- lib
- require.js
- require_src_config.js
- require_main.js
- require_test_config.js
- require_test.js
- model
- Location.js
- (other src model classes)
- test
- lib
- (jasmine libs)
- model
- LocationTest.js
- (other test model classes eventually)
index.html
...
<script type='text/javascript' data-main='js/src/lib/require_main' src='js/src/lib/require.js'></script>
...
LocationTest.html
...
<script type='text/javascript' data-main='js/src/lib/require_test' src='js/src/lib/require.js'></script>
...
js/src/lib/require_src_config.js
requirejs.config({
// Base URL for main application
baseUrl: 'js',
// Shortcuts to modules relative to baseUrl
paths: {
Location: 'src/model/Location'
(many other modules...)
}
});
js/src/lib/require_test_config.js
requirejs.config({
baseUrl: 'js',
// Paths (no extension)
paths: {
// Tests
LocationTest: 'test/model/LocationTest',
// Framework
jasmine: 'test/lib/jasmine/jasmine',
jasmineHtml: 'test/lib/jasmine/jasmine-html',
jasmineBoot: 'test/lib/jasmine/boot',
},
// Make external libraries compatible with requirejs (AMD)
shim: {
jasmineHtml: {
deps : ['jasmine']
},
jasmineBoot: {
deps : ['jasmine', 'jasmineHtml']
}
}
});
js/src/lib/require_main.js
requirejs(['./require_src_config'], function (require_src_config) {
(Application Code)
});
js/src/lib/require_test.js
requirejs(['./require_src_config'], function (require_src_config) {
requirejs(['./src/lib/require_test_config'], function (require_test_config) {
requirejs(['jasmineBoot'], function (require) {
requirejs(['LocationTest'], function (LocationTest) {
// Trigger Jasmine
window.onload();
});
});
});
});
The above all works, although I do not understand why I had to revise the path to require_config_test.js in require_test.js. The baseUrl for both configs is 'js'.
I would like to move LocationTest.html to js/test/model.
1) What should my data-main be set to?
2) How (and why) does this impact require_test.js settings?
3) Is there a better way to nest (or not) the configs for src and test to ensure src gets loaded first?
I was hoping to only have to set data-main to the path of the file with the entrypoint and be flexible to move things around. Thanks for your help!
Related
I'm new to bundlers and am currently learning about Fusebox. I really like it so far except that I can't figure out how to use it for a multi-page project. So far I've only been able to find a tutorial on how to do this using webpack, not for fusebox.
Input files in src folder:
index.html
index2.html
index.ts
Desired output in dist folder:
app.js
vendor.js
index.html
index2.html
Actual output in dist folder:
app.js
vendor.js
index.html
Here is my config in the fuse.js file:
Sparky.task("config", () => {
fuse = FuseBox.init({
homeDir: "src",
output: "dist/$name.js",
hash: isProduction,
sourceMaps: !isProduction,
plugins: [
[SassPlugin(), CSSPlugin()],
CSSPlugin(),
WebIndexPlugin({
title: "Welcome to FuseBox index",
template: "src/index.html"
},
WebIndexPlugin({
title: "Welcome to FuseBox index2",
template: "src/index2.html"
},
isProduction && UglifyJSPlugin()
]
});
// vendor should come first
vendor = fuse.bundle("vendor")
.instructions("~ index.ts");
// out main bundle
app = fuse.bundle("app")
.instructions(`!> [index.ts]`);
if (!isProduction) {
fuse.dev();
}
});
Setting WebIndexPlugin twice within plugins doesn't work. What is the correct way to set up a multi-html page project with fusebox?
The WebIndexPlugin can not be configured, to output more than one html file.
But if you don't use a hash for the generated bundles (e.g.: output: "dist/$name.$hash.js"), you don't need the WebIndexPlugin -- you can remove it completly from the plugins option. Because you already know the names of the generated bundles (vendor.js and app.js) you can just include the following lines
<script src="vendor.js"></script>
<script src="app.js"></script>
instead of the placeholder $bundles.
If you want, that both html files are copied from your src directory into your dist directory, you can add the following lines to your fuse.js script:
const fs = require('fs-extra');
fs.copySync('src/index.html', 'dist/index.html');
fs.copySync('src/index2.html', 'dist/index2.html');
Note: Don't forget to add fs-extra:^5.0.0 to your package.json
Might not been the case when the question was asked, but WebIndexPlugin now can be specified multiple times and also takes optional bundles parameter where list of bundles to be included in html can be specified (all bundles are included by default).
For example 2 html files (app1.html, app2.html) where each includes a common library (vendor.js), and different entry points (app1.js and app2.js)
app1.html
vendor.js
app1.js
app2.html
vendor.js
app2.js
Config would look like this:
const fuse = FuseBox.init({
homeDir : "src",
target : 'browser#es6',
output : "dist/$name.js",
plugins: [
WebIndexPlugin({
target: 'app1.html',
bundles:['vendor', 'app1']
}),
WebIndexPlugin({
target: 'app2.html',
bundles:['vendor', 'app2']
})
]
})
// vendor bundle, extracts dependencies from index1 and index2:
fuse.bundle("vendor").instructions("~[index1.ts,index2.ts]")
// app1 and app2, bundled separately without dependencies:
fuse.bundle("app1").instructions("!>index1.ts")
fuse.bundle("app2").instructions("!>index2.ts")
While using MVC and requireJs for AMD i am stuck in a problem where i want to load the view-model script when the main script file for respective module is called instead of defining all the view-models in require.configure.js
Here is the Sequence of Code:
BundleConfig.cs
bundles.Add(new ScriptBundle("~/bundles/app").Include(
"~/Scripts/Common/require.configure.js",
"~/Scripts/Common/require.js"));
Index.cshtml
#section scripts {
#Scripts.Render("~/bundles/app")}
<script data-main="Scripts/BookService/Testing/HomeMain"></script>
require.configure.js
var require = {
baseUrl: "/",
paths: {
"bootstrap": "Scripts/Common/bootstrap",
"jquery": "Scripts/Common/jquery-1.10.2",
"knockout": "Scripts/Common/knockout-3.3.0",
"knockout-projections": "Scripts/Common/knockout-projections.min",
"domready": "Scripts/Common/domReady",
/////////View Models///////////////////////////////////////
"HomeViewModel": "Scripts/BookService/Testing/HomeViewModel"
},
shim: {
"bootstrap": {
deps: ["jquery"]
}
}}
HomeMain.js
require(['knockout', 'HomeViewModel'], function (ko, appViewModel) {
ko.applyBindings(new appViewModel());});
HomeViewModel.js
define(['jquery','knockout'], function ($,ko) {
return function appViewModel() {
//viewModel here
}
});
Scripts File hierarchy:
You can use require.config for adding to config at run time. I am not sure if this is officially supported (will try to find references), but when I tried it worked.
Use it like following to add new paths to your config whenever you want.
require.config({
paths : {
"HomeViewModel" : '/path/to/homeviewmodel'
}
});
Take care of the absolute and relative path as per requirement.
I'm having a hell of a time with Karma/Jasmine. I'm just trying to run the example specs from Jasmine's site.
When I run jasmine on command line, the tests run fine. However, if I try to run them using Karma test runner, I have a multitude of problems.
Here's My File Structure
Here's my karma.conf.js file:
module.exports = function(config) {
config.set({
// base path that will be used to resolve all patterns (eg. files, exclude)
basePath: '',
// frameworks to use
// available frameworks: https://npmjs.org/browse/keyword/karma-adapter
frameworks: ['jasmine', 'requirejs'],
// list of files / patterns to load in the browser
files: [
'node_modules/requirejs/require.js',
'**/test-main.js', {
pattern: 'spec/jasmine_examples/*.js',
included: false
}
],
// list of files to exclude
exclude: ['**/*conf.js'],
...port,browser etc.
Here's my test.main.js file
var allTestFiles = [];
var TEST_REGEXP = /(spec|test)\.js$/i;
var pathToModule = function(path) {
return path.replace(/^\/base\//, '').replace(/\.js$/, '');
};
Object.keys(window.__karma__.files).forEach(function(file) {
if (TEST_REGEXP.test(file)) {
// Normalize paths to RequireJS module names.
allTestFiles.push(pathToModule(file));
}
});
require.config({
// Karma serves files under /base, which is the basePath from your config file
baseUrl: '/base',
// dynamically load all test files
deps: allTestFiles,
// we have to kickoff jasmine, as it is asynchronous
callback: window.__karma__.start
});
When I run karma start, I get a 404 that PlayerTest.js and SongTest.js are not under base/. However they are loaded and located in base/spec/jasmine_examples. In addition, PlayerTest.js throws an error "module not defined".
Honestly, what am I doing wrong?
I think you need to refer to the karma-requirejs and the karma-jasmine plugins in the karma.conf.js file -
config.set({
plugins: [
'karma-jasmine',
'karma-requirejs'
],
From karma doc, it states:
Please note just about all frameworks in Karma require an additional plugin/framework library to be installed (via NPM).
Additional information can be found in plugins.
You will not need to have require.js in the files section.
I've seen the many solutions for using urlArgs to force the file to change but is there an efficient way to have it automatically change only single files if the file has been updated?
Is it possible to bust the cache of only files that have been modified?
The biggest example being this topic.
function bust(path) {
return path + '?bust=' + (new Date()).getTime();
}
require.config({
baseUrl: '/base/path',
paths: {
'fileAlias': bust('fileLikelyToChange'),
'anotherFileAlias': bust('anotherFileLikelyToChange'),
'jQuery': 'jQuery'
},
});
The problem is this solution busts the cache every time instead of only when the file has been modified.
My solution may be a bit heavy handed and is a hybrid of this and other solutions posted but it works for me.
Part 1:
In the html, append all dependent scripts with cache busting query string-->
<script type="text/javascript">
var require = {
urlArgs : "bust="+ Math.random()
}
</script>
<script data-main="js/app" src="js/require.js"></script>
Part 2:
Then in app.js add a custom bust method to apply to only the files that may have changed:
function bust(path) {
return path + '.js?bust=' + Math.random();
}
Part 3:
update the urlArs with an empty string: urlArgs: "" to overide cache busting query string ubiquitously applied in Part 1 and add the cache busting method only to files of your choice:
requirejs.config({
urlArgs: "",
paths: {
'utilities': 'utilities',
'controller': bust('controller'),
'main': 'main'
}
});
My question, while at first somewhat similar to this one, seems to be a more basic question - and might be signaling a bug in the build system. I've created a custom build for my dojo application. I only build one layer right now, here's what the profile script/object looks like:
dependencies = {
stripConsole: "all",
action: "release",
optimize: "shrinksafe",
releaseName: "myProject",
// list of locales we want to expose
localeList: "en-gb,en-us,de-de",
layers: [
{
// Name: a relative path from the dojo.js in the desination directory.
name: "../../myProject.js",
dependencies: [
"myPackage.MyDataStore",
// MyWidget depends on a few other widgets, and has its own
// translation files.
"myPackage.MyWidget"
]
}
],
prefixes: [
// These paths are relative to the location of dojo.js
[ "dijit", "../dijit" ],
[ "dojox", "../dojox" ],
[ "myPackage", "../../../src/myPackage" ]
]
}
When I run a build with that description it outputs files in the following directory structure:
release/
release/myProject/
release/myProject/dijit/
... dijit ...
release/myProject/dojo/
... dojo ...
release/myProject/dojox/
... dojox ...
release/myProject/myPackage/
... my custom package ...
release/nls/
myProject_en-us.js
myProject_de.js
etc..
../myproject.js
../myProject.js.uncompressed.js
Finally, in my test HTML page - I've got the following:
<script type="text/javascript">
var djConfig = {
debug: true,
parseOnLoad: false,
modulePaths: { // paths to directories in relation to dojo's location.... hurr.
'myPackage': '../myPackage',
'dojox': '../dojox',
'dijit': '../dijit'
}
};
</script>
<script type="text/javascript" src="./release/myProject/dojo/dojo.js.uncompressed.js"></script>
<script type="text/javascript" src="./release/myProject.js.uncompressed.js"></script>
<script type="text/javascript">
dojo.addOnLoad(function(){
dojo.require('myPackage.MyDataStore');
dojo.require('myPackage.MyWidget');
var store = new myPackage.MyDataStore();
var widget = new myPackage.MyWidget({
store: store
}, dojo.byId('testWidget'));
widget.startup();
});
</script>
But unfortunately, Firebug spits this out at me:
Bundle not found: MyWidget in myPackage , locale=en-us
What I Think is Happening
I've traced through some of the code leading up to the above error and it seems like the dojo.i18n._preloadLocalizations() call at the end of the file doesn't actually load in the correct nls file from ./release/nls.
Any idea how to fix this without resorting to manually including the nls files with <script> tags?
It's a bug of dojo, you should not use '..' in your layers name in case it will generate a NLS package.
please refer to http://bugs.dojotoolkit.org/ticket/5225