How can I use relative module path for client script by #NAmdConfig? SuiteScript 2.0 - suitescript2.0

I would like to load non-AMD modules (jQuery and blockUI) using #NAmdConfig for client script, but my code occurs error in browser.
Uncaught Error: Module does not exist: ../../lib/jquery-blockUI.js
If I use absolute path instead of relative path, it works.
"baseUrl": "../../lib/"
Replace above with below, then works.
"baseUrl": "/SuiteScripts/ComponentA/SuiteScript2/lib/"
However I would like to use relative path because these scripts are going to be released as a bundle.
My current solution for this issue is using absolute path and replacing the path with bundle path when I release a bundle.
Does anyone know how to use relative path or better solution?
Script Files
File Structure
SuiteScripts/
└── ComponentA/
└── SuiteScript2/
├── FunctionA/
│ ├ config.json
│ ├ Suitelet.js
│ └ ClientScript.js
└── lib/
├ jquery.min.js
└ jquery-blockUI.js
config.json
{
"baseUrl": "../../lib/",
"paths": {
"jquery": "jquery.min.js",
"blockUI": "jquery-blockUI.js"
},
"shim": {
"blockUI": ["jquery"]
}
}
Suitelet.js
/**
* #NApiVersion 2.x
* #NScriptType Suitelet
* #NModuleScope SameAccount
* #NAmdConfig ./config.json
*/
define(['N/record', 'N/url', 'N/ui/serverWidget'],
function(record, nsUrl, serverWidget) {
function onRequest(context) {
// code abbreviated
var form = serverWidget.createForm({title: 'FunctionA', hideNavBar: false});
// Set client script
form.clientScriptModulePath = './ClientScript.js';
// code abbreviated
}
})
ClientScript.js
/**
* #NApiVersion 2.x
* #NScriptType ClientScript
* #NModuleScope SameAccount
* #NAmdConfig ./config.json
*/
define(['N/runtime', 'N/url', 'blockUI'],
function (runtime, url, blockUI) {
// code using blockUI
});

