im about to write a complex Incoming WebHook for Rocket.Chat. To avoid a mess in one single file i took Typescript. Rocket.Chat requires a class named Script with some predefined methods like process_incoming_request (one simple example: https://rocket.chat/docs/administrator-guides/integrations/).
my current project setup looks like:
tsconfig.ts
{
"files": [
"src/main.ts"
],
"compilerOptions": {
"noImplicitAny": true,
"target": "es2015"
}
}
gulpfile.js
var gulp = require("gulp");
var browserify = require("browserify");
var source = require("vinyl-source-stream");
var tsify = require("tsify");
var uglify = require("gulp-uglify");
var buffer = require("vinyl-buffer");
gulp.task(
"default",
function () {
return browserify({
basedir: ".",
debug: true,
entries: ["src/main.ts"],
cache: {},
packageCache: {}
})
.plugin(tsify)
.transform("babelify", {
presets: ["es2015"],
extensions: [".ts"]
})
.bundle()
.pipe(source("bundle.js"))
.pipe(buffer())
.pipe(uglify())
.pipe(gulp.dest("dist"));
}
);
main.ts
import {RequestInterface} from "./Interface/RequestInterface";
class Script {
process_incoming_request(request: RequestInterface) {
// some code
}
}
The yarn gulp process runs smoothly without errors but when using the generated code inside the script part of the webhook it results in an error:
Incoming WebHook.error script.js:1
ReferenceError: module is not defined
at script.js:1:4307
at Script.runInContext (vm.js:127:20)
at Script.runInNewContext (vm.js:133:17)
at getIntegrationScript (app/integrations/server/api/api.js:70:12)
at Object.executeIntegrationRest (app/integrations/server/api/api.js:166:13)
at app/api/server/api.js:343:82
at Meteor.EnvironmentVariable.EVp.withValue (packages/meteor.js:1234:12)
at Object._internalRouteActionHandler [as action] (app/api/server/api.js:343:39)
at Route.share.Route.Route._callEndpoint (packages/nimble_restivus/lib/route.coffee:150:32)
at packages/nimble_restivus/lib/route.coffee:59:33
Im not that familiar with Typescript, Node and all the stuff. So the main question is, how can i achive that the process generates a class (or a script which exposes a class) named Script with the method process_incoming_request. Im also not sure if my script generates the error or the RocketChat part.
Thanks!
I guess the problem is that Gulp (or some of it's plugins) generates a scaffolding code, necessary for JS's (non-existent) module system, and it often implies wrapping the compiler output into weird multi-layered anonymous functions.
If you don't need any kind of module system and just want your multiple TS files translated directly to a single JS file (which supposedly goes to the RocketChat), then I'd suggest ditching Gulp altogether, letting TSC to compile your code as usual, then bundling the resulting .js files into a single one with a script.
So, the overall setup would be as follows (assuming src is a source code folder):
tsconfig.json
{
"include": [
"src/**/*.ts"
],
"compilerOptions": {
"noImplicitAny": true,
"target": "es2016"
}
}
build.sh
#!/bin/bash
# TSC puts compiled JS files along with their TS sources.
node_modules/typescript/bin/tsc
# Creating an empty bundle file.
echo "">dist/app.js
# Bundling all the JS together.
# sed removes the 'export' keywords & 'import' statements.
while read p; do
cat $p | sed -E "s/^export\s+(class|function|async|const|var)/\1/" | sed -E "s/import.*$//" >> dist/app.js
done <<< $(find src -type f -name "*.js")
So you program your thing as usual, build it with ./build.sh, get the dist/app.js file and use it in RocketChat.
There must be a way to do something along these lines in Gulp, but I'm not familiar with it, and don't think a full-blown build system is really needed here.
Related
Project setup:
Vuejs 3
Webpack 4
Babel
TS
We created the project using vue-cli and add the dependency to the library.
We then imported a project (Vue Currency Input v2.0.0) that uses optional chaining. But we get the following error while executing the serve script:
error in ./node_modules/vue-currency-input/dist/index.esm.js
Module parse failed: Unexpected token (265:36)
You may need an appropriate loader to handle this file type, currently no loaders are configured to process this file. See https://webpack.js.org/concepts#loaders
| getMinValue() {
| let min = this.toFloat(-Number.MAX_SAFE_INTEGER);
> if (this.options.valueRange?.min !== undefined) {
| min = Math.max(this.options.valueRange?.min, this.toFloat(-Number.MAX_SAFE_INTEGER));
| }
I read that Webpack 4 doesn't support optional chaining by default. So, we added the Babel plugin for optional chaining. This is our babel.config.js file:
module.exports = {
presets: ["#vue/cli-plugin-babel/preset"],
plugins: ["#babel/plugin-proposal-optional-chaining"],
};
(But, if I am correct, this plugin is now enable by default in the babel-preset. So this modification might be useless ^^)
One thing that I don't understand is that we can use optional chaining in the .vue files.
I created a SandBox with all the files: SandBox
How could I solve this error?
I was able to overcome this issue using #babel/plugin-proposal-optional-chaining, but for me the only way I could get Webpack to use the Babel plugin was to shove the babel-loader configuration through the Webpack options in vue.config.js. Here is a minimal vue.config.js:
const path = require('path');
module.exports = {
chainWebpack: config => {
config.module
.rule('supportChaining')
.test(/\.js$/)
.include
.add(path.resolve('node_modules/PROBLEM_MODULE'))
.end()
.use('babel-loader')
.loader('babel-loader')
.tap(options => ({ ...options,
plugins : ['#babel/plugin-proposal-optional-chaining']
}))
.end()
}
};
NB replace "PROBLEM_MODULE" in the above with the module where you have the problem.
Surprisingly I did not need to install #babel/plugin-proposal-optional-chaining with NPM. I did a go/no-go test with an app scaffolded with #vue/cli 4.5.13, in my case without typescript. I imported the NPM module that has been causing my grief (#vime/vue-next 5.0.31 BTW), ran the serve script and got the Unexpected token error on a line containing optional chaining. I then plunked the above vue.config.js into the project root and ran the serve script again, this time with no errors.
My point is it appears this problem can be addressed without polluting one's development environment very much.
The Vue forums are in denial about this problem, claiming Vue 3 supports optional chaining. Apparently not, however, in node modules. A post in this thread by atflick on 2/26/2021 was a big help.
Had same issue with Vue 2 without typescript.
To fix this you need to force babel preset to include optional chaining rule:
presets: [
[
'#vue/cli-plugin-babel/preset',
{
include: ['#babel/plugin-proposal-optional-chaining'],
},
],
],
Can also be achieved by setting old browser target in browserslist config.
Most importantly, you need to add your failing module to transpileDependencies in vue.config.js:
module.exports = {
...
transpileDependencies: ['vue-currency-input],
}
This is required, because babel by default will exclude all node_modules from transpilation (mentioned in vue cli docs), thus no configured plugins will be applied.
I had a similar problem. I'm using nuxt but my .babelrc file looks like the below, and got it working for me.
{
"presets": [
["#babel/preset-env"]
],
"plugins":[
["#babel/plugin-transform-runtime",
{
"regenerator": true
}
]
],
"env": {
"test": {
"plugins": [
["transform-regenerator", {
"regenerator": true
}],
"#babel/plugin-transform-runtime"
],
"presets": [
["#babel/preset-env", {
"useBuiltIns": false
}]
]
}
}
}
I managed to fix the solution by adding these lines to package.json:
...
"scripts": {
"preinstall": "npx npm-force-resolutions",
...
},
"resolutions": {
"acorn": "8.0.1"
},
...
I use Jest with Typescript. I want to create a monorepo.
My folders structure is:
fe
|-app1
|--.jest.config.ts
|--tsconfig.json
|-shared
|--dummyData.ts
I want a unit test from app1 to access some data from shared folder.
fe/app1/demo.spec.ts:
import { someData } from '#shared/dummyData' //<--HERE: the alias is works, but jest can not understand "export" keyword
descrube('demo' () => {
it('demo test', () => {
expect(someData).toBe('DUMMY'));
})
})
fe/shared/dummyData.ts:
export const someData = 'DUMMY';
The problem is that jest throws an error:
Jest encountered an unexpected token
{"Object.<anonymous>":function(module,exports,require,__dirname,__filename,global,jest){export const someData = 'DUMMY';
^^^^^^
As I understand it cannot parse typescript or es6 modules that were produced by ts and babel.
The thing that it works fine while shared folder is inside app1, but once it's outside (and outside the root folder, i.g '<rootDir>/../shared/$1') it start throwing that error.
Here is my configs:
I described alias in fe/app1/tsconfig.json:
{
...
"compilerOptions": {
"target": "esnext",
"module": "esnext",
"paths": {
"#/*": ["app/src/*"],
"#shared/*": ["shared/*"]
}
}
}
And in fe/app1/.jest.config.ts:
module.exports = {
...
transform: {
'^.+\\.js$': '<rootDir>/node_modules/babel-jest',
'^.+\\.(jsx?|tsx?)$': 'ts-jest'
},
moduleNameMapper: {
'^#/(.*)$': '<rootDir>/src/$1',
'^#shared/(.*)$': '<rootDir>/../shared/$1',
}
}
Some thoughts:
As I understand I have to tell to jest to apply same parsers (babel, etc) for the code outside of the rootDir.
I guess it's possible to create a tsconfig in the root folder(fe), but I want to launch unit tests from fe/app1 folder...
Maybe it's possible to configure the things with such properties as projects, roots and etc, but I'm out of luck so far.
Okay, I found the solution:
For monorepo it is important to have babel config as a JS file
So, simply rename .babelrc to babel.config.js (in the folder you called jest from, in my case it is fe/app1)
For more info you could check this comment (and maybe this thread)
I'm trying to require the xml-js module (https://www.npmjs.com/package/xml-js) to do some XML parsing in my existing Electron + Typescript project.
What I'm slightly confused about is why it is throwing an error.
First of all, the tsconfig.json file is the following:
{
"compilerOptions": {
"target": "es5",
"module": "commonjs",
"moduleResolution": "node",
"sourceMap": true,
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"removeComments": false,
"noImplicitAny": false,
"outDir": "dist"
},
"exclude": [
"node_modules"
]
}
the relevant part of the systemjs config is here:
paths: {
'npm:': 'node_modules/'
},
map: {
'xml-js': 'npm:xml-js'
},
packages: {
app: {
main: './main.js',
defaultExtension: 'js'
},
rxjs: {
defaultExtension: 'js'
}
}
So the module is located in node_modules/xml-js (like many others).
When I build my application, I get the following error:
file://<path>/Documents/projectFolder/electron-angular2-rc6/ng2-electron/node_modules/xml-js Failed to load resource: net::ERR_FILE_NOT_FOUND
and :
index.html:18 Error: (SystemJS) XHR error loading file://<path>/Documents/projectFolder/electron-angular2-rc6/ng2-electron/node_modules/xml-js
I'm attempting to load the module through:
const parser = require("xml-js");
(so, as vanilla JS), in my typescript component (application.component.ts).
The relevant part of my index.html is:
<script src="systemjs.config.js"></script>
<script>
System.import('app').catch(function(err){ console.error(err); });
document.addEventListener('dragover',function(event){
event.preventDefault();
return false;
},false);
document.addEventListener('drop',function(event){
event.preventDefault();
return false;
},false);
</script>
The module has been installed through:
npm install xml-js --save-dev
And exists in the directory node_modules/xml-js
I have tons of other modules (including rxjs and angular-in-memory-web-api) along with the regular #angular ones: all of them are working properly and throwing no error, but the xml-js one seems to be not working whatsoever.
After a couple of hours of documentation and, being honest, disappointment, I've found out that you can't directly include node modules with systems, because systemjs cannot interprete them directly.
I had to change the whole library, and relied on this one:
https://www.npmjs.com/package/jxon
which says "A complete, bidirectional, JXON (lossless JavaScript XML Object Notation) library. Packed as UMD.". I've check what "UMD" means, which means "Universal Module Definition": https://github.com/umdjs/umd
So, you likely can use the UMD tools to wrap the modules in your application, else you can rely on existing ones available somewhere.
In my case, using jxon, I had to change my code a bit:
First of all, in package.json I've removed xml-js and added jxon
In my systemjs.config.js file I've added the module in the map list (along with the other ones): 'jxon':'npm:jxon'
Still in the systemjs.config.js file, I had to tell in the "packages" section that the defaultextension was JS and that the main file was jxon.min.js: packages: { jxon: { defaultExtension: 'js', main: 'jxon.min.js' } }
Last but not least, I could include it regularly in my typescript file with no errors, and could use the prototypes it offered to accomplish my goals (I've required it using "require", not "import", but it should work aswell with something like import * as jxonparser from 'jxon', though I've used: const jxon_parser = require('jxon');
So, in a very concise nutshell: you can't include node modules with systemjs unless they are either as UMD or that can be interpreted from systemjs. If you want to check the formats supported by systemjs, check this: https://github.com/systemjs/systemjs/blob/master/docs/module-formats.md
I've been steadily improving my JS workflow thanks to gulp. Now that I've got the basics of es6 and typescript down, I'd like to add it to my gulp workflow. I am using Visual Studio Code on a Mac and I am using a tsconfig.json rather than gulp-typescript options.
My gulp workflow should look a bit like:
take all ts files from src/ts
compile to es5
combine to a single file
use es6 imports/exports pull functions/objects from one module into another.
The goal is to have a single closure wrapped module (revealing module pattern) and all of my classes/functions inside of that module.
My current setup (relevant lines):
var gulp = require('gulp'),
plumber = require('gulp-plumber'),
ts = require('gulp-typescript'),
tsProject = ts.createProject('tsconfig.json');
gulp.task('ts', function() {
var tsResult = tsProject.src()
.pipe(plumber())
.pipe(ts(tsProject));
return tsResult.js.pipe(gulp.dest('./'));
});
tsconfig:
{
"compilerOptions": {
"target": "es5",
"outFile": "build/js/app.js",
"module": "es6",
"noImplicitAny": true,
"removeComments": true
},
"files": [
"src/ts/file1.ts",
"src/ts/file2.ts"
]
}
I created two simple test ts files:
file1:
import {someFunction} from "ajax";
someFunction("Testing");
file2:
export function someFunction(message: string) {
let test = `Hello World ${message}`;
console.log(test);
}
So first issue: it produces an app.js in the build/js folder, but it is empty unless I comment out the import statement. Am I misunderstanding how es6 imports work or how maybe typescript works?
Second issue: it creates a new directory structure at root that is basically: Users/username/Documents/JS/Project/ts/files. These files are partially transpiled to es5, retaining the export and import keywords.
I really like typescript and es6 and want to add it to my workflow but so far no online resources have helped me get this build working.
UPDATE:
I just realized I was trying to compile directly to ES5 using es6 modules. Since that won't work, would it be easier to compile to es5 with commonjs modules and then use something like browserify to combine the code?
If I change my tsconfig to:
{
"compilerOptions": {
"target": "es5",
"outFile": "build/js/app.js",
"module": "commonjs",
"noImplicitAny": true,
"removeComments": true
},
"files": [
"src/ts/file1.ts",
"src/ts/file2.ts"
]
}
That weird full path directory contains the correctly transpiled code but the app.js file is still blank.
UPDATE 2:
I got the weird added directory problem solved by adding "isolatedModules": false to my compilerOptions. I changed the module to commonjs as I've decided to use browserify to handle combining modules.
I changed the the gulp.dest to build/js and removed outFile. This has gotten me to the point where I get the output in the correct directory but it adds it to a sub-folder. Basically I want it to output: build/js/files but it outputs build/js/src/ts/files.
UPDATE 3:
I just wanted to add something to the above for anyone else who runs into this problem. The reason I had issues with the modules, is because I was using outFile. outFile does not allow external modules and will output an empty file.
I have a Typescript project:
myproject
|
+-src (folder)
| |
| +-main.ts
| +-stringHandler.ts
| +-disposable.ts
+-out (folder)
| |
| +-...
+-Gruntfile.js
In my Grunt configuration I have a 2-step task which compiles all .ts files in myproject/src/ and generates corresponding .js files into myproject/out/. So after the first step of the task is complete, I have the following:
myproject
|
+-out (folder)
|
+-main.js
+-stringHandler.js
+-disposable.js
Bundling
The second step of the task is generating bundle file myproject.js. I am using RequireJS for this purpose.
I have installed grunt-contrib-requirejs. The Gruntfile.js file handling the bundling task is as follows (showing only relevant parts in the file):
module.exports = function(grunt) {
var config = {
pkg: grunt.file.readJSON('package.json'),
requirejs: {
compile: {
options: {
baseUrl: "out",
bundles: {
'myproject': ['main', 'stringHandler', 'disposable']
},
out: 'out/myproject.js'
}
}
}
};
grunt.initConfig(config);
grunt.loadNpmTasks('grunt-contrib-requirejs');
grunt.registerTask('default', ['compile', 'requirejs']);
};
When Grunt reaches requirejs, after successfully compiling the project, I get the following error:
Running "requirejs:compile" (requirejs) task { [Error: Error: Missing
either a "name", "include" or "modules" option
at Function.build.createConfig (C:\Users\myuser\Documents\myproject\node_modules\grunt-contrib-requirejs\node_modules\requirejs\bin\r.js:29567:19)
] originalError: [Error: Missing either a "name", "include" or
"modules" option] }
I can understand there are missing parameters, but when I use name I get other errors. I guess there must be something wrong at a more generic level. What is the correct configuration format? Thanks
This assumes main.ts is your application's entry point and that it contains a require.config section with your application dependencies (libraries and shims).
First, move the require.config section out of main.ts and into its own file, config.ts. Leave the application bootstrap code in main.ts.
Then determine where you want this optimized application code deployed. Let's assume it is to a directory named build, which is parallel to your src and out folders.
Update you Gruntfile to reflect this configuration:
requirejs: {
compile: {
options: {
baseUrl: "out",
mainConfigFile: "out/config.js",
modules: [
{ name: "main" }
],
dir: "build",
optimize: "none" // skip compression while debugging
}
}
}
You can read more about each of these config options at http://requirejs.org/ but here's the basic rundown:
baseUrl: Where the source JS code lives.
mainConfigFile: Points to the config object mentioned above. It tells the plugin where the dependencies live. This obviates the need to specify and manually update the list of dependencies in two places.
modules: Is an array of application bootstraps. In this case a list of one, main.js.
dir: Where the optimized application will be generated. Note that your dependencies will also be copied here.
optimize: I left this off so you can easily debug the resulting app under ./build. Remove it when you're happy and the plugin will optimize (compress and munge) your build files.