chore: remove legacy webpack4 as no longer used

Still accessible on a branch though here: https://github.com/NativeScript/NativeScript/tree/feat/webpack4
This commit is contained in:
Nathan Walker
2022-07-09 11:01:14 -07:00
parent 2e0f0cc0ac
commit 6f48b4bef3
123 changed files with 0 additions and 9310 deletions

View File

@@ -1,47 +0,0 @@
node_modules
*.tgz
package-lock.json
*.js.map
plugins/NativeScriptAngularCompilerPlugin.d.ts
plugins/NativeScriptAngularCompilerPlugin.js
transformers/*.d.ts
transformers/*.js
utils/*.d.ts
utils/*.js
hmr/*.d.ts
hmr/*.js
plugins/PlatformFSPlugin.d.ts
plugins/PlatformFSPlugin.js
plugins/WatchStateLoggerPlugin.d.ts
plugins/WatchStateLoggerPlugin.js
host/resolver.d.ts
host/resolver.js
jasmine-config/reporter.d.ts
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

View File

@@ -1,17 +0,0 @@
build
demo
*.ts
!*.d.ts
*.spec.*
.vscode/
.github/
.nyc_output
coverage/
jasmine-config/
CONTRIBUTING.md
CODE_OF_CONDUCT.md
PULL_REQUEST_TESTS.md
*.tgz

View File

@@ -1,5 +0,0 @@
{
"extends": "@istanbuljs/nyc-config-typescript",
"exclude": ["/demo/**"],
"reporter": ["text", "lcov"]
}

View File

File diff suppressed because it is too large Load Diff

View File

@@ -1,201 +0,0 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "{}"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright (c) 2015-2019 Progress Software Corporation
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

View File

@@ -1,49 +0,0 @@
# NativeScript Webpack
This repository contains the code for the @nativescript/webpack plugin which helps in webpacking [NativeScript](https://www.nativescript.org/) apps.
JavaScript code and general asset bundling have been a member of the web developer toolbox for a long time. Tools like Webpack have been providing support for an enjoyable development experience that lets you assemble client-side code from various module sources and formats and then package it together. Most importantly, they allow for page load time optimizations that reduce or parallelize the number of requests a browser makes to the server.
Why bundle scripts in a mobile app though?
- Fewer filesystem operations on app startup
- Smaller code size
- Tree-shaking
- Preprocessing and interoperability hooks. Webpack provides a way to resolve modules and expressions differently according to its configuration. It also contains a lot of plugins and loaders that let you embed different content in your application or use code written in different programming languages
For more details, see the [NativeScript docs for building with webpack](http://docs.nativescript.org/angular/best-practices/bundling-with-webpack.html).
<!-- TOC depthFrom:2 -->
- [Ingredients](#ingredients)
- [Usage](#usage)
- [Contribute](#contribute)
- [Get Help](#get-help)
<!-- /TOC -->
## Ingredients
* webpack config templates.
* helper functions that place files at the correct locations before packaging apps.
* loaders and plugins for vanilla NativeScript and Angular apps.
## Usage
```sh
$ npm install --save-dev @nativescript/webpack
$ tns run android
or
$ tns run ios
```
## Contribute
We love PRs! Check out the [contributing guidelines](CONTRIBUTING.md) and [instructions for local setup](https://github.com/NativeScript/nativescript-dev-webpack/blob/master/CONTRIBUTING.md#setup). If you want to contribute, but you are not sure where to start - look for [issues labeled `help wanted`](https://github.com/NativeScript/nativescript-dev-webpack/issues?q=is%3Aopen+is%3Aissue+label%3A%22help+wanted%22).
## Get Help
Please, use [github issues](https://github.com/NativeScript/nativescript-dev-webpack/issues) strictly for [reporting bugs](CONTRIBUTING.md#reporting-bugs) or [requesting features](CONTRIBUTING.md#requesting-features). For general questions and support, check out [Stack Overflow](https://stackoverflow.com/questions/tagged/nativescript) or ask our experts in [NativeScript community Slack channel](http://developer.telerik.com/wp-login.php?action=slack-invitation).
![](https://ga-beacon.appspot.com/UA-111455-24/nativescript/nativescript-dev-webpack?pixel)

View File

@@ -1,6 +0,0 @@
#!/usr/bin/env node
const ProjectSnapshotGenerator = require("../snapshot/android/project-snapshot-generator");
const args = require("../snapshot/android/project-snapshot-generator-cli-ags-parser")();
const generator = new ProjectSnapshotGenerator(args);
generator.generate(args);

View File

@@ -1 +0,0 @@
@node %~dp0\generate-android-snapshot %*

View File

@@ -1,3 +0,0 @@
#!/usr/bin/env node
const installer = require("../installer");
installer.install();

View File

@@ -1 +0,0 @@
@node %~dp0\install-ns-webpack %*

View File

@@ -1,4 +0,0 @@
#!/usr/bin/env node
console.error("Using npm run scripts is no longer supported. Use CLI commands instead.\nExample:\ntns build android")
process.exit(1);

View File

@@ -1 +0,0 @@
@node %~dp0\ns-bundle %*

View File

@@ -1,104 +0,0 @@
#!/usr/bin/env node
console.error("The ns-verify-bundle script is deprecated!")
const path = require("path");
const fs = require("fs");
const { getProjectDir } = require("../projectHelpers");
const PROJECT_DIR = getProjectDir();
const APP_ID = require(path.resolve(PROJECT_DIR, "./package.json")).nativescript.id;
const APP_NAME = APP_ID.substring(APP_ID.lastIndexOf(".") + 1);
const PROJECT_PATHS = {
android: path.resolve(PROJECT_DIR, "platforms/android/src/main/assets/app"),
ios: path.resolve(PROJECT_DIR, `platforms/ios/build/emulator/${APP_NAME}.app/app`),
};
const npmArgs = JSON.parse(process.env.npm_config_argv).original;
const flags = npmArgs.filter(a => a.startsWith("--")).map(a => a.substring(2));
const file = getTargetFile(flags);
const platform = getPlatform(flags);
const filePath = path.resolve(PROJECT_PATHS[platform], file);
console.log(`Checking ${filePath} exists`);
if (!fs.existsSync(filePath)) {
throwError({message: `${filePath} doesn not exist!`});
}
const maxSize = getMaxSize(flags);
if (maxSize) {
checkFileSizeIsUnder(filePath, maxSize).then().catch(throwError);
}
function getTargetFile(flags) {
let fileFlags = flags.filter(f => f.startsWith("file="));
if (fileFlags.length != 1) {
throwError({message: "You must provide a target file!"});
}
fileFlags = fileFlags[0];
return fileFlags.substring(fileFlags.indexOf("=") + 1);
}
function getMaxSize(flags) {
let sizeFlags = flags.filter(f => f.startsWith("maxSize="));
if (sizeFlags.length == 0) {
return;
} else if (sizeFlags.length > 1) {
throwError({message: "You must provide 0 or 1 maxSize flags!"});
}
sizeFlags = sizeFlags[0];
return sizeFlags.substring(sizeFlags.indexOf("=") + 1);
}
function getPlatform(flags) {
if (flags.includes("android") && flags.includes("ios")) {
throwError({message: "You cannot use both --android and --ios flags!"});
}
if (flags.includes("android")) {
return "android";
} else if (flags.includes("ios")) {
return "ios";
} else {
throwError({message: "You must provide a target platform! Use either --android, or --ios flag."});
}
}
function checkFileSizeIsUnder(fileName, sizeInBytes) {
console.log(`Checking ${fileName} size is under ${sizeInBytes}`);
return new Promise((resolve, reject) => {
readFile(fileName)
.then(content => {
if (content.length <= sizeInBytes) {
resolve();
} else {
reject({message: `File "${fileName}" exceeded file size of "${sizeInBytes}".`});
}
});
});
}
function readFile(fileName) {
return new Promise((resolve, reject) => {
fs.readFile(fileName, "utf-8", (err, data) => {
if (err) {
reject(err);
} else {
resolve(data);
}
});
});
}
function throwError(error) {
console.error(error.message);
process.exit(error.code || 1);
}

View File

@@ -1 +0,0 @@
@node %~dp0\ns-verify-bundle %*

View File

@@ -1 +0,0 @@
@node %~dp0\ns-webpack-update %*

View File

@@ -1,3 +0,0 @@
#!/usr/bin/env node
const installer = require("../installer");
installer.uninstall();

View File

@@ -1 +0,0 @@
@node %~dp0\remove-ns-webpack %*

View File

@@ -1,20 +0,0 @@
#!/usr/bin/env node
const update = require("../verify/update");
const { original: npmArgs = [] } = JSON.parse(process.env.npm_config_argv || "{}");
const [, , ...processArgs ] = process.argv;
const flags = [ ...npmArgs, ...processArgs ]
.filter(f => f.startsWith("--"))
.map(f => f.substring(2));
function addOption(all, current) {
all[current] = true;
return all;
}
const options = flags.reduce(addOption, {});
update(options);

View File

@@ -1,30 +0,0 @@
#!/usr/bin/env node
const { dirname: pathDirname } = require("path");
const { getPackageJson, writePackageJson } = require("../projectHelpers");
let processArgs = process.argv;
const distTag = processArgs.includes("--distTag") ? processArgs[processArgs.indexOf("--distTag") + 1] : undefined;
const tag = distTag || process.env["distTag"] || "next";
const projectDir = pathDirname(__dirname);
const packageJson = getPackageJson(projectDir);
if (distTag) {
processArgs = processArgs.splice(processArgs.indexOf("--distTag"), 2);
}
const [, , packageVersion = new Date()] = processArgs;
packageJson.publishConfig = Object.assign(
packageJson.publishConfig || {},
{ tag }
);
delete packageJson.private;
const currentVersion = packageJson.version;
const nextVersion = `${currentVersion}-${packageVersion}`;
const newPackageJson = Object.assign(packageJson, { version: nextVersion });
writePackageJson(newPackageJson, projectDir);

View File

@@ -1,3 +0,0 @@
import { loader } from 'webpack';
declare const loader: loader.Loader;
export default loader;

View File

@@ -1 +0,0 @@
export {};

View File

@@ -1,114 +0,0 @@
import bundleConfigLoader from './bundle-config-loader';
const defaultLoaderOptions = {
angular: false,
loadCss: true,
unitTesting: false,
};
const defaultTestFiles = {
'./app-root.xml': true,
'./app-root.land.xml': true,
'./app.ts': true,
'./app.css': true,
'./app.android.scss': true,
'./app.ios.scss': true,
'./app.land.css': true,
'./components/my-component.css': true,
'./components/my-component.land.ts': true,
'./components/my-component.ts': true,
'./components/my-component.land.xml': true,
'./components/my-component.xml': true,
'./components/my-component.land.css': true,
'./main/main-page.land.css': true,
'./main/main-page.css': true,
'./main/main-page.ts': true,
'./main/main-page.land.xml': true,
'./main/main-page.xml': true,
'./main/main-page.land.ts': true,
'./main/main-page-vm.ts': true,
'./app_component.scss': true,
'./App_Resources123/some-file.xml': true,
'./MyApp_Resources/some-file.xml': true,
'./App_Resources_Nobody_Names_Folders_Like_That_Roska/some-file.xml': true,
'./package.json': false, // do not include package.json files
'./app.d.ts': false, // do not include ts definitions
'./_app-common.scss': false, // do not include scss partial files
'./_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 = {
angular: false,
loadCss: true,
unitTesting: false,
ignoredFiles: ['./application', './activity'],
};
const ignoredTestFiles = {
'./application.ts': false,
'./activity.ts': false,
};
function getRequireContextRegex(source: string): RegExp {
const requireContextStr = `require.context("~/", true, `;
expect(source).toContain(requireContextStr);
const start = source.indexOf(requireContextStr) + requireContextStr.length;
const end = source.indexOf(');\n', start);
const regexStr = source.substring(start, end);
const regex: RegExp = eval(regexStr);
expect(regex instanceof RegExp).toBeTruthy();
return regex;
}
function assertTestFilesAreMatched(testFiles: { [key: string]: boolean }, regex: RegExp) {
for (let fileName in testFiles) {
if (defaultTestFiles[fileName]) {
expect(fileName).toMatch(regex);
} else {
expect(fileName).not.toMatch(regex);
}
}
}
describe('BundleConfigLoader should create require.context', () => {
it('default case', (done) => {
const loaderContext = {
callback: (error, source: string, map) => {
const regex = getRequireContextRegex(source);
assertTestFilesAreMatched(defaultTestFiles, regex);
done();
},
query: defaultLoaderOptions,
};
bundleConfigLoader.call(loaderContext, ' ___CODE___');
});
it('with ignored files', (done) => {
const loaderContext = {
callback: (error, source: string, map) => {
const regex = getRequireContextRegex(source);
const testFiles = { ...defaultTestFiles, ...ignoredTestFiles };
assertTestFilesAreMatched(testFiles, regex);
done();
},
query: loaderOptionsWithIgnore,
};
bundleConfigLoader.call(loaderContext, ' ___CODE___');
});
});

View File

@@ -1,88 +0,0 @@
import * as unitTestingConfigLoader from './unit-testing-config-loader';
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 and in tests folder
const defaultMatch = '(?<!\\bApp_Resources\\b.*)(?<!\\.\\/\\btests\\b\\/.*?)\\.(xml|css|js|(?<!\\.d\\.)ts|(?<!\\b_[\\w-]*\\.)scss)$';
const loader: loader.Loader = function (source, map) {
let { angular = false, loadCss = true, platform, unitTesting, projectRoot, appFullPath, registerModules, ignoredFiles = [] } = getOptions(this);
if (!registerModules) {
registerModules = defaultMatch;
for (const key in <any>ignoredFiles) {
registerModules = `(?<!${escapeRegExp(ignoredFiles[key])})` + registerModules;
}
registerModules = <any>new RegExp(registerModules);
}
if (unitTesting) {
source = unitTestingConfigLoader({ appFullPath, projectRoot, angular, rootPagesRegExp: registerModules });
this.callback(null, source);
return;
}
const hmr = `
if (module.hot) {
const hmrUpdate = require("@nativescript/webpack/hmr").hmrUpdate;
global.__coreModulesLiveSync = global.__onLiveSync;
global.__onLiveSync = function () {
// handle hot updated on LiveSync
hmrUpdate();
};
global.hmrRefresh = function({ type, path } = {}) {
// the hot updates are applied, ask the modules to apply the changes
setTimeout(() => {
global.__coreModulesLiveSync({ type, path });
});
};
// handle hot updated on initial app start
hmrUpdate();
}
`;
let sourceModule = '@nativescript/core';
if (platform && platform !== 'ios' && platform !== 'android') {
sourceModule = `nativescript-platform-${platform}`;
}
source = `
import "${sourceModule}/bundle-entry-points";
${source}
`;
if (angular) {
source = `
${hmr}
${source}
`;
} else if (registerModules) {
source = `
${hmr}
const context = require.context("~/", true, ${registerModules});
global.registerWebpackModules(context);
if (module.hot) {
module.hot.accept(context.id, () => {
console.log("HMR: Accept module '" + context.id + "' from '" + module.id + "'");
});
}
${source}
`;
}
if (loadCss) {
source = `
require("${angular ? '@nativescript/webpack/helpers/load-application-css-angular' : '@nativescript/webpack/helpers/load-application-css-regular'}")();
${source}
`;
}
this.callback(null, source, map);
};
export default loader;

View File

@@ -1,18 +0,0 @@
const { convertSlashesInPath } = require("./projectHelpers");
module.exports = function (source, map) {
this.cacheable();
const { modules } = this.query;
const imports = modules.map(convertSlashesInPath)
.map(m => `require("${m}");`).join("\n");
const augmentedSource = `
const isAndroid = require("@nativescript/core").isAndroid;
if (isAndroid && !global["__snapshot"]) {
${imports}
}
${source}
`;
this.callback(null, augmentedSource, map);
};

View File

@@ -1,77 +0,0 @@
const { join, resolve } = require("path");
const { existsSync, readFileSync } = require("fs");
const { getPackageJson } = require("./projectHelpers");
const PLATFORMS_ANDROID = "platforms/android";
const ANDROID_PROJECT_DIR = join(PLATFORMS_ANDROID, "app");
const ANDROID_APP_PATH = join(ANDROID_PROJECT_DIR, "src/main/assets/app");
const ANDROID_CONFIGURATIONS_PATH = join(ANDROID_PROJECT_DIR, "build/configurations");
const getAndroidRuntimeVersion = (projectDir) => {
try {
const projectPackageJSON = getPackageJson(projectDir);
const version = projectPackageJSON["nativescript"]["tns-android"]["version"];
return version && toReleaseVersion(version);
} catch (e) {
return null;
}
}
const toReleaseVersion = version => version.replace(/-.*/, "");
const getAndroidV8Version = (projectDir) => {
try {
const androidSettingsJSON = getAndroidSettingsJson(projectDir);
if (androidSettingsJSON !== null) {
return androidSettingsJSON.v8Version;
} else {
return null;
}
} catch (e) {
return null;
}
}
const getMksnapshotParams = (projectDir) => {
try {
const androidSettingsJSON = getAndroidSettingsJson(projectDir);
if (androidSettingsJSON !== null) {
return androidSettingsJSON.mksnapshotParams;
} else {
return null;
}
} catch (e) {
return null;
}
};
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)) {
return JSON.parse(readFileSync(androidSettingsJsonPath, "utf8"));
} else {
return null;
}
};
module.exports = {
ANDROID_PROJECT_DIR,
ANDROID_APP_PATH,
ANDROID_CONFIGURATIONS_PATH,
getAndroidRuntimeVersion,
getAndroidV8Version,
getMksnapshotParams,
getRuntimeNdkRevision
};

View File

