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

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.

Related

Forge Viewer property database userFunction not found due to Vue webpack mangling (terser)

I am using a userFunction to query the property database in a custom Forge Viewer extension. This works great while testing the site locally using npm run serve. However, when I deploy the website to the web (which uses npm run build), the function no longer executes. The error says: SyntaxError: Function statements require a function name. This is because, according to the documentation, the function executed through executeUserFunction has to be named userFunction.
Upon further inspection I discovered that this was because of Vue & Webpack's mangling feature (executed by terser-webpack-plugin), where it renames variables and removes function names to decrease file size.
I have tried many different things, from making the function part of the extension's class to moving it to the global JS scope, but nothing helped. I also tried to exclude objects.js (which is the name of the extension I wrote) from mangling, but this didn't work either.
How do I configure terser to stop mangling this one variable?
I eventually figured out a way to do this which worked:
Add the following to vue.config.js:
module.exports = {
...
chainWebpack: config => {
config.optimization.minimizer('terser').tap(args => {
// eslint-disable-next-line no-param-reassign
args[0].terserOptions.keep_fnames = true;
return args;
});
},
};
This will prevent terser from removing function names, and will make it so userFunction still works. Weird design choice by Autodesk to require a function name, but at least it works now :)

Is there a way to lazily load static json during Angular6 routing?

I've got an Angular6 app that is being built as more of a framework for internal apps. The header / footer / major navigation will be shared, but each app (feature module) should have internal flows separate from each other. For example one feature might be purchasing, another might be onboarding, another might be a sales dashboard. They're very different from each other.
What I'm trying to do is come up with a declarative way to define known configurations for each feature. For example, the minor navigation (page to page within a feature), top level header title, and various other context related data points. My initial thought was to have them defined as JSON within each feature, which works great except I now have to import every feature's config regardless of whether a user navigates, or even has access to, that feature.
I've already got the context service set up that is checking the URL on navigation and setting some items, but again, it has to import all possible configs using this at the top of that service.
import { fooConfig } from '#foo\foo.config';
import { barConfig } from '#bar\bar.config';
import { bazConfig } from '#baz\baz.config';
So the question is: Is there a way for me to check the URL on navigation, and within that subscription, pick up the config from the correct file without pre-maturely importing them? Or maybe I can use the module to express / declare those options?
Using Typescript's Dynamic Import Expressions might be a viable option in your case..
let config;
switch (val) {
case 'foo': config = await import('#foo\foo.config'); break;
case 'bar': config = await import('#bar\bar.config'); break;
case 'baz': config = await import('#baz\baz.config'); break;
}
Though, as far as I know, there's now way at the time of writing to use variables for the import string (e.g. await import(path)).
updateConfig(feature: string) {
import(`../configs/${feature}.config.json`).then(cfg => {
this._currentConfig$.next(cfg);
});
}
The above snippet shows what I came up with. It turns out WebPack can't digest the # paths you normally use on imports and you also can't use fully variable paths. All possible options for the module package must have some common part of the path. I ended up moving my configs from #foo\foo.config.ts to #core\configs\foo.config.json. Which makes them less modular because now core is holding their config, but it makes the module lazy.

JavaScript import statements running code

