Magento 2 JS libraries sometimes don't load - magento2

We are currently running a Magento 2.2.6 instance. Sometimes the product gallery breaks with a JS error:
Uncaught TypeError: settings.$elementF.fotorama is not a function
This also happens to other js libraries on the homepage and catalog page. Sometimes we get an error
Uncaught TypeError: $(...).CustomMenu is not a function
IF you hard-refresh/clear your browser cache it works and all the errors disappear for that page, but if you browse the site it will throw the errors again after visiting a few pages.
I have already tried re-deploying the static assets, turning on and off static signing, disabling all extension, disabling all backend caches and changing my requirejs-config.js file to depend and shim jquery
var config = {
deps: ['jquery'],
map: {
'*' : {
'hello' : 'js/custom'
}
},
'fotorama/fotorama': {
deps: ['jquery']
}
}
This issue has been posted various times before, any advice would be greatly appreciated.

Unfortunately, the dependencies array is not guaranteed to load in order.
You can use a shim within requireJS that will let you define dependencies between libraries and export global variables that can be used with other libraries.
Essentially within the shim, you can use the deps property to define libraries that should be loaded before using the included library.
What you have now is close, but it's not within a shim
Try this:
var config = {
deps: ['jquery'],
map: {
'*' : {
'hello' : 'js/custom'
}
},
shim: {
// export the variable
jquery: {
exports: '$'
},
// fotorama depends on jquery
'fotorama/fotorama': {
deps: ['jquery']
}
}
}

Related

chrome extension v3 - Import script with dependencies into background.js

I am making a chrome extension using manifest v3.
I am trying to import my script files into the background.js.
I am using the second method described here
I have thus this in manifest.json:
"background": {
"service_worker": "background.js",
"type": "module"
},
On a simple script with no other dependency:
in myscript.js:
export function myFunction() {
console.log("In myFunction!")
}
and in my background.js :
import { myFunction } from "./lib/myscript.js";
myFunction()
In that simple case, it works fine.
However, if I try to add another dependency in myscript.js from myscript2.js, like this:
myscript2.js:
export function mySecondFunction() {
console.log("In mySecondFunction!")
}
and myscript.js (same directory as myscript2):
import { mySecondFunction} from "./myscript2.js";
export function myFunction() {
console.log("In myFunction!")
mySecondFunction()
}
In that case, it doesn't work anymore and I get the error "Service worker registration failed".
What should I be doing to fix this issue?

Javascript module function in GWT with JsInterop

