Open paxperscientiam opened 5 years ago
ES Modules aren't supported in this library. (for require
and import
)
I think some developers have their own webpack setup for TypeScript compilation.
noLib
just removes things like window
, document
from autocompletion.
import is not supported and it is commented out by ts2gas, however export
is actually transpiled into exports.foo=foo, this completely breaks the compiled appscript, as foo is not part of the global scope and imports are simply commented out.
Is there a way to disable this behavior, so both import and export are simply commented out?
@paxperscientiam @angrykoala Can you provide some test cases so we can test and fix this?
@PopGoesTheWza has written most of this library, so you may want to check up on the case with him/her.
export default const myFoo="foo";
my_foo.ts
import myFoo from './my_foo.ts';
console.log(myFoo)
main.ts
This should transpile to valid Appscript, preferably by overriding export/import (a lรก browserify) but at the very least should completely ignore export and import:
var myFoo="foo";
my_foo.gs
console.log(myFoo)
main.gs
I hope these examples help
@angrykoala please specify your build/publish workflow. Currently ts2gas
is not designed to transpile more than one source .ts
file at a time.
With my (little and very personal) use of clasp
/ts2gas
, I hardly had to mess with export
and `import:
export
in the source code of GAS libraries (with its .d.ts
).d.ts
Unless @grant decides a different design approach, in which import should be handled (I am not sure yet of a satisfying option...)
the problem I have is that export is being transpiled instead of ignored, so I need to manually remove the generated code. Simply ignoring it (like it happens with import) would be enough and will work on transpiling one file at a time
@paxperscientiam please bear with my slowmindedness...
Checking at the result of ts2gas
test suite:
v--TS--v
// next statement will be ignored
export { foo, bar }
from "file";
// next statement will be preserved
/** Supported languages for localized data */
export enum Languages {
/** English (USA) */
eng_us = 'eng_us',
/** French (France) */
fre_fr = 'fre_fr',
}
export class Client {
/** URL to the login endpoint */
private readonly signinUrl: string;
}
export function foo() {}
export default Client;
export
{ ZipCodeValidator };
// now resume with next statement
โโโ
// Compiled using ts2gas 1.6.0 (TypeScript 3.3.3333)
var exports = exports || {};
var module = module || { exports: exports };
//export { foo, bar }\n from "file";
// next statement will be preserved
/** Supported languages for localized data */
var Languages;
(function (Languages) {
/** English (USA) */
Languages["eng_us"] = "eng_us";
/** French (France) */
Languages["fre_fr"] = "fre_fr";
})(Languages = Languages || (Languages = {}));
var Client = /** @class */ (function () {
function Client() {
}
return Client;
}());
exports.Client = Client;
function foo() { }
exports.foo = foo;
// now resume with next statement
^--GS--^
Most export
statements are removed or ignored during transpiling. Classes, functions and variables end up producing the following:
exports.Client = Client;
exports.foo = foo;
Are those the lines you wish would not be produced? What is the impact of those lines being in your gas?
incidentally, I now wonder why export enum Languages ...
produces an incorrect (Languages = Languages || (Languages = {}))
The correct resulting javascript should read (Languages = exports.Languages || (exports.Languages = {}))
@grant I'll investigate this sometime this week.
@PopGoesTheWza Yep, those are the things that I would like to ignore (thanks, sorry for not explaining myself, it was quite some time ago since I had this problem), unless import actually access the "export" global variable" it is impossible for the code to reach these variables
thanks
@angrykoala I failed to see the use of export
(for classes, functions, enums and variables) unless when producing a gas library. Does this match your use case?
If you are solely producing "alongside .gs
scripts", they all exist in the same global namespace:
Code.gs
foo()
lib.gs
function foo() {
return 'zug zug';
}
Is there something obvious I am overlooking?
lets assume 2 files in ts:
export default class Foo{ }
import Foo from 'foo'
new Foo()
It will transpile to:
exports.Foo=class Foo ....
new Foo()
As you can see, the Foo in the second file won't be found because Foo is not in the global scope, but in the object exports.
@angrykoala are you using clasp
to publish your two files? If you have src1.ts and src2.ts, doesn't it ends up as src1.gs and src2.gs in your gas document?
Yes, that's correct,but the class Foo won't be found in src2.gs because it is not declared in the global scope of src1.gs, but as part of the exports object
@angrykoala odd... for this does not match my (limited) experience. Maybe it is related to my project settings, but when pushing src1.ts and src2.ts, I get in my gas project src1.gs and src2.gs which share the same global namespace... hence src1.gs call functions from src2.gs as if they were alongside in a single file.
To illustrate, you may give a look at https://github.com/PopGoesTheWza/swgoh-tb-sheets which has some limited import
to access a gas library (using lib/index.d.ts
for type definitions) and all other scripts sharing the same global namespace (hence no need for import
/export
@grant could you share some of your wisdow here?
As I see, you are defining namespaces instead of directly exporting a class from the fule, maybe that's the reason the namespace is available across multiple files?
@angrykoala I added namespaces later on, in order to organise sets of function of classes in a semantically meaningful... manner. It also serves the purpose of disallowing direct use from Google Sheets formula, but this is another topic. Also note that the same namespace definition can appear in several files, hence src1.ts and src.2.ts can both add stuff to a single namespace. Still, any function (or any kind of statement in fact) starts in the same global namespace (which is in fact key for defining Google Sheets custom functions.
If I define 2 classes in separate files, without export nor import everything works fine (are global variables), the problem comes when I try to use export/import (mainly for testing purposes using ts) that export is being transpiled into export.Foo
While export is indeed a global variable, Foo is not, is a parameter of the global object export, so calling Foo directly from a separate file won't work. At least, that was what is happening to me when exporting classes using clasp
@angrykoala Probably the confusion is that, in the context of node or browser environnemment, import
/export
not only define a namespace to hold statements, but mostly the dynamic for "loading" resource.
With the current gas engine, all .gs
files exist in the very same global namespace (gas library being sort of an exception and instantiated in their own namespace) and thus import
doesn't make sense.
So, if we limit ourself to "regular" .ts files, namespace
is probably the best option to organise your code.
@angrykoala here is the result of transpiling your test case:
First export
export default class Foo {}
The Foo class is defined within a closure which is available as Foo
from the global namespace
// Compiled using ts2gas 1.6.2 (TypeScript 3.3.3333)
var exports = exports || {};
var module = module || { exports: exports };
var Foo = /** @class */ (function () {
function Foo() {
}
return Foo;
}());
Then import
import Foo from 'foo'
new Foo()
The import
statement is commented out. the Foo class is accessed directly from the global namespace
// Compiled using ts2gas 1.6.2 (TypeScript 3.3.3333)
var exports = exports || {};
var module = module || { exports: exports };
//import Foo from 'foo'
new Foo();
Please comment.
Hi @PopGoesTheWza Sorry for taking too long, I've been busy and It's been a long time since I've been working on the appscript project. I think I found the case where I'm still having problems, if you don't mind I'll copy part of the original source code It's troubling me. This problem was found using clasp and ts2gas 1.6.0
Typescript original snippet
import { ICONS } from "../../constants";
// SOME CODE
doSomething(ICONS.email);
....
}
_thread_summarysection.ts
export const CACHE_ENABLED = true;
export const ICONS = {
email: `foo.png`,
};
constants.ts
After compiling and executing I get the following error:
ReferenceError: "ICONS" is not defined. [line: 28, function: ThreadSummarySection]
Compiled GS
//import { ICONS } from "../../constants";
doSomething(ICONS.email);
thread_summary_section.gs
// Compiled using ts2gas 1.6.0 (TypeScript 3.2.4)
var exports = exports || {};
var module = module || { exports: exports };
exports.CACHE_ENABLED = true;
exports.ICONS = {
email: "foo.png",
};
constants.gs
Maybe this case is only happening when trying to export without default?
I hope this helps
@grant do you have an opinion about if/how such export/import should be addressed?
(I personally use namespace
instead)
@PopGoesTheWza as @grant already mentioned there are Webpack setup for Google Apps Script. And it's a much better solution as it completely solve the issue with the module by preserving the private environment of a module.
Right now if I want to write TS code that works when compiled to gas (with CLASP), my editor just display errors everywhere because of no import / exports... Not really usefull :/
@JeanRemiDelteil I am dubious about adding webpack to my build process. I manage proper code isolation across many source files by using namespace
with no problem:
namespace Package {
export function foo() {}
}
Package.foo();
const nameIWantForMyImports = Package.foo;
foo();
Namespaces can be defined across multiple files (i.e. the "one file per class" way.) They also can be nested (i.e. Module.Submodule.Class)
One very ugly and kludgy solution, if one has to use export
/import
import { ICONS } from './package';
// BEGIN: typescript kludge
type __ICONS = typeof ICONS; // sad but I couldn't avoid adding this type definition
declare namespace exports { // because this is the namespace where transpiled exports go
const ICONS: __ICONS; // Yeah! now TypeScript is aware of my transpiled exports
}
exports.ICONS.email; // this code is valid in both TypeScript and GAS
@PopGoesTheWza I don't know how this should be addressed. Please suggest a solution.
@grant @angrykoala @JeanRemiDelteil
With ts2gas currently build around ts.transpileModule
, there is no hope in having it to modify the output script in a satisfying way.
Going for another approach (TypeScript compiler service if I remember correctly) would be great but is a task I can barely fathom at the moment.
So if we look at we have now:
ts2gas
will not support it: ""shoo shoo go awayexports
namespace declaration (though it is ugly and kludgy, but functional)namespace
statements (my personal favorite for all that it's worth)To hint at what I would suggest, I opened a @google/clasp
PR with edits to the TypeScript.md
N.B. I am only a small contributor, with no intent to step up for involvement (enough on my plate thanks you ;) ) so I let you all decide.
My current workaround (which falls a bit into the "third party packager") but is less ugly than the one mentioned above (imo) it to remove with a regex all the export and export default statements from the ts files before compiling. It is currently working for me using clasp. I would like to have that as a embedded option either in clasp or here, but I understand it may fall out of the scope of both projects and it is quite hacky. Just in case this is useful for anyone with the same problem:
https://gist.github.com/angrykoala/f5456c1f7a00d286b155e67774948855
In order to make this work, just add bin (or the target folder) to the gitignore and set as the compilation target (if using clasp, adding !bin/** to the .claspignore does the trick) and execute it before ts2gs or clasp.
My package.json scripts using clasp (preprocessing.js is the file in the gist above):
"scripts": {
"test": "nyc mocha -r ts-node/register tests/*.test.ts tests/*/*.test.ts",
"deploy": "npm run test && node tasks/preprocessing.js && clasp push",
},
Under @google/clasp
is now documented possible workaround: https://github.com/google/clasp/blob/master/docs/typescript.md
@angrykoala @paxperscientiam could you please comment latest updates and state if your issue has been properly addressed?
I haven't had time to test the namespace workaround @PopGoesTheWza
@PopGoesTheWza I tested the namespace workaround. Even if it feels like a hack, it works.
However I had to export/import the namespace because my editor was complaining that it could not find it otherwise:
I would change the demo like following:
// module.ts
export namespace Module {
export function foo() {}
function bar() {} // this function can only be addressed from within the `Module` namespace
}
import {Module} from "./module.ts"
// anyFiles.ts
Module.foo(); // address a namespace's exported content directly
const nameIWantForMyImports = Module.foo; // to simulate `import` with renaming
nameIWantForMyImports();
@JeanRemiDelteil may I ask about your editor and settings? The namespace
workaround should not required that you export the namespace
less that you import it.
Could the errors in your editor be related with your tsconfig.json
or something?
I'm using Webstorm, and for my test project I don't have a tsconfig.json
as the editor has TS working by default (it's running the v3.3.3333 of TS)
If it's just a configuration issue, that should not matter.
Even with the config file recommended in the clasp/ts doc it gives this error:
TS2304: cannot find name 'Module'
when I don't export/import
@JeanRemiDelteil could you please check with this minimal project?
https://github.com/PopGoesTheWza/gas-ts-namespace
and its associated gas project
The only issue I could experiencing is hoisting (using a namespace/module resource before it gets defined)
PS: totally off topic, but you have a very french sounding name
@PopGoesTheWza I found why I need to import: In your minimal project, add a sub/ folder in ./src/ add a new .ts file, and write a function that user Module.hello();
For me it worked correctly until I use files in another folder.
@JeanRemiDelteil I updated gas-ts-namespace
with a module in a subfolder.
For some unknown reason, typescript+vsc was initially confused, but after closing and reopening the project everything was fine in the editor.
Note: for simplicity, this project has matching namespace and file name (i.e. Module
in Module.ts) but they are totally unrelated and do not need to match.
Request For Comments:
I have put up a repository which illustrate and document what I believe is the best alternative to the lack of support for module. https://github.com/PopGoesTheWza/ts-gas-project-starter
I am very interested in your comments, critics and feedback
As of today, import
seems to be mostly working with clasp
and it claims to use
// Compiled using ts2gas 3.6.2 (TypeScript 3.9.6)
Heartfelt thanks for this ๐ !
However, when I include Moment by adding moment.js and moment.d.ts to lib
and import it to any module in src
with
import moment from '../lib/moment.js'
then I get the following error in Google Sheets:
ReferenceError: moment is not defined
at [unknown function](lib/moment.d:5:18)
lib/moment.d.gs
looks as follows after going through ts2gas
:
// Compiled using ts2gas 3.6.2 (TypeScript 3.9.6)
var exports = exports || {};
var module = module || { exports: exports };
"use strict";
module.exports = moment;
//# sourceMappingURL=module.js.map
lib/moment.gs
itself remains more or less pristine moment.js after going through ts2gas
and it handles module export with
;(function(global, factory) {
typeof exports === 'object' && typeof module !== 'undefined'
? (module.exports = factory())
: typeof define === 'function' && define.amd
? define(factory)
: (global.moment = factory())
})(this, function() { ...
So it seems global.moment = factory()
in moment.gs
does not actually create the global moment
symbol and therefore module.exports = moment
fails in moment.d.gs
.
What is the right way to work around this error?
Hey @mrts , i haven't used this google stuff in a bit, but I've run into something similar. You might have luck by explicitly attaching moment to globalThis
@mrts check the clasp
repos. Some docs was added recently which involved moment.js
if I remember correctly
Thanks, I was able to get rid of moment.js meanwhile, so all is well at the moment.
Just in case someone is trying to avoid the problematic syntax.
// .eslintrc.js
module.exports = {
// ...
rules: {
// ...
'no-restricted-syntax': [
'error',
{
selector: 'ExportNamedDeclaration > VariableDeclaration > VariableDeclarator:not([init.type=ArrowFunctionExpression])',
message: 'Don\'t use exported variable declarations unless it\'s a function (https://github.com/grant/ts2gas/issues/26)',
},
],
}
}
`
As of today, import seems to be mostly working with clasp and it claims to use
// Compiled using ts2gas 3.6.2 (TypeScript 3.9.6)
Something changed recently and broke the quite well-functioning import system. I hadn't used clasp
for a while and when I installed clasp
into my new computer, cloned my script repository, made a minor change to a TypeScript file and did a clasp push
, then opened the Google Spreadsheet that uses the scripts, I suddenly started getting ReferenceError: MyImportedModule is not defined
errors.
Also, for some reason the ts2gas
version is no longer reported in the script header, the script name is used instead (incorrectly I presume, this looks like a bug to me):
// Compiled using myscriptname 0.1.0 (TypeScript 4.5.2)
I had to use npm list
to figure out the versions:
$ npm list -g ts2gas
/usr/lib
โโโฌ @google/clasp@2.4.1
โโโ ts2gas@4.2.0
@PopGoesTheWza, any ideas what might have caused this?
@mrts I have the same issue, have you found the reason by any chance?
@DarekDarecki, no, I retreated and removed the nice import system... built a poor man's Webpack instead:
cd src && cat *.ts > Bundle.ts
.Bundle.ts
..claspignore
so that only appsscript.json
and src/Bundle.ts
are included.clasp push
.What a great loss!
@mrts have you tried using namespaces?
To clarify, either go bundling (webpack and all that) or go namespace (like https://github.com/PopGoesTheWza/ts-gas-project-starter)
All else is bound to fail
I'm glad to know I'm not the only one who is experiencing this issue. Almost 3000 lines of apps script code is suddenly broken by ts2gas and I've been pulling my hair out trying to figure out why. Any reason why this broke all of a sudden? How can we revert @PopGoesTheWza ?
Functions from imported files that used to be transpiled like this stringifyData(travelData.travelMap, "travelData");
all of a sudden transpile like this: (0, Util_1.stringifyData)(travelData.travelMap, "travelData");
. and obviously this completely breaks any function that is used from another class
Ok. I think I now do see your issue. Updating clasp/ts2gas/typescript changed your transpiled code in a no longer functioning state.
First, I should restate here: Google App Script do NOT support modules. Never use import/export (except for namespaces)
ts2gas handling of import/export of module is in fact extremely limited, deceitful and a bad idea to start with.
Now the no easy way to possibly restore your project should be to downgrade clasp/ts2gas/typescript to previous versions.
Any other solution will likely require some amount of refactoring in order to either rely on some bundler or use namespaces.
There's a note in the readme indicating that
noLib
is fixed at false. Does that meants2gas
does not currently handle imports? Like, I'm trying to import a module, but nothing seems to happen.Thanks.