How can I establish a plugin ecosystem using ES6 modules? - es6-modules

I want to publish a JS library and let anyone develop plugins for the library.
For example, here's a library I publish:
// awesome-lib.js
export class Library {
registerPlugin(plugin) {
...
}
}
export class PluginBase {
...
}
And a plugin implemented by other developer:
// third-party-plugin.js
import {PluginBase} from './awesome-lib';
export default MyPlugin extends PluginBase {
...
}
And the end-user's code would be something like this:
import {Library} from './awesome-lib';
import MyPlugin from './third-party-plugin';
const lib = new Library();
lib.registerPlugin(new MyPlugin());
However, there's a restriction that the end-user needs to deploy the awesome-lib.js and third-party-plugin in the same directory because the latter imports PluginBase from the awesome-lib.js in the same directory.
In Node.js, this kind of plugin is easy because its module discovery is managed by npm. But in browsers, things get difficult as its module discovery only relies on relative/absolute paths.
Is there any solution for this?

Related

Avoid using web-only libraries outside Flutter web plugin packages

I'm building a Flutter app that I am trying to make work on the web. Part of it contains some web specific code:
import 'dart:html' as html;
import 'package:flutter/foundation.dart';
class DownloadViewModel extends ChangeNotifier {
static const String url = 'https://example.com/api/v1/app/myapp_1.0.0.apk';
void onAndroidDownloadPressed() {
html.window.open(url, 'AndroidApp');
}
}
However the dart:html import gives the following error:
Avoid using web-only libraries outside Flutter web plugin packages
The longer version of the warning looks like this:
Avoid using web libraries, dart:html, dart:js and dart:js_util in
Flutter packages that are not web plugins. These libraries are not
supported outside a web context; functionality that depends on them
will fail at runtime in Flutter mobile, and their use is generally
discouraged in Flutter web.
Web library access is allowed in:
plugin packages that declare web as a supported context
otherwise, imports of dart:html, dart:js and dart:js_util are disallowed.
And it's not just a warning. This actually prevents building an Android or iOS app (even though this method isn't accessible from non-Web Flutter apps).
The only solution I've figured out is to comment out the import when I am building for Android and iOS and then uncomment it when I am building for the web. Is there a better solution?
Use the universal_html package. It supports the browser, Dart VM, and Flutter and is a stand-in replacement for dart:html and other web related libraries.
dependencies:
universal_html: ^1.2.1
Then instead of using import 'dart:html' as html; you can use the following import:
import 'package:universal_html/html.dart' as html;
For those who came to this page for other related web import problems (like dart:js), this plugin also supports the following imports:
import 'package:universal_html/driver.dart';
import 'package:universal_html/html.dart';
import 'package:universal_html/indexed_db.dart';
import 'package:universal_html/js.dart';
import 'package:universal_html/js_util.dart';
import 'package:universal_html/prefer_sdk/html.dart';
import 'package:universal_html/prefer_sdk/indexed_db.dart';
import 'package:universal_html/prefer_sdk/js.dart';
import 'package:universal_html/prefer_sdk/js_util.dart';
import 'package:universal_html/prefer_sdk/svg.dart';
import 'package:universal_html/prefer_sdk/web_gl.dart';
import 'package:universal_html/prefer_universal/html.dart';
import 'package:universal_html/prefer_universal/indexed_db.dart';
import 'package:universal_html/prefer_universal/js.dart';
import 'package:universal_html/prefer_universal/js_util.dart';
import 'package:universal_html/prefer_universal/svg.dart';
import 'package:universal_html/prefer_universal/web_gl.dart';
import 'package:universal_html/svg.dart';
import 'package:universal_html/web_gl.dart';
Since the merge of Flutter-web into the main Flutter repository, it's no longer possible to directly add imports for web libraries (e.g. dart:html, or dart:js) in a Flutter project on the main channel when targeting Web, Android and iOS.
Use the universal html package which provides extensive support for multiple platforms and web libraries.
From the root level of your project, command
flutter pub add universal_html
import 'package:universal_html/html.dart' as html
This package isn't required to run some web files (e.g. dart:js). In my case, I just had to remove the import 'dart:js' import statement.

How do I import the Three.js Line library as an ES6 module?

