SCRIPT5005: String expected - Rxjs crashes on IE 11 symbol polyfill - javascript

At work we have (sigh!) to support IE 11 for the current project I'm working on.
The project uses RxJS 6.
To support most of the features we've included, we used Webpack with Babel and core-js (as suggested by Babel itself) as polyfiller.
If we try to import RxJS, our application loading get stuck on a specific line:
Observable.prototype[_symbol_observable__WEBPACK_IMPORTED_MODULE_2__["observable"]] = function () {
return this;
};
with the error SCRIPT5005: String Expected.
We are using the es5 transpiled version of RxJS. So the code original typescript code should be this one.
I know symbols are not supported in IE 11, as per Kangax's Ecmascript compatibility table and that core-js includes a polyfill for Symbols, and we are importing all the polyfills.
In fact, if I try to run this with the polyfill after the error through the console, it works perfectly.
var obj = {};
Object.defineProperty(obj, Symbol.for("test"), {
value: 5
});
What's weird is that if I try to set a breakpoint on the line that give us the error, I can access singularly to these components without problems.
Observable.prototype
_symbol_observable__WEBPACK_IMPORTED_MODULE_2__
_symbol_observable__WEBPACK_IMPORTED_MODULE_2__["observable"]
The second and the third lines returns me an object.
If I do manually Observable.prototype[_symbol_observable__WEBPACK_IMPORTED_MODULE_2__["observable"]] it still returns me the error.
Accessing to Object.prototype through an Object is not allowed (as using an object as index makes the container object automatically call .toString() on the key). Therefore IE returns me that error. Or, at least, I think that might be the reason.
What's more weird is that Symbol.for("test") returns an object that is the same as _symbol_observable__WEBPACK_IMPORTED_MODULE_2__["observable"] (with description: test instead of observable). And if I do obj[Symbol.for("test")] it works perfectly.
Also, it seems like I'm unable to create variables or such while I'm stopped on a breakpoint in IE 11 console, so I cannot even export that symbol to test that later (it allows me to do var x = {}; or var x = 5, but if I call 'x', it throws me 'x' is undefined).
Any clues about this problem and how we might solve this?
Might this be a problem of the polyfill?
I'm attaching here below my webpack config and my .babelrc
module.exports = {
module: {
rules: [
{
test: /\.jsx?$/,
use: {
loader: 'babel-loader',
options: {
presets: ['#babel/preset-env', '#babel/preset-react', '#babel/preset-flow'],
},
},
exclude: /node_modules\/(core-js|(react.+)|(rxjs.+))/,
},
...
]
}
}
{
"presets": [
"#babel/preset-react",
"#babel/preset-flow",
["#babel/preset-env", {
"targets": {
"browsers": [
"last 2 versions",
"safari >= 7",
"Explorer 11"
]
},
"useBuiltIns": true
}]
],
"plugins": [
"#babel/plugin-proposal-object-rest-spread",
"#babel/plugin-transform-runtime"
],
"env": {
"test": {
"presets": ["#babel/preset-env", "#babel/preset-react", "#babel/preset-flow"]
}
}
}
Thank you.

TL;DR
Since we have a monorepo with multiple projects (a "sample app" that loads another project on its inside), I was including the core-js polyfill both on the sample app and in that project.
They were somehow conflicting, I'm still not sure why and how. This wasn't concerning rxjs but affecting it.
Also, I wrote a wrong regex on the webpack.config.js on babel-loader, but I'm not sure this was really affecting all.
It was: /node_modules\/(core-js|(react.+)|(rxjs.+))/
It should have been: /node_modules\/(core-js|rxjs|react[^\s+]*)/
Since I had to target the packages, not the files as I was thinking. I added a more complex regex on react to match also react-dom or other react-things (if any).
I was able to create a sample application that could work without problems.
I discovered RxJS has somehow its own polyfill for symbols.
In fact, running that sample with React, webpack, babel, rxjs but not core-js, wasn't giving any problem. (Note: the sample has core-js installed).
Setting a breaking point on
Observable.prototype[_symbol_observable__WEBPACK_IMPORTED_MODULE_2__["observable"]], reveals this:
While using core-js, it reveals this:
Although this difference, both with and without the sample, it works perfectly on that sample. So I thought how it was possible and thought that I read somewhere that the polyfill should have been loaded only once in the most global object.
Since we are developing a project in a mono-repo that has React-based Website and another complex React component that gets imported on runtime, and that website seemed to need also polyfills, I added core-js import on both the projects.
By removing the one on the website, the error changed (it got stuck on another point not concerning this one).
Moreover, as said in the TL;DR, I think there was a problem with the regex applied on babel-loader.

