diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md
index d5b81f7c..e0ec1877 100644
--- a/.github/PULL_REQUEST_TEMPLATE.md
+++ b/.github/PULL_REQUEST_TEMPLATE.md
@@ -35,5 +35,26 @@ Migration steps:
[Provide a migration path for existing applications.]
-->
+
-[CLA]: http://www.nativescript.org/cla
\ No newline at end of file
+[CLA]: http://www.nativescript.org/cla
diff --git a/.gitignore b/.gitignore
index 8f8de90d..fca848e3 100644
--- a/.gitignore
+++ b/.gitignore
@@ -31,10 +31,17 @@ jasmine-config/reporter.js
bundle-config-loader.d.ts
bundle-config-loader.js
+xml-namespace-loader.d.ts
+xml-namespace-loader.js
+
+css2json-loader.d.ts
+css2json-loader.js
+
**/*.spec.js*
**/*.spec.d.ts*
hooks
.DS_Store
-
+.nyc_output
+coverage
!projectHelpers.spec.js
diff --git a/.npmignore b/.npmignore
index 1c2c46d5..58343b36 100644
--- a/.npmignore
+++ b/.npmignore
@@ -7,6 +7,8 @@ demo
*.spec.*
.vscode/
.github/
+.nyc_output
+coverage/
jasmine-config/
CONTRIBUTING.md
CODE_OF_CONDUCT.md
diff --git a/.nycrc b/.nycrc
new file mode 100644
index 00000000..3294892a
--- /dev/null
+++ b/.nycrc
@@ -0,0 +1,5 @@
+{
+ "extends": "@istanbuljs/nyc-config-typescript",
+ "exclude": ["/demo/**"],
+ "reporter": ["text", "lcov"]
+}
\ No newline at end of file
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 6ba86021..66b0d205 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,129 @@
+
+## [1.5.1](https://github.com/NativeScript/nativescript-dev-webpack/compare/v1.5.0...v1.5.1) (2020-02-25)
+
+
+### Bug Fixes
+
+* `The provided Android NDK is vnull while the recommended one is v21.0.6113669` error in some cases ([23aa6c5](https://github.com/NativeScript/nativescript-dev-webpack/commit/23aa6c5))
+* AOT compilation of multiple workers should work ([af5cb84](https://github.com/NativeScript/nativescript-dev-webpack/commit/af5cb84))
+* replace extension coming from package.json ([a04c0f8](https://github.com/NativeScript/nativescript-dev-webpack/commit/a04c0f8))
+
+
+### Features
+
+* Add .kt extension to known entry types ([55b56c8](https://github.com/NativeScript/nativescript-dev-webpack/commit/55b56c8))
+* Add .kt extension to known ones ([6e145a4](https://github.com/NativeScript/nativescript-dev-webpack/commit/6e145a4))
+
+
+
+
+# [1.5.0](https://github.com/NativeScript/nativescript-dev-webpack/compare/1.4.1...1.5.0) (2020-02-04)
+
+
+### Bug Fixes
+
+* ensure the js snapshot entry dir if not created (avoid ENOENT error) ([2a0eaf6](https://github.com/NativeScript/nativescript-dev-webpack/commit/2a0eaf6))
+* stop searching for snapshot artefacts when the snapshot tools are skipped (it's a cloud build, there aren't any snapshot artefacts locally) ([b8da140](https://github.com/NativeScript/nativescript-dev-webpack/commit/b8da140))
+
+
+### Features
+
+* **dependencies:** updated `[@angular](https://github.com/angular)/compiler-cli` dependency ([1dbcbf2](https://github.com/NativeScript/nativescript-dev-webpack/commit/1dbcbf2)), closes [#1114](https://github.com/NativeScript/nativescript-dev-webpack/issues/1114)
+* allow extending webpack.config.js through env ([69ace1e](https://github.com/NativeScript/nativescript-dev-webpack/commit/69ace1e))
+
+
+
+
+## [1.4.1](https://github.com/NativeScript/nativescript-dev-webpack/compare/1.4.0...1.4.1) (2020-01-07)
+
+
+### Bug Fixes
+
+* add missing tsconfig paths when the app is using only scoped modules and angular ([87ec157](https://github.com/NativeScript/nativescript-dev-webpack/commit/87ec157))
+* handle missing paths obj in tsconfig ([867a9f1](https://github.com/NativeScript/nativescript-dev-webpack/commit/867a9f1))
+
+
+
+# [1.4.0](https://github.com/NativeScript/nativescript-dev-webpack/compare/1.3.1...1.4.0) (2019-12-08)
+
+### Bug Fixes
+
+* add import of `.css` file into another `.css` file ([c5e4552](https://github.com/NativeScript/nativescript-dev-webpack/commit/c5e4552))
+* avoid duplicate modules from tns-core-modules and [@nativescript](https://github.com/nativescript)/core causing app crashes on Android ([b74b231](https://github.com/NativeScript/nativescript-dev-webpack/commit/b74b231))
+* bundle emitted on save without changes ([2d01df9](https://github.com/NativeScript/nativescript-dev-webpack/commit/2d01df9)), closes [/github.com/webpack/webpack/blob/4056506488c1e071dfc9a0127daa61bf531170bf/lib/Compiler.js#L326](https://github.com//github.com/webpack/webpack/blob/4056506488c1e071dfc9a0127daa61bf531170bf/lib/Compiler.js/issues/L326)
+* fix module import of local css files ([2c0a36e](https://github.com/NativeScript/nativescript-dev-webpack/commit/2c0a36e)), closes [/github.com/webpack-contrib/css-loader/blob/967fb66da2545f04055eb0900a69f86e484dd842/src/utils.js#L220](https://github.com//github.com/webpack-contrib/css-loader/blob/967fb66da2545f04055eb0900a69f86e484dd842/src/utils.js/issues/L220)
+* remove the tns-core-modules dependency in order to allow [@nativescrip](https://github.com/nativescrip)/core migration ([7d60958](https://github.com/NativeScript/nativescript-dev-webpack/commit/7d60958))
+* stop ignoring the initial hot updates ([d032e4c](https://github.com/NativeScript/nativescript-dev-webpack/commit/d032e4c))
+* stop on compilation error in typescript applications ([df7d122](https://github.com/NativeScript/nativescript-dev-webpack/commit/df7d122))
+* update worker loader in order to fix HMR ([5ad141e](https://github.com/NativeScript/nativescript-dev-webpack/commit/5ad141e))
+
+### Features
+
+* snapshot in Docker on macOS with Android runtime 6.3.0 or higher as it will not contain snapshot tools for macOS anymore ([9e99683](https://github.com/NativeScript/nativescript-dev-webpack/commit/9e99683))
+* stop using the proxy `tns-core-modules` package when the `[@nativescript](https://github.com/nativescript)/core` is available ([061b270](https://github.com/NativeScript/nativescript-dev-webpack/commit/061b270))
+
+
+
+
+# [1.3.0](https://github.com/NativeScript/nativescript-dev-webpack/compare/1.2.1...1.3.0) (2019-10-31)
+
+
+### Bug Fixes
+
+* exclude files from tests folder from built application ([c61f10e](https://github.com/NativeScript/nativescript-dev-webpack/commit/c61f10e))
+* fix dependencies in package.json ([eefd042](https://github.com/NativeScript/nativescript-dev-webpack/commit/eefd042)), closes [/github.com/NativeScript/nativescript-dev-webpack/blob/master/bundle-config-loader.ts#L4](https://github.com//github.com/NativeScript/nativescript-dev-webpack/blob/master/bundle-config-loader.ts/issues/L4) [/github.com/NativeScript/nativescript-dev-webpack/blob/2978b81b5a8100774b2bb4a331ac8637205927b8/package.json#L54](https://github.com//github.com/NativeScript/nativescript-dev-webpack/blob/2978b81b5a8100774b2bb4a331ac8637205927b8/package.json/issues/L54)
+* fix xxd path for local snapshots generation ([f63d493](https://github.com/NativeScript/nativescript-dev-webpack/commit/f63d493))
+* handle correctly webpack compilation errors ([363c4da](https://github.com/NativeScript/nativescript-dev-webpack/commit/363c4da))
+* handle imports like [@import](https://github.com/import) url("./xxx.css") ([8921120](https://github.com/NativeScript/nativescript-dev-webpack/commit/8921120))
+* search for the proper NDK executable on Windows ([f93bb6c](https://github.com/NativeScript/nativescript-dev-webpack/commit/f93bb6c))
+* Unbound namespace error with ios and android ([#1053](https://github.com/NativeScript/nativescript-dev-webpack/issues/1053)) ([6cd6efe](https://github.com/NativeScript/nativescript-dev-webpack/commit/6cd6efe))
+
+
+### Features
+
+* add useForImports option ([632af4f](https://github.com/NativeScript/nativescript-dev-webpack/commit/632af4f))
+* ensure valid CLI version when Windows snapshot is requested ([3a687c0](https://github.com/NativeScript/nativescript-dev-webpack/commit/3a687c0))
+* support [@nativescript](https://github.com/nativescript) scope in host resolver ([efda509](https://github.com/NativeScript/nativescript-dev-webpack/commit/efda509))
+* support useLibs though env.compileSnapshot and calculate the NDK path internally ([5431bd7](https://github.com/NativeScript/nativescript-dev-webpack/commit/5431bd7))
+* support V8 snapshots on Windows ([2e9b753](https://github.com/NativeScript/nativescript-dev-webpack/commit/2e9b753))
+* use css2json loader by default ([6b0c9ae](https://github.com/NativeScript/nativescript-dev-webpack/commit/6b0c9ae))
+
+
+
+
+## [1.2.1](https://github.com/NativeScript/nativescript-dev-webpack/compare/1.2.0...1.2.1) (2019-10-03)
+
+
+### Features
+
+* snapshot in docker container when the local tools are not available ([6861d22](https://github.com/NativeScript/nativescript-dev-webpack/commit/6861d22))
+
+
+
+
+# [1.2.0](https://github.com/NativeScript/nativescript-dev-webpack/compare/1.1.1...1.2.0) (2019-09-03)
+
+
+### Bug Fixes
+
+* register non-relative app.css module ([710acd7](https://github.com/NativeScript/nativescript-dev-webpack/commit/710acd7))
+
+
+### Features
+
+* support dynamic ES6 import ([4a07932](https://github.com/NativeScript/nativescript-dev-webpack/commit/4a07932))
+
+
+
+## [1.1.1](https://github.com/NativeScript/nativescript-dev-webpack/compare/1.1.0...1.1.1) (2019-08-22)
+
+
+### Bug Fixes
+
+* add ia64 as supported architecture ([65d5d3f](https://github.com/NativeScript/nativescript-dev-webpack/commit/65d5d3f))
+
+
+
# [1.1.0](https://github.com/NativeScript/nativescript-dev-webpack/compare/1.0.2...1.1.0) (2019-08-19)
diff --git a/androidProjectHelpers.js b/androidProjectHelpers.js
index e6a772a0..58ce9fa4 100644
--- a/androidProjectHelpers.js
+++ b/androidProjectHelpers.js
@@ -47,6 +47,16 @@ const getMksnapshotParams = (projectDir) => {
}
};
+const getRuntimeNdkRevision = (projectDir) => {
+ try {
+ const androidSettingsJSON = getAndroidSettingsJson(projectDir);
+ const result = androidSettingsJSON && androidSettingsJSON.ndkRevision;
+ return result;
+ } catch (e) {
+ return null;
+ }
+};
+
const getAndroidSettingsJson = projectDir => {
const androidSettingsJsonPath = resolve(projectDir, PLATFORMS_ANDROID, "settings.json");
if (existsSync(androidSettingsJsonPath)) {
@@ -62,5 +72,6 @@ module.exports = {
ANDROID_CONFIGURATIONS_PATH,
getAndroidRuntimeVersion,
getAndroidV8Version,
- getMksnapshotParams
-};
\ No newline at end of file
+ getMksnapshotParams,
+ getRuntimeNdkRevision
+};
diff --git a/apply-css-loader.js b/apply-css-loader.js
index a543e525..41e0f674 100644
--- a/apply-css-loader.js
+++ b/apply-css-loader.js
@@ -1,18 +1,43 @@
+const cssLoaderWarning = "The apply-css-loader expects the file to be pre-processed by css-loader. It might not work properly. Please check your webpack configuration";
+
+function checkForCssLoader(loaders, loaderIndex) {
+ if (!loaders) {
+ return false;
+ }
+
+ for (var i = loaderIndex + 1; i < loaders.length; i++) {
+ const loader = loaders[i];
+ if (loader.path && loader.path.indexOf("css-loader") > 0) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
module.exports = function (content, map) {
if (this.request.match(/\/app\.(css|scss|less|sass)$/)) {
return content;
}
+
+ // Emit a warning if the file was not processed by the css-loader.
+ if (!checkForCssLoader(this.loaders, this.loaderIndex)) {
+ this.emitWarning(new Error(cssLoaderWarning));
+ }
+
content += `
const application = require("tns-core-modules/application");
require("tns-core-modules/ui/styling/style-scope");
- exports.forEach(cssExport => {
- if (cssExport.length > 1 && cssExport[1]) {
- // applying the second item of the export as it contains the css contents
- application.addCss(cssExport[1]);
- }
- });
- `;
+ if (typeof exports.forEach === "function") {
+ exports.forEach(cssExport => {
+ if (cssExport.length > 1 && cssExport[1]) {
+ // applying the second item of the export as it contains the css contents
+ application.addCss(cssExport[1]);
+ }
+ });
+ }
+`;
this.callback(null, content, map);
-}
\ No newline at end of file
+}
diff --git a/bundle-config-loader.spec.ts b/bundle-config-loader.spec.ts
index ec8ad99b..1d745cb7 100644
--- a/bundle-config-loader.spec.ts
+++ b/bundle-config-loader.spec.ts
@@ -39,6 +39,9 @@ const defaultTestFiles = {
"./_app-variables.scss": false, // do not include scss partial files
"./App_Resources/Android/src/main/res/values/colors.xml": false, // do not include App_Resources
"./App_Resources/Android/src/main/AndroidManifest.xml": false, // do not include App_Resources
+ "./tests/example.js": false, // do not include files from tests folder
+ "./tests/component1/model1/file1.js": false, // do not include files from tests folder
+ "./components/tests/example.js": true,
};
const loaderOptionsWithIgnore = {
diff --git a/bundle-config-loader.ts b/bundle-config-loader.ts
index 6732058c..34b4de27 100644
--- a/bundle-config-loader.ts
+++ b/bundle-config-loader.ts
@@ -3,13 +3,14 @@ import { loader } from "webpack";
import { getOptions } from "loader-utils";
import * as escapeRegExp from "escape-string-regexp";
-// Matches all source, markup and style files that are not in App_Resources
-const defaultMatch = "(? {
- global.__hmrSyncBackup({ type, path });
+ global.__coreModulesLiveSync({ type, path });
});
};
- hmrUpdate().then(() => {
- global.__initialHmrUpdate = false;
- })
+ // handle hot updated on initial app start
+ hmrUpdate();
}
`;
+ let sourceModule = "tns-core-modules";
+
+ if (platform && platform !== "ios" && platform !== "android") {
+ sourceModule = `nativescript-platform-${platform}`;
+ }
+
source = `
- require("tns-core-modules/bundle-entry-points");
+ require("${sourceModule}/bundle-entry-points");
${source}
`;
@@ -96,4 +99,4 @@ const loader: loader.Loader = function (source, map) {
};
-export default loader;
\ No newline at end of file
+export default loader;
diff --git a/css2json-loader.js b/css2json-loader.js
deleted file mode 100644
index 022d645b..00000000
--- a/css2json-loader.js
+++ /dev/null
@@ -1,29 +0,0 @@
-const parse = require("tns-core-modules/css").parse;
-const nl = "\n";
-
-module.exports = function (content, map) {
- const ast = parse(content);
- const dependencies = getImportsFrom(ast)
- .map(mapURI)
- .reduce((dependencies, { uri, requireURI }) =>
- dependencies + `global.registerModule(${uri}, () => require(${requireURI}));${nl}`, "");
-
- const str = JSON.stringify(ast, (k, v) => k === "position" ? undefined : v);
- this.callback(null, `${dependencies}module.exports = ${str};`, map);
-}
-
-function getImportsFrom(ast) {
- if (!ast || ast.type !== "stylesheet" || !ast.stylesheet) {
- return [];
- }
- return ast.stylesheet.rules
- .filter(rule => rule.type === "import")
- .map(importRule => importRule.import.replace(/[\'\"]/gm, ""));
-}
-
-function mapURI(uri) {
- return {
- uri: JSON.stringify(uri),
- requireURI: JSON.stringify(uri[0] === "~" && uri[1] !== "/" ? uri.substr(1) : uri)
- };
-}
diff --git a/css2json-loader.spec.ts b/css2json-loader.spec.ts
new file mode 100644
index 00000000..b5229cd8
--- /dev/null
+++ b/css2json-loader.spec.ts
@@ -0,0 +1,79 @@
+import css2jsonLoader from "./css2json-loader";
+
+const importTestCases = [
+ `@import url("custom.css");`,
+ `@import url('custom.css');`,
+ `@import url('custom.css') print;`,
+ `@import url("custom.css") print;`,
+ `@import url('custom.css') screen and (orientation:landscape);`,
+ `@import url("custom.css") screen and (orientation:landscape);`,
+ `@import 'custom.css';`,
+ `@import "custom.css";`,
+ `@import 'custom.css' screen;`,
+ `@import "custom.css" screen;`,
+ `@import url(custom.css);`,
+]
+
+const someCSS = `
+.btn {
+ background-color: #7f9
+}
+`
+
+describe("css2jsonLoader", () => {
+ it("converts CSS to JSON", (done) => {
+ const loaderContext = {
+ callback: (error, source: string, map) => {
+ expect(source).toContain(`{"type":"declaration","property":"background-color","value":"#7f9"}`);
+
+ done();
+ }
+ }
+
+ css2jsonLoader.call(loaderContext, someCSS);
+ })
+
+ importTestCases.forEach((importTestCase) => {
+ it(`handles: ${importTestCase}`, (done) => {
+
+ const loaderContext = {
+ callback: (error, source: string, map) => {
+ expect(source).toContain(`global.registerModule("./custom.css", () => require("./custom.css"))`);
+ expect(source).toContain(`{"type":"declaration","property":"background-color","value":"#7f9"}`);
+
+ done();
+ },
+ }
+
+ css2jsonLoader.call(loaderContext, importTestCase + someCSS);
+ })
+ });
+
+ it("inlines css2json loader in imports if option is provided", (done) => {
+ const loaderContext = {
+ callback: (error, source: string, map) => {
+ expect(source).toContain(`global.registerModule("./custom.css", () => require("!nativescript-dev-webpack/css2json-loader?useForImports!./custom.css"))`);
+ expect(source).toContain(`{"type":"declaration","property":"background-color","value":"#7f9"}`);
+
+ done();
+ },
+ query: { useForImports: true }
+ }
+
+ css2jsonLoader.call(loaderContext, `@import url("custom.css");` + someCSS);
+ })
+
+ it("registers modules for paths starting with ~", (done) => {
+ const loaderContext = {
+ callback: (error, source: string, map) => {
+ expect(source).toContain(`global.registerModule("~custom.css", () => require("custom.css"))`);
+ expect(source).toContain(`global.registerModule("custom.css", () => require("custom.css"))`);
+ expect(source).toContain(`{"type":"declaration","property":"background-color","value":"#7f9"}`);
+
+ done();
+ }
+ }
+
+ css2jsonLoader.call(loaderContext, `@import url("~custom.css");` + someCSS);
+ })
+});
diff --git a/css2json-loader.ts b/css2json-loader.ts
new file mode 100644
index 00000000..19f75bfa
--- /dev/null
+++ b/css2json-loader.ts
@@ -0,0 +1,62 @@
+import { parse, Import, Stylesheet } from "css";
+import { loader } from "webpack";
+import { getOptions, urlToRequest } from "loader-utils";
+
+const betweenQuotesPattern = /('|")(.*?)\1/;
+const unpackUrlPattern = /url\(([^\)]+)\)/;
+const inlineLoader = "!nativescript-dev-webpack/css2json-loader?useForImports!"
+
+const loader: loader.Loader = function (content: string, map) {
+ const options = getOptions(this) || {};
+ const inline = !!options.useForImports;
+ const requirePrefix = inline ? inlineLoader : "";
+
+ const ast = parse(content, undefined);
+
+ let dependencies = [];
+ getImportRules(ast)
+ .map(extractUrlFromRule)
+ .map(createRequireUri)
+ .forEach(({ uri, requireURI }) => {
+ dependencies.push(`global.registerModule("${uri}", () => require("${requirePrefix}${requireURI}"));`);
+
+ // Call registerModule with requireURI to handle cases like @import "~@nativescript/theme/css/blue.css";
+ if (uri !== requireURI) {
+ dependencies.push(`global.registerModule("${requireURI}", () => require("${requirePrefix}${requireURI}"));`);
+ }
+ });
+ const str = JSON.stringify(ast, (k, v) => k === "position" ? undefined : v);
+ this.callback(null, `${dependencies.join("\n")}module.exports = ${str};`, map);
+}
+
+function getImportRules(ast: Stylesheet): Import[] {
+ if (!ast || (ast).type !== "stylesheet" || !ast.stylesheet) {
+ return [];
+ }
+ return ast.stylesheet.rules
+ .filter(rule => rule.type === "import" && (rule).import)
+}
+
+/**
+ * Extracts the url from import rule (ex. `url("./platform.css")`)
+ */
+function extractUrlFromRule(importRule: Import): string {
+ const urlValue = importRule.import;
+
+ const unpackedUrlMatch = urlValue.match(unpackUrlPattern);
+ const unpackedValue = unpackedUrlMatch ? unpackedUrlMatch[1] : urlValue
+
+ const quotesMatch = unpackedValue.match(betweenQuotesPattern);
+ return quotesMatch ? quotesMatch[2] : unpackedValue;
+};
+
+function createRequireUri(uri): { uri: string, requireURI: string } {
+ return {
+ uri: uri,
+ requireURI: urlToRequest(uri)
+ };
+}
+
+
+
+export default loader;
\ No newline at end of file
diff --git a/demo/.gitignore b/demo/.gitignore
index ee19ca4e..f8679d9c 100644
--- a/demo/.gitignore
+++ b/demo/.gitignore
@@ -17,4 +17,5 @@ vendor.js
vendor.ts
tsconfig.esm.json
-mochawesome-report
\ No newline at end of file
+mochawesome-report
+webpack.config.js
\ No newline at end of file
diff --git a/demo/AngularApp/app/App_Resources/Android/app.gradle b/demo/AngularApp/app/App_Resources/Android/app.gradle
index e45b55b8..84cd3ad5 100644
--- a/demo/AngularApp/app/App_Resources/Android/app.gradle
+++ b/demo/AngularApp/app/App_Resources/Android/app.gradle
@@ -5,12 +5,11 @@
// compile 'com.android.support:recyclerview-v7:+'
//}
-android {
- defaultConfig {
+android {
+ defaultConfig {
generatedDensities = []
- applicationId = "org.nativescript.AngularApp"
- }
- aaptOptions {
- additionalParameters "--no-version-vectors"
- }
-}
+ }
+ aaptOptions {
+ additionalParameters "--no-version-vectors"
+ }
+}
diff --git a/demo/AngularApp/app/package.json b/demo/AngularApp/app/package.json
index 80037c0e..d0929617 100644
--- a/demo/AngularApp/app/package.json
+++ b/demo/AngularApp/app/package.json
@@ -1,8 +1,9 @@
{
"android": {
- "v8Flags": "--expose_gc"
+ "v8Flags": "--expose_gc",
+ "markingMode": "none"
},
"main": "main.js",
"name": "tns-template-hello-world-ng",
"version": "3.3.0"
-}
\ No newline at end of file
+}
diff --git a/demo/AngularApp/custom-application-activity.webpack.config.js b/demo/AngularApp/custom-application-activity.webpack.config.js
new file mode 100644
index 00000000..a02e2fef
--- /dev/null
+++ b/demo/AngularApp/custom-application-activity.webpack.config.js
@@ -0,0 +1,13 @@
+const webpackConfig = require("./webpack.config");
+const path = require("path");
+
+module.exports = env => {
+ env = env || {};
+ env.appComponents = env.appComponents || [];
+ env.appComponents.push(path.resolve(__dirname, "app/activity.android.ts"));
+
+ env.entries = env.entries || {};
+ env.entries.application = "./application.android";
+ const config = webpackConfig(env);
+ return config;
+};
\ No newline at end of file
diff --git a/demo/AngularApp/nsconfig.json b/demo/AngularApp/nsconfig.json
new file mode 100644
index 00000000..2f45709d
--- /dev/null
+++ b/demo/AngularApp/nsconfig.json
@@ -0,0 +1,3 @@
+{
+ "webpackConfigPath": "custom-application-activity.webpack.config.js"
+}
\ No newline at end of file
diff --git a/demo/AngularApp/package.json b/demo/AngularApp/package.json
index 81f77544..013838ca 100644
--- a/demo/AngularApp/package.json
+++ b/demo/AngularApp/package.json
@@ -17,7 +17,6 @@
"@angular/compiler": "8.2.0",
"@angular/core": "8.2.0",
"@angular/forms": "8.2.0",
- "@angular/http": "8.0.0-beta.10",
"@angular/platform-browser": "8.2.0",
"@angular/platform-browser-dynamic": "8.2.0",
"@angular/router": "8.2.0",
@@ -40,6 +39,7 @@
"chai-as-promised": "~7.1.1",
"lazy": "1.0.11",
"mocha": "~5.2.0",
+ "chai": "4.2.0",
"mochawesome": "~3.1.2",
"nativescript-dev-appium": "next",
"nativescript-dev-webpack": "next",
diff --git a/demo/AngularApp/tsconfig.tns.json b/demo/AngularApp/tsconfig.tns.json
index 95f2ecee..9ce50ed9 100644
--- a/demo/AngularApp/tsconfig.tns.json
+++ b/demo/AngularApp/tsconfig.tns.json
@@ -1,7 +1,7 @@
{
"extends": "./tsconfig",
"compilerOptions": {
- "module": "es2015",
+ "module": "esNext",
"moduleResolution": "node"
}
}
diff --git a/demo/AngularApp/webpack.config.js b/demo/AngularApp/webpack.config.js
deleted file mode 100644
index 04179234..00000000
--- a/demo/AngularApp/webpack.config.js
+++ /dev/null
@@ -1,320 +0,0 @@
-const { join, relative, resolve, sep, dirname } = require("path");
-
-const webpack = require("webpack");
-const nsWebpack = require("nativescript-dev-webpack");
-const nativescriptTarget = require("nativescript-dev-webpack/nativescript-target");
-const { nsReplaceBootstrap } = require("nativescript-dev-webpack/transformers/ns-replace-bootstrap");
-const { nsReplaceLazyLoader } = require("nativescript-dev-webpack/transformers/ns-replace-lazy-loader");
-const { nsSupportHmrNg } = require("nativescript-dev-webpack/transformers/ns-support-hmr-ng");
-const { getMainModulePath } = require("nativescript-dev-webpack/utils/ast-utils");
-const CleanWebpackPlugin = require("clean-webpack-plugin");
-const CopyWebpackPlugin = require("copy-webpack-plugin");
-const { BundleAnalyzerPlugin } = require("webpack-bundle-analyzer");
-const { NativeScriptWorkerPlugin } = require("nativescript-worker-loader/NativeScriptWorkerPlugin");
-const TerserPlugin = require("terser-webpack-plugin");
-const { getAngularCompilerPlugin } = require("nativescript-dev-webpack/plugins/NativeScriptAngularCompilerPlugin");
-const hashSalt = Date.now().toString();
-
-module.exports = env => {
- // Add your custom Activities, Services and other Android app components here.
- const appComponents = [
- "tns-core-modules/ui/frame",
- "tns-core-modules/ui/frame/activity",
- resolve(__dirname, "app/activity.android.ts")
- ];
-
- const platform = env && (env.android && "android" || env.ios && "ios");
- if (!platform) {
- throw new Error("You need to provide a target platform!");
- }
-
- const AngularCompilerPlugin = getAngularCompilerPlugin(platform);
- const projectRoot = __dirname;
-
- // Default destination inside platforms//...
- const dist = resolve(projectRoot, nsWebpack.getAppPath(platform, projectRoot));
-
- const {
- // The 'appPath' and 'appResourcesPath' values are fetched from
- // the nsconfig.json configuration file.
- appPath = "src",
- appResourcesPath = "App_Resources",
-
- // You can provide the following flags when running 'tns run android|ios'
- aot, // --env.aot
- snapshot, // --env.snapshot,
- production, // --env.production
- uglify, // --env.uglify
- report, // --env.report
- sourceMap, // --env.sourceMap
- hiddenSourceMap, // --env.hiddenSourceMap
- hmr, // --env.hmr,
- unitTesting, // --env.unitTesting
- verbose, // --env.verbose
- } = env;
-
- const isAnySourceMapEnabled = !!sourceMap || !!hiddenSourceMap;
- const externals = nsWebpack.getConvertedExternals(env.externals);
- const appFullPath = resolve(projectRoot, appPath);
- const appResourcesFullPath = resolve(projectRoot, appResourcesPath);
- const tsConfigName = "tsconfig.tns.json";
- const entryModule = `${nsWebpack.getEntryModule(appFullPath, platform)}.ts`;
- const entryPath = `.${sep}${entryModule}`;
- const entries = { bundle: entryPath, application: "./application.android" };
- const areCoreModulesExternal = Array.isArray(env.externals) && env.externals.some(e => e.indexOf("tns-core-modules") > -1);
- if (platform === "ios" && !areCoreModulesExternal) {
- entries["tns_modules/tns-core-modules/inspector_modules"] = "inspector_modules";
- };
-
- const ngCompilerTransformers = [];
- const additionalLazyModuleResources = [];
- if (aot) {
- ngCompilerTransformers.push(nsReplaceBootstrap);
- }
-
- if (hmr) {
- ngCompilerTransformers.push(nsSupportHmrNg);
- }
-
- // when "@angular/core" is external, it's not included in the bundles. In this way, it will be used
- // directly from node_modules and the Angular modules loader won't be able to resolve the lazy routes
- // fixes https://github.com/NativeScript/nativescript-cli/issues/4024
- if (env.externals && env.externals.indexOf("@angular/core") > -1) {
- const appModuleRelativePath = getMainModulePath(resolve(appFullPath, entryModule), tsConfigName);
- if (appModuleRelativePath) {
- const appModuleFolderPath = dirname(resolve(appFullPath, appModuleRelativePath));
- // include the lazy loader inside app module
- ngCompilerTransformers.push(nsReplaceLazyLoader);
- // include the new lazy loader path in the allowed ones
- additionalLazyModuleResources.push(appModuleFolderPath);
- }
- }
-
- const ngCompilerPlugin = new AngularCompilerPlugin({
- hostReplacementPaths: nsWebpack.getResolver([platform, "tns"]),
- platformTransformers: ngCompilerTransformers.map(t => t(() => ngCompilerPlugin, resolve(appFullPath, entryModule), projectRoot)),
- mainPath: join(appFullPath, entryModule),
- tsConfigPath: join(__dirname, tsConfigName),
- skipCodeGeneration: !aot,
- sourceMap: !!isAnySourceMapEnabled,
- additionalLazyModuleResources: additionalLazyModuleResources
- });
-
- let sourceMapFilename = nsWebpack.getSourceMapFilename(hiddenSourceMap, __dirname, dist);
-
- const itemsToClean = [`${dist}/**/*`];
- if (platform === "android") {
- itemsToClean.push(`${join(projectRoot, "platforms", "android", "app", "src", "main", "assets", "snapshots")}`);
- itemsToClean.push(`${join(projectRoot, "platforms", "android", "app", "build", "configurations", "nativescript-android-snapshot")}`);
- }
-
- nsWebpack.processAppComponents(appComponents, platform);
- const config = {
- mode: production ? "production" : "development",
- context: appFullPath,
- externals,
- watchOptions: {
- ignored: [
- appResourcesFullPath,
- // Don't watch hidden files
- "**/.*",
- ]
- },
- target: nativescriptTarget,
- entry: entries,
- output: {
- pathinfo: false,
- path: dist,
- sourceMapFilename,
- libraryTarget: "commonjs2",
- filename: "[name].js",
- globalObject: "global",
- hashSalt
- },
- resolve: {
- extensions: [".ts", ".js", ".scss", ".css"],
- // Resolve {N} system modules from tns-core-modules
- modules: [
- resolve(__dirname, "node_modules/tns-core-modules"),
- resolve(__dirname, "node_modules"),
- "node_modules/tns-core-modules",
- "node_modules",
- ],
- alias: {
- '~': appFullPath
- },
- symlinks: true
- },
- resolveLoader: {
- symlinks: false
- },
- node: {
- // Disable node shims that conflict with NativeScript
- "http": false,
- "timers": false,
- "setImmediate": false,
- "fs": "empty",
- "__dirname": false,
- },
- devtool: hiddenSourceMap ? "hidden-source-map" : (sourceMap ? "inline-source-map" : "none"),
- optimization: {
- runtimeChunk: "single",
- splitChunks: {
- cacheGroups: {
- vendor: {
- name: "vendor",
- chunks: "all",
- test: (module, chunks) => {
- const moduleName = module.nameForCondition ? module.nameForCondition() : '';
- return /[\\/]node_modules[\\/]/.test(moduleName) ||
- appComponents.some(comp => comp === moduleName);
- },
- enforce: true,
- },
- }
- },
- minimize: !!uglify,
- minimizer: [
- new TerserPlugin({
- parallel: true,
- cache: true,
- sourceMap: isAnySourceMapEnabled,
- terserOptions: {
- output: {
- comments: false,
- semicolons: !isAnySourceMapEnabled
- },
- compress: {
- // The Android SBG has problems parsing the output
- // when these options are enabled
- 'collapse_vars': platform !== "android",
- sequences: platform !== "android",
- }
- }
- })
- ],
- },
- module: {
- rules: [
- {
- include: join(appFullPath, entryPath),
- use: [
- // Require all Android app components
- platform === "android" && {
- loader: "nativescript-dev-webpack/android-app-components-loader",
- options: { modules: appComponents }
- },
-
- {
- loader: "nativescript-dev-webpack/bundle-config-loader",
- options: {
- angular: true,
- loadCss: !snapshot, // load the application css if in debug mode
- unitTesting,
- appFullPath,
- projectRoot,
- ignoredFiles: nsWebpack.getUserDefinedEntries(entries, platform)
- }
- },
- ].filter(loader => !!loader)
- },
-
- { test: /\.html$|\.xml$/, use: "raw-loader" },
-
- // tns-core-modules reads the app.css and its imports using css-loader
- {
- test: /[\/|\\]app\.css$/,
- use: [
- "nativescript-dev-webpack/style-hot-loader",
- { loader: "css-loader", options: { url: false } }
- ]
- },
- {
- test: /[\/|\\]app\.scss$/,
- use: [
- "nativescript-dev-webpack/style-hot-loader",
- { loader: "css-loader", options: { url: false } },
- "sass-loader"
- ]
- },
-
- // Angular components reference css files and their imports using raw-loader
- { test: /\.css$/, exclude: /[\/|\\]app\.css$/, use: "raw-loader" },
- { test: /\.scss$/, exclude: /[\/|\\]app\.scss$/, use: ["raw-loader", "resolve-url-loader", "sass-loader"] },
-
- {
- test: /(?:\.ngfactory\.js|\.ngstyle\.js|\.ts)$/,
- use: [
- "nativescript-dev-webpack/moduleid-compat-loader",
- "nativescript-dev-webpack/lazy-ngmodule-hot-loader",
- "@ngtools/webpack",
- ]
- },
-
- // Mark files inside `@angular/core` as using SystemJS style dynamic imports.
- // Removing this will cause deprecation warnings to appear.
- {
- test: /[\/\\]@angular[\/\\]core[\/\\].+\.js$/,
- parser: { system: true },
- },
- ],
- },
- plugins: [
- // Define useful constants like TNS_WEBPACK
- new webpack.DefinePlugin({
- "global.TNS_WEBPACK": "true",
- "process": undefined,
- }),
- // Remove all files from the out dir.
- new CleanWebpackPlugin(itemsToClean, { verbose: !!verbose }),
- // Copy assets to out dir. Add your own globs as needed.
- new CopyWebpackPlugin([
- { from: { glob: "fonts/**" } },
- { from: { glob: "**/*.jpg" } },
- { from: { glob: "**/*.png" } },
- ], { ignore: [`${relative(appPath, appResourcesFullPath)}/**`] }),
- new nsWebpack.GenerateNativeScriptEntryPointsPlugin("bundle"),
- // For instructions on how to set up workers with webpack
- // check out https://github.com/nativescript/worker-loader
- new NativeScriptWorkerPlugin(),
- ngCompilerPlugin,
- // Does IPC communication with the {N} CLI to notify events when running in watch mode.
- new nsWebpack.WatchStateLoggerPlugin(),
- ],
- };
-
- if (report) {
- // Generate report files for bundles content
- config.plugins.push(new BundleAnalyzerPlugin({
- analyzerMode: "static",
- openAnalyzer: false,
- generateStatsFile: true,
- reportFilename: resolve(projectRoot, "report", `report.html`),
- statsFilename: resolve(projectRoot, "report", `stats.json`),
- }));
- }
-
- if (snapshot) {
- config.plugins.push(new nsWebpack.NativeScriptSnapshotPlugin({
- chunk: "vendor",
- angular: true,
- requireModules: [
- "reflect-metadata",
- "@angular/platform-browser",
- "@angular/core",
- "@angular/common",
- "@angular/router",
- "nativescript-angular/platform-static",
- "nativescript-angular/router",
- ],
- projectRoot,
- webpackConfig: config,
- }));
- }
-
- if (hmr) {
- config.plugins.push(new webpack.HotModuleReplacementPlugin());
- }
-
- return config;
-};
\ No newline at end of file
diff --git a/demo/JavaScriptApp/app/app.android.css b/demo/JavaScriptApp/app/app.android.css
index fd5d1243..859d3c5e 100644
--- a/demo/JavaScriptApp/app/app.android.css
+++ b/demo/JavaScriptApp/app/app.android.css
@@ -10,6 +10,7 @@ of writing your own CSS rules. For a full list of class names in the theme
refer to http://docs.nativescript.org/ui/theme.
*/
@import '~nativescript-theme-core/css/core.light.css';
+@import 'app.common.css';
ActionBar {
background-color: #7F9;
diff --git a/demo/JavaScriptApp/app/app.common.css b/demo/JavaScriptApp/app/app.common.css
new file mode 100644
index 00000000..e69de29b
diff --git a/demo/JavaScriptApp/app/app.ios.css b/demo/JavaScriptApp/app/app.ios.css
index ea07d338..964ae579 100644
--- a/demo/JavaScriptApp/app/app.ios.css
+++ b/demo/JavaScriptApp/app/app.ios.css
@@ -10,6 +10,7 @@ of writing your own CSS rules. For a full list of class names in the theme
refer to http://docs.nativescript.org/ui/theme.
*/
@import '~nativescript-theme-core/css/core.light.css';
+@import 'app.common.css';
ActionBar {
background-color: #999;
diff --git a/demo/JavaScriptApp/app/package.json b/demo/JavaScriptApp/app/package.json
index fa35743e..753ef1a0 100644
--- a/demo/JavaScriptApp/app/package.json
+++ b/demo/JavaScriptApp/app/package.json
@@ -1,8 +1,9 @@
{
"android": {
- "v8Flags": "--expose_gc"
+ "v8Flags": "--expose_gc",
+ "markingMode": "none"
},
"main": "app.js",
"name": "tns-template-hello-world",
"version": "3.3.0"
-}
\ No newline at end of file
+}
diff --git a/demo/JavaScriptApp/custom-application-activity.webpack.config.js b/demo/JavaScriptApp/custom-application-activity.webpack.config.js
new file mode 100644
index 00000000..8c105595
--- /dev/null
+++ b/demo/JavaScriptApp/custom-application-activity.webpack.config.js
@@ -0,0 +1,13 @@
+const webpackConfig = require("./webpack.config");
+const path = require("path");
+
+module.exports = env => {
+ env = env || {};
+ env.appComponents = env.appComponents || [];
+ env.appComponents.push(path.resolve(__dirname, "app/activity.android.js"));
+
+ env.entries = env.entries || {};
+ env.entries.application = "./application.android";
+ const config = webpackConfig(env);
+ return config;
+};
\ No newline at end of file
diff --git a/demo/JavaScriptApp/nsconfig.json b/demo/JavaScriptApp/nsconfig.json
new file mode 100644
index 00000000..8d06e691
--- /dev/null
+++ b/demo/JavaScriptApp/nsconfig.json
@@ -0,0 +1,3 @@
+{
+ "webpackConfigPath": "./custom-application-activity.webpack.config.js"
+}
\ No newline at end of file
diff --git a/demo/JavaScriptApp/package.json b/demo/JavaScriptApp/package.json
index 58364992..1a04d9af 100644
--- a/demo/JavaScriptApp/package.json
+++ b/demo/JavaScriptApp/package.json
@@ -24,6 +24,7 @@
"babel-types": "6.26.0",
"babylon": "6.18.0",
"lazy": "1.0.11",
+ "chai": "4.2.0",
"mocha": "~5.2.0",
"mochawesome": "~3.1.2",
"nativescript-dev-appium": "next",
diff --git a/demo/JavaScriptApp/webpack.config.js b/demo/JavaScriptApp/webpack.config.js
deleted file mode 100644
index c4684e61..00000000
--- a/demo/JavaScriptApp/webpack.config.js
+++ /dev/null
@@ -1,259 +0,0 @@
-const { join, relative, resolve, sep } = require("path");
-
-const webpack = require("webpack");
-const nsWebpack = require("nativescript-dev-webpack");
-const nativescriptTarget = require("nativescript-dev-webpack/nativescript-target");
-const CleanWebpackPlugin = require("clean-webpack-plugin");
-const CopyWebpackPlugin = require("copy-webpack-plugin");
-const { BundleAnalyzerPlugin } = require("webpack-bundle-analyzer");
-const { NativeScriptWorkerPlugin } = require("nativescript-worker-loader/NativeScriptWorkerPlugin");
-const TerserPlugin = require("terser-webpack-plugin");
-const hashSalt = Date.now().toString();
-
-module.exports = env => {
- // Add your custom Activities, Services and other android app components here.
- const appComponents = [
- "tns-core-modules/ui/frame",
- "tns-core-modules/ui/frame/activity",
- resolve(__dirname, "app/activity.android.js")
- ];
-
- const platform = env && (env.android && "android" || env.ios && "ios");
- if (!platform) {
- throw new Error("You need to provide a target platform!");
- }
-
- const platforms = ["ios", "android"];
- const projectRoot = __dirname;
-
- // Default destination inside platforms//...
- const dist = resolve(projectRoot, nsWebpack.getAppPath(platform, projectRoot));
-
- const {
- // The 'appPath' and 'appResourcesPath' values are fetched from
- // the nsconfig.json configuration file.
- appPath = "app",
- appResourcesPath = "app/App_Resources",
-
- // You can provide the following flags when running 'tns run android|ios'
- snapshot, // --env.snapshot
- production, // --env.production
- uglify, // --env.uglify
- report, // --env.report
- sourceMap, // --env.sourceMap
- hiddenSourceMap, // --env.hiddenSourceMap
- hmr, // --env.hmr,
- unitTesting, // --env.unitTesting,
- verbose, // --env.verbose
- } = env;
-
- const isAnySourceMapEnabled = !!sourceMap || !!hiddenSourceMap;
- const externals = nsWebpack.getConvertedExternals(env.externals);
- const appFullPath = resolve(projectRoot, appPath);
- const appResourcesFullPath = resolve(projectRoot, appResourcesPath);
-
- const entryModule = nsWebpack.getEntryModule(appFullPath, platform);
- const entryPath = `.${sep}${entryModule}.js`;
- const entries = { bundle: entryPath, application: "./application.android" };
- const areCoreModulesExternal = Array.isArray(env.externals) && env.externals.some(e => e.indexOf("tns-core-modules") > -1);
- if (platform === "ios" && !areCoreModulesExternal) {
- entries["tns_modules/tns-core-modules/inspector_modules"] = "inspector_modules";
- };
-
- let sourceMapFilename = nsWebpack.getSourceMapFilename(hiddenSourceMap, __dirname, dist);
-
- const itemsToClean = [`${dist}/**/*`];
- if (platform === "android") {
- itemsToClean.push(`${join(projectRoot, "platforms", "android", "app", "src", "main", "assets", "snapshots")}`);
- itemsToClean.push(`${join(projectRoot, "platforms", "android", "app", "build", "configurations", "nativescript-android-snapshot")}`);
- }
-
- nsWebpack.processAppComponents(appComponents, platform);
- const config = {
- mode: production ? "production" : "development",
- context: appFullPath,
- externals,
- watchOptions: {
- ignored: [
- appResourcesFullPath,
- // Don't watch hidden files
- "**/.*",
- ]
- },
- target: nativescriptTarget,
- entry: entries,
- output: {
- pathinfo: false,
- path: dist,
- sourceMapFilename,
- libraryTarget: "commonjs2",
- filename: "[name].js",
- globalObject: "global",
- hashSalt
- },
- resolve: {
- extensions: [".js", ".scss", ".css"],
- // Resolve {N} system modules from tns-core-modules
- modules: [
- "node_modules/tns-core-modules",
- "node_modules",
- ],
- alias: {
- '~': appFullPath
- },
- // don't resolve symlinks to symlinked modules
- symlinks: true
- },
- resolveLoader: {
- // don't resolve symlinks to symlinked loaders
- symlinks: false
- },
- node: {
- // Disable node shims that conflict with NativeScript
- "http": false,
- "timers": false,
- "setImmediate": false,
- "fs": "empty",
- "__dirname": false,
- },
- devtool: hiddenSourceMap ? "hidden-source-map" : (sourceMap ? "inline-source-map" : "none"),
- optimization: {
- runtimeChunk: "single",
- splitChunks: {
- cacheGroups: {
- vendor: {
- name: "vendor",
- chunks: "all",
- test: (module, chunks) => {
- const moduleName = module.nameForCondition ? module.nameForCondition() : '';
- return /[\\/]node_modules[\\/]/.test(moduleName) ||
- appComponents.some(comp => comp === moduleName);
-
- },
- enforce: true,
- },
- }
- },
- minimize: !!uglify,
- minimizer: [
- new TerserPlugin({
- parallel: true,
- cache: true,
- sourceMap: isAnySourceMapEnabled,
- terserOptions: {
- output: {
- comments: false,
- semicolons: !isAnySourceMapEnabled
- },
- compress: {
- // The Android SBG has problems parsing the output
- // when these options are enabled
- 'collapse_vars': platform !== "android",
- sequences: platform !== "android",
- }
- }
- })
- ],
- },
- module: {
- rules: [
- {
- include: join(appFullPath, entryPath),
- use: [
- // Require all Android app components
- platform === "android" && {
- loader: "nativescript-dev-webpack/android-app-components-loader",
- options: { modules: appComponents }
- },
-
- {
- loader: "nativescript-dev-webpack/bundle-config-loader",
- options: {
- loadCss: !snapshot, // load the application css if in debug mode
- unitTesting,
- appFullPath,
- projectRoot,
- ignoredFiles: nsWebpack.getUserDefinedEntries(entries, platform)
- }
- },
- ].filter(loader => !!loader)
- },
-
- {
- test: /\.(js|css|scss|html|xml)$/,
- use: "nativescript-dev-webpack/hmr/hot-loader"
- },
-
- { test: /\.(html|xml)$/, use: "nativescript-dev-webpack/xml-namespace-loader" },
-
- {
- test: /\.css$/,
- use: { loader: "css-loader", options: { url: false } }
- },
-
- {
- test: /\.scss$/,
- use: [
- { loader: "css-loader", options: { url: false } },
- "sass-loader"
- ]
- },
- ]
- },
- plugins: [
- // Define useful constants like TNS_WEBPACK
- new webpack.DefinePlugin({
- "global.TNS_WEBPACK": "true",
- "process": undefined,
- }),
- // Remove all files from the out dir.
- new CleanWebpackPlugin(itemsToClean, { verbose: !!verbose }),
- // Copy assets to out dir. Add your own globs as needed.
- new CopyWebpackPlugin([
- { from: { glob: "fonts/**" } },
- { from: { glob: "**/*.jpg" } },
- { from: { glob: "**/*.png" } },
- ], { ignore: [`${relative(appPath, appResourcesFullPath)}/**`] }),
- new nsWebpack.GenerateNativeScriptEntryPointsPlugin("bundle"),
-
- // For instructions on how to set up workers with webpack
- // check out https://github.com/nativescript/worker-loader
- new NativeScriptWorkerPlugin(),
- new nsWebpack.PlatformFSPlugin({
- platform,
- platforms,
- }),
- // Does IPC communication with the {N} CLI to notify events when running in watch mode.
- new nsWebpack.WatchStateLoggerPlugin()
- ],
- };
-
- if (report) {
- // Generate report files for bundles content
- config.plugins.push(new BundleAnalyzerPlugin({
- analyzerMode: "static",
- openAnalyzer: false,
- generateStatsFile: true,
- reportFilename: resolve(projectRoot, "report", `report.html`),
- statsFilename: resolve(projectRoot, "report", `stats.json`),
- }));
- }
-
- if (snapshot) {
- config.plugins.push(new nsWebpack.NativeScriptSnapshotPlugin({
- chunk: "vendor",
- requireModules: [
- "tns-core-modules/bundle-entry-points",
- ],
- projectRoot,
- webpackConfig: config,
- }));
- }
-
- if (hmr) {
- config.plugins.push(new webpack.HotModuleReplacementPlugin());
- }
-
-
- return config;
-};
\ No newline at end of file
diff --git a/demo/TypeScriptApp/app/package.json b/demo/TypeScriptApp/app/package.json
index a2e5436e..1a4b32df 100644
--- a/demo/TypeScriptApp/app/package.json
+++ b/demo/TypeScriptApp/app/package.json
@@ -1,8 +1,9 @@
{
"android": {
- "v8Flags": "--expose_gc"
+ "v8Flags": "--expose_gc",
+ "markingMode": "none"
},
"main": "app.js",
"name": "tns-template-hello-world-ts",
"version": "3.3.0"
-}
\ No newline at end of file
+}
diff --git a/demo/TypeScriptApp/custom-application-activity.webpack.config.js b/demo/TypeScriptApp/custom-application-activity.webpack.config.js
new file mode 100644
index 00000000..a02e2fef
--- /dev/null
+++ b/demo/TypeScriptApp/custom-application-activity.webpack.config.js
@@ -0,0 +1,13 @@
+const webpackConfig = require("./webpack.config");
+const path = require("path");
+
+module.exports = env => {
+ env = env || {};
+ env.appComponents = env.appComponents || [];
+ env.appComponents.push(path.resolve(__dirname, "app/activity.android.ts"));
+
+ env.entries = env.entries || {};
+ env.entries.application = "./application.android";
+ const config = webpackConfig(env);
+ return config;
+};
\ No newline at end of file
diff --git a/demo/TypeScriptApp/nsconfig.json b/demo/TypeScriptApp/nsconfig.json
index a6d75472..564d5b27 100644
--- a/demo/TypeScriptApp/nsconfig.json
+++ b/demo/TypeScriptApp/nsconfig.json
@@ -1,3 +1,3 @@
{
- "useLegacyWorkflow": false
+ "webpackConfigPath": "./custom-application-activity.webpack.config.js"
}
\ No newline at end of file
diff --git a/demo/TypeScriptApp/package.json b/demo/TypeScriptApp/package.json
index aabe8c7e..dd8eee9e 100644
--- a/demo/TypeScriptApp/package.json
+++ b/demo/TypeScriptApp/package.json
@@ -25,6 +25,7 @@
"babylon": "6.18.0",
"lazy": "1.0.11",
"mocha": "~5.2.0",
+ "chai": "4.2.0",
"mochawesome": "~3.1.2",
"nativescript-dev-appium": "next",
"nativescript-dev-webpack": "next",
diff --git a/demo/TypeScriptApp/tsconfig.tns.json b/demo/TypeScriptApp/tsconfig.tns.json
index 95f2ecee..9ce50ed9 100644
--- a/demo/TypeScriptApp/tsconfig.tns.json
+++ b/demo/TypeScriptApp/tsconfig.tns.json
@@ -1,7 +1,7 @@
{
"extends": "./tsconfig",
"compilerOptions": {
- "module": "es2015",
+ "module": "esNext",
"moduleResolution": "node"
}
}
diff --git a/demo/TypeScriptApp/webpack.config.js b/demo/TypeScriptApp/webpack.config.js
deleted file mode 100644
index 2418be9d..00000000
--- a/demo/TypeScriptApp/webpack.config.js
+++ /dev/null
@@ -1,290 +0,0 @@
-const { join, relative, resolve, sep } = require("path");
-
-const webpack = require("webpack");
-const nsWebpack = require("nativescript-dev-webpack");
-const nativescriptTarget = require("nativescript-dev-webpack/nativescript-target");
-const CleanWebpackPlugin = require("clean-webpack-plugin");
-const CopyWebpackPlugin = require("copy-webpack-plugin");
-const ForkTsCheckerWebpackPlugin = require('fork-ts-checker-webpack-plugin');
-const { BundleAnalyzerPlugin } = require("webpack-bundle-analyzer");
-const { NativeScriptWorkerPlugin } = require("nativescript-worker-loader/NativeScriptWorkerPlugin");
-const TerserPlugin = require("terser-webpack-plugin");
-const hashSalt = Date.now().toString();
-
-module.exports = env => {
- // Add your custom Activities, Services and other Android app components here.
- const appComponents = [
- "tns-core-modules/ui/frame",
- "tns-core-modules/ui/frame/activity",
- resolve(__dirname, "app/activity.android.ts")
- ];
-
- const platform = env && (env.android && "android" || env.ios && "ios");
- if (!platform) {
- throw new Error("You need to provide a target platform!");
- }
-
- const platforms = ["ios", "android"];
- const projectRoot = __dirname;
-
- // Default destination inside platforms//...
- const dist = resolve(projectRoot, nsWebpack.getAppPath(platform, projectRoot));
-
- const {
- // The 'appPath' and 'appResourcesPath' values are fetched from
- // the nsconfig.json configuration file.
- appPath = "app",
- appResourcesPath = "app/App_Resources",
-
- // You can provide the following flags when running 'tns run android|ios'
- snapshot, // --env.snapshot
- production, // --env.production
- uglify, // --env.uglify
- report, // --env.report
- sourceMap, // --env.sourceMap
- hiddenSourceMap, // --env.hiddenSourceMap
- hmr, // --env.hmr,
- unitTesting, // --env.unitTesting,
- verbose, // --env.verbose
- } = env;
- const isAnySourceMapEnabled = !!sourceMap || !!hiddenSourceMap;
- const externals = nsWebpack.getConvertedExternals(env.externals);
-
- const appFullPath = resolve(projectRoot, appPath);
- const appResourcesFullPath = resolve(projectRoot, appResourcesPath);
-
- const entryModule = nsWebpack.getEntryModule(appFullPath, platform);
- const entryPath = `.${sep}${entryModule}.ts`;
- const entries = { bundle: entryPath, application: "./application.android" };
-
- const tsConfigPath = resolve(projectRoot, "tsconfig.tns.json");
-
- const areCoreModulesExternal = Array.isArray(env.externals) && env.externals.some(e => e.indexOf("tns-core-modules") > -1);
- if (platform === "ios" && !areCoreModulesExternal) {
- entries["tns_modules/tns-core-modules/inspector_modules"] = "inspector_modules";
- };
-
- let sourceMapFilename = nsWebpack.getSourceMapFilename(hiddenSourceMap, __dirname, dist);
-
- const itemsToClean = [`${dist}/**/*`];
- if (platform === "android") {
- itemsToClean.push(`${join(projectRoot, "platforms", "android", "app", "src", "main", "assets", "snapshots")}`);
- itemsToClean.push(`${join(projectRoot, "platforms", "android", "app", "build", "configurations", "nativescript-android-snapshot")}`);
- }
-
- nsWebpack.processAppComponents(appComponents, platform);
- const config = {
- mode: production ? "production" : "development",
- context: appFullPath,
- externals,
- watchOptions: {
- ignored: [
- appResourcesFullPath,
- // Don't watch hidden files
- "**/.*",
- ]
- },
- target: nativescriptTarget,
- entry: entries,
- output: {
- pathinfo: false,
- path: dist,
- sourceMapFilename,
- libraryTarget: "commonjs2",
- filename: "[name].js",
- globalObject: "global",
- hashSalt
- },
- resolve: {
- extensions: [".ts", ".js", ".scss", ".css"],
- // Resolve {N} system modules from tns-core-modules
- modules: [
- resolve(__dirname, "node_modules/tns-core-modules"),
- resolve(__dirname, "node_modules"),
- "node_modules/tns-core-modules",
- "node_modules",
- ],
- alias: {
- '~': appFullPath
- },
- // resolve symlinks to symlinked modules
- symlinks: true
- },
- resolveLoader: {
- // don't resolve symlinks to symlinked loaders
- symlinks: false
- },
- node: {
- // Disable node shims that conflict with NativeScript
- "http": false,
- "timers": false,
- "setImmediate": false,
- "fs": "empty",
- "__dirname": false,
- },
- devtool: hiddenSourceMap ? "hidden-source-map" : (sourceMap ? "inline-source-map" : "none"),
- optimization: {
- runtimeChunk: "single",
- splitChunks: {
- cacheGroups: {
- vendor: {
- name: "vendor",
- chunks: "all",
- test: (module, chunks) => {
- const moduleName = module.nameForCondition ? module.nameForCondition() : '';
- return /[\\/]node_modules[\\/]/.test(moduleName) ||
- appComponents.some(comp => comp === moduleName);
-
- },
- enforce: true,
- },
- }
- },
- minimize: !!uglify,
- minimizer: [
- new TerserPlugin({
- parallel: true,
- cache: true,
- sourceMap: isAnySourceMapEnabled,
- terserOptions: {
- output: {
- comments: false,
- semicolons: !isAnySourceMapEnabled
- },
- compress: {
- // The Android SBG has problems parsing the output
- // when these options are enabled
- 'collapse_vars': platform !== "android",
- sequences: platform !== "android",
- }
- }
- })
- ],
- },
- module: {
- rules: [
- {
- include: join(appFullPath, entryPath),
- use: [
- // Require all Android app components
- platform === "android" && {
- loader: "nativescript-dev-webpack/android-app-components-loader",
- options: { modules: appComponents }
- },
-
- {
- loader: "nativescript-dev-webpack/bundle-config-loader",
- options: {
- loadCss: !snapshot, // load the application css if in debug mode
- unitTesting,
- appFullPath,
- projectRoot,
- ignoredFiles: nsWebpack.getUserDefinedEntries(entries, platform)
- }
- },
- ].filter(loader => !!loader)
- },
-
- {
- test: /\.(ts|css|scss|html|xml)$/,
- use: "nativescript-dev-webpack/hmr/hot-loader"
- },
-
- { test: /\.(html|xml)$/, use: "nativescript-dev-webpack/xml-namespace-loader" },
-
- {
- test: /\.css$/,
- use: { loader: "css-loader", options: { url: false } }
- },
-
- {
- test: /\.scss$/,
- use: [
- { loader: "css-loader", options: { url: false } },
- "sass-loader"
- ]
- },
-
- {
- test: /\.ts$/,
- use: {
- loader: "ts-loader",
- options: {
- configFile: tsConfigPath,
- // https://github.com/TypeStrong/ts-loader/blob/ea2fcf925ec158d0a536d1e766adfec6567f5fb4/README.md#faster-builds
- // https://github.com/TypeStrong/ts-loader/blob/ea2fcf925ec158d0a536d1e766adfec6567f5fb4/README.md#hot-module-replacement
- transpileOnly: true,
- allowTsInNodeModules: true,
- compilerOptions: {
- sourceMap: isAnySourceMapEnabled,
- declaration: false
- }
- },
- }
- },
- ]
- },
- plugins: [
- // Define useful constants like TNS_WEBPACK
- new webpack.DefinePlugin({
- "global.TNS_WEBPACK": "true",
- "process": undefined,
- }),
- // Remove all files from the out dir.
- new CleanWebpackPlugin(itemsToClean, { verbose: !!verbose }),
- // Copy assets to out dir. Add your own globs as needed.
- new CopyWebpackPlugin([
- { from: { glob: "fonts/**" } },
- { from: { glob: "**/*.jpg" } },
- { from: { glob: "**/*.png" } },
- ], { ignore: [`${relative(appPath, appResourcesFullPath)}/**`] }),
- new nsWebpack.GenerateNativeScriptEntryPointsPlugin("bundle"),
- // For instructions on how to set up workers with webpack
- // check out https://github.com/nativescript/worker-loader
- new NativeScriptWorkerPlugin(),
- new nsWebpack.PlatformFSPlugin({
- platform,
- platforms,
- }),
- // Does IPC communication with the {N} CLI to notify events when running in watch mode.
- new nsWebpack.WatchStateLoggerPlugin(),
- // https://github.com/TypeStrong/ts-loader/blob/ea2fcf925ec158d0a536d1e766adfec6567f5fb4/README.md#faster-builds
- // https://github.com/TypeStrong/ts-loader/blob/ea2fcf925ec158d0a536d1e766adfec6567f5fb4/README.md#hot-module-replacement
- new ForkTsCheckerWebpackPlugin({
- tsconfig: tsConfigPath,
- async: false,
- useTypescriptIncrementalApi: true,
- memoryLimit: 4096
- })
- ],
- };
-
- if (report) {
- // Generate report files for bundles content
- config.plugins.push(new BundleAnalyzerPlugin({
- analyzerMode: "static",
- openAnalyzer: false,
- generateStatsFile: true,
- reportFilename: resolve(projectRoot, "report", `report.html`),
- statsFilename: resolve(projectRoot, "report", `stats.json`),
- }));
- }
-
- if (snapshot) {
- config.plugins.push(new nsWebpack.NativeScriptSnapshotPlugin({
- chunk: "vendor",
- requireModules: [
- "tns-core-modules/bundle-entry-points",
- ],
- projectRoot,
- webpackConfig: config,
- }));
- }
-
- if (hmr) {
- config.plugins.push(new webpack.HotModuleReplacementPlugin());
- }
-
-
- return config;
-};
\ No newline at end of file
diff --git a/dependencyManager.js b/dependencyManager.js
index 8f348013..4cfeddcd 100644
--- a/dependencyManager.js
+++ b/dependencyManager.js
@@ -73,7 +73,7 @@ function getRequiredDeps(packageJson) {
}
const deps = {
- "@angular/compiler-cli": "8.2.0",
+ "@angular/compiler-cli": "~8.2.0",
};
if (!dependsOn(packageJson, "@angular-devkit/build-angular")) {
diff --git a/host/resolver.ts b/host/resolver.ts
index 6cff232c..071c0222 100644
--- a/host/resolver.ts
+++ b/host/resolver.ts
@@ -3,7 +3,7 @@ import { statSync } from "fs";
export function getResolver(platforms: string[], explicitResolve?: string[], nsPackageFilters?: string[], platformSpecificExt?: string[]) {
explicitResolve = explicitResolve || [];
- nsPackageFilters = nsPackageFilters || ['nativescript', 'tns', 'ns'];
+ nsPackageFilters = nsPackageFilters || ['nativescript', 'tns', 'ns', '@nativescript'];
platformSpecificExt = platformSpecificExt || [".ts", ".js", ".scss", ".less", ".css", ".html", ".xml", ".vue", ".json"];
return function (path: string) {
diff --git a/index.js b/index.js
index 7effb6c3..4846dc02 100644
--- a/index.js
+++ b/index.js
@@ -10,6 +10,54 @@ const {
Object.assign(exports, require("./plugins"));
Object.assign(exports, require("./host/resolver"));
+exports.processTsPathsForScopedModules = function ({ compilerOptions }) {
+ const tnsModulesOldPackage = "tns-core-modules";
+ const tnsModulesNewPackage = "@nativescript/core";
+ replacePathInCompilerOptions({
+ compilerOptions,
+ targetPath: tnsModulesOldPackage,
+ replacementPath: tnsModulesNewPackage
+ });
+ ensurePathInCompilerOptions({
+ compilerOptions,
+ sourcePath: tnsModulesOldPackage,
+ destinationPath: `./node_modules/${tnsModulesNewPackage}`
+ });
+ ensurePathInCompilerOptions({
+ compilerOptions,
+ sourcePath: `${tnsModulesOldPackage}/*`,
+ destinationPath: `./node_modules/${tnsModulesNewPackage}/*`
+ });
+}
+
+exports.processTsPathsForScopedAngular = function ({ compilerOptions }) {
+ const nsAngularOldPackage = "nativescript-angular";
+ const nsAngularNewPackage = "@nativescript/angular";
+ replacePathInCompilerOptions({
+ compilerOptions,
+ targetPath: nsAngularOldPackage,
+ replacementPath: nsAngularNewPackage
+ });
+ ensurePathInCompilerOptions({
+ compilerOptions,
+ sourcePath: nsAngularOldPackage,
+ destinationPath: `./node_modules/${nsAngularNewPackage}`
+ });
+ ensurePathInCompilerOptions({
+ compilerOptions,
+ sourcePath: `${nsAngularOldPackage}/*`,
+ destinationPath: `./node_modules/${nsAngularNewPackage}/*`
+ });
+}
+
+exports.hasRootLevelScopedModules = function ({ projectDir }) {
+ return hasRootLevelPackage({ projectDir, packageName: "@nativescript/core" });
+}
+
+exports.hasRootLevelScopedAngular = function ({ projectDir }) {
+ return hasRootLevelPackage({ projectDir, packageName: "@nativescript/angular" });
+}
+
exports.getAotEntryModule = function (appDirectory) {
verifyEntryModuleDirectory(appDirectory);
@@ -31,12 +79,14 @@ exports.getEntryModule = function (appDirectory, platform) {
const entry = getPackageJsonEntry(appDirectory);
const tsEntryPath = path.resolve(appDirectory, `${entry}.ts`);
+ const ktEntryPath = path.resolve(appDirectory, `${entry}.kt`);
const jsEntryPath = path.resolve(appDirectory, `${entry}.js`);
- let entryExists = existsSync(tsEntryPath) || existsSync(jsEntryPath);
+ let entryExists = existsSync(tsEntryPath) || existsSync(ktEntryPath) || existsSync(jsEntryPath);
if (!entryExists && platform) {
const platformTsEntryPath = path.resolve(appDirectory, `${entry}.${platform}.ts`);
+ const platformKtEntryPath = path.resolve(appDirectory, `${entry}.${platform}.kt`);
const platformJsEntryPath = path.resolve(appDirectory, `${entry}.${platform}.js`);
- entryExists = existsSync(platformTsEntryPath) || existsSync(platformJsEntryPath);
+ entryExists = existsSync(platformTsEntryPath) || existsSync(platformKtEntryPath) || existsSync(platformJsEntryPath);
}
if (!entryExists) {
@@ -55,6 +105,8 @@ exports.getAppPath = (platform, projectDir) => {
return `platforms/ios/${sanitizedName}/app`;
} else if (isAndroid(platform)) {
return ANDROID_APP_PATH;
+ } else if (hasPlatformPlugin(projectDir, platform)) {
+ return `platforms/${platform}/app`;
} else {
throw new Error(`Invalid platform: ${platform}`);
}
@@ -143,6 +195,13 @@ const sanitize = name => name
.filter(char => /[a-zA-Z0-9]/.test(char))
.join("");
+function hasPlatformPlugin(appDirectory, platform) {
+ const packageJsonSource = getPackageJson(appDirectory);
+ const { dependencies } = packageJsonSource;
+
+ return !!dependencies[`nativescript-platform-${platform}`];
+}
+
function getPackageJsonEntry(appDirectory) {
const packageJsonSource = getPackageJson(appDirectory);
const entry = packageJsonSource.main;
@@ -151,7 +210,7 @@ function getPackageJsonEntry(appDirectory) {
throw new Error(`${appDirectory}/package.json must contain a 'main' attribute!`);
}
- return entry.replace(/\.js$/i, "");
+ return entry.replace(/\.js$/i, "").replace(/\.kt$/i, "");
}
function verifyEntryModuleDirectory(appDirectory) {
@@ -163,3 +222,45 @@ function verifyEntryModuleDirectory(appDirectory) {
throw new Error(`The specified path to app directory ${appDirectory} does not exist. Unable to find entry module.`);
}
}
+
+
+function hasRootLevelPackage({ projectDir, packageName }) {
+ let hasRootLevelPackage;
+ try {
+ require.resolve(packageName, { paths: [projectDir] });
+ hasRootLevelPackage = true;
+ } catch (e) {
+ hasRootLevelPackage = false;
+ }
+
+ return hasRootLevelPackage;
+}
+
+function replacePathInCompilerOptions({ compilerOptions, targetPath, replacementPath }) {
+ const paths = (compilerOptions && compilerOptions.paths) || {};
+ for (const key in paths) {
+ if (paths.hasOwnProperty(key)) {
+ const pathsForPattern = paths[key];
+ if (Array.isArray(pathsForPattern)) {
+ for (let i = 0; i < pathsForPattern.length; ++i) {
+ if (typeof pathsForPattern[i] === "string") {
+ pathsForPattern[i] = pathsForPattern[i].replace(targetPath, replacementPath);
+ }
+ }
+ }
+ }
+ }
+}
+
+function ensurePathInCompilerOptions({ compilerOptions, sourcePath, destinationPath }) {
+ compilerOptions = compilerOptions || {};
+ compilerOptions.paths = compilerOptions.paths || {};
+ const paths = compilerOptions.paths;
+ if (paths[sourcePath]) {
+ if (Array.isArray(paths[sourcePath]) && paths[sourcePath].indexOf(destinationPath) === -1) {
+ paths[sourcePath].push(destinationPath);
+ }
+ } else {
+ paths[sourcePath] = [destinationPath];
+ }
+}
diff --git a/jasmine-config/jasmine.json b/jasmine-config/jasmine.json
index 8d3ecdc5..3d06fa01 100644
--- a/jasmine-config/jasmine.json
+++ b/jasmine-config/jasmine.json
@@ -3,7 +3,7 @@
"spec_files": [
"!node_modules/**/*.spec.js",
"!demo/**/*.spec.js",
- "./*.spec.js"
+ "./**/*.spec.js"
],
"helpers": [
"jasmine-config/**/*.js"
diff --git a/lib/after-prepare.js b/lib/after-prepare.js
index 7138b402..333e60f7 100644
--- a/lib/after-prepare.js
+++ b/lib/after-prepare.js
@@ -12,7 +12,12 @@ module.exports = function (hookArgs) {
release: hookArgs.prepareData.release
};
- if (env.snapshot && shouldSnapshot(shouldSnapshotOptions)) {
+ if (env.snapshot &&
+ shouldSnapshot(shouldSnapshotOptions) &&
+ (!hookArgs.prepareData ||
+ !hookArgs.prepareData.nativePrepare ||
+ !hookArgs.prepareData.nativePrepare.skipNativePrepare)) {
+
installSnapshotArtefacts(hookArgs.prepareData.projectDir);
}
}
diff --git a/lib/before-checkForChanges.js b/lib/before-checkForChanges.js
index d456a1d3..fd81fd44 100644
--- a/lib/before-checkForChanges.js
+++ b/lib/before-checkForChanges.js
@@ -1,6 +1,11 @@
+const { shouldSnapshot, isWinOS } = require("./utils");
+const semver = require("semver");
+
module.exports = function ($staticConfig, hookArgs) {
- const majorVersionMatch = ($staticConfig.version || '').match(/^(\d+)\./);
- const majorVersion = majorVersionMatch && majorVersionMatch[1] && +majorVersionMatch[1];
+ const cliVersion = semver.parse($staticConfig.version);
+ const majorVersion = cliVersion && cliVersion.major;
+ const minorVersion = cliVersion && cliVersion.minor;
+
if (majorVersion && majorVersion < 6) {
// check if we are using the bundle workflow or the legacy one.
const isUsingBundleWorkflow = hookArgs &&
@@ -12,5 +17,17 @@ module.exports = function ($staticConfig, hookArgs) {
const packageJsonData = require("../package.json")
throw new Error(`The current version of ${packageJsonData.name} (${packageJsonData.version}) is not compatible with the used CLI: ${$staticConfig.version}. Please upgrade your NativeScript CLI version (npm i -g nativescript).`);
}
+ } else {
+ const env = hookArgs.prepareData.env || {};
+ const shouldSnapshotOptions = {
+ platform: hookArgs.prepareData.platform,
+ release: hookArgs.prepareData.release
+ };
+
+ const shouldSnapshotOnWin = env.snapshot && shouldSnapshot(shouldSnapshotOptions) && isWinOS();
+ const isCLIWithoutWinSnapshotsSupport = majorVersion && majorVersion == 6 && minorVersion && minorVersion < 2;
+ if (shouldSnapshotOnWin && isCLIWithoutWinSnapshotsSupport) {
+ throw new Error(`In order to generate Snapshots on Windows, please upgrade your NativeScript CLI version (npm i -g nativescript).`);
+ }
}
}
\ No newline at end of file
diff --git a/lib/utils.js b/lib/utils.js
index a527b24a..17b1eb33 100644
--- a/lib/utils.js
+++ b/lib/utils.js
@@ -1,18 +1,41 @@
const os = require("os");
+const { dirname } = require("path");
+const { existsSync, mkdirSync } = require("fs");
const { isAndroid } = require("../projectHelpers");
function shouldSnapshot(config) {
const platformSupportsSnapshot = isAndroid(config.platform);
- const osSupportsSnapshot = os.type() !== "Windows_NT";
- return config.release && platformSupportsSnapshot && osSupportsSnapshot;
+ return config.release && platformSupportsSnapshot;
}
function convertToUnixPath(relativePath) {
return relativePath.replace(/\\/g, "/");
}
+function isWinOS() {
+ return os.type() === "Windows_NT";
+}
+
+function warn(message) {
+ if (message) {
+ console.log(`\x1B[33;1m${message}\x1B[0m`);
+ }
+}
+
+function ensureDirectoryExistence(filePath) {
+ var dir = dirname(filePath);
+ if (existsSync(dir)) {
+ return true;
+ }
+ ensureDirectoryExistence(dir);
+ mkdirSync(dir);
+}
+
module.exports = {
shouldSnapshot,
- convertToUnixPath
+ convertToUnixPath,
+ isWinOS,
+ warn,
+ ensureDirectoryExistence
};
diff --git a/load-application-css-angular.js b/load-application-css-angular.js
index 314203c6..42e72167 100644
--- a/load-application-css-angular.js
+++ b/load-application-css-angular.js
@@ -3,5 +3,6 @@ const loadCss = require("./load-application-css");
module.exports = function() {
loadCss(function() {
global.registerModule("./app.css", () => require("~/app"));
+ global.registerModule("app.css", () => require("~/app"));
});
}
diff --git a/package.json b/package.json
index 7421d2e5..c4b42a23 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "nativescript-dev-webpack",
- "version": "1.1.0",
+ "version": "1.5.1",
"main": "index",
"description": "",
"homepage": "http://www.telerik.com",
@@ -35,6 +35,7 @@
"prepare": "npm run tsc && npm run jasmine",
"test": "npm run prepare",
"jasmine": "jasmine --config=jasmine-config/jasmine.json",
+ "coverage": "nyc npm run test",
"version": "rm package-lock.json && conventional-changelog -p angular -i CHANGELOG.md -s && git add CHANGELOG.md"
},
"bin": {
@@ -49,19 +50,22 @@
"@angular-devkit/core": "8.2.0",
"clean-webpack-plugin": "~1.0.0",
"copy-webpack-plugin": "~4.6.0",
+ "css": "2.2.1",
"css-loader": "~2.1.1",
- "extra-watch-webpack-plugin": "1.0.3",
- "fork-ts-checker-webpack-plugin": "1.3.0",
+ "escape-string-regexp": "1.0.5",
+ "fork-ts-checker-webpack-plugin": "2.0.0",
"global-modules-path": "2.0.0",
"loader-utils": "^1.2.3",
"minimatch": "3.0.4",
"nativescript-hook": "0.2.4",
- "nativescript-worker-loader": "~0.9.0",
+ "nativescript-worker-loader": "~0.11.0",
+ "properties-reader": "0.3.1",
"proxy-lib": "0.4.0",
"raw-loader": "~0.5.1",
"request": "2.88.0",
"resolve-url-loader": "~3.0.0",
"sass-loader": "~7.1.0",
+ "sax": "^1.2.4",
"schema-utils": "0.4.5",
"semver": "^6.0.0",
"shelljs": "0.6.0",
@@ -77,18 +81,23 @@
"devDependencies": {
"@angular/compiler": "8.2.0",
"@angular/compiler-cli": "8.2.0",
+ "@istanbuljs/nyc-config-typescript": "^0.1.3",
"@ngtools/webpack": "8.2.0",
+ "@types/css": "0.0.31",
"@types/jasmine": "^3.3.7",
"@types/loader-utils": "^1.1.3",
"@types/node": "^10.12.12",
"@types/proxyquire": "1.3.28",
+ "@types/sax": "^1.2.0",
"@types/semver": "^6.0.0",
"@types/webpack": "^4.4.34",
"conventional-changelog-cli": "^1.3.22",
"jasmine": "^3.2.0",
"jasmine-spec-reporter": "^4.2.1",
+ "nyc": "^14.1.1",
"proxyquire": "2.1.0",
+ "source-map-support": "^0.5.13",
"tns-core-modules": "next",
"typescript": "~3.5.3"
}
-}
\ No newline at end of file
+}
diff --git a/plugins/GenerateNativeScriptEntryPointsPlugin.js b/plugins/GenerateNativeScriptEntryPointsPlugin.js
index 9417be22..6f7177c3 100644
--- a/plugins/GenerateNativeScriptEntryPointsPlugin.js
+++ b/plugins/GenerateNativeScriptEntryPointsPlugin.js
@@ -1,5 +1,5 @@
const { convertToUnixPath } = require("../lib/utils");
-const { RawSource } = require("webpack-sources");
+const { RawSource, ConcatSource } = require("webpack-sources");
const { getPackageJson } = require("../projectHelpers");
const { SNAPSHOT_ENTRY_NAME } = require("./NativeScriptSnapshotPlugin");
const path = require("path");
@@ -83,7 +83,12 @@ exports.GenerateNativeScriptEntryPointsPlugin = (function () {
return `require("./${depRelativePathUnix}");`;
}).join("");
const currentEntryFileContent = compilation.assets[filePath].source();
- compilation.assets[filePath] = new RawSource(`${requireDeps}${currentEntryFileContent}`);
+
+ if(compilation.assets[filePath] instanceof ConcatSource) {
+ compilation.assets[filePath].children.unshift(`${requireDeps}`);
+ } else {
+ compilation.assets[filePath] = new RawSource(`${requireDeps}${currentEntryFileContent}`);
+ }
}
});
}
diff --git a/plugins/NativeScriptSnapshotPlugin/index.js b/plugins/NativeScriptSnapshotPlugin/index.js
index 97cebd7e..58b14876 100644
--- a/plugins/NativeScriptSnapshotPlugin/index.js
+++ b/plugins/NativeScriptSnapshotPlugin/index.js
@@ -8,6 +8,7 @@ const {
ANDROID_PROJECT_DIR,
ANDROID_APP_PATH,
} = require("../../androidProjectHelpers");
+const { ensureDirectoryExistence } = require("../../lib/utils");
const schema = require("./options.json");
const SNAPSHOT_ENTRY_NAME = "snapshot-entry";
@@ -57,6 +58,7 @@ exports.NativeScriptSnapshotPlugin = (function () {
snapshotEntryContent += [...requireModules, ...internalRequireModules]
.map(mod => `require('${mod}')`).join(";");
+ ensureDirectoryExistence(snapshotEntryPath);
writeFileSync(snapshotEntryPath, snapshotEntryContent, { encoding: "utf8" });
// add the module to the entry points to make sure it's content is evaluated
@@ -68,7 +70,6 @@ exports.NativeScriptSnapshotPlugin = (function () {
// ensure that the runtime is installed only in the snapshotted chunk
webpackConfig.optimization.runtimeChunk = { name: SNAPSHOT_ENTRY_NAME };
}
-
NativeScriptSnapshotPlugin.getInternalRequireModules = function (webpackContext) {
const packageJson = getPackageJson(webpackContext);
return (packageJson && packageJson["android"] && packageJson["android"]["requireModules"]) || [];
@@ -97,6 +98,11 @@ exports.NativeScriptSnapshotPlugin = (function () {
NativeScriptSnapshotPlugin.prototype.generate = function (webpackChunks) {
const options = this.options;
+ if (options.skipSnapshotTools) {
+ console.log(`Skipping snapshot tools.`);
+ return Promise.resolve();
+ }
+
const inputFiles = webpackChunks.map(chunk => join(options.webpackConfig.output.path, chunk.files[0]));
const preprocessedInputFile = join(
this.options.projectRoot,
@@ -113,6 +119,8 @@ exports.NativeScriptSnapshotPlugin = (function () {
useLibs: options.useLibs,
androidNdkPath: options.androidNdkPath,
v8Version: options.v8Version,
+ snapshotInDocker: options.snapshotInDocker,
+ skipSnapshotTools: options.skipSnapshotTools
}).then(() => {
// Make the original files empty
inputFiles.forEach(inputFile =>
diff --git a/plugins/NativeScriptSnapshotPlugin/options.json b/plugins/NativeScriptSnapshotPlugin/options.json
index 513afd4e..22e08d60 100644
--- a/plugins/NativeScriptSnapshotPlugin/options.json
+++ b/plugins/NativeScriptSnapshotPlugin/options.json
@@ -5,8 +5,8 @@
"type": "string"
},
"angular": {
- "type": "boolean",
- "default": false
+ "type": "boolean",
+ "default": false
},
"chunk": {
"type": "string"
@@ -25,17 +25,13 @@
},
"targetArchs": {
"type": "array",
- "default": [
- "arm",
- "arm64",
- "ia32"
- ],
"items": {
"type": "string",
"enum": [
"arm",
"arm64",
- "ia32"
+ "ia32",
+ "ia64"
]
}
},
@@ -43,6 +39,13 @@
"type": "boolean",
"default": false
},
+ "snapshotInDocker": {
+ "type": "boolean"
+ },
+ "skipSnapshotTools": {
+ "type": "boolean",
+ "default": false
+ },
"v8Version": {
"type": "string"
},
@@ -60,4 +63,4 @@
"webpackConfig"
],
"additionalProperties": false
-}
+}
\ No newline at end of file
diff --git a/plugins/PlatformFSPlugin.ts b/plugins/PlatformFSPlugin.ts
index 5619ea4e..91daccac 100644
--- a/plugins/PlatformFSPlugin.ts
+++ b/plugins/PlatformFSPlugin.ts
@@ -8,7 +8,7 @@ export interface PlatformFSPluginOptions {
platform?: string;
/**
- * A list of all platforms. By default it is `["ios", "android"]`.
+ * A list of all platforms. By default it is `["ios", "android", "desktop"]`.
*/
platforms?: string[];
@@ -18,6 +18,8 @@ export interface PlatformFSPluginOptions {
ignore?: string[];
}
+const internalPlatforms = ["ios", "android"];
+
export class PlatformFSPlugin {
protected readonly platform: string;
protected readonly platforms: ReadonlyArray;
@@ -26,7 +28,7 @@ export class PlatformFSPlugin {
constructor({ platform, platforms, ignore }: PlatformFSPluginOptions) {
this.platform = platform || "";
- this.platforms = platforms || ["ios", "android"];
+ this.platforms = platforms || internalPlatforms;
this.ignore = ignore || [];
}
@@ -58,6 +60,8 @@ export function mapFileSystem(args: MapFileSystemArgs): any {
const fs = compiler.inputFileSystem;
ignore = args.ignore || [];
+ const isExternal = internalPlatforms.indexOf(platform) === -1;
+
const minimatchFileFilters = ignore.map(pattern => {
const minimatchFilter = minimatch.filter(pattern);
return file => minimatchFilter(relative(context, file));
@@ -80,7 +84,7 @@ export function mapFileSystem(args: MapFileSystemArgs): any {
return join(dir, name.substr(0, name.length - currentPlatformExt.length) + ext);
}
return file;
- }
+ };
const isNotIgnored = file => !isIgnored(file);
@@ -95,7 +99,28 @@ export function mapFileSystem(args: MapFileSystemArgs): any {
function platformSpecificFile(file: string): string {
const {dir, name, ext} = parseFile(file);
- const platformFilePath = join(dir, `${name}.${platform}${ext}`);
+ let platformFilePath = join(dir, `${name}.${platform}${ext}`);
+
+ if (isExternal && dir.indexOf("/@nativescript/core/") !== -1) {
+ let replacedPath;
+ try {
+ replacedPath = dir.replace(
+ /node_modules(\/[^/]+)?\/@nativescript\/core/,
+ `node_modules/nativescript-platform-${platform}`
+ );
+
+ platformFilePath = require.resolve(join(replacedPath, `${name}.${platform}${ext}`));
+ } catch (e) {
+ if (replacedPath) {
+ if (ext === ".d") {
+ platformFilePath = undefined;
+ } else {
+ platformFilePath = join(replacedPath, `${name}${ext}`);
+ }
+ }
+ }
+ }
+
return platformFilePath;
}
diff --git a/plugins/WatchStateLoggerPlugin.ts b/plugins/WatchStateLoggerPlugin.ts
index 7e5302d7..2767809d 100644
--- a/plugins/WatchStateLoggerPlugin.ts
+++ b/plugins/WatchStateLoggerPlugin.ts
@@ -29,15 +29,14 @@ export class WatchStateLoggerPlugin {
console.log(messages.compilationComplete);
}
- let emittedFiles = Object
+ const emittedFiles = Object
.keys(compilation.assets)
.filter(assetKey => compilation.assets[assetKey].emitted);
const chunkFiles = getChunkFiles(compilation);
-
process.send && process.send(messages.compilationComplete, error => null);
// Send emitted files so they can be LiveSynced if need be
- process.send && process.send({ emittedFiles, chunkFiles }, error => null);
+ process.send && process.send({ emittedFiles, chunkFiles, hash: compilation.hash }, error => null);
});
}
}
diff --git a/snapshot/android/project-snapshot-generator-cli-ags-parser.js b/snapshot/android/project-snapshot-generator-cli-ags-parser.js
index 76af75b1..6d3b9b61 100644
--- a/snapshot/android/project-snapshot-generator-cli-ags-parser.js
+++ b/snapshot/android/project-snapshot-generator-cli-ags-parser.js
@@ -9,6 +9,14 @@ module.exports = function parseProjectSnapshotGeneratorArgs() {
result.useLibs = parseBool(result.useLibs);
}
+ if (result.snapshotInDocker !== undefined) {
+ result.snapshotInDocker = parseBool(result.snapshotInDocker);
+ }
+
+ if (result.skipSnapshotTools !== undefined) {
+ result.skipSnapshotTools = parseBool(result.skipSnapshotTools);
+ }
+
if (result.install !== undefined) {
result.install = parseBool(result.install);
}
@@ -22,7 +30,7 @@ function parseJsonFromProcessArgs() {
var currentKey = "";
var currentValue = "";
- args.forEach(function(value, index, array) {
+ args.forEach(function (value, index, array) {
if (value.startsWith("--")) { // if is key
addKeyAndValueToResult(currentKey, currentValue, result);
currentKey = value.slice(2);
diff --git a/snapshot/android/project-snapshot-generator.js b/snapshot/android/project-snapshot-generator.js
index be5ef1f3..6d168149 100644
--- a/snapshot/android/project-snapshot-generator.js
+++ b/snapshot/android/project-snapshot-generator.js
@@ -1,30 +1,26 @@
-const { dirname, isAbsolute, join, resolve } = require("path");
-const { existsSync, readFileSync, writeFileSync } = require("fs");
+const { isAbsolute, join, resolve, sep } = require("path");
+const { readFileSync, writeFileSync } = require("fs");
const shelljs = require("shelljs");
const semver = require("semver");
const SnapshotGenerator = require("./snapshot-generator");
const {
- CONSTANTS,
- createDirectory,
- getJsonFile,
+ CONSTANTS
} = require("./utils");
-const { getPackageJson } = require("../../projectHelpers");
const {
ANDROID_PROJECT_DIR,
ANDROID_APP_PATH,
ANDROID_CONFIGURATIONS_PATH,
getAndroidRuntimeVersion,
getAndroidV8Version,
+ getRuntimeNdkRevision,
getMksnapshotParams
} = require("../../androidProjectHelpers");
-const MIN_ANDROID_RUNTIME_VERSION = "3.0.0";
+// min version with settings.json file specifying the V8 version
+const MIN_ANDROID_RUNTIME_VERSION = "5.2.1";
const VALID_ANDROID_RUNTIME_TAGS = Object.freeze(["next", "rc"]);
-const V8_VERSIONS_FILE_NAME = "v8-versions.json";
-const V8_VERSIONS_URL = `https://raw.githubusercontent.com/NativeScript/android-runtime/master/${V8_VERSIONS_FILE_NAME}`;
-const V8_VERSIONS_LOCAL_PATH = resolve(CONSTANTS.SNAPSHOT_TMP_DIR, V8_VERSIONS_FILE_NAME);
const resolveRelativePath = (path) => {
if (path)
@@ -103,7 +99,7 @@ ProjectSnapshotGenerator.installSnapshotArtefacts = function (projectRoot) {
// Copy the libs to the specified destination in the platforms folder
shelljs.mkdir("-p", libsDestinationPath);
- shelljs.cp("-R", join(buildPath, "ndk-build/libs") + "/", libsDestinationPath);
+ shelljs.cp("-R", join(buildPath, "ndk-build/libs") + sep, libsDestinationPath);
} else {
// useLibs = false
const blobsSrcPath = join(buildPath, "snapshots/blobs");
@@ -111,12 +107,7 @@ ProjectSnapshotGenerator.installSnapshotArtefacts = function (projectRoot) {
const appPackageJsonPath = join(appPath, "package.json");
// Copy the blobs in the prepared app folder
- shelljs.cp("-R", blobsSrcPath + "/", resolve(appPath, "../snapshots"));
-
- /*
- Rename TNSSnapshot.blob files to snapshot.blob files. The xxd tool uses the file name for the name of the static array. This is why the *.blob files are initially named TNSSnapshot.blob. After the xxd step, they must be renamed to snapshot.blob, because this is the filename that the Android runtime is looking for.
- */
- shelljs.exec("find " + blobsDestinationPath + " -name '*.blob' -execdir mv {} snapshot.blob ';'");
+ shelljs.cp("-R", blobsSrcPath + sep, resolve(appPath, "../snapshots"));
// Update the package.json file
const appPackageJson = shelljs.test("-e", appPackageJsonPath) ? JSON.parse(readFileSync(appPackageJsonPath, 'utf8')) : {};
@@ -126,73 +117,6 @@ ProjectSnapshotGenerator.installSnapshotArtefacts = function (projectRoot) {
}
}
-const versionIsPrerelease = version => version.indexOf("-") > -1;
-const v8VersionsFileExists = () => existsSync(V8_VERSIONS_LOCAL_PATH);
-const saveV8VersionsFile = versionsMap =>
- writeFileSync(V8_VERSIONS_LOCAL_PATH, JSON.stringify(versionsMap));
-const readV8VersionsFile = () => JSON.parse(readFileSync(V8_VERSIONS_LOCAL_PATH));
-const fetchV8VersionsFile = () =>
- new Promise((resolve, reject) => {
- getJsonFile(V8_VERSIONS_URL)
- .then(versionsMap => {
- createDirectory(dirname(V8_VERSIONS_LOCAL_PATH));
- saveV8VersionsFile(versionsMap);
- return resolve(versionsMap);
- })
- .catch(reject);
- });
-
-const findV8Version = (runtimeVersion, v8VersionsMap) => {
- const runtimeRange = Object.keys(v8VersionsMap)
- .find(range => semver.satisfies(runtimeVersion, range));
-
- return v8VersionsMap[runtimeRange];
-}
-
-const getV8VersionsMap = runtimeVersion =>
- new Promise((resolve, reject) => {
- if (!v8VersionsFileExists() || versionIsPrerelease(runtimeVersion)) {
- fetchV8VersionsFile()
- .then(versionsMap => resolve({ versionsMap, latest: true }))
- .catch(reject);
- } else {
- const versionsMap = readV8VersionsFile();
- return resolve({ versionsMap, latest: false });
- }
- });
-
-ProjectSnapshotGenerator.prototype.getV8Version = function (generationOptions) {
- return new Promise((resolve, reject) => {
- const maybeV8Version = generationOptions.v8Version;
- if (maybeV8Version) {
- return resolve(maybeV8Version);
- }
-
- // try to get the V8 Version from the settings.json file in android runtime folder
- const runtimeV8Version = getAndroidV8Version(this.options.projectRoot);
- if(runtimeV8Version) {
- return resolve(runtimeV8Version);
- }
-
- const runtimeVersion = getAndroidRuntimeVersion(this.options.projectRoot);
- getV8VersionsMap(runtimeVersion)
- .then(({ versionsMap, latest }) => {
- const v8Version = findV8Version(runtimeVersion, versionsMap);
-
- if (!v8Version && !latest) {
- fetchV8VersionsFile().then(latestVersionsMap => {
- const version = findV8Version(runtimeVersion, latestVersionsMap)
- return resolve(version);
- })
- .catch(reject);
- } else {
- return resolve(v8Version);
- }
- })
- .catch(reject);
- });
-}
-
ProjectSnapshotGenerator.prototype.validateAndroidRuntimeVersion = function () {
const currentRuntimeVersion = getAndroidRuntimeVersion(this.options.projectRoot);
@@ -209,6 +133,11 @@ ProjectSnapshotGenerator.prototype.validateAndroidRuntimeVersion = function () {
}
ProjectSnapshotGenerator.prototype.generate = function (generationOptions) {
+ if (generationOptions.skipSnapshotTools) {
+ console.log("Skipping snapshot tools.");
+ return Promise.resolve();
+ }
+
generationOptions = generationOptions || {};
console.log("Running snapshot generation with the following arguments: ");
@@ -219,49 +148,50 @@ ProjectSnapshotGenerator.prototype.generate = function (generationOptions) {
shelljs.mkdir("-p", this.getBuildPath());
const snapshotToolsPath = resolveRelativePath(generationOptions.snapshotToolsPath) || CONSTANTS.SNAPSHOT_TMP_DIR;
- const androidNdkPath = generationOptions.androidNdkPath || process.env.ANDROID_NDK_HOME;
-
console.log("Snapshot tools path: " + snapshotToolsPath);
// Generate snapshots
const generator = new SnapshotGenerator({ buildPath: this.getBuildPath() });
-
const noV8VersionFoundMessage = `Cannot find suitable v8 version!`;
- let shouldRethrow = false;
-
const mksnapshotParams = getMksnapshotParams(this.options.projectRoot);
+ const recommendedAndroidNdkRevision = getRuntimeNdkRevision(this.options.projectRoot);
+ const v8Version = generationOptions.v8Version || getAndroidV8Version(this.options.projectRoot);
+ if (!v8Version) {
+ throw new Error(noV8VersionFoundMessage);
+ }
- return this.getV8Version(generationOptions).then(v8Version => {
- shouldRethrow = true;
- if (!v8Version) {
- throw new Error(noV8VersionFoundMessage);
- }
+ // NOTE: Order is important! Add new archs at the end of the array
+ const defaultTargetArchs = ["arm", "arm64", "ia32", "ia64"];
+ const runtimeVersion = getAndroidRuntimeVersion(this.options.projectRoot);
+ if (runtimeVersion && semver.lt(semver.coerce(runtimeVersion), "6.0.2")) {
+ const indexOfIa64 = defaultTargetArchs.indexOf("ia64");
+ // Before 6.0.2 version of Android runtime we supported only arm, arm64 and ia32.
+ defaultTargetArchs.splice(indexOfIa64, defaultTargetArchs.length - indexOfIa64);
+ }
- const options = {
- snapshotToolsPath,
- targetArchs: generationOptions.targetArchs || ["arm", "arm64", "ia32"],
- v8Version: generationOptions.v8Version || v8Version,
- preprocessedInputFile: generationOptions.preprocessedInputFile,
- useLibs: generationOptions.useLibs || false,
- inputFiles: generationOptions.inputFiles || [join(this.options.projectRoot, "__snapshot.js")],
- androidNdkPath,
- mksnapshotParams: mksnapshotParams
- };
-
- return generator.generate(options).then(() => {
- console.log("Snapshots build finished succesfully!");
-
- if (generationOptions.install) {
- ProjectSnapshotGenerator.cleanSnapshotArtefacts(this.options.projectRoot);
- ProjectSnapshotGenerator.installSnapshotArtefacts(this.options.projectRoot);
- console.log(generationOptions.useLibs ?
- "Snapshot is included in the app as dynamically linked library (.so file)." :
- "Snapshot is included in the app as binary .blob file. The more space-efficient option is to embed it in a dynamically linked library (.so file).");
- }
- });
- }).catch(error => {
- throw shouldRethrow ?
- error :
- new Error(`${noV8VersionFoundMessage} Original error: ${error.message || error}`);
- });
+ const options = {
+ snapshotToolsPath,
+ targetArchs: generationOptions.targetArchs || defaultTargetArchs,
+ v8Version: generationOptions.v8Version || v8Version,
+ preprocessedInputFile: generationOptions.preprocessedInputFile,
+ useLibs: generationOptions.useLibs || false,
+ inputFiles: generationOptions.inputFiles || [join(this.options.projectRoot, "__snapshot.js")],
+ androidNdkPath: generationOptions.androidNdkPath,
+ mksnapshotParams: mksnapshotParams,
+ snapshotInDocker: generationOptions.snapshotInDocker,
+ recommendedAndroidNdkRevision,
+ runtimeVersion
+ };
+
+ return generator.generate(options).then(() => {
+ console.log("Snapshots build finished succesfully!");
+
+ if (generationOptions.install) {
+ ProjectSnapshotGenerator.cleanSnapshotArtefacts(this.options.projectRoot);
+ ProjectSnapshotGenerator.installSnapshotArtefacts(this.options.projectRoot);
+ console.log(generationOptions.useLibs ?
+ "Snapshot is included in the app as dynamically linked library (.so file)." :
+ "Snapshot is included in the app as binary .blob file. The more space-efficient option is to embed it in a dynamically linked library (.so file).");
+ }
+ });;
}
diff --git a/snapshot/android/snapshot-generator.js b/snapshot/android/snapshot-generator.js
index 8c177809..eb055dcd 100644
--- a/snapshot/android/snapshot-generator.js
+++ b/snapshot/android/snapshot-generator.js
@@ -1,12 +1,16 @@
const fs = require("fs");
-const { dirname, join, EOL } = require("path");
-const os = require("os");
+const { dirname, relative, join, EOL } = require("path");
const child_process = require("child_process");
-
+const { convertToUnixPath, warn } = require("../../lib/utils");
+const { isWindows } = require("./utils");
+const PropertiesReader = require('properties-reader');
+const semver = require("semver");
const shelljs = require("shelljs");
-const { createDirectory, downloadFile } = require("./utils");
+const { createDirectory, downloadFile, getHostOS, getHostOSArch, CONSTANTS, has32BitArch, isMacOSCatalinaOrHigher, isSubPath } = require("./utils");
+const SNAPSHOTS_DOCKER_IMAGE = "nativescript/v8-snapshot:latest";
+const SNAPSHOT_TOOLS_DIR_NAME = "mksnapshot-tools";
const NDK_BUILD_SEED_PATH = join(__dirname, "snapshot-generator-tools/ndk-build");
const BUNDLE_PREAMBLE_PATH = join(__dirname, "snapshot-generator-tools/bundle-preamble.js");
const BUNDLE_ENDING_PATH = join(__dirname, "snapshot-generator-tools/bundle-ending.js");
@@ -14,6 +18,8 @@ const INCLUDE_GRADLE_PATH = join(__dirname, "snapshot-generator-tools/include.gr
const MKSNAPSHOT_TOOLS_DOWNLOAD_ROOT_URL = "https://raw.githubusercontent.com/NativeScript/mksnapshot-tools/production/";
const MKSNAPSHOT_TOOLS_DOWNLOAD_TIMEOUT = 60000;
const SNAPSHOT_BLOB_NAME = "TNSSnapshot";
+const DOCKER_IMAGE_OS = "linux";
+const DOCKER_IMAGE_ARCH = "x64";
function shellJsExecuteInDir(dir, action) {
const currentDir = shelljs.pwd();
@@ -25,17 +31,6 @@ function shellJsExecuteInDir(dir, action) {
}
}
-function getHostOS() {
- const hostOS = os.type().toLowerCase();
- if (hostOS.startsWith("darwin"))
- return "darwin";
- if (hostOS.startsWith("linux"))
- return "linux";
- if (hostOS.startsWith("win"))
- return "win";
- return hostOS;
-}
-
function SnapshotGenerator(options) {
this.buildPath = options.buildPath || join(__dirname, "build");
}
@@ -43,6 +38,24 @@ module.exports = SnapshotGenerator;
SnapshotGenerator.SNAPSHOT_PACKAGE_NANE = "nativescript-android-snapshot";
+SnapshotGenerator.prototype.shouldSnapshotInDocker = function (hostOS, targetArchs, currentRuntimeVersion) {
+ let shouldSnapshotInDocker = false;
+ const minRuntimeWithoutMacOSSnapshotTools = "6.3.0";
+ const generateInDockerMessage = "The snapshots will be generated in a docker container.";
+ if (hostOS === CONSTANTS.WIN_OS_NAME) {
+ console.log(`The V8 snapshot tools are not supported on Windows. ${generateInDockerMessage}`);
+ shouldSnapshotInDocker = true;
+ } else if (hostOS === CONSTANTS.MAC_OS_NAME && semver.gte(currentRuntimeVersion, minRuntimeWithoutMacOSSnapshotTools)) {
+ console.log(`Starting from Android Runtime 6.3.0, the Snapshot tools are no longer supported on macOS. ${generateInDockerMessage}`);
+ shouldSnapshotInDocker = true;
+ } else if (isMacOSCatalinaOrHigher() && has32BitArch(targetArchs)) {
+ console.log(`Starting from macOS Catalina, the 32-bit processes are no longer supported. ${generateInDockerMessage}`);
+ shouldSnapshotInDocker = true;
+ }
+
+ return shouldSnapshotInDocker;
+}
+
SnapshotGenerator.prototype.preprocessInputFiles = function (inputFiles, outputFile) {
// Make some modifcations on the original bundle and save it on the specified path
const bundlePreambleContent = fs.readFileSync(BUNDLE_PREAMBLE_PATH, "utf8");
@@ -53,7 +66,7 @@ SnapshotGenerator.prototype.preprocessInputFiles = function (inputFiles, outputF
// Example:
// (function() {
// some code here
- // })()
+ // })()
// // sourceMapUrl......
// ** when we join without `;` here, the next IIFE is assumed as a function call to the result of the first IIFE
// (function() {
@@ -67,9 +80,26 @@ SnapshotGenerator.prototype.preprocessInputFiles = function (inputFiles, outputF
const snapshotToolsDownloads = {};
-SnapshotGenerator.prototype.downloadMksnapshotTool = function (snapshotToolsPath, v8Version, targetArch) {
- const hostOS = getHostOS();
- const mksnapshotToolRelativePath = join("mksnapshot-tools", "v8-v" + v8Version, hostOS + "-" + os.arch(), "mksnapshot-" + targetArch);
+SnapshotGenerator.prototype.downloadMksnapshotTools = function (snapshotToolsPath, v8Version, targetArchs, snapshotInDocker) {
+ var toolsOS = "";
+ var toolsArch = "";
+ if (snapshotInDocker) {
+ toolsOS = DOCKER_IMAGE_OS;
+ toolsArch = DOCKER_IMAGE_ARCH;
+ } else {
+ toolsOS = getHostOS();
+ toolsArch = getHostOSArch();
+ }
+
+ return Promise.all(targetArchs.map((arch) => {
+ return this.downloadMksnapshotTool(snapshotToolsPath, v8Version, arch, toolsOS, toolsArch).then(path => {
+ return { path, arch };
+ });
+ }));
+}
+
+SnapshotGenerator.prototype.downloadMksnapshotTool = function (snapshotToolsPath, v8Version, targetArch, hostOS, hostArch) {
+ const mksnapshotToolRelativePath = join(SNAPSHOT_TOOLS_DIR_NAME, "v8-v" + v8Version, hostOS + "-" + hostArch, "mksnapshot-" + targetArch);
const mksnapshotToolPath = join(snapshotToolsPath, mksnapshotToolRelativePath);
if (fs.existsSync(mksnapshotToolPath))
return Promise.resolve(mksnapshotToolPath);
@@ -101,77 +131,76 @@ SnapshotGenerator.prototype.convertToAndroidArchName = function (archName) {
case "arm": return "armeabi-v7a";
case "arm64": return "arm64-v8a";
case "ia32": return "x86";
- case "x64": return "x64";
+ case "ia64": return "x86_64";
default: return archName;
}
}
-SnapshotGenerator.prototype.runMksnapshotTool = function (snapshotToolsPath, inputFile, v8Version, targetArchs, buildCSource, mksnapshotParams) {
+SnapshotGenerator.prototype.generateSnapshots = function (snapshotToolsPath, inputFile, v8Version, targetArchs, buildCSource, mksnapshotParams, snapshotInDocker) {
// Cleans the snapshot build folder
shelljs.rm("-rf", join(this.buildPath, "snapshots"));
+ return this.downloadMksnapshotTools(snapshotToolsPath, v8Version, targetArchs, snapshotInDocker).then((localTools) => {
+ var shouldDownloadDockerTools = false;
+ if (!snapshotInDocker) {
+ snapshotInDocker = localTools.some(tool => !this.canUseSnapshotTool(tool.path));
+ shouldDownloadDockerTools = snapshotInDocker;
+ }
- const mksnapshotStdErrPath = join(this.buildPath, "mksnapshot-stderr.txt");
+ if (shouldDownloadDockerTools) {
+ return this.downloadMksnapshotTools(snapshotToolsPath, v8Version, targetArchs, snapshotInDocker).then((dockerTools) => {
+ console.log(`Generating snapshots in a docker container.`);
+ return this.runMksnapshotTools(snapshotToolsPath, dockerTools, inputFile, mksnapshotParams, buildCSource, snapshotInDocker);
+ });
+ }
- return Promise.all(targetArchs.map((arch) => {
- return this.downloadMksnapshotTool(snapshotToolsPath, v8Version, arch).then((currentArchMksnapshotToolPath) => {
- if (!fs.existsSync(currentArchMksnapshotToolPath)) {
- throw new Error("Can't find mksnapshot tool for " + arch + " at path " + currentArchMksnapshotToolPath);
- }
+ return this.runMksnapshotTools(snapshotToolsPath, localTools, inputFile, mksnapshotParams, buildCSource, snapshotInDocker);
+ });
+}
- const androidArch = this.convertToAndroidArchName(arch);
- console.log("***** Generating snapshot for " + androidArch + " *****");
- // Generate .blob file
- const currentArchBlobOutputPath = join(this.buildPath, "snapshots/blobs", androidArch);
- shelljs.mkdir("-p", currentArchBlobOutputPath);
- var params = "--profile_deserialization";
- if (mksnapshotParams) {
- // Starting from android runtime 5.3.0, the parameters passed to mksnapshot are read from the settings.json file
- params = mksnapshotParams;
- }
- const command = `${currentArchMksnapshotToolPath} ${inputFile} --startup_blob ${join(currentArchBlobOutputPath, `${SNAPSHOT_BLOB_NAME}.blob`)} ${params}`;
-
- return new Promise((resolve, reject) => {
- const child = child_process.exec(command, { encoding: "utf8" }, (error, stdout, stderr) => {
- const errorHeader = `Target architecture: ${androidArch}\n`;
- let errorFooter = ``;
- if (stderr.length || error) {
- try {
- require(inputFile);
- } catch (e) {
- errorFooter = `\nJavaScript execution error: ${e.stack}$`;
- }
- }
-
- if (stderr.length) {
- const message = `${errorHeader}${stderr}${errorFooter}`;
- reject(new Error(message));
- } else if (error) {
- error.message = `${errorHeader}${error.message}${errorFooter}`;
- reject(error);
- } else {
- console.log(stdout);
- resolve();
- }
- })
- }).then(() => {
- // Generate .c file
- if (buildCSource) {
- const currentArchSrcOutputPath = join(this.buildPath, "snapshots/src", androidArch);
- shelljs.mkdir("-p", currentArchSrcOutputPath);
- shellJsExecuteInDir(currentArchBlobOutputPath, function () {
- shelljs.exec(`xxd -i ${SNAPSHOT_BLOB_NAME}.blob > ${join(currentArchSrcOutputPath, `${SNAPSHOT_BLOB_NAME}.c`)}`);
- });
- }
+SnapshotGenerator.prototype.runMksnapshotTools = function (snapshotToolsBasePath, snapshotTools, inputFile, mksnapshotParams, buildCSource, snapshotInDocker) {
+ let currentSnapshotOperation = Promise.resolve();
+ const canRunInParallel = !snapshotInDocker;
+ return Promise.all(snapshotTools.map((tool) => {
+ if (canRunInParallel) {
+ return this.runMksnapshotTool(tool, mksnapshotParams, inputFile, snapshotInDocker, snapshotToolsBasePath, buildCSource);
+ } else {
+ currentSnapshotOperation = currentSnapshotOperation.then(() => {
+ return this.runMksnapshotTool(tool, mksnapshotParams, inputFile, snapshotInDocker, snapshotToolsBasePath, buildCSource);
});
- });
+
+ return currentSnapshotOperation;
+ }
})).then(() => {
console.log("***** Finished generating snapshots. *****");
});
}
-SnapshotGenerator.prototype.buildSnapshotLibs = function (androidNdkBuildPath, targetArchs) {
+SnapshotGenerator.prototype.canUseSnapshotTool = function (snapshotToolPath) {
+ try {
+ child_process.execSync(`${snapshotToolPath} --help`);
+ return true;
+ }
+ catch (error) {
+ console.log(`Unable to execute '${snapshotToolPath}' locally.Error message: '${error.message}'`);
+ return false;
+ }
+}
+
+SnapshotGenerator.prototype.setupDocker = function () {
+ try {
+ child_process.execSync(`docker --version`);
+ }
+ catch (error) {
+ throw new Error(`Docker installation cannot be found. Install Docker and add it to your PATH in order to build snapshots.`);
+ }
+
+ child_process.execSync(`docker pull ${SNAPSHOTS_DOCKER_IMAGE}`);
+}
+
+SnapshotGenerator.prototype.buildSnapshotLibs = function (androidNdkPath, recommendedAndroidNdkRevision, targetArchs) {
// Compile *.c files to produce *.so libraries with ndk-build tool
+ const androidNdkBuildPath = this.getAndroidNdkBuildPath(androidNdkPath, recommendedAndroidNdkRevision);
const ndkBuildPath = join(this.buildPath, "ndk-build");
const androidArchs = targetArchs.map(arch => this.convertToAndroidArchName(arch));
console.log("Building native libraries for " + androidArchs.join());
@@ -185,6 +214,78 @@ SnapshotGenerator.prototype.buildSnapshotLibs = function (androidNdkBuildPath, t
return join(ndkBuildPath, "libs");
}
+SnapshotGenerator.prototype.getAndroidNdkBuildPath = function (androidNdkPath, recommendedAndroidNdkRevision) {
+ const ndkBuildExecutableName = isWindows() ? "ndk-build.cmd" : "ndk-build";
+ let hasNdk = false;
+ // fallback for Android Runtime < 6.2.0 with the 6.1.0 value
+ recommendedAndroidNdkRevision = recommendedAndroidNdkRevision || "20.0.5594570";
+ let androidNdkBuildPath = "";
+ if (androidNdkPath) {
+ // specified by the user
+ const localNdkRevision = this.getAndroidNdkRevision(androidNdkPath);
+ androidNdkBuildPath = join(androidNdkPath, ndkBuildExecutableName);
+ if (!fs.existsSync(androidNdkBuildPath)) {
+ throw new Error(`The provided Android NDK path does not contain ${ndkBuildExecutableName} executable.`);
+ } else if (localNdkRevision !== recommendedAndroidNdkRevision) {
+ warn(this.getRecommendedNdkWarning(localNdkRevision, recommendedAndroidNdkRevision));
+ }
+
+ hasNdk = true;
+ console.log("Using Android NDK from webpack.config.");
+ } else {
+ if (process.env.ANDROID_NDK_HOME) {
+ // check ANDROID_NDK_HOME
+ const localNdkRevision = this.getAndroidNdkRevision(process.env.ANDROID_NDK_HOME);
+ androidNdkBuildPath = join(process.env.ANDROID_NDK_HOME, ndkBuildExecutableName);
+ if (fs.existsSync(androidNdkBuildPath)) {
+ hasNdk = true;
+ console.log("Using Android NDK from ANDROID_NDK_HOME.");
+ }
+
+ if (localNdkRevision !== recommendedAndroidNdkRevision) {
+ warn(this.getRecommendedNdkWarning(localNdkRevision, recommendedAndroidNdkRevision));
+ }
+ }
+
+ if (!hasNdk) {
+ // available globally
+ androidNdkBuildPath = ndkBuildExecutableName;
+ try {
+ child_process.execSync(`${androidNdkBuildPath} --version`, { stdio: "ignore" });
+ hasNdk = true;
+ console.log("Using Android NDK from PATH.");
+ console.log(`Cannot determine the version of the global Android NDK. The recommended versions is v${recommendedAndroidNdkRevision}`);
+ } catch (_) {
+ }
+ }
+
+ if (!hasNdk) {
+ // installed in ANDROID_HOME
+ androidNdkBuildPath = join(process.env.ANDROID_HOME, "ndk", recommendedAndroidNdkRevision, ndkBuildExecutableName);
+ if (fs.existsSync(androidNdkBuildPath)) {
+ hasNdk = true;
+ console.log("Using Android NDK from ANDROID_HOME.");
+ }
+ }
+ }
+
+ if (!hasNdk) {
+ throw new Error(`Android NDK v${recommendedAndroidNdkRevision} is not installed. Install it from Android Studio or download it and set ANDROID_NDK_HOME or add it to your PATH. You can find installation instructions in this article: https://developer.android.com/studio/projects/install-ndk#specific-version`);
+ }
+
+ return androidNdkBuildPath;
+}
+
+SnapshotGenerator.prototype.getAndroidNdkRevision = function (androidNdkPath) {
+ const ndkPropertiesFile = join(androidNdkPath, "source.properties");
+ if (fs.existsSync(ndkPropertiesFile)) {
+ const properties = PropertiesReader(ndkPropertiesFile);
+ return properties.get("Pkg.Revision");
+ } else {
+ return null;
+ }
+}
+
SnapshotGenerator.prototype.buildIncludeGradle = function () {
shelljs.cp(INCLUDE_GRADLE_PATH, join(this.buildPath, "include.gradle"));
}
@@ -199,21 +300,169 @@ SnapshotGenerator.prototype.generate = function (options) {
console.log("***** Starting snapshot generation using V8 version: ", options.v8Version);
this.preprocessInputFiles(options.inputFiles, preprocessedInputFile);
+ const hostOS = getHostOS();
+ const snapshotInDocker = options.snapshotInDocker || this.shouldSnapshotInDocker(hostOS, options.targetArchs, options.runtimeVersion);
// generates the actual .blob and .c files
- return this.runMksnapshotTool(
+ return this.generateSnapshots(
options.snapshotToolsPath,
preprocessedInputFile,
options.v8Version,
options.targetArchs,
options.useLibs,
- options.mksnapshotParams
+ options.mksnapshotParams,
+ snapshotInDocker
).then(() => {
this.buildIncludeGradle();
if (options.useLibs) {
- const androidNdkBuildPath = options.androidNdkPath ? join(options.androidNdkPath, "ndk-build") : "ndk-build";
- this.buildSnapshotLibs(androidNdkBuildPath, options.targetArchs);
+ this.buildSnapshotLibs(options.androidNdkPath, options.recommendedAndroidNdkRevision, options.targetArchs);
}
return this.buildPath;
});
}
+
+SnapshotGenerator.prototype.getSnapshotToolCommand = function (snapshotToolPath, inputFilePath, outputPath, toolParams) {
+ return `${snapshotToolPath} ${inputFilePath} --startup_blob ${outputPath} ${toolParams}`;
+}
+
+SnapshotGenerator.prototype.getXxdCommand = function (srcOutputDir, xxdLocation) {
+ // https://github.com/NativeScript/docker-images/tree/master/v8-snapshot/bin
+ return `${xxdLocation || ""}xxd -i ${SNAPSHOT_BLOB_NAME}.blob > ${srcOutputDir}`;
+}
+
+SnapshotGenerator.prototype.getPathInDocker = function (mappedLocalDir, mappedDockerDir, targetPath) {
+ if (!isSubPath(mappedLocalDir, targetPath)) {
+ throw new Error(`Cannot determine a docker path. '${targetPath}' should be inside '${mappedLocalDir}'`)
+ }
+
+ const pathInDocker = join(mappedDockerDir, relative(mappedLocalDir, targetPath));
+
+ return convertToUnixPath(pathInDocker);
+}
+
+SnapshotGenerator.prototype.handleSnapshotToolResult = function (error, stdout, stderr, inputFile, androidArch) {
+ let toolError = null;
+ const errorHeader = `Target architecture: ${androidArch}\n`;
+ let errorFooter = ``;
+ if ((stderr && stderr.length) || error) {
+ try {
+ require(inputFile);
+ }
+ catch (e) {
+ errorFooter = `\nJavaScript execution error: ${e.stack}$`;
+ }
+ }
+
+ if (stderr && stderr.length) {
+ const message = `${errorHeader}${stderr}${errorFooter}`;
+ toolError = new Error(message);
+ }
+ else if (error) {
+ error.message = `${errorHeader}${error.message}${errorFooter}`;
+ toolError = error;
+ } else {
+ console.log(stdout);
+ }
+
+ return toolError;
+}
+
+SnapshotGenerator.prototype.copySnapshotTool = function (allToolsDir, targetTool, destinationDir) {
+ // we cannot mount the source tools folder as its not shared by default:
+ // docker: Error response from daemon: Mounts denied:
+ // The path /var/folders/h2/1yck52fx2mg7c790vhcw90s8087sk8/T/snapshot-tools/mksnapshot-tools
+ // is not shared from OS X and is not known to Docker.
+ const toolPathRelativeToAllToolsDir = relative(allToolsDir, targetTool);
+ const toolDestinationPath = join(destinationDir, toolPathRelativeToAllToolsDir)
+ createDirectory(dirname(toolDestinationPath));
+ shelljs.cp(targetTool, toolDestinationPath);
+
+ return toolDestinationPath;
+}
+
+SnapshotGenerator.prototype.buildCSource = function (androidArch, blobInputDir, snapshotInDocker) {
+ const srcOutputDir = join(this.buildPath, "snapshots/src", androidArch);
+ createDirectory(srcOutputDir);
+ let command = "";
+ if (snapshotInDocker) {
+ const blobsInputInDocker = `/blobs/${androidArch}`
+ const srcOutputDirInDocker = `/dist/src/${androidArch}`;
+ const outputPathInDocker = this.getPathInDocker(srcOutputDir, srcOutputDirInDocker, join(srcOutputDir, `${SNAPSHOT_BLOB_NAME}.c`));
+ const buildCSourceCommand = this.getXxdCommand(outputPathInDocker, "/bin/");
+ command = `docker run --rm -v "${blobInputDir}:${blobsInputInDocker}" -v "${srcOutputDir}:${srcOutputDirInDocker}" ${SNAPSHOTS_DOCKER_IMAGE} /bin/sh -c "cd ${blobsInputInDocker} && ${buildCSourceCommand}"`;
+ }
+ else {
+ command = this.getXxdCommand(join(srcOutputDir, `${SNAPSHOT_BLOB_NAME}.c`));
+ }
+ shellJsExecuteInDir(blobInputDir, function () {
+ shelljs.exec(command);
+ });
+}
+
+SnapshotGenerator.prototype.getRecommendedNdkWarning = function (localNdkRevision, recommendedAndroidNdkRevision) {
+ if (localNdkRevision) {
+ return `The provided Android NDK is v${localNdkRevision} while the required one is v${recommendedAndroidNdkRevision}`;
+ } else {
+ return `The provided Android NDK version is different than the required one - v${recommendedAndroidNdkRevision}`;
+ }
+}
+
+SnapshotGenerator.prototype.runMksnapshotTool = function (tool, mksnapshotParams, inputFile, snapshotInDocker, snapshotToolsPath, buildCSource) {
+ const toolPath = tool.path;
+ const androidArch = this.convertToAndroidArchName(tool.arch);
+ if (!fs.existsSync(toolPath)) {
+ throw new Error(`Can't find mksnapshot tool for ${androidArch} at path ${toolPath}`);
+ }
+
+ const tempFolders = [];
+ return new Promise((resolve, reject) => {
+ console.log("***** Generating snapshot for " + androidArch + " *****");
+ const inputFileDir = dirname(inputFile);
+ const blobOutputDir = join(this.buildPath, "snapshots/blobs", androidArch);
+ createDirectory(blobOutputDir);
+ const toolParams = mksnapshotParams || "--profile_deserialization";
+
+ let command = "";
+ if (snapshotInDocker) {
+ this.setupDocker();
+ const appDirInDocker = "/app";
+ const blobOutputDirInDocker = `/dist/blobs/${androidArch}`;
+ const toolsTempFolder = join(inputFileDir, "tmp");
+ tempFolders.push(toolsTempFolder);
+ const toolPathInAppDir = this.copySnapshotTool(snapshotToolsPath, toolPath, toolsTempFolder);
+ const toolPathInDocker = this.getPathInDocker(inputFileDir, appDirInDocker, toolPathInAppDir);
+ const inputFilePathInDocker = this.getPathInDocker(inputFileDir, appDirInDocker, inputFile);
+ const outputPathInDocker = this.getPathInDocker(blobOutputDir, blobOutputDirInDocker, join(blobOutputDir, `${SNAPSHOT_BLOB_NAME}.blob`));
+ const toolCommandInDocker = this.getSnapshotToolCommand(toolPathInDocker, inputFilePathInDocker, outputPathInDocker, toolParams);
+ command = `docker run --rm -v "${inputFileDir}:${appDirInDocker}" -v "${blobOutputDir}:${blobOutputDirInDocker}" ${SNAPSHOTS_DOCKER_IMAGE} /bin/sh -c "${toolCommandInDocker}"`;
+ } else {
+ command = this.getSnapshotToolCommand(toolPath, inputFile, join(blobOutputDir, `${SNAPSHOT_BLOB_NAME}.blob`), toolParams);
+ }
+
+ // Generate .blob file
+ child_process.exec(command, { encoding: "utf8" }, (error, stdout, stderr) => {
+ tempFolders.forEach(tempFolder => {
+ shelljs.rm("-rf", tempFolder);
+ });
+
+ const snapshotError = this.handleSnapshotToolResult(error, stdout, stderr, inputFile, androidArch);
+ if (snapshotError) {
+ return reject(snapshotError);
+ }
+
+ return resolve(blobOutputDir);
+ });
+ }).then((blobOutputDir) => {
+ // Generate .c file
+ if (buildCSource) {
+ this.buildCSource(androidArch, blobOutputDir, snapshotInDocker)
+ }
+
+ /*
+ Rename TNSSnapshot.blob files to snapshot.blob files. The xxd tool uses the file name for the name of the static array.
+ This is why the *.blob files are initially named TNSSnapshot.blob.
+ After the xxd step, they must be renamed to snapshot.blob, because this is the filename that the Android runtime is looking for.
+ */
+ shelljs.mv(join(blobOutputDir, `${SNAPSHOT_BLOB_NAME}.blob`), join(blobOutputDir, `snapshot.blob`));
+ });
+}
diff --git a/snapshot/android/utils.js b/snapshot/android/utils.js
index 2b63ae28..947f0302 100644
--- a/snapshot/android/utils.js
+++ b/snapshot/android/utils.js
@@ -1,17 +1,69 @@
-const { chmodSync, createWriteStream, existsSync } = require("fs");
+const { chmodSync, createWriteStream } = require("fs");
const { tmpdir, EOL } = require("os");
-const { dirname, join } = require("path");
+const { join, relative, isAbsolute } = require("path");
+const os = require("os");
const { mkdir } = require("shelljs");
const { get } = require("request");
const { getProxySettings } = require("proxy-lib");
+const semver = require("semver");
const CONSTANTS = {
SNAPSHOT_TMP_DIR: join(tmpdir(), "snapshot-tools"),
+ MAC_OS_NAME: "darwin",
+ WIN_OS_NAME: "win",
+ LINUX_OS_NAME: "linux"
};
const createDirectory = dir => mkdir('-p', dir);
+function getHostOS() {
+ const hostOS = os.type().toLowerCase();
+ if (hostOS.startsWith(CONSTANTS.MAC_OS_NAME))
+ return CONSTANTS.MAC_OS_NAME;
+ if (hostOS.startsWith(CONSTANTS.LINUX_OS_NAME))
+ return CONSTANTS.LINUX_OS_NAME;
+ if (hostOS.startsWith(CONSTANTS.WIN_OS_NAME))
+ return CONSTANTS.WIN_OS_NAME;
+ return hostOS;
+}
+
+function getHostOSVersion() {
+ return os.release();
+}
+
+function getHostOSArch() {
+ return os.arch();
+}
+
+function has32BitArch(targetArchs) {
+ return (Array.isArray(targetArchs) && targetArchs.some(arch => arch === "arm" || arch === "ia32")) ||
+ (targetArchs === "arm" || targetArchs === "ia32");
+}
+
+function isSubPath(parentPath, childPath) {
+ const relativePath = relative(parentPath, childPath);
+
+ return relativePath === "" ||
+ (relativePath && !relativePath.startsWith('..') && !isAbsolute(relativePath));
+}
+
+function isMacOSCatalinaOrHigher() {
+ let isCatalinaOrHigher = false;
+ const catalinaVersion = "19.0.0";
+ const hostOS = getHostOS();
+ if (hostOS === CONSTANTS.MAC_OS_NAME) {
+ const hostOSVersion = getHostOSVersion();
+ isCatalinaOrHigher = semver.gte(hostOSVersion, catalinaVersion);
+ }
+
+ return isCatalinaOrHigher;
+}
+
+function isWindows() {
+ return getHostOS() === CONSTANTS.WIN_OS_NAME;
+}
+
const downloadFile = (url, destinationFilePath, timeout) =>
new Promise((resolve, reject) => {
getRequestOptions(url, timeout)
@@ -64,6 +116,13 @@ const getRequestOptions = (url, timeout) =>
module.exports = {
CONSTANTS,
createDirectory,
+ has32BitArch,
+ getHostOS,
+ getHostOSVersion,
+ getHostOSArch,
+ isMacOSCatalinaOrHigher,
downloadFile,
getJsonFile,
+ isSubPath,
+ isWindows
};
diff --git a/templates/tsconfig.tns.json b/templates/tsconfig.tns.json
index 95f2ecee..9ce50ed9 100644
--- a/templates/tsconfig.tns.json
+++ b/templates/tsconfig.tns.json
@@ -1,7 +1,7 @@
{
"extends": "./tsconfig",
"compilerOptions": {
- "module": "es2015",
+ "module": "esNext",
"moduleResolution": "node"
}
}
diff --git a/templates/webpack.angular.js b/templates/webpack.angular.js
index e6110ff0..f0b43bbe 100644
--- a/templates/webpack.angular.js
+++ b/templates/webpack.angular.js
@@ -7,6 +7,7 @@ const { nsReplaceBootstrap } = require("nativescript-dev-webpack/transformers/ns
const { nsReplaceLazyLoader } = require("nativescript-dev-webpack/transformers/ns-replace-lazy-loader");
const { nsSupportHmrNg } = require("nativescript-dev-webpack/transformers/ns-support-hmr-ng");
const { getMainModulePath } = require("nativescript-dev-webpack/utils/ast-utils");
+const { getNoEmitOnErrorFromTSConfig, getCompilerOptionsFromTSConfig } = require("nativescript-dev-webpack/utils/tsconfig-utils");
const CleanWebpackPlugin = require("clean-webpack-plugin");
const CopyWebpackPlugin = require("copy-webpack-plugin");
const { BundleAnalyzerPlugin } = require("webpack-bundle-analyzer");
@@ -17,12 +18,13 @@ const hashSalt = Date.now().toString();
module.exports = env => {
// Add your custom Activities, Services and other Android app components here.
- const appComponents = [
+ const appComponents = env.appComponents || [];
+ appComponents.push(...[
"tns-core-modules/ui/frame",
"tns-core-modules/ui/frame/activity",
- ];
+ ]);
- const platform = env && (env.android && "android" || env.ios && "ios");
+ const platform = env && (env.android && "android" || env.ios && "ios" || env.platform);
if (!platform) {
throw new Error("You need to provide a target platform!");
}
@@ -50,16 +52,41 @@ module.exports = env => {
hmr, // --env.hmr,
unitTesting, // --env.unitTesting
verbose, // --env.verbose
+ snapshotInDocker, // --env.snapshotInDocker
+ skipSnapshotTools, // --env.skipSnapshotTools
+ compileSnapshot // --env.compileSnapshot
} = env;
+ const useLibs = compileSnapshot;
const isAnySourceMapEnabled = !!sourceMap || !!hiddenSourceMap;
const externals = nsWebpack.getConvertedExternals(env.externals);
const appFullPath = resolve(projectRoot, appPath);
- const appResourcesFullPath = resolve(projectRoot, appResourcesPath);
const tsConfigName = "tsconfig.tns.json";
+ const tsConfigPath = join(__dirname, tsConfigName);
+ const hasRootLevelScopedModules = nsWebpack.hasRootLevelScopedModules({ projectDir: projectRoot });
+ const hasRootLevelScopedAngular = nsWebpack.hasRootLevelScopedAngular({ projectDir: projectRoot });
+ let coreModulesPackageName = "tns-core-modules";
+ const alias = env.alias || {};
+ alias['~'] = appFullPath;
+
+ const compilerOptions = getCompilerOptionsFromTSConfig(tsConfigPath);
+ if (hasRootLevelScopedModules) {
+ coreModulesPackageName = "@nativescript/core";
+ alias["tns-core-modules"] = coreModulesPackageName;
+ nsWebpack.processTsPathsForScopedModules({ compilerOptions });
+ }
+
+ if (hasRootLevelScopedAngular) {
+ alias["nativescript-angular"] = "@nativescript/angular";
+ nsWebpack.processTsPathsForScopedAngular({ compilerOptions });
+ }
+
+ const appResourcesFullPath = resolve(projectRoot, appResourcesPath);
const entryModule = `${nsWebpack.getEntryModule(appFullPath, platform)}.ts`;
const entryPath = `.${sep}${entryModule}`;
- const entries = { bundle: entryPath };
+ const entries = env.entries || {};
+ entries.bundle = entryPath;
+
const areCoreModulesExternal = Array.isArray(env.externals) && env.externals.some(e => e.indexOf("tns-core-modules") > -1);
if (platform === "ios" && !areCoreModulesExternal) {
entries["tns_modules/tns-core-modules/inspector_modules"] = "inspector_modules";
@@ -93,10 +120,11 @@ module.exports = env => {
hostReplacementPaths: nsWebpack.getResolver([platform, "tns"]),
platformTransformers: ngCompilerTransformers.map(t => t(() => ngCompilerPlugin, resolve(appFullPath, entryModule), projectRoot)),
mainPath: join(appFullPath, entryModule),
- tsConfigPath: join(__dirname, tsConfigName),
+ tsConfigPath,
skipCodeGeneration: !aot,
sourceMap: !!isAnySourceMapEnabled,
- additionalLazyModuleResources: additionalLazyModuleResources
+ additionalLazyModuleResources: additionalLazyModuleResources,
+ compilerOptions: { paths: compilerOptions.paths }
});
let sourceMapFilename = nsWebpack.getSourceMapFilename(hiddenSourceMap, __dirname, dist);
@@ -107,6 +135,8 @@ module.exports = env => {
itemsToClean.push(`${join(projectRoot, "platforms", "android", "app", "build", "configurations", "nativescript-android-snapshot")}`);
}
+ const noEmitOnErrorFromTSConfig = getNoEmitOnErrorFromTSConfig(join(projectRoot, tsConfigName));
+
nsWebpack.processAppComponents(appComponents, platform);
const config = {
mode: production ? "production" : "development",
@@ -134,14 +164,12 @@ module.exports = env => {
extensions: [".ts", ".js", ".scss", ".css"],
// Resolve {N} system modules from tns-core-modules
modules: [
- resolve(__dirname, "node_modules/tns-core-modules"),
+ resolve(__dirname, `node_modules/${coreModulesPackageName}`),
resolve(__dirname, "node_modules"),
- "node_modules/tns-core-modules",
+ `node_modules/${coreModulesPackageName}`,
"node_modules",
],
- alias: {
- '~': appFullPath
- },
+ alias,
symlinks: true
},
resolveLoader: {
@@ -158,6 +186,7 @@ module.exports = env => {
devtool: hiddenSourceMap ? "hidden-source-map" : (sourceMap ? "inline-source-map" : "none"),
optimization: {
runtimeChunk: "single",
+ noEmitOnErrors: noEmitOnErrorFromTSConfig,
splitChunks: {
cacheGroups: {
vendor: {
@@ -220,19 +249,24 @@ module.exports = env => {
{ test: /\.html$|\.xml$/, use: "raw-loader" },
- // tns-core-modules reads the app.css and its imports using css-loader
{
test: /[\/|\\]app\.css$/,
use: [
"nativescript-dev-webpack/style-hot-loader",
- { loader: "css-loader", options: { url: false } }
+ {
+ loader: "nativescript-dev-webpack/css2json-loader",
+ options: { useForImports: true }
+ }
]
},
{
test: /[\/|\\]app\.scss$/,
use: [
"nativescript-dev-webpack/style-hot-loader",
- { loader: "css-loader", options: { url: false } },
+ {
+ loader: "nativescript-dev-webpack/css2json-loader",
+ options: { useForImports: true }
+ },
"sass-loader"
]
},
@@ -308,6 +342,9 @@ module.exports = env => {
],
projectRoot,
webpackConfig: config,
+ snapshotInDocker,
+ skipSnapshotTools,
+ useLibs
}));
}
diff --git a/templates/webpack.config.spec.ts b/templates/webpack.config.spec.ts
index 024461bd..8d62b062 100644
--- a/templates/webpack.config.spec.ts
+++ b/templates/webpack.config.spec.ts
@@ -1,7 +1,6 @@
import * as proxyquire from 'proxyquire';
import * as nsWebpackIndex from '../index';
import { join } from 'path';
-import { skipPartiallyEmittedExpressions } from 'typescript';
// With noCallThru enabled, `proxyquire` will not fall back to requiring the real module to populate properties that are not mocked.
// This allows us to mock packages that are not available in node_modules.
// In case you want to enable fallback for a specific object, just add `'@noCallThru': false`.
@@ -30,9 +29,15 @@ const nativeScriptDevWebpack = {
PlatformFSPlugin: EmptyClass,
getAppPath: () => 'app',
getEntryModule: () => 'EntryModule',
+ hasRootLevelScopedModules: () => false,
+ hasRootLevelScopedAngular: () => false,
+ processTsPathsForScopedModules: () => false,
+ processTsPathsForScopedAngular: () => false,
getResolver: () => null,
getConvertedExternals: nsWebpackIndex.getConvertedExternals,
- getSourceMapFilename: nsWebpackIndex.getSourceMapFilename
+ getSourceMapFilename: nsWebpackIndex.getSourceMapFilename,
+ processAppComponents: nsWebpackIndex.processAppComponents,
+ getUserDefinedEntries: nsWebpackIndex.getUserDefinedEntries,
};
const emptyObject = {};
@@ -46,6 +51,7 @@ const webpackConfigAngular = proxyquire('./webpack.angular', {
'nativescript-dev-webpack/transformers/ns-replace-lazy-loader': { nsReplaceLazyLoader: () => { return FakeLazyTransformerFlag } },
'nativescript-dev-webpack/transformers/ns-support-hmr-ng': { nsSupportHmrNg: () => { return FakeHmrTransformerFlag } },
'nativescript-dev-webpack/utils/ast-utils': { getMainModulePath: () => { return "fakePath"; } },
+ 'nativescript-dev-webpack/utils/tsconfig-utils': { getNoEmitOnErrorFromTSConfig: () => { return false; }, getCompilerOptionsFromTSConfig: () => { return false; } },
'nativescript-dev-webpack/plugins/NativeScriptAngularCompilerPlugin': { getAngularCompilerPlugin: () => { return AngularCompilerStub; } },
'@ngtools/webpack': {
AngularCompilerPlugin: AngularCompilerStub
@@ -56,6 +62,7 @@ const webpackConfigAngular = proxyquire('./webpack.angular', {
const webpackConfigTypeScript = proxyquire('./webpack.typescript', {
'nativescript-dev-webpack': nativeScriptDevWebpack,
'nativescript-dev-webpack/nativescript-target': emptyObject,
+ 'nativescript-dev-webpack/utils/tsconfig-utils': { getNoEmitOnErrorFromTSConfig: () => { return false; }, getCompilerOptionsFromTSConfig: () => { return false; } },
'terser-webpack-plugin': TerserJsStub
});
@@ -354,6 +361,29 @@ describe('webpack.config.js', () => {
expect(config.output.sourceMapFilename).toEqual(join("..", newSourceMapFolder, "[file].map"));
});
});
+
+ describe(`alias for webpack.${type}.js (${platform})`, () => {
+ it('should add alias when @nativescript/core is at the root of node_modules', () => {
+ nativeScriptDevWebpack.hasRootLevelScopedModules = () => true;
+ nativeScriptDevWebpack.hasRootLevelScopedAngular = () => true;
+ const input = getInput({ platform });
+ const config = webpackConfig(input);
+ expect(config.resolve.alias['tns-core-modules']).toBe('@nativescript/core');
+ if (type === 'angular') {
+ expect(config.resolve.alias['nativescript-angular']).toBe('@nativescript/angular');
+ }
+ });
+ it('shouldn\'t add alias when @nativescript/core is not at the root of node_modules', () => {
+ nativeScriptDevWebpack.hasRootLevelScopedModules = () => false;
+ nativeScriptDevWebpack.hasRootLevelScopedAngular = () => false;
+ const input = getInput({ platform });
+ const config = webpackConfig(input);
+ expect(config.resolve.alias['tns-core-modules']).toBeUndefined();
+ if (type === 'angular') {
+ expect(config.resolve.alias['nativescript-angular']).toBeUndefined();
+ }
+ });
+ });
});
});
});
\ No newline at end of file
diff --git a/templates/webpack.javascript.js b/templates/webpack.javascript.js
index 7460dd2a..59360c38 100644
--- a/templates/webpack.javascript.js
+++ b/templates/webpack.javascript.js
@@ -12,12 +12,13 @@ const hashSalt = Date.now().toString();
module.exports = env => {
// Add your custom Activities, Services and other android app components here.
- const appComponents = [
+ const appComponents = env.appComponents || [];
+ appComponents.push(...[
"tns-core-modules/ui/frame",
"tns-core-modules/ui/frame/activity",
- ];
+ ]);
- const platform = env && (env.android && "android" || env.ios && "ios");
+ const platform = env && (env.android && "android" || env.ios && "ios" || env.platform);
if (!platform) {
throw new Error("You need to provide a target platform!");
}
@@ -25,6 +26,10 @@ module.exports = env => {
const platforms = ["ios", "android"];
const projectRoot = __dirname;
+ if (env.platform) {
+ platforms.push(env.platform);
+ }
+
// Default destination inside platforms//...
const dist = resolve(projectRoot, nsWebpack.getAppPath(platform, projectRoot));
@@ -44,16 +49,31 @@ module.exports = env => {
hmr, // --env.hmr,
unitTesting, // --env.unitTesting,
verbose, // --env.verbose
+ snapshotInDocker, // --env.snapshotInDocker
+ skipSnapshotTools, // --env.skipSnapshotTools
+ compileSnapshot // --env.compileSnapshot
} = env;
+ const useLibs = compileSnapshot;
const isAnySourceMapEnabled = !!sourceMap || !!hiddenSourceMap;
const externals = nsWebpack.getConvertedExternals(env.externals);
const appFullPath = resolve(projectRoot, appPath);
+ const hasRootLevelScopedModules = nsWebpack.hasRootLevelScopedModules({ projectDir: projectRoot });
+ let coreModulesPackageName = "tns-core-modules";
+ const alias = env.alias || {};
+ alias['~'] = appFullPath;
+
+ if (hasRootLevelScopedModules) {
+ coreModulesPackageName = "@nativescript/core";
+ alias["tns-core-modules"] = coreModulesPackageName;
+ }
const appResourcesFullPath = resolve(projectRoot, appResourcesPath);
const entryModule = nsWebpack.getEntryModule(appFullPath, platform);
const entryPath = `.${sep}${entryModule}.js`;
- const entries = { bundle: entryPath };
+ const entries = env.entries || {};
+ entries.bundle = entryPath;
+
const areCoreModulesExternal = Array.isArray(env.externals) && env.externals.some(e => e.indexOf("tns-core-modules") > -1);
if (platform === "ios" && !areCoreModulesExternal) {
entries["tns_modules/tns-core-modules/inspector_modules"] = "inspector_modules";
@@ -95,14 +115,12 @@ module.exports = env => {
extensions: [".js", ".scss", ".css"],
// Resolve {N} system modules from tns-core-modules
modules: [
- resolve(__dirname, "node_modules/tns-core-modules"),
+ resolve(__dirname, `node_modules/${coreModulesPackageName}`),
resolve(__dirname, "node_modules"),
- "node_modules/tns-core-modules",
+ `node_modules/${coreModulesPackageName}`,
"node_modules",
],
- alias: {
- '~': appFullPath
- },
+ alias,
// resolve symlinks to symlinked modules
symlinks: true
},
@@ -121,6 +139,7 @@ module.exports = env => {
devtool: hiddenSourceMap ? "hidden-source-map" : (sourceMap ? "inline-source-map" : "none"),
optimization: {
runtimeChunk: "single",
+ noEmitOnErrors: true,
splitChunks: {
cacheGroups: {
vendor: {
@@ -190,13 +209,13 @@ module.exports = env => {
{
test: /\.css$/,
- use: { loader: "css-loader", options: { url: false } }
+ use: "nativescript-dev-webpack/css2json-loader"
},
{
test: /\.scss$/,
use: [
- { loader: "css-loader", options: { url: false } },
+ "nativescript-dev-webpack/css2json-loader",
"sass-loader"
]
},
@@ -249,6 +268,9 @@ module.exports = env => {
],
projectRoot,
webpackConfig: config,
+ snapshotInDocker,
+ skipSnapshotTools,
+ useLibs
}));
}
diff --git a/templates/webpack.typescript.js b/templates/webpack.typescript.js
index 44a60a10..35c4fe65 100644
--- a/templates/webpack.typescript.js
+++ b/templates/webpack.typescript.js
@@ -3,6 +3,7 @@ const { join, relative, resolve, sep } = require("path");
const webpack = require("webpack");
const nsWebpack = require("nativescript-dev-webpack");
const nativescriptTarget = require("nativescript-dev-webpack/nativescript-target");
+const { getNoEmitOnErrorFromTSConfig } = require("nativescript-dev-webpack/utils/tsconfig-utils");
const CleanWebpackPlugin = require("clean-webpack-plugin");
const CopyWebpackPlugin = require("copy-webpack-plugin");
const ForkTsCheckerWebpackPlugin = require('fork-ts-checker-webpack-plugin');
@@ -13,12 +14,13 @@ const hashSalt = Date.now().toString();
module.exports = env => {
// Add your custom Activities, Services and other Android app components here.
- const appComponents = [
+ const appComponents = env.appComponents || [];
+ appComponents.push(...[
"tns-core-modules/ui/frame",
"tns-core-modules/ui/frame/activity",
- ];
+ ]);
- const platform = env && (env.android && "android" || env.ios && "ios");
+ const platform = env && (env.android && "android" || env.ios && "ios" || env.platform);
if (!platform) {
throw new Error("You need to provide a target platform!");
}
@@ -26,6 +28,10 @@ module.exports = env => {
const platforms = ["ios", "android"];
const projectRoot = __dirname;
+ if (env.platform) {
+ platforms.push(env.platform);
+ }
+
// Default destination inside platforms//...
const dist = resolve(projectRoot, nsWebpack.getAppPath(platform, projectRoot));
@@ -45,16 +51,31 @@ module.exports = env => {
hmr, // --env.hmr,
unitTesting, // --env.unitTesting,
verbose, // --env.verbose
+ snapshotInDocker, // --env.snapshotInDocker
+ skipSnapshotTools, // --env.skipSnapshotTools
+ compileSnapshot // --env.compileSnapshot
} = env;
+
+ const useLibs = compileSnapshot;
const isAnySourceMapEnabled = !!sourceMap || !!hiddenSourceMap;
const externals = nsWebpack.getConvertedExternals(env.externals);
const appFullPath = resolve(projectRoot, appPath);
+ const hasRootLevelScopedModules = nsWebpack.hasRootLevelScopedModules({ projectDir: projectRoot });
+ let coreModulesPackageName = "tns-core-modules";
+ const alias = env.alias || {};
+ alias['~'] = appFullPath;
+
+ if (hasRootLevelScopedModules) {
+ coreModulesPackageName = "@nativescript/core";
+ alias["tns-core-modules"] = coreModulesPackageName;
+ }
const appResourcesFullPath = resolve(projectRoot, appResourcesPath);
const entryModule = nsWebpack.getEntryModule(appFullPath, platform);
const entryPath = `.${sep}${entryModule}.ts`;
- const entries = { bundle: entryPath };
+ const entries = env.entries || {};
+ entries.bundle = entryPath;
const tsConfigPath = resolve(projectRoot, "tsconfig.tns.json");
@@ -71,6 +92,8 @@ module.exports = env => {
itemsToClean.push(`${join(projectRoot, "platforms", "android", "app", "build", "configurations", "nativescript-android-snapshot")}`);
}
+ const noEmitOnErrorFromTSConfig = getNoEmitOnErrorFromTSConfig(tsConfigPath);
+
nsWebpack.processAppComponents(appComponents, platform);
const config = {
mode: production ? "production" : "development",
@@ -98,14 +121,12 @@ module.exports = env => {
extensions: [".ts", ".js", ".scss", ".css"],
// Resolve {N} system modules from tns-core-modules
modules: [
- resolve(__dirname, "node_modules/tns-core-modules"),
+ resolve(__dirname, `node_modules/${coreModulesPackageName}`),
resolve(__dirname, "node_modules"),
- "node_modules/tns-core-modules",
+ `node_modules/${coreModulesPackageName}`,
"node_modules",
],
- alias: {
- '~': appFullPath
- },
+ alias,
// resolve symlinks to symlinked modules
symlinks: true
},
@@ -124,6 +145,7 @@ module.exports = env => {
devtool: hiddenSourceMap ? "hidden-source-map" : (sourceMap ? "inline-source-map" : "none"),
optimization: {
runtimeChunk: "single",
+ noEmitOnErrors: noEmitOnErrorFromTSConfig,
splitChunks: {
cacheGroups: {
vendor: {
@@ -183,7 +205,7 @@ module.exports = env => {
},
].filter(loader => !!loader)
},
-
+
{
test: /\.(ts|css|scss|html|xml)$/,
use: "nativescript-dev-webpack/hmr/hot-loader"
@@ -193,13 +215,13 @@ module.exports = env => {
{
test: /\.css$/,
- use: { loader: "css-loader", options: { url: false } }
+ use: "nativescript-dev-webpack/css2json-loader"
},
{
test: /\.scss$/,
use: [
- { loader: "css-loader", options: { url: false } },
+ "nativescript-dev-webpack/css2json-loader",
"sass-loader"
]
},
@@ -253,6 +275,7 @@ module.exports = env => {
tsconfig: tsConfigPath,
async: false,
useTypescriptIncrementalApi: true,
+ checkSyntacticErrors: true,
memoryLimit: 4096
})
],
@@ -277,6 +300,9 @@ module.exports = env => {
],
projectRoot,
webpackConfig: config,
+ snapshotInDocker,
+ skipSnapshotTools,
+ useLibs
}));
}
diff --git a/templates/webpack.vue.js b/templates/webpack.vue.js
index 28bbfe9f..16339117 100644
--- a/templates/webpack.vue.js
+++ b/templates/webpack.vue.js
@@ -16,12 +16,13 @@ const hashSalt = Date.now().toString();
module.exports = env => {
// Add your custom Activities, Services and other android app components here.
- const appComponents = [
+ const appComponents = env.appComponents || [];
+ appComponents.push(...[
"tns-core-modules/ui/frame",
"tns-core-modules/ui/frame/activity",
- ];
+ ]);
- const platform = env && (env.android && "android" || env.ios && "ios");
+ const platform = env && (env.android && "android" || env.ios && "ios" || env.platform);
if (!platform) {
throw new Error("You need to provide a target platform!");
}
@@ -29,6 +30,10 @@ module.exports = env => {
const platforms = ["ios", "android"];
const projectRoot = __dirname;
+ if (env.platform) {
+ platforms.push(env.platform);
+ }
+
// Default destination inside platforms//...
const dist = resolve(projectRoot, nsWebpack.getAppPath(platform, projectRoot));
@@ -47,19 +52,37 @@ module.exports = env => {
hiddenSourceMap, // --env.hiddenSourceMap
unitTesting, // --env.unitTesting
verbose, // --env.verbose
+ snapshotInDocker, // --env.snapshotInDocker
+ skipSnapshotTools, // --env.skipSnapshotTools
+ compileSnapshot // --env.compileSnapshot
} = env;
+ const useLibs = compileSnapshot;
const isAnySourceMapEnabled = !!sourceMap || !!hiddenSourceMap;
const externals = nsWebpack.getConvertedExternals(env.externals);
const mode = production ? "production" : "development"
const appFullPath = resolve(projectRoot, appPath);
+ const hasRootLevelScopedModules = nsWebpack.hasRootLevelScopedModules({ projectDir: projectRoot });
+ let coreModulesPackageName = "tns-core-modules";
+ const alias = env.alias || {};
+ alias['~'] = appFullPath;
+ alias['@'] = appFullPath;
+ alias['vue'] = 'nativescript-vue';
+
+ if (hasRootLevelScopedModules) {
+ coreModulesPackageName = "@nativescript/core";
+ alias["tns-core-modules"] = coreModulesPackageName;
+ }
+
const appResourcesFullPath = resolve(projectRoot, appResourcesPath);
const entryModule = nsWebpack.getEntryModule(appFullPath, platform);
const entryPath = `.${sep}${entryModule}`;
- const entries = { bundle: entryPath };
+ const entries = env.entries || {};
+ entries.bundle = entryPath;
+
const areCoreModulesExternal = Array.isArray(env.externals) && env.externals.some(e => e.indexOf("tns-core-modules") > -1);
if (platform === "ios" && !areCoreModulesExternal) {
entries["tns_modules/tns-core-modules/inspector_modules"] = "inspector_modules";
@@ -102,16 +125,12 @@ module.exports = env => {
extensions: [".vue", ".ts", ".js", ".scss", ".css"],
// Resolve {N} system modules from tns-core-modules
modules: [
- resolve(__dirname, "node_modules/tns-core-modules"),
+ resolve(__dirname, `node_modules/${coreModulesPackageName}`),
resolve(__dirname, "node_modules"),
- "node_modules/tns-core-modules",
+ `node_modules/${coreModulesPackageName}`,
"node_modules",
],
- alias: {
- '~': appFullPath,
- '@': appFullPath,
- 'vue': 'nativescript-vue'
- },
+ alias,
// resolve symlinks to symlinked modules
symlinks: true,
},
@@ -130,6 +149,7 @@ module.exports = env => {
devtool: hiddenSourceMap ? "hidden-source-map" : (sourceMap ? "inline-source-map" : "none"),
optimization: {
runtimeChunk: "single",
+ noEmitOnErrors: true,
splitChunks: {
cacheGroups: {
vendor: {
@@ -190,8 +210,30 @@ module.exports = env => {
},
].filter(loader => Boolean(loader)),
},
+ {
+ test: /[\/|\\]app\.css$/,
+ use: [
+ 'nativescript-dev-webpack/style-hot-loader',
+ {
+ loader: "nativescript-dev-webpack/css2json-loader",
+ options: { useForImports: true }
+ },
+ ],
+ },
+ {
+ test: /[\/|\\]app\.scss$/,
+ use: [
+ 'nativescript-dev-webpack/style-hot-loader',
+ {
+ loader: "nativescript-dev-webpack/css2json-loader",
+ options: { useForImports: true }
+ },
+ 'sass-loader',
+ ],
+ },
{
test: /\.css$/,
+ exclude: /[\/|\\]app\.css$/,
use: [
'nativescript-dev-webpack/style-hot-loader',
'nativescript-dev-webpack/apply-css-loader.js',
@@ -200,11 +242,12 @@ module.exports = env => {
},
{
test: /\.scss$/,
+ exclude: /[\/|\\]app\.scss$/,
use: [
'nativescript-dev-webpack/style-hot-loader',
'nativescript-dev-webpack/apply-css-loader.js',
{ loader: "css-loader", options: { url: false } },
- "sass-loader",
+ 'sass-loader',
],
},
{
@@ -296,6 +339,9 @@ module.exports = env => {
],
projectRoot,
webpackConfig: config,
+ snapshotInDocker,
+ skipSnapshotTools,
+ useLibs
}));
}
diff --git a/utils/ast-utils.ts b/utils/ast-utils.ts
index 88660b86..5c0b19f8 100644
--- a/utils/ast-utils.ts
+++ b/utils/ast-utils.ts
@@ -18,6 +18,7 @@ import { dirname, join, relative } from "path";
import * as ts from "typescript";
import { readFileSync, existsSync } from "fs";
import { collectDeepNodes } from "@ngtools/webpack/src/transformers";
+import { getCompilerOptionsFromTSConfig } from "./tsconfig-utils";
export function getMainModulePath(entryFilePath: string, tsConfigName: string) {
try {
@@ -43,23 +44,13 @@ export function getMainModulePath(entryFilePath: string, tsConfigName: string) {
function tsResolve(moduleName: string, containingFilePath: string, tsConfigName: string) {
let result = moduleName;
try {
- const parseConfigFileHost: ts.ParseConfigFileHost = {
- getCurrentDirectory: ts.sys.getCurrentDirectory,
- useCaseSensitiveFileNames: false,
- readDirectory: ts.sys.readDirectory,
- fileExists: ts.sys.fileExists,
- readFile: ts.sys.readFile,
- onUnRecoverableConfigFileDiagnostic: undefined
- };
-
- const tsConfig = ts.getParsedCommandLineOfConfigFile(tsConfigName, ts.getDefaultCompilerOptions(), parseConfigFileHost);
-
- const compilerOptions: ts.CompilerOptions = tsConfig.options || ts.getDefaultCompilerOptions();
const moduleResolutionHost: ts.ModuleResolutionHost = {
fileExists: ts.sys.fileExists,
readFile: ts.sys.readFile
};
+ const compilerOptions = getCompilerOptionsFromTSConfig(tsConfigName);
+
const resolutionResult = ts.resolveModuleName(moduleName, containingFilePath, compilerOptions, moduleResolutionHost);
if (resolutionResult && resolutionResult.resolvedModule && resolutionResult.resolvedModule.resolvedFileName) {
diff --git a/utils/tsconfig-utils.ts b/utils/tsconfig-utils.ts
new file mode 100644
index 00000000..5b8bbf13
--- /dev/null
+++ b/utils/tsconfig-utils.ts
@@ -0,0 +1,25 @@
+import * as ts from "typescript";
+
+export function getCompilerOptionsFromTSConfig(tsConfigPath: string): ts.CompilerOptions {
+ const parseConfigFileHost: ts.ParseConfigFileHost = {
+ getCurrentDirectory: ts.sys.getCurrentDirectory,
+ useCaseSensitiveFileNames: false,
+ readDirectory: ts.sys.readDirectory,
+ fileExists: ts.sys.fileExists,
+ readFile: ts.sys.readFile,
+ onUnRecoverableConfigFileDiagnostic: undefined
+ };
+
+ const tsConfig = ts.getParsedCommandLineOfConfigFile(tsConfigPath, ts.getDefaultCompilerOptions(), parseConfigFileHost);
+
+ const compilerOptions: ts.CompilerOptions = tsConfig.options || ts.getDefaultCompilerOptions();
+
+ return compilerOptions;
+}
+
+export function getNoEmitOnErrorFromTSConfig(tsConfigPath: string): boolean {
+ const compilerOptions = getCompilerOptionsFromTSConfig(tsConfigPath);
+ const noEmitOnError = !!compilerOptions.noEmitOnError;
+
+ return noEmitOnError;
+}
\ No newline at end of file
diff --git a/xml-namespace-loader.spec.ts b/xml-namespace-loader.spec.ts
new file mode 100644
index 00000000..80ab872f
--- /dev/null
+++ b/xml-namespace-loader.spec.ts
@@ -0,0 +1,315 @@
+import xmlNsLoader from "./xml-namespace-loader";
+import { convertSlashesInPath } from "./projectHelpers";
+
+const CODE_FILE = `
+
+
+
+
+
+
+
+
+
+
+`;
+
+interface TestSetup {
+ resolveMap: { [path: string]: string },
+ expectedDeps: string[],
+ expectedRegs: { name: string, path: string }[],
+ ignore?: RegExp,
+ assureNoDeps?: boolean,
+ expectError?: boolean,
+ expectWarnings?: number
+}
+
+function getContext(
+ done: DoneFn,
+ { resolveMap, expectedDeps, expectedRegs, assureNoDeps, ignore, expectError, expectWarnings }: TestSetup) {
+ const actualDeps: string[] = [];
+ const actualWarnings: Error[] =[]
+ let callbackCalled = false;
+
+ const loaderContext = {
+ rootContext: "app",
+ context: "app/component",
+ async: () => (error, source: string) => {
+ if (callbackCalled) {
+ done.fail("Callback called more than once!");
+ }
+ callbackCalled = true;
+
+ expectedDeps.forEach(expectedDep => expect(actualDeps).toContain(expectedDep));
+
+ expectedRegs.forEach(({ name, path }) => {
+ const regCode = `global.registerModule("${name}", function() { return require("${path}"); });`;
+ expect(source).toContain(regCode);
+ })
+
+ if (assureNoDeps) {
+ expect(actualDeps.length).toBe(0);
+ expect(source).not.toContain("global.registerModule");
+ }
+
+ if(expectWarnings){
+ expect(actualWarnings.length).toEqual(expectWarnings);
+ }
+
+ if (error && !expectError) {
+ done.fail(error)
+ } else if (!error && expectError) {
+ done.fail("Error expected here")
+ } else {
+ done();
+ }
+ },
+ resolve: (context: string, request: string, callback: (err: Error, result: string) => void) => {
+ request = convertSlashesInPath(request);
+ if (resolveMap[request]) {
+ callback(undefined, resolveMap[request]);
+ } else {
+ callback(new Error(`Module ${request} not found`), undefined);
+ }
+ },
+ addDependency: (dep: string) => {
+ actualDeps.push(dep);
+ },
+ emitWarning: (err: Error) => {
+ actualWarnings.push(err);
+ },
+ query: { ignore }
+ }
+
+ return loaderContext;
+}
+
+describe("XmlNamespaceLoader", () => {
+ it("with namespace pointing to files", (done) => {
+ const resolveMap = {
+ "app/nativescript-ui-chart": "app/nativescript-ui-chart.js",
+ "app/nativescript-ui-chart.xml": "app/nativescript-ui-chart.xml",
+ "app/nativescript-ui-chart.css": "app/nativescript-ui-chart.css",
+ };
+
+ const expectedDeps = [
+ "app/nativescript-ui-chart.js",
+ "app/nativescript-ui-chart.xml",
+ "app/nativescript-ui-chart.css",
+ ];
+
+ const expectedRegs = [
+ { name: "nativescript-ui-chart", path: "app/nativescript-ui-chart.js" },
+ { name: "nativescript-ui-chart/RadCartesianChart", path: "app/nativescript-ui-chart.js" },
+ { name: "nativescript-ui-chart/RadCartesianChart.xml", path: "app/nativescript-ui-chart.xml" },
+ { name: "nativescript-ui-chart/RadCartesianChart.css", path: "app/nativescript-ui-chart.css" },
+ ];
+
+ const loaderContext = getContext(done, { resolveMap, expectedDeps, expectedRegs });
+
+ xmlNsLoader.call(loaderContext, CODE_FILE);
+ })
+
+ it("with namespace/elementName pointing to files (with package.json)", (done) => {
+ const resolveMap = {
+ "app/nativescript-ui-chart": "app/nativescript-ui-chart/RadCartesianChart.js", //simulate package.json
+ "app/nativescript-ui-chart/RadCartesianChart": "app/nativescript-ui-chart/RadCartesianChart.js",
+ "app/nativescript-ui-chart/RadCartesianChart.xml": "app/nativescript-ui-chart/RadCartesianChart.xml",
+ "app/nativescript-ui-chart/RadCartesianChart.css": "app/nativescript-ui-chart/RadCartesianChart.css",
+ }
+
+ const expectedDeps = [
+ "app/nativescript-ui-chart/RadCartesianChart.js",
+ "app/nativescript-ui-chart/RadCartesianChart.xml",
+ "app/nativescript-ui-chart/RadCartesianChart.css",
+ ];
+
+ const expectedRegs = [
+ { name: "nativescript-ui-chart", path: "app/nativescript-ui-chart/RadCartesianChart.js" },
+ { name: "nativescript-ui-chart/RadCartesianChart", path: "app/nativescript-ui-chart/RadCartesianChart.js" },
+ { name: "nativescript-ui-chart/RadCartesianChart.xml", path: "app/nativescript-ui-chart/RadCartesianChart.xml" },
+ { name: "nativescript-ui-chart/RadCartesianChart.css", path: "app/nativescript-ui-chart/RadCartesianChart.css" },
+ ];
+
+ const loaderContext = getContext(done, { resolveMap, expectedDeps, expectedRegs });
+ xmlNsLoader.call(loaderContext, CODE_FILE);
+ })
+
+ it("with namespace/elementName pointing to files", (done) => {
+ const resolveMap = {
+ "app/nativescript-ui-chart/RadCartesianChart": "app/nativescript-ui-chart/RadCartesianChart.js",
+ "app/nativescript-ui-chart/RadCartesianChart.xml": "app/nativescript-ui-chart/RadCartesianChart.xml",
+ "app/nativescript-ui-chart/RadCartesianChart.css": "app/nativescript-ui-chart/RadCartesianChart.css",
+ }
+
+ const expectedDeps = [
+ "app/nativescript-ui-chart/RadCartesianChart.js",
+ "app/nativescript-ui-chart/RadCartesianChart.xml",
+ "app/nativescript-ui-chart/RadCartesianChart.css",
+ ];
+
+ const expectedRegs = [
+ { name: "nativescript-ui-chart", path: "app/nativescript-ui-chart/RadCartesianChart.js" },
+ { name: "nativescript-ui-chart/RadCartesianChart", path: "app/nativescript-ui-chart/RadCartesianChart.js" },
+ { name: "nativescript-ui-chart/RadCartesianChart.xml", path: "app/nativescript-ui-chart/RadCartesianChart.xml" },
+ { name: "nativescript-ui-chart/RadCartesianChart.css", path: "app/nativescript-ui-chart/RadCartesianChart.css" },
+ ];
+
+ const loaderContext = getContext(done, { resolveMap, expectedDeps, expectedRegs });
+ xmlNsLoader.call(loaderContext, CODE_FILE);
+ })
+
+ it("with namespace/elementName pointing to files - only XML and CSS", (done) => {
+ const resolveMap = {
+ "app/nativescript-ui-chart/RadCartesianChart.xml": "app/nativescript-ui-chart/RadCartesianChart.xml",
+ "app/nativescript-ui-chart/RadCartesianChart.css": "app/nativescript-ui-chart/RadCartesianChart.css",
+ }
+
+ const expectedDeps = [
+ "app/nativescript-ui-chart/RadCartesianChart.xml",
+ "app/nativescript-ui-chart/RadCartesianChart.css",
+ ];
+
+ const expectedRegs = [
+ { name: "nativescript-ui-chart/RadCartesianChart.xml", path: "app/nativescript-ui-chart/RadCartesianChart.xml" },
+ { name: "nativescript-ui-chart/RadCartesianChart.css", path: "app/nativescript-ui-chart/RadCartesianChart.css" },
+ ];
+
+ const loaderContext = getContext(done, { resolveMap, expectedDeps, expectedRegs });
+ xmlNsLoader.call(loaderContext, CODE_FILE);
+ })
+
+ it("with plugin path", (done) => {
+ const resolveMap = {
+ "nativescript-ui-chart": "node_module/nativescript-ui-chart/ui-chart.js",
+ }
+
+ const expectedDeps = [
+ ];
+
+ const expectedRegs = [
+ { name: "nativescript-ui-chart", path: "nativescript-ui-chart" },
+ { name: "nativescript-ui-chart/RadCartesianChart", path: "nativescript-ui-chart" },
+ ];
+
+ const loaderContext = getContext(done, { resolveMap, expectedDeps, expectedRegs });
+ xmlNsLoader.call(loaderContext, CODE_FILE);
+ })
+
+ it("with ignored namespace should not add deps or register calls", (done) => {
+ const resolveMap = {
+ "app/nativescript-ui-chart": "app/nativescript-ui-chart.js",
+ "app/nativescript-ui-chart.xml": "app/nativescript-ui-chart.xml",
+ "app/nativescript-ui-chart.css": "app/nativescript-ui-chart.css",
+ };
+ const expectedDeps = [];
+ const expectedRegs = [];
+
+ const loaderContext = getContext(done, { resolveMap, expectedDeps, expectedRegs, ignore: /nativescript\-ui\-chart/, assureNoDeps: true });
+
+ xmlNsLoader.call(loaderContext, CODE_FILE);
+ })
+
+ it("with XML declaration and Doctype does not fail", (done) => {
+ const resolveMap = {};
+ const expectedDeps = [];
+ const expectedRegs = [];
+
+ const testXml = `
+
+
+
+ `;
+
+ const loaderContext = getContext(done, { resolveMap, expectedDeps, expectedRegs, assureNoDeps: true });
+
+ xmlNsLoader.call(loaderContext, testXml);
+ })
+ it("with invalid XML fails", (done) => {
+ const resolveMap = {};
+ const expectedDeps = [];
+ const expectedRegs = [];
+
+ const testXml = ``;
+
+ const loaderContext = getContext(done, { resolveMap, expectedDeps, expectedRegs, expectError: true });
+
+ xmlNsLoader.call(loaderContext, testXml);
+ })
+
+ it("doesn't throw with ios and android platform namespaces", (done) => {
+ const resolveMap = {};
+ const expectedDeps = [];
+ const expectedRegs = [];
+
+ const testXml = `
+
+
+
+ `;
+
+ const loaderContext = getContext(done, { resolveMap, expectedDeps, expectedRegs, assureNoDeps: true });
+
+ xmlNsLoader.call(loaderContext, testXml);
+ })
+
+ it("doesn't throw with ios and android platform namespaces", (done) => {
+ const resolveMap = {};
+ const expectedDeps = [];
+ const expectedRegs = [];
+
+ const testXml = `
+
+
+
+
+
+ `;
+
+ const loaderContext = getContext(done, { resolveMap, expectedDeps, expectedRegs, assureNoDeps: true });
+
+ xmlNsLoader.call(loaderContext, testXml);
+ })
+
+ it("throws with unbound namespace namespaces", (done) => {
+ const resolveMap = {};
+ const expectedDeps = [];
+ const expectedRegs = [];
+
+ const testXml = `
+
+
+
+ `;
+
+ const loaderContext = getContext(done, { resolveMap, expectedDeps, expectedRegs, expectError: true });
+
+ xmlNsLoader.call(loaderContext, testXml);
+ })
+
+
+ it("with '&&', '||', '<=' and '>=' in binding expression, emits warnings, but does not fail", (done) => {
+ const resolveMap = {
+ "nativescript-ui-chart": "node_module/nativescript-ui-chart/ui-chart.js",
+ }
+
+ const expectedDeps = [];
+
+ const expectedRegs = [
+ { name: "nativescript-ui-chart", path: "nativescript-ui-chart" },
+ { name: "nativescript-ui-chart/RadCartesianChart", path: "nativescript-ui-chart" },
+ ];
+
+ const testXml = `
+
+
+
+
+
+ `;
+
+ const loaderContext = getContext(done, { resolveMap, expectedDeps, expectedRegs, expectWarnings: 1 });
+
+ xmlNsLoader.call(loaderContext, testXml);
+ })
+});
diff --git a/xml-namespace-loader.js b/xml-namespace-loader.ts
similarity index 59%
rename from xml-namespace-loader.js
rename to xml-namespace-loader.ts
index e1294953..f3a16404 100644
--- a/xml-namespace-loader.js
+++ b/xml-namespace-loader.ts
@@ -1,22 +1,34 @@
-const { parse, relative, join, basename, extname } = require("path");
-const { promisify } = require('util');
-const { convertSlashesInPath } = require("./projectHelpers");
+import { parse, join } from "path";
+import { promisify } from "util";
+import { loader } from "webpack";
+import { parser, QualifiedTag } from "sax";
-module.exports = function (source, map) {
- this.value = source;
+import { convertSlashesInPath } from "./projectHelpers";
+
+interface NamespaceEntry {
+ name: string;
+ path: string
+}
+
+const loader: loader.Loader = function (source: string, map) {
const { ignore } = this.query;
+
+ let callbackCalled = false;
const callback = this.async();
+ const callbackWrapper = (error?: Error, content?: string, map?: any) => {
+ if (!callbackCalled) {
+ callbackCalled = true;
+ callback(error, content, map);
+ }
+ }
- const { XmlParser } = require("tns-core-modules/xml");
const resolvePromise = promisify(this.resolve);
- const promises = [];
+ const promises: Promise[] = [];
+ const namespaces: NamespaceEntry[] = [];
- const namespaces = [];
- const parser = new XmlParser((event) => {
- const { namespace, elementName } = event;
+ const handleOpenTag = (namespace: string, elementName: string) => {
const moduleName = `${namespace}/${elementName}`;
-
if (
namespace &&
!namespace.startsWith("http") &&
@@ -55,7 +67,7 @@ module.exports = function (source, map) {
promises.push(resolvePromise(this.context, localNamespacePath)
.then(path => pathResolved(path))
.catch(() => {
- return promise = resolvePromise(this.context, localModulePath)
+ return resolvePromise(this.context, localModulePath)
.then(path => pathResolved(path))
.catch(() => {
return Promise.all([
@@ -81,17 +93,40 @@ module.exports = function (source, map) {
})
);
}
- }, undefined, true);
+ }
+
+ const saxParser = parser(true, { xmlns: true });
+
+ // Register ios and android prefixes as namespaces to avoid "unbound xml namespace" errors
+ (saxParser).ns["ios"] = "http://schemas.nativescript.org/tns.xsd";
+ (saxParser).ns["android"] = "http://schemas.nativescript.org/tns.xsd";
+ (saxParser).ns["desktop"] = "http://schemas.nativescript.org/tns.xsd";
+ (saxParser).ns["web"] = "http://schemas.nativescript.org/tns.xsd";
+
+ saxParser.onopentag = (node: QualifiedTag) => { handleOpenTag(node.uri, node.local); };
+ saxParser.onerror = (err) => {
+ // Do only warning about invalid character "&"" for back-compatibility
+ // as it is common to use it in a binding expression
+ if (err &&
+ err.message.indexOf("Invalid character") >= 0 &&
+ err.message.indexOf("Char: &") >= 0) {
+ this.emitWarning(err)
+ } else {
+ callbackWrapper(err);
+ }
- parser.parse(source);
+ saxParser.error = null;
+ };
+ saxParser.write(source).close();
Promise.all(promises).then(() => {
- const moduleRegisters = namespaces
- .map(convertPath)
- .map(n =>
- `global.registerModule("${n.name}", function() { return require("${n.path}"); });`
- )
- .join("");
+ const distinctNamespaces = new Map();
+ namespaces.forEach(({ name, path }) => distinctNamespaces.set(name, convertSlashesInPath(path)));
+
+ const moduleRegisters: string[] = [];
+ distinctNamespaces.forEach((path, name) => {
+ moduleRegisters.push(`global.registerModule("${name}", function() { return require("${path}"); });\n`);
+ });
// escape special whitespace characters
// see: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify#Issue_with_plain_JSON.stringify_for_use_as_JavaScript
@@ -99,16 +134,12 @@ module.exports = function (source, map) {
.replace(/\u2028/g, '\\u2028')
.replace(/\u2029/g, '\\u2029');
- const wrapped = `${moduleRegisters}\nmodule.exports = ${json}`;
+ const wrapped = `${moduleRegisters.join("")}\nmodule.exports = ${json}`;
- callback(null, wrapped, map);
+ callbackWrapper(null, wrapped, map);
}).catch((err) => {
- callback(err);
+ callbackWrapper(err);
})
-
}
-function convertPath(obj) {
- obj.path = convertSlashesInPath(obj.path);
- return obj;
-}
+export default loader;