I am writing a script in TypeScript, and when I use browserify, to convert it, my master variable is not found in the browser.
main.ts
import { GameSmart } from './GameSmart';
const gamesmart = new GameSmart();
gulpfile.js
/* require's snipped */
gulp.task('build', function () {
return browserify()
.add('./src/gamesmart/main.ts')
.plugin(tsify)
.bundle()
.on('error', function (error) {
console.error(error);
throw error;
})
.pipe(source('gamesmart.js'))
.pipe(gulp.dest('build/'));
});
In my page I am including the newly created file, and attempting to call the gamesmart variable, but it is not found.
<script scr="/path/to/sdk.js">
<script>
gamesmart.game.started();
</script>
How can I make it as a global/root variable?
I was able to fix this by adding the variable to the window.
window['gamesmart'] = new GameSmart();
Related
I am struggeling with the current situation. Up to today i had my own JavaScript implementation with own methods to load single js files per website - when needed. This way i had a tiny loader script with an scp sha256-checksum in the header which then loaded the "master" script which controlled everything else.
Now i want to go some steps further and a) switch over to modules, because most libs do so too, i was unable to load modules from normal scripts and b) implement typescript into my system.
What starts my script implementation:
/scripts/modules-loader.js
<html>
...
<script async type=module>
"use strict";
import {master} from '/scripts/modules/master-28021723540.js';
new master();
</script>
...
</html>
So far so good. This loader executes as expected, the js file is been loaded and the sha-256 hash from the header is valid.
And now where the struggle begins. This file is been compiled to
/master.ts
export class master {
constructor() {
this.init();
}
async init() {
console.log( "Hello, lets get started...");
}
}
compiled file:
define("wkwussmxlyk4d7ainbsq93pnpnugjs5g", ["require", "exports"], function (require, exports) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.master = void 0;
class master {
constructor() {
this.init();
}
async init() {
console.log("Hello, lets get started...");
}
}
exports.master = master;
});
with the result that the browser cannot find the module called "master"
Uncaught SyntaxError: The requested module '/scripts/modules/master-28021723540.js'
does not provide an export named 'master' (at (Index):11:9)
However i also tried this, which i found in the internet:
"use strict";
module master {
class master {
constructor() {
this.init();
}
async init() {
console.log( "Hello, lets get started...");
}
}
}
which gets compiled into this
"use strict";
var master;
(function (master_1) {
class master {
constructor() {
this.init();
}
async init() {
console.log("Hello, lets get started...");
}
}
})(master || (master = {}));
which leads to the same result.
How can i get my class get compiled from ts to be able to call it from the loader or in the file itself?
Just for understanding, i dont want to use the "tsc --watch" over my entire project. I am using single js/ts files and compile/optimize them in the request thread and put them into static optimized files afterwards ( if in production ). Keeping this in mind i am also open into different implementations. I am aiming for maximum pagespeed without compromises and my current solution was working very good but just limited because it was unable to load modules, so now i want to switch over to modules only.
i am compiling like this
/usr/bin/tsc --alwaysStrict --strict --module amd --target es2020 /srv/http/domain.local/tmp/domain.local/ncumh5cnk5uq2s7b64oa31cv19agr2s9.ts --outfile /srv/http/domain.local/tmp/domain.local/compiled-ncumh5cnk5uq2s7b64oa31cv19agr2s9.ts
Thanks in advance
Response on answer from #Quentin
/usr/bin/tsc --alwaysStrict --strict --target es6 /srv/http/domain.local/tmp/domain.local/ncumh5cnk5uq2s7b64oa31cv19agr2s9.ts --outfile /srv/http/domain.local/tmp/domain.local/compiled-ncumh5cnk5uq2s7b64oa31cv19agr2s9.ts
i am getting this compilation
"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
class master {
constructor() {
this.init();
}
init() {
return __awaiter(this, void 0, void 0, function* () {
console.log("Hello, lets get started...");
});
}
}
and with
export class()
i am getting this:
Cannot compile modules using option 'outFile' unless the '--module' flag is 'amd' or 'system'.
--module amd
This is your problem.
AMD modules use the define syntax and do no have native support in browsers. To use them you’ll need to use something like Require.JS.
Instead, tell TypeScript to generate an es6 module.
I have written some function in commands.js file for cypress automation testing, out of which I am able to invoke only one i.e."login" but unable to invoke other functions form another .js file. Cypress Test Runner showing
"TypeError: cy.FillAddCaseDetails is not a function"
describe('Adding a Case on CSS Poratal ', function() {
before(function () {
cy.login() // calling login function successfully
})
it('open add case',function(){
cy.wait(9000)
cy.hash().should('contains','#/home')
cy.wait(['#GETcontentLoad']);
cy.wait(['#POSTcontentLoad']);
cy.get('[uib-tooltip="Add Case"]').click({force:true})
cy.log('clicked on Add case')
cy.wait(3000)
cy.get('[ng-click="lookup.cancel()"]').click({force: true})
cy.get('[ng-click="lookup.closeAddCase()"]').click({force: true})
cy.get('[uib-tooltip="Add Case"]').click({force:true})
cy.wait(3000)
cy.get('[ng-model="lookup.selectedPartner"]',{force:true})
.type(AddJob.JobData.Partner,{force: true})
cy.xpath('//input[#ng-model="lookup.selectedPartner"]')
.should('be.visible').then(() => {
cy.FillAddCaseDetails() // unable to call
cy.FillCustomerDetails() // unable to call
})
Function:
Cypress.Commands.add("FillCustomerDetails", () => {
cy.get('[ng-model="lookup.firstName"]')
.type(AddJob.JobData.FirstName, { force: true})
cy.get('[ng-model="lookup.lastName"]')
.type(AddJob.JobData.LastName, { force: true })
cy.get('[ng-model="lookup.customerPhone"]')
.type(AddJob.JobData.CustomerPhone, { force: true })
cy.get('[value="NEXT"]').click({ force: true })
})
expected : function will get called
actual : TypeError: cy.FillAddCaseDetails is not a function
This is the top result for this error so I would like to add what I did to fix it. This is relevant to version >=10 and typescript. The problem ended up being that the supportFile setting in cypress.config.ts was set to false; I changed my config to this:
import cypress, { defineConfig } from 'cypress'
export default defineConfig({
e2e: {
'baseUrl': 'http://localhost:4200',
supportFile: 'cypress/support/e2e.ts'
},
})
I created the custom commands in commands.ts
declare namespace Cypress {
interface Chainable<Subject = any> {
/**
* Custom command to select DOM element by data-cy attribute.
* #example cy.dataCy('greeting')
*/
clearIndexedDB(): Promise<void>
}
}
Cypress.Commands.add('clearIndexedDB', async () => {
const databases = await window.indexedDB.databases();
await Promise.all(
databases.map(
({ name }) => {
if (!name) return
return new Promise((resolve, reject) => {
const request = window.indexedDB.deleteDatabase(name);
request.addEventListener('success', resolve);
request.addEventListener('blocked', resolve);
request.addEventListener('error', reject);
})
},
),
);
});
Then I uncommented this line in my e2e.ts file
import './commands';
In my case solution was a restart of the cypress test runner.
If you added your Custom Command to support/commands.js file, You need to import that file from support/index.js file. Create support/index.js, if it's not available and add the line import "./commands.js" to it.
From the Cypress docs: https://on.cypress.io/typescript#Types-for-custom-commands
if you add the command cy.dataCy into your supportFile like this:
// cypress/support/index.js
Cypress.Commands.add('dataCy', (value) => {
return cy.get(`[data-cy=${value}]`)
})
Then you can add the dataCy command to the global Cypress Chainable interface (so called because commands are chained together) by creating a new TypeScript definitions file beside your supportFile, in this case at cypress/support/index.d.ts.
// in cypress/support/index.d.ts
// load type definitions that come with Cypress module
/// <reference types="cypress" />
declare namespace Cypress {
interface Chainable {
/**
* Custom command to select DOM element by data-cy attribute.
* #example cy.dataCy('greeting')
*/
dataCy(value: string): Chainable<Element>
}
}
cy.xpath("//div[#class='c-navigatorItem-faceplate ng-scope ng-isolate-scope']").click();
Is it a valid to use because I am getting the TypeError cy.xpath is not a function
I'm refactoring my gulpfile now I'm using gulp v4 and am having an issue with gulp watch not running my stylesCompileIncremental function. Any help or pointers would be much appreciated.
My refactoring includes:
Switching to using functions instead of gulp.task
Using series and parallel as per the docs
Exporting public tasks at the bottom of my gulpfile ie exports.stylesWatch = stylesWatch;
Adding callbacks in functions to tell Gulp the function is complete
The code for the affected tasks is as follows (directory paths are stored in package.json file hence pathConfig.ui... values):
// Compile only particular Sass file that has import of changed file
function stylesCompileIncremental(cb) {
sassCompile({
source: getResultedFilesList(changedFilePath),
dest: pathConfig.ui.core.sass.dest,
alsoSearchIn: [pathConfig.ui.lib.resources]
});
cb();
}
// Compile all Sass files and watch for changes
function stylesWatch(cb) {
createImportsGraph();
var watcher = gulp.watch(pathConfig.ui.core.sass.src + '**/*.scss', gulp.parallel(devServReloadStyles));
watcher.on('change', function(event) {
changedFilePath = event;
});
cb();
}
// reload css separated into own function. No callback needed as returning event stream
function reloadCss() {
return gulp.src(generateFilePath)
.pipe($.connect.reload()); // css only reload
}
function devServReloadStyles(cb) {
gulp.series(stylesCompileIncremental, reloadCss);
cb();
}
When I run gulp stylesWatch using my refactored code I get the below output (notice the stylesCompileIncremental task is not run):
So my watch tasking is successfully running but there's something wrong when the devServReloadStyles is run for the stylesCompileIncremental function to not kick in.
The original code before refactoring (when using gulp v3) is below:
// Compile only particular Sass file that has import of changed file
gulp.task('styles:compile:incremental', () => {
return sassCompile({
source: getResultedFilesList(changedFilePath),
dest: pathConfig.ui.core.sass.dest,
alsoSearchIn: [pathConfig.ui.lib.resources]
});
});
// Compile all Sass files and watch for changes
gulp.task('styles:watch', () => {
createImportsGraph();
gulp.watch(
pathConfig.ui.core.sass.src + '**/*.scss',
['devServ:reload:styles']
).on('change', event => changedFilePath = event.path);
});
// Reload the CSS links right after 'styles:compile:incremental' task is returned
gulp.task('devServ:reload:styles', ['styles:compile:incremental'], () => {
return gulp.src(generateFilePath) // css only reload
.pipe($.connect.reload());
});
The original task output when running styles:watch is this:
And this is the sassCompile variable used inside stylesCompileIncremental which I've currently not changed in anyway.
/**
* Configurable Sass compilation
* #param {Object} config
*/
const sassCompile = config => {
const sass = require('gulp-sass');
const postcss = require('gulp-postcss');
const autoprefixer = require('autoprefixer');
const postProcessors = [
autoprefixer({
flexbox: 'no-2009'
})
];
return gulp.src(config.source)
.pipe($.sourcemaps.init({
loadMaps: true,
largeFile: true
}))
.pipe(sass({
includePaths: config.alsoSearchIn,
sourceMap: false,
outputStyle: 'compressed',
indentType: 'tab',
indentWidth: '1',
linefeed: 'lf',
precision: 10,
errLogToConsole: true
}))
.on('error', function (error) {
$.util.log('\x07');
$.util.log(error.message);
this.emit('end');
})
.pipe(postcss(postProcessors))
.pipe($.sourcemaps.write('.'))
.pipe(gulp.dest(config.dest));
};
UPDATE
This is due to an issue with my devServReloadStyles function, although I'm still unsure why. If I change my stylesWatch function to use the original devServ:reload:styles task stylesCompileIncremental gets run.
// Compile all Sass files and watch for changes
function stylesWatch(cb) {
createImportsGraph();
var watcher = gulp.watch(pathConfig.ui.core.sass.src + '**/*.scss', gulp.parallel('devServ:reload:styles'));
watcher.on('change', function(event) {
changedFilePath = event;
});
cb();
}
It would still be good to not use the old task and have this as a function though.
Can anybody tell me why my refactored version doesn't work and have any suggestions as to how this should look?
I've fixed this now.
gulp.series and gulp.parallel return functions so there was no need to wrap stylesCompileIncremental and reloadCss inside another function ie. devServReloadStyles.
As per Blaine's comment here.
So my function:
function devServReloadStyles(cb) {
gulp.series(stylesCompileIncremental, reloadCss);
cb();
}
Can just be assigned to a variable:
const devServReloadStyles = gulp.series(stylesCompileIncremental, reloadCss);
And my stylesWatch task is already calling devServReloadStyles:
// Compile all Sass files and watch for changes
function stylesWatch(cb) {
createImportsGraph();
var watcher = gulp.watch(pathConfig.ui.core.sass.src + '**/*.scss', gulp.parallel(devServReloadStyles));
watcher.on('change', function(event) {
changedFilePath = event;
});
cb();
}
So running gulp stylesWatch now runs the stylesCompileIncremental job (notice how devServReloadStyles doesn't show as it's not a function).
I'm working on a angular2 application written in TypeScript.
This works:
I have a module called plugin-map.ts which looks something like this:
import { Type } from '#angular/core';
import { SomeComponent } from './plugins/some.component';
export const PluginMap: { [key: string]: Type<any> } = {
'e690bec6-f8d1-46a5-abc4-ed784e4f0058': SomeComponent
};
It gets transpiled to a plugin-map.js which looks something like this:
"use strict";
var some_component_1 = require('./plugins/some.component');
exports.PluginMap = {
'e690bec6-f8d1-46a5-abc4-ed784e4f0058': some_component_1.SomeComponent
};
//# sourceMappingURL=plugin-map.js.map
And in other modules I'm importing my PluginMap like this:
import { PluginMap } from './plugin-map';
What I want:
Now I want to create plugin-map.js at runtime when my server starts. So I want to get rid of my plugin-map.ts and instead have only a (generated) plugin-map.js. And I don't want to have any transpile-time dependencies to that plugin-map.js.
What I tried:
In modules where I need to access the PluginMap I replaced the import statement with a declaration like this:
declare const PluginMap: { [key: string]: Type<any> };
Now of course at runtime I get a Error: (SystemJS) 'PluginMap' is undefined. So my next step was to load plugin-map.js explicitly from my index.html like this:
...
<script src="js/system.src.js"></script>
<script src="systemjs.config.js"></script>
<script>
System.import('./app/plugins/plugin-map.js').catch(function (err) { console.error(err); });
System.import('app').catch(function(err){ console.error(err); });
</script>
...
When I start my application I can see that the browser actually requests the plugin-map.js file. But I still get the Error: (SystemJS) 'PluginMap' is undefined.
Question:
How/where do I have to load my generated plugin-map.js so that this works?
I had to make sure that PluginMap is available in the global context. So the generated plugin-map.js has to look like this:
var some_component_1 = require('./plugins/some.component');
PluginMap = {
'e690bec6-f8d1-46a5-abc4-ed784e4f0058': some_component_1.SomeComponent
};
and not like this:
"use strict";
var some_component_1 = require('./plugins/some.component');
exports.PluginMap = {
'e690bec6-f8d1-46a5-abc4-ed784e4f0058': some_component_1.SomeComponent
};
//# sourceMappingURL=plugin-map.js.map
Now it seems to work.
I have a Gulp task that renders a file containing a Lodash template and puts it in my build directory. I use gulp-template to do the rendering.
To render correctly, my template needs to be passed a list of files from my build directory. I get this list using glob. Since the glob API is asynchronous, I'm forced to write my task like this:
gulp.task('render', function() {
glob('src/**/*.js', function (err, appJsFiles) {
// Get rid of the first path component.
appJsFiles = _.map(appJsFiles, function(f) {
return f.slice(6);
});
// Render the file.
gulp.src('src/template.html')
.pipe(template({
scripts: appJsFiles,
styles: ['style1.css', 'style2.css', 'style3.css']
}))
.pipe(gulp.dest(config.build_dir));
});
});
This seems inelegant to me. Is there a better way to write this task?
The easiest way to fix your specific problem is to use the synchronous mode for glob, which is in the docs you linked to. Then return the result of gulp.src.
gulp.task('render', function() {
var appJsFiles = _.map(glob.sync('src/**/*.js'), function(f) {
return f.slice(6);
});
// Render the file.
return gulp.src('src/template.html')
.pipe(template({
scripts: appJsFiles,
styles: ['style1.css', 'style2.css', 'style3.css']
}))
.pipe(gulp.dest(config.build_dir));
});
If you want a task to run asynchronously, take in a callback.
gulp.task('render', function(cb) {
glob('src/**/*.js', function (err, appJsFiles) {
if (err) {
return cb(err);
}
// Get rid of the first path component.
appJsFiles = _.map(appJsFiles, function(f) {
return f.slice(6);
});
// Render the file.
gulp.src('src/template.html')
.pipe(template({
scripts: appJsFiles,
styles: ['style1.css', 'style2.css', 'style3.css']
}))
.pipe(gulp.dest(config.build_dir))
.on('end', cb);
});
});