Related

New error when accessing properties of top "Object is possibly 'null'." after upgrading to TS 4.4.3

I recently upgraded my project to TypeScript 4.4.3 from 3.9.9.
My project's using "strictNullChecks": true, in its tsconfig.json, and runs in the browser, not server-side on Node.
In TypeScript 4.4.3, it seems like the type declarations for top has changed to WindowProxy | null (node_modules/typescript/lib/lib.dom.d.ts)
This means that I get the following error1 wherever I try to access properties of top2: TS Playground
const topUrl = top.window.location.href; // => Object is possibly 'null'.
How can I ignore this category of errors only for when top is possibly null?3
1 I understand that this error is warning me against the scenario where my website is loaded in an iframe, and therefore can't access top due to XSS. This isn't an issue because my 'X-Frame-Options' is set to 'sameorigin' and will therefore refuse to load my website in a cross-origin iframe.
2 I access properties of top because I use iframes inside my project a lot, where it loads sub-pages on the same domain.
3 I could use the following fixes to get around this Object is possibly 'null'., but I'd prefer not to, as my project is quite large and this fix would be tedious with minimal improvement.
let topUrl = top?.window.location.href || '';
let topUrl = '';
if (top) {
topUrl = top.window.location.href;
}
I could also ignore these errors on every line with // #ts-ignore, but there's a lot of references to top and I don't want to clutter the project (also, other TypeScript errors on the same line would be ignored).
// #ts-ignore
const topUrl = top.window.location.href;
I found a solution which would possibly fit your needs. And there are 2 versions of the solution you can take into consideration.
Both of these versions work by overriding the built-in lib-dom with a npm package #types/web which is also provided by Microsoft.
Beta but systematic one - Using the latest official 'lib override' from typescript#4.5.0-beta
Follow steps below and things are gonna work as you expect without any other code modifications:
Upgrade to TypeScript 4.5.0:
npm i -D typescript#4.5.0-beta
or install globally
npm i -g typescript#4.5.0-beta
Install the #types/web#0.0.1 type package which has top: Window type
npm i -D #typescript/lib-dom#npm:#types/web#0.0.1
I have made some simple tests on this solution and managed to get behaviour you want.
The only shortcoming of this solution is that typescript#4.5 is still beta currently. But It worth your consideration since its final release will be just on next month.
TypeScript 4.5 Iteration Plan
Stable one - typescript 4.4.3 and switch the built-in dom lib.
install #types/web
npm i -D #types/web#0.0.1
notice that the install command is different from the above one.
Update your tsconfig.json. There are two cases to consider depending on if you have lib defined in your tsconfig.json or not.
Without "lib" - You will need to add "lib": []. The value you want to add inside your lib should correlate to your "target". For example if you had "target": "es2017", then you would add "lib": ["es2017"]
With "lib" - You should remove "dom".
The drawback of this second version of solution is, it cannot prevent your dependencies in node_modules from pulling in the TypeScript DOM library.
Please bear in mind that despite #types/web is up to version 0.0.40, only version 0.0.1 of #types/web has top typed top: Window instead of top: WindowProxy | null which is what you need.
The problem
You decided to upgrade your compiler version, and, as mentioned in a comment, major software version changes almost always come with breaking API changes.
The correct way to solve your issue (prevent compiler errors) is to modify your source code to satisfy the compiler. You said that modifying your source code in this way would be a chore, and asked about modifying the compiler configuration instead such that you can avoid modifying your source code.
It is not possible to override the types in lib.dom.d.ts in new type declarations. TypeScript will emit additional errors if you attempt to do this, even if you disable type-checking of your new declaration file, resulting in an incompatible merging of your new declarations. Your only option is to exclude the built-in DOM library and provide your own modified version of it.
Here is an overview of how to do that:
Starting TSConfig
You haven't provided your tsconfig.json file, so here's an example to use as a base, with the assumption that your source is organized in your repo's src/ directory:
Note: "strict": true implies "strictNullChecks": true
{
"compilerOptions": {
"isolatedModules": true,
"lib": [
"esnext",
"dom",
"dom.iterable"
],
"module": "esnext",
"outDir": "dist",
"strict": true,
"target": "esnext"
},
"include": [
"./src/**/*"
]
}
Creating the modified lib.dom.d.ts library
First download the lib.dom.d.ts file from the tag that matches your TypeScript version (4.4.3): https://github.com/microsoft/TypeScript/blob/v4.4.3/lib/lib.dom.d.ts
Move the file to src/types/lib.dom.d.ts in your project
Remove the triple-slash reference on line 18 by deleting the entire line. (This will allow you to continue using other built-in libraries.)
Modify line 17286 from this:
readonly top: WindowProxy | null;
to this:
readonly top: WindowProxy;
Modify line 18350 from this:
declare var top: WindowProxy | null;
to this:
declare var top: WindowProxy;
Save the file
Modifying your TSConfig
Now that you have a replacement library for the DOM types in your program, you need to tell the compiler to use it that way. Here's what you need to change:
{
"compilerOptions": {
// ...
"lib": [
"esnext",
"dom", // Delete this from the array
"dom.iterable"
],
// ...
// Add this array property
"typeRoots": [
"./node_modules/#types",
"./src/types"
]
},
// ...
}
So the modified tsconfig.json now looks like this:
{
"compilerOptions": {
"isolatedModules": true,
"lib": [
"esnext",
"dom.iterable"
],
"module": "esnext",
"outDir": "dist",
"strict": true,
"target": "esnext",
"typeRoots": [
"./node_modules/#types",
"./src/types"
]
},
"include": [
"./src/**/*"
]
}
Conclusion
That's it. Now you should be able to compile your program and reference window.top or just the global top as a non-nullable value without a compiler error.
You'll need to repeat this process every time you upgrade TypeScript. Is this strategy more sustainable than modifying your source code? That's up to you.
I preface this answer with a strong warning that I would not do this to my project and encourage anyone in this position to fix the errors the proper way using null coalescing or not null assertion. EG:
window.top!.scrollTo()
top!.scrollTo()
window.top?.scrollTo()
top?.scrollTo()
// etc..
Even though theres 1500 I think using some regular expression you could easily target a large portion of those errors and fix with ease. With that said heres some other options:
I havent done this in a production project and might result in some other strange errors, its largely untested by myself outside of quick testing
The summary of this solution is you could clone the lib.dom.ts file and make the modifications by hand.
Copy ./node_modules/typescript/lib/lib.dom.d.ts to somewhere in your project, say ./lib.dom.modified-4.4.3.d.ts
Make the modifications to remove the null type from window.top and top types
// old
// readonly top: WindowProxy | null;
// new
readonly top: WindowProxy;
...
// old
// declare var top: WindowProxy | null;
// new
declare var top: WindowProxy;
Update your tsconfig.json to remove dom as one of the libraries and add it to the list of types
{
"compilerOptions": {
"lib": [
"ES6"
],
"strictNullChecks": true,
"module": "commonjs",
"target": "ES6",
"types": [
"./lib.dom.modified-4.4.3"
]
},
"include": [
"src/**/*"
]
}
Now you have a custom dom library with the top property not nullable
Alternatively you could make a patch for lib-dom using git and apply it post install. Details about how to do that are outlined in several solutions of this question How to overwrite incorrect TypeScript type definition installed via #types/package
You can initialize a VCS if you have not already done so. Then
look at the place of your error
see what you would need to replace it to
use whatever tools you use to replace all occurrences of the source text to the target text
if there are still errors, repeat
Once you have replaced all occurrences of issues this way, you will need to review your changes. You will find the changes via the VCS. If you use git, then the command is
git diff
See all the changes and whichever looks even a little bit suspect, investigate and see whether the automatic change was correct. If not, perform whatever you need to ensure that your code is correct.
Test everything. You would do well if you would create a separate versioning branch for this work which would be tested for a long time before it's being released to production.
instead that you shoud use !, that typescript ignores the fact that the value could be null which in your case it is not
const topUrl = top!.window.location.href;
if your ES-LINT complains on that you can set the in config file like that
module.exports = {
...
rules: {
...
'#typescript-eslint/no-non-null-assertion': 'off'
},
...
}
I access properties of top because I use iframes inside my project a lot, where it loads sub-pages on the same domain.
top is potentially null...
This isn't an issue because my 'X-Frame-Options' is set to 'sameorigin' and will therefore refuse to load my website in a cross-origin iframe.
But you're saying that's impossible, in which case...
function getTop(): NonNullable<typeof top> {
if (!top) throw new Error('global var top is null');
return top;
}
...then replace any occurrence of top.example with getTop().example so as to centralize all potential 'null' errors.
While this isn't the most simple solution, it should be the safest.
In your question, you state:
I could use the following fixes to get around this Object is possibly 'null'., but I'd prefer not to, as my project is quite large and this fix would be tedious with minimal improvement.
let topUrl = top?.window.location.href || '';
I can appreciate the tedious nature of this task, but if you're insistent on using TypeScript, I must also be insistent that you employ this solution. It is necessary in TypeScript.
One way I would solve this problem would be to use my code editor/IDE program to search/replace all text references in my project. I use Visual Studio Code which allows me to Search and Replace specific text in my source files. It allows for Regex searching, including and excluding particular files. I'm certain that a great majority of code editors/IDEs have similar functionality.

