How to import a .js module in Frappe?

I have a custom .js file attached to the main page via hooks.py and I’d like to clean it up by breaking into separate modules. Is it possible to import a .js module without adding it to the hooks.py?

I tried standard ES6 imports, but since hooks.py do not attach the .js file as type="module" I can’t use the import system. I tried using the frappe.require() function, but couldn’t figure out how it works.

Okay, I figured out this one also.

You frappe.require() a script symlinked to assets/app_name/js/... folder of the site in question, then use the imported function in the provided callback. The callback is executed in the scope, containing the imported function and you can assign it to a variable you define:

var imported_helper = undefined;
frappe.require('assets/app_name/js/utils/utilities.js', () => {
    imported_helper = helper_defined_in_utilities_js;
});

imported_helper(args);

It would be lovely if the docs mentioned it somewhere :neutral_face:

7 Likes

Hi. I know this is an ancient topic at this stage, but I thought you might appreciate my input as it will simplify your life a lot when it comes to doing custom scripts.

I had been facing an issue with importing my own libraries of functions. I had found the documentation for frappe.require() but it said that you need to put your code that makes use of the called assets inside the callback so I put my entire script inside a frappe.require() callback, however that created loading issues. Eventually I found your thread, and your example works so thank you very much. The optimisation that I have found for it is that you do not need to set functions to the functions that you are after in your script you simply call the frappe.require() and all functions listed in your assets will become available, so in your case:

var imported_helper = undefined;
frappe.require('assets/app_name/js/utils/utilities.js', () => {
    imported_helper = helper_defined_in_utilities_js;
});

imported_helper(args);

becomes:

frappe.require('assets/app_name/js/utils/utilities.js');

imported_helper(args);

I hope that helps, and thank you for giving me your solution :smile:

I’ve been trying to import a node module but its not working Can't import external JS Library in Frappe App, I’ve tried frappe.require('/assets/myapp/node_modules/@xeokit/xeokit-sdk/dist/xeokit-sdk.min.es.js'); but that gives

VM788:1 Uncaught SyntaxError: Unexpected token 'export'
    at Object.eval (dom.js:33)
    at Object.js (assets.js:162)
    at Object.eval_assets (assets.js:89)
    at assets.js:76
    at Object.callback (assets.js:127)
    at Object.callback [as success_callback] (request.js:78)
    at 200 (request.js:122)
    at Object.<anonymous> (request.js:247)
    at i (jquery.min.js:2)
    at Object.fireWith [as resolveWith] (jquery.min.js:2)

Hi @satyajit_ghana. Try removing your export statements. Export is not needed in frappe.require() and if I remember correctly, it does interfere with the import if the keyword is found in your script.

As an example, if you had a script, stored in your app’s public/js directory, with a function that prints a greeting message, and you wanted to call it in your document each time you refresh the page:

example.js

function greet_user() {
    frappe.msgprint("Hello " + frappe.session.user_fullname)
}

Then in your client script:

frappe.require([
    '/assets/example_app/js/example.js',
]);

frappe.ui.form.on('Example Doc', {
    refresh(frm) {
        greet_user()
    }
}

Does this help?

Hi @archais , thanks for the reply

My concern is better explained in this: Can't import external JS Library in Frappe App

So what i basically want to do is import a node_module (xeokit) in a js file, but i’m not able to.

I don’t want to modify the node module package (xeokit) because every time i deploy i have to do that, and every time there’s a npm i i have to do that.

Ah, I see your problem now. Unfortunately, I’ve never worked with NPM modules, nevermind using them in Frappe. However, I did find a tutorial for using them in Frappe, have you seen this: Tutorial: Adding node modules to Frappe?

yes i did

node_modules = {
	'xeokit': {
		'js': [
			'/assets/myapp/node_modules/@xeokit/xeokit-sdk/dist/xeokit-sdk.es.js'
		]
	}
}

# include js, css files in header of desk.html
app_include_js = [
	*node_modules.get('xeokit').get('js'),
	# "/assets/js/xeokit.min.js",
	"/assets/js/myapp.min.js"
]

this is my build.json

{
	"js/myapp.min.js": [
		"public/js/xeokit.js"
	]
}

and this is my public/js/xeokit.js

import { Viewer, XKTLoaderPlugin, NavCubePlugin, TreeViewPlugin, FastNavPlugin } from "@xeokit/xeokit-sdk/dist/xeokit-sdk.min.es.js";

frappe.provide("frappe.xeokit")

frappe.xeokit.Viewer = Viewer
frappe.xeokit.XKTLoaderPlugin = XKTLoaderPlugin
frappe.xeokit.NavCubePlugin = NavCubePlugin
frappe.xeokit.TreeViewPlugin = TreeViewPlugin
frappe.xeokit.FastNavPlugin = FastNavPlugin

This gives me

$ node rollup/build.js --app myapp
Development mode
✔ Built js/moment-bundle.min.js
✔ Built js/libs.min.js

Building myapp assets...

SyntaxError: Unexpected token (16:416) in /workspace/development/mileone-bench/apps/myapp/node_modules/@xeokit/xeokit-sdk/dist/xeokit-sdk.min.es.js

What is the token at that address?

you can view the file yourself at: https://cdn.jsdelivr.net/npm/@xeokit/xeokit-sdk@2.0.0-beta.17/dist/xeokit-sdk.es.min.js

I was finally able to import it

It turns out xeokit had this

XKTLoaderPlugin.js

 getProperties: async (propertiesId) => {
         return await this._dataSource.getProperties(src, propertiesId);
}

and frappe DOES NOT support the arrow syntax

i simply had to make it

 getProperties: async function(propertiesId) {
         return await this._dataSource.getProperties(src, propertiesId);
}

and that made it work

BUT

I’ve read that frappe support ES6, so why isn’t ES6 syntax working ?

1 Like

That’s quite strange as I use the arrow format in my own code without any issues. Well done for getting it to work! Out of interest, is your frappe up to date?

ERPNext: v13.9.2 (version-13)
Frappe Framework: v13.9.0 (version-13)

So it’s not a version problem because that’s what I’m on. Maybe the arrow syntax only doesn’t work in node modules for some reason? :thinking:

update: i was wrong, just changing the arrow function syntax didn’t work

it turns out i had to retranspile xeokit with buble, and everything worked !

for anyone who comes here, here’s the commit which fixed it: change: add buble; feat: now supports frappe · satyajitghana/xeokit-sdk@3ed8859 · GitHub

but something curious to note is that frappe does use buble on its own in rollup, i wonder why that didn’t work

1 Like

Well done! It’s at least less strange that it’s not using buble on it when compared to it not using the arrow syntax.

1 Like

this problem cannot resolved, you have other ways ?

Hi Eric. What do you mean the problem cannot be resolved? What is your particular issue?

i try use this way deal with this problem, but alert exception, You have a good solved way?



@archais, Do you have a good idea?

What is the absolute path of your demo.js file? It should be something like

../apps/your_app/your_app/public/js/demo.js