@@ -1,114 +0,0 @@
const { resolve } = require('path');
const fs = require('fs');
const parseWorkspaceConfig = function(platform, envConfigs, projectName, debug) {
if (debug) {
console.log('-- config DEBUG ---');
console.log('platform:', platform);
console.log('configuration:', envConfigs);
}
// configuration file replacements
const fileReplacements = {};
// anything other than .ts files should be added as part of copy plugin
const copyReplacements = [];
if (hasConfigurations(envConfigs)) {
envConfigs = envConfigs.split(',').map(e => e.trim());
const configData = findConfig(__dirname);
const rootPath = configData.rootPath;
const workspaceConfig = configData.workspaceConfig;
if (workspaceConfig && projectName) {
const projectSettings = workspaceConfig.projects[projectName];
if (projectSettings) {
var targetProp = 'architect';
if (!projectSettings.architect) {
targetProp = 'targets'; // Nx
}
// default project configurations
for (const envConfig of envConfigs) {
if (projectSettings[targetProp]) {
if (projectSettings[targetProp].default && projectSettings[targetProp].default.configurations) {
const defaultConfigurations = projectSettings[targetProp].default.configurations;
if (defaultConfigurations && defaultConfigurations[envConfig]) {
if (defaultConfigurations[envConfig].fileReplacements) {
for (const fileReplace of defaultConfigurations[envConfig].fileReplacements) {
if (debug) {
console.log('project fileReplacement:', fileReplace);
}
if (fileReplace.replace.indexOf('.ts') > -1) {
fileReplacements[resolve(__dirname, `${rootPath}${fileReplace.replace}`)] = resolve(__dirname, `${rootPath}${fileReplace.with}`);
} else {
copyReplacements.push({ from: resolve(__dirname, `${rootPath}${fileReplace.with}`), to: resolve(__dirname, `${rootPath}${fileReplace.replace}`), force: true });
}
}
}
}
}
}
}
// platform specific configurations (always override top level project configurations)
for (const envConfig of envConfigs) {
if (projectSettings[targetProp]) {
if (projectSettings[targetProp] && projectSettings[targetProp][platform]) {
const platformConfig = projectSettings[targetProp][platform].configurations;
if (platformConfig && platformConfig[envConfig] && platformConfig[envConfig].fileReplacements) {
for (const fileReplace of platformConfig[envConfig].fileReplacements) {
if (debug) {
console.log(`"${platform}" specific fileReplacement:`, fileReplace);
}
if (fileReplace.replace.indexOf('.ts') > -1) {
fileReplacements[resolve(__dirname, `${rootPath}${fileReplace.replace}`)] = resolve(__dirname, `${rootPath}${fileReplace.with}`);
} else {
copyReplacements.push({ from: resolve(__dirname, `${rootPath}${fileReplace.with}`), to: resolve(__dirname, `${rootPath}${fileReplace.replace}`), force: true });
}
}
}
}
}
}
}
}
}
if (debug && copyReplacements.length) {
console.log('Adding to CopyWebpackPlugin:', copyReplacements);
}
return {
fileReplacements,
copyReplacements
};
}
const findConfig = function(projectDir, rootPath = '') {
// support workspace.json and angular.json configurations
const angularConfigName = 'angular.json';
const angularConfig = resolve(projectDir, angularConfigName);
const workspaceConfigName = 'workspace.json';
const workspaceConfig = resolve(projectDir, workspaceConfigName);
if (fs.existsSync(workspaceConfig)) {
return {
rootPath,
workspaceConfig: require(workspaceConfig)
};
} else if (fs.existsSync(angularConfig)) {
return {
rootPath,
workspaceConfig: require(angularConfig)
};
} else {
rootPath += '../';
return findConfig(resolve(projectDir, '..'), rootPath);
}
}
const hasConfigurations = function(envConfigs) {
return envConfigs && envConfigs !== 'undefined';
}
module.exports = {
parseWorkspaceConfig,
findConfig,
hasConfigurations
};

View File

@@ -1,43 +0,0 @@
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("@nativescript/core");
require("@nativescript/core/ui/styling/style-scope");
if (___CSS_LOADER_EXPORT___ && typeof ___CSS_LOADER_EXPORT___.forEach === "function") {
___CSS_LOADER_EXPORT___.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);
}

View File

@@ -1,3 +0,0 @@
import { loader } from 'webpack';
declare const loader: loader.Loader;
export default loader;

View File

@@ -1 +0,0 @@
export {};

View File

@@ -1,66 +0,0 @@
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/webpack/helpers/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);
});
});

View File