Run script to show alert and redirect on IE before error kicks in in Gatsby

I am running a Gatsby site on Netlify, there is an error / crash specific to IE and I want to show an alert and then redirect to chrome download page if the user is using IE, since I am not supporting IE anyway. I think the crash is caused by a npm package and i dont wanna change to other one because it suit my need.
I put the below script in the Layout component which is run on every page, but the error kicks in and crash the site, the site flash a normal dom before it crash. Am wondering if there are any way to fix it
The script works fine if i change the condition to chrome and run it on chrome to test it
//Layout.js
const handleIE = () => {
if (typeof window !== "undefined") {
let ua = window.navigator.userAgent
if(ua.indexOf('MSIE') > -1) {
alert('Internet Explorer is not supported. Please use Chrome or Firefox')
window.location.href = "https://www.google.com/chrome/";
}
}
}
<Helmet>
<script>{handleIE()}</script>
</Helmet>
// Error log on IE
DOM7011: The code on this page disabled back and forward caching. For more information, see: http://go.microsoft.com/fwlink/?LinkID=291337
HTML1300: Navigation occurred.
SEC7118: XMLHttpRequest for https://ka-f.fontawesome.com/releases/v5.15.3/css/free.min.css?token=e4232fccfser required Cross Origin Resource Sharing (CORS).
SEC7118: XMLHttpRequest for https://ka-f.fontawesome.com/releases/v5.15.3/css/free-v4-shims.min.css?token=e4232fccfser required Cross Origin Resource Sharing (CORS).
SEC7118: XMLHttpRequest for https://ka-f.fontawesome.com/releases/v5.15.3/css/free-v4-font-face.min.css?token=e4232fccfser required Cross Origin Resource Sharing (CORS).
SCRIPT1003: Expected ':' File: classic.js, Line: 679, Column: 32
SCRIPT1003: Expected ':' File: classic.js, Line: 679, Column: 32
[object Error]
description "Object doesn't support property or method 'trunc'"
message "Object doesn't support property or method 'trunc'"
name "TypeError"
number -2146827850
stack "TypeError: Object doesn't support property or method 'trunc'
SCRIPT438: Object doesn't support property or method 'trunc'
File: framework-7bdbbf12b92c7ff172a2.js, Line: 2, Column: 2442
Thank you!!
The IE issue is breaking your application before the script can load. Despite being improbable because you are loading the script in each page that imports the Layout component (you may want to use one of the gatsby-browser APIs), your code seems to work.
If you want to be able to bypass this limitation by showing an alert to the IE
user you may need to polyfill the functionality, at least to allow the trunc method, which seems to be the offending one.
Said that, this trunc method may come from your code (unlikely) or from third-party dependencies. If it's from your code just remove it or find another IE-friendly solution. If it comes from one of the Node modules, you may need to find another solution.
First of all, you need to find which dependency is causing this issue, then, as it is described in this GitHub thread.
Create a .babelrc at the root of your Gatsby project:
{
"env": {
"test": {
"presets": [
"#babel/preset-env",
{
"loose": true,
"modules": "commonjs",
"useBuiltIns": "entry",
"targets": { "browsers": [">0.25%", "not dead"] }
}
]
}
},
"presets": [
"babel-preset-gatsby",
[
"#babel/preset-env",
{
"debug": true,
"loose": true,
"modules": false,
"useBuiltIns": "entry",
"targets": { "browsers": [">0.25%", "not dead"] }
}
]
],
"plugins": [
[
"#babel/plugin-proposal-class-properties",
{
"loose": true
}
],
"babel-plugin-macros",
"#babel/plugin-syntax-dynamic-import",
[
"#babel/plugin-transform-runtime",
{
"helpers": true,
"regenerator": true
}
]
]
}
Note: you may need to install the required modules.
Then, you only need to add the polyfill. In your gatsby-browser.js:
import '#babel/polyfill'
// eslint-disable-next-line import/prefer-default-export
export const onClientEntry = () => {
// Without this function body the import will not be picked up.
// handleIE()
}
Aas I said at the beginning of the post, this gatsby-browser.js API (onClientEntry) will work for your use case, so you can re-use it. You can load there your script (handleIE) to check the user agent. In that way, you will be only loading once per user entry the script, rather X times (one for each page that extends the Layout component the user visit)
You may also find useful this GitHub gist about polyfill IE 10-11 in Gatsby.

