AoT + Webpack + json dynamic require - json

After installing the AoT plugin (https://www.npmjs.com/package/#ngtools/webpack) for webpack, dynamic requires no longer work:
// Example that used to work
public getJson<T>(fileName: String): T {
return require(`../../${fileName}_${this.lang}.json`);
}
With the standard ts-loader or awesome-typescript-loader etc, dynamic requires worked and webpack bundled the json files into the main app bundle. However, with the AoT/Webpack plugin the json files are not bundled at all. I don't even think the aot loader iterates over the json files anymore.
Any ideas how to get this to work again? Thanks.
Info:
https://github.com/angular/angular-cli/issues/3306
https://github.com/angular/angular-cli/pull/4153
Update:
Works somewhat with SystemJS -> System.import() but erratically
https://github.com/angular/angular-cli/issues/6629#issuecomment-336411537

Workaround is to use System.import() to build load and bundle the dynamic files then use the standard webpack mechanism to load the actual files:
public getLazyFiles<T>(somePath: string): T {
/* AoT Hack - causes the AoT to find and prepare the dynamically loaded files */
System.import(`../../${somePath}_${this.someSuffix}.json`);
/* ------- */
// This is then used by webpack to actually load the files
return require(`../../${somePath}_${this.someSuffix}.json`);
}
Why this workaround is needed is explained here: https://github.com/angular/angular-cli/issues/6629#issuecomment-336478854

Related

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).

TagHelpers not working when assembly is dynamically loaded in ASP.NET core 2.1

I'm using ASP.NET core 2.1. I load all the assemblies that have the Views dynamically from a plugins folder. I use the following code for that. The Views get loaded correctly.
services.AddMvc().
AddRazorPagesOptions(o => o.AllowAreas = true).
SetCompatibilityVersion(CompatibilityVersion.Version_2_1).
ConfigureApplicationPartManager(ConfigureApplicationParts);
private void ConfigureApplicationParts(ApplicationPartManager apm)
{
var pluginsPath = Path.Combine(_env.WebRootPath, "Plugins");
var assemblyFiles = Directory.GetFiles(pluginsPath, "*.dll", SearchOption.AllDirectories);
foreach (var assemblyFile in assemblyFiles)
{
var assembly = Assembly.LoadFile(assemblyFile);
if (assemblyFile.EndsWith(".Views.dll"))
{
apm.ApplicationParts.Add(new CompiledRazorAssemblyPart(assembly));
}
else
{
apm.ApplicationParts.Add(new AssemblyPart(assembly));
}
}
}
The views have some custom taghelpers.
The _ViewImports.cshtml file looks like
#addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
#addTagHelper *, MyTagHelpers
The problem is that the custom tag helpers don't get loaded and gives an error:
Error: Could not load file or assembly MyTagHelpers
The reason I get the error may be the Razor View Engine may be looking for the DLL in the bin folder of the main app and it can't find the DLL and gives this error.
What should I do in the startup to say the taghelpers are available in a DLL and can be loaded from there? Should I use TagHelperFeatureProvider to do it?
UPDATE: I moved the tag helpers to a separate DLL called MyTagHelpers.Common and dropped in the plugins folder. I'm not getting any assembly not found error anymore, but the tag helpers are not working.
After 2 days trying to resolve this - please note - the 'assembly name' is the compiled (assembled?) .DLL name which would normally match the project name which may not match the namespace name/prefix!
So if your project name is not the same as the namespace as mine was, then the #addTagHelper reference is the project name which is being used to create the compiled .DLL - see your build output to check.
And therefore, this is also usually the same as the prefix for your .csproj file which is why the official documentation says to create a new app.

Using lodash module in babel-node REPL?

I am trying to test lodash, and apparently the following line won't work in the REPL:
import curry from 'lodash/curry';
See, e.g., babel-node es6 "Modules aren't supported in the REPL"
Why does babel-node not support module loading in the REPL?
Is there a way that I can pre-load a module like lodash into babel-node? (e.g. via a command line option or a configuration file)
If not, is there another way of evaluating ES6 with lodash preloaded?
So far, I've tried the online REPL at https://babeljs.io/repl/, and evaluation in the Console in Firefox. None worked.
import surely won't work, because the package should be installed first and bundled in order to be imported, which isn't the duty of Babel REPL.
Lodash is already loaded and used in Babel REPL, it can be used in REPL as _ global:
const { curry } = _;
If the question isn't limited to Lodash, the quickest way to get third-party library in REPL is to load the script manually. And since Babel website has jQuery loaded (as almost any website), the shortest way to do this is jQuery.getScript, in console:
$.getScript('https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.4/lodash.js');
After that Lodash _ global becomes available in REPL.
The whole code can be wrapped with callback to skip console part:
$.getScript('https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.4/lodash.js', () => {
console.log(_.VERSION);
});
babel-node REPL doesn't support imports, as the links in the original post say. Instead, require should be used, like anywhere else in Node.
var { curry } = require('lodash')
This requires to have lodash installed in node_modules that exists in current working directory.

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.

Loading a global module in ECMAScript 6

Some old Javascript libraries directly attach an object to the global scope (no AMD, no UMD, no commonJS).
Is there a nice way to "include" a global module in ECMAScript 6 code?
I'm just aware of the following line:
import './globallib.js';
And then access the global variable directly.
Example: to load QUnit test function:
import 'qunit';
test('my test', function () {
ok(true, 'QUnit loaded');
});
Is this the right way?
PS. I encountered this problem while working with QUnit 1.8 in a project that compiles to ES 5 using Babel and Browserify. In QUnit 2 they're gonna avoid globals. But I have this question in general.
As far the QUnit exports its methods into window (according source code), you right, import expression is enough.
But anyway, you can't use raw imports in browsers, so you have to use some preprocessing. Webpack and browserify will work for you, but it would not be so for another build systems.