I created a StencilJS project that has a bunch of web components bundled in my mwc project. From within Stencil, I can execute npm run start and see my components working as expected.
I created an Electron project and in it I'm importing the stencil mwc package using :
<script src="dist/mwc/mwc.js"></script>
When I do this I've noticed the stencil generated code fails to run any for-loops that iterate over a Map or Set. Basically the for-loop exits and never iterates.
For example, in one of my components I have a class that defines a variable this way:
private _groupedItems : Map<string, Item[]> = new Map();
This variable gets populated and when the following code tries to run, it always fails:
#Method()
async updateItemAsync( arg : { value : string, data : UpdateSelectItem } ) {
//find where the item value is located
let item : Item | undefined = undefined;
for( const key of this._groupedItems.keys() ) {
const groupedItems = this._groupedItems.get( key );
if( groupedItems ) {
item = groupedItems.find( item => item.value === arg.value );
if( item ) {
break;
}
}
}
if( item === undefined ) {
console.error( 'Could not find item to update with value=', arg.value );
return;
}
//NEVER GETS HERE!
//more code below snipped out
}
In Chrome devTools I can see that the generated JavaScript that is trying to run looks like this:
e.prototype.updateItemAsync = function(e) {
return __awaiter(this, void 0, void 0, function() {
var t, i, n, r, s;
return __generator(this, function(a) {
t = undefined;
for (i = 0,
n = this._groupedItems.keys(); i < n.length; i++) {
r = n[i];
s = this._groupedItems.get(r);
if (s) {
t = s.find(function(t) {
return t.value === e.value
});
if (t) {
break
}
}
}
if (t === undefined) {
console.error("Could not find item to update with value=", e.value);
return [2]
}
I discovered if instead of using the aforementioned script, I use this instead :
<script type="module" src="dist/mwc/mwc.esm.js"></script>
Then everything works fine (sort of). Basically when I fire up my Electron package using webpack all the code works as expected and my for-loops are now working. The problem with this solution is that when I package my Electron application using electron-webpack, I can't run the resulting standalone EXE because I get an error message when the application starts up. Chrome gives me an error when it tries to load the mwc.esm.js file:
Failed to load module script: The server responded with a non-JavaScript MIME type of "". Strict MIME type checking is enforced for module scripts per HTML spec.
What is the difference between the mwc.js and mwc.esm.js files? Why won't the mwc.js file run my for-loops properly?
The esm.js file is a Javascript Module which will be served to browsers that support it.
When you use the old way of including a Stencil component (/dist/mwc.js) you will get a console warning about how to properly include it, which is also documented in the breaking changes for version 1:
[mwc] Deprecated script, please remove: <script src="/dist/mwc.js"></script>
To improve performance it is recommended to set the differential scripts in the head as follows:
<script type="module" src="/dist/mwc/mwc.esm.js"></script>
<script nomodule src="/dist/mwc/mwc.js"></script>
I don't know why Map and Set loops would not work with the non-module file but the module is the recommended way of importing in Chrome.
The MIME type error seems to be a known issue in Electron which seems to be because Electron uses the file:// protocol by default which doesn't allow including modules, as per spec.
Related
I've developed a project using Angular.
When I build it using for production I get no errors and everything works fine on Chrome.
If I run the app on Webkit/Safari the console prints this error:
SyntaxError: Left hand side of operator '=' must be a reference.
(funzione anonima) — main.ebe5d823e9b701706d6f.js:1
Elemento selezionato
Here is the content of main.ebe5d823e9b701706d6f.js (formatted by the browser):
(self.webpackChunkfrontend = self.webpackChunkfrontend || []).push([[179], {
3597: t => {
function e(t) {
return Promise.resolve().then(() => {
var e = new Error("Cannot find module '" + t + "'");
throw e.code = "MODULE_NOT_FOUND", e
})
}
e.keys = () => [],
e.resolve = e,
e.id = 3597,
t.exports = e
},
88642: (t, e, n) => {
"use strict";
n.d(e, {
T: () => l
});
I don't think that the error is thrown by self.webpackChunkfrontend = self.webpackChunkfrontend but I don't know how to solve it because the browser doesn't says which is the assignment that causes the error.
Setting buildOptimizer=false to angular.json seems to solve the issue for versions 11 and down. In the GitHub repo they say that this issue is fixed for versions 12+ but I haven't tested it to confirm it.
https://github.com/angular/angular-cli/issues/21107
I've solved the issue.
The problem was caused by the npm module three. There is a problem in the isSpotLight method.
I've removed the feature that was using that module, so now the app works also on Safari/WebKit.
These are the steps that I've followed to debug the issue:
Copy the content of main.ebe5d823e9b701706d6f.js
Go to an online JS Beautifier
Paste the code and beautify it
Copy the beautified code to main.ebe5d823e9b701706d6f.js
Reload the page in Safari. Now the console prints the correct line number with the error.
I have created a code that it actually works, but I call a library that has an error, and I would like to know if it is possible to avoid that specific line of code. I will try to explain the case as well as possible:
Error
Uncaught TypeError: fs.openSync is not a function
Previous code
function synthesizeToAudioFile(authorizationToken, message) {
// replace with your own subscription key,
// service region (e.g., "westus"), and
// the name of the file you save the synthesized audio.
var serviceRegion = "westus"; // e.g., "westus"
var filename = "./audiocue.wav";
//Use token.Otherwise use the provided subscription key
var audioConfig, speechConfig;
audioConfig = SpeechSDK.AudioConfig.fromAudioFileOutput(filename);
speechConfig = SpeechSDK.SpeechConfig.fromAuthorizationToken(authorizationToken, serviceRegion);
// create the speech synthesizer.
var synthesizer = new SpeechSDK.SpeechSynthesizer(speechConfig, audioConfig);
// start the synthesizer and wait for a result.
synthesizer.speakTextAsync(message,
function (result) {
if (result.reason === SpeechSDK.ResultReason.SynthesizingAudioCompleted) {
console.log("synthesis finished.");
} else {
console.error("Speech synthesis canceled, " + result.errorDetails +
"\nDid you update the subscription info?");
}
synthesizer.close();
synthesizer = undefined;
},
function (err) {
console.trace("err - " + err);
synthesizer.close();
synthesizer = undefined;
});
console.log("Now synthesizing to: " + filename);
}
I created a method, which later I have replicated in my current code. The difference was that I was using Browserify in order to import a library from a script of a HTML file:
<script type="text/javascript" src="js/dist/sub_mqtt.js"></script>
This file had my method, and the whole library, which made it crazy unreadable, and therefore I started using ScriptJS to import it. The problem is that, using browserify I was able to remove the line of code that it was failing using fs.openSync(and I do not even need), but by importing it with ScriptJS I do not have access to the source code.
I assume that what is missing is that I am not importing the library fs, which is being used by the library that I am importing with ScriptJS before importing that one, but how could I do it? I have tried:
<script src="../text-to-speech/node_modules/fs.realpath/index.js"></script>
, or
<script type="text/javascript" src="../text-to-speech/node_modules/fs.realpath/index.js"></script>
and also wrapping the content of synthesizeToAudioFile() with
require(["node_modules/fs.realpath/index.js"], function (fs) { });
but I get the following error:
Uncaught ReferenceError: module is not defined
at index.js:1
After researching about this question I found out the next statement:
The fs package on npm was empty and didn't do anything, however many
packages mistakenly depended on it. npm, Inc. has taken ownership of
it.
It's also a built-in Node module. If you've depended on fs, you can
safely remove it from your package dependencies.
therefore what I have done is to access to the file that I was requiring with ScriptJS
require(["../text-to-speech/microsoft.cognitiveservices.speech.sdk.bundle.js"]
and directly remove that line on it. Note that, at least in my case, clearing the cache of the browser was needed.
I'm working on a customised BigCommerce theme using Cornerstone as a starting point to customise from. I have been using CLI to add JavaScript libraries via NPM.
Following this, the build compiles without errors & runs in my local host environment, but there is an error in console that is stopping my jQuery from working.
The Error is within the compiled file generated by Stencil called datatags.js
My JS isn't the strongest so would appreciate any advice on this one.
I have tested my JS functions as working ok by adding the jQuery library as a linked resource in the <head> of the page via their CDN.
The issue seems to have arisen following jQuery being installed via NPM. The error is triggered by the a.ready(function() on line 5 of the code snippet below.
window.initDataTags = function(o, i, a) {
const r = ["data-banner-id", "data-entity-id", "data-event-type", "data-list-name", "data-name", "data-position", "data-product-brand", "data-product-category", "data-product-price", "data-product-sku", "data-product-variant", "data-currency-code"].concat(o).concat(i);
t = o,
n = i,
a.ready(function() {
setTimeout(function() {
const t = [];
c(document.body.querySelectorAll(u), function(n) {
t.push(n)
}),
d(t, a),
new MutationObserver(function(t) {
const n = [];
function o(t) {
e(t, u) && n.push(t),
c(t.querySelectorAll(u), function(t) {
n.push(t)
})
}
c(t, function(t) {
"childList" === t.type ? c(t.addedNodes, function(t) {
t instanceof Element && o(t)
}) : "attributes" === t.type && o(t.target)
}),
d(n, a)
}
).observe(document.body, {
childList: !0,
attributes: !0,
subtree: !0,
attributeFilter: r
})
}, 100)
})
}
The console error is:
Uncaught TypeError: Cannot read property 'ready' of undefined.
I can't share a URL as this is running on my local environment.
This script has to do with the bigcommerce tracking scripts, and only shows when developing locally. I have never had it cause any issues, besides making the console dirty. The source is {{{footer.scripts}}} in base.html - you can remove it but then other scripts in the script manager footer wont fire.
I use the experimentalCodeSplitting: true feature of rollup 0.61.2 to get nice code splitting. Because my project consists also of assets I created a plugin which copies and minifies the asset files accordingly. The problem is that the hooks I used are called for every chunk which is created. Therefore the assets are copied and minified multiple time. The only workaround I found is, to create some flag which is set to true after everything is done correctly. Is there a functionality to call a rollup hook after everything (or before everything) is finished and not on every chunk? Now my plugin looks something like the following code (I removed some parts and simplified for readability):
export default function copy(userOptions = {}) {
const name = 'copyAndMinify';
const files = userOptions.files || [];
let isCopyDone = false;
return {
name: name,
// also tried onwrite, ongenerate, buildEnd and generateBundle
buildStart() {
if (isCopyDone) {
return;
}
for (let key in files) {
const src = key;
const dest = files[key];
try {
minifyFile(src, dest);
} catch (err) {
fatal(name, src, dest, err);
}
}
isCopyDone = true;
}
};
};
Maybe there is a better way of doing this kind of stuff because with this implementation I always have to completely restart rollup to execute my plugin
The rollup site lists all the available plugin hooks.
generateBundle seems like what you'd want.
generateBundle (formerly onwrite and ongenerate) - a ( outputOptions, bundle, isWrite ) => void function hook called when bundle.generate() or bundle.write() is being executed; you can also return a Promise. bundle provides the full list of files being written or generated along with their details.
I am currently developing a web application to edit some custom file formats (a small editor offering an interface to edit configuration files).
A key element of this app is an object called "FileReader" that is a wrapper who triggers the right interface according to the detected type of config file. This way I just spawn a new FileReader( src ) - src being an url or a blob and it keeps clean.
Once the type of the config file is retrieved, the wrapper checks if it has an interface associated to it and simply use it. Each interface (for each different config type) is defined in a separate JS file. Once the wrapper is fully loaded, I hook all the events and the execution of app begins.
Here is the HTML skeleton I used with the relevant elements :
<!doctype html>
<html><head></head>
<body>
<div id="UI"></div>
<script src="js/fileReader/fileReader.js" >App.FileReader = function(){ ... }</script>
<script src="js/fileReader/configType1.js" >App.FileReader.registerFileType("config1",object)</script>
<script src="js/fileReader/configType2.js" >App.FileReader.registerFileType("config2",object)</script>
<script src="js/fileReader/configType3.js" >App.FileReader.registerFileType("config3",object)</script>
<script src="js/main.js" >App.run();</script>
</body>
</html>
Now, the app grew up and I decided to convert my app to use requirejs.
My problem are mostly about "what's the best organisation" to deal with those modules because I can only consider that the FileReader is ready to use once all the file-type modules are loaded but I can't load them first because they need the mainWrapper to register their type.
The best structure I could come up with is :
main.js :
require(['require','FileReader','./fileReader/config1','./fileReader/config2','./fileReader/config3'],
function( require ) {
require(['require','./core/app'], function( require , App ) {
App.run( window ) ;
});
});
});
fileReader.js :
define(function () {
...
return FileReader ;
});
config1.js :
define(['FileReader'], function ( FileReader ) {
var Interface = ... ;
...
App.FileReader.registerFileType("config1",Interface)
return FileReader ;
});
app.js :
define(['FileReader'], function ( FileReader ) {
var App = function(){} ;
App.run = function(){ FileReader.openFile( ... ) ; ... } ;
return App ;
});
What's the point of my problem ?
To get sure that the FileReader object contains the right Interface objects, I first force requirejs manually to load all the FileReader related files and THEN load the main app file. This way, once the App object requests for the FileReader object, it already contains the right Interfaces registred.
What I'd like to achieve is just to only request for
"./core/app" in the main file
"./fileReader" in the core/app file
and then during it's first load, it would be fine if it could load the config-type modules IN the fileReader file, register all the stuff and only then returns the answer.
What I tried was : (in FileReader)
fileReader.js :
define(["require"],function ( require ) {
...
require(["config1","config2","config3"],
function(){
//occurs asynchronously, after the App.run() is triggered because of the synchronous return
callback(FileReader) ; //unfortunately I did not find any callback of this sort
//if it existed I could asynchronously find the list of the modules in the directory, load the them, and only then fire this fictive "ready" event
}
return FileReader ; //the first return occurs synchronously, so before the the fileReader is ready : modules are missing
});
So what is the best way to load this sort of modules (with some sort of circular dependency between the the config-type files and the the main FileReader) ? What'if I'd like to force it to be asynchronous so I can read what's available in the config-type directory ? With the simple HTML scripts list they were loaded in the right order and I had an easy access to the modules lists, now I'd like to be sure that everyting is ready before the app starts.
It seems to me you could parcel off the functionality that records what file types are available into a new module, maybe named FileTypeRegistry. So your config files could be like this:
define(['FileTypeRegistry'], function ( FileTypeRegistry ) {
var Interface = ... ;
...
FileTypeRegistry.registerFileType("config1", Interface);
});
These modules don't need to return a value. registerFileType just registers a type with the registry.
FileReader.js could contain:
define(["FileTypeRegistry", "config1", "config2", "config3"], function ( FileTypeRegistry ) {
var FileReader = {};
var FileReader.openFile = function (...) {
var impl = FileTypeRegister.getFileTypeImplementation(...);
};
return FileReader;
});
getFileTypeImplementation retrieves an implementation (an Interface previously registered) on the basis of a type name.
Louis' answer was interesting but it just moved the problem creating a new module, and even if it placed the dependencies in the right context, I tried to search for some real asynchronous module definition (not just loading).
The best I could finally come up with was to edit the require.js source file to add the behaviour I expected. I registered a new handler (a special dependency behaviour like "require" or "exports" ) called "delay" that provided a callback function for the module definition :
basically, this works this way :
define(["delay"], function ( delay ) {
var Module = ... ;
setTimeout(function(){ //Some asynchronous actions
delay(Module) ; //This does actually returns the Module's value and triggers the "defined" event.
//It's actually transparent so the other modules just "feel" that the network's load time was a bit longer.
},1000);
return Module ; //This does not do anything because delay is active
});
Thanks to this asynchronous definition, I can now scan the directory (an asynchronous request) and then transparently load all the modules.
The modifications represents only about 10 lines so if someone is interested, I post it there :
https://github.com/jrburke/requirejs/pull/1078/files
Apparently it's not the first request for asynchronous exports but it's still in debate so I just use this for personal projects.