@@ -1,59 +0,0 @@
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/webpack/helpers/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};`);
};
function getImportRules(ast: Stylesheet): Import[] {
if (!ast || (<any>ast).type !== 'stylesheet' || !ast.stylesheet) {
return [];
}
return <Import[]>ast.stylesheet.rules.filter((rule) => rule.type === 'import' && (<any>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;

View File

@@ -1,110 +0,0 @@
const { isAngular } = require("./projectHelpers");
const NEW_DEPS_MESSAGE = `\
A few new dependencies were added. \
Run "npm install" before building your project.
`;
const ALREADY_ADDED_MESSAGE = `\
Some dependencies have already been added. \
If you want to force update them, please run "node_modules/.bin/update-ns-webpack".
`;
function addProjectDeps(packageJson, force = false) {
packageJson.devDependencies = packageJson.devDependencies || {};
const postinstallOptions = {
deps: Object.assign({}, packageJson.devDependencies),
};
const depsToAdd = getRequiredDeps(packageJson);
Object.keys(depsToAdd).forEach(function (name) {
const version = depsToAdd[name];
Object.assign(postinstallOptions,
addDependency(postinstallOptions.deps, name, version, force));
});
return postinstallOptions;
}
function forceUpdateProjectDeps(packageJson) {
removeObsoleteDeps(packageJson);
return addProjectDeps(packageJson, true);
}
function removeObsoleteDeps(packageJson) {
const depsToRemove = [
"webpack",
"webpack-cli",
"webpack-bundle-analyzer",
"webpack-sources",
"clean-webpack-plugin",
"copy-webpack-plugin",
"raw-loader",
"css-loader",
"nativescript-worker-loader",
"terser-webpack-plugin",
"@angular-devkit/core",
"resolve-url-loader",
"sass-loader",
];
depsToRemove.forEach(dep => delete packageJson.devDependencies[dep]);
}
function addDependency(deps, name, version, force) {
const options = { deps };
if (!deps[name] || force) {
deps[name] = version;
options.newDepsAdded = true;
console.info(`Adding dev dependency: ${name}@${version}`);
} else if (deps[name] !== version) {
options.hasOldDeps = true;
console.info(`Dev dependency: ${name} already added. Leaving version: ${deps[name]}`);
}
return options;
}
function getRequiredDeps(packageJson) {
if (!isAngular({ packageJson })) {
return false;
}
const deps = {
"@angular/compiler-cli": "~10.0.0",
};
if (!dependsOn(packageJson, "@angular-devkit/build-angular")) {
deps["@ngtools/webpack"] = "~10.0.0";
}
return deps;
}
function dependsOn(packageJson, package) {
if (!packageJson) {
return false;
}
return packageJson.dependencies.hasOwnProperty(package) ||
packageJson.devDependencies.hasOwnProperty(package);
}
function showHelperMessages({ newDepsAdded, hasOldDeps }) {
if (hasOldDeps) {
console.info(ALREADY_ADDED_MESSAGE);
}
if (newDepsAdded) {
console.info(NEW_DEPS_MESSAGE);
}
}
module.exports = {
addProjectDeps,
forceUpdateProjectDeps,
showHelperMessages,
};

View File

@@ -1,9 +0,0 @@
module.exports.reload = function ({ type, path }) {
return `
if (module.hot) {
module.hot.accept();
module.hot.dispose(() => {
global.hmrRefresh({ type: '${type}', path: '${path}' });
})
}
`};

View File

@@ -1,172 +0,0 @@
const hmrPrefix = 'HMR:';
const log = {
info: (message) => console.info(`${hmrPrefix} ${message}`),
warn: (message) => console.warn(`${hmrPrefix} ${message}`),
error: (message) => console.error(`${hmrPrefix} ${message}`),
};
const refresh = 'Application needs to be restarted in order to apply the changes.';
const hotOptions = {
ignoreUnaccepted: false,
ignoreDeclined: false,
ignoreErrored: false,
onUnaccepted(data) {
const chain = [].concat(data.chain);
const last = chain[chain.length - 1];
if (last === 0) {
chain.pop();
}
log.warn(`Ignored an update to unaccepted module: `);
chain.forEach(mod => log.warn(`${mod}`));
},
onDeclined(data) {
log.warn(`Ignored an update to declined module:`);
data.chain.forEach(mod => log.warn(`${mod}`));
},
onErrored(data) {
log.warn(
`Ignored an error while updating module ${data.moduleId} <${data.type}>`
);
log.warn(data.error);
},
};
let nextHash;
let currentHash;
function upToDate() {
return nextHash.indexOf(__webpack_hash__) >= 0;
}
function result(modules, appliedModules) {
const unaccepted = modules.filter(
(moduleId) => appliedModules && appliedModules.indexOf(moduleId) < 0
);
if (unaccepted.length > 0) {
log.warn('The following modules could not be updated:');
for (const moduleId of unaccepted) {
log.warn(`${moduleId}`);
}
}
if (!(appliedModules || []).length) {
log.info('No Modules Updated.');
} else {
log.info('The following modules were updated:');
for (const moduleId of appliedModules) {
log.info(`${moduleId}`);
}
const numberIds = appliedModules.every(
(moduleId) => typeof moduleId === 'number'
);
if (numberIds) {
log.info(
'Please consider using the NamedModulesPlugin for module names.'
);
}
}
}
function check(options) {
return module.hot
.check()
.then((modules) => {
if (!modules) {
log.warn(
`Cannot find update. ${refresh}`
);
return null;
}
return module.hot
.apply(hotOptions)
.then((appliedModules) => {
let nextCheck;
if (!upToDate()) {
nextCheck = check(options);
}
result(modules, appliedModules);
if (upToDate()) {
// Do not modify message - CLI depends on this exact content to determine hmr operation status.
log.info(`Successfully applied update with hmr hash ${currentHash}. App is up to date.`);
}
return nextCheck || null;
})
.catch((err) => {
const status = module.hot.status();
if (['abort', 'fail'].indexOf(status) >= 0) {
// Do not modify message - CLI depends on this exact content to determine hmr operation status.
log.error(`Cannot apply update with hmr hash ${currentHash}.`);
log.error(err.message || err.stack);
} else {
log.error(`Update failed: ${err.message || err.stack}`);
}
});
})
.catch((err) => {
const status = module.hot.status();
if (['abort', 'fail'].indexOf(status) >= 0) {
log.error(`Cannot check for update. ${refresh}`);
log.error(err.message || err.stack);
} else {
log.error(`Update check failed: ${err.message || err.stack}`);
}
});
}
if (module.hot) {
log.info('Hot Module Replacement Enabled. Waiting for signal.');
} else {
log.error('Hot Module Replacement is disabled.');
}
function update(latestHash, options) {
nextHash = latestHash;
if (!upToDate()) {
const status = module.hot.status();
if (status === 'idle') {
//Do not modify message - CLI depends on this exact content to determine hmr operation status.
log.info(`Checking for updates to the bundle with hmr hash ${currentHash}.`);
return check(options);
} else if (['abort', 'fail'].indexOf(status) >= 0) {
log.warn(
`Cannot apply update. A previous update ${status}ed. ${refresh}`
);
}
}
};
function getNextHash(hash, getFileContent) {
const file = getFileContent(`${hash}.hot-update.json`);
if (!file) {
return Promise.resolve(hash);
}
return file.readText().then(hotUpdateContent => {
if (hotUpdateContent) {
const manifest = JSON.parse(hotUpdateContent);
const newHash = manifest.h;
return getNextHash(newHash, getFileContent);
} else {
return Promise.resolve(hash);
}
}).catch(error => Promise.reject(error));
}
module.exports = function checkState(initialHash, getFileContent) {
currentHash = initialHash;
return getNextHash(initialHash, getFileContent).then(nextHash => {
if (nextHash != initialHash) {
return update(nextHash, {});
}
})
}

View File

@@ -1,30 +0,0 @@
const { safeGet } = require("./projectHelpers");
const LAZY_RESOURCE_CONTEXT = "$$_lazy_route_resource";
const HOT_SELF_ACCEPT = "module.hot.accept();";
const HOT_DISPOSE = `
module.hot.dispose(() => {
// Currently the context is needed only for application style modules.
const moduleContext = {};
global.hmrRefresh(moduleContext);
});`;
const HMR_HANDLER = `
if (module.hot) {
${HOT_SELF_ACCEPT}${HOT_DISPOSE}
}
`;
const isLazyLoadedNgModule = resource => {
const issuer = safeGet(resource, "issuer");
const issuerContext = safeGet(issuer, "context");
return issuerContext && issuerContext.endsWith(LAZY_RESOURCE_CONTEXT);
};
module.exports = function (source, map) {
const modifiedSource = isLazyLoadedNgModule(this._module) ?
`${source};${HMR_HANDLER}`:
source;
this.callback(null, modifiedSource, map);
};

View File

@@ -1,8 +0,0 @@
const loadCss = require("./load-application-css");
module.exports = function() {
loadCss(function() {
global.registerModule("./app.css", () => require("~/app"));
global.registerModule("app.css", () => require("~/app"));
});
}

View File

@@ -1,8 +0,0 @@
const loadCss = require("./load-application-css");
module.exports = function() {
loadCss(function() {
const appCssContext = require.context("~/", false, /^\.\/app\.(css|scss|less|sass)$/);
global.registerWebpackModules(appCssContext);
});
}

View File

@@ -1,8 +0,0 @@
module.exports = function (loadModuleFn) {
const nsCore = require("@nativescript/core");
require("@nativescript/core/ui/styling/style-scope");
loadModuleFn();
nsCore.Application.loadAppCss();
}

View File

@@ -1,10 +0,0 @@
const { reload } = require("./hot-loader-helper");
const { convertToUnixPath } = require("../lib/utils");
module.exports = function (source, map) {
const typeMarkup = "markup";
const moduleRelativePath = this.resourcePath.replace(this.rootContext, ".");
const modulePath = convertToUnixPath(moduleRelativePath);
this.callback(null, `${source};${reload({ type: typeMarkup, path: modulePath })}`, map);
};

View File

@@ -1,16 +0,0 @@
/**
* When building NativeScript angular apps without webpack (`tns run android`) the moduleId: module.id is necessary.
* When building with webpack the angular compiler and webpack handle relative paths in the modules and no longer need moduleId
* to be set, however webpack emits numbers for module.id and angular has typecheck for moduleId to be a string.
*/
module.exports = function (source, map) {
this.cacheable();
// Strips occurrences of `moduleId: module.id,`, since it is no longer needed for webpack builds
const noModuleIdsSource = source.replace(/moduleId\:\s*module\.id\s*(\,)?/g, result =>
// Try to preserve char count so sourcemaps may remain intact
"/*" + result.substring(2, result.length - 2) + "*/"
);
this.callback(null, noModuleIdsSource, map);
};

View File

@@ -1,152 +0,0 @@
const path = require("path");
const fs = require("fs");
const { isTypeScript, isAngular, isVue, isReact, isSvelte, isShared, isPlugin } = require("./projectHelpers");
function addProjectFiles(projectDir) {
const projectTemplates = getProjectTemplates(projectDir);
Object.keys(projectTemplates).forEach(function (templateName) {
const templateDestination = projectTemplates[templateName];
templateName = path.resolve(templateName);
copyTemplate(templateName, templateDestination);
});
}
function removeProjectFiles(projectDir) {
const projectTemplates = getProjectTemplates(projectDir);
Object.keys(projectTemplates).forEach(function (templateName) {
const templateDestination = projectTemplates[templateName];
deleteFile(templateDestination);
});
}
function forceUpdateProjectFiles(projectDir) {
removeProjectFiles(projectDir);
addProjectFiles(projectDir);
}
function compareProjectFiles(projectDir) {
const projectTemplates = getProjectTemplates(projectDir);
Object.keys(projectTemplates).forEach(newTemplatePath => {
const currentTemplatePath = projectTemplates[newTemplatePath];
if (fs.existsSync(currentTemplatePath)) {
const currentTemplate = fs.readFileSync(currentTemplatePath).toString();
const newTemplate = fs.readFileSync(newTemplatePath).toString();
if (newTemplate.replace(/\s/g, '') !== currentTemplate.replace(/\s/g, '')) {
const message = `Note: ${path.basename(currentTemplatePath)} is different than the suggested @nativescript/webpack config at ${newTemplatePath}.\nIf you have build trouble you can update to latest config using "./node_modules/.bin/update-ns-webpack --configs" command.`;
console.info(`\x1B[33;1m${message}\x1B[0m`);
}
}
});
}
function deleteFile(destinationPath) {
if (fs.existsSync(destinationPath)) {
console.info(`Deleting file: ${destinationPath}`);
fs.unlinkSync(destinationPath);
}
}
function copyTemplate(templateName, destinationPath) {
// Create destination file, only if not present.
if (!fs.existsSync(destinationPath)) {
console.info(`Creating file: ${destinationPath}`);
const content = fs.readFileSync(templateName, "utf8");
fs.writeFileSync(destinationPath, content);
}
}
function getProjectTemplates(projectDir) {
const WEBPACK_CONFIG_NAME = "webpack.config.js";
let templates;
if (isPlugin({ projectDir })) {
// TODO: create config for plugin builds
templates = [];
} else if (isAngular({ projectDir })) {
if (isShared({ projectDir })) {
templates = getSharedAngularTemplates(WEBPACK_CONFIG_NAME);
} else {
templates = getAngularTemplates(WEBPACK_CONFIG_NAME);
}
} else if (isVue({ projectDir })) {
templates = getVueTemplates(WEBPACK_CONFIG_NAME);
} else if (isReact({ projectDir })) {
templates = getReactTemplates(WEBPACK_CONFIG_NAME);
} else if (isSvelte({ projectDir })) {
templates = getSvelteTemplates(WEBPACK_CONFIG_NAME);
} else if (isTypeScript({ projectDir })) {
templates = getTypeScriptTemplates(WEBPACK_CONFIG_NAME);
} else {
templates = getJavaScriptTemplates(WEBPACK_CONFIG_NAME);
}
return getFullTemplatesPath(projectDir, templates);
}
function getSharedAngularTemplates(webpackConfigName) {
return {
"webpack.angular.js": webpackConfigName,
};
}
function getAngularTemplates(webpackConfigName) {
return {
"webpack.angular.js": webpackConfigName
};
}
function getTypeScriptTemplates(webpackConfigName) {
return {
"webpack.typescript.js": webpackConfigName
};
}
function getVueTemplates(webpackConfigName) {
return {
"webpack.vue.js": webpackConfigName
};
}
function getReactTemplates(webpackConfigName) {
return {
"webpack.typescript.js": "webpack.typescript.js",
"webpack.react.js": webpackConfigName
};
}
function getSvelteTemplates(webpackConfigName) {
return {
"webpack.svelte.js": webpackConfigName
};
}
function getJavaScriptTemplates(webpackConfigName) {
return {
"webpack.javascript.js": webpackConfigName,
};
}
function getFullTemplatesPath(projectDir, templates) {
let updatedTemplates = {};
Object.keys(templates).forEach(key => {
const updatedKey = getFullPath(path.join(__dirname, "..", "templates"), key);
const updatedValue = getFullPath(projectDir, templates[key])
updatedTemplates[updatedKey] = updatedValue;
});
return updatedTemplates;
}
function getFullPath(projectDir, filePath) {
return path.resolve(projectDir, filePath);
}
module.exports = {
addProjectFiles,
removeProjectFiles,
forceUpdateProjectFiles,
compareProjectFiles,
};

View File

@@ -1,162 +0,0 @@
const { resolve } = require("path");
const fs = require("fs");
const hook = require("@nativescript/hook")(__dirname);
const isTypeScript = ({ projectDir, packageJson } = {}) => {
packageJson = packageJson || getPackageJson(projectDir);
return (
packageJson.dependencies &&
packageJson.dependencies.hasOwnProperty("typescript"))
|| (packageJson.devDependencies &&
packageJson.devDependencies.hasOwnProperty("typescript"))
|| isAngular({ packageJson });
};
const isShared = ({ projectDir }) => {
const nsConfig = getNsConfig(projectDir);
return nsConfig && !!nsConfig.shared;
}
const isPlugin = ({ projectDir, packageJson } = {}) => {
packageJson = packageJson || getPackageJson(projectDir);
return packageJson && packageJson.bootstrapper && (['nativescript-plugin-seed', '@nativescript/plugin-seed'].includes(packageJson.bootstrapper));
}
const isAngular = ({ projectDir, packageJson } = {}) => {
packageJson = packageJson || getPackageJson(projectDir);
return packageJson.dependencies && Object.keys(packageJson.dependencies)
.some(dependency => /^@angular\b/.test(dependency));
};
const getAngularVersion = ({ projectDir, packageJson } = {}) => {
packageJson = packageJson || getPackageJson(projectDir);
return packageJson.dependencies && packageJson.dependencies["@angular/core"];
}
const isVue = ({ projectDir, packageJson } = {}) => {
packageJson = packageJson || getPackageJson(projectDir);
return packageJson.dependencies && Object.keys(packageJson.dependencies)
.some(dependency => dependency === "nativescript-vue");
};
const isReact = ({ projectDir, packageJson } = {}) => {
packageJson = packageJson || getPackageJson(projectDir);
return packageJson.dependencies && Object.keys(packageJson.dependencies)
.some(dependency => dependency === "react-nativescript");
};
const isSvelte = ({ projectDir, packageJson } = {}) => {
packageJson = packageJson || getPackageJson(projectDir);
return packageJson.dependencies && Object.keys(packageJson.dependencies)
.some(dependency => dependency === "svelte-native");
};
const getPackageJson = projectDir => {
const packageJsonPath = getPackageJsonPath(projectDir);
const result = readJsonFile(packageJsonPath);
return result;
};
const getNsConfig = projectDir => {
const nsConfigPath = getNsConfigPath(projectDir);
const result = readJsonFile(nsConfigPath);
return result;
};
const readJsonFile = filePath => {
let result;
try {
result = JSON.parse(fs.readFileSync(filePath, "utf8"));
} catch (e) {
result = {};
}
return result;
};
const writePackageJson = (content, projectDir) => {
const packageJsonPath = getPackageJsonPath(projectDir);
const currentJsonContent = fs.readFileSync(packageJsonPath);
const indentation = getIndentationCharacter(currentJsonContent);
const stringifiedContent = JSON.stringify(content, null, indentation);
const currentPackageJsonContent = JSON.parse(currentJsonContent);
if (JSON.stringify(currentPackageJsonContent, null, indentation) !== stringifiedContent) {
fs.writeFileSync(packageJsonPath, stringifiedContent)
}
}
const getIndentationCharacter = (jsonContent) => {
const matches = jsonContent && jsonContent.toString().match(/{\r*\n*(\W*)"/m);
return matches && matches[1];
}
const getProjectDir = hook.findProjectDir;
const getPackageJsonPath = projectDir => {
const packagePath = resolve(projectDir, "package.json");
if (fs.existsSync(packagePath)) {
return packagePath;
} else {
return getPackageJsonPath(resolve(projectDir, '..'));
}
}
const getNsConfigPath = projectDir => resolve(projectDir, "nsconfig.json");
const isAndroid = platform => /android/i.test(platform);
const isIos = platform => /ios/i.test(platform);
function safeGet(object, property, ...args) {
if (!object) {
return;
}
const value = object[property];
if (!value) {
return;
}
return typeof value === "function" ?
value.bind(object)(...args) :
value;
}
// Convert paths from C:\some\path to C:/some/path in order to be required
function convertSlashesInPath(modulePath) {
if (isWindows) {
modulePath = modulePath.replace(/\\/g, "/");
}
return modulePath;
}
const isWindows = process.platform.startsWith("win32");
module.exports = {
getPackageJson,
getProjectDir,
isAndroid,
isIos,
isAngular,
isShared,
isPlugin,
getAngularVersion,
isVue,
isReact,
isSvelte,
isTypeScript,
writePackageJson,
convertSlashesInPath,
getIndentationCharacter,
safeGet,
};

View File

@@ -1,132 +0,0 @@
const { getIndentationCharacter, writePackageJson, safeGet } = require("./projectHelpers");
const fs = require("fs");
describe('projectHelpers', () => {
const originalReadFileSync = fs.readFileSync;
const originalWriteFileSync = fs.writeFileSync;
const tab = "\t";
const multipleSpaces = " ";
const twoSpaces = " ";
afterEach(() => {
fs.readFileSync = originalReadFileSync;
fs.writeFileSync = originalWriteFileSync;
});
describe('getIndentationCharacter', () => {
[
{
testName: 'returns two spaces when file starts with two spaces',
input: `{${twoSpaces}"abc": "1"${twoSpaces}}`,
expectedResult: twoSpaces
},
{
testName: 'returns two spaces when file starts with two spaces and binary content is passed',
input: Buffer.from(`{${twoSpaces}"abc": "1"${twoSpaces}}`),
expectedResult: twoSpaces
},
{
testName: 'returns empty string when file starts without any indentation',
input: `{"abc": "1"}`,
expectedResult: ''
},
{
testName: 'returns tab when file starts with tab',
input: `{${tab}"abc": "1"${tab}}`,
expectedResult: tab
},
{
testName: 'returns two spaces when file starts with two spaces and new line before them',
input: `{\n${twoSpaces}"abc": "1"\n}`,
expectedResult: twoSpaces
},
{
testName: 'returns tab when file starts with tab and new line before them',
input: `{\n${tab}"abc": "1"\n}`,
expectedResult: tab
},
{
testName: 'returns multiple spaces when file starts with multiple spaces and new line before them',
input: `{\n${multipleSpaces}"abc": "1"\n}`,
expectedResult: multipleSpaces
}
].forEach(({ testName, input, expectedResult }) => {
it(testName, () => {
expect(getIndentationCharacter(input)).toEqual(expectedResult);
});
});
});
describe('safeGet', () => {
it('should return the correct value of existing properties', () => {
const obj = { a: 15 };
expect(safeGet(obj, 'a')).toBe(15);
});
it('should return undefined for unexisting properties', () => {
const obj = { a: 15 };
expect(safeGet(obj, 'random')).toBeUndefined();
});
it('should return undefined when the first argument is undefined', () => {
let obj;
expect(safeGet(obj, 'random')).toBeUndefined();
});
it('should return undefined when the first argument is not an object and does not have inherited property with the queried name', () => {
const num = 15;
expect(safeGet(num, 'random')).toBeUndefined();
});
});
describe('writePackageJson', () => {
const mockFileSystemApi = () => {
const data = {
isWriteFileSyncCalled: false
};
fs.readFileSync = (p) => {
return JSON.stringify({ a: 1 });
};
fs.writeFileSync = (p, c) => {
data.isWriteFileSyncCalled = true;
};
return data;
};
it('does not write package.json when content has not changed', () => {
const data = mockFileSystemApi();
writePackageJson({ a: 1 }, "projDir");
expect(data.isWriteFileSyncCalled).toBe(false);
});
it('writes content, when the new one is different from the current one', () => {
const data = mockFileSystemApi();
writePackageJson({ b: 2 }, "projDir");
expect(data.isWriteFileSyncCalled).toBe(true);
});
it('keeps indentation of the package.json when rewriting it', () => {
let currentIndentSymbol = tab;
fs.readFileSync = (p) => {
return JSON.stringify({ a: 1 }, null, currentIndentSymbol);
};
let writtenContent = null;
fs.writeFileSync = (p, c) => {
writtenContent = c;
};
// Ensure tab indentation is persisted
writePackageJson({ b: 2 }, "projDir");
expect(writtenContent).toBe(`{\n${tab}"b": 2\n}`);
// Ensure spaces indentation is persisted
currentIndentSymbol = multipleSpaces;
writePackageJson({ b: 2 }, "projDir");
expect(writtenContent).toBe(`{\n${multipleSpaces}"b": 2\n}`);
});
});
});

View File

@@ -1,9 +0,0 @@
const { reload } = require("./hot-loader-helper");
const { convertToUnixPath } = require("../lib/utils");
module.exports = function (source, map) {
const typeScript = "script";
const moduleRelativePath = this.resourcePath.replace(this.rootContext, ".");
const modulePath = convertToUnixPath(moduleRelativePath);
this.callback(null, `${source};${reload({ type: typeScript, path: modulePath })}`, map);
};

View File

@@ -1,10 +0,0 @@
const { reload } = require("./hot-loader-helper");
const { convertToUnixPath } = require("../lib/utils");
module.exports = function (source, map) {
const typeStyle = "style";
const moduleRelativePath = this.resourcePath.replace(this.rootContext, ".");
const modulePath = convertToUnixPath(moduleRelativePath);
this.callback(null, `${source};${reload({ type: typeStyle, path: modulePath })}`, map);
};

View File

@@ -1,3 +0,0 @@
import { loader } from 'webpack';
declare const loader: loader.Loader;
export default loader;

View File

@@ -1 +0,0 @@
export {};

View File

@@ -1,296 +0,0 @@
import xmlNsLoader from './xml-namespace-loader';
import { convertSlashesInPath } from './projectHelpers';
const CODE_FILE = `
<Page xmlns="http://www.nativescript.org/tns.xsd">
<StackLayout>
<GridLayout xmlns:chart="nativescript-ui-chart">
<chart:RadCartesianChart></chart:RadCartesianChart>
</GridLayout>
<GridLayout xmlns:chart="nativescript-ui-chart">
<chart:RadCartesianChart></chart:RadCartesianChart>
</GridLayout>
</StackLayout>
</Page>
`;
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 = `
<?xml version="1.0" encoding="UTF-8" standalone="no" ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<!-- comment.xml -->
<Page xmlns="http://www.nativescript.org/tns.xsd"></Page>`;
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 = `<Page xmlns="http://www.nativescript.org/tns.xsd"></PageOpsWrongTagHere>`;
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 = `
<Page xmlns="http://www.nativescript.org/tns.xsd">
<ios:GridLayout />
<android:GridLayout />
</Page>`;
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 = `
<Page xmlns="http://www.nativescript.org/tns.xsd">
<ios:GridLayout />
<ios:GridLayout></ios:GridLayout>
<android:GridLayout />
<android:GridLayout></android:GridLayout>
</Page>`;
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 = `
<Page xmlns="http://www.nativescript.org/tns.xsd">
<custom1:CustomComponent />
<custom2:CustomComponent />
</Page>`;
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 = `
<Page xmlns="http://www.nativescript.org/tns.xsd">
<StackLayout xmlns:chart="nativescript-ui-chart">
<TextField text="{{ var1 && var2 || var1 >= var2 || var2 <= var1 }}" />
<chart:RadCartesianChart></chart:RadCartesianChart>
</StackLayout>
</Page>`;
const loaderContext = getContext(done, { resolveMap, expectedDeps, expectedRegs, expectWarnings: 1 });
xmlNsLoader.call(loaderContext, testXml);
});
});

View File

@@ -1,141 +0,0 @@
import { parse, join } from 'path';
import { promisify } from 'util';
import { loader } from 'webpack';
import { parser, QualifiedTag } from 'sax';
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 resolvePromise = promisify(this.resolve);
const promises: Promise<any>[] = [];
const namespaces: NamespaceEntry[] = [];
const handleOpenTag = (namespace: string, elementName: string) => {
const moduleName = `${namespace}/${elementName}`;
if (namespace && !namespace.startsWith('http') && !namespaces.some((n) => n.name === moduleName) && (!ignore || !moduleName.match(ignore))) {
const localNamespacePath = join(this.rootContext, namespace);
const localModulePath = join(localNamespacePath, elementName);
const pathResolved = (resolvedPath) => {
this.addDependency(resolvedPath);
namespaces.push({ name: namespace, path: resolvedPath });
namespaces.push({ name: moduleName, path: resolvedPath });
const { dir, name } = parse(resolvedPath);
const noExtFilename = join(dir, name);
return Promise.all([
resolvePromise(this.context, `${noExtFilename}.xml`)
.then((xml) => {
this.addDependency(xml);
namespaces.push({ name: `${moduleName}.xml`, path: xml });
})
.catch((err) => {}),
resolvePromise(this.context, `${noExtFilename}.css`)
.then((css) => {
this.addDependency(css);
namespaces.push({ name: `${moduleName}.css`, path: css });
})
.catch((err) => {}),
]);
};
promises.push(
resolvePromise(this.context, localNamespacePath)
.then((path) => pathResolved(path))
.catch(() => {
return resolvePromise(this.context, localModulePath)
.then((path) => pathResolved(path))
.catch(() => {
return Promise.all([
resolvePromise(this.context, `${localModulePath}.xml`)
.then((xml) => {
namespaces.push({ name: `${moduleName}.xml`, path: xml });
this.addDependency(xml);
})
.catch(() => {
namespaces.push({ name: namespace, path: namespace });
namespaces.push({ name: moduleName, path: namespace });
}),
resolvePromise(this.context, `${localModulePath}.css`)
.then((css) => {
namespaces.push({ name: `${moduleName}.css`, path: css });
this.addDependency(css);
})
.catch(() => {}),
]);
});
})
);
}
};
const saxParser = parser(true, { xmlns: true });
// Register ios and android prefixes as namespaces to avoid "unbound xml namespace" errors
(<any>saxParser).ns['ios'] = 'http://schemas.nativescript.org/tns.xsd';
(<any>saxParser).ns['android'] = 'http://schemas.nativescript.org/tns.xsd';
(<any>saxParser).ns['desktop'] = 'http://schemas.nativescript.org/tns.xsd';
(<any>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);
}
saxParser.error = null;
};
saxParser.write(source).close();
Promise.all(promises)
.then(() => {
const distinctNamespaces = new Map<string, string>();
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
const json = JSON.stringify(source)
.replace(/\u2028/g, '\\u2028')
.replace(/\u2029/g, '\\u2029');
const wrapped = `${moduleRegisters.join('')}\nmodule.exports = ${json}`;
callbackWrapper(null, wrapped, map);
})
.catch((err) => {
callbackWrapper(err);
});
};
export default loader;

View File

@@ -1 +0,0 @@
export declare function hmrUpdate(): any;

View File

@@ -1,13 +0,0 @@
import * as hot from '../helpers/hot';
declare const __webpack_require__: any;
export function hmrUpdate() {
const coreFile = require('@nativescript/core');
const currentAppFolder = coreFile.knownFolders.currentApp();
const latestHash = __webpack_require__['h']();
return hot(latestHash, (filename) => {
const fullFilePath = coreFile.path.join(currentAppFolder.path, filename);
return coreFile.File.exists(fullFilePath) ? currentAppFolder.getFile(filename) : null;
});
}

View File

@@ -1,3 +0,0 @@
import { loader } from 'webpack';
declare const loader: loader.Loader;
export default loader;

View File

@@ -1,41 +0,0 @@
import { loader } from 'webpack';
import { convertToUnixPath } from '../lib/utils';
import { extname } from 'path';
import { getOptions } from 'loader-utils';
const extMap = {
'.css': 'style',
'.scss': 'style',
'.less': 'style',
'.js': 'script',
'.ts': 'script',
'.xml': 'markup',
'.html': 'markup',
};
const loader: loader.Loader = function (source, map) {
const moduleRelativePath = this.resourcePath.replace(this.rootContext, '.');
const modulePath = convertToUnixPath(moduleRelativePath);
const ext = extname(modulePath).toLowerCase();
const moduleType = extMap[ext] || 'unknown';
const options = getOptions(this) || {};
const alwaysSelfAccept = options.alwaysSelfAccept;
const trace = options.trace;
const shouldAutoAcceptCheck = `&& global._isModuleLoadedForUI && global._isModuleLoadedForUI("${modulePath}")`;
const traceCode = `console.log("[hot-loader]: Self-accept module: ${modulePath}");`;
const hotCode = `
if (module.hot ${alwaysSelfAccept ? '' : shouldAutoAcceptCheck} ) {
${trace ? traceCode : ''}
module.hot.accept();
module.hot.dispose(() => {
global.hmrRefresh({ type: "${moduleType}", path: "${modulePath}" });
});
}`;
this.callback(null, `${source}; ${hotCode} `, map);
};
export default loader;

View File

@@ -1 +0,0 @@
export { hmrUpdate } from './hmr-update';

View File

@@ -1 +0,0 @@
export { hmrUpdate } from './hmr-update';

View File

@@ -1 +0,0 @@
export declare function getResolver(platforms: string[], explicitResolve?: string[], nsPackageFilters?: string[], platformSpecificExt?: string[]): (path: string) => string;

View File

@@ -1,41 +0,0 @@
import { parse, join } from 'path';
import { statSync } from 'fs';
export function getResolver(platforms: string[], explicitResolve?: string[], nsPackageFilters?: string[], platformSpecificExt?: string[]) {
explicitResolve = explicitResolve || [];
nsPackageFilters = nsPackageFilters || ['nativescript', 'tns', 'ns', '@nativescript'];
platformSpecificExt = platformSpecificExt || ['.ts', '.js', '.scss', '.less', '.css', '.html', '.xml', '.vue', '.json'];
return function (path: string) {
const { dir, name, ext } = parse(path);
if (platformSpecificExt.indexOf(ext) === -1) {
return path;
}
for (const platform of platforms) {
const platformFileName = `${name}.${platform}${ext}`;
const platformPath = toSystemPath(join(dir, platformFileName));
try {
if (statSync(platformPath)) {
return platformPath;
}
} catch (_e) {
// continue checking the other platforms
}
}
return path;
};
}
// Convert paths from \c\some\path to c:\some\path
function toSystemPath(path: string) {
if (!process.platform.startsWith('win32')) {
return path;
}
const drive = path.match(/^\\(\w)\\(.*)$/);
return drive ? `${drive[1]}:\\${drive[2]}` : path;
}

View File

@@ -1,264 +0,0 @@
const path = require("path");
const { existsSync } = require("fs");
const { ANDROID_APP_PATH } = require("./helpers/androidProjectHelpers");
const {
getPackageJson,
isAndroid,
isIos,
} = require("./helpers/projectHelpers");
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);
const entry = getPackageJsonEntry(appDirectory);
const aotEntry = `${entry}.aot.ts`;
const aotEntryPath = path.resolve(appDirectory, aotEntry);
if (!existsSync(aotEntryPath)) {
throw new Error(`For ahead-of-time compilation you need to have an entry module ` +
`at ${aotEntryPath} that bootstraps the app with a static platform instead of dynamic one!`)
}
return aotEntry;
}
exports.getEntryModule = function (appDirectory, platform) {
verifyEntryModuleDirectory(appDirectory);
const entry = getPackageJsonEntry(appDirectory);
const tsEntryPath = path.resolve(appDirectory, `${entry}.ts`);
const jsEntryPath = path.resolve(appDirectory, `${entry}.js`);
let entryExists = existsSync(tsEntryPath) || existsSync(jsEntryPath);
if (!entryExists && platform) {
const platformTsEntryPath = path.resolve(appDirectory, `${entry}.${platform}.ts`);
const platformJsEntryPath = path.resolve(appDirectory, `${entry}.${platform}.js`);
entryExists = existsSync(platformTsEntryPath) || existsSync(platformJsEntryPath);
}
if (!entryExists) {
throw new Error(`The entry module ${entry} specified in ` +
`${appDirectory}/package.json doesn't exist!`)
}
return entry;
};
exports.getAppPath = (platform, projectDir) => {
if (isIos(platform)) {
const appName = path.basename(projectDir);
const sanitizedName = sanitize(appName);
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}`);
}
};
/**
* For backward compatibility. This method is deprecated. Do not use it anymore.
* This method also has a bug of not escaping valid regex symbols in entry path.
* For module rules use: "include" instead of "test".
*/
exports.getEntryPathRegExp = (appFullPath, entryModule) => {
const entryModuleFullPath = path.join(appFullPath, entryModule);
// Windows paths contain `\`, so we need to convert each of the `\` to `\\`, so it will be correct inside RegExp
const escapedPath = entryModuleFullPath.replace(/\\/g, "\\\\");
return new RegExp(escapedPath);
}
exports.getSourceMapFilename = (hiddenSourceMap, appFolderPath, outputPath) => {
const appFolderRelativePath = path.join(path.relative(outputPath, appFolderPath));
let sourceMapFilename = "[file].map";
if (typeof hiddenSourceMap === "string") {
sourceMapFilename = path.join(appFolderRelativePath, hiddenSourceMap, "[file].map");
} else if (typeof hiddenSourceMap === "boolean" && !!hiddenSourceMap) {
sourceMapFilename = path.join(appFolderRelativePath, "sourceMap", "[file].map");
}
return sourceMapFilename;
}
/**
* Converts an array of strings externals to an array of regular expressions.
* Input is an array of string, which we need to convert to regular expressions, so all imports for this module will be treated as externals.
* For example, in case we want nativescript-vue to be external, we will pass `["nativescript-vue"]`.
* If we pass it to webpack in this way, it will treat all `require("nativescript-vue")` as externals.
* However, you may import some file/subdir of the module, for example `require("nativescript-vue/somedir/file1")`.
* To treat this as external, we convert the strings to regular expressions, which makes webpack exclude all imports of the module.
* @param {string[]} externals Array of strings.
* @returns {RegExp[]} Array of regular expressions constructed from the input strings. In case the input is nullable, an empty array is returned.
*/
exports.getConvertedExternals = (externals) => {
const modifiedExternals = (externals || []).map((e) => {
return new RegExp(`^${e}((/.*)|$)`);
});
return modifiedExternals;
};
/**
* The `require.context` call in `bundle-config-loader` will ask the FS for files and
* the PlatformFSPlugin will return files without `.${platform}`. The SplitChunksPlugin will
* compare the `appComponents` with the files returned from the `PlatformFSPlugin` and when they
* do not match because of the platform extension, it will duplicate the custom components
* in `bundle` (activity.js - included by the `require.context` call in `bundle-config-loader`)
* and `vendor` (activity.android.js - included by `android-app-components-loader` and `SplitChunksPlugin`).
* We are post-processing the `appComponents` in order to unify the file names and avoid getting
* a build-time SBG exception for duplicate native class definition.
*/
exports.processAppComponents = (appComponents, platform) => {
for (const key in appComponents) {
appComponents[key] = appComponents[key].replace(`.${platform}`, "");
}
};
/**
* The `bundle-config-loader` needs this in order to skip the custom entries in its `require.context` call.
* If we don't skip them, custom entries like custom Android application will be included in both `application.js`
* (because its defined as an entry) and `bundle.js` (included by the `require.context` call in `bundle-config-loader`)
* causing a build-time SBG exception for duplicate native class definition.
* We are removing the extension in order to unify the file names with the `PlatformFSPlugin`.
*/
exports.getUserDefinedEntries = (entries, platform) => {
const userDefinedEntries = [];
for (const entry in entries) {
if (entry !== "bundle" && entry !== "tns_modules/@nativescript/core/inspector_modules") {
userDefinedEntries.push(entries[entry].replace(`.${platform}`, ""));
}
}
return userDefinedEntries;
};
const sanitize = name => name
.split("")
.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;
if (!entry) {
throw new Error(`${appDirectory}/package.json must contain a 'main' attribute!`);
}
return entry.replace(/\.js$/i, "");
}
function verifyEntryModuleDirectory(appDirectory) {
if (!appDirectory) {
throw new Error("Path to app directory is not specified. Unable to find entry module.");
}
if (!existsSync(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];
}
}

View File

@@ -1 +0,0 @@
export {};

View File

@@ -1,44 +0,0 @@
import { getConvertedExternals } from './index';
describe('index', () => {
describe('getConvertedExternals', () => {
it('returns empty array when nullable is passed', () => {
const actualResult = getConvertedExternals(null);
expect(actualResult).toEqual([]);
});
const testCases = [
{
input: ['nativescript-vue'],
expectedOutput: [/^nativescript-vue((\/.*)|$)/],
},
{
input: ['nativescript-vue', 'nativescript-angular'],
expectedOutput: [/^nativescript-vue((\/.*)|$)/, /^nativescript-angular((\/.*)|$)/],
},
];
testCases.forEach((testCase) => {
it('converts passed strings to regular expressions', () => {
const actualResult = getConvertedExternals(testCase.input);
expect(actualResult).toEqual(testCase.expectedOutput);
});
it(`returns regular expressions which match expected modules and their subdirs, for input ${testCase.input}`, () => {
['nativescript-vue', 'nativescript-vue/subdir', 'nativescript-vue/subdir/subdir-2'].forEach((testString) => {
const regExpsExternals = getConvertedExternals(testCase.input);
const result = regExpsExternals.some((regExp: RegExp) => !!regExp.exec(testString));
expect(result).toBe(true, `String ${testString} does not match any of the regular expressions: ${regExpsExternals.join(', ')}`);
});
});
it(`returns regular expressions which does not match expected modules and their subdirs, for input ${testCase.input}`, () => {
['nativescript-facebook', 'nativescript-facebook/nativescript-vue', 'main-plugin/subdir/nativescript-vue', 'nativescript-vue-template-compiler', 'nativescript-vue-template-compiler/subdir'].forEach((testString) => {
const regExpsExternals = getConvertedExternals(testCase.input);
const result = regExpsExternals.some((regExp: RegExp) => !!regExp.exec(testString));
expect(result).toBe(false, `String ${testString} matches some of the regular expressions: ${regExpsExternals.join(', ')}`);
});
});
});
});
});

View File

@@ -1,29 +0,0 @@
const helpers = require("./helpers/projectHelpers");
const projectFilesManager = require("./helpers/projectFilesManager");
const dependencyManager = require("./helpers/dependencyManager");
function install() {
const projectDir = helpers.getProjectDir();
const packageJson = helpers.getPackageJson(projectDir);
projectFilesManager.addProjectFiles(projectDir);
const postinstallOptions = dependencyManager.addProjectDeps(packageJson);
packageJson.devDependencies = postinstallOptions.deps;
helpers.writePackageJson(packageJson, projectDir);
dependencyManager.showHelperMessages(postinstallOptions);
}
function uninstall() {
const projectDir = helpers.getProjectDir();
projectFilesManager.removeProjectFiles(projectDir);
console.log("NativeScript Webpack removed!");
}
module.exports = {
install,
uninstall,
};

View File

@@ -1,13 +0,0 @@
{
"spec_dir": ".",
"spec_files": [
"!node_modules/**/*.spec.js",
"!demo/**/*.spec.js",
"./**/*.spec.js"
],
"helpers": [
"jasmine-config/**/*.js"
],
"stopSpecOnExpectationFailure": false,
"random": false
}

View File

@@ -1 +0,0 @@
export {};

View File

@@ -1,6 +0,0 @@
import { SpecReporter } from 'jasmine-spec-reporter';
jasmine.getEnv().clearReporters();
// @ts-ignore https://github.com/bcaudan/jasmine-spec-reporter/issues/588
jasmine.getEnv().addReporter(new SpecReporter());

View File

@@ -1,23 +0,0 @@
const { installSnapshotArtefacts } = require("../snapshot/android/project-snapshot-generator");
const { shouldSnapshot } = require("./utils");
module.exports = function (hookArgs) {
if (hookArgs && hookArgs.appFilesUpdaterOptions && !hookArgs.appFilesUpdaterOptions.bundle) {
return;
}
const env = hookArgs.prepareData.env || {};
const shouldSnapshotOptions = {
platform: hookArgs.prepareData.platform,
release: hookArgs.prepareData.release
};
if (env.snapshot &&
shouldSnapshot(shouldSnapshotOptions) &&
(!hookArgs.prepareData ||
!hookArgs.prepareData.nativePrepare ||
!hookArgs.prepareData.nativePrepare.skipNativePrepare)) {
installSnapshotArtefacts(hookArgs.prepareData.projectDir);
}
}

View File

@@ -1,33 +0,0 @@
const { shouldSnapshot, isWinOS } = require("./utils");
const semver = require("semver");
module.exports = function ($staticConfig, hookArgs) {
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 &&
hookArgs.checkForChangesOpts &&
hookArgs.checkForChangesOpts.projectChangesOptions &&
hookArgs.checkForChangesOpts.projectChangesOptions.bundle;
if (isUsingBundleWorkflow) {
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).`);
}
}
}

