Web worker importScripts fails to place script variables in global scope - namespaces

TL;DR I have a worker which imports two objects via importScripts. They do not appear in "self". Moz docs say:
importScripts() method of the WorkerGlobalScope interface
synchronously imports one or more scripts into the worker's scope.
So where are they?!
Details:
Three files: index.html, worker.js, foo.js
index.html contains:
const worker = new Worker('./worker.js')
worker.postMessage({ file: './foo.js', var: 'foo' })
worker.js contains:
onmessage = e => {
params = e.data
console.log('worker: params', params)
console.log('worker: this === self', this === self)
importScripts(params.file)
console.log(self[params.var], eval(params.var), foo)
}
foo.js contains:
const foo = { one: 1, two: 2 }
const bar = { three: 3, four: 4 }
I.e. the html passes the path and variable name to the worker which imports the path and, using the name of the variable, attempts to find it in "self". Fails.
Yet using either "eval" or the variable itself, not its name, works as expected.
Here are the console messages:
worker: params {file: "./foo.js", var: "foo"}
worker: this === self true
undefined {one: 1, two: 2} {one: 1, two: 2}
So where does the worker place the variables imported from foo.js?!
NB: this may seem like an odd thing to do, why not just use the variable? Deep reasons exist having to do with a highly (re)factored repo.

Using const and let to declare variables does not add them to the global scope object, such as the workers self or window (see MDN description for const). Using var does, however:
const x = 1;
let y = 2;
var z = 3;
console.log(window.x, window.y, window.z);
// undefined undefined 3
Changing your variable declarations for foo and bar to use var should fix the problem.

Related

How to add multiple files to IPFS Mutable Files System without revealing the content to the IPFS network

I need to be able to add a bunch of files to a common directory using the mutable file system from ipfs.files.* without revealing this content to the IPFS network until it is needed.
This is the code I have been using to test:
const onDrop = useCallback(async (acceptedFiles) => {
const test = JSON.stringify({test:"test"});
const testFile = new File([test], "test.json")
try{
for(let i = 0; i < acceptedFiles.length; i++){
console.log(acceptedFiles[i].path)
await ipfs.files.write(files[i].path, files[i], {parents: true})
}
await ipfs.files.write("/test.json", [testFile], { create: true })
} catch(e){
console.log(e)
}
console.log((await ipfs.files.stat( "/" )).cid.toV1().toString())
}, [])
This throws me the error:
ReferenceError: global is not defined
at toAsyncIterator (to-async-iterator.js?6924:44:1)
at eval (write.js?db08:99:37)
at eval (create-lock.js?5096:33:1)
at async mfsWrite (write.js?db08:98:1)
caused by the ipfs.files.wite() statement in the loop.
I initially added content through the ipfs.addAll() function, but this did not seem to work for adding it to the global MFS directory.
I think I am using the documentation right, but I might be trying to use MFS for something it was not intended for. If anyone knows the best way to achieve combining many files into a directory without revealing the content to the IPFS network, please let me know.

Avoid Repeating: Use Global Variables in Google Apps Script or Not?

