Closed Serge-SDL closed 5 years ago
Hi @Serge-SDL,
As far as I know, the ES imports are not fully supported in the NativeScript runtimes and in order to get this working with the Bundle (Webpack) workflow, you need a common-js require
call runtime.
In order to do that, you could use the __non_webpack_require__. In this way, Webpack will put a common-js require
in the generated bundle and the NativeScript runtimes will be able to resolve it.
In other words, I suggest you try the following code:
const file =knownFolders.documents().getFile('test.js');
const jsCode = `
module.exports = { test: function(str) { return 'x' + str; } }
`;
file.writeTextSync(jsCode);
const dynamicFile = __non_webpack_require__(file.path);
console.log(dynamicFile.test("---- This is working ----"));
I suppose that in the Legacy workflow you've tested this in a TypeScript app where the TypeScript compiler (tsc
) is replacing the import
with require
.
UPDATE
Another possible workaround without using Webpack features is the global.require
method - the require
method exposed by the NativeScript runtimes.
const file =knownFolders.documents().getFile('test.js');
const jsCode = `
module.exports = { test: function(str) { return 'x' + str; } }
`;
file.writeTextSync(jsCode);
const dynamicFile = global.require(file.path);
console.log(dynamicFile.test("---- This is working ----"));
Hi @DimitarTachev, thank you very much for your quick and very usefull answer! This work fine (both solutions)!
if I import a simple module everything woks perfectly, but if there are some require() in the code I import this doesn't work anymode :'(
for example something like:
const file =knownFolders.documents().getFile('test.js');
const jsCode = `
require('nativescript-angular/router');
module.exports = { test: function(str) { return 'x' + str; } }
`;
file.writeTextSync(jsCode);
const dynamicFile = __non_webpack_require__(file.path);
console.log(dynamicFile.test("---- This is working ----"));
to fix this I'am looking for a way to pass dependecies to require, as in SystemJS:
SystemJS.set('@angular/router', SystemJS.newModule(AngularRouter));
or a way to have access to webpack function __webpack_require__ (or something like that...)
to you have any idea?? thanks!
@Serge-SDL, the require
statement is not working because it is searching for the router
files runtime but these files are bundled in vendor.js
build time.
When the CLI prepares your app, it starts a Webpack process generating a few bundled files based on the specified entry points and split chunks (e.g. bundle.js
and vendor.js
). These files are containing only the required modules from your application (it's "tree shaking" the app).
For this reason, even if you find a way to tell your dynamic files to search for nativescript-angular/router
in vendor.js
, it could be missing there if you don't have any other references to this router
module in your app (because of the tree shaking). Also, it will be a huge hack to use something like the internal __webpack_require__
hardcoded in your dynamic files and it will not work properly for production builds where the files are registered with ids instead of paths (e.g. nativescript-angular/router
will be something like 141
).
The easiest way of sharing node_modules
between your app and dynamically imported modules is by using additional Webpack entry points. For example, open the webpack.config.js
of your app and add the following code just after const entries = { bundle: entryPath };
:
entries["tns_modules/nativescript-angular/router"] = "nativescript-angular/router";
In this way, the Webpack compilation will produce an additional file named <app-destination-in-platforms>/tns_modules/nativescript-angular/router.js
which will be resolved both by the Runtimes when evaluating require('nativescript-angular/router');
and the __webpack_require__
calls in your application as the new entry point is both a file and part of the Webpack compilation.
Important note: The
tns_modules
part of the entry is required because of legacy code in the NativeScript runtimes module resolution. When evaluating therequire
call, the runtimes are searching intns_modules
instead ofnode_modules
. In other words, bear in mind that as shown in the above-mentioned example, you have to register the entry points starting withtns_modules
when you target something fromnode_modules
.
The above-mentioned approach has one important limitation, you have to know and list as entry points all of your dynamically required modules. If this approach is not applicable for you and you need all node_modules
available runtime (e.g. when you don't have an idea what kind of dynamic files will be required), you have to mark all dependencies as Webpack externals, copy the node_modules
in tns_modules
using the CopyWebpackPlugin and post-process the node_modules
using the nativescript-dev-babel plugin. This approach is similar to the Legacy CLI prepare mechanism and it is much more complex and disables the whole tree-shaking of the node_modules
. The build time of the app will become much slower and the app size will become much bigger. However, if you have such requirements, we could assist you with a configured webpack.config.js
file and the proper Babel config as we are already using such approach for building the NativeScript Preview app where we cannot know what kind of files will be required by the Preview users when they execute tns preview
in their apps or test something in the NativeScript Playground. More information about this approach can be found in this issue.
Angular 8 now uses dynamic imports for lazy-loaded routes. Ex:
old
{ path: '/cart', loadChildren: './cart/cart.module#CartModule' }
new
{ path: `/cart`, loadChildren: () => import(`./cart/cart.module`).then(m => m.CartModule) }
Does this issue prevent one from using this syntax? I have to admit I have not tried it on NS v6.0 yet. I tried on v5.4 and it didn't work.
IIRC, you have to change your tsconfig.tns.json and set "module": "esnext"
https://github.com/Microsoft/TypeScript/issues/16675#issuecomment-309999772
Hi @rynop, @edusperoni.
This latest comments in this issue are about using the ES dynamic imports in plain JavaScript files that are directly executed Runtime.
The dynamic import
statements in Angular8 are defined in TypeScript and processed by the AngularCompilerPlugin
build time. In other words, most probably the generated JavaScript code will have some kind of a dynamic require
or __webpack_require__
statements instead of the ES dynamic import().then..
calls and the NativeScript runtimes will be able to process it.
If you have any issues with the Angular 8 lazy routes with dynamic imports, please log them in the nativescript-angular repository.
@DimitarTachev a huge thank you for all your informations! that was wery usefull and I can't see how I could find this without you!
some further details: NS 5.4:
entries["tns_modules/tns-core-modules/nativescript-angular/router"] = "nativescript-angular/router";
-> everything works perfectly with webpack for my app... the distant code is loading as axpectedNS 6.0: my next move is upgrading to 6.0 version and it doesn't work :(
I got an issue I can't understand when I perform const modules = __non_webpack_require__(file.path);
:
ERROR Error: com.tns.NativeScriptException: Failed to find module: "./......
what is this module?? "./.... ? Why there is no double quote at the end ?? do you have any idea or a way to debug? I'm a little bit lost...
@DimitarTachev
I tried to restart from scratch to avoir app complexity:
tns create ns6codeloading --template tns-template-blank-ng
new CopyWebpackPlugin([
{ from: { glob: "fonts/**" } },
{ from: { glob: "**/*.jpg" } },
{ from: { glob: "**/*.png" } },
{ from: { glob: "**/*.js" } },
], { ignore: [`${relative(appPath, appResourcesFullPath)}/**`] }),
entries["tns_modules/tns-core-modules/nativescript-angular/router"] = "nativescript-angular/router";
entries["tns_modules/tns-core-modules/@angular/core"] = "@angular/core";
import { Component, OnInit } from "@angular/core";
import * as fs from 'tns-core-modules/file-system';
declare var __non_webpack_require__: any;
@Component({ selector: "Home", moduleId: module.id, templateUrl: "./home.component.html" }) export class HomeComponent implements OnInit {
constructor() {
// Use the component constructor to inject providers.
}
ngOnInit(): void {
const file = fs.knownFolders.currentApp().getFile('basic-module-example.js');
console.log('js content', file.path, file.readTextSync());
const modules = __non_webpack_require__(file.path);
console.log('modules', modules);
}
}
-> got the same error as in my real project when I run tns run android: `ERROR Error: com.tns.NativeScriptException: Failed to find module: "./......`
test source can be downloaded here:
https://bitbucket.org/sdlab/ns6-dynamic-import-example/src/master/
-> look like it's a nativescript 6 bug (same code work fine on ns5.4)
is it the right place? Or should I create a new bug report?
thanks!
@Serge-SDL,
I've never seen such ./......
module error :)
The attached application is running as expected on my side but its loading an empty basic-module-example.js
file.
We will need the basic-module-example.js
content in order to investigate this further as it's not generated by the application code from the provided repository.
P.S. I suppose I'm missing something but why do you need the CopyWebpackPlugin
to copy all JavaScript files from your app? I believe you don't need this step.
@DimitarTachev Sorry for the missing basic-module-example.js, it was gitignored!, I just add it ...
with the debug tool (tns debug android), I found that the real error is:
err = Error: com.tns.NativeScriptException: Failed to find module: "./......
untime.js", relative to: app/tns_modules/tns-core-modules/nativescript-angular/
and is comming from this code in nativescript-angular/router.js: require("./..\..\..\runtime.js");
It looks like \r is interpreted as a line break
about the CopyWebpackPlugin
that copy all js file: in fact I just need to copy basic-module-example.js but copying every js file is just a shortcut
@Serge-SDL,
After providing the missing js file, everything is still working as expected.
Reviewing your logs once again, I suppose that we could have an issue with the slashes in the path. Maybe you are running this on Windows?
@DimitarTachev
thanks for your answer!
yes I'm on windows :( and I thing something in webpack use windows path to generate this code:
require("./..\..\..\runtime.js");require("./..\..\..\vendor.js")
do you have any idea to fix this? Maybe a webpack plugin that can perform a replace with regex? Do you know where I can find the code that generate this code? I think it's in the new version of nativescript-dev-webpack?
All the team is on windows... no possibility to change that :(
Hi @Serge-SDL,
Thanks for the additional details!
I've opened and merges a pull request with the Windows fix.
It's already available in nativescript-dev-webpack@rc
and should be officially released in a few days.
Hi @DimitarTachev,
again a big thank you for your answers! Everything works well! I still have a last issue: I cannot find a way to make my project work with angular aot compilation. I think there is no way to compile a lib as aot (maybe it doesn't mean anything), but when I require the lib I have a compilation error (something like JIT compliler is not loaded).
Did you manage to fix that on your projects? or do you have an idea to manage this problem?
thanks!
@Serge-SDL,
I'm glad I was able to help you.
As far as I see, the basic-module-example.js
is an Angular module which is not compiled with the Angular AOT compiler (it's compiled and prepared for JIT compilation).
I suppose that you will have to compile this file with the Angular AOT compiler in order to dynamically use it in Angular apps with AOT compilation. If you have any problems with that, please open a new issue as it's not related to the dynamic imports with Webpack anymore.
Any update about this? I'm trying use dynamic imports on vue router...
Hi,
I need to use dynamic import in my project to perform semthing like this (load a module js file stored in app documents):
this code work fine with useLegacyWorkflow: true, but failed with useLegacyWorkflow: false -> I got the error: "Not supported" on android -> I got the error: "Could not resolve the module specifier" on IOS
The webpack generate code looks ok:
how can I fix this? I dont understand the error (what is not supported ?) thanks!