mirror of
https://github.com/NativeScript/NativeScript.git
synced 2025-11-05 13:26:48 +08:00
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:
47
packages/webpack/.gitignore
vendored
47
packages/webpack/.gitignore
vendored
@@ -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
|
||||
@@ -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
|
||||
@@ -1,5 +0,0 @@
|
||||
{
|
||||
"extends": "@istanbuljs/nyc-config-typescript",
|
||||
"exclude": ["/demo/**"],
|
||||
"reporter": ["text", "lcov"]
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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.
|
||||
@@ -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).
|
||||
|
||||

|
||||
|
||||
|
||||
@@ -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);
|
||||
@@ -1 +0,0 @@
|
||||
@node %~dp0\generate-android-snapshot %*
|
||||
@@ -1,3 +0,0 @@
|
||||
#!/usr/bin/env node
|
||||
const installer = require("../installer");
|
||||
installer.install();
|
||||
@@ -1 +0,0 @@
|
||||
@node %~dp0\install-ns-webpack %*
|
||||
@@ -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);
|
||||
@@ -1 +0,0 @@
|
||||
@node %~dp0\ns-bundle %*
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
@node %~dp0\ns-verify-bundle %*
|
||||
@@ -1 +0,0 @@
|
||||
@node %~dp0\ns-webpack-update %*
|
||||
@@ -1,3 +0,0 @@
|
||||
#!/usr/bin/env node
|
||||
const installer = require("../installer");
|
||||
installer.uninstall();
|
||||
@@ -1 +0,0 @@
|
||||
@node %~dp0\remove-ns-webpack %*
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
3
packages/webpack/bundle-config-loader.d.ts
vendored
3
packages/webpack/bundle-config-loader.d.ts
vendored
@@ -1,3 +0,0 @@
|
||||
import { loader } from 'webpack';
|
||||
declare const loader: loader.Loader;
|
||||
export default loader;
|
||||
@@ -1 +0,0 @@
|
||||
export {};
|
||||
@@ -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___');
|
||||
});
|
||||
});
|
||||
@@ -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;
|
||||
@@ -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);
|
||||
};
|
||||
@@ -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
|
||||
};
|
||||
114
packages/webpack/helpers/angular-config-parser.js
vendored
114
packages/webpack/helpers/angular-config-parser.js
vendored
@@ -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
|
||||
};
|
||||
@@ -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);
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
import { loader } from 'webpack';
|
||||
declare const loader: loader.Loader;
|
||||
export default loader;
|
||||
@@ -1 +0,0 @@
|
||||
export {};
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
@@ -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;
|
||||
@@ -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,
|
||||
};
|
||||
@@ -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}' });
|
||||
})
|
||||
}
|
||||
`};
|
||||
@@ -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, {});
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -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);
|
||||
};
|
||||
@@ -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"));
|
||||
});
|
||||
}
|
||||
@@ -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);
|
||||
});
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
module.exports = function (loadModuleFn) {
|
||||
const nsCore = require("@nativescript/core");
|
||||
require("@nativescript/core/ui/styling/style-scope");
|
||||
|
||||
loadModuleFn();
|
||||
|
||||
nsCore.Application.loadAppCss();
|
||||
}
|
||||
@@ -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);
|
||||
};
|
||||
@@ -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);
|
||||
};
|
||||
@@ -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,
|
||||
};
|
||||
@@ -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,
|
||||
};
|
||||
|
||||
@@ -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}`);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -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);
|
||||
};
|
||||
@@ -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);
|
||||
};
|
||||
@@ -1,3 +0,0 @@
|
||||
import { loader } from 'webpack';
|
||||
declare const loader: loader.Loader;
|
||||
export default loader;
|
||||
@@ -1 +0,0 @@
|
||||
export {};
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
@@ -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;
|
||||
1
packages/webpack/hmr/hmr-update.d.ts
vendored
1
packages/webpack/hmr/hmr-update.d.ts
vendored
@@ -1 +0,0 @@
|
||||
export declare function hmrUpdate(): any;
|
||||
@@ -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;
|
||||
});
|
||||
}
|
||||
3
packages/webpack/hmr/hot-loader.d.ts
vendored
3
packages/webpack/hmr/hot-loader.d.ts
vendored
@@ -1,3 +0,0 @@
|
||||
import { loader } from 'webpack';
|
||||
declare const loader: loader.Loader;
|
||||
export default loader;
|
||||
@@ -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;
|
||||
1
packages/webpack/hmr/index.d.ts
vendored
1
packages/webpack/hmr/index.d.ts
vendored
@@ -1 +0,0 @@
|
||||
export { hmrUpdate } from './hmr-update';
|
||||
@@ -1 +0,0 @@
|
||||
export { hmrUpdate } from './hmr-update';
|
||||
1
packages/webpack/host/resolver.d.ts
vendored
1
packages/webpack/host/resolver.d.ts
vendored
@@ -1 +0,0 @@
|
||||
export declare function getResolver(platforms: string[], explicitResolve?: string[], nsPackageFilters?: string[], platformSpecificExt?: string[]): (path: string) => string;
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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];
|
||||
}
|
||||
}
|
||||
1
packages/webpack/index.spec.d.ts
vendored
1
packages/webpack/index.spec.d.ts
vendored
@@ -1 +0,0 @@
|
||||
export {};
|
||||
@@ -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(', ')}`);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -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,
|
||||
};
|
||||
@@ -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
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
export {};
|
||||
@@ -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());
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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).`);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
};
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
})();
|
||||
@@ -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;
|
||||
})();
|
||||
@@ -1 +0,0 @@
|
||||
export declare function getAngularCompilerPlugin(platform: string): any;
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
})();
|
||||
|
||||
@@ -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
|
||||
}
|
||||
33
packages/webpack/plugins/PlatformFSPlugin.d.ts
vendored
33
packages/webpack/plugins/PlatformFSPlugin.d.ts
vendored
@@ -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;
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
module.exports = Object.assign({},
|
||||
require("./GenerateBundleStarterPlugin"),
|
||||
require("./GenerateNativeScriptEntryPointsPlugin"),
|
||||
require("./NativeScriptSnapshotPlugin"),
|
||||
require("./PlatformSuffixPlugin"),
|
||||
require("./PlatformFSPlugin"),
|
||||
require("./WatchStateLoggerPlugin")
|
||||
);
|
||||
@@ -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");
|
||||
}
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
"use strict";
|
||||
|
||||
const hook = require("@nativescript/hook")(__dirname);
|
||||
|
||||
const { getProjectDir } = require("./helpers/projectHelpers");
|
||||
|
||||
const projectDir = getProjectDir();
|
||||
|
||||
if (projectDir) {
|
||||
hook.preuninstall();
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
@@ -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).");
|
||||
}
|
||||
});;
|
||||
}
|
||||
@@ -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
|
||||
});
|
||||
@@ -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 {};
|
||||
}
|
||||
};
|
||||
}());
|
||||
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
android {
|
||||
productFlavors {
|
||||
"nativescript-android-snapshot" {
|
||||
dimension "nativescript-android-snapshot"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
android {
|
||||
aaptOptions {
|
||||
ignoreAssetsPattern "_embedded_script_.js"
|
||||
}
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
/libs
|
||||
/obj
|
||||
/jni/*/TNSSnapshot.c
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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`));
|
||||
});
|
||||
}
|
||||
@@ -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
|
||||
};
|
||||
@@ -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;
|
||||
};
|
||||
@@ -1 +0,0 @@
|
||||
export {};
|
||||
@@ -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
Reference in New Issue
Block a user