As of 2021, with V8 engine, I'm wondering if it's a good idea to use Global Variables in Google Apps Script? And if it is, how to use them? Is my way a good way (described below)?
Now I checked, of course, all other similar questions. But there are still some details I couldn't find:
Basically what I tried to do is to not repeat the code: I have multiple functions, and I'm storing the active spreadsheet and the current sheet like so:
const ss = SpreadsheetApp.getActiveSpreadsheet();
const sheet = ss.getActiveSheet();
which leads to
repeating (1)
wasting resources - instantiating spreadsheet and sheet (2)
Increasing variables names inconsistency (ss / spreadsheet / spreadSheet - when copy/pasting from a snippet on the web) (3)
Right?
So I thought of using the Global Variables (GV) whenever I have common local variables in multiple functions.
However, since they’re will be unnecessarily allocated on every function call (there are also other functions that don't need the GVs), I tried to define them only when needed (only when there's function call that uses them) - by not using a defining keyword (var, const, let):
According to this link, it seems to be a good approach (pattern 1).
Anyway, I'm wondering if there are any other considerations or downsides I'm not aware of? Is it really a good idea to go this road? Because so far, I didn’t see any snippet that implements this, and I saw a lot of GAS snippets.
One downside I'm aware of is the lack of autocompletion in the new GAS editor, for my GVs (since I didn't define them using 'var' or 'let' to set their scope Global on purpose).
Otherwise, I'm aware of PropertiesService and CacheService. However I'm willing to reuse my script (where I defined my GVs) as a library for my other scripts.
Plus, you can only store values as strings in PropertiesService and CacheService (and not SpreadsheetApp.getActiveSpreadsheet()) right? Not to mention that I don't need persistency after the script execution.
So I'm also hesitant to use them instead of GVs.
You can use the lazy loading technique in my answer
To make it dynamic and avoid repetition, You can use enclosing arrow functions(()=>{}) to avoid direct execution and use Object.defineProperty() to add a getter.
One of the significant advantage of this method is modular lazy loading. If a object isn't needed, it isn't loaded. If you have ss, sheet1, sheet2,rangeOfSheet1 and rangeOfSheet2 as global variables, if you access rangeOfSheet2, only it's dependencies are loaded, i.e, sheet1 and ss. The rest are untouched.
const g = {};//global object
const addGetter_ = (name, value, obj = g) => {
Object.defineProperty(obj, name, {
enumerable: true,
configurable: true,
get() {
delete this[name];
return (this[name] = value());
},
});
return obj;
};
//MY GLOBAL VARIABLES in g
[
['ss', () => SpreadsheetApp.getActive()],
['MasterSheet', () => g.ss.getSheetByName('Sheet1')],
['MasterRangeColA1_5', () => g.MasterSheet.getRange('A1:A5')],
['MasterRangeColAValues', () => g.MasterRangeColA1_5.getValues()],
].forEach(([n, v]) => addGetter_(n, v));
const test = () => {
console.info('start');
console.log({ g });
console.info('Accessing MasterSheet');
console.log(g.MasterSheet);
console.log({ g }); //note ss is loaded as well
console.info('Accessing MasterRangeColAValues');
console.log(g.MasterRangeColAValues);
console.log({ g }); //note MasterRangeColA1_5 is loaded as well
};
Instead of a global object g, we can also use the global this, in which case, all variables directly become members of a global object:
const addGetter_ = (name, value, obj = this) => {
Object.defineProperty(obj, name, {
enumerable: true,
configurable: true,
get() {
delete this[name];
return (this[name] = value());
},
});
return obj;
};
[
['ss', () => SpreadsheetApp.getActive()],
['MasterSheet', () => ss.getSheetByName('Sheet1')],
['MasterRangeColA1_5', () => MasterSheet.getRange('A1:A5')],
['MasterRangeColAValues', () => MasterRangeColA1_5.getValues()],
].forEach(([n, v]) => addGetter_(n, v));
const test = () => {
console.info('start');
console.log(this);
console.info('Accessing MasterSheet');
console.log(MasterSheet);
console.log(this); //note ss is loaded as well
console.info('Accessing MasterRangeColAValues');
console.log(MasterRangeColAValues);
console.log(this); //note MasterRangeColA1_5 is loaded as well
};
Advantage: You don't have to prefix variables with g. But, global space is polluted.

Vuejs dynamic require components

I have a array with data and want to require vue js components from it, some=thing like:
var d = ["a.vue","b.vue"];
for(var k in d){
let test = require('./'+d[k]);
...
}
It works fine but I get a warning in compile time because it tries to load only the './' - current root (without the d[k]). The components are loaded normal in the browser.
I also tried this but it didn't work:
var test = resolve => require(['./a.vue'], resolve);