Linter error in Deno project tsconfig.json, and cascading into project files - but code runs successfully?

I need to use the ES2019 flatMap() method on an array in a Deno project, so I have created a tsconfig.json file as below:
{
"compilerOptions": {
"target": "es5",
"lib": [
"es2019"
]
}
}
This gives what appear to be 6 of the same linter errors, specifically Cannot find type definition file for 'cacheable-request' in the tsconfig.json file. Have I created this file incorrectly or somehow caused conflict with the Deno structure?
In a project module where I am attempting to use flatMap() I am seeing the error
Property 'flatMap' does not exist on type 'number[][]'. Do you need to change your target library? Try changing the `lib` compiler option to 'es2019' or later.
This doesn't make much sense since I have set my lib to es2019 in my tsonfig - unless I've created it wrong and it's thus not being picked up... then again the code works, so I'm assuming that it has correctly compiled to es2019.
Am I missing some aspect of creating a tsconfig file for a Deno project that allows use of es2019?
I believe there's no need normally to include a tsconfig.json in order to use ES2019 features in Deno. I was able to use flatMap without problems in Deno 1.9.0, for example:
// mod.ts
const map = [1,2,3,[1,2,3],[1,2]].flatMap((_value, _index, _array) => {
return 1;
});
console.log(map);
deno run mod.ts
>> [ 1, 1, 1, 1, 1 ]
I couldn't find if this was unsupported or bugged in previous versions but try upgrading Deno and omitting tsconfig.json to fix this.