View File

@@ -1,41 +0,0 @@
const os = require("os");
const { dirname } = require("path");
const { existsSync, mkdirSync } = require("fs");
const { isAndroid } = require("../helpers/projectHelpers");
function shouldSnapshot(config) {
const platformSupportsSnapshot = isAndroid(config.platform);
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,
isWinOS,
warn,
ensureDirectoryExistence
};

View File

@@ -1,48 +0,0 @@
/*
MIT License http://www.opensource.org/licenses/mit-license.php
Author Tobias Koppers @sokra
*/
"use strict";
const ConcatSource = require("webpack-sources").ConcatSource;
class JsonpChunkTemplatePlugin {
apply(chunkTemplate) {
chunkTemplate.hooks.render.tap(
"JsonpChunkTemplatePlugin",
(modules, chunk) => {
const jsonpFunction = chunkTemplate.outputOptions.jsonpFunction;
const globalObject = chunkTemplate.outputOptions.globalObject;
const source = new ConcatSource();
source.add(
`(${globalObject}[${JSON.stringify(jsonpFunction)}] = ${
globalObject
}[${JSON.stringify(jsonpFunction)}] || []).push([${JSON.stringify(
chunk.ids
)},`
);
source.add(modules);
const entries = [chunk.entryModule].filter(Boolean).map(m =>
[m.id].concat(
Array.from(chunk.groupsIterable)[0]
.chunks.filter(c => c !== chunk)
.map(c => c.id)
)
);
if (entries.length > 0) {
source.add(`,${JSON.stringify(entries)}`);
}
source.add("])");
return source;
}
);
chunkTemplate.hooks.hash.tap("JsonpChunkTemplatePlugin", hash => {
hash.update("JsonpChunkTemplatePlugin");
hash.update("4");
hash.update(`${chunkTemplate.outputOptions.jsonpFunction}`);
hash.update(`${chunkTemplate.outputOptions.globalObject}`);
});
}
}
module.exports = JsonpChunkTemplatePlugin;

View File

@@ -1,32 +0,0 @@
module.exports = function () {
function webpackHotUpdateCallback(chunkId, moreModules) {
hotAddUpdateChunk(chunkId, moreModules);
if (parentHotUpdateCallback) {
parentHotUpdateCallback(chunkId, moreModules);
}
}
function hotDownloadUpdateChunk(chunkId) {
const requestPath = './' + $hotChunkFilename$;
try {
require(requestPath);
} catch (e) {
console.log("Hot download for update chunk failed.");
console.error(e);
}
}
function hotDownloadManifest() {
return new Promise(function (resolve, reject) {
const requestPath = './' + $hotMainFilename$;
try {
const update = require(requestPath);
resolve(update);
} catch (e) {
console.log("Hot download for manifest failed.");
console.error(e);
reject(e);
}
});
}
};

View File

