How to use jquery from views on rails 5.1.3 + webpacker - javascript

I want to use a simple jQuery function like so:
$( document ).ready(function(){
$('body').addClass("faded");
});
From a single view in a rails 5.1.3 app, in this case 'login.html.erb'
On webpack's application.js I have:
var $ = require('jquery');
which makes the aforementioned jquery call work IF I put it on the same application.js file but this would mean it would be called for all the pages since <%= javascript_pack_tag 'application' %> is on the layout.
When I try to run it on login.html.erb like so:
<script type="text/javascript">
$( document ).ready(function(){
$('body').addClass("faded");
});
</script>
I get an error on console: "ReferenceError: $ is not defined".
If I try to do " import $ from 'jquery'; " on the login.html.erb file I get this error instead: "SyntaxError: import declarations may only appear at top level of a module" which understandably means that my local .erb view doesn't get access to the javascript referenced on webpacker's application.js and cant import it from there.
How can I reference jquery and for that matter any module being served by webpacker, from the views?
I apologize if this has been asked before, I'm posting this question after days of reading about webpack, the webpacker gem and javascript without finding a solution.
:)

Install jQuery /bin/yarn add jquery
open config/webpack/shared.js, and add/overwrite with this code:
module: {
rules: sync(join(loadersDir, '*.js')).map(loader => require(loader)),
noParse: function(content) {
return /jquery/.test(content);
}
},
plugins: [
new webpack.EnvironmentPlugin(JSON.parse(JSON.stringify(env))),
new ExtractTextPlugin(env.NODE_ENV === 'production' ? '[name]-[hash].css' : '[name].css'),
new ManifestPlugin({
publicPath: output.publicPath,
writeToFileEmit: true
}),
new webpack.ProvidePlugin({
$: "jquery",
jQuery: "jquery",
"window.jQuery": 'jquery',
}),
],
resolve: {
extensions: settings.extensions,
modules: [
resolve(settings.source_path),
'node_modules'
],
alias: {
jquery: 'jquery/dist/jquery.slim.js',
}
},
you might want full jQuery, instead jQuery slim
import jQuery global.$ = require('jquery') into any js file on app/javascripts/packs
Now you can use it
$(document).ready(function() {
//
});