Like so. Its tricky at first but once you get it:
AMD config file ( /SuiteScripts/MyLibs/MyLibs.config.json ):
{
"packages": [
],
"paths": {
"sugar-2.0.4.min": "SuiteScripts/MyLibs/libs/sugar-2.0.4.min",
"buckets-1.98.2.min": "SuiteScripts/MyLibs/libs/buckets-1.98.2.min",
"jquery.jexcel-1.5.7": "SuiteScripts/MyLibs/libs/jquery.jexcel-1.5.7",
"jquery.jcalendar-1.5.7": "SuiteScripts/MyLibs/libs/jquery.jcalendar-1.5.7"
}
}
And the usage in a client script
*#NApiVersion 2.x
*#NScriptType ClientScript
*#NAmdConfig /SuiteScripts/MyLibs/MyLibs.config.json
*/
define([
'N/error',
'N/search',
'sugar-2.0.4.min',
'buckets-1.98.2.min',
'jquery.jexcel-1.5.7',
'jquery.jcalendar-1.5.7',
],
function (error, search, sugar, buckets, jexcel, jcalendar) {
function pageInit(context) {
// example 1 w/ sugar.js
var num = Sugar.Number.random(1, 100);
var hm = new buckets.Dictionary();
// example 2 w/ jquery grid
jQuery('#ui-grid').jexcel({
data: data,
colHeaders: ['Country', 'Description', 'Type', 'Stock', 'Next purchase'],
colWidths: [300, 80, 100, 60, 120],
columns: [
{type: 'autocomplete', url: 'https://bossanova.uk/jexcel/countries'},
{type: 'text'},
{
type: 'dropdown',
source: [{'id': '1', 'name': 'Fruits'}, {'id': '2', 'name': 'Legumes'}, {
'id': '3',
'name': 'General Food'
}]
},
{type: 'checkbox'},
{type: 'calendar'},
]
});
...

I found a workaround for this problem although it does not use relative path. jajo1987 told me this trick on Reddit, thanks jajo1987. Reddit
The workaround is having copy of config.json under /SuiteBundles/ folder in development environment.
With this trick, I don't have to replace path in config file when I release a bundle.
Script Files
Assuming bundle number is 00000.
File Structure
├── SuiteScripts/
│ └── ComponentA/
│ └── SuiteScript2/
│ ├── FunctionA/
│ │ ├ config.json
│ │ ├ Suitelet.js
│ │ └ ClientScript.js
│ └── lib/
│ ├ jquery.min.js
│ └ jquery-blockUI.js
└── SuiteBundles/
└── Bundle 00000/
└── SuiteScript2/
└── FunctionA/
└ config.json
/SuiteScripts/ComponentA/SuiteScript2/FunctionA/config.json
{
"baseUrl": "/SuiteBundles/Bundle 00000/SuiteScript2/lib/",
"paths": {
"jquery": "jquery.min.js",
"blockUI": "jquery-blockUI.js"
},
"shim": {
"blockUI": ["jquery"]
}
}
/SuiteBundles/Bundle 00000/SuiteScript2/FunctionA/config.json
{
"baseUrl": "/SuiteScripts/ComponentA/SuiteScript2/lib/",
"paths": {
"jquery": "jquery.min.js",
"blockUI": "jquery-blockUI.js"
},
"shim": {
"blockUI": ["jquery"]
}
}
ClientScript.js
/**
* #NApiVersion 2.x
* #NScriptType ClientScript
* #NModuleScope SameAccount
* #NAmdConfig /SuiteBundles/ComponentA/SuiteScript2/FunctionA/config.json
*/
define(['N/runtime', 'N/url', 'blockUI'],
function (runtime, url, blockUI) {
// code using blockUI
});

Related

Webpack - how to tell not compiling a js file for a specific entry point

Context
I am actually managing some dependencies via npm. Then I wrote a webpack.config.js to handle my imports :
Bundle Jquery + popper + Bootstrap.js
Bundle codemirror with my modules needs (xml autocomplete and so on)
...
Collect fonts from the fontawesome node_module
I am using django, so there is a collectstatic command which is helping me to collect all scripts, fonts, styles etc. to the folder I want. Everything is working great :
My bundled scripts are available
And for style sheets, I'm using relative imports to the 'vendor' node_module.
I am capable to get fonts to node_modules, and deliver it to the correct folder as expected.
Problem
I'm new to webpack world, and for sure I'm not using it as it should be, but it's working.
However when I run to compile my stuffs, an extra file is generated fontawesome.js in my fonts folder, is it possible to avoid this unexpected behavior.
What am I missing ?
It is not a big deal as long as I'm not importing this file, but I don't want to pollute my repo.
Edit
I updated the code of my webpack config.
Well, I figured out that the js file was generated from the filename in my entry point. But I don't want this file :)
wepback.config.js
"webpack": "^5.6.0",
var path = require('path');
const MiniCssExtractPlugin = require('mini-css-extract-plugin'); // Extract css in its own file
const webpack = require('webpack');
const path_modules = path.resolve(__dirname, 'node_modules');
const base_path = path.resolve(__dirname, 'src');
const dist_static = path.resolve(__dirname, 'webpacked_src');
module.exports = {
entry: {
bootstrap: {
import: [
base_path + '/bootstrap/bootstrap-bundle.js',
//base_path + '/bootstrap/test.scss'
],
filename: 'js_packed/[name].js',
},
fontawesome: {
import: [
path_modules + '/#fortawesome/fontawesome-free/webfonts/fa-brands-400.eot',
//path_modules + '/#fortawesome/fontawesome-free/webfonts/fa-brands-400.svg',
path_modules + '/#fortawesome/fontawesome-free/webfonts/fa-brands-400.ttf',
path_modules + '/#fortawesome/fontawesome-free/webfonts/fa-brands-400.woff',
path_modules + '/#fortawesome/fontawesome-free/webfonts/fa-brands-400.woff2',
path_modules + '/#fortawesome/fontawesome-free/webfonts/fa-regular-400.eot',
//path_modules + '/#fortawesome/fontawesome-free/webfonts/fa-regular-400.svg',
path_modules + '/#fortawesome/fontawesome-free/webfonts/fa-regular-400.ttf',
path_modules + '/#fortawesome/fontawesome-free/webfonts/fa-regular-400.woff',
path_modules + '/#fortawesome/fontawesome-free/webfonts/fa-regular-400.woff2',
path_modules + '/#fortawesome/fontawesome-free/webfonts/fa-solid-900.eot',
//path_modules + '/#fortawesome/fontawesome-free/webfonts/fa-solid-900.svg',
path_modules + '/#fortawesome/fontawesome-free/webfonts/fa-solid-900.ttf',
path_modules + '/#fortawesome/fontawesome-free/webfonts/fa-solid-900.woff',
path_modules + '/#fortawesome/fontawesome-free/webfonts/fa-solid-900.woff2',
],
filename: 'fonts/[name].js'
},
codemirror: {
import: [
base_path + '/codemirror/code-mirror.js',
path_modules + '/codemirror/lib/codemirror.css',
path_modules + '/codemirror/theme/monokai.css', // Import the theme style --> Little selection midnight / material-darker / material-ocean / monokai (sublimetext)
path_modules + '/codemirror/addon/hint/show-hint.css',
],
filename: 'js_packed/[name].js',
},
vue: {
import: [
base_path + '/vue/vue.js',
],
filename: 'js_packed/[name].js',
},
},
output: {
path: dist_static
},
plugins: [
new webpack.ProvidePlugin({
$: 'jquery',
jQuery: 'jquery'
}),
autoprefixer = require('autoprefixer'),
new MiniCssExtractPlugin({
filename: "css_packed/[name].css", // change this RELATIVE to the output.path
}),
],
module: {
rules: [
{
test: /\.(woff(2)?|ttf|eot|svg)(\?v=\d+\.\d+\.\d+)?$/,
use: [
{
loader: 'file-loader',
options: {
name: '[name].[ext]',
outputPath: 'fonts/'
}
}
]
},
{
test: /\.css$/,
use: [
MiniCssExtractPlugin.loader, // instead of style-loader
'css-loader'
]
},
{
test: /\.scss$/,
exclude: /node_modules/,
use: [
{
loader: 'file-loader',
options: {outputPath: 'css/', name: '[name].min.css'}
},
'sass-loader'
]
}
],
},
};
fontawesome.js
And the content of the generated file, if it can help webpack experts :
(() => {
"use strict";
var t = {};
t.g = function () {
if ("object" == typeof globalThis) return globalThis;
try {
return this || new Function("return this")()
} catch (t) {
if ("object" == typeof window) return window
}
}(), (() => {
var r;
t.g.importScripts && (r = t.g.location + "");
var e = t.g.document;
if (!r && e && (e.currentScript && (r = e.currentScript.src), !r)) {
var p = e.getElementsByTagName("script");
p.length && (r = p[p.length - 1].src)
}
if (!r) throw new Error("Automatic publicPath is not supported in this browser");
r = r.replace(/#.*$/, "").replace(/\?.*$/, "").replace(/\/[^\/]+$/, "/"), t.p = r + "../"
})(), t.p, t.p, t.p, t.p, t.p, t.p, t.p, t.p, t.p, t.p, t.p, t.p
})();
Unfortunately, this is a bug in webpack: https://github.com/webpack/webpack/issues/11671
In the meantime, it's straight forward to add a custom webpack plugin that will remove these files. Here's what I'm using to delete all JS files (because I'm using a CSS file as my entry):
plugins: [
{
// Delete empty JS files; work around for https://github.com/webpack/webpack/issues/11671
apply: (compiler) => {
compiler.hooks.afterEmit.tap('DeleteJSFilesPlugin', (compilation) => {
const iter = compilation.emittedAssets.entries();
for (const [key] of iter) {
if (key.match(/.*\.js$/)) {
fs.unlinkSync(path.join(compilation.outputOptions.path, key));
}
}
});
}
}
]

Mink/Selenium can't find a button

I'm trying to run this scenario
#javascript
Scenario: Greeting in an alert box
Given I am on "/"
And I get content
When I press "say"
And I wait for the greeting to appear
Then I should see "Hello JavaScript!"
With my debug step
/**
* #Given I get content
*/
public function iGetContent(){
echo $this->getSession()->getPage()->getContent();
}
However, it reports it cannot find the button, when (I believe) it should be able to
#javascript
Scenario: Greeting in an alert box # features/home.feature:11
Given I am on "/" # FeatureContext::visit()
And I get content # FeatureContext::iGetContent()
│ <html lang="en"><head></head><body>
│ <h1>Home</h1>
│ <span id="js-greeting"></span>
│ <button type="button" id="say" value="say">say</button>
│
│
│ <script>
│ function showGreeting(){
│ document.getElementById("js-greeting").innerHTML = "Hello JavaScript!"
│ }
│
│ function delayedGreeting(){
│ window.setTimeout(showGreeting, 1000);
│ }
│ </script>
│
│ </body></html>
When I press "say" # FeatureContext::pressButton()
Button with id|name|title|alt|value "say" not found. (Behat\Mink\Exception\ElementNotFoundException)
And I wait for the greeting to appear # FeatureContext::iWaitForTheGreetingToAppear()
Then I should see "Hello JavaScript!" # FeatureContext::assertPageContainsText()
Selenium launches Firefox, and I can see that the page successfully loads, and I can physically see the button. Is there something I've missed? Here's my composer.json/behat .yml in case there's something here I've missed.
{
"name": "",
"description": "",
"keywords": [],
"repositories": [
{
"type": "vcs",
"url": "git#bitbucket.org:xxx.git"
}
],
"require": {
"php": "^5.6 || ^7.0",
"judgeservice/mvc": "dev-master",
"php-amqplib/php-amqplib": ">=2.6.1",
"behat/behat": "3.0.6",
"behat/mink": "1.6.*",
"behat/mink-extension": "*",
"behat/mink-selenium2-driver": "*",
"behat/mink-goutte-driver": "*",
"laravel/framework": "4.2.*",
"behat/mink-zombie-driver": "*"
},
"autoload": {
"psr-4": {
"Application\\": "module/Application/src/"
}
},
"require-dev": {
"phpunit/phpunit": "^5.7"
}
}
default:
extensions:
Behat\MinkExtension:
base_url: "http://api.example.com"
browser_name: 'firefox'
goutte: ~
selenium2: ~
public function pressButton($button)
{
$button = $this->fixStepArgument($button);
$this->getSession()->getPage()->pressButton($button);
}
protected function fixStepArgument($argument)
{
return str_replace('\\"', '"', $argument);
}
public function pressButton($locator)
{
$button = $this->findButton($locator);
if (null === $button) {
throw $this->elementNotFound('button', 'id|name|title|alt|value', $locator);
}
$button->press();
}
public function findButton($locator)
{
return $this->find('named', array(
'button', $this->getSelectorsHandler()->xpathLiteral($locator),
));
}
/**
* #Given I hit say
*/
public function iHitSay(){
$button = $this->getSession()
->getPage()
->find('css', '#say');
$button->press();
}
These solutions helped me:
https://github.com/minkphp/MinkSelenium2Driver/issues/293#issuecomment-519920991
https://github.com/Behat/MinkExtension/issues/345#issuecomment-510712489
The problem is starting with Chrome 75 version and above.

Testing elm lib with local Native directory

How do we organize our test directory when developing some libraries that uses Native js code?
I tried to work this out, but I'm blocked here, with this error at runtime when running test/test.sh:
Elm.Native.Mylib = {};
^
TypeError: Cannot read property 'Native' of undefined
git repository
My directories are structured this way:
Mylib:
- src :
- Mylib.elm
- Native :
- MyLib.js
- tests :
- Test.elm
- Test.sh
- elm-package.json
the tests/elm-package.json contains :
{
"version": "1.0.0",
"summary": "helpful summary of your project, less than 80 characters",
"repository": "https://github.com/user/project.git",
"license": "BSD3",
"source-directories": [
"."
,"../src"
],
"exposed-modules": [],
"native-modules": true,
"dependencies": {
"elm-community/elm-test": "1.1.0 <= v < 2.0.0",
"elm-lang/core": "4.0.1 <= v < 5.0.0"
},
"elm-version": "0.17.0 <= v < 0.18.0"
}
the tests/Test.elm is :
module Main exposing (..)
import Basics exposing (..)
import ElmTest exposing (..)
import Mylib exposing (..)
tests : Test
tests =
suite "elm-Mylib Library Tests"
[ ]
main =
runSuite tests
The tests/test.sh is
#!/bin/sh
elm-package install -y
elm-make --yes --output test.js Test.elm
node test.js
The src/Mylib.elm is
module Mylib exposing (..)
import Native.Mylib exposing (..)
import Task exposing (Task)
import Time exposing (Time)
print : a -> Task x ()
print value =
Native.Mylib.log (toString value)
getCurrentTime : Task x Time
getCurrentTime =
Native.Mylib.getCurrentTime
The src/Native/Mylib.js is
Elm.Native.Mylib = {};
Elm.Native.Mylib.make = function(localRuntime) {
localRuntime.Native = localRuntime.Native || {};
localRuntime.Native.Mylib = localRuntime.Native.Mylib || {};
if (localRuntime.Native.Mylib.values)
{
return localRuntime.Native.Mylib.values;
}
var Task = Elm.Native.Task.make(localRuntime);
var Utils = Elm.Native.Utils.make(localRuntime);
function log(string)
{
return Task.asyncFunction(function(callback) {
console.log(string);
return callback(Task.succeed(Utils.Tuple0));
});
}
var getCurrentTime = Task.asyncFunction(function(callback) {
return callback(Task.succeed(Date.now()));
});
return localRuntime.Native.Mylib.values = {
log: log,
getCurrentTime: getCurrentTime
};
};
Try this:
var _user$project$Native_MyLib = function() {
return {
exported: function(arg) { return "One" },
exported2: F2(function(arg) { return "Two" }),
exported3: F3(function(arg) { return "Three" }),
}
}();
It works for grater than Elm 0.17.
Buy you should also use full qualified import:
import Natve.MyLib
exported : String -> String
Native.MyLib.exported
exported2 : String -> String -> String
Native.MyLib.exported2
exported3 : String -> String -> String -> String
Native.MyLib.exported3
User and project values are from your/local elm-package.json:
"repository": "https://github.com/user/project.git",

Include npm dependency in karma

How can I include a dependency in package.json (not a devDependency) in karma?
I can include the file in node_modules/<dependency>, but I was looking for something more generic, including the main file.
I did not find any other better way to include them in karma.conf.js than
module.exports = function(config) {
var cfg = {
frameworks: ["jasmine", "commonjs"]
, files: [
{pattern: "node_modules/" + deps + "/*.js", included: true, watched: false}
, ...
]
...
};
cfg.preprocessors["node_modules/" + deps + "/**/*.js"] = ["commonjs"];
config.set(cfg);
};

How to remap a Durandal viewmodel

This is my main.js file in a Durandal project.
What I'm trying to do is set things up so that the name 'upload-item' resolves to either 'upload-item' or 'upload-item-prehtml5' depending on whether File is defined.
requirejs.config({
paths: {
'text': '../lib/require/text',
'durandal': '../lib/durandal/js',
'plugins': '../lib/durandal/js/plugins',
'transitions': '../lib/durandal/js/transitions',
'knockout': '../lib/knockout/knockout-2.3.0',
'bootstrap': '../lib/bootstrap/js/bootstrap',
'jquery': '../lib/jquery/jquery-1.9.1.min',
'jquery-ui': '../lib/jquery-ui/jquery-ui-1.10.4.custom/js/jquery-ui-1.10.4.custom.min',
'moment': '../lib/moment/moment',
'knockout-jqueryui': '../lib/knockout/knockout-jqueryui.min',
'file-size-formatting': '../lib/wone/file-size-formatting'
},
shim: {
'bootstrap': {
deps: ['jquery'],
exports: 'jQuery'
}
}
});
define(['durandal/system', 'durandal/app', 'durandal/viewLocator'], function (system, app, viewLocator) {
//>>excludeStart("build", true);
system.debug(true);
//>>excludeEnd("build");
var filetype = typeof(File);
if (filetype == 'undefined') {
//apply pre-html5 fixups
require.config({
map: {
'*': {
'upload-item': 'upload-item-prehtml5'
}
}
});
}
app.title = 'Jumbo File Transfer';
//specify which plugins to install and their configuration
app.configurePlugins({
router: true,
dialog: true,
widget: {
kinds: ['expander']
}
});
app.start().then(function () {
//Replace 'viewmodels' in the moduleId with 'views' to locate the view.
//Look for partial views in a 'views' folder in the root.
viewLocator.useConvention();
//Show the app by setting the root view model for our application.
app.setRoot('shell');
});
});
Testing on IE8 shows that the call to require.config occurs and the mapping is added, but it doesn't seem to have the effect I expected: upload-item.js and upload-item.html are loaded when I expected upload-item-prehtml5.js and upload-item-prehtml5.html to be loaded.
If this is the wrong way to go about this, then what is the right way to perform this kind of conditional resolution?
It's not quite what I originally wanted, but I found you can do this:
var prehtml5 = (typeof (File) == 'undefined');
requirejs.config({
paths: {
...
'upload-item': prehtml5 ? 'upload-item-prehtml5' : 'upload-item'
},
shim: {
'bootstrap': {
deps: ['jquery'],
exports: 'jQuery'
}
}
});
Path remapping seems to extend into the file name. Normally you wouldn't list siblings of main.js but you can and if you do then you can remap them, including the file name.