@@ -1,324 +0,0 @@
const Template = require("webpack/lib/Template");
const SyncWaterfallHook = require("tapable").SyncWaterfallHook;
class NsJsonpMainTemplatePlugin {
apply(mainTemplate) {
const needChunkOnDemandLoadingCode = chunk => {
for (const chunkGroup of chunk.groupsIterable) {
if (chunkGroup.getNumberOfChildren() > 0) return true;
}
return false;
};
const needChunkLoadingCode = chunk => {
for (const chunkGroup of chunk.groupsIterable) {
if (chunkGroup.chunks.length > 1) return true;
if (chunkGroup.getNumberOfChildren() > 0) return true;
}
return false;
};
const needEntryDeferringCode = chunk => {
for (const chunkGroup of chunk.groupsIterable) {
if (chunkGroup.chunks.length > 1) return true;
}
return false;
};
if (!mainTemplate.hooks.jsonpScript) {
mainTemplate.hooks.jsonpScript = new SyncWaterfallHook([
"source",
"chunk",
"hash"
]);
}
mainTemplate.hooks.localVars.tap(
"JsonpMainTemplatePlugin",
(source, chunk) => {
if (needChunkLoadingCode(chunk)) {
return Template.asString([
source,
"",
"// object to store loaded and loading chunks",
"var installedChunks = {",
Template.indent(
chunk.ids.map(id => `${JSON.stringify(id)}: 0`).join(",\n")
),
"};",
"",
needEntryDeferringCode(chunk) ? "var deferredModules = [];" : ""
]);
}
return source;
}
);
mainTemplate.hooks.requireEnsure.tap(
"JsonpMainTemplatePlugin",
(source, chunk, hash) => {
const chunkFilename = mainTemplate.outputOptions.chunkFilename;
const chunkMaps = chunk.getChunkMaps();
const request = mainTemplate.getAssetPath(
JSON.stringify(`./${chunkFilename}`),
{
hash: `" + ${mainTemplate.renderCurrentHashCode(hash)} + "`,
hashWithLength: length =>
`" + ${mainTemplate.renderCurrentHashCode(hash, length)} + "`,
chunk: {
id: '" + chunkId + "',
hash: `" + ${JSON.stringify(chunkMaps.hash)}[chunkId] + "`,
hashWithLength(length) {
const shortChunkHashMap = Object.create(null);
for (const chunkId of Object.keys(chunkMaps.hash)) {
if (typeof chunkMaps.hash[chunkId] === "string")
shortChunkHashMap[chunkId] = chunkMaps.hash[chunkId].substr(
0,
length
);
}
return `" + ${JSON.stringify(shortChunkHashMap)}[chunkId] + "`;
},
name: `" + (${JSON.stringify(
chunkMaps.name
)}[chunkId]||chunkId) + "`
}
}
);
return Template.asString([
source,
"",
"// JSONP chunk loading for javascript",
"",
"var installedChunkData = installedChunks[chunkId];",
'if(installedChunkData !== 0) { // 0 means "already installed".',
Template.indent([`var chunk = require(${request})`]),
"}"
]);
}
);
mainTemplate.hooks.requireExtensions.tap(
"JsonpMainTemplatePlugin",
(source, chunk) => {
if (!needChunkOnDemandLoadingCode(chunk)) return source;
return Template.asString([
source,
"",
"// on error function for async loading",
`${
mainTemplate.requireFn
}.oe = function(err) { console.error(err); throw err; };`
]);
}
);
mainTemplate.hooks.bootstrap.tap(
"JsonpMainTemplatePlugin",
(source, chunk, hash) => {
if (needChunkLoadingCode(chunk)) {
const withDefer = needEntryDeferringCode(chunk);
return Template.asString([
source,
"",
"// install a JSONP callback for chunk loading",
"function webpackJsonpCallback(data) {",
Template.indent([
"var chunkIds = data[0];",
"var moreModules = data[1];",
withDefer ? "var executeModules = data[2];" : "",
'// add "moreModules" to the modules object,',
'// then flag all "chunkIds" as loaded and fire callback',
"var moduleId, chunkId, i = 0, resolves = [];",
"for(;i < chunkIds.length; i++) {",
Template.indent([
"chunkId = chunkIds[i];",
"if(installedChunks[chunkId]) {",
Template.indent("resolves.push(installedChunks[chunkId][0]);"),
"}",
"installedChunks[chunkId] = 0;"
]),
"}",
"for(moduleId in moreModules) {",
Template.indent([
"if(Object.prototype.hasOwnProperty.call(moreModules, moduleId)) {",
Template.indent(
mainTemplate.renderAddModule(
hash,
chunk,
"moduleId",
"moreModules[moduleId]"
)
),
"}"
]),
"}",
"if(parentJsonpFunction) parentJsonpFunction(data);",
"while(resolves.length) {",
Template.indent("resolves.shift()();"),
"}",
withDefer
? Template.asString([
"",
"// add entry modules from loaded chunk to deferred list",
"deferredModules.push.apply(deferredModules, executeModules || []);",
"",
"// run deferred modules when all chunks ready",
"return checkDeferredModules();"
])
: ""
]),
"};",
withDefer
? Template.asString([
"function checkDeferredModules() {",
Template.indent([
"var result;",
"for(var i = 0; i < deferredModules.length; i++) {",
Template.indent([
"var deferredModule = deferredModules[i];",
"var fulfilled = true;",
"for(var j = 1; j < deferredModule.length; j++) {",
Template.indent([
"var depId = deferredModule[j];",
"if(installedChunks[depId] !== 0) fulfilled = false;"
]),
"}",
"if(fulfilled) {",
Template.indent([
"deferredModules.splice(i--, 1);",
"result = " +
mainTemplate.requireFn +
"(" +
mainTemplate.requireFn +
".s = deferredModule[0]);"
]),
"}"
]),
"}",
"return result;"
]),
"}"
])
: ""
]);
}
return source;
}
);
mainTemplate.hooks.beforeStartup.tap(
"JsonpMainTemplatePlugin",
(source, chunk, hash) => {
if (needChunkLoadingCode(chunk)) {
var jsonpFunction = mainTemplate.outputOptions.jsonpFunction;
var globalObject = mainTemplate.outputOptions.globalObject;
return Template.asString([
`var jsonpArray = ${globalObject}[${JSON.stringify(
jsonpFunction
)}] = ${globalObject}[${JSON.stringify(jsonpFunction)}] || [];`,
"var oldJsonpFunction = jsonpArray.push.bind(jsonpArray);",
"jsonpArray.push = webpackJsonpCallback;",
"jsonpArray = jsonpArray.slice();",
"for(var i = 0; i < jsonpArray.length; i++) webpackJsonpCallback(jsonpArray[i]);",
"var parentJsonpFunction = oldJsonpFunction;",
"",
source
]);
}
return source;
}
);
mainTemplate.hooks.startup.tap(
"JsonpMainTemplatePlugin",
(source, chunk, hash) => {
if (needEntryDeferringCode(chunk)) {
if (chunk.hasEntryModule()) {
const entries = [chunk.entryModule].filter(Boolean).map(m =>
[m.id].concat(
Array.from(chunk.groupsIterable)[0]
.chunks.filter(c => c !== chunk)
.map(c => c.id)
)
);
return Template.asString([
"// add entry module to deferred list",
`deferredModules.push(${entries
.map(e => JSON.stringify(e))
.join(", ")});`,
"// run deferred modules when ready",
"return checkDeferredModules();"
]);
} else {
return Template.asString([
"// run deferred modules from other chunks",
"checkDeferredModules();"
]);
}
}
return source;
}
);
mainTemplate.hooks.hotBootstrap.tap(
"JsonpMainTemplatePlugin",
(source, chunk, hash) => {
const globalObject = mainTemplate.outputOptions.globalObject;
const hotUpdateChunkFilename =
mainTemplate.outputOptions.hotUpdateChunkFilename;
const hotUpdateMainFilename =
mainTemplate.outputOptions.hotUpdateMainFilename;
const crossOriginLoading =
mainTemplate.outputOptions.crossOriginLoading;
const hotUpdateFunction = mainTemplate.outputOptions.hotUpdateFunction;
const currentHotUpdateChunkFilename = mainTemplate.getAssetPath(
JSON.stringify(hotUpdateChunkFilename),
{
hash: `" + ${mainTemplate.renderCurrentHashCode(hash)} + "`,
hashWithLength: length =>
`" + ${mainTemplate.renderCurrentHashCode(hash, length)} + "`,
chunk: {
id: '" + chunkId + "'
}
}
);
const currentHotUpdateMainFilename = mainTemplate.getAssetPath(
JSON.stringify(hotUpdateMainFilename),
{
hash: `" + ${mainTemplate.renderCurrentHashCode(hash)} + "`,
hashWithLength: length =>
`" + ${mainTemplate.renderCurrentHashCode(hash, length)} + "`
}
);
const runtimeSource = Template.getFunctionContent(
require("./NsJsonpMainTemplate.runtime.js")
)
.replace(/\/\/\$semicolon/g, ";")
.replace(/\$require\$/g, mainTemplate.requireFn)
.replace(
/\$crossOriginLoading\$/g,
crossOriginLoading
? `script.crossOrigin = ${JSON.stringify(crossOriginLoading)}`
: ""
)
.replace(/\$hotMainFilename\$/g, currentHotUpdateMainFilename)
.replace(/\$hotChunkFilename\$/g, currentHotUpdateChunkFilename)
.replace(/\$hash\$/g, JSON.stringify(hash));
return `${source}
function hotDisposeChunk(chunkId) {
delete installedChunks[chunkId];
}
var parentHotUpdateCallback = ${globalObject}[${JSON.stringify(
hotUpdateFunction
)}];
${globalObject}[${JSON.stringify(hotUpdateFunction)}] = ${runtimeSource}`;
}
);
mainTemplate.hooks.hash.tap("JsonpMainTemplatePlugin", hash => {
hash.update("jsonp");
hash.update("5");
hash.update(`${mainTemplate.outputOptions.globalObject}`);
hash.update(`${mainTemplate.outputOptions.chunkFilename}`);
hash.update(`${mainTemplate.outputOptions.jsonpFunction}`);
hash.update(`${mainTemplate.outputOptions.hotUpdateFunction}`);
});
}
}
module.exports = NsJsonpMainTemplatePlugin;

View File

@@ -1,17 +0,0 @@
const NsJsonpMainTemplatePlugin = require("./NsJsonpMainTemplatePlugin");
const JsonpChunkTemplatePlugin = require("webpack/lib/web/JsonpChunkTemplatePlugin");
const JsonpHotUpdateChunkTemplatePlugin = require("webpack/lib/web/JsonpHotUpdateChunkTemplatePlugin");
class NsJsonpTemplatePlugin {
apply(compiler) {
compiler.hooks.thisCompilation.tap("NsJsonpTemplatePlugin", compilation => {
new NsJsonpMainTemplatePlugin().apply(compilation.mainTemplate);
new JsonpChunkTemplatePlugin().apply(compilation.chunkTemplate);
new JsonpHotUpdateChunkTemplatePlugin().apply(
compilation.hotUpdateChunkTemplate
);
})
}
}
module.exports = NsJsonpTemplatePlugin;

View File

@@ -1,16 +0,0 @@
module.exports = function nativescriptTarget(compiler) {
const options = this;
const webpackLib = "webpack/lib";
// Custom template plugin
const NsJsonpTemplatePlugin = require("./NsJsonpTemplatePlugin");
const FunctionModulePlugin = require(webpackLib + "/FunctionModulePlugin");
const NodeSourcePlugin = require(webpackLib + "/node/NodeSourcePlugin");
const LoaderTargetPlugin = require(webpackLib + "/LoaderTargetPlugin");
new NsJsonpTemplatePlugin(options.output).apply(compiler);
new FunctionModulePlugin(options.output).apply(compiler);
new NodeSourcePlugin(options.node).apply(compiler);
new LoaderTargetPlugin("web").apply(compiler);
}

View File

@@ -1,103 +0,0 @@
{
"name": "@nativescript/webpack",
"version": "4.1.0",
"main": "index",
"description": "Webpack plugin for NativeScript",
"homepage": "https://nativescript.org",
"bugs": "https://github.com/NativeScript/NativeScript/issues",
"contributors": [
"Hristo Deshev <hristo.deshev@telerik.com>",
"NativeScript <oss@nativescript.org>"
],
"nativescript": {
"hooks": [
{
"type": "after-prepare",
"script": "lib/after-prepare.js",
"inject": true
},
{
"type": "before-checkForChanges",
"script": "lib/before-checkForChanges.js",
"inject": true
}
]
},
"license": "Apache-2.0",
"repository": {
"type": "git",
"url": "https://github.com/NativeScript/nativescript-dev-webpack.git"
},
"scripts": {
"tsc": "tsc",
"postinstall": "node postinstall.js",
"preuninstall": "node preuninstall.js",
"postpack": "rm -rf node_modules",
"setup": "npm run clean && npm run build",
"build": "npm run tsc && npm run jasmine",
"test": "npm run build",
"jasmine": "jasmine --config=jasmine-config/jasmine.json",
"coverage": "nyc npm run test",
"changelog": "conventional-changelog -p angular -i CHANGELOG.md -s"
},
"bin": {
"install-ns-webpack": "./bin/install-ns-webpack",
"remove-ns-webpack": "./bin/remove-ns-webpack",
"update-ns-webpack": "./bin/update-ns-webpack",
"ns-bundle": "./bin/ns-bundle",
"ns-verify-bundle": "./bin/ns-verify-bundle",
"generate-android-snapshot": "./bin/generate-android-snapshot"
},
"dependencies": {
"@angular-devkit/core": "~10.0.0",
"@nativescript/hook": "~2.0.0",
"clean-webpack-plugin": "~3.0.0",
"copy-webpack-plugin": "4.6.0",
"css": "~3.0.0",
"css-loader": "~4.2.0",
"escape-string-regexp": "~4.0.0",
"fork-ts-checker-webpack-plugin": "~5.0.0",
"global-modules-path": "~2.3.0",
"loader-utils": "~2.0.0",
"minimatch": "~3.0.4",
"nativescript-worker-loader": "~0.12.0",
"properties-reader": "~2.0.0",
"proxy-lib": "0.4.0",
"raw-loader": "~4.0.0",
"resolve-url-loader": "~3.1.0",
"sass-loader": "~9.0.0",
"sax": "^1.2.4",
"schema-utils": "~2.7.0",
"semver": "^7.3.0",
"shelljs": "~0.8.4",
"tapable": "~1.1.3",
"terser": "~5.0.0",
"terser-webpack-plugin": "~3.0.6",
"ts-loader": "^8.0.2",
"webpack": "~4.44.1",
"webpack-bundle-analyzer": "~3.8.0",
"webpack-cli": "~3.3.12",
"webpack-sources": "~1.4.3"
},
"devDependencies": {
"@angular/compiler": "~10.0.0",
"@angular/compiler-cli": "~10.0.0",
"@istanbuljs/nyc-config-typescript": "^1.0.0",
"@ngtools/webpack": "~10.0.0",
"@types/css": "~0.0.31",
"@types/jasmine": "3.6.6",
"@types/loader-utils": "^2.0.0",
"@types/node": "~14.0.0",
"@types/proxyquire": "~1.3.28",
"@types/sax": "^1.2.0",
"@types/semver": "^7.3.0",
"@types/webpack": "^4.41.21",
"conventional-changelog-cli": "~2.0.34",
"jasmine": "3.6.4",
"jasmine-spec-reporter": "6.0.0",
"nyc": "^15.1.0",
"proxyquire": "~2.1.0",
"source-map-support": "^0.5.13",
"typescript": "4.5.4"
}
}

View File

@@ -1,14 +0,0 @@
const { GenerateNativeScriptEntryPointsPlugin } = require("./GenerateNativeScriptEntryPointsPlugin");
// backwards compatibility for <= 0.22 configs
exports.GenerateBundleStarterPlugin = (function () {
function GenerateBundleStarterPlugin() {
this.entryPointsPlugin = new GenerateNativeScriptEntryPointsPlugin("bundle");
};
GenerateBundleStarterPlugin.prototype.apply = function (compiler) {
this.entryPointsPlugin.apply(compiler);
}
return GenerateBundleStarterPlugin;
})();

View File

@@ -1,111 +0,0 @@
const { convertToUnixPath } = require("../lib/utils");
const { RawSource, ConcatSource } = require("webpack-sources");
const { getPackageJson } = require("../helpers/projectHelpers");
const { SNAPSHOT_ENTRY_NAME } = require("./NativeScriptSnapshotPlugin");
const path = require("path");
exports.GenerateNativeScriptEntryPointsPlugin = (function () {
const GenerationFailedError = "Unable to generate entry files.";
function GenerateNativeScriptEntryPointsPlugin(appEntryName) {
this.appEntryName = appEntryName;
this.files = {};
};
GenerateNativeScriptEntryPointsPlugin.prototype.apply = function (compiler) {
this.webpackContext = compiler.options.context;
compiler.hooks.emit.tapAsync("GenerateNativeScriptEntryPointsPlugin", (compilation, cb) => {
compilation.entrypoints.forEach(entryPoint => {
this.generateEntryFile(compilation, entryPoint);
});
this.addAsset(compilation, "package.json", this.generatePackageJson());
this.generateTnsJavaClasses(compilation);
cb();
});
}
GenerateNativeScriptEntryPointsPlugin.prototype.generateTnsJavaClasses = function (compilation) {
const path = compilation.compiler.outputPath;
const isAndroid = path.indexOf("android") > -1;
if (isAndroid && !compilation.assets["tns-java-classes.js"]) {
this.addAsset(compilation, "tns-java-classes.js", ""); 0
}
}
GenerateNativeScriptEntryPointsPlugin.prototype.generatePackageJson = function () {
const packageJson = getPackageJson(this.webpackContext);
packageJson.main = this.appEntryName;
return JSON.stringify(packageJson, null, 4);
}
GenerateNativeScriptEntryPointsPlugin.prototype.generateEntryFile = function (compilation, entryPoint) {
const entryPointName = entryPoint.options.name;
let entryChunk;
if (entryPointName === SNAPSHOT_ENTRY_NAME) {
// Do not require the snapshot entry dependencies as the snapshot will fail.
return;
}
const requiredFiles = [];
entryPoint.chunks.forEach(chunk => {
if (chunk.name === entryPointName) {
entryChunk = chunk;
} else {
chunk.files.forEach(fileName => {
if (!this.isHMRFile(fileName) && this.isSourceFile(fileName)) {
requiredFiles.push(fileName);
}
});
}
});
if (!entryChunk) {
throw new Error(`${GenerationFailedError} Entry chunk not found for entry "${entryPointName}".`);
}
entryChunk.files.forEach(filePath => {
if (!compilation.assets[filePath]) {
throw new Error(`${GenerationFailedError} File "${filePath}" not found for entry "${entryPointName}".`);
}
if (!this.isHMRFile(filePath) && this.isSourceFile(filePath)) {
const currFileDirRelativePath = path.dirname(filePath);
const pathToRootFromCurrFile = path.relative(currFileDirRelativePath, ".");
const requireDeps = requiredFiles.map(depPath => {
const depRelativePath = path.join(pathToRootFromCurrFile, depPath);
const depRelativePathUnix = convertToUnixPath(depRelativePath);
return `require("./${depRelativePathUnix}");`;
}).join("");
const currentEntryFileContent = compilation.assets[filePath].source();
if(compilation.assets[filePath] instanceof ConcatSource) {
compilation.assets[filePath].children.unshift(`${requireDeps}`);
} else {
compilation.assets[filePath] = new RawSource(`${requireDeps}${currentEntryFileContent}`);
}
}
});
}
GenerateNativeScriptEntryPointsPlugin.prototype.addAsset = function (compilation, name, content) {
if (this.files[name] !== content) {
this.files[name] = content;
compilation.assets[name] = new RawSource(content);
}
}
GenerateNativeScriptEntryPointsPlugin.prototype.isHMRFile = function (fileName) {
return fileName.indexOf("hot-update") > -1;
}
GenerateNativeScriptEntryPointsPlugin.prototype.isSourceFile = function (fileName) {
return fileName.endsWith(".js");
}
return GenerateNativeScriptEntryPointsPlugin;
})();

View File

@@ -1 +0,0 @@
export declare function getAngularCompilerPlugin(platform: string): any;

View File