First things first. You cannot transpile es6 in erb templates within script tags with webpacker.
Since introduction of assets pipeline in rails the general idea is to deliver bundles of multiple javascript files. Prior to webpacker by defining sprockets manifests and now with packs. This allows to save http requests for the js assets files. The browser loads only one file with all of the javascript on the first http request to the app and caches it for further page requests.
Thus you can define separate js files for the parts of your application and import the all in the application.js pack. Only pack files can be inlcuded via a javascript_include_tag. You must also import jquery in each imported es6 module, because es6 modules have their own variable scope. (just check the transpiled output in chrome dev tools)
However now, you can`t just call addClass on body, because you only want it to happen on the login page. One way to solve is setting a separate class (e.g ".login") within the erb template and use it as a selector.
//app/javascript/packs/application.js
import 'startpage'
import 'login'
//app/javascript/src/login/index.js
import $ from 'jquery'
$('.login .content').addClass("faded");
If faded really has to be added on the body (which is not in the template) you can either try select it via parent matcher from the ".login" class or somehow introduce a variable in the rails layout which is set from within an erb template, in order to add controller specific marker for the layout.
Ofc you can also deliver a separate js file per page, if you think that this does not produce to many http requests and the speed is good enough for your users. Just make them all separate packfiles within packs folder.

Related

Mark script as already defined in require js

I had to move some scripts to synchronious load through bundle, and I want to set those scripts as already defined, so require.js would not ask server for them in next calls.
Let me explain:
I had some scripts that were required everywhere, e.g. i18n, and jquery. So I have hundreeds if calls all around the project such as
require(['jquery', 'i18n', 'commonjs', ...
I bundled 'jquery', 'i18n', 'commonjs' into one script core.js which now is inserted in layout <script src="/core.js"></script>
All functions from jquer, i18n now can be accesses globally, without need of requiring them. I want to specifically say to reuire.js that those scripts are already loaded, something wich bundles should do.
I've read article about using bundles and tryied to put
bundle in my config file
bundles: {
'core': ['jquery', 'i18n', 'commonjs']
}
but it doesnt work, there lots of mistakes fallen and as I understood th only way to use bundle is to use r.js which optimizes js and folders.
Actually, all I want is to set some scripts as already loaded for require.js. Is ther a dirty way to do it?
There's no configuration option to mark a script as already defined. What you can do is to call define yourself with a module name and an appropriate return value. For instance, if you load jQuery with a script element before you start loading any module through RequireJS, you can do:
define("jquery", [], function () {
return $;
});
The code above makes it so that whenever any module requires the module jquery, they get the value of $. I normally place such modules like the one just before my call to require.config. It's just a convenient place for them.

Requirejs static included script the callback does not get executed

In a current project i need to use RequireJS as well with dynamic included modules as with static included javascripts.
The static included scripts are needed for every page and get concatinated at the end of the development process. These are for example jQuery jQueryUi and some more scripts like an autosuggest script.
The dynamic part is only used on some pages like a configurator which has an additional script needed that is to big to be loaded on every page. That's why this script is loaded as a module on the page where it is needed.
While jQuery and jQueryUi are playing nice and are available as amd modules due to the how the factory method gets executed:
(function(t){
"function"==typeof define&&define.amd?define('jquery-ui',["jquery"],t):t(jQuery)
})(function(t){...});
This is not true for the autosuggest part. Though i added the functionality in exact the same pattern:
(function(t){
"function"==typeof define&&define.amd?define('suggest',['jquery','jquery-ui'],t):t(jQuery)
})(function(t){
console.log('yeah');
});
The yeah part inside the factory does not get executed.
What am i missing? Is there anything else needed to make it work?
From the RequireJS documentation (http://requirejs.org/docs/api.html):
Ideally the scripts you load will be modules that are defined by
calling define(). However, you may need to use some traditional/legacy
"browser globals" scripts that do not express their dependencies via
define(). For those, you can use the shim config. To properly express
their dependencies.
You can try using something like:
requirejs.config({
...
shim: {
'suggest': {
deps: ['jquery', 'jquery-ui'],
exports: 'suggest'
}
}
...
}
You might need to add sugget to you paths property too.

How let users to access requirejs modules outside main?

I'm implementing an AMD module oriented js framework that will be used on third party sites.
With this line, framework users will configure necessary modules
<script data-main="/main.js" src="/require.js"></script>
The problem is that data-main reference is loaded asynchronously so any js logic depending on modules loaded by main.js would fail unless I can be sure that it finished loading.
I'm pretty new to requirejs so not sure what's the good practices to create a framework that will be used by other people.
How could resolve this very simple problem?
EDIT
An example to explain my point
main.js
requirejs.config({
paths: {
jquery: '//ajax.googleapis.com/ajax/libs/jquery/1.11.0/jquery.min',
}
});
main js reference + extra code
<script data-main="/main.js" src="/require.js"></script>
<script>
require(['jquery'], function($) {
// since main.js is not loaded yet, it will assume that there is a jquery.js
// file on the same folder that this file
console.log('myModule was also loaded and can use jQuery');
});
</script>
If you want to depend on other libraries and are specifically targeting being in a Require pipeline, all you need to do is to declare some dependencies with
define(
'myModule', // module name
['foo'], // module dependencies
function(foo){ // module construction
var myModule = {};
.... code to set up the module ....
return myModule;
});
and Require will take care of things. This will register your module with Require and won't attempt to build your module until all of your dependencies are available. This functionality is discussed here.
Update with example
Require JS is designed to work both with and without a prebuilt configuration. The paths property of the Require config object only provides Require with information on how to attempt to find libraries which have not yet been registered. However, the registration and then dependency resolution is handled by Require regardless of how/where the module was registered. Please see this JSFiddle for a working example of how you can register and use dependencies.
Update 2 regarding config
Since RequireJS loads everything asynchronously, you are correct, your code example will not work. However, you're making an incorrect assumption about how it is "supposed" to work. You have an incorrect example of what your library's clients' Require configuration will look like. If someone else is building an application using RequireJS and they want to use your library, they should declare the path to your library in their require.config:
require.config({
paths: {
// this tells require how to load jQuery (a library maintained neither by you nor your clients).
'jquery': '//ajax.googleapis.com/ajax/libs/jquery/1.11.0/jquery.min',
// this tells require how to load myModule (the library you are building for your clients).
'myModule': '//example.com/js/my-module.min',
// this tells require how to load foo (a library built and hosted by your clients).
'foo': 'scripts/foo'
}
});
On the other hand, if your clients can't update their Require config to include your library in the declarations, then you're out of luck. All you can do is take all of your dependencies and bundle them up in your distribution file and then declare no dependencies:
define(
'myModule',
[], // can't declare any dependencies
function() {
// these dependencies are inside of the definition function to keep the global namespace clean
// since we need jQuery, we have to inline it:
var jQuery = ....
// same goes for d3.js
var d3 = ....
// now we can set up the module itself
var myModule = {};
.... code to set up the module ....
return myModule;
}
);
Obviously, this option means that you can't use the libraries which are being used by your clients. This means your library will be a lot heavier and include effectively duplicate code and data.
Hope that helps you understand how Require works and how other people will use your library.
I've finally used this approach
<script src="http://mydomain/js/require.js"></script>
<script>
requirejs.config({
baseUrl: 'http://mydomain/js'
});
require(['main'],function(){
// here users can do anything they want as all required libraries are loaded
});
</script>
main.js is loaded with a require instruction instead of using data-main attribute from script tag , this provides a callback where users can put their code.

RequireJS - Importing modules inside r.js optimized bundle

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.

Preserving jQuery dependency for highcharts with requireJS optimizer

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
}
}
});

Categories