Gulp - using ES6 modules in a combined js file?

I am trying to use ES6 modules in my current GULP setup. I've read that this is yet to be supported by browsers or Babel, so there is a need some elaborate setup to make this work, using things such Browserify, babelify, vinyl-source-stream. (Seems extremely complex setup).
What I want is different from examples I had found online. All the examples are with external files being imported, and I really don't want that. I want all the files to be bundled into a single file, with all the modules there already. Here's what I have:
My current GULP setup is like this:
gulp.task('buildJS', function() {
var src = [
'./js/dist/app.js',
'./js/dist/templates.js',
'./js/dist/connect.js',
'./js/dist/config.js',
'./js/dist/utilities.js',
'./js/dist/components/*.js',
'./js/dist/pages/**/*.js',
'./js/dist/modals/*.js',
'./js/dist/init.js' // must be last
];
gulp.src(src)
.pipe(concat('app.js'))
.pipe(babel({modules:"common"})) // I have no idea what "modules" actually does
.pipe(gulp.dest('../js/'))
});
And this is an example of a component file in /js/dist/components/.
There are many files like this, and they are all combined to a single file.
module "components/foo" {
export function render(settings) {
return ...
}
}
So later in some page controller I would use it:
import { render } from "components/foo";
Question:
Now that I have a single file, (been transformed using Babel), how can I use the modules via Import?
No, don't naively concatenate the files. Use browserify to bundle them, with babelify to compile them (via babel). A basic example would look something like this:
browserify('./entry')
.transform(babelify)
.bundle()
// ...
It's hard to give more specific advice because your use case is so unclear. Do you have a dependency graph that begins at one file, or are you trying to bundle together a bunch of indepdendent modules? Are you trying to run a script to kick off an application, or do you just want to be able to access modules individually?
Based on the example you linked to in your comment you should have something like this:
components/doughnut.js
export default function Doughnut (settings = {}) {
// ...
};
Doughnut.prototype = {}
routes/home.js
import Doughnut from './components/doughnut';
export default function () {
var component = new Doughnut();
$('body').html(component.render());
};
Have each module export what you want to be available from any other module. Have each module import whatever it needs from any other module(s). Whatever uses the controller from this example should then do import home from './routes/home'; These modules aren't tied to a global variable App and can be reused in other applications (as long as you otherwise make them reusable).
.pipe(babel({modules:"common"})) // I have no idea what "modules"
modules is a babel option that determines what module format it compiles ES6 module syntax to. In this case, CommonJS.
module "components/foo" {
Thanks to your comments I now understand why you have this. You need to eliminate that. Your component file should look something like:
export function render (settings) {
return ...
}
Paired with:
import { render } from "components/foo";
Or if you want a default export / import:
export default function render (settings) {
return ...
}
import render from "components/foo";
import { render } from "components/foo";
If you're browserifying your modules, you're probably going to need to use relative paths like ./components/foo or use something else to deal with the paths, like babel's resolveModuleSource option.
Since the end of 2015 I have been using rollupjs in order to create a bundle of ES2015 (ES6) modules, so I could use import/export freely in my code.
I've found Rollupjs to be very good and easy to use. The people behind
it are great people which devote themselves to the project. I've had
many questions which I had posted on the project's Github issues page
and I always got answered pretty quickly.
Setup includes these rollupjs plugins:
rollup (basic rollupjs bundler)
rollup-plugin-babel (converts ES2015 code to ES5 or earlier, for legacy browsers support)
rollup-plugin-eslint (verify the javascript code is valid)
rollup-plugin-uglify (minify the code, to make it smaller)
rollup-plugin-progress (shows bundle progress in terminal. shows which file being "worked on")
beepbeep (Make a console beep sound. I use this to inform me of compilaction errors)
Simplified GULP setup I'm using:
var gulp = require('gulp'),
gutil = require('gulp-util'),
rollup = require('rollup').rollup,
babelRollup = require('rollup-plugin-babel'),
eslintRollup = require('rollup-plugin-eslint'),
uglifyRollup = require('rollup-plugin-uglify'),
rollupProgress = require('rollup-plugin-progress'),
beep = require('beepbeep');
// ESlint
var eslint_settings = {
rulePaths: [],
rules: {
"no-mixed-spaces-and-tabs" : [2, "smart-tabs"],
"block-spacing" : [2, "always"],
"comma-style" : [2, "last"],
"no-debugger" : [1],
"no-alert" : [2],
"indent-legacy" : [1, 4, {"SwitchCase":1}],
'strict' : 0,
'no-undef' : 1
},
ecmaFeatures : {
modules: true,
sourceType: "module"
},
"parserOptions": {
"ecmaVersion" : 6,
"sourceType": "module",
"ecmaFeatures": {
"jsx": false,
"experimentalObjectRestSpread": true
}
},
globals : ['$', '_', 'afterEach', 'assert', 'beforeEach', 'Cookies', 'd3', 'dataLayer', 'describe', 'done', 'expect', 'ga', 'it', 'jQuery', 'sinon'], baseConfig: {
//parser: 'babel-eslint',
},
envs: [
'browser', 'es6'
]
};
// Rollup plugins configuration
function getRollupPlugins( settings = {} ){
var rollupPlugins = [];
rollupPlugins.push({
presets : [['es2015', {"modules": false}]], //['es2015-rollup'],
runtimeHelpers : true,
exclude : 'node_modules/**',
plugins : ["external-helpers"]
});
rollupPlugins.push(eslintRollup( Object.assign({throwOnError:true}, eslint_settings) ))
rollupPlugins.push(rollupProgress({
clearLine:true // default: true
}))
// I would advise Babel to only be used for production output since it greatly slower bundle creation
if( settings.ENV == 'production' ){
rollupPlugins.push(uglifyRollup())
rollupPlugins.push(babelRollup(rollupPlugins__babel));
}
return rollupPlugins;
}
var rollupPlugins = getRollupPlugins();
/**
* a generic Rollup bundle creator
* #param {String} outputPath [where to save the bundle to (must end with /)]
* #param {String} outputFileName [bundle file name]
* #param {String} entryFile [rollup entry file to start scanning from]
* #return {Object} [Promise]
*/
function rollupBundle(outputPath, outputFileName, entryFile, bundleOptions){
bundleOptions = bundleOptions || {};
bundleOptions.plugins = bundleOptions.plugins || rollupPlugins;
return new Promise(function(resolve, reject) {
outputFileName += '.js';
var cache;
// fs.truncate(outputPath + outputFileName, 0, function() {
// gutil.log( gutil.colors.dim.gray('Emptied: '+ outputPath + outputFileName) );
// });
rollup({
entry : entryFile,
plugins : bundleOptions.plugins,
cache : cache
})
.then(function (bundle) {
var bundleSettings = {
format : bundleOptions.format || 'umd',
sourceMap : false,
banner : config.banner
},
result = bundle.generate(bundleSettings),
mapFileName = outputFileName + '.map',
sourceMappingURL = '\n//# sourceMappingURL='+ mapFileName;
cache = bundle;
// if folder does not exists, create it
if( !fs.existsSync(outputPath) ){
gutil.log( gutil.colors.black.bgWhite('Creating directory ' + outputPath) );
fs.mkdirSync(outputPath);
}
// save bundle file to disk
fs.writeFile( outputPath + outputFileName, result.code + (bundleSettings.sourceMap ? sourceMappingURL : ''), function(){
resolve();
});
// save map file to disk
if( bundleSettings.sourceMap )
fs.writeFile( outputPath + mapFileName, result.map.toString());
})
.catch(function(err){
beep(1);
gutil.log( gutil.colors.white.bgRed('Rollup [catch]: ', err.stack) );
resolve();
})
});
}
// This task bundles the main application, using an entry file which itself has many imports,
// and those imports also has imports.. like a tree branching
gulp.task('bundle-app', ()=>{
return rollupBundle('../dist/js/', 'app', 'js/dist/app.js', {format:'cjs'});
});

How to pass a parameter to gulp-watch invoked task

I am trying to pass a parameter to a task that is being invoked by gulp-watch. I need it because I am trying to build a modular framework.
So if a file changes in module 1, the other modules don't need to be rebuild.
And I want just one function to create the concatted & uglified files per module.
This is what I got so far:
//here I need the 'module' parameter
gulp.task('script', function(module) { ... }
gulp.task('watch', function() {
gulp.watch('files/in/module1/*.js', ['script']); //here I want to pass module1
gulp.watch('files/in/module2/*.js', ['script']); //here I want to pass module2
});
A lot of the documentation/examples seems to be outdated (gulp.run(), gulp.start()).
I hope someone can help me out here.
I had the very same issue, searched for a while, and the "cleanest" way I came up with, uses the .on() event handler of gulp.watch(), and the .env property of gulp-util:
var gulp = require('gulp');
$.util = require('gulp-util');
var modules = {
module1: {}, // awesome module1
module2: {} // awesome module2
};
gulp.task('script', function(){
var moduleName = $.util.env.module;
// Exit if the value is missing...
var module = modules[moduleName];
if (!module) {
$.util.log($.util.colors.red('Error'), "Wrong module value!");
return;
}
$.util.log("Executing task on module '" + moduleName + "'");
// Do your task on "module" here.
});
gulp.task('watch', function () {
gulp.watch(['files/in/module1/*.js'], ['script']).on('change', function () {
$.util.env.module = 'module1';
});
gulp.watch(['files/in/module2/*.js'], ['script']).on('change', function () {
$.util.env.module = 'module2';
});
});
gulp-util also comes in handy if you need to pass (global) parameters from the shell:
[emiliano#dev ~]# gulp script --module=module1 --minify
Hope this helps someone else out there!
Regards.
In that i will answer directly the question "How to pass a parameter to gulp-watch invoked task"
My way of doing, and one of the possibility i see, is to use a global variable to pass the value between the two blocks. you set it just before launching the task in the watcher. And in the task, just at the start you pass it to a local variable.
See this answer for more details: https://stackoverflow.com/a/49733123/7668448
In what you want to achieve, you can too use just one watcher over the directory that hold all modules. If so is the structure. Then when a change happen, you can recover the changed file path. From that you can deduce what module does belong to. By getting the Module folder. That way you will not need to add a new watcher for each new module. Which can be nice when there is multiple contributors to the project for example when working on open source. And you do it one time, and don't have to care about adding anything. Just like with the delegation principle, with DOM event handling when there is multiple elements. Even if the chosen structure, doesn't have all the modules in one directory. You can stay pass multiple globs to the one watcher.
gulp.watch(['glob1/**/*.js', 'glob2/**/*.js',...], function(evt) {/*.....*/});
And following the structure you have, you can work your way to deduce what module is.
For the watcher here how i suggest you do it:
watch('./your/allModulesFolder/**/*.js', function (evt) {
rebuildModulWatchEvt = evt; //here you update the global var
gulp.start('rebuildModul'); // you start the task
})
The evt here hold multiple info: cwd, base, state, _contents ...etc And what interest us is path. So evt.path will give you the path of the changed file.
In your task either you do that:
gulp.task('rebuildModul', function() {
let evt = rebuildModulWatchEvt; // at all start you pass it to a local var
let filePath = evt.path; // how you get the changed file path
// your code go here for the rest, following your structure, get the path for the module folder
});
or you use a function :
gulp.task('rebuildModul', function() {
rebuildModulTaskRun(rebuildModulWatchEvt);
});
function rebuilModulTaskRun(evt) {
let filePath = evt.path;
// your code go here for the rest, following your structure, get the path for the module folder
}