Hoping this is way easier than I'm making it - I'm a Java coder, some inner Javascript aspects are a tad unfamiliar to me.
Trying to embed the great CodeJar library inside a GWT panel. There's a pretty nice/simple example for CodeJar:
<script type="module">
import {CodeJar} from './codejar.js'
import {withLineNumbers} from './linenumbers.js';
const editor = document.querySelector('.editor')
const highlight = editor => {
// highlight.js does not trim old tags,
// let's do it by this hack.
editor.textContent = editor.textContent
hljs.highlightBlock(editor)
}
const jar = CodeJar(editor, withLineNumbers(highlight), {
indentOn: /[(\[{]$/
})
jar.updateCode(localStorage.getItem('code'))
jar.onUpdate(code => {
localStorage.setItem('code', code)
})
</script>
The module function itself looks like this:
export function CodeJar(editor, highlight, opt = {}) { ... }
'editor' is a Div reference, and 'highlight' is a callback library function for handling code highlighting.
What I'm battling with is the JsInterop markup and code to make Javascript modules work with GWT. The above has a few aspects which I'm battling with
replacing the "import" such that the javascript module code is available to GWT. Obvioulsy I can just import the js in my top level index.html, but as I understand it JS modules don't become part of the global namespace, they're only usable from the JS module that imports them. Which in my case, presumably needs to be the GWT code.
how to pass the callback function in when recoding the above in GWT
how to get my own 'jar' reference to do own text set/get (replacing the use of local storage)
To load the script and have it available for GWT consumption, you have (at least) 3 possibilities:
use a static import in a <script type=module>, and then assign the CodeJar function to a window property to make it available globally (that could be another global object than window actually)
use a dynamic import() from GWT, using JsInterop and possibly elemental2-promise
use Rollup/Webpack/whatever to turn the CodeJar module into a non-module script so you can use it differently
Next, you need to create JsInterop bindings so you can call it from GWT; something like that (assuming you made CodeJar available globally as window.CodeJar, and using elemental2-dom for HTMLElement, but com.google.gwt.dom.client.Element would work just as well):
#JsType(isNative = true, namespace = JsPackage.GLOBAL, name = "?")
interface CodeJar {
#JsMethod(namespace = JsPackage.GLOBAL, name = "CodeJar")
static native CodeJar newInstance(HTMLElement element, HighlightFn highlight);
#JsMethod(namespace = JsPackage.GLOBAL, name = "CodeJar")
static native CodeJar newInstance(HTMLElement element, HighlightFn highlight, Options opts);
void updateOptions(Options options);
void updateCode(String code);
void onUpdate(UpdateFn cb);
void destroy();
}
#JsType(isNative = true, namespace = JsPackage.GLOBAL, name = "Object")
class Options {
public String tab;
public JsRegExp indentOn;
public boolean spellcheck;
public boolean addClosing;
}
#JsFunction
#FunctionalInterface
interface HighlightFn {
void highlight(HTMLElement e);
}
#JsFunction
#FunctionalInterface
interface UpdateFn {
void onUpdate(String code);
}
With the above code, you should be able to create an editor using something like:
CodeJar jar = CodeJar.newInstance(editor, MyHighlighter::highlight);
If you use a dynamic import(), replace the static methods with instance ones in a #JsType interface representing the module received from the promise.

How to use an mvvmcross plugin such as the file plugin

I'm using mvvmcross version 6.4.1 to develop an app for IOS, Android, and WPF.
I've searched all over for my to use plugins. There seems to be no code examples. The documentation said to install the nuget in both my core and ui application projects. Which I did. Is there any special IOC registration/setup/or loading that needs to be done before I can use the plugin and how do I go about using the plugin? Do they get injected in the constructor or Do I have to manually pull them from the IOC container or new () them up.
I've installed nuget for the File plugin into my WPF UI and Core project. I added the IMvxFileStore to one of my core project's service constructor thinking it automagically gets added to the DI container, but it doesn't seem to get injected.
namespace My.Core.Project.Services
{
public class SomeService : ISomeService
{
private IMvxFileStore mvxFileStore;
public SomeService(IMvxFileStore mvxFileStore)
{
this.mvxFileStore = mvxFileStore;
}
public string SomeMethod(string somePath)
{
mvxFileStore.TryReadTextFile(somePath, out string content);
return content;
}
}
}
App.xaml.cs
using MvvmCross.Core;
using MvvmCross.Platforms.Wpf.Views;
...
public partial class App : MvxApplicatin
{
protected override void RegisterSetup()
{
this.RegisterSetupType<Setup<Core.App>>();
}
}
App.cs
using MvvmCross;
using MvvmCross.ViewModels;
using My.Core.Project.Services;
public class App: MvxApplication
{
public override void Initialize()
{
Mvx.IocProvider.RegisterType<ISomeService, SomeService>();
RegisterCustomAppStart<AppStart>();
}
}
AppStart.cs
using MvvmCross.Exceptions;
using MvvmCross.Navigation;
using MvvmCross.ViewModels;
using My.Core.Project.ViewModels;
using System;
using System.Threading.Tasks;
....
public class AppStart : MvxAppStart
{
public AppStart(IMvxApplication application, IMvxNavigationService navigationService) : base(application, navigationService)
{}
public override Task NavigateToFirstViewModel(object hint = null)
{
try {
return NavigationService.Navigate<FirstPageViewModel>();
} catch {
throw e.MvxWrap("Some error message {0}", typeof(FirstPageViewModel).Name);
}
}
}
Setup.cs in WPF project
using MvvmCross;
using MvvmCross.Base;
using MvvmCross.Platforms.Wpf.Core;
using MvvmCross.Plugin.File;
using MvvmCross.Plugin.Json;
using MvvmCross.ViewModels;
using My.Wpf.Project.Services;
...
public class Setup<T> : MvxWpfSetup
{
public Setup() : base() {}
protected override IMvxApplication CreateApp()
{
return new Core.App();
}
protected override void InitializeFirstChange()
{
base.InitializeFirstChange();
Mvx.IocProvider.RegisterType<ISomeWpfSpecificService>(() => new SomeWpfSpecificService());
}
protected override void InitializeLastChange()
{
base.InitializeLastChange();
}
}
I'm expecting my service to load but instead, I get the error message
MvxIoCResolveException: Failed to resolve parameter for parameter mvxJsonConverter of type IMvxJsonConverter
NOTE: I get the same error message for both File and Json plugin, The plugin that gets listed first in the constructor gets the error message when the app trys to load.
Am I properly using or loading the plugin?
UPDATE: I manually registered the Plugins in the UI Setup.cs and it is working but I am not sure if this is the proper way to do it.
WPF UI project Setup.cs
using MvvmCross;
using MvvmCross.Base;
using MvvmCross.Platforms.Wpf.Core;
using MvvmCross.Plugin.File;
using MvvmCross.Plugin.Json;
using MvvmCross.ViewModels;
using My.Wpf.Project.Services;
...
public class Setup<T> : MvxWpfSetup
{
public Setup() : base() {}
protected override IMvxApplication CreateApp()
{
return new Core.App();
}
protected override void InitializeFirstChange()
{
base.InitializeFirstChange();
Mvx.IocProvider.RegisterType<ISomeWpfSpecificService>(() => new SomeWpfSpecificService());
Mvx.IoCProvider.RegisterType<IMvxFileStore, MvxFileStoreBase>();
Mvx.IoCProvider.RegisterType<IMvxJsonConverter, MvxJsonConverter>();
}
protected override void InitializeLastChange()
{
base.InitializeLastChange();
}
}
Yes you are using the plugin properly and I think that for now your solution to manually register your plugin is viable.
The root of the problem is located in the MvxSetup class. This class contains the method LoadPlugins which is responsible for loading the MvvmCross plugins which are referenced by your UI project. This is how LoadPlugins determines what plugins to load:
Get all assemblies that have been loaded into the execution context of the application domain.
Find types within these assemblies which contain the MvxPluginAttribute.
Now the problem occurs in step 1. In a .NET framework project, by default, your referenced assemblies won't be loaded into the execution context until you actually use them in your code. So if you don't use something from your MvvmCross.Plugin.File reference in your UI project it won't be loaded into your execution context and it won't be found in step 1 and thus it won't be registered by LoadPlugins. (good read: when does a .NET assembly Dependency get loaded)
One way I have tested this is by doing this:
protected override void InitializeFirstChance()
{
// Because a type of the MvvmCross.Plugin.File.Platforms.Wpf reference is
// used here the assembly will now get loaded in the execution context
var throwaway = typeof(Plugin);
base.InitializeFirstChance();
}
With the above code you don't have to manually register the Plugin.
There has been a pull request to fix this in the MvvmCross framework but this has been reverted later since it caused problems on other platforms.
In other platforms the plugin assemblies will get loaded into the execution context without any tricks so I would say updating the MvvmCross documentation stating you have to register your plugin manually for WPF would be useful for other developers in the future.

How can I make a symfony 4 command to be registered only in dev environment and disabled in prod?

In my App I have a helper class App\Command\GenerateFixturesCommand that provides a command named my-nice-project:generate-fixtures.
This command consumes a service of my own project named App\Services\CatalogFixtureGenerator that generates 1000 random PDF documents for testing while developing the app.
To do so, this service uses the joshtronic\LoremIpsum class which is required in composer only in dev. LoremIpsum is a third-party library. I require it under composer's require-dev.
So the injection is:
I run my GenerateFixturesCommand.
Before that, the system transparently locates my CatalogFixtureGenerator and to inject it into the command.
Before that, the system transparently locates the LoremIpsum third party service to inject it into my fixture generator service.
All is autowired.
When I deploy to prod and do composer install --no-dev --optimize-autoloader of course the LoremIpsum class is not installed.
But when I clear the cache with APP_ENV=prod php bin/console cache:clear the framework finds the command and cannot inject the autowired dependencies.
[WARNING] Some commands could not be registered:
In CatalogsFixtureGenerator.php line 26:
Class 'joshtronic\LoremIpsum' not found
This my-nice-project:generate-fixtures command is never going to be used in the production server.
Question
How can I "disable" the command in prod?
I mean: How can I tell the framework that the class GenerateFixturesCommand should not be loaded nor its autowired dependencies, and neither of them should be autowired in prod?
Use the isEnabled() method in Command.
For example
public function isEnabled(): bool
{
// disable on prod
if ($this->appKernel->getEnvironment() === 'prod') {
return false;
}
return true;
}
In my last project, I need some commands to work only in dev environment. You use getenv function to achieve this:
# src/Command/SomeCommand.php
...
public function __construct()
{
parent::__construct();
if (getenv("APP_ENV") !== "dev") {
exit('This command should work only "dev" environment.');
}
}
This will do the trick.
Code fun :)
The solution #gusDeCooL suggests doesn't work with lazy-loaded commands (at least not for me).
I ended up implementing the isEnabled() method anyway, but then I added a guard in execute():
<?php
namespace App\Command;
use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;
#[AsCommand(
name: 'command:name',
description: 'Some description',
)]
class CommandName extends Command
{
public function isEnabled(): bool
{
return 'dev' === getenv('APP_ENV');
}
protected function execute(InputInterface $input, OutputInterface $output): int
{
$io = new SymfonyStyle($input, $output);
if (!$this->isEnabled()) {
$io->error('This command is only available in `dev` environment.');
exit(1);
}
// the rest
}
}

How can I use ember-simple-auth with ember-cli?

I'm trying to adapt the instructions on https://github.com/simplabs/ember-simple-auth for an app build using v0.0.28 of ember-cli. I am fairly new to ember-cli and use of AMD modules.
Can anyone help with the proper way/place to import ember-simple-auth rather than the global namespace as covered in the README?
The following initializer
import SimpleAuth from 'vendor/ember-simple-auth/amd/ember-simple-auth';
export default {
name: 'authentication',
initialize: function(container, application) {
SimpleAuth.setup(container, application);
}
}
Is compiled to
define("myapp/initializers/ember-simple-auth",
["vendor/ember-simple-auth/amd/ember-simple-auth","exports"],
function(__dependency1__, __exports__) {
"use strict";
var SimpleAuth = __dependency1__["default"];
__exports__["default"] = {
name: 'authentication',
initialize: function(container, application) {
SimpleAuth.setup(container, application);
}
}
});//# sourceURL=myapp/initializers/ember-simple-auth.js
And __dependency1__ is undefined.
Please see the installation instructions for ember-cli in the README: https://github.com/simplabs/ember-simple-auth#installation - unfortunately it's not yet possible to import individual parts of Ember.SimpleAuth though.