Sencha Cmd, C2001 closure compiler error: extending native class: Array

When running Sencha Cmd v6.5.3.6, i get the following error message:
[ERR] C2001: Closure Compiler Error (This code cannot be converted from ES6. extending native class: Array) -- compression-input:111263
The error is caused by this code:
class Chains extends Array {
}
The error still occurs with methods inside class declaration.
Is there a way to make this code compiled by Sencha Cmd ?
UPDATED :
To solve the problem, I change the code to:
function Chains() { };
Chains.prototype = new Array;
Chains.prototype.anyMethod = function () { }
You are using a feature of ES6 that cannot be transpiled into pre-ES6 code.
Sencha Cmd by default transpiles your code into pre-ES6 code because IE11 support has not yet been dropped.
You can disable code transpilation starting with Sencha Cmd 6.5.0 as described in the official docs:
There are cases where you won't need all that transpiling. Maybe you’re targeting Electron or you only support modern browsers that have all these features. You can disable the transpiler and still use the Sencha Cmd code compressor against your native ES6 code. Just a tweak to the app.json file and say goodbye to the transpiler and its polyfills:
"output": {
"js": {
"version": "ES6"
}
}
I don't think ExtJS supports that syntax as of now. For the time being, you might have to go with their syntax:
Ext.define('Chains', {
extend: 'Array'
});
Then in your code you can call it like this:
var chns = Ext.create('Chains');
chns.push('a');
console.log(chns);

