Is it possible to import individual modules from within an optimized RequireJS/r.js bundle?
I have a javascript project broken up into two separate components - 'MyLibrary' and 'MyApplication'
MyLibrary consists of two separate modules, 'MyModule1' and 'MyModule2'.
In development mode, I can import each of these modules using RequireJS with the normal define(['MyLibrary/MyModule1'],function(){}) syntax from MyApplication.
However once running MyLibrary through r.js, this no longer appears to be possible - there doesn't appear to be a way to reference the internal modules directly anymore?
I can see from the compiled/optimized source that there are define() blocks for each module, however RequireJS within My Application doesn't appear to be able to reference these directly.
Is this possible, or will I need to bundle my entire application into a single file for this to work.
Edit: The RequireJS optimization phase is being done my the Play framework, and I have minimal control over the build config.
({appDir: "javascripts",
[info] baseUrl: ".",
[info] dir:"javascripts-min", mainConfigFile: "javascripts/build.js", modules: [{name: "main"}]})
In order to use the modules from the library, you need to instruct RequireJS how to find this modules. In main.js you need to have something like this:
require.config({
// ...
paths: {
// ...
'MyLibraryBundleName': 'dist/MyLibraryFile',
// ...
},
// ...
bundles: {
//...
'MyLibraryBundleName': ['MyLibrary/MyModule1', 'MyLibrary/MyModule2'],
//...
}
});
When MyApplication is referencing a module like this:
define(['MyLibrary/MyModule1'],function(){})
... as you mention, RequireJS will look for 'MyLibrary/MyModule1' and will find it into the 'bundles' section and after that will check the 'path' section to locate the actual file 'dist/MyLibraryFile' which will be loaded.
Related
I'm trying to add custom functionality to extend lodash (note lodash
is npm'ed in). But I keep getting a resolve error.
I've added a new file called lodash-mixins.js to my test project scripts folder e.g: project/frontend/src/web/Scripts/
var _ = require('lodash');
_.mixin({
mixinLoaded function () { console.log("lodash mixins are loaded");}
});
module.exports = _;
Overview Folder Structure (simplified)
project/frontend/src/web
...frontend.web.csproj
...angular-cli.json
project/frontend/src/web/Scripts/
...lodash-mixins.js
project/frontend/src/web/app/
...app.module.ts
I've manually added my "lodash-mixins.js" to the "angular-cli.json"
"apps": [
{
"scripts": [
"../node_modules/jquery/dist/jquery.min.js",
etc
"../node_modules/lodash/lodash.min.js",
"../Scripts/lodash-mixins.js", // <<<<< not picking up
"../Scripts/global-error-handler.js",
],
Test by changing existing reference in one of my test.service.ts
from:
"import * as _ from 'lodash';"
to:
"import * as _ from 'lodash-mixins';"
I've rebuilt my c# solution for good measure.
Run in CLI: $ng build --watch
ERROR in project/frontend/src/web/app/test/services/test.service.ts
Module not found: Error: Can't resolve 'lodash-mixins
Any ideas?
You're confusing two different things here.
1) The "scripts" config for Angular CLI tells WebPack to include those JavaScrip files in the output bundle. It doesn't add those as importable modules. They get loaded as if you added <script src="..."> tags to your HTML file (not exactly accurate, but gives the idea).
2) When you import using TypeScript it searches for the module. The reason it's giving the error is because the file isn't in one of the search paths. Had it actually found the file. It would have loaded it twice. Since you've already added it to the "scripts" config option.
JQuery, Lodash, etc.. etc.. can be loaded using modules or just plain global variables. When you add it to the "scripts" config, then I think this tells WebPack to load it in the global browser space.
When you use "import _ from 'lodash'" in TypeScript. You're actually resolving to the "#types/lodash" module which people often install so that TypeScript knows about the interface for lodash. When WebPack bundles everything for the browser it swaps out the #types for the real reference to the lodash instance.
So you have a couple of options.
1) Continue with what you've done. Add TypeScript definition file in the import path for your new module named "lodash-mixin". That module just defines the new interface with the new methods. The import will find that file and use it to compile the TypeScript, but the WebPack bundle will load your JS file in it's place.
2) Remove your "lodash-mixin" from the "scripts" config, then import using a relative path import _ from '../Scripts/lodash-mixins'. This is what I usually do. Note: You might have to add the file extension ".js"
3) Add your "Scripts" folder to your tsconfig.json as one of the type roots. This allows you to just use import _ from 'lodash-mixins'.
4) There is a way (and I forget exactly how), but you can tell TypeScript to alias lodash to your lodash-mixin so that all imports use that type instead. I don't recommend this approach.
I am trying to use video.js via webpack.
I installed video.js via npm - npm install video.js --save-dev
In webpack I read that video.js should be loaded via script loader else it throws an error.
This is how I am loading video.js through the babel loader
module:
loaders: [
{
test: /video\.js/,
loader: 'script'
}
]
I got this solution from here https://github.com/videojs/video.js/issues/2750
This is my import statement
import videojs from 'video.js';
The issue that I now face is the import is returning an empty object, so when I try to do this:
var vidTag = ReactDOM.findDOMNode(this.refs.html5Video);
this.videojs = videojs(vidTag);
I get this error:
renderer-0.js:8031 Uncaught (in promise) TypeError: (0 , _video2.default) is not a function(…)
Any help will be much appreciated. I am new to ES6 / React / Webpack
Please take a look at the loader's README before copy&pasting some random code. The script-loader is not appropiate here, because it imports scripts into the global scope while skipping the whole module system.
So, if you wanted to use the script-loader, you would just write:
import "script-loader!video.js";
console.log(videojs); // should be an object now
Usually I would not recommend the use of the script-loader because it neglects the whole point of a module system where you import stuff explicitly into the local scope. In the example above, the import happens as a side-effect into the global scope which is effectively the same as just using a <script> tag with all its downsides like name clashes, etc.
There are often better alternatives to it, like the exports-loader, which appends a module.exports at the end of the module, thus turning an old-school global script into a CommonJS module.
In this particular case, however, you don't need a loader at all because video.js is already aware of a CommonJS module system. Just write import videojs from "video.js";.
There is another minor problem, however. If you compile this with webpack, it will print a warning to the console:
WARNING in ../~/video.js/dist/video.js
Critical dependencies:
13:480-487 This seems to be a pre-built javascript file. Though this is possible, it's not recommended. Try to require the original source to get better results.
# ../~/video.js/dist/video.js 13:480-487
This is because webpack detects that this file has already been bundled somehow. Often it's better to include the actual src with all its tiny modules instead of one large dist because this way webpack is able to optimize the bundle in a better way. I've written down an exhaustive explanation about how to import legacy scripts with webpack.
Unfortunately, video.js does not include its src in the version deployed at npm, so you're forced to use the dist. In order to get rid of the error message and to improve webpack's build time, you can instruct webpack to skip video.js when parsing the code for require() statements by setting the module.noParse option in your webpack.config.js:
module: {
noParse: [
/node_modules[\\/]video\.js/
]
}
Usually it's safe to flag all pre-bundled modules (typically those with a dist folder) as noParse because they are already self-contained.
include SDN
<script src="//vjs.zencdn.net/5.11/video.min.js"></script>
webpack config:
config.externals = {
'video.js': 'videojs'
};
I'm new to RequireJS. I understand it for the most part. However, the r.js optimization process confuses me. Two questions:
Doesn't concatenating all source into a single file defeat the purpose of RequireJS's lazy-loading abilities?
If I do optimize using r.js and have everything in a single file, do I then have to manually update the path info in the config to point to that single file? And do the dependencies I've defined as individual modules have to now be changed throughout the entire application to point to this single file? Here's just a pretend source to illustrate how I'm currently setup:
requirejs.config({
paths : {
mod1 : 'app/common/module1',
mod2 : 'app/common/module2',
mod3 : 'app/common/module3',
},
});
-- MOD 1
define(["mod2", "mod3"], function(mod2, mod3) {
// do something
}
Does that now have to be manually updated after optimization to look like this?
requirejs.config({
paths : {
optimizedMod : 'build-dir/optimizedModule',
},
});
-- MOD 1
define(["optimizedMod"], function(optimizedMod) {
// do something
}
Re. 1. No, it doesn't. r.js analyzes your dependency tree and (by default) only includes modules you'd need to load on application startup anyway. The dependencies that are required dynamically won't be included, they'll be lazy-loaded at runtime (unless you set findNestedDependencies to true).
However, lazy-loading is arguably not the main benefit of using RequireJS, a bigger thing is modularisation itself. Being forced to manage dependencies makes it harder to write code that's not testable or refactorable - bad architecture can be immediatelly spotted (lengthy dependencies lists, "god" modules, etc.)
Re. 2. This is precisely the reason why you shouldn't be naming your own modules or mapping them in the paths configuration element. paths should be used for third party libraries and not your own code, centralising name->path mappings reduces flexibility. Once you refer to dependencies via their paths (relative to baseUrl) r.js can rewrite them at build time:
define(["app/common/module2", "app/common/module3"], function(mod2, mod3) {
// do something
}
I'm testing out requireJS and am trying to make a simple project using highcharts. I've started with the requireJS multipage example project as a starting point.
My dir structure looks the same as the base structure, with highstock.js added in the lib directory.
page1.html: page 1 of the app.
page2.html: page 2 of the app.
js
app: the directory to store app-specific modules.
lib: the directory to hold third party modules, like jQuery.
common.js: contains the requirejs config, and it will be the build
target for the set of common modules.
page1.js: used for the data-main for page1.html. Loads the common
module, then loads app/main1, the main module for page 1.
page2.js: used for the data-main for page2.html. Loads the common
module, then loads app/main2, the main module for page 2.
common.js holds the configuration and I've added a shim for highstock there:
requirejs.config({
baseUrl: 'js/lib',
paths: {
app: '../app'
},
shim: {
"highstock": {
"exports": "Highcharts",
"deps": [ "jquery"]
},
} // end Shim Configuration
} );
I also am using the base build file, with the addition of a line to set common.js as the config file and another to disable minifying.
optimize: "none",
mainConfigFile: '../www/js/common.js',
In apps/main1.js I've added a var HighCharts= require('highstock'); and I then try to use it.
When I run this in the normal build everything works fine. All the dependencies hold and everything loads.
When I attempt to optimize my build, highcharts doesn't receive the jQuery dependency. I think I see why its happening, but I'm not sure how to fix it.
My build creates 3 files, common.js, page1.js, and page2.js.
The relevant parts of the build output:
js/lib/../common.js
----------------
js/lib/../common.js
js/lib/jquery.js
...
js/lib/../page1.js
----------------
js/lib/../page1.js
js/lib/highstock.js
js/app/main1.js
...
My page then references the built page1. When it attempts to load the highstock module it errors out since jQuery has not yet been loaded/isn't accessible.
When I see the built page1 I can see why.
require(['./common'], function (common) {
require(['app/main1']); //highcharts is in main1 in the non-optimized version
});
define("../page1", function(){});
//a few more defines
(function () { // start highcharts module definition HERE
So instead of being defined in the callback after common (including jQuery) has been loaded, its loaded after making the request, but before the callback executes.
My question is, why is this happening there instead of inside the callback (which is where it is loaded in the non-optimized version). I've tried multiple options in the build.js file and config file and I seem to be missing some key concept or small error.
Sorry for the super long question but I felt all the info was necessary. If more info is needed I can post it, or get rid of something superfluous.
Please take look at very simple example which use require js http://jsfiddle.net/wAM3h/
require({
paths: {
jquery: "//cdnjs.cloudflare.com/ajax/libs/jquery/1.8.2/jquery.min",
hchart: [
"http://code.highcharts.com/highcharts",
"http://code.highcharts.com/highcharts-more",
"http://code.highcharts.com/modules/exporting"
]
}
},
['jquery', 'hchart'], function($, hc) {
window.chart = new Highcharts.Chart(options);
});
Not sure you're still involved with the project or not:
I see that you've not defined the path to the highcharts library in the code above. I could not see it even in the repo you mentioned.
And, again, highcharts prevents re-declaration of this namespace, so you must use a different name
- Hence, you must use a different name while shim-ming it
Note: Libraries like highcharts can be safely used in an amd module without using a shim (unless you need explicit access to the object exported by it).
So, your Config File should look like this:
requirejs.config({
baseUrl: 'js/lib',
paths: {
app: '../app',
'highstock-custom-name': 'path/to/highcharts.js'
},
shim: {
"highstock-custom-name": {
... //as is, although not necessary
}
}
});
I have many modules. One module loads another module, which loads another module. Etc...
And of course, when I load the page, all of these modules load. It works perfectly. Without the optimizer. (even though it takes a minute, because the browser has to load 50 things).
When I use optimizer...in my app.build.js, it seems that I have to manually specify each module!??
Why can't optimizer automatically traverse through the modules?
You only have to specify the module you wanna optimize, not its dependencies. From the docs:
In the modules array, specify the module names that you want to
optimize, in the example, "main". "main" will be mapped to
appdirectory/scripts/main.js in your project. The build system will
then trace the dependencies for main.js and inject them into the
appdirectory-build/scripts/main.js file.
({
appDir: "../",
baseUrl: "scripts",
dir: "../../appdirectory-build",
modules: [
{
name: "main"
}
]
})
Solved.
I had my paths wrong (I didn't understand baseURL , etc) . That's why things broke in the middle.