@@ -1,32 +0,0 @@
import { parse, join } from 'path';
import { AngularCompilerPlugin } from '@ngtools/webpack';
export function getAngularCompilerPlugin(platform: string): any {
class NativeScriptAngularCompilerPlugin extends AngularCompilerPlugin {
// This is the bridge between the @ngtols/webpack loader and the AngularCompilerPlugin plugin itself:
// https://github.com/angular/angular-cli/blob/bf52b26219ffc16bed2dd55716e21773b415fd2a/packages/ngtools/webpack/src/loader.ts#L49
// The problem is that the loader does not call the `hostReplacementPaths` method when asking for the compiledFile.
// By overriding this method, we work around this issue and support platform specific files from the loader
// that are not compiled by the AngularCompilerPlugin plugin. e.g. main.ts is the webpack entry point and
// it's loaded by the @ngtools/webpack loader but its not compiled by the plugin because the TypeScript Compiler
// knowns only about main.android.ts and main.ios.ts (main.ts is not imported/required anywhere in the app).
getCompiledFile(file) {
try {
if (platform) {
const parsed = parse(file);
const platformFile = join(parsed.dir, `${parsed.name}.${platform}${parsed.ext}`);
const result = super.getCompiledFile(platformFile);
// In case there's some issue with the generation, getCompilePath may fail or
// produce a result with empty outputText and errorDependencies array populated with files.
if (result && result.outputText) {
return result;
}
}
} catch (e) {}
return super.getCompiledFile(file);
}
}
return NativeScriptAngularCompilerPlugin;
}

View File

@@ -1,173 +0,0 @@
const { relative, resolve, join } = require("path");
const { closeSync, openSync, writeFileSync } = require("fs");
const validateOptions = require("schema-utils");
const ProjectSnapshotGenerator = require("../../snapshot/android/project-snapshot-generator");
const { getPackageJson } = require("../../helpers/projectHelpers");
const {
ANDROID_PROJECT_DIR,
ANDROID_APP_PATH,
} = require("../../helpers/androidProjectHelpers");
const { ensureDirectoryExistence } = require("../../lib/utils");
const schema = require("./options.json");
const SNAPSHOT_ENTRY_NAME = "snapshot-entry";
const SNAPSHOT_ENTRY_MODULE = `${SNAPSHOT_ENTRY_NAME}.js`;
exports.SNAPSHOT_ENTRY_NAME = SNAPSHOT_ENTRY_NAME;
exports.NativeScriptSnapshotPlugin = (function () {
function NativeScriptSnapshotPlugin(options) {
NativeScriptSnapshotPlugin.validateSchema(options);
ProjectSnapshotGenerator.call(this, options);
const { webpackConfig } = this.options;
NativeScriptSnapshotPlugin.removeLibraryTarget(webpackConfig);
const { entry } = webpackConfig;
if (typeof entry === "string" || Array.isArray(entry)) {
webpackConfig.entry = { bundle: entry };
}
NativeScriptSnapshotPlugin.ensureSnapshotModuleEntry(this.options);
}
NativeScriptSnapshotPlugin.removeLibraryTarget = function (webpackConfig) {
const { output } = webpackConfig;
if (output) {
output.libraryTarget = undefined;
}
}
NativeScriptSnapshotPlugin.ensureSnapshotModuleEntry = function (options) {
const { webpackConfig, requireModules, chunks, includeApplicationCss } = options;
const internalRequireModules = this.getInternalRequireModules(webpackConfig.context);
const snapshotEntryPath = join(ANDROID_PROJECT_DIR, SNAPSHOT_ENTRY_MODULE);
let snapshotEntryContent = "";
if (includeApplicationCss) {
snapshotEntryContent += `
require("${
options.angular ?
'@nativescript/webpack/helpers/load-application-css-angular' :
'@nativescript/webpack/helpers/load-application-css-regular'
}")();
`;
}
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
webpackConfig.entry[SNAPSHOT_ENTRY_NAME] = relative(webpackConfig.context, snapshotEntryPath);
// prepend the module to the script that will be snapshotted
chunks.unshift(SNAPSHOT_ENTRY_NAME);
// 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"]) || [];
}
NativeScriptSnapshotPlugin.validateSchema = function (options) {
if (!options.chunk && !options.chunks) {
const error = NativeScriptSnapshotPlugin.extendError({ message: `No chunks specified!` });
throw error;
}
try {
validateOptions(schema, options, "NativeScriptSnapshotPlugin");
if (options.chunk) {
options.chunks = options.chunks || [];
options.chunks.push(options.chunk);
}
} catch (error) {
throw new Error(error.message);
}
}
NativeScriptSnapshotPlugin.prototype = Object.create(ProjectSnapshotGenerator.prototype);
NativeScriptSnapshotPlugin.prototype.constructor = NativeScriptSnapshotPlugin;
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,
ANDROID_APP_PATH,
"_embedded_script_.js"
);
console.log(`\n Snapshotting bundle from ${inputFiles}`);
return ProjectSnapshotGenerator.prototype.generate.call(this, {
inputFiles,
preprocessedInputFile,
targetArchs: options.targetArchs,
useLibs: options.useLibs,
androidNdkPath: options.androidNdkPath,
v8Version: options.v8Version,
snapshotInDocker: options.snapshotInDocker,
skipSnapshotTools: options.skipSnapshotTools
}).then(() => {
// Make the original files empty
inputFiles.forEach(inputFile =>
closeSync(openSync(inputFile, "w")) // truncates the input file content
);
});
}
NativeScriptSnapshotPlugin.prototype.apply = function (compiler) {
const options = this.options;
compiler.hooks.afterEmit.tapAsync("NativeScriptSnapshotPlugin", function (compilation, callback) {
const chunksToSnapshot = options.chunks
.map(name => ({ name, chunk: compilation.chunks.find(chunk => chunk.name === name) }));
const unexistingChunks = chunksToSnapshot.filter(pair => !pair.chunk);
if (unexistingChunks.length) {
const message = `The following chunks does not exist: ` + unexistingChunks.map(pair => pair.name).join(", ");
const error = NativeScriptSnapshotPlugin.extendError({ message });
compilation.errors.push(error);
return callback();
}
this.generate(chunksToSnapshot.map(pair => pair.chunk))
.then(() => {
console.log("Successfully generated snapshots!");
return callback();
})
.catch((error) => {
const extendedError = NativeScriptSnapshotPlugin.extendError({ originalError: error });
compilation.errors.push(extendedError);
return callback();
});
}.bind(this));
}
NativeScriptSnapshotPlugin.extendError = function ({ originalError, message } = {}) {
const header = `NativeScriptSnapshot. Snapshot generation failed!\n`;
if (originalError) {
originalError.message = `${header}${originalError.message}`;
return originalError;
}
const newMessage = message ? `${header}${message}` : header;
return new Error(newMessage);
};
return NativeScriptSnapshotPlugin;
})();

View File

@@ -1,66 +0,0 @@
{
"type": "object",
"properties": {
"androidNdkPath": {
"type": "string"
},
"angular": {
"type": "boolean",
"default": false
},
"chunk": {
"type": "string"
},
"chunks": {
"type": "array",
"items": {
"type": "string"
}
},
"projectRoot": {
"type": "string"
},
"webpackConfig": {
"type": "object"
},
"targetArchs": {
"type": "array",
"items": {
"type": "string",
"enum": [
"arm",
"arm64",
"ia32",
"ia64"
]
}
},
"useLibs": {
"type": "boolean",
"default": false
},
"snapshotInDocker": {
"type": "boolean"
},
"skipSnapshotTools": {
"type": "boolean",
"default": false
},
"v8Version": {
"type": "string"
},
"requireModules": {
"type": "array",
"default": []
},
"includeApplicationCss": {
"type": "boolean",
"default": true
}
},
"required": [
"projectRoot",
"webpackConfig"
],
"additionalProperties": false
}

View File

@@ -1,33 +0,0 @@
export interface PlatformFSPluginOptions {
/**
* The target platform.
*/
platform?: string;
/**
* A list of all platforms. By default it is `["ios", "android", "desktop"]`.
*/
platforms?: string[];
/**
* An array of minimatch expressions used to filter nodes from the file system.
*/
ignore?: string[];
}
export declare class PlatformFSPlugin {
protected readonly platform: string;
protected readonly platforms: ReadonlyArray<string>;
protected readonly ignore: ReadonlyArray<string>;
protected context: string;
constructor({ platform, platforms, ignore }: PlatformFSPluginOptions);
apply(compiler: any): void;
}
export interface MapFileSystemArgs {
/**
* This is the underlying webpack compiler.inputFileSystem, its interface is similar to Node's fs.
*/
readonly context: string;
readonly platform: string;
readonly platforms: ReadonlyArray<string>;
readonly ignore: ReadonlyArray<string>;
readonly compiler: any;
}
export declare function mapFileSystem(args: MapFileSystemArgs): any;

View File

@@ -1,241 +0,0 @@
import { parse as parseFile, join, basename, relative } from 'path';
import * as minimatch from 'minimatch';
export interface PlatformFSPluginOptions {
/**
* The target platform.
*/
platform?: string;
/**
* A list of all platforms. By default it is `["ios", "android", "desktop"]`.
*/
platforms?: string[];
/**
* An array of minimatch expressions used to filter nodes from the file system.
*/
ignore?: string[];
}
const internalPlatforms = ['ios', 'android'];
export class PlatformFSPlugin {
protected readonly platform: string;
protected readonly platforms: ReadonlyArray<string>;
protected readonly ignore: ReadonlyArray<string>;
protected context: string;
constructor({ platform, platforms, ignore }: PlatformFSPluginOptions) {
this.platform = platform || '';
this.platforms = platforms || internalPlatforms;
this.ignore = ignore || [];
}
public apply(compiler) {
this.context = compiler.context;
compiler.inputFileSystem = mapFileSystem({
platform: this.platform,
platforms: this.platforms,
ignore: this.ignore,
context: this.context,
compiler,
});
}
}
export interface MapFileSystemArgs {
/**
* This is the underlying webpack compiler.inputFileSystem, its interface is similar to Node's fs.
*/
readonly context: string;
readonly platform: string;
readonly platforms: ReadonlyArray<string>;
readonly ignore: ReadonlyArray<string>;
readonly compiler: any;
}
export function mapFileSystem(args: MapFileSystemArgs): any {
let { platform, platforms, ignore, context, compiler } = args;
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), 0, []);
});
const isIgnored = (file) => minimatchFileFilters.some((filter) => filter(file));
const alienPlatforms = platforms.filter((p) => p !== platform);
const alienPlatformFilters = alienPlatforms.map((platform) => `.${platform}.`).map((contains) => (baseFileName) => baseFileName.indexOf(contains) !== -1);
const isNotAlienPlatformFile = (file) => !alienPlatformFilters.some((filter) => filter(basename(file)));
const currentPlatformExt = `.${platform}`;
const trimPlatformSuffix = (file) => {
const { dir, name, ext } = parseFile(file);
if (name.endsWith(currentPlatformExt)) {
return join(dir, name.substr(0, name.length - currentPlatformExt.length) + ext);
}
return file;
};
const isNotIgnored = (file) => !isIgnored(file);
const mappedFS: any = {
get _statStorage() {
return fs._statStorage;
},
get _readFileStorage() {
return fs._readFileStorage;
},
get _readdirStorage() {
return fs._readdirStorage;
},
};
['readFile', 'provide', 'stat', 'readJson', 'readlink'].forEach(mapPath);
['readdir'].forEach(filterResultingFiles);
function platformSpecificFile(file: string): string {
const { dir, name, ext } = parseFile(file);
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;
}
function listWithPlatformSpecificFiles(files: string[]): string[] {
const mappedFiles = [];
files.forEach((file) => {
mappedFiles.push(file);
mappedFiles.push(platformSpecificFile(file));
});
return mappedFiles;
}
/**
* Maps and filters the input files.
* Expects array with absolute paths.
* Returns array with absolute paths.
*/
function filterIgnoredFilesAlienFilesAndMap(files: string[]): string[] {
const mappedFiles = files.filter(isNotIgnored).filter(isNotAlienPlatformFile).map(trimPlatformSuffix);
const uniqueMappedFiles = Array.from(new Set(mappedFiles));
return uniqueMappedFiles;
}
const platformSuffix = '.' + platform + '.';
const baseWatch = compiler.watchFileSystem.watch;
compiler.watchFileSystem.watch = function (files, dirs, missing, startTime, watchOptions, callback: (err, filesModified, contextModified, missingModified, fileTimestamps, contextTimestamps) => void, callbackUndelayed: (filename, timestamp) => void) {
const mappedFiles = listWithPlatformSpecificFiles(files);
const newCallback = function (err, filesModified, contextModified, missingModified, fileTimestamps, contextTimestamps) {
const mappedFilesModified = filterIgnoredFilesAlienFilesAndMap(filesModified);
const mappedTimestamps = new Map();
const fileTimestampsAsArray = Array.from(fileTimestamps);
for (const entry of fileTimestampsAsArray) {
const file = entry[0];
const timestamp = entry[1];
mappedTimestamps.set(file, timestamp);
const platformSuffixIndex = file.lastIndexOf(platformSuffix);
if (platformSuffixIndex != -1) {
// file name without platform suffix
const mappedFile = file.substr(0, platformSuffixIndex) + file.substr(platformSuffixIndex + platformSuffix.length - 1);
if (!(mappedFile in fileTimestamps)) {
mappedTimestamps.set(mappedFile, timestamp);
}
}
}
callback.call(this, err, mappedFilesModified, contextModified, missingModified, mappedTimestamps, contextTimestamps);
};
const newCallbackUndelayed = function (filename, timestamp) {
compiler.watchFileSystem.inputFileSystem.purge(filename);
callbackUndelayed(filename, timestamp);
};
baseWatch.apply(compiler.watchFileSystem.watch, [mappedFiles, dirs, missing, startTime, watchOptions, newCallback, newCallbackUndelayed]);
};
/**
* For FS functions that get as first argument a file path,
* this will map it to a platform specific file if such file exists or fallback to the default.
* Also the last argument must be a function that handles results such as (err, files[]),
* it will invoke err for files that are ignored.
*/
function mapPath(fName) {
const base = fs[fName];
mappedFS[fName] = function () {
const args = arguments;
const originalFilePath = args[0];
const callback = args[args.length - 1];
if (isIgnored(originalFilePath)) {
callback(new Error('File ' + originalFilePath + ' is ignored!'));
return;
}
const platformFilePath = platformSpecificFile(originalFilePath);
fs.stat(platformFilePath, (err, stat) => {
if (!err && stat && stat.isFile()) {
args[0] = platformFilePath;
}
base.apply(fs, args);
});
};
}
/**
* For FS functions that get as a last argument a function,
* that handles results such as (err, files[]),
* will filter and map the returned files[].
*/
function filterResultingFiles(name) {
const base = fs[name];
mappedFS[name] = function () {
const dir = arguments[0];
const callback = arguments[arguments.length - 1];
if (isIgnored(dir)) {
// Return empty file list for filtered directories.
callback(null, []);
return;
}
arguments[arguments.length - 1] = function (err, files: string[]) {
if (err) {
callback(err);
} else {
// Create absolute paths for "ignored" testing, map platforms, and return back the base name.
const absoluteFilePaths = files.map((file) => join(dir, file));
const resultAbsolute = filterIgnoredFilesAlienFilesAndMap(absoluteFilePaths);
const resultFileNames = resultAbsolute.map((f) => basename(f));
callback(null, resultFileNames);
}
};
base.apply(fs, arguments);
};
}
return mappedFS;
}

View File

@@ -1,35 +0,0 @@
const parseFile = require("path").parse;
function PlatformSuffixPlugin(platform) {
this.platform = platform;
}
exports.PlatformSuffixPlugin = PlatformSuffixPlugin;
PlatformSuffixPlugin.prototype.apply = function(resolver) {
var platform = this.platform;
resolver.plugin("file", function(request, callback) {
const fs = this.fileSystem;
const file = this.join(request.path, request.request);
const query = request.query;
const pFile = parseFile(file);
const platformFile = this.join(pFile.dir, pFile.name + ("." + platform) + pFile.ext);
fs.stat(platformFile, (err, stat) => {
if (!err && stat && stat.isFile()) {
const err = undefined;
const path = platformFile;
callback(err, { file: true, path, query });
} else {
fs.stat(file, (err, stat) => {
if (!err && stat && stat.isFile()) {
const err = undefined;
const path = file;
callback(err, { file: true, path, query });
} else {
callback();
}
});
}
});
});
}

View File

@@ -1,13 +0,0 @@
export declare enum messages {
compilationComplete = "Webpack compilation complete.",
startWatching = "Webpack compilation complete. Watching for file changes.",
changeDetected = "File change detected. Starting incremental webpack compilation..."
}
/**
* This little plugin will report the webpack state through the console.
* So the {N} CLI can get some idea when compilation completes.
*/
export declare class WatchStateLoggerPlugin {
isRunningWatching: boolean;
apply(compiler: any): void;
}

View File

@@ -1,57 +0,0 @@
export enum messages {
compilationComplete = 'Webpack compilation complete.',
startWatching = 'Webpack compilation complete. Watching for file changes.',
changeDetected = 'File change detected. Starting incremental webpack compilation...',
}
/**
* This little plugin will report the webpack state through the console.
* So the {N} CLI can get some idea when compilation completes.
*/
export class WatchStateLoggerPlugin {
isRunningWatching: boolean;
apply(compiler) {
const plugin = this;
compiler.hooks.watchRun.tapAsync('WatchStateLoggerPlugin', function (compiler, callback) {
plugin.isRunningWatching = true;
if (plugin.isRunningWatching) {
console.log(messages.changeDetected);
}
process.send && process.send(messages.changeDetected, (error) => null);
callback();
});
compiler.hooks.afterEmit.tapAsync('WatchStateLoggerPlugin', function (compilation, callback) {
callback();
if (plugin.isRunningWatching) {
console.log(messages.startWatching);
} else {
console.log(messages.compilationComplete);
}
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, hash: compilation.hash }, (error) => null);
});
}
}
function getChunkFiles(compilation) {
const chunkFiles = [];
try {
compilation.chunks.forEach((chunk) => {
chunk.files.forEach((file) => {
if (file.indexOf('hot-update') === -1) {
chunkFiles.push(file);
}
});
});
} catch (e) {
console.log('Warning: Unable to find chunk files.');
}
return chunkFiles;
}

View File

@@ -1,8 +0,0 @@
module.exports = Object.assign({},
require("./GenerateBundleStarterPlugin"),
require("./GenerateNativeScriptEntryPointsPlugin"),
require("./NativeScriptSnapshotPlugin"),
require("./PlatformSuffixPlugin"),
require("./PlatformFSPlugin"),
require("./WatchStateLoggerPlugin")
);