webpack 2 expose webworker as global

I'm trying to write tests for a react/redux app, and we have a bunch of webworkers which are currently imported via require('worker-loader?inline!downloadTrackWorker')
I've been going in circles trying to figure out how to separate out this code so I can run tests in node.js without having trouble with loading the webworker.
One solution I came up with was to expose the webworker globally in my webpack, which would mean I could define a stub or mock in my tests.
In my webpack config, I've added
module: {
loaders: [...],
rules: [{
test: require.resolve(APP_DIR + '/helpers/downloadTrackWorkerLoader'),
loader: 'expose-loader',
options: 'DownloadTrackWorker'
}]
}
my trackWorkerLoader is simply
const DownloadTrackWorker = require('worker-loader?inline!./downloadTrackWorker.js');
module.export = DownloadTrackWorker;
I've also tried the above without inline, but no luck.
I'm experiencing two problems.
when I look for DownloadTrackWorker in my console, it is undefined
with my updated webpack.config, I get an error that webpack can't parse may need appropriate loader at
ReactDOM.render(
<Provider store={store}>
<Player />
</Provider>,
document.getElementById('root')
);
Any suggestions on what I'm doing wrong? It appears to me the issues I'm seeing are related.
when I look for DownloadTrackWorker in my console, it is undefined
As the expose-loader notes in Readme - Usage, you need to import it in order to be included in the bundle and therefore exposed. The rules are not including anything but are applied to the imports in your app which satisfy the test. Besides that you're also not applying the loader to the correct file. You want to apply the expose-loader to trackWorkerLoader.js, so the correct rule would be:
{
test: require.resolve(APP_DIR + '/helpers/trackWorkerLoader'),
loader: 'expose-loader',
options: 'DownloadTrackWorker'
}
Now you need to import it somewhere in your app with:
require('./path/to/helpers/trackWorkerLoader');
This will correctly expose DownloadTrackWorker as a global variable, but you have a typo in trackWorkerLoader.js instead of module.exports you have module.export. Currently you're not actually exporting anything. It should be:
module.exports = DownloadTrackWorker;
Instead of inlining the worker-loader in the require (not talking about its option) you can also define it as a rule:
{
test: require.resolve(APP_DIR + '/helpers/downloadTrackWorker'),
loader: 'worker-loader',
options: {
inline: true
}
}
And now you can simply require it without needing to specify the loaders in trackWorkerLoader.js:
const DownloadTrackWorker = require('./downloadTrackWorker');
module.exports = DownloadTrackWorker;
with my updated webpack.config, I get an error that webpack can't parse may need appropriate loader
You're defining both module.loaders and module.rules at the same time. Although module.loaders still exists for compatibility reasons, it will be ignored completely if module.rules is present. Hence the loaders you configured before, are not being applied. Simply move all rules to module.rules.

Categories