I do my development using modern JS (ES6) which means modules.
Although Three.js is available as an ES6 module. The line library - LineSegmentsGeometry, LineGeometry, LineSegments2, etc. - is not.
What are my options here?
You have a couple options.
First and foremost, edit the code.
You're welcome to modify the code, and so you could manually turn it into an ES6 module. You would want to remove any references of THREE, and export anything that was normally attached to that object. You'll also need to import any required core THREE.js components, like Mesh, Vector3, etc.
The way I prefer to do this is to copy the file I'm updating locally, and change references to it. For example:
// local_three_modules/LineSegments2.js
import { Mesh, Vector3, etc. } from "three"
let LineSegments2 = function ( geometry, material ) {
// ...
}
export default LineSegments2
// app.js
import { Mesh, Vector3, etc. } from "three"
import LineSegments2 from "./local_three_overrides/LineSegments2.js"
// and so on...
Your other option is to use a bundler with an export loader.
Webpack (and other bundlers, I'm just more familiar with Webpack) provides a exports-loader which can be used against specific files that don't export anything. For example, you can tell the exports-loader to export THREE from LineSegments2.js. To get webpack involved in this process, you need to tell it to use the loader on the file. You can do this through the webpack configuration, or inline in the code like this:
import THREE from "exports-loader?THREE!./node_modules/three/examples/js/lines/LineSegments2.js"

Failed to load local data with Angular 6 and ASP.NET MVC5

For security reason, we have to stick to ASP.NET MVC5. That's why we have to build the frontend Angular app and use BundleConfig to package and grab everything. For the view, we established a view placeholder for our Angular App.
Yes, I agree that it's ugly. However, it seems it is a best solution for us.
Here is the problem: When I try to load the static files. (e.g. images, json files) we have to grab them from the mvc5 backend.
I tried to move the static files to the angular assets folder, and everything got successfully compiled and copied to the dist folder. However, using relative path, Angular cannot recognize them.
Here is the error:
As you may know that the root of our angular app is: http://localhost:1220/client
and the absolute json file location is: 'http://localhost:1220/client/dist/assets/data/UiData.json'
This is my Angular service:
import { Injectable } from '#angular/core';
import { HttpClient } from '#angular/common/http';
import { Observable } from 'rxjs/observable';
#Injectable()
export class LocalDataService {
private staticDataURL = '../../../assets/data/UiData.json';
// private staticDataURL = 'assets/data/UiData.json';
constructor(private http: HttpClient) { }
public getJSON(): Observable<any> {
return this.http.get(this.staticDataURL);
}
}
both path '../../../assets/data/UiData.json' and 'assets/data/UiData.json' won't work for this case, because the server won't recognize the frontend Angular App.
This is the BundleConfig from our MVC5 backend:
public class BundleConfig
{
// For more information on bundling, visit https://go.microsoft.com/fwlink/?LinkId=301862
public static void RegisterBundles(BundleCollection bundles)
{
bundles.Add(new ScriptBundle("~/script/bundle").Include("~/Scripts/dist/bundle.js"));
bundles.Add(new StyleBundle("~/style/main").Include("~/Scripts/dist/style.css", new CssRewriteUrlTransform()));
// Angular build artifacts
bundles.Add(new ScriptBundle("~/script/report-bundle").Include(
"~/client/dist/runtime.*",
"~/client/dist/polyfills.*",
"~/client/dist/scripts.*",
"~/client/dist/main.*"
));
bundles.Add(new StyleBundle("~/style/report-style")
.Include(
"~/client/dist/styles.*",
new CssRewriteUrlTransform()
));
}
}
Is there any way to load the local json file using relative path in this case?
Thanks in advance!
We have a very similar environment (for security reasons also).
I'm hoping I can help answer your question by explaining what I 'think' would be the standard way to handle this...
I think the main thing is to just know / decide on a folder to treat as your "web root folder".
In older versions of MVC, it was the /Content folder.
In asp.net core versions, it is the /wwwroot folder.
And then put all of your scripts and assets in that folder.. As long as they are all relative to each other it should work..
So basically, you'll want to edit your output dir of your angular-cli to output your angular app to something like
/Content/js/dist/your files
All of your assets will be in something like
/Content/images/your images
and
/Content/static/your static files
something like that.
in other words, you'll put all your assets in that "web root folder" and also make sure to output your /dist folder to that same folder.
hope that helps!

Using ES6 `import` with CSS/HTML files in Meteor project: bug or feature?

I am currently learning Meteor and I found out something that intrigued me.
I can load HTML and CSS assets from a JS file using the import statement.
import '../imports/hello/myapp.html';
import '../imports/hello/myapp.css';
import * as myApp from '../imports/hello/myapp.js';
This was a surprise to me so I ran to google but could not find this behavior documented in the specification for ES6 import or in Meteor's Docs.
So my questions are:
Can I rely on this behavior to build my apps?
Will my app will break when Meteor gets around to fix it -- if it's a bug --?
Notes
I am using Meteor v1.3, not sure if this works also with previous versions.
You can download the app to see this behavior from Github
After going through the implementation of the built files for my app
I found out why this works.
HTML
Files are read from the file system and their contents added to the global Template object, e.g.,
== myapp.html ==
<body>
<h1>Welcome to Meteor!</h1>
{{> hello}}
</body>
results in the following JS code:
Template.body.addContent((function () {
var view = this;
return [
HTML.Raw("<h1>Welcome to Meteor!</h1>\n\n "),
Spacebars.include(view.lookupTemplate("hello"))
];
}));
Which is wrapped in a function with the name of the file as it's key:
"myapp.html": function (require, exports, module) {
Template.body.addContent((function () {
var view = this;
return [
HTML.Raw("<h1>Welcome to Meteor!</h1>\n\n "),
Spacebars.include(view.lookupTemplate("hello"))];
}));
Meteor.startup(Template.body.renderToDocument);
Template.__checkName("hello");
Template["hello"] = new Template("Template.hello", (
function () {
var view = this;
return [
HTML.Raw("<button>Click Me</button>\n "),
HTML.P("You've pressed the button ",
Blaze.View("lookup:counter",
function () {
return Spacebars.mustache(view.lookup("counter"));
}), " times.")
];
}));
},
So all of our HTML is now pure JS code which will be included by using require like any other module.
CSS
The files are also read from the file system and their contents are embedded also in JS functions, e.g.
== myapp.css ==
/* CSS declarations go here */
body {
background-color: lightblue;
}
Gets transformed into:
"myapp.css": ["meteor/modules", function (require, exports, module) {
module.exports = require("meteor/modules").addStyles("/* CSS declarations go here */\n\nbody {\n background-color: lightblue;\n}\n");
}]
So all of our CSS is also now a JS module that's again imported later on by using require.
Conclusion
All files are in one way or another converted to JS modules that follow similar rules for inclusion as AMD/CommonJS modules.
They will be included/bundled if another module refers to them. And since all of them are transformed to JS code
there's no magic behind the deceitful syntax:
import '../imports/hello/myapp.html';
import '../imports/hello/myapp.css';
They both are transpiled to their equivalent forms with require once the assets have been transformed to JS modules.
Whereas the approach of placing static assets in the imports directory is not mentioned in the official documentation,
this way of importing static assets works.
This seems to be at the core of how Meteor works so I'd bet this functionality is going to be there for a long while.
I don't know if to call this a feature maybe a more appropriate description is unexpected consequence but that would
only be true from the user's perspective, I assume the people who wrote the code understood this would happen and perhaps even
designed it purposely this way.
One of the features in Meteor 1.3 is lazy-loading where you place your files in the /imports folder and will not be evaluated eagerly.
Quote from Meteor Guide:
To fully use the module system and ensure that our code only runs when
we ask it to, we recommend that all of your application code should be
placed inside the imports/ directory. This means that the Meteor build
system will only bundle and include that file if it is referenced from
another file using an import.
So you can lazy load your css files by importing them from the /imports folder. I would say it's a feature.
ES6 export and import functionally are available in Meteor 1.3. You should not be importing HTML and CSS files if you are using Blaze, the current default templating enginge. The import/export functionality is there, but you may be using the wrong approach for building your views.

Yfiles HTML and webpack

did anybody tried to use yfiles html with webpack and es6?
// All yfiles modules return the yfiles namespace object
import yfiles from '../../lib/yfiles/graph-input';
import '../../lib/yfiles/graph-style-defaults';
import '../../lib/yfiles/layout-organic';
import '../../lib/yfiles/layout-misc';
import '../../lib/yfiles/graph-layout-bridge';
const graphControl = new yfiles.canvas.GraphControl.ForId('graph');
When I tried to instantiate a new graph I get the following error:
Uncaught TypeError: Cannot read property 'ForId' of undefined(…)
I digged around a little bit and I noticed that yfiles.canvas.GraphControl in undefined.
Do you have any idea why this is happening? Or a webpack example that I can use?
Thank you
With ReactJS I use
componentDidMount() {
const yfiles = require('../lib/yfiles/complete');
const styles = this.__createStyles(yfiles);
}
The yfiles is available and you can inject it in other functions. Loading of the library happens only when I am rendering that Component..meaning it behaves the same as import.
See the yeoman generator for yFiles apps - it can scaffold an es6 webpack powered application using yFiles for HTML:
npm install -g yo
npm install -g generator-yfiles-app
yo yfiles-app