View File

@@ -1,55 +0,0 @@
"use strict";
const hook = require("@nativescript/hook")(__dirname);
const { compareProjectFiles } = require("./helpers/projectFilesManager");
const { getProjectDir } = require("./helpers/projectHelpers");
const path = require("path");
const fs = require("fs");
const projectDir = getProjectDir();
// This method is introduced as in version 1.0.0 of @nativescript/webpack (compatible and required for NativeScript 6.0.0)
// we have changed a lot of hooks and old ones are incompatible. This should be automatically handled with preuninstall script of the old version.
// However, old versions of nativescript-dev-webpack do not have such logic, so remove them manually on postinstall of the current version.
// This logic can be removed later, once most of the projects are migrated to 1.0.0 of the package or later.
// These new versions have preuninstall script that will automatically handle this case.
function removeOldHooks() {
const oldHooks = [
"before-prepareJSApp",
"before-cleanApp",
"before-watch",
"after-watch",
"before-watchPatterns",
"before-shouldPrepare",
"after-prepare",
"before-preview-sync"
];
const hooksDir = path.join(projectDir, "hooks");
const pkgName = require("./package.json").name;
const filename = `${pkgName}.js`;
oldHooks.forEach(hookName => {
const hookPath = path.join(hooksDir, hookName, filename);
try {
if (fs.existsSync(hookPath)) {
fs.unlinkSync(hookPath);
}
} catch (err) {
console.warn(`${pkgName} postinstall task: unable to delete hook ${hookPath}. Error is: ${err}`);
}
});
}
if (projectDir) {
compareProjectFiles(projectDir);
removeOldHooks();
hook.postinstall();
const installer = require("./installer");
installer.install();
} else {
// We are installing dev dependencies for the @nativescript/webpack plugin.
console.log("Skipping postinstall artifacts! We assumed the @nativescript/webpack is installing devDependencies");
}

View File

@@ -1,11 +0,0 @@
"use strict";
const hook = require("@nativescript/hook")(__dirname);
const { getProjectDir } = require("./helpers/projectHelpers");
const projectDir = getProjectDir();
if (projectDir) {
hook.preuninstall();
}

View File

@@ -1,31 +0,0 @@
{
"$schema": "../../node_modules/nx/schemas/project-schema.json",
"sourceRoot": "packages/webpack",
"projectType": "library",
"generators": {},
"targets": {
"lint": {
"executor": "@nrwl/linter:eslint",
"options": {
"lintFilePatterns": []
}
},
"test": {
"executor": "@nrwl/workspace:run-commands",
"options": {
"commands": ["npm run tsc", "npm run jasmine"],
"cwd": "packages/webpack",
"parallel": false
}
},
"build": {
"executor": "@nrwl/workspace:run-commands",
"outputs": ["dist/packages"],
"options": {
"commands": ["npm run setup", "mkdir -p ../../dist/packages", "mv \"$(npm pack | tail -n 1)\" ../../dist/packages/nativescript-webpack.tgz"],
"cwd": "packages/webpack",
"parallel": false
}
}
}
}

View File

@@ -1,74 +0,0 @@
module.exports = function parseProjectSnapshotGeneratorArgs() {
var result = parseJsonFromProcessArgs();
if (result.targetArchs) {
result.targetArchs = parseStringArray(result.targetArchs);
}
if (result.useLibs !== undefined) {
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);
}
return result;
}
function parseJsonFromProcessArgs() {
var args = process.argv.slice(2);
var result = {};
var currentKey = "";
var currentValue = "";
args.forEach(function (value, index, array) {
if (value.startsWith("--")) { // if is key
addKeyAndValueToResult(currentKey, currentValue, result);
currentKey = value.slice(2);
currentValue = null;
}
else { // if is first value
currentValue = (currentValue === null) ? value : currentValue + " " + value;
}
if (index == array.length - 1) { // if is the last one
addKeyAndValueToResult(currentKey, currentValue, result);
}
});
return result;
}
function addKeyAndValueToResult(key, value, result) {
if (!key)
return;
var jsValue = (value === null) ? null : value.toString();
var keyPath = key.split(".");
var parentObject = result;
for (var i = 0; i < keyPath.length; i++) {
if (i == keyPath.length - 1) {
parentObject[keyPath[i]] = jsValue;
}
else {
parentObject[keyPath[i]] = parentObject[keyPath[i]] || {};
parentObject = parentObject[keyPath[i]];
}
}
}
function parseBool(value) {
return (value === null || value === "true") ? true : false;
}
function parseStringArray(str) {
return str.toString().split(",").map(value => value.trim());
}

View File

@@ -1,197 +0,0 @@
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
} = require("./utils");
const {
ANDROID_PROJECT_DIR,
ANDROID_APP_PATH,
ANDROID_CONFIGURATIONS_PATH,
getAndroidRuntimeVersion,
getAndroidV8Version,
getRuntimeNdkRevision,
getMksnapshotParams
} = require("../../helpers/androidProjectHelpers");
// 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 resolveRelativePath = (path) => {
if (path)
return isAbsolute(path) ? path : resolve(process.cwd(), path);
return null;
};
function ProjectSnapshotGenerator(options) {
this.options = options = options || {};
options.projectRoot = resolveRelativePath(options.projectRoot) || process.cwd();
console.log("Project root: " + options.projectRoot);
console.log("Snapshots build directory: " + this.getBuildPath());
this.validateAndroidRuntimeVersion();
}
module.exports = ProjectSnapshotGenerator;
ProjectSnapshotGenerator.calculateBuildPath = function (projectRoot) {
return join(
ProjectSnapshotGenerator.calculateProjectPath(projectRoot),
"snapshot-build",
"build"
);
}
ProjectSnapshotGenerator.prototype.getBuildPath = function () {
return ProjectSnapshotGenerator.calculateBuildPath(this.options.projectRoot);
}
ProjectSnapshotGenerator.calculateProjectPath = function (projectRoot) {
return join(projectRoot, ANDROID_PROJECT_DIR);
}
ProjectSnapshotGenerator.calculateConfigurationsPath = function (projectRoot) {
return join(projectRoot, ANDROID_CONFIGURATIONS_PATH);
}
ProjectSnapshotGenerator.calculateAppPath = function (projectRoot) {
return join(projectRoot, ANDROID_APP_PATH);
}
ProjectSnapshotGenerator.prototype.getProjectPath = function () {
return ProjectSnapshotGenerator.calculateProjectPath(this.options.projectRoot);
}
ProjectSnapshotGenerator.cleanSnapshotArtefacts = function (projectRoot) {
const platformPath = ProjectSnapshotGenerator.calculateProjectPath(projectRoot);
// Remove blob files from prepared folder
shelljs.rm("-rf", join(platformPath, "src/main/assets/snapshots"));
// Remove prepared include.gradle configurations
const configurationsPath = ProjectSnapshotGenerator.calculateConfigurationsPath(projectRoot);
shelljs.rm("-rf", join(configurationsPath, SnapshotGenerator.SNAPSHOT_PACKAGE_NANE));
}
ProjectSnapshotGenerator.installSnapshotArtefacts = function (projectRoot) {
const buildPath = ProjectSnapshotGenerator.calculateBuildPath(projectRoot);
const platformPath = ProjectSnapshotGenerator.calculateProjectPath(projectRoot);
const appPath = ProjectSnapshotGenerator.calculateAppPath(projectRoot);
const configurationsPath = ProjectSnapshotGenerator.calculateConfigurationsPath(projectRoot);
const configDestinationPath = join(configurationsPath, SnapshotGenerator.SNAPSHOT_PACKAGE_NANE);
// Remove build folder to make sure that the apk will be fully rebuild
shelljs.rm("-rf", join(platformPath, "build"));
// Copy include.gradle to the specified destination in the platforms folder
shelljs.mkdir("-p", configDestinationPath);
shelljs.cp(join(buildPath, "include.gradle"), join(configDestinationPath, "include.gradle"));
if (shelljs.test("-e", join(buildPath, "ndk-build/libs"))) {
// useLibs = true
const libsDestinationPath = join(platformPath, "src", SnapshotGenerator.SNAPSHOT_PACKAGE_NANE, "jniLibs");
// Copy the libs to the specified destination in the platforms folder
shelljs.mkdir("-p", libsDestinationPath);
shelljs.cp("-R", join(buildPath, "ndk-build/libs") + sep, libsDestinationPath);
} else {
// useLibs = false
const blobsSrcPath = join(buildPath, "snapshots/blobs");
const blobsDestinationPath = resolve(appPath, "../snapshots");
const appPackageJsonPath = join(appPath, "package.json");
// Copy the blobs in the prepared app folder
shelljs.cp("-R", blobsSrcPath + sep, resolve(appPath, "../snapshots"));
// Update the package.json file
const appPackageJson = shelljs.test("-e", appPackageJsonPath) ? JSON.parse(readFileSync(appPackageJsonPath, 'utf8')) : {};
appPackageJson["android"] = appPackageJson["android"] || {};
appPackageJson["android"]["heapSnapshotBlob"] = "../snapshots";
writeFileSync(appPackageJsonPath, JSON.stringify(appPackageJson, null, 2));
}
}
ProjectSnapshotGenerator.prototype.validateAndroidRuntimeVersion = function () {
const currentRuntimeVersion = getAndroidRuntimeVersion(this.options.projectRoot);
if (!currentRuntimeVersion || !this.getProjectPath()) {
throw new Error("In order to generate a V8 snapshot you must have the \"android\" platform installed - to do so please run \"tns platform add android\".");
}
if (!VALID_ANDROID_RUNTIME_TAGS.includes(currentRuntimeVersion) &&
!semver.gte(currentRuntimeVersion, MIN_ANDROID_RUNTIME_VERSION)) {
throw new Error("In order to support heap snapshots, you must have at least tns-android@" + MIN_ANDROID_RUNTIME_VERSION +
" installed. Current Android Runtime version is: " + currentRuntimeVersion + ".");
}
}
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: ");
console.log(JSON.stringify(generationOptions, null, '\t'));
// Clean build folder
shelljs.rm("-rf", this.getBuildPath());
shelljs.mkdir("-p", this.getBuildPath());
const snapshotToolsPath = resolveRelativePath(generationOptions.snapshotToolsPath) || CONSTANTS.SNAPSHOT_TMP_DIR;
console.log("Snapshot tools path: " + snapshotToolsPath);
// Generate snapshots
const generator = new SnapshotGenerator({ buildPath: this.getBuildPath() });
const noV8VersionFoundMessage = `Cannot find suitable v8 version!`;
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);
}
// 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 || 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).");
}
});;
}

View File

@@ -1,12 +0,0 @@
/*!**********************************************************!*\
!*** ../android-snapshot-bundle-ending.js ***!
\**********************************************************/
// Set the __snapshot flag to false
Object.defineProperty(global, "__snapshot", {
value: false,
writable: false,
configurable: false,
enumerable: false
});

View File

