Good evening to everyone.
I'm not sure how can I explain my issue. I will show it to you by showing examples of the code and expected results. I could not use code from the real issue because the code is under license. I am very sorry for that and I will be glad of someone can help me solve my issue.
I'm using latest version of webpack, babel.
My application is spliced to three parts what are dynamically imported by each other. It is mean if I run split chunks plugin it will really create three clear files.
The parts are Core, Shared, Application. Where the Core only creating an instance of the application.
Result of the parts is bundled to single file. So it is linked by one html's script tag.
Project structure is:
src/app // For Application
src/core // For Core
src/shared // For Shared
In webpack configuration I am resolving alias for import ˙Editor$˙.
I renamed naming of variables because they are including project name.
resolve: {
alias: {
"Editor$": path.resolve('./src/app/statics/Editor.js'),
}
},
The content of Core file is
function createInstance(name, id) {
import("app").then(App => {
App(name, id)
});
}
The little bit of Application file is
imports...
import Framework from "./framework"
function createApp(name, id) {
new Framework({name, id}).$mount(...)
}
export default createApp
In the Application classes (what are instantiated inside Framework)
Is this import
import Editor from "Editor"
The Editor class is a singleton. But only for created instance.
class Editor {
static instance;
id = null;
constructor(){
if(this.constructor.instance){
return this.constructor.instance
}
this.constructor.instance = this
}
static get Instance() {
return this.instance || (this.instance = new this())
}
static get Id {
return this.Instance.id;
}
}
export default Editor
The issue is webpack dependency resolving. Because webpack puts and unify all imports to the top of the file.
So the imports are evaluated once through the life-cycle of the program.
But I need to tell webpack something like: There is an instance creation. Declare the new Editor singleton for this scope. Don not use the already cached one.
My another idea how to fix this is to set context for the instance. And in the Editor singleton create something like new Map<Context, Editor> if you get what I mean. But I did not find a way how to set a context for an instance or scope the import only for it.
I will appreciate any help. I am googling two days and still no have idea how to do it without rewriting all the imports.
Sorry for bugs in my English. I am not native speaker and my brain is not for languages.
Thanks everyone who take look into my issue.
How about recreating the Editor:
// Editor.js
class Editor {
// ...
}
let instance;
export function scope(cb) {
instance = new Editor();
cb();
instance = null;
}
export default function createEditor() {
if(!instance) throw Error("Editor created out of scope!");
return instance;
}
That way you can easily set up different scopes:
// index.js
import {scope} from "./editor";
scope(() => {
require("A");
require("B");
});
scope(() => {
require("C");
});
// A
import Editor from "./editor";
(new Editor()).sth = 1;
// B
import Editor from "./editor";
console.log((new Editor()).sth); // 1
// C
import Editor from "./editor";
console.log((new Editor()).sth); // undefined
// note that this will fail:
setTimeout(() => {
new Editor(); // error: Editor created out of scope
}, 0);
That also works for nested requires and imports as long as they are not dynamic.
Related
I support a relatively complex legacy codebase, but am looking to modernise it a little by bringing in Webpack so that we'd have import & export capabilities in JS.
The problem I'm having is that we use a global object called App where we define and add different properties depending on the page. So for example we have the following file where we instantiate App (loaded on all pages):
app.js
const App = (() => {
const obj = {
Lib: {},
Util: {},
// etc
}
return obj;
})();
Then in another file we add to App.Lib just for the specific page that needs it:
lazyload.js
App.Lib.Lazyload = (() => {
// lazyload logic
})();
We simply concatenate the files during the bundling process, but obviously this is not ideal as none of the files have no knowledge of what goes on outside of it.
Exporting only seems to work for the top level object (where the object is defined), so anything I add to it elsewhere cannot be exported again. For example if I add export default App.Lib.Lazyload; at the end of lazyload.js and then try to import it elsewhere it will not import the Lazyload property.
Is there any way to get this to work without major refactor? If not, would you have any suggestions about the best way to handle it?
I don't think you can import Object.properties in JS. If you want to bundle specific packages (say Lazyload) for packages that need them, you might try:
//lazyload.js
export const LazyLoad = {
//lazyload logic
}
then somewhere else...
import {LazyLoad} from 'path/to/lazyload.js';
// assuming App has already been created/instantiated
App.Lib.Lazyload = LazyLoad;
Using Export Default...
//lazyload.js
const LazyLoad = {};
export default LazyLoad;
then...
import LazyLoad from 'path/to/lazyload.js';
App.Lib.LazyLoad = LazyLoad;
You can find help with Imports and Exports at MDN.
I am new to JS and to Kotlin/JS. I have the following minimal working Javascript code for a Plugin for Obsidian from an example. It works as expected:
var obsidian = require('obsidian');
class SomePlugin extends obsidian.Plugin {
onload() {
new obsidian.Notice('This is a notice!');
}
}
module.exports = Plugin;
I was hoping to extend this plugin using Kotlin as I know the language, but I have some problems converting this to Kotlin/JS. My approach so far:
The runnable project can be found here on Github. Run gradle build to generate the build folder. It will fail in the browser step, but that step is not necessary. After the build the generated js file can be found in build\js\packages\main\kotlin\main.js.
main.kt
#JsExport
class SomePlugin: Plugin() {
override fun onload() {
Notice("This is a notice!")
}
}
#JsModule("obsidian")
#JsNonModule // required by the umd moduletype
external open class Component {
open fun onload()
}
#JsModule("obsidian")
#JsNonModule
external open class Plugin : Component {
}
#JsModule("obsidian")
#JsNonModule
external open class Notice(message: String, timeout: Number = definedExternally) {
open fun hide()
}
Edit: Thanks to the comment of #S.Janssen I switched the module type to umd
build.gradle.kts
plugins {
kotlin("js") version "1.5.20"
}
group = "de.example"
version = "1.0-SNAPSHOT"
repositories {
mavenCentral()
}
dependencies {
implementation(npm("obsidian", "0.12.5", false))
}
kotlin {
js(IR) {
binaries.executable()
browser {
webpackTask {
output.libraryTarget = "umd"
}
}
}
}
tasks.withType<KotlinJsCompile>().configureEach {
kotlinOptions.moduleKind = "umd"
}
I don't actually need a result that can be run in the browser, but without the browser definition, it would not even generate a js file. With the browser part, an exception is thrown saying Can't resolve 'obsidian' in 'path\kotlin'. But at least a .js file is created under build/js/packages/test/kotlin/test.js. However the code is completely different from my expected code and also is not accepted by obsidian as a valid plugin code. I also tried some other gradle options. like "umd", "amd", "plain", legacy compiler instead of IR, nodejs instead of browser. But nothing creates a runnable js file. The error messages differ. With the legacy compiler it requires the kotlin.js file, that it cannot find even if I put it right next to it in the folder or copy the content into the script.
How do I get code functionally similar to the Javascript code posted above? I understand that it will have overhead, but the code currently generated does not even define or export my class by my understanding.
The error message that I get from obisidan debugger:
Plugin failure: obsidian-sample-plugin TypeError: Object prototype may only be an Object or null: undefined
The code generated:
(function (root, factory) {
if (typeof define === 'function' && define.amd)
define(['exports', 'obsidian', 'obsidian', 'obsidian'], factory);
else if (typeof exports === 'object')
factory(module.exports, require('obsidian'), require('obsidian'), require('obsidian'));
else {
if (typeof Component === 'undefined') {
throw new Error("Error loading module 'main'. Its dependency 'obsidian' was not found. Please, check whether 'obsidian' is loaded prior to 'main'.");
}if (typeof Plugin === 'undefined') {
throw new Error("Error loading module 'main'. Its dependency 'obsidian' was not found. Please, check whether 'obsidian' is loaded prior to 'main'.");
}if (typeof Notice === 'undefined') {
throw new Error("Error loading module 'main'. Its dependency 'obsidian' was not found. Please, check whether 'obsidian' is loaded prior to 'main'.");
}root.main = factory(typeof main === 'undefined' ? {} : main, Component, Plugin, Notice);
}
}(this, function (_, Component, Plugin, Notice) {
'use strict';
SomePlugin.prototype = Object.create(Plugin.prototype);
SomePlugin.prototype.constructor = SomePlugin;
function Unit() {
Unit_instance = this;
}
Unit.$metadata$ = {
simpleName: 'Unit',
kind: 'object',
interfaces: []
};
var Unit_instance;
function Unit_getInstance() {
if (Unit_instance == null)
new Unit();
return Unit_instance;
}
function SomePlugin() {
Plugin.call(this);
}
SomePlugin.prototype.onload_sv8swh_k$ = function () {
new Notice('This is a notice!');
Unit_getInstance();
};
SomePlugin.prototype.onload = function () {
return this.onload_sv8swh_k$();
};
SomePlugin.$metadata$ = {
simpleName: 'SomePlugin',
kind: 'class',
interfaces: []
};
_.SomePlugin = SomePlugin;
return _;
}));
You can find a working example of what you're going for here. I'll go through some of the changes that needed to be made to your code one-by-one in this reply.
Being unable to resolve obsidian
Can't resolve 'obsidian' in 'path\kotlin' occurs because the obsidian-api package is not a standalone library. Instead, it only consist of a obsidian.d.ts file, which is a TypeScript declaration file. Similar to a header file in other languages, this header file does not provide any implementations, but only the signatures and types for the library – meaning Kotlin/JS' webpack (or any JavaScript tooling, for that matter) won't be able to resolve the actual implementations. This is expected, and can be addressed by declaring the module as external. To do so in Kotlin/JS, create a directory called webpack.config.d, and add a file 01.externals.js with the following content:
config.externals = {
obsidian: 'obsidian',
};
(You can actually find an equivalent snippet in the offical sample-plugin configuration, as well, since this isn't a Kotlin/JS specific problem)
Grouping multiple #JsModule declarations
Because you're importing multiple declarations from the same package, instead of annotating multiple signatures with #JsModule / #JsNonModule, you'll have to create a separate file, and annotate it with #file:#JsModule("...") / #file:JsNonModule:
#file:JsModule("obsidian")
#file:JsNonModule
open external class Component {
open fun onload()
open fun onunload()
}
open external class Plugin(
app: Any,
manifest: Any
) : Component
open external class Notice(message: String, timeout: Number = definedExternally) {
open fun hide()
}
Kotlin's ES5 vs Obsidian's ES6
Additionally, some of your problems stem from the fact that Obsidian's examples implicitly make the assumption that you are targeting ES6 (while Kotlin's current target is ES5). Specifically, this makes a difference in regards to how your plugin exports its members, as well as how classes are instantiated.
Inheritance
In regards to inheritance (since YourPlugin inherits from Plugin), ES6 classes automatically initialize the parent class with all arguments. This is something that is not supported in ES5's prototype inheritance. This is why in the snippet above, we need to explicitly pass the Plugin class constructor the app and manifest parameters, and pass them through in the implementation of your specific plugin:
class SomePlugin(
app: Any,
manifest: Any
) : Plugin(
app,
manifest
)
Exports / Module System
In regards to exporting your plugin, Obsidian expects either module.exports or exports.default to be your Plugin class directly. To achieve this exact export behavior, a few conditions need to be met, which is unfortunately a bit cumbersome:
- The library target needs to be CommonJS: output.libraryTarget = "commonjs" (not CommonJS2)
- To prevent creating a level of indirection, as is usually the case, the exported library need to be set to null: output.library = null
- To export your Plugin under as default, its class declaration needs to be marked as #JsName("default").
I am trying to bundle together several javascript files using RollUp.js but when I do, the classes that aren't used get removed. This process is called tree shaking and I want to disable it.
I have found this but it doesn't seem to have any effect.
// rollup.config.js
let configuration = {
output: {
format: 'es',
},
name: 'namename',
input: './main.js',
treeshake: false, // <-- disabling tree shaking?
};
export default configuration;
I added treeshake: false to the configuration, but it doesn't seem to have any effect. Is this supposed to be placed somewhere else?
Here are the files I am trying to roll up.
// Base.js
export default class Base {
aMethod() {
return "Hello";
}
}
// main.js
import Base from './Base.js';
So with this set up, I call rollup --config and it produces something empty. So clearly, tree shaking is happening and it is removing the Base class even though I imported it.
So far the only workaround I've found is to actually create an instance of the class, which is undesirable.
// main.js
import Base from './Base.js';
export default function () {
{
new Base();
}
}
My goal is to use the bundled javascript file with JSContext. It will take in the javascript as a string and then from there, I'd invoke methods as needed.
// suppose rollup.js produces a file called "product.js"
let s = String(contentsOfFile: "path/to/product.js")
let context = JSContext()!
context.evaluateScript(s)
context.evaluateScript("var b = new Base()")
context.evaluateScript("b.aMethod()")
But because of the tree shaking the Base class never gets placed in product.js
Is there a way to disable tree shaking?
I've included a sample project for this.
Your entry file — main.js — needs to export any classes or other values that need to be accessible to the outside world:
// main.js
import Base from './Base.js';
import SubB from './SubB.js';
import SubA from './SubA.js';
export { Base, SubA, SubB };
I am having an issue with extending items, where I am getting:
Uncaught TypeError: Cannot read property 'prototype' of undefined
From what I have read items need to be defined in a particular order, so here is what I am doing, as it seems like they are in the correct order.
This doesn't happen at compile time, but at runtime in the browser. I am compiling the files into one file with browserify and tsify.
Here is my entry point main.ts:
import GameSmartWeb from './GameSmartWeb';
window.gs = new GameSmartWeb();
It then calls this file GameSmartWeb.ts which references a GUI class:
import GUI from './apis/GUI';
export default class GameSmartWeb {
public get gui(): GUI { return new GUI(); }
}
Then the GUI class apis/GUI.ts looks somewhat like this:
export default class GUI extends GameSmartWeb {
public get rewards(): Rewards { return new Rewards(); }
}
class Rewards extends GUI {
// More methods
}
When looking in the browser it says the error is here:
var __extends = (this && this.__extends) || function (d, b) {
for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p];
function __() { this.constructor = d; }
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); // The error is on this line
};
var GUI = (function (_super) {
__extends(GUI, _super); // This is the call to the function
// more auto generated code
});
This happened to me today, though it does not seem to be the same cause as for you. I'm writing an answer anyway for other people coming here in the future.
I had my files set up like this:
item.ts:
export class Item {
myAwesomeFunction() {
...
}
}
index.ts (to be able to reference smoothly):
...
export * from './item';
...
other.item.ts:
import { ..., Item, ... } from './index';
export class OtherItem extends Item ... {
...
}
And this caused the error for me:
Cannot read property 'prototype' of undefined
After changing other.item.ts to the following, it worked:
import { ... } from './index';
import { Item } from './item';
export class OtherItem extends Item ... {
...
}
I had the same issue. In my case, the order of class files in the bundle config file was the reason of it. I had the file of the derived class specified before the one of the base class, switching the order, fixed it.
The issue was because in my compiler, the order of the files were wrong. When ordering the files in the correct order the error goes away and the JavaScript works.
So, the order in the compiler should look like this:
'GameSmartWeb',
'GUI'
For me, strangely, I was trying to extend a component I'm importing via webpack from a node_modules package (angular-dual-listbox), and when I imported it using the "from 'angular-dual-listbox'", it failed with the above message. When I imported from 'angular-dual-listbox/index', viola!
In My Case, I was using browserify.
Browserify file path: browserify/bundle.js
Client.js file path: public/js/client.js
Import file path: app.js
So when i'm importing bundle.js, app.js file path is different then loading from client.js (as i'm making changes in client.js and then converting it in bundle.js)
The reason for me was not calling super() in one of the classes. You could check if super is called from each constructor.
Scenario
I'm tasked with creating a Knockout Components based UI using Typescript.
This is something I've done hundreds of times in vanilla JS, but I just can't seem to get TS to generate a JS module in the correct format to be consumed by Require JS.
Ideally, what I'd like is for Typescript to generate identical output to that written in JS, but right now I'd just like to get things working.
The Javascript
This is the Javascript that I'm trying to get TS to generate, this is Javascript from a DIFFERENT project that does not use TS, and in that project when the JS is in this format, everything works fine.
define(["knockout", "text!./menubar.html"], function (ko, menubarTemplate)
{
function menubarViewModel()
{
var self = this;
self.menuBrand = ko.observable("Menu Brand");
self.menuItems = ko.observableArray([]);
self.load();
return self;
}
menubarViewModel.prototype.load = function ()
{
var self = this;
$.getJSON("data/menudata.json", function (data)
{
self.menuItems(data);
});
};
return { viewModel: menubarViewModel, template: menubarTemplate };
});
In my actual JS file that uses the component all I need to do is:
define(["jquery", "knockout", "bootstrap"], function ($, ko)
{
ko.components.register("menubar",
{
require: "application/components/menubar"
});
ko.applyBindings();
});
The HTML for the menubar component is just a simple chunk of plain HTML markup sprinkled with "data-bind" attributes where needed to inject the data into the component.
As I say, this JavaScript version works perfectly, but the client I'm working for at the moment wants this in Typescript, so the first challenge I need to tackle is how to return
return { viewModel: menubarViewModel, template: menubarTemplate };
from a typescript module.
Typescript so far
Iv'e had a small amount of success, for instance if I do:
import ko = require("knockout");
module HelloComponent {
export class HelloViewModel {
helloText = ko.observable<string>("Hello World");
}
}
That produces a JS class, that ko tries to load, but complains that it has no template.
This says to me that if I can take the TS class above and export the require text HTML from the same class, then I might just make this work.
If I further expand that class as follows:
import ko = require("knockout");
import helloTemplate = require("text!application/components/hello.html");
module HelloComponent {
export class HelloViewModel {
helloText = ko.observable<string>("Hello World");
}
var tmp = helloTemplate;
}
I've been trying to solve this for a couple of days now, and most of the experimentation I've tried has either failed, or appears to run in the chrome debugger, but produces no output in the component.
There are dozens of posts here on SO, but none of them apply to Knockout Components, all the others apply to page level standard binding, which is different from KO components, it's the same scenario with the various blog posts I've been reading.
If anyone has an insight on how to implement this as per the advice in the KnockoutJS docs but using TS rather than JS, then I'd love to hear your ideas.
Update 12-08-2015 (Based on James Reply)
After changing one of my components to match 'James Brantly' s answer, I now see the following in Visual Studio:
Update 13-08-2015 (Post testing James Reply)
Even with the errors shown above, I've now put together several components, all using the same methodology, and everything works perfectly.
Visual studio still flags these files as having errors, but it still allows me to compile the project, and Typescript still does what it needs to and compiles to JavaScript.
At this point, am marking the question as answered, as the initial question has been solved.
I think the key to your question is that you want the module to return something like { viewModel: menubarViewModel, template: menubarTemplate };. You do that like this:
import menubarTemplate = require('text!./menubar.html');
class MenubarViewModel{
}
export = {
viewModel: MenubarViewModel,
template: menubarTemplate
}
In the code :
import ko = require("knockout");
module HelloComponent {
You are mixing internal modules (the module keyword now called namespaces) and file based modules (the import keyword).
DON'T
Use External modules only:
import ko = require("knockout");
export class HelloViewModel {
helloText = ko.observable<string>("Hello World");
}
More : https://www.youtube.com/watch?v=KDrWLMUY0R0&hd=1