I'm trying to diagnose a problem thats recently arisen for us, after upgrading our suppported browser (~40 -> ~60)
We have this effective code in an external (now unsupported) library, that sits in an iffe:
(function(window, undefined){
# external library
if(!window.terribleIdea){
window.terribleIdea = {}
}
<some code>
if(!window.terribleIdea.config.url){
window.terribleIdea.config.url = 'the wrong url'
}
localStorage.set('somethingImportant', getStuff(window.terribleIdea.config.url))
})( window );
Now we did have a bootstap type file that looked like this:
# appBootstrapper.js
import applyConfig from './app/configApplier';
import ALL_ANGULAR_MODULES from './app'; # contains angular.module set up for
# app and every dependency
fetchConfig()
.then(applyConfig)
.then () => angular.bootstrap(document, [ALL_ANGULAR_MODULES])
.catch( error => {
alert(`It borked: ${error.message}`)
});
Amongst other things applyConfig does:
window.terribleIdea = {
config: {
url: 'not terrible'
}
}
What now happens, is that the import statement of ALL_ANGULAR_MODULES ends up running the code in the external library, and setting local storage. What we think used to happen is that it was only called on angular.bootstrap running.
Now what I need to find out is, has the functionality of the import statement changed in a later version of chrome, or has it always been running this code and gone unnoticed?
All I can find is references to Dynamic Imports, and the order of scripts running, in <script></script> tags.
It is hard to debug without access to the project (see discussion in comments above). Here are some possibilities worth exploring while encountering such issues. It is of course possible that it was like this all along.
Change in the bundler configuration. Webpack accepts entries as arrays, and order matters in them.
Change in the way the bundler/dependency manager reacts to dynamic imports
Change in the way your application loads its dependency during the its bootstrap phase. It is not (imo) angular specific, as many app use some kind of "componentization" which translates in files executed in a different order they are imported (as they may only export constructors or whatnot).

Import a specific Javascript library in to Angular 4 (if library doesn't export a variable)

I am trying to show a diff for two JSON objects in Angular 4 view, I am using this library (angular-object-diff) originally built for AngularJS.
Demo of that library: Link
I tried to import this JS library the following way:
JS file I am trying to import: angular-object-diff.js, doesnt have a exported variable
In my typings.d.ts ( I added the following):
declare var ObjectDiff: any;
In my angular-cli.json, I added
"scripts": [
"../node_modules/angular-object-diff/dist/angular-object-diff.js"
],
In my component file:
const json1 = {
name: 'John'
};
const json2 = {
name: 'Johnny'
};
const diff = ObjectDiff.diffOwnProperties(json1, json2);
this.jsonViewData = ObjectDiff.toJsonDiffView(diff);
In my view:
<pre ng-bind-html="jsonViewData"></pre>
<pre> {{jsonViewData}}</pre>
Nothing seems to be working, I get the error that "ObjectDiff" is not defined in the console"
Can someone please let me know if I am doing in thing wrong ?
Suggestions for displaying the JSON diff are also welcomed :)
** Thank you
The library doesn't export anything. It uses IIFE to not pollute global scope with local variables. It's impossible to reach local variables from the outside, this makes Module pattern so effective (and annoying).
The library uses AngularJS angular global and expects that it will exist. This creates a problem, because Angular 4 application should mock angular global in this case. Moreover, the code itself relies on AngularJS-specific units ($sce service).
The library should be forked and modified to suit the expectations. The mentions of angular should be removed. Considering that script will be executed in module scope, IIFE should be removed and appropriate exports should be added.

will gulp have an issue compiling javascript 1.7 object destructing syntax?

I have seen this "destructing" syntax before but haven't ever used it. until now anyways. I want to pull in a react-router repo on github to handle my applications routing (making a single page app). but I noticed that it has this syntax
var { Route, RouteHandler, Link } = Router;
which is just a fancy way of writing
var Route = Router.Route
var RouteHandler = Router.RouteHandler
var Link = Router.Link
so my question is does anyone know if gulp will have any issues compiling this from jsx to javascript?
Do I need to install any additional dependencies for this syntax?
I have done some research but couldn't find anything conclusive. Thanks
The default gulp, yes. JSX syntax needs to be transpiled down to native js. Take a look at gulp-react to see how you can deal with this. Basically , it works in similar concept to other transpiled code (ie. coffeescript). Convert to native before piping along to the next task.
Edit
after seeing you're edit, it looks like you also need to convert the ES6. Take a look at the react browserify, it should help to get you going. Basically the concept is the same, compile jsx/es6 to native js. There's a section specific to the ES6 react components.
2nd Edit
Looks like gulp-react has an option to set to ES6. I think if you add this line, it will work.
.pipe(react({harmony: true}))
//full example
gulp.task('default', function () {
return gulp.src('template.jsx')
.pipe(react({harmony: true))
.pipe(gulp.dest('dist'));
});