@@ -1,34 +0,0 @@
/*!**********************************************************!*\
!*** ../android-snapshot-bundle-preamble.js ***!
\**********************************************************/
var global = Function('return this')(); global.global = global; // Mock global object
// Set the __snapshotEnabled flag to true
Object.defineProperty(global, "__snapshotEnabled", {
value: true,
writable: false,
configurable: false,
enumerable: false
});
// Set the __snapshot flag to true
Object.defineProperty(global, "__snapshot", {
value: true,
writable: false,
configurable: true,
enumerable: false
});
global.__requireOverride = (function() {
return function(moduleId, dirname) {
/*
The android runtime loads in advance all JS modules that contain a native class successor generated statically at build time.
In case of snapshot this file always is the bundled one. Since it is snapshoted it is already loaded in the heap and is not meant
to be required. The main entry file (bundle.js) is responsible for actually executing the modules containing native java classes.
*/
var resolvedModuleId = moduleId.replace(/^\.\/tns_modules\//, "");
if (resolvedModuleId === './_embedded_script_.js') {
return {};
}
};
}());

View File

@@ -1,13 +0,0 @@
android {
productFlavors {
"nativescript-android-snapshot" {
dimension "nativescript-android-snapshot"
}
}
}
android {
aaptOptions {
ignoreAssetsPattern "_embedded_script_.js"
}
}

View File

@@ -1,4 +0,0 @@
/libs
/obj
/jni/*/TNSSnapshot.c

View File

@@ -1,7 +0,0 @@
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := snapshot
LOCAL_SRC_FILES := $(TARGET_ARCH_ABI)/TNSSnapshot.c
include $(BUILD_SHARED_LIBRARY)

View File

@@ -1,464 +0,0 @@
const fs = require("fs");
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, 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");
const INCLUDE_GRADLE_PATH = join(__dirname, "snapshot-generator-tools/include.gradle");
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();
shelljs.cd(dir);
try {
action();
} finally {
shelljs.cd(currentDir);
}
}
function SnapshotGenerator(options) {
this.buildPath = options.buildPath || join(__dirname, "build");
}
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");
const bundleEndingContent = fs.readFileSync(BUNDLE_ENDING_PATH, "utf8");
// IMPORTANT: join by "\n;" as we are joining IIFE functions and if the snapshot tool is used
// along with Uglify configuration for replacing `;` with `/n`, we will generate invalid JavaScript
// 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() {
// some code here
// })()
// // sourceMapUrl......
const inputFilesContent = inputFiles.map(file => fs.readFileSync(file, "utf8")).join("\n;");
const snapshotFileContent = bundlePreambleContent + "\n" + inputFilesContent + "\n" + bundleEndingContent;
fs.writeFileSync(outputFile, snapshotFileContent, { encoding: "utf8" });
}
const snapshotToolsDownloads = {};
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);
if (snapshotToolsDownloads[mksnapshotToolPath])
return snapshotToolsDownloads[mksnapshotToolPath];
const downloadUrl = MKSNAPSHOT_TOOLS_DOWNLOAD_ROOT_URL + mksnapshotToolRelativePath;
createDirectory(dirname(mksnapshotToolPath));
snapshotToolsDownloads[mksnapshotToolPath] = downloadFile(downloadUrl, mksnapshotToolPath, MKSNAPSHOT_TOOLS_DOWNLOAD_TIMEOUT);
snapshotToolsDownloads[mksnapshotToolPath].catch(err => {
const errorMessage = err && err.message ? err.message : "";
let cleanupError = "";
try {
fs.unlinkSync(mksnapshotToolPath);
} catch (unlinkErr) {
if (unlinkErr && unlinkErr.code !== "ENOENT") {
cleanupError = `${EOL}Failed to cleanup mksnapshot tool.`;
}
}
throw new Error(`Failed to download mksnapshot tool. Error: ${errorMessage}.${cleanupError}`);
});
return snapshotToolsDownloads[mksnapshotToolPath];
}
SnapshotGenerator.prototype.convertToAndroidArchName = function (archName) {
switch (archName) {
case "arm": return "armeabi-v7a";
case "arm64": return "arm64-v8a";
case "ia32": return "x86";
case "ia64": return "x86_64";
default: return archName;
}
}
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;
}
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 this.runMksnapshotTools(snapshotToolsPath, localTools, inputFile, mksnapshotParams, buildCSource, snapshotInDocker);
});
}
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.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());
shelljs.rm("-rf", ndkBuildPath);
shelljs.cp("-r", NDK_BUILD_SEED_PATH, ndkBuildPath);
fs.writeFileSync(join(ndkBuildPath, "jni/Application.mk"), "APP_ABI := " + androidArchs.join(" ")); // create Application.mk file
shelljs.mv(join(this.buildPath, "snapshots/src/*"), join(ndkBuildPath, "jni"));
shellJsExecuteInDir(ndkBuildPath, function () {
shelljs.exec(androidNdkBuildPath);
});
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"));
}
SnapshotGenerator.prototype.generate = function (options) {
// Arguments validation
options = options || {};
if (!options.v8Version) { throw new Error("No v8 version specified."); }
if (!options.snapshotToolsPath) { throw new Error("snapshotToolsPath option is not specified."); }
const preprocessedInputFile = options.preprocessedInputFile || join(this.buildPath, "inputFile.preprocessed");
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.generateSnapshots(
options.snapshotToolsPath,
preprocessedInputFile,
options.v8Version,
options.targetArchs,
options.useLibs,
options.mksnapshotParams,
snapshotInDocker
).then(() => {
this.buildIncludeGradle();
if (options.useLibs) {
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) {
return `The provided Android NDK is v${localNdkRevision} while the recommended one is 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`));
});
}

View File

@@ -1,128 +0,0 @@
const { chmodSync, createWriteStream } = require("fs");
const { tmpdir, EOL } = require("os");
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)
// .then(options =>
// get(options)
// .on("error", reject)
// .pipe(createWriteStream(destinationFilePath, { autoClose: true }))
// .on("finish", _ => {
// chmodSync(destinationFilePath, 0755);
// return resolve(destinationFilePath);
// })
// ).catch(reject);
});
const getJsonFile = url =>
new Promise((resolve, reject) => {
// getRequestOptions(url)
// .then(options =>
// get(options, (error, response, body) => {
// if (error) {
// return reject(error);
// }
// if (!response || response.statusCode !== 200) {
// return reject(`Couldn't fetch ${url}! Response:${EOL}${response}`);
// }
// try {
// const data = JSON.parse(body);
// resolve(data);
// } catch (error) {
// reject(`Couldn't parse json data! Original error:${EOL}${error}`);
// }
// })
// ).catch(reject);
});
const getRequestOptions = (url, timeout) =>
new Promise((resolve, reject) => {
const options = { url, timeout };
getProxySettings()
.then(proxySettings => {
const allOptions = Object.assign(options, proxySettings);
resolve(allOptions);
})
.catch(error =>
reject(`Couldn't get proxy settings! Original error:${EOL}${error}`));
});
module.exports = {
CONSTANTS,
createDirectory,
has32BitArch,
getHostOS,
getHostOSVersion,
getHostOSArch,
isMacOSCatalinaOrHigher,
downloadFile,
getJsonFile,
isSubPath,
isWindows
};

View File

@@ -1,375 +0,0 @@
const { join, relative, resolve, sep, dirname } = require('path');
const fs = require('fs');
const webpack = require('webpack');
const nsWebpack = require('@nativescript/webpack');
const nativescriptTarget = require('@nativescript/webpack/nativescript-target');
const { nsSupportHmrNg } = require('@nativescript/webpack/transformers/ns-support-hmr-ng');
const { nsTransformNativeClassesNg } = require('@nativescript/webpack/transformers/ns-transform-native-classes-ng');
const { parseWorkspaceConfig, hasConfigurations } = require('@nativescript/webpack/helpers/angular-config-parser');
const { getMainModulePath } = require('@nativescript/webpack/utils/ast-utils');
const { getNoEmitOnErrorFromTSConfig, getCompilerOptionsFromTSConfig } = require('@nativescript/webpack/utils/tsconfig-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/webpack/plugins/NativeScriptAngularCompilerPlugin');
const hashSalt = Date.now().toString();
module.exports = (env) => {
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/<platform>/...
const dist = resolve(projectRoot, nsWebpack.getAppPath(platform, projectRoot));
const {
// The 'appPath' and 'appResourcesPath' values are fetched from
// the nsconfig.json configuration file
// when bundling with `tns run android|ios --bundle`.
appPath = 'src',
appResourcesPath = 'App_Resources',
// You can provide the following flags when running 'tns run android|ios'
snapshot, // --env.snapshot,
production, // --env.production
configuration, // --env.configuration (consistent with angular cli usage)
projectName, // --env.projectName (drive configuration through angular projects)
uglify, // --env.uglify
report, // --env.report
sourceMap, // --env.sourceMap
hiddenSourceMap, // --env.hiddenSourceMap
hmr, // --env.hmr,
unitTesting, // --env.unitTesting
testing, // --env.testing
verbose, // --env.verbose
ci, // --env.ci
snapshotInDocker, // --env.snapshotInDocker
skipSnapshotTools, // --env.skipSnapshotTools
compileSnapshot, // --env.compileSnapshot
appComponents = [],
entries = {},
} = env;
const { fileReplacements, copyReplacements } = parseWorkspaceConfig(platform, configuration, projectName);
const useLibs = compileSnapshot;
const isAnySourceMapEnabled = !!sourceMap || !!hiddenSourceMap;
const externals = nsWebpack.getConvertedExternals(env.externals);
const appFullPath = resolve(projectRoot, appPath);
const appResourcesFullPath = resolve(projectRoot, appResourcesPath);
let tsConfigName = 'tsconfig.json';
let tsConfigPath = resolve(projectRoot, tsConfigName);
const tsConfigTnsName = 'tsconfig.tns.json';
const tsConfigTnsPath = resolve(projectRoot, tsConfigTnsName);
if (fs.existsSync(tsConfigTnsPath)) {
// support shared angular app configurations
tsConfigName = tsConfigTnsName;
tsConfigPath = tsConfigTnsPath;
}
const tsConfigEnvName = 'tsconfig.env.json';
const tsConfigEnvPath = resolve(projectRoot, tsConfigEnvName);
if (hasConfigurations(configuration) && fs.existsSync(tsConfigEnvPath)) {
// when configurations are used, switch to environments supported config
tsConfigName = tsConfigEnvName;
tsConfigPath = tsConfigEnvPath;
}
const entryModule = `${nsWebpack.getEntryModule(appFullPath, platform)}.ts`;
const entryPath = `.${sep}${entryModule}`;
Object.assign(entries, { bundle: entryPath }, entries);
const areCoreModulesExternal = Array.isArray(env.externals) && env.externals.some((e) => e.indexOf('@nativescript') > -1);
if (platform === 'ios' && !areCoreModulesExternal && !testing) {
entries['tns_modules/inspector_modules'] = '@nativescript/core/inspector_modules';
}
const compilerOptions = getCompilerOptionsFromTSConfig(tsConfigPath);
nsWebpack.processTsPathsForScopedModules({ compilerOptions });
nsWebpack.processTsPathsForScopedAngular({ compilerOptions });
const ngCompilerTransformers = [nsTransformNativeClassesNg];
const additionalLazyModuleResources = [];
const copyIgnore = { ignore: [`${relative(appPath, appResourcesFullPath)}/**`] };
const copyTargets = [{ from: { glob: 'assets/**', dot: false } }, { from: { glob: 'fonts/**', dot: false } }, ...copyReplacements];
if (!production) {
// for development purposes only
// for example, include mock json folder
// copyTargets.push({ from: 'tools/mockdata', to: 'assets/mockdata' });
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 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,
skipCodeGeneration: false,
sourceMap: !!isAnySourceMapEnabled,
additionalLazyModuleResources: additionalLazyModuleResources,
compilerOptions: { paths: compilerOptions.paths },
});
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')}`);
}
const noEmitOnErrorFromTSConfig = getNoEmitOnErrorFromTSConfig(tsConfigName);
// Add your custom Activities, Services and other android app components here.
appComponents.push('@nativescript/core/ui/frame', '@nativescript/core/ui/frame/activity');
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 @nativescript/core
modules: [resolve(__dirname, 'node_modules/@nativescript/core'), resolve(__dirname, 'node_modules'), 'node_modules/@nativescript/core', 'node_modules'],
alias: {
'~/package.json': resolve(projectRoot, 'package.json'),
'~': appFullPath,
'tns-core-modules': '@nativescript/core',
'nativescript-angular': '@nativescript/angular',
...fileReplacements,
},
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',
noEmitOnErrors: noEmitOnErrorFromTSConfig,
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: !ci,
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',
// For v8 Compatibility
keep_infinity: true, // for V8
reduce_funcs: false, // for V8
// custom
drop_console: production,
drop_debugger: true,
ecma: 6,
global_defs: {
__UGLIFIED__: true,
},
},
// Required for Element Level CSS, Observable Events, & Android Frame
keep_classnames: true,
// custom
ecma: 6,
safari10: platform !== 'android',
},
}),
],
},
module: {
rules: [
{
include: join(appFullPath, entryPath),
use: [
// Require all Android app components
platform === 'android' && {
loader: '@nativescript/webpack/helpers/android-app-components-loader',
options: { modules: appComponents },
},
{
loader: '@nativescript/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' },
{
test: /[\/|\\]app\.css$/,
use: [
'@nativescript/webpack/helpers/style-hot-loader',
{
loader: '@nativescript/webpack/helpers/css2json-loader',
options: { useForImports: true },
},
],
},
{
test: /[\/|\\]app\.scss$/,
use: [
'@nativescript/webpack/helpers/style-hot-loader',
{
loader: '@nativescript/webpack/helpers/css2json-loader',
options: { useForImports: true },
},
'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/webpack/helpers/moduleid-compat-loader', '@nativescript/webpack/helpers/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',
'global.isAndroid': platform === 'android',
'global.isIOS': platform === 'ios',
process: 'global.process',
}),
// Remove all files from the out dir.
new CleanWebpackPlugin({
cleanOnceBeforeBuildPatterns: itemsToClean,
verbose: !!verbose,
}),
// Copy assets
new CopyWebpackPlugin([...copyTargets, { from: { glob: '**/*.jpg', dot: false } }, { from: { glob: '**/*.png', dot: false } }], copyIgnore),
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'],
projectRoot,
webpackConfig: config,
snapshotInDocker,
skipSnapshotTools,
useLibs,
})
);
}
if (!production && hmr) {
config.plugins.push(new webpack.HotModuleReplacementPlugin());
}
return config;
};

View File

@@ -1 +0,0 @@
export {};

View File

@@ -1,306 +0,0 @@
import * as proxyquire from 'proxyquire';
import * as nsWebpackIndex from '../index';
import { join } from 'path';
// 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`.
proxyquire.noCallThru();
class EmptyClass {}
let angularCompilerOptions: any;
class AngularCompilerStub {
constructor(options) {
angularCompilerOptions = options;
}
}
let terserOptions: any;
class TerserJsStub {
constructor(options) {
terserOptions = options;
}
}
const nativeScriptDevWebpack = {
GenerateBundleStarterPlugin: EmptyClass,
GenerateNativeScriptEntryPointsPlugin: EmptyClass,
WatchStateLoggerPlugin: EmptyClass,
PlatformFSPlugin: EmptyClass,
getAppPath: () => 'app',
getEntryModule: () => 'EntryModule',
hasRootLevelScopedModules: () => false,
hasRootLevelScopedAngular: () => false,
processTsPathsForScopedModules: () => false,
processTsPathsForScopedAngular: () => false,
getResolver: () => null,
getConvertedExternals: nsWebpackIndex.getConvertedExternals,
getSourceMapFilename: nsWebpackIndex.getSourceMapFilename,
processAppComponents: nsWebpackIndex.processAppComponents,
getUserDefinedEntries: nsWebpackIndex.getUserDefinedEntries,
};
const emptyObject = {};
const FakeHmrTransformerFlag = 'hmr';
const FakeNativeClassTransformerFlag = 'NativeClass';
const FakeLazyTransformerFlag = 'lazy';
const webpackConfigAngular = proxyquire('./webpack.angular', {
'@nativescript/webpack': nativeScriptDevWebpack,
'@nativescript/webpack/nativescript-target': emptyObject,
'@nativescript/webpack/transformers/ns-support-hmr-ng': {
nsSupportHmrNg: () => {
return FakeHmrTransformerFlag;
},
},
'@nativescript/webpack/transformers/ns-transform-native-classes-ng': {
nsTransformNativeClassesNg: () => {
return FakeNativeClassTransformerFlag;
},
},
'@nativescript/webpack/helpers/angular-config-parser': {
parseWorkspaceConfig: (platform, envConfigs, rootPath = '') => {
return {
fileReplacements: {},
copyReplacements: [],
};
},
hasConfigurations: (envConfigs) => {
return false;
},
},
'@nativescript/webpack/utils/ast-utils': {
getMainModulePath: () => {
return 'fakePath';
},
},
'@nativescript/webpack/utils/tsconfig-utils': {
getNoEmitOnErrorFromTSConfig: () => {
return false;
},
getCompilerOptionsFromTSConfig: () => {
return false;
},
},
'@nativescript/webpack/plugins/NativeScriptAngularCompilerPlugin': {
getAngularCompilerPlugin: () => {
return AngularCompilerStub;
},
},
'@ngtools/webpack': {
AngularCompilerPlugin: AngularCompilerStub,
},
'terser-webpack-plugin': TerserJsStub,
});
const webpackConfigTypeScript = proxyquire('./webpack.typescript', {
'@nativescript/webpack': nativeScriptDevWebpack,
'@nativescript/webpack/nativescript-target': emptyObject,
'@nativescript/webpack/transformers/ns-transform-native-classes': emptyObject,
'@nativescript/webpack/utils/tsconfig-utils': {
getNoEmitOnErrorFromTSConfig: () => {
return false;
},
getCompilerOptionsFromTSConfig: () => {
return false;
},
},
'terser-webpack-plugin': TerserJsStub,
});
const webpackConfigJavaScript = proxyquire('./webpack.javascript', {
'@nativescript/webpack': nativeScriptDevWebpack,
'@nativescript/webpack/nativescript-target': emptyObject,
'terser-webpack-plugin': TerserJsStub,
});
const webpackConfigVue = proxyquire('./webpack.vue', {
'@nativescript/webpack': nativeScriptDevWebpack,
'@nativescript/webpack/nativescript-target': emptyObject,
'@nativescript/webpack/transformers/ns-transform-native-classes': emptyObject,
'vue-loader/lib/plugin': EmptyClass,
'nativescript-vue-template-compiler': emptyObject,
'terser-webpack-plugin': TerserJsStub,
});
describe('webpack.config.js', () => {
const getInput = (options: { platform: string; hmr?: boolean; externals?: string[]; sourceMap?: boolean; hiddenSourceMap?: boolean | string }) => {
const input: any = Object.assign({}, options);
input[options.platform] = true;
return input;
};
[
{ type: 'javascript', webpackConfig: webpackConfigJavaScript },
{ type: 'typescript', webpackConfig: webpackConfigTypeScript },
{ type: 'angular', webpackConfig: webpackConfigAngular },
{ type: 'vue', webpackConfig: webpackConfigVue },
].forEach((element) => {
const { type, webpackConfig } = element;
['android', 'ios'].forEach((platform) => {
describe(`verify externals for webpack.${type}.js (${platform})`, () => {
afterEach(() => {
nativeScriptDevWebpack.getConvertedExternals = nsWebpackIndex.getConvertedExternals;
});
it('returns empty array when externals are not passed', () => {
const input = getInput({ platform });
const config = webpackConfig(input);
expect(config.externals).toEqual([]);
});
it('calls getConvertedExternals to convert externals', () => {
let isCalled = false;
nativeScriptDevWebpack.getConvertedExternals = () => {
isCalled = true;
return [];
};
const input = getInput({ platform, externals: ['nativescript-vue'] });
webpackConfig(input);
expect(isCalled).toBe(true, 'Webpack.config.js must use the getConvertedExternals method');
});
// if (platform === "ios") {
// it('has inspector_modules entry when tns-core-modules are not externals', () => {
// const input = getInput({ platform, externals: ['nativescript-vue'] });
// const config = webpackConfig(input);
// expect(config.entry["tns_modules/@nativescript/core/inspector_modules"]).toEqual("inspector_modules");
// });
// it('does not have inspector_modules entry when @nativescript/core are externals', () => {
// const input = getInput({ platform, externals: ['@nativescript'] });
// const config = webpackConfig(input);
// expect(config.entry["tns_modules/@nativescript/core/inspector_modules"]).toBeUndefined();
// });
// }
[
{
input: ['nativescript-vue'],
expectedOutput: [/^nativescript-vue((\/.*)|$)/],
},
{
input: ['nativescript-vue', 'nativescript-angular'],
expectedOutput: [/^nativescript-vue((\/.*)|$)/, /^nativescript-angular((\/.*)|$)/],
},
].forEach((testCase) => {
const input = getInput({ platform, externals: testCase.input });
it(`are correct regular expressions, for input ${testCase.input}`, () => {
const config = webpackConfig(input);
expect(config.externals).toEqual(testCase.expectedOutput);
});
});
});
if (type === 'angular') {
describe(`angular transformers for webpack.${type}.js (${platform})`, () => {
beforeEach(() => {
angularCompilerOptions = null;
});
it('should contain only NativeClass transformer by default', () => {
const input = getInput({ platform });
webpackConfig(input);
expect(angularCompilerOptions).toBeDefined();
expect(angularCompilerOptions.platformTransformers).toBeDefined();
expect(angularCompilerOptions.platformTransformers.length).toEqual(1);
expect(angularCompilerOptions.platformTransformers[0]).toEqual(FakeNativeClassTransformerFlag);
});
it('should contain the HMR transformer when the HMR flag is passed', () => {
const input = getInput({ platform, hmr: true });
webpackConfig(input);
expect(angularCompilerOptions).toBeDefined();
expect(angularCompilerOptions.platformTransformers).toBeDefined();
expect(angularCompilerOptions.platformTransformers.length).toEqual(2);
expect(angularCompilerOptions.platformTransformers[1]).toEqual(FakeHmrTransformerFlag);
});
});
}
describe(`source map for webpack.${type}.js (${platform})`, () => {
beforeEach(() => {
terserOptions = null;
});
it('should not set source maps without the flag', () => {
const input = getInput({ platform, sourceMap: false });
const config = webpackConfig(input);
expect(config.devtool).toEqual('none');
expect(terserOptions.sourceMap).toBeFalsy();
expect(terserOptions.terserOptions.output.semicolons).toBeTruthy();
expect(config.output.sourceMapFilename).toEqual('[file].map');
});
it('should set inline-source-map devtool', () => {
const input = getInput({ platform, sourceMap: true });
const config = webpackConfig(input);
expect(config.devtool).toEqual('inline-source-map');
expect(terserOptions.sourceMap).toBeTruthy();
expect(terserOptions.terserOptions.output.semicolons).toBeFalsy();
expect(config.output.sourceMapFilename).toEqual('[file].map');
});
});
describe(`hidden source map for webpack.${type}.js (${platform})`, () => {
beforeEach(() => {
terserOptions = null;
});
it('should not set source maps without the flag', () => {
const input = getInput({ platform, hiddenSourceMap: false });
const config = webpackConfig(input);
expect(config.devtool).toEqual('none');
expect(terserOptions.sourceMap).toBeFalsy();
expect(terserOptions.terserOptions.output.semicolons).toBeTruthy();
expect(config.output.sourceMapFilename).toEqual('[file].map');
});
it('should set hidden-source-map devtool and the default sourceMap folder', () => {
const input = getInput({ platform, hiddenSourceMap: true });
const config = webpackConfig(input);
expect(config.devtool).toEqual('hidden-source-map');
expect(terserOptions.sourceMap).toBeTruthy();
expect(terserOptions.terserOptions.output.semicolons).toBeFalsy();
expect(config.output.sourceMapFilename).toEqual(join('..', 'sourceMap', '[file].map'));
});
it('should override the sourceMap property and the default sourceMap folder', () => {
const input = getInput({ platform, sourceMap: true, hiddenSourceMap: true });
const config = webpackConfig(input);
expect(config.devtool).toEqual('hidden-source-map');
expect(terserOptions.sourceMap).toBeTruthy();
expect(terserOptions.terserOptions.output.semicolons).toBeFalsy();
expect(config.output.sourceMapFilename).toEqual(join('..', 'sourceMap', '[file].map'));
});
it('should set hidden-source-map devtool and override the sourceMapFilename', () => {
const newSourceMapFolder = 'myCoolSourceMapFolder';
const input = getInput({ platform, sourceMap: true, hiddenSourceMap: newSourceMapFolder });
const config = webpackConfig(input);
expect(config.devtool).toEqual('hidden-source-map');
expect(terserOptions.sourceMap).toBeTruthy();
expect(terserOptions.terserOptions.output.semicolons).toBeFalsy();
expect(config.output.sourceMapFilename).toEqual(join('..', newSourceMapFolder, '[file].map'));
});
});
});
});
});

Some files were not shown because too many files have changed in this diff Show More