This commit is contained in:
Martin Guillon
2021-04-08 16:37:08 +02:00
73 changed files with 9615 additions and 4 deletions

8
.gitignore vendored
View File

@ -6,10 +6,10 @@
/out-tsc
# dependencies
node_modules
package-lock.json
yarn.lock
pnpm-lock.yaml
**/node_modules
**/package-lock.json
**/yarn.lock
**/pnpm-lock.yaml
# IDEs and editors
.idea

View File

@ -42,6 +42,9 @@
},
"webpack": {
"tags": []
},
"webpack5": {
"tags": []
}
}
}

3
packages/webpack5/.gitignore vendored Normal file
View File

@ -0,0 +1,3 @@
#
dist
coverage

View File

@ -0,0 +1,6 @@
{
"useTabs": true,
"printWidth": 80,
"tabWidth": 2,
"singleQuote": true
}

View File

@ -0,0 +1,30 @@
@nativescript/webpack rewrite
The rewrite allows us to simplify things, and introduce some breaking changes.
Listing them here, so we can keep track of them - will be in the merge commit, and the release notes once we are ready.
BREAKING CHANGES:
- `package.json` main should now use a relative path to the package.json instead of the app directory
For example (given we have a `src` directory where our app is):
`"main": "app.js"` becomes `"main": "src/app.js"` **OR** `"main": "src/app.ts"` (whether using JS or TS)
This simplifies things, and will allow ctrl/cmd + clicking on the filename in some editors.
- `postinstall` scripts have been removed.
The configuration will not need to change in the user projects between updates.
For existing projects we will provide an easy upgrade path, through `ns migrate` and a binary in the package.
For new projects `ns create` should create the config file by invoking a binary in the package.
- removed resolutions for short imports - use full imports instead.
For example:
```
import http from 'http'
// becomes
import { http } from '@nativescript/core'
```

View File

@ -0,0 +1,47 @@
import { parseEnvFlags } from '../../src/cli/parseEnvFlags';
describe.only('parseEnvFlags', () => {
it('parses all possible flags', () => {
const res = parseEnvFlags([
'--env', // invalid
'--env.foo',
'--env.externals=ext1',
'--env.externals=ext2',
'--env.externals=ext3',
'--env.externals=ext4',
'--env.externals=ext4',
'--env.externals=/path/to/a/very/long/path with spaces/foo.js',
'--env.externals=~/package.json',
'--env.externals=package.json',
'--env.ios=false',
'--env.android',
'--env.verbose',
'--env.sourceMap',
'--env.appPath=app',
'--env.appResourcesPath=App_Resources',
'--env.num=5',
'--env.float=5.4',
'--env.numArray=3',
'--env.numArray=4',
'--env.numArray=5',
'--no-hmr',
'--not-env-flag',
]);
expect(res).toBeDefined();
expect(res.foo).toBe(true);
expect(res.externals).toBeInstanceOf(Array);
expect(res.externals.length).toBe(8);
expect(res.ios).toBe(false);
expect(res.android).toBe(true);
expect(res.verbose).toBe(true);
expect(res.sourceMap).toBe(true);
expect(res.sourceMap).toBe(true);
expect(res.appPath).toBe('app');
expect(res.appResourcesPath).toBe('App_Resources');
expect(res.num).toBe(5);
expect(res.float).toBe(5.4);
expect(res.numArray).toStrictEqual([3, 4, 5]);
expect(Object.keys(res).length).toBe(11);
});
});

View File

@ -0,0 +1,714 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`angular configuration for android 1`] = `
"{
mode: 'development',
externals: [
'package.json',
'~/package.json'
],
devtool: 'inline-source-map',
target: 'node',
watchOptions: {
ignored: [
'__jest__/platforms/**',
'__jest__/App_Resources/**'
]
},
ignoreWarnings: [
/System.import\\\\(\\\\) is deprecated/,
/Zone\\\\.js does not support native async\\\\/await/,
/environment.(\\\\w+).ts is part of the TypeScript compilation but it's unused/
],
output: {
path: '__jest__/platforms/android/app/src/main/assets/app',
pathinfo: false,
publicPath: '',
libraryTarget: 'commonjs',
globalObject: 'global',
clean: true
},
resolve: {
symlinks: true,
alias: {
'~': '__jest__/src',
'@': '__jest__/src'
},
extensions: [
'.android.ts',
'.ts',
'.android.js',
'.js',
'.android.css',
'.css',
'.android.scss',
'.scss',
'.android.json',
'.json'
],
mainFields: [
'module',
'main'
],
modules: [
'__jest__/node_modules',
'node_modules'
]
},
resolveLoader: {
modules: [
'__jest__/node_modules/@nativescript/webpack/dist/loaders',
'__jest__/node_modules',
'node_modules'
]
},
module: {
rules: [
/* config.module.rule('bundle') */
{
enforce: 'post',
test: '__jest__/src/app.js',
use: [
/* config.module.rule('bundle').use('app-css-loader') */
{
loader: 'app-css-loader',
options: {
platform: 'android'
}
},
/* config.module.rule('bundle').use('nativescript-hot-loader') */
{
loader: 'nativescript-hot-loader',
options: {
injectHMRRuntime: true
}
}
]
},
/* config.module.rule('js') */
{
test: /\\\\.js$/,
exclude: [
/node_modules/
],
use: [
/* config.module.rule('js').use('babel-loader') */
{
loader: 'babel-loader',
options: {
generatorOpts: {
compact: false
}
}
}
]
},
/* config.module.rule('workers') */
{
test: /\\\\.(js|ts)$/,
exclude: [
/node_modules/
],
use: [
/* config.module.rule('workers').use('nativescript-worker-loader') */
{
loader: 'nativescript-worker-loader'
}
]
},
/* config.module.rule('css') */
{
test: /\\\\.css$/,
exclude: [
/\\\\.component\\\\.css$/
],
use: [
/* config.module.rule('css').use('apply-css-loader') */
{
loader: 'apply-css-loader'
},
/* config.module.rule('css').use('css2json-loader') */
{
loader: 'css2json-loader'
},
/* config.module.rule('css').use('postcss-loader') */
{
loader: 'postcss-loader',
options: {
postcssOptions: {
plugins: [
'postcss-import'
]
}
}
}
]
},
/* config.module.rule('scss') */
{
test: /\\\\.scss$/,
exclude: [
/\\\\.component\\\\.scss$/
],
use: [
/* config.module.rule('scss').use('apply-css-loader') */
{
loader: 'apply-css-loader'
},
/* config.module.rule('scss').use('css2json-loader') */
{
loader: 'css2json-loader'
},
/* config.module.rule('scss').use('postcss-loader') */
{
loader: 'postcss-loader',
options: {
postcssOptions: {
plugins: [
'postcss-import'
]
}
}
},
/* config.module.rule('scss').use('sass-loader') */
{
loader: 'sass-loader'
}
]
},
/* config.module.rule('angular') */
{
test: /(?:\\\\.ngfactory.js|\\\\.ngstyle\\\\.js|\\\\.ts)$/,
use: [
/* config.module.rule('angular').use('@ngtools/webpack') */
{
loader: '@ngtools/webpack'
}
]
},
/* config.module.rule('@angular/core') */
{
test: /[\\\\/\\\\\\\\]@angular[\\\\/\\\\\\\\]core[\\\\/\\\\\\\\].+\\\\.js$/,
parser: {
system: true
}
},
/* config.module.rule('html') */
{
test: /\\\\.html$/,
use: [
/* config.module.rule('html').use('raw-loader') */
{
loader: 'raw-loader'
}
]
},
/* config.module.rule('css|component') */
{
test: /\\\\.component\\\\.css$/,
use: [
/* config.module.rule('css|component').use('raw-loader') */
{
loader: 'raw-loader'
}
]
},
/* config.module.rule('scss|component') */
{
test: /\\\\.component\\\\.scss$/,
use: [
/* config.module.rule('scss|component').use('raw-loader') */
{
loader: 'raw-loader'
},
/* config.module.rule('scss|component').use('postcss-loader') */
{
loader: 'postcss-loader',
options: {
postcssOptions: {
plugins: [
'postcss-import'
]
}
}
},
/* config.module.rule('scss|component').use('sass-loader') */
{
loader: 'sass-loader'
}
]
}
]
},
optimization: {
splitChunks: {
cacheGroups: {
defaultVendor: {
test: /[\\\\\\\\/]node_modules[\\\\\\\\/]/,
priority: -10,
name: 'vendor',
chunks: 'all'
}
}
},
minimizer: [
/* config.optimization.minimizer('TerserPlugin') */
new TerserPlugin(
{
terserOptions: {
compress: {
collapse_vars: false,
sequences: false,
keep_infinity: true,
drop_console: false,
global_defs: {
__UGLIFIED__: true
}
},
keep_fnames: true,
keep_classnames: true
}
}
)
]
},
plugins: [
/* config.plugin('PlatformSuffixPlugin') */
new PlatformSuffixPlugin(
{
platform: 'android'
}
),
/* config.plugin('ContextExclusionPlugin|App_Resources') */
new ContextExclusionPlugin(
/(.*)App_Resources(.*)/
),
/* config.plugin('DefinePlugin') */
new DefinePlugin(
{
__DEV__: true,
__NS_WEBPACK__: true,
__NS_ENV_VERBOSE__: false,
__NS_DEV_HOST_IPS__: '[\\"127.0.0.1\\",\\"192.168.0.10\\"]',
__CSS_PARSER__: '\\"css-tree\\"',
__ANDROID__: true,
__IOS__: false,
'global.isAndroid': true,
'global.isIOS': false,
process: 'global.process'
}
),
/* config.plugin('CopyWebpackPlugin') */
new CopyPlugin(
{
patterns: [
{
from: 'assets/**',
context: '__jest__/src',
noErrorOnMissing: true,
globOptions: {
dot: false,
ignore: []
}
},
{
from: 'fonts/**',
context: '__jest__/src',
noErrorOnMissing: true,
globOptions: {
dot: false,
ignore: []
}
},
{
from: '**/*.+(jpg|png)',
context: '__jest__/src',
noErrorOnMissing: true,
globOptions: {
dot: false,
ignore: []
}
}
]
}
),
/* config.plugin('WatchStatePlugin') */
new WatchStatePlugin(),
/* config.plugin('AngularCompilerPlugin') */
new AngularCompilerPlugin(
{
tsConfigPath: '__jest__/tsconfig.json',
mainPath: '__jest__/src/app.js',
platformTransformers: [
function () { /* omitted long function */ }
]
}
)
],
entry: {
bundle: [
'@nativescript/core/globals/index.js',
'__jest__/src/app.js',
'@nativescript/core/ui/frame',
'@nativescript/core/ui/frame/activity'
]
}
}"
`;
exports[`angular configuration for ios 1`] = `
"{
mode: 'development',
externals: [
'package.json',
'~/package.json'
],
devtool: 'inline-source-map',
target: 'node',
watchOptions: {
ignored: [
'__jest__/platforms/**',
'__jest__/App_Resources/**'
]
},
ignoreWarnings: [
/System.import\\\\(\\\\) is deprecated/,
/Zone\\\\.js does not support native async\\\\/await/,
/environment.(\\\\w+).ts is part of the TypeScript compilation but it's unused/
],
output: {
path: '__jest__/platforms/ios/jest/app',
pathinfo: false,
publicPath: '',
libraryTarget: 'commonjs',
globalObject: 'global',
clean: true
},
resolve: {
symlinks: true,
alias: {
'~': '__jest__/src',
'@': '__jest__/src'
},
extensions: [
'.ios.ts',
'.ts',
'.ios.js',
'.js',
'.ios.css',
'.css',
'.ios.scss',
'.scss',
'.ios.json',
'.json'
],
mainFields: [
'module',
'main'
],
modules: [
'__jest__/node_modules',
'node_modules'
]
},
resolveLoader: {
modules: [
'__jest__/node_modules/@nativescript/webpack/dist/loaders',
'__jest__/node_modules',
'node_modules'
]
},
module: {
rules: [
/* config.module.rule('bundle') */
{
enforce: 'post',
test: '__jest__/src/app.js',
use: [
/* config.module.rule('bundle').use('app-css-loader') */
{
loader: 'app-css-loader',
options: {
platform: 'ios'
}
},
/* config.module.rule('bundle').use('nativescript-hot-loader') */
{
loader: 'nativescript-hot-loader',
options: {
injectHMRRuntime: true
}
}
]
},
/* config.module.rule('js') */
{
test: /\\\\.js$/,
exclude: [
/node_modules/
],
use: [
/* config.module.rule('js').use('babel-loader') */
{
loader: 'babel-loader',
options: {
generatorOpts: {
compact: false
}
}
}
]
},
/* config.module.rule('workers') */
{
test: /\\\\.(js|ts)$/,
exclude: [
/node_modules/
],
use: [
/* config.module.rule('workers').use('nativescript-worker-loader') */
{
loader: 'nativescript-worker-loader'
}
]
},
/* config.module.rule('css') */
{
test: /\\\\.css$/,
exclude: [
/\\\\.component\\\\.css$/
],
use: [
/* config.module.rule('css').use('apply-css-loader') */
{
loader: 'apply-css-loader'
},
/* config.module.rule('css').use('css2json-loader') */
{
loader: 'css2json-loader'
},
/* config.module.rule('css').use('postcss-loader') */
{
loader: 'postcss-loader',
options: {
postcssOptions: {
plugins: [
'postcss-import'
]
}
}
}
]
},
/* config.module.rule('scss') */
{
test: /\\\\.scss$/,
exclude: [
/\\\\.component\\\\.scss$/
],
use: [
/* config.module.rule('scss').use('apply-css-loader') */
{
loader: 'apply-css-loader'
},
/* config.module.rule('scss').use('css2json-loader') */
{
loader: 'css2json-loader'
},
/* config.module.rule('scss').use('postcss-loader') */
{
loader: 'postcss-loader',
options: {
postcssOptions: {
plugins: [
'postcss-import'
]
}
}
},
/* config.module.rule('scss').use('sass-loader') */
{
loader: 'sass-loader'
}
]
},
/* config.module.rule('angular') */
{
test: /(?:\\\\.ngfactory.js|\\\\.ngstyle\\\\.js|\\\\.ts)$/,
use: [
/* config.module.rule('angular').use('@ngtools/webpack') */
{
loader: '@ngtools/webpack'
}
]
},
/* config.module.rule('@angular/core') */
{
test: /[\\\\/\\\\\\\\]@angular[\\\\/\\\\\\\\]core[\\\\/\\\\\\\\].+\\\\.js$/,
parser: {
system: true
}
},
/* config.module.rule('html') */
{
test: /\\\\.html$/,
use: [
/* config.module.rule('html').use('raw-loader') */
{
loader: 'raw-loader'
}
]
},
/* config.module.rule('css|component') */
{
test: /\\\\.component\\\\.css$/,
use: [
/* config.module.rule('css|component').use('raw-loader') */
{
loader: 'raw-loader'
}
]
},
/* config.module.rule('scss|component') */
{
test: /\\\\.component\\\\.scss$/,
use: [
/* config.module.rule('scss|component').use('raw-loader') */
{
loader: 'raw-loader'
},
/* config.module.rule('scss|component').use('postcss-loader') */
{
loader: 'postcss-loader',
options: {
postcssOptions: {
plugins: [
'postcss-import'
]
}
}
},
/* config.module.rule('scss|component').use('sass-loader') */
{
loader: 'sass-loader'
}
]
}
]
},
optimization: {
splitChunks: {
cacheGroups: {
defaultVendor: {
test: /[\\\\\\\\/]node_modules[\\\\\\\\/]/,
priority: -10,
name: 'vendor',
chunks: 'all'
}
}
},
minimizer: [
/* config.optimization.minimizer('TerserPlugin') */
new TerserPlugin(
{
terserOptions: {
compress: {
collapse_vars: true,
sequences: true,
keep_infinity: true,
drop_console: false,
global_defs: {
__UGLIFIED__: true
}
},
keep_fnames: true,
keep_classnames: true
}
}
)
]
},
plugins: [
/* config.plugin('PlatformSuffixPlugin') */
new PlatformSuffixPlugin(
{
platform: 'ios'
}
),
/* config.plugin('ContextExclusionPlugin|App_Resources') */
new ContextExclusionPlugin(
/(.*)App_Resources(.*)/
),
/* config.plugin('DefinePlugin') */
new DefinePlugin(
{
__DEV__: true,
__NS_WEBPACK__: true,
__NS_ENV_VERBOSE__: false,
__NS_DEV_HOST_IPS__: '[\\"127.0.0.1\\",\\"192.168.0.10\\"]',
__CSS_PARSER__: '\\"css-tree\\"',
__ANDROID__: false,
__IOS__: true,
'global.isAndroid': false,
'global.isIOS': true,
process: 'global.process'
}
),
/* config.plugin('CopyWebpackPlugin') */
new CopyPlugin(
{
patterns: [
{
from: 'assets/**',
context: '__jest__/src',
noErrorOnMissing: true,
globOptions: {
dot: false,
ignore: []
}
},
{
from: 'fonts/**',
context: '__jest__/src',
noErrorOnMissing: true,
globOptions: {
dot: false,
ignore: []
}
},
{
from: '**/*.+(jpg|png)',
context: '__jest__/src',
noErrorOnMissing: true,
globOptions: {
dot: false,
ignore: []
}
}
]
}
),
/* config.plugin('WatchStatePlugin') */
new WatchStatePlugin(),
/* config.plugin('AngularCompilerPlugin') */
new AngularCompilerPlugin(
{
tsConfigPath: '__jest__/tsconfig.json',
mainPath: '__jest__/src/app.js',
platformTransformers: [
function () { /* omitted long function */ }
]
}
)
],
entry: {
bundle: [
'@nativescript/core/globals/index.js',
'__jest__/src/app.js'
],
'tns_modules/inspector_modules': [
'@nativescript/core/inspector_modules'
]
}
}"
`;

View File

@ -0,0 +1,604 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`base configuration for android 1`] = `
"{
mode: 'development',
externals: [
'package.json',
'~/package.json'
],
devtool: 'inline-source-map',
target: 'node',
watchOptions: {
ignored: [
'__jest__/platforms/**',
'__jest__/App_Resources/**'
]
},
ignoreWarnings: [
/System.import\\\\(\\\\) is deprecated/
],
output: {
path: '__jest__/platforms/android/app/src/main/assets/app',
pathinfo: false,
publicPath: '',
libraryTarget: 'commonjs',
globalObject: 'global',
clean: true
},
resolve: {
symlinks: true,
alias: {
'~': '__jest__/src',
'@': '__jest__/src'
},
extensions: [
'.android.ts',
'.ts',
'.android.js',
'.js',
'.android.css',
'.css',
'.android.scss',
'.scss',
'.android.json',
'.json'
],
modules: [
'__jest__/node_modules',
'node_modules'
]
},
resolveLoader: {
modules: [
'__jest__/node_modules/@nativescript/webpack/dist/loaders',
'__jest__/node_modules',
'node_modules'
]
},
module: {
rules: [
/* config.module.rule('bundle') */
{
enforce: 'post',
test: '__jest__/src/app.js',
use: [
/* config.module.rule('bundle').use('app-css-loader') */
{
loader: 'app-css-loader',
options: {
platform: 'android'
}
},
/* config.module.rule('bundle').use('nativescript-hot-loader') */
{
loader: 'nativescript-hot-loader',
options: {
injectHMRRuntime: true
}
}
]
},
/* config.module.rule('ts') */
{
test: [
/\\\\.ts$/
],
use: [
/* config.module.rule('ts').use('ts-loader') */
{
loader: 'ts-loader',
options: {
transpileOnly: true,
allowTsInNodeModules: true,
compilerOptions: {
sourceMap: true,
declaration: false
},
getCustomTransformers: function () { /* omitted long function */ }
}
}
]
},
/* config.module.rule('js') */
{
test: /\\\\.js$/,
exclude: [
/node_modules/
],
use: [
/* config.module.rule('js').use('babel-loader') */
{
loader: 'babel-loader',
options: {
generatorOpts: {
compact: false
}
}
}
]
},
/* config.module.rule('workers') */
{
test: /\\\\.(js|ts)$/,
exclude: [
/node_modules/
],
use: [
/* config.module.rule('workers').use('nativescript-worker-loader') */
{
loader: 'nativescript-worker-loader'
}
]
},
/* config.module.rule('css') */
{
test: /\\\\.css$/,
use: [
/* config.module.rule('css').use('apply-css-loader') */
{
loader: 'apply-css-loader'
},
/* config.module.rule('css').use('css2json-loader') */
{
loader: 'css2json-loader'
},
/* config.module.rule('css').use('postcss-loader') */
{
loader: 'postcss-loader',
options: {
postcssOptions: {
plugins: [
'postcss-import'
]
}
}
}
]
},
/* config.module.rule('scss') */
{
test: /\\\\.scss$/,
use: [
/* config.module.rule('scss').use('apply-css-loader') */
{
loader: 'apply-css-loader'
},
/* config.module.rule('scss').use('css2json-loader') */
{
loader: 'css2json-loader'
},
/* config.module.rule('scss').use('postcss-loader') */
{
loader: 'postcss-loader',
options: {
postcssOptions: {
plugins: [
'postcss-import'
]
}
}
},
/* config.module.rule('scss').use('sass-loader') */
{
loader: 'sass-loader'
}
]
}
]
},
optimization: {
splitChunks: {
cacheGroups: {
defaultVendor: {
test: /[\\\\\\\\/]node_modules[\\\\\\\\/]/,
priority: -10,
name: 'vendor',
chunks: 'all'
}
}
},
minimizer: [
/* config.optimization.minimizer('TerserPlugin') */
new TerserPlugin(
{
terserOptions: {
compress: {
collapse_vars: false,
sequences: false,
keep_infinity: true,
drop_console: false,
global_defs: {
__UGLIFIED__: true
}
},
keep_fnames: true,
keep_classnames: true
}
}
)
]
},
plugins: [
/* config.plugin('ForkTsCheckerWebpackPlugin') */
new ForkTsCheckerWebpackPlugin(
{
typescript: {
memoryLimit: 4096
}
}
),
/* config.plugin('PlatformSuffixPlugin') */
new PlatformSuffixPlugin(
{
platform: 'android'
}
),
/* config.plugin('ContextExclusionPlugin|App_Resources') */
new ContextExclusionPlugin(
/(.*)App_Resources(.*)/
),
/* config.plugin('DefinePlugin') */
new DefinePlugin(
{
__DEV__: true,
__NS_WEBPACK__: true,
__NS_ENV_VERBOSE__: false,
__NS_DEV_HOST_IPS__: '[\\"127.0.0.1\\",\\"192.168.0.10\\"]',
__CSS_PARSER__: '\\"css-tree\\"',
__ANDROID__: true,
__IOS__: false,
'global.isAndroid': true,
'global.isIOS': false,
process: 'global.process'
}
),
/* config.plugin('CopyWebpackPlugin') */
new CopyPlugin(
{
patterns: [
{
from: 'assets/**',
context: '__jest__/src',
noErrorOnMissing: true,
globOptions: {
dot: false,
ignore: []
}
},
{
from: 'fonts/**',
context: '__jest__/src',
noErrorOnMissing: true,
globOptions: {
dot: false,
ignore: []
}
},
{
from: '**/*.+(jpg|png)',
context: '__jest__/src',
noErrorOnMissing: true,
globOptions: {
dot: false,
ignore: []
}
}
]
}
),
/* config.plugin('WatchStatePlugin') */
new WatchStatePlugin()
],
entry: {
bundle: [
'@nativescript/core/globals/index.js',
'__jest__/src/app.js',
'@nativescript/core/ui/frame',
'@nativescript/core/ui/frame/activity'
]
}
}"
`;
exports[`base configuration for ios 1`] = `
"{
mode: 'development',
externals: [
'package.json',
'~/package.json'
],
devtool: 'inline-source-map',
target: 'node',
watchOptions: {
ignored: [
'__jest__/platforms/**',
'__jest__/App_Resources/**'
]
},
ignoreWarnings: [
/System.import\\\\(\\\\) is deprecated/
],
output: {
path: '__jest__/platforms/ios/jest/app',
pathinfo: false,
publicPath: '',
libraryTarget: 'commonjs',
globalObject: 'global',
clean: true
},
resolve: {
symlinks: true,
alias: {
'~': '__jest__/src',
'@': '__jest__/src'
},
extensions: [
'.ios.ts',
'.ts',
'.ios.js',
'.js',
'.ios.css',
'.css',
'.ios.scss',
'.scss',
'.ios.json',
'.json'
],
modules: [
'__jest__/node_modules',
'node_modules'
]
},
resolveLoader: {
modules: [
'__jest__/node_modules/@nativescript/webpack/dist/loaders',
'__jest__/node_modules',
'node_modules'
]
},
module: {
rules: [
/* config.module.rule('bundle') */
{
enforce: 'post',
test: '__jest__/src/app.js',
use: [
/* config.module.rule('bundle').use('app-css-loader') */
{
loader: 'app-css-loader',
options: {
platform: 'ios'
}
},
/* config.module.rule('bundle').use('nativescript-hot-loader') */
{
loader: 'nativescript-hot-loader',
options: {
injectHMRRuntime: true
}
}
]
},
/* config.module.rule('ts') */
{
test: [
/\\\\.ts$/
],
use: [
/* config.module.rule('ts').use('ts-loader') */
{
loader: 'ts-loader',
options: {
transpileOnly: true,
allowTsInNodeModules: true,
compilerOptions: {
sourceMap: true,
declaration: false
},
getCustomTransformers: function () { /* omitted long function */ }
}
}
]
},
/* config.module.rule('js') */
{
test: /\\\\.js$/,
exclude: [
/node_modules/
],
use: [
/* config.module.rule('js').use('babel-loader') */
{
loader: 'babel-loader',
options: {
generatorOpts: {
compact: false
}
}
}
]
},
/* config.module.rule('workers') */
{
test: /\\\\.(js|ts)$/,
exclude: [
/node_modules/
],
use: [
/* config.module.rule('workers').use('nativescript-worker-loader') */
{
loader: 'nativescript-worker-loader'
}
]
},
/* config.module.rule('css') */
{
test: /\\\\.css$/,
use: [
/* config.module.rule('css').use('apply-css-loader') */
{
loader: 'apply-css-loader'
},
/* config.module.rule('css').use('css2json-loader') */
{
loader: 'css2json-loader'
},
/* config.module.rule('css').use('postcss-loader') */
{
loader: 'postcss-loader',
options: {
postcssOptions: {
plugins: [
'postcss-import'
]
}
}
}
]
},
/* config.module.rule('scss') */
{
test: /\\\\.scss$/,
use: [
/* config.module.rule('scss').use('apply-css-loader') */
{
loader: 'apply-css-loader'
},
/* config.module.rule('scss').use('css2json-loader') */
{
loader: 'css2json-loader'
},
/* config.module.rule('scss').use('postcss-loader') */
{
loader: 'postcss-loader',
options: {
postcssOptions: {
plugins: [
'postcss-import'
]
}
}
},
/* config.module.rule('scss').use('sass-loader') */
{
loader: 'sass-loader'
}
]
}
]
},
optimization: {
splitChunks: {
cacheGroups: {
defaultVendor: {
test: /[\\\\\\\\/]node_modules[\\\\\\\\/]/,
priority: -10,
name: 'vendor',
chunks: 'all'
}
}
},
minimizer: [
/* config.optimization.minimizer('TerserPlugin') */
new TerserPlugin(
{
terserOptions: {
compress: {
collapse_vars: true,
sequences: true,
keep_infinity: true,
drop_console: false,
global_defs: {
__UGLIFIED__: true
}
},
keep_fnames: true,
keep_classnames: true
}
}
)
]
},
plugins: [
/* config.plugin('ForkTsCheckerWebpackPlugin') */
new ForkTsCheckerWebpackPlugin(
{
typescript: {
memoryLimit: 4096
}
}
),
/* config.plugin('PlatformSuffixPlugin') */
new PlatformSuffixPlugin(
{
platform: 'ios'
}
),
/* config.plugin('ContextExclusionPlugin|App_Resources') */
new ContextExclusionPlugin(
/(.*)App_Resources(.*)/
),
/* config.plugin('DefinePlugin') */
new DefinePlugin(
{
__DEV__: true,
__NS_WEBPACK__: true,
__NS_ENV_VERBOSE__: false,
__NS_DEV_HOST_IPS__: '[\\"127.0.0.1\\",\\"192.168.0.10\\"]',
__CSS_PARSER__: '\\"css-tree\\"',
__ANDROID__: false,
__IOS__: true,
'global.isAndroid': false,
'global.isIOS': true,
process: 'global.process'
}
),
/* config.plugin('CopyWebpackPlugin') */
new CopyPlugin(
{
patterns: [
{
from: 'assets/**',
context: '__jest__/src',
noErrorOnMissing: true,
globOptions: {
dot: false,
ignore: []
}
},
{
from: 'fonts/**',
context: '__jest__/src',
noErrorOnMissing: true,
globOptions: {
dot: false,
ignore: []
}
},
{
from: '**/*.+(jpg|png)',
context: '__jest__/src',
noErrorOnMissing: true,
globOptions: {
dot: false,
ignore: []
}
}
]
}
),
/* config.plugin('WatchStatePlugin') */
new WatchStatePlugin()
],
entry: {
bundle: [
'@nativescript/core/globals/index.js',
'__jest__/src/app.js'
],
'tns_modules/inspector_modules': [
'@nativescript/core/inspector_modules'
]
}
}"
`;

View File

@ -0,0 +1,680 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`javascript configuration for android 1`] = `
"{
mode: 'development',
externals: [
'package.json',
'~/package.json'
],
devtool: 'inline-source-map',
target: 'node',
watchOptions: {
ignored: [
'__jest__/platforms/**',
'__jest__/App_Resources/**'
]
},
ignoreWarnings: [
/System.import\\\\(\\\\) is deprecated/
],
output: {
path: '__jest__/platforms/android/app/src/main/assets/app',
pathinfo: false,
publicPath: '',
libraryTarget: 'commonjs',
globalObject: 'global',
clean: true
},
resolve: {
symlinks: true,
alias: {
'~': '__jest__/src',
'@': '__jest__/src'
},
extensions: [
'.android.ts',
'.ts',
'.android.js',
'.js',
'.android.css',
'.css',
'.android.scss',
'.scss',
'.android.json',
'.json'
],
modules: [
'__jest__/node_modules',
'node_modules'
]
},
resolveLoader: {
modules: [
'__jest__/node_modules/@nativescript/webpack/dist/loaders',
'__jest__/node_modules',
'node_modules'
]
},
module: {
rules: [
/* config.module.rule('bundle') */
{
enforce: 'post',
test: '__jest__/src/app.js',
use: [
/* config.module.rule('bundle').use('app-css-loader') */
{
loader: 'app-css-loader',
options: {
platform: 'android'
}
},
/* config.module.rule('bundle').use('nativescript-hot-loader') */
{
loader: 'nativescript-hot-loader',
options: {
injectHMRRuntime: true
}
}
]
},
/* config.module.rule('ts') */
{
test: [
/\\\\.ts$/
],
use: [
/* config.module.rule('ts').use('ts-loader') */
{
loader: 'ts-loader',
options: {
transpileOnly: true,
allowTsInNodeModules: true,
compilerOptions: {
sourceMap: true,
declaration: false
},
getCustomTransformers: function () { /* omitted long function */ }
}
}
]
},
/* config.module.rule('js') */
{
test: /\\\\.js$/,
exclude: [
/node_modules/
],
use: [
/* config.module.rule('js').use('babel-loader') */
{
loader: 'babel-loader',
options: {
generatorOpts: {
compact: false
}
}
}
]
},
/* config.module.rule('workers') */
{
test: /\\\\.(js|ts)$/,
exclude: [
/node_modules/
],
use: [
/* config.module.rule('workers').use('nativescript-worker-loader') */
{
loader: 'nativescript-worker-loader'
}
]
},
/* config.module.rule('css') */
{
test: /\\\\.css$/,
use: [
/* config.module.rule('css').use('apply-css-loader') */
{
loader: 'apply-css-loader'
},
/* config.module.rule('css').use('css2json-loader') */
{
loader: 'css2json-loader'
},
/* config.module.rule('css').use('postcss-loader') */
{
loader: 'postcss-loader',
options: {
postcssOptions: {
plugins: [
'postcss-import'
]
}
}
}
]
},
/* config.module.rule('scss') */
{
test: /\\\\.scss$/,
use: [
/* config.module.rule('scss').use('apply-css-loader') */
{
loader: 'apply-css-loader'
},
/* config.module.rule('scss').use('css2json-loader') */
{
loader: 'css2json-loader'
},
/* config.module.rule('scss').use('postcss-loader') */
{
loader: 'postcss-loader',
options: {
postcssOptions: {
plugins: [
'postcss-import'
]
}
}
},
/* config.module.rule('scss').use('sass-loader') */
{
loader: 'sass-loader'
}
]
},
/* config.module.rule('xml') */
{
test: /\\\\.xml$/,
use: [
/* config.module.rule('xml').use('xml-namespace-loader') */
{
loader: 'xml-namespace-loader'
}
]
},
/* config.module.rule('hmr-core') */
{
test: /\\\\.js$/,
exclude: [
/node_modules/,
'__jest__/src/app.js'
],
use: [
/* config.module.rule('hmr-core').use('nativescript-hot-loader') */
{
loader: 'nativescript-hot-loader',
options: {
appPath: '__jest__/src'
}
}
]
}
]
},
optimization: {
splitChunks: {
cacheGroups: {
defaultVendor: {
test: /[\\\\\\\\/]node_modules[\\\\\\\\/]/,
priority: -10,
name: 'vendor',
chunks: 'all'
}
}
},
minimizer: [
/* config.optimization.minimizer('TerserPlugin') */
new TerserPlugin(
{
terserOptions: {
compress: {
collapse_vars: false,
sequences: false,
keep_infinity: true,
drop_console: false,
global_defs: {
__UGLIFIED__: true
}
},
keep_fnames: true,
keep_classnames: true
}
}
)
]
},
plugins: [
/* config.plugin('ForkTsCheckerWebpackPlugin') */
new ForkTsCheckerWebpackPlugin(
{
typescript: {
memoryLimit: 4096
}
}
),
/* config.plugin('PlatformSuffixPlugin') */
new PlatformSuffixPlugin(
{
platform: 'android'
}
),
/* config.plugin('ContextExclusionPlugin|App_Resources') */
new ContextExclusionPlugin(
/(.*)App_Resources(.*)/
),
/* config.plugin('DefinePlugin') */
new DefinePlugin(
{
__DEV__: true,
__NS_WEBPACK__: true,
__NS_ENV_VERBOSE__: false,
__NS_DEV_HOST_IPS__: '[\\"127.0.0.1\\",\\"192.168.0.10\\"]',
__CSS_PARSER__: '\\"css-tree\\"',
__ANDROID__: true,
__IOS__: false,
'global.isAndroid': true,
'global.isIOS': false,
process: 'global.process'
}
),
/* config.plugin('CopyWebpackPlugin') */
new CopyPlugin(
{
patterns: [
{
from: 'assets/**',
context: '__jest__/src',
noErrorOnMissing: true,
globOptions: {
dot: false,
ignore: []
}
},
{
from: 'fonts/**',
context: '__jest__/src',
noErrorOnMissing: true,
globOptions: {
dot: false,
ignore: []
}
},
{
from: '**/*.+(jpg|png)',
context: '__jest__/src',
noErrorOnMissing: true,
globOptions: {
dot: false,
ignore: []
}
}
]
}
),
/* config.plugin('WatchStatePlugin') */
new WatchStatePlugin(),
/* config.plugin('ContextExclusionPlugin|__@nativescript_webpack_virtual_entry_javascript__') */
new ContextExclusionPlugin(
/__@nativescript_webpack_virtual_entry_javascript__.js$/
),
/* config.plugin('VirtualModulesPlugin') */
new VirtualModulesPlugin(
{
'__jest__/src/__@nativescript_webpack_virtual_entry_javascript__': '// VIRTUAL ENTRY START\\\\nrequire(\\\\'@nativescript/core/bundle-entry-points\\\\')\\\\nconst context = require.context(\\"~/\\", /* deep: */ true, /* filter: */ /.(xml|js|s?css)$/);\\\\nglobal.registerWebpackModules(context);\\\\n// VIRTUAL ENTRY END'
}
)
],
entry: {
bundle: [
'@nativescript/core/globals/index.js',
'__jest__/src/app.js',
'@nativescript/core/ui/frame',
'@nativescript/core/ui/frame/activity',
'__jest__/src/__@nativescript_webpack_virtual_entry_javascript__'
]
}
}"
`;
exports[`javascript configuration for ios 1`] = `
"{
mode: 'development',
externals: [
'package.json',
'~/package.json'
],
devtool: 'inline-source-map',
target: 'node',
watchOptions: {
ignored: [
'__jest__/platforms/**',
'__jest__/App_Resources/**'
]
},
ignoreWarnings: [
/System.import\\\\(\\\\) is deprecated/
],
output: {
path: '__jest__/platforms/ios/jest/app',
pathinfo: false,
publicPath: '',
libraryTarget: 'commonjs',
globalObject: 'global',
clean: true
},
resolve: {
symlinks: true,
alias: {
'~': '__jest__/src',
'@': '__jest__/src'
},
extensions: [
'.ios.ts',
'.ts',
'.ios.js',
'.js',
'.ios.css',
'.css',
'.ios.scss',
'.scss',
'.ios.json',
'.json'
],
modules: [
'__jest__/node_modules',
'node_modules'
]
},
resolveLoader: {
modules: [
'__jest__/node_modules/@nativescript/webpack/dist/loaders',
'__jest__/node_modules',
'node_modules'
]
},
module: {
rules: [
/* config.module.rule('bundle') */
{
enforce: 'post',
test: '__jest__/src/app.js',
use: [
/* config.module.rule('bundle').use('app-css-loader') */
{
loader: 'app-css-loader',
options: {
platform: 'ios'
}
},
/* config.module.rule('bundle').use('nativescript-hot-loader') */
{
loader: 'nativescript-hot-loader',
options: {
injectHMRRuntime: true
}
}
]
},
/* config.module.rule('ts') */
{
test: [
/\\\\.ts$/
],
use: [
/* config.module.rule('ts').use('ts-loader') */
{
loader: 'ts-loader',
options: {
transpileOnly: true,
allowTsInNodeModules: true,
compilerOptions: {
sourceMap: true,
declaration: false
},
getCustomTransformers: function () { /* omitted long function */ }
}
}
]
},
/* config.module.rule('js') */
{
test: /\\\\.js$/,
exclude: [
/node_modules/
],
use: [
/* config.module.rule('js').use('babel-loader') */
{
loader: 'babel-loader',
options: {
generatorOpts: {
compact: false
}
}
}
]
},
/* config.module.rule('workers') */
{
test: /\\\\.(js|ts)$/,
exclude: [
/node_modules/
],
use: [
/* config.module.rule('workers').use('nativescript-worker-loader') */
{
loader: 'nativescript-worker-loader'
}
]
},
/* config.module.rule('css') */
{
test: /\\\\.css$/,
use: [
/* config.module.rule('css').use('apply-css-loader') */
{
loader: 'apply-css-loader'
},
/* config.module.rule('css').use('css2json-loader') */
{
loader: 'css2json-loader'
},
/* config.module.rule('css').use('postcss-loader') */
{
loader: 'postcss-loader',
options: {
postcssOptions: {
plugins: [
'postcss-import'
]
}
}
}
]
},
/* config.module.rule('scss') */
{
test: /\\\\.scss$/,
use: [
/* config.module.rule('scss').use('apply-css-loader') */
{
loader: 'apply-css-loader'
},
/* config.module.rule('scss').use('css2json-loader') */
{
loader: 'css2json-loader'
},
/* config.module.rule('scss').use('postcss-loader') */
{
loader: 'postcss-loader',
options: {
postcssOptions: {
plugins: [
'postcss-import'
]
}
}
},
/* config.module.rule('scss').use('sass-loader') */
{
loader: 'sass-loader'
}
]
},
/* config.module.rule('xml') */
{
test: /\\\\.xml$/,
use: [
/* config.module.rule('xml').use('xml-namespace-loader') */
{
loader: 'xml-namespace-loader'
}
]
},
/* config.module.rule('hmr-core') */
{
test: /\\\\.js$/,
exclude: [
/node_modules/,
'__jest__/src/app.js'
],
use: [
/* config.module.rule('hmr-core').use('nativescript-hot-loader') */
{
loader: 'nativescript-hot-loader',
options: {
appPath: '__jest__/src'
}
}
]
}
]
},
optimization: {
splitChunks: {
cacheGroups: {
defaultVendor: {
test: /[\\\\\\\\/]node_modules[\\\\\\\\/]/,
priority: -10,
name: 'vendor',
chunks: 'all'
}
}
},
minimizer: [
/* config.optimization.minimizer('TerserPlugin') */
new TerserPlugin(
{
terserOptions: {
compress: {
collapse_vars: true,
sequences: true,
keep_infinity: true,
drop_console: false,
global_defs: {
__UGLIFIED__: true
}
},
keep_fnames: true,
keep_classnames: true
}
}
)
]
},
plugins: [
/* config.plugin('ForkTsCheckerWebpackPlugin') */
new ForkTsCheckerWebpackPlugin(
{
typescript: {
memoryLimit: 4096
}
}
),
/* config.plugin('PlatformSuffixPlugin') */
new PlatformSuffixPlugin(
{
platform: 'ios'
}
),
/* config.plugin('ContextExclusionPlugin|App_Resources') */
new ContextExclusionPlugin(
/(.*)App_Resources(.*)/
),
/* config.plugin('DefinePlugin') */
new DefinePlugin(
{
__DEV__: true,
__NS_WEBPACK__: true,
__NS_ENV_VERBOSE__: false,
__NS_DEV_HOST_IPS__: '[\\"127.0.0.1\\",\\"192.168.0.10\\"]',
__CSS_PARSER__: '\\"css-tree\\"',
__ANDROID__: false,
__IOS__: true,
'global.isAndroid': false,
'global.isIOS': true,
process: 'global.process'
}
),
/* config.plugin('CopyWebpackPlugin') */
new CopyPlugin(
{
patterns: [
{
from: 'assets/**',
context: '__jest__/src',
noErrorOnMissing: true,
globOptions: {
dot: false,
ignore: []
}
},
{
from: 'fonts/**',
context: '__jest__/src',
noErrorOnMissing: true,
globOptions: {
dot: false,
ignore: []
}
},
{
from: '**/*.+(jpg|png)',
context: '__jest__/src',
noErrorOnMissing: true,
globOptions: {
dot: false,
ignore: []
}
}
]
}
),
/* config.plugin('WatchStatePlugin') */
new WatchStatePlugin(),
/* config.plugin('ContextExclusionPlugin|__@nativescript_webpack_virtual_entry_javascript__') */
new ContextExclusionPlugin(
/__@nativescript_webpack_virtual_entry_javascript__.js$/
),
/* config.plugin('VirtualModulesPlugin') */
new VirtualModulesPlugin(
{
'__jest__/src/__@nativescript_webpack_virtual_entry_javascript__': '// VIRTUAL ENTRY START\\\\nrequire(\\\\'@nativescript/core/bundle-entry-points\\\\')\\\\nconst context = require.context(\\"~/\\", /* deep: */ true, /* filter: */ /.(xml|js|s?css)$/);\\\\nglobal.registerWebpackModules(context);\\\\n// VIRTUAL ENTRY END'
}
)
],
entry: {
bundle: [
'@nativescript/core/globals/index.js',
'__jest__/src/app.js',
'__jest__/src/__@nativescript_webpack_virtual_entry_javascript__'
],
'tns_modules/inspector_modules': [
'@nativescript/core/inspector_modules'
]
}
}"
`;

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,654 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`svelte configuration for android 1`] = `
"{
mode: 'development',
externals: [
'package.json',
'~/package.json'
],
devtool: 'inline-source-map',
target: 'node',
watchOptions: {
ignored: [
'__jest__/platforms/**',
'__jest__/App_Resources/**'
]
},
ignoreWarnings: [
/System.import\\\\(\\\\) is deprecated/
],
output: {
path: '__jest__/platforms/android/app/src/main/assets/app',
pathinfo: false,
publicPath: '',
libraryTarget: 'commonjs',
globalObject: 'global',
clean: true
},
resolve: {
symlinks: true,
alias: {
'~': '__jest__/src',
'@': '__jest__/src'
},
extensions: [
'.android.svelte',
'.svelte',
'.android.ts',
'.ts',
'.android.js',
'.js',
'.android.css',
'.css',
'.android.scss',
'.scss',
'.android.json',
'.json'
],
modules: [
'__jest__/node_modules',
'node_modules'
]
},
resolveLoader: {
modules: [
'__jest__/node_modules/@nativescript/webpack/dist/loaders',
'__jest__/node_modules',
'node_modules'
]
},
module: {
rules: [
/* config.module.rule('bundle') */
{
enforce: 'post',
test: '__jest__/src/app.js',
use: [
/* config.module.rule('bundle').use('app-css-loader') */
{
loader: 'app-css-loader',
options: {
platform: 'android'
}
},
/* config.module.rule('bundle').use('nativescript-hot-loader') */
{
loader: 'nativescript-hot-loader',
options: {
injectHMRRuntime: true
}
}
]
},
/* config.module.rule('ts') */
{
test: [
/\\\\.ts$/
],
use: [
/* config.module.rule('ts').use('ts-loader') */
{
loader: 'ts-loader',
options: {
transpileOnly: true,
allowTsInNodeModules: true,
compilerOptions: {
sourceMap: true,
declaration: false
},
getCustomTransformers: function () { /* omitted long function */ }
}
}
]
},
/* config.module.rule('js') */
{
test: /\\\\.js$/,
exclude: [
/node_modules/
],
use: [
/* config.module.rule('js').use('babel-loader') */
{
loader: 'babel-loader',
options: {
generatorOpts: {
compact: false
}
}
}
]
},
/* config.module.rule('workers') */
{
test: /\\\\.(js|ts)$/,
exclude: [
/node_modules/
],
use: [
/* config.module.rule('workers').use('nativescript-worker-loader') */
{
loader: 'nativescript-worker-loader'
}
]
},
/* config.module.rule('css') */
{
test: /\\\\.css$/,
use: [
/* config.module.rule('css').use('apply-css-loader') */
{
loader: 'apply-css-loader'
},
/* config.module.rule('css').use('css2json-loader') */
{
loader: 'css2json-loader'
},
/* config.module.rule('css').use('postcss-loader') */
{
loader: 'postcss-loader',
options: {
postcssOptions: {
plugins: [
'postcss-import'
]
}
}
}
]
},
/* config.module.rule('scss') */
{
test: /\\\\.scss$/,
use: [
/* config.module.rule('scss').use('apply-css-loader') */
{
loader: 'apply-css-loader'
},
/* config.module.rule('scss').use('css2json-loader') */
{
loader: 'css2json-loader'
},
/* config.module.rule('scss').use('postcss-loader') */
{
loader: 'postcss-loader',
options: {
postcssOptions: {
plugins: [
'postcss-import'
]
}
}
},
/* config.module.rule('scss').use('sass-loader') */
{
loader: 'sass-loader'
}
]
},
/* config.module.rule('svelte') */
{
test: /\\\\.svelte$/,
exclude: [
/node_modules/
],
use: [
/* config.module.rule('svelte').use('svelte-loader-hot') */
{
loader: 'svelte-loader-hot',
options: {
dev: true,
preprocess: undefined,
hotReload: true,
hotOptions: {
injectCss: false,
'native': true
},
onwarn: function () { /* omitted long function */ }
}
}
]
}
]
},
optimization: {
splitChunks: {
cacheGroups: {
defaultVendor: {
test: /[\\\\\\\\/]node_modules[\\\\\\\\/]/,
priority: -10,
name: 'vendor',
chunks: 'all'
}
}
},
minimizer: [
/* config.optimization.minimizer('TerserPlugin') */
new TerserPlugin(
{
terserOptions: {
compress: {
collapse_vars: false,
sequences: false,
keep_infinity: true,
drop_console: false,
global_defs: {
__UGLIFIED__: true
}
},
keep_fnames: true,
keep_classnames: true
}
}
)
]
},
plugins: [
/* config.plugin('ForkTsCheckerWebpackPlugin') */
new ForkTsCheckerWebpackPlugin(
{
typescript: {
memoryLimit: 4096
}
}
),
/* config.plugin('PlatformSuffixPlugin') */
new PlatformSuffixPlugin(
{
platform: 'android'
}
),
/* config.plugin('ContextExclusionPlugin|App_Resources') */
new ContextExclusionPlugin(
/(.*)App_Resources(.*)/
),
/* config.plugin('DefinePlugin') */
new DefinePlugin(
{
__DEV__: true,
__NS_WEBPACK__: true,
__NS_ENV_VERBOSE__: false,
__NS_DEV_HOST_IPS__: '[\\"127.0.0.1\\",\\"192.168.0.10\\"]',
__CSS_PARSER__: '\\"css-tree\\"',
__ANDROID__: true,
__IOS__: false,
'global.isAndroid': true,
'global.isIOS': false,
process: 'global.process'
}
),
/* config.plugin('CopyWebpackPlugin') */
new CopyPlugin(
{
patterns: [
{
from: 'assets/**',
context: '__jest__/src',
noErrorOnMissing: true,
globOptions: {
dot: false,
ignore: []
}
},
{
from: 'fonts/**',
context: '__jest__/src',
noErrorOnMissing: true,
globOptions: {
dot: false,
ignore: []
}
},
{
from: '**/*.+(jpg|png)',
context: '__jest__/src',
noErrorOnMissing: true,
globOptions: {
dot: false,
ignore: []
}
}
]
}
),
/* config.plugin('WatchStatePlugin') */
new WatchStatePlugin()
],
entry: {
bundle: [
'@nativescript/core/globals/index.js',
'__jest__/src/app.js',
'@nativescript/core/ui/frame',
'@nativescript/core/ui/frame/activity'
]
}
}"
`;
exports[`svelte configuration for ios 1`] = `
"{
mode: 'development',
externals: [
'package.json',
'~/package.json'
],
devtool: 'inline-source-map',
target: 'node',
watchOptions: {
ignored: [
'__jest__/platforms/**',
'__jest__/App_Resources/**'
]
},
ignoreWarnings: [
/System.import\\\\(\\\\) is deprecated/
],
output: {
path: '__jest__/platforms/ios/jest/app',
pathinfo: false,
publicPath: '',
libraryTarget: 'commonjs',
globalObject: 'global',
clean: true
},
resolve: {
symlinks: true,
alias: {
'~': '__jest__/src',
'@': '__jest__/src'
},
extensions: [
'.ios.svelte',
'.svelte',
'.ios.ts',
'.ts',
'.ios.js',
'.js',
'.ios.css',
'.css',
'.ios.scss',
'.scss',
'.ios.json',
'.json'
],
modules: [
'__jest__/node_modules',
'node_modules'
]
},
resolveLoader: {
modules: [
'__jest__/node_modules/@nativescript/webpack/dist/loaders',
'__jest__/node_modules',
'node_modules'
]
},
module: {
rules: [
/* config.module.rule('bundle') */
{
enforce: 'post',
test: '__jest__/src/app.js',
use: [
/* config.module.rule('bundle').use('app-css-loader') */
{
loader: 'app-css-loader',
options: {
platform: 'ios'
}
},
/* config.module.rule('bundle').use('nativescript-hot-loader') */
{
loader: 'nativescript-hot-loader',
options: {
injectHMRRuntime: true
}
}
]
},
/* config.module.rule('ts') */
{
test: [
/\\\\.ts$/
],
use: [
/* config.module.rule('ts').use('ts-loader') */
{
loader: 'ts-loader',
options: {
transpileOnly: true,
allowTsInNodeModules: true,
compilerOptions: {
sourceMap: true,
declaration: false
},
getCustomTransformers: function () { /* omitted long function */ }
}
}
]
},
/* config.module.rule('js') */
{
test: /\\\\.js$/,
exclude: [
/node_modules/
],
use: [
/* config.module.rule('js').use('babel-loader') */
{
loader: 'babel-loader',
options: {
generatorOpts: {
compact: false
}
}
}
]
},
/* config.module.rule('workers') */
{
test: /\\\\.(js|ts)$/,
exclude: [
/node_modules/
],
use: [
/* config.module.rule('workers').use('nativescript-worker-loader') */
{
loader: 'nativescript-worker-loader'
}
]
},
/* config.module.rule('css') */
{
test: /\\\\.css$/,
use: [
/* config.module.rule('css').use('apply-css-loader') */
{
loader: 'apply-css-loader'
},
/* config.module.rule('css').use('css2json-loader') */
{
loader: 'css2json-loader'
},
/* config.module.rule('css').use('postcss-loader') */
{
loader: 'postcss-loader',
options: {
postcssOptions: {
plugins: [
'postcss-import'
]
}
}
}
]
},
/* config.module.rule('scss') */
{
test: /\\\\.scss$/,
use: [
/* config.module.rule('scss').use('apply-css-loader') */
{
loader: 'apply-css-loader'
},
/* config.module.rule('scss').use('css2json-loader') */
{
loader: 'css2json-loader'
},
/* config.module.rule('scss').use('postcss-loader') */
{
loader: 'postcss-loader',
options: {
postcssOptions: {
plugins: [
'postcss-import'
]
}
}
},
/* config.module.rule('scss').use('sass-loader') */
{
loader: 'sass-loader'
}
]
},
/* config.module.rule('svelte') */
{
test: /\\\\.svelte$/,
exclude: [
/node_modules/
],
use: [
/* config.module.rule('svelte').use('svelte-loader-hot') */
{
loader: 'svelte-loader-hot',
options: {
dev: true,
preprocess: undefined,
hotReload: true,
hotOptions: {
injectCss: false,
'native': true
},
onwarn: function () { /* omitted long function */ }
}
}
]
}
]
},
optimization: {
splitChunks: {
cacheGroups: {
defaultVendor: {
test: /[\\\\\\\\/]node_modules[\\\\\\\\/]/,
priority: -10,
name: 'vendor',
chunks: 'all'
}
}
},
minimizer: [
/* config.optimization.minimizer('TerserPlugin') */
new TerserPlugin(
{
terserOptions: {
compress: {
collapse_vars: true,
sequences: true,
keep_infinity: true,
drop_console: false,
global_defs: {
__UGLIFIED__: true
}
},
keep_fnames: true,
keep_classnames: true
}
}
)
]
},
plugins: [
/* config.plugin('ForkTsCheckerWebpackPlugin') */
new ForkTsCheckerWebpackPlugin(
{
typescript: {
memoryLimit: 4096
}
}
),
/* config.plugin('PlatformSuffixPlugin') */
new PlatformSuffixPlugin(
{
platform: 'ios'
}
),
/* config.plugin('ContextExclusionPlugin|App_Resources') */
new ContextExclusionPlugin(
/(.*)App_Resources(.*)/
),
/* config.plugin('DefinePlugin') */
new DefinePlugin(
{
__DEV__: true,
__NS_WEBPACK__: true,
__NS_ENV_VERBOSE__: false,
__NS_DEV_HOST_IPS__: '[\\"127.0.0.1\\",\\"192.168.0.10\\"]',
__CSS_PARSER__: '\\"css-tree\\"',
__ANDROID__: false,
__IOS__: true,
'global.isAndroid': false,
'global.isIOS': true,
process: 'global.process'
}
),
/* config.plugin('CopyWebpackPlugin') */
new CopyPlugin(
{
patterns: [
{
from: 'assets/**',
context: '__jest__/src',
noErrorOnMissing: true,
globOptions: {
dot: false,
ignore: []
}
},
{
from: 'fonts/**',
context: '__jest__/src',
noErrorOnMissing: true,
globOptions: {
dot: false,
ignore: []
}
},
{
from: '**/*.+(jpg|png)',
context: '__jest__/src',
noErrorOnMissing: true,
globOptions: {
dot: false,
ignore: []
}
}
]
}
),
/* config.plugin('WatchStatePlugin') */
new WatchStatePlugin()
],
entry: {
bundle: [
'@nativescript/core/globals/index.js',
'__jest__/src/app.js'
],
'tns_modules/inspector_modules': [
'@nativescript/core/inspector_modules'
]
}
}"
`;

View File

@ -0,0 +1,680 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`typescript configuration for android 1`] = `
"{
mode: 'development',
externals: [
'package.json',
'~/package.json'
],
devtool: 'inline-source-map',
target: 'node',
watchOptions: {
ignored: [
'__jest__/platforms/**',
'__jest__/App_Resources/**'
]
},
ignoreWarnings: [
/System.import\\\\(\\\\) is deprecated/
],
output: {
path: '__jest__/platforms/android/app/src/main/assets/app',
pathinfo: false,
publicPath: '',
libraryTarget: 'commonjs',
globalObject: 'global',
clean: true
},
resolve: {
symlinks: true,
alias: {
'~': '__jest__/src',
'@': '__jest__/src'
},
extensions: [
'.android.ts',
'.ts',
'.android.js',
'.js',
'.android.css',
'.css',
'.android.scss',
'.scss',
'.android.json',
'.json'
],
modules: [
'__jest__/node_modules',
'node_modules'
]
},
resolveLoader: {
modules: [
'__jest__/node_modules/@nativescript/webpack/dist/loaders',
'__jest__/node_modules',
'node_modules'
]
},
module: {
rules: [
/* config.module.rule('bundle') */
{
enforce: 'post',
test: '__jest__/src/app.js',
use: [
/* config.module.rule('bundle').use('app-css-loader') */
{
loader: 'app-css-loader',
options: {
platform: 'android'
}
},
/* config.module.rule('bundle').use('nativescript-hot-loader') */
{
loader: 'nativescript-hot-loader',
options: {
injectHMRRuntime: true
}
}
]
},
/* config.module.rule('ts') */
{
test: [
/\\\\.ts$/
],
use: [
/* config.module.rule('ts').use('ts-loader') */
{
loader: 'ts-loader',
options: {
transpileOnly: true,
allowTsInNodeModules: true,
compilerOptions: {
sourceMap: true,
declaration: false
},
getCustomTransformers: function () { /* omitted long function */ }
}
}
]
},
/* config.module.rule('js') */
{
test: /\\\\.js$/,
exclude: [
/node_modules/
],
use: [
/* config.module.rule('js').use('babel-loader') */
{
loader: 'babel-loader',
options: {
generatorOpts: {
compact: false
}
}
}
]
},
/* config.module.rule('workers') */
{
test: /\\\\.(js|ts)$/,
exclude: [
/node_modules/
],
use: [
/* config.module.rule('workers').use('nativescript-worker-loader') */
{
loader: 'nativescript-worker-loader'
}
]
},
/* config.module.rule('css') */
{
test: /\\\\.css$/,
use: [
/* config.module.rule('css').use('apply-css-loader') */
{
loader: 'apply-css-loader'
},
/* config.module.rule('css').use('css2json-loader') */
{
loader: 'css2json-loader'
},
/* config.module.rule('css').use('postcss-loader') */
{
loader: 'postcss-loader',
options: {
postcssOptions: {
plugins: [
'postcss-import'
]
}
}
}
]
},
/* config.module.rule('scss') */
{
test: /\\\\.scss$/,
use: [
/* config.module.rule('scss').use('apply-css-loader') */
{
loader: 'apply-css-loader'
},
/* config.module.rule('scss').use('css2json-loader') */
{
loader: 'css2json-loader'
},
/* config.module.rule('scss').use('postcss-loader') */
{
loader: 'postcss-loader',
options: {
postcssOptions: {
plugins: [
'postcss-import'
]
}
}
},
/* config.module.rule('scss').use('sass-loader') */
{
loader: 'sass-loader'
}
]
},
/* config.module.rule('xml') */
{
test: /\\\\.xml$/,
use: [
/* config.module.rule('xml').use('xml-namespace-loader') */
{
loader: 'xml-namespace-loader'
}
]
},
/* config.module.rule('hmr-core') */
{
test: /\\\\.(js|ts)$/,
exclude: [
/node_modules/,
'__jest__/src/app.js'
],
use: [
/* config.module.rule('hmr-core').use('nativescript-hot-loader') */
{
loader: 'nativescript-hot-loader',
options: {
appPath: '__jest__/src'
}
}
]
}
]
},
optimization: {
splitChunks: {
cacheGroups: {
defaultVendor: {
test: /[\\\\\\\\/]node_modules[\\\\\\\\/]/,
priority: -10,
name: 'vendor',
chunks: 'all'
}
}
},
minimizer: [
/* config.optimization.minimizer('TerserPlugin') */
new TerserPlugin(
{
terserOptions: {
compress: {
collapse_vars: false,
sequences: false,
keep_infinity: true,
drop_console: false,
global_defs: {
__UGLIFIED__: true
}
},
keep_fnames: true,
keep_classnames: true
}
}
)
]
},
plugins: [
/* config.plugin('ForkTsCheckerWebpackPlugin') */
new ForkTsCheckerWebpackPlugin(
{
typescript: {
memoryLimit: 4096
}
}
),
/* config.plugin('PlatformSuffixPlugin') */
new PlatformSuffixPlugin(
{
platform: 'android'
}
),
/* config.plugin('ContextExclusionPlugin|App_Resources') */
new ContextExclusionPlugin(
/(.*)App_Resources(.*)/
),
/* config.plugin('DefinePlugin') */
new DefinePlugin(
{
__DEV__: true,
__NS_WEBPACK__: true,
__NS_ENV_VERBOSE__: false,
__NS_DEV_HOST_IPS__: '[\\"127.0.0.1\\",\\"192.168.0.10\\"]',
__CSS_PARSER__: '\\"css-tree\\"',
__ANDROID__: true,
__IOS__: false,
'global.isAndroid': true,
'global.isIOS': false,
process: 'global.process'
}
),
/* config.plugin('CopyWebpackPlugin') */
new CopyPlugin(
{
patterns: [
{
from: 'assets/**',
context: '__jest__/src',
noErrorOnMissing: true,
globOptions: {
dot: false,
ignore: []
}
},
{
from: 'fonts/**',
context: '__jest__/src',
noErrorOnMissing: true,
globOptions: {
dot: false,
ignore: []
}
},
{
from: '**/*.+(jpg|png)',
context: '__jest__/src',
noErrorOnMissing: true,
globOptions: {
dot: false,
ignore: []
}
}
]
}
),
/* config.plugin('WatchStatePlugin') */
new WatchStatePlugin(),
/* config.plugin('ContextExclusionPlugin|__@nativescript_webpack_virtual_entry_typescript__') */
new ContextExclusionPlugin(
/__@nativescript_webpack_virtual_entry_typescript__.js$/
),
/* config.plugin('VirtualModulesPlugin') */
new VirtualModulesPlugin(
{
'__jest__/src/__@nativescript_webpack_virtual_entry_typescript__': '// VIRTUAL ENTRY START\\\\nrequire(\\\\'@nativescript/core/bundle-entry-points\\\\')\\\\nconst context = require.context(\\"~/\\", /* deep: */ true, /* filter: */ /\\\\\\\\.(xml|js|(?<!d\\\\\\\\.)ts|s?css)$/);\\\\nglobal.registerWebpackModules(context);\\\\n// VIRTUAL ENTRY END'
}
)
],
entry: {
bundle: [
'@nativescript/core/globals/index.js',
'__jest__/src/app.js',
'@nativescript/core/ui/frame',
'@nativescript/core/ui/frame/activity',
'__jest__/src/__@nativescript_webpack_virtual_entry_typescript__'
]
}
}"
`;
exports[`typescript configuration for ios 1`] = `
"{
mode: 'development',
externals: [
'package.json',
'~/package.json'
],
devtool: 'inline-source-map',
target: 'node',
watchOptions: {
ignored: [
'__jest__/platforms/**',
'__jest__/App_Resources/**'
]
},
ignoreWarnings: [
/System.import\\\\(\\\\) is deprecated/
],
output: {
path: '__jest__/platforms/ios/jest/app',
pathinfo: false,
publicPath: '',
libraryTarget: 'commonjs',
globalObject: 'global',
clean: true
},
resolve: {
symlinks: true,
alias: {
'~': '__jest__/src',
'@': '__jest__/src'
},
extensions: [
'.ios.ts',
'.ts',
'.ios.js',
'.js',
'.ios.css',
'.css',
'.ios.scss',
'.scss',
'.ios.json',
'.json'
],
modules: [
'__jest__/node_modules',
'node_modules'
]
},
resolveLoader: {
modules: [
'__jest__/node_modules/@nativescript/webpack/dist/loaders',
'__jest__/node_modules',
'node_modules'
]
},
module: {
rules: [
/* config.module.rule('bundle') */
{
enforce: 'post',
test: '__jest__/src/app.js',
use: [
/* config.module.rule('bundle').use('app-css-loader') */
{
loader: 'app-css-loader',
options: {
platform: 'ios'
}
},
/* config.module.rule('bundle').use('nativescript-hot-loader') */
{
loader: 'nativescript-hot-loader',
options: {
injectHMRRuntime: true
}
}
]
},
/* config.module.rule('ts') */
{
test: [
/\\\\.ts$/
],
use: [
/* config.module.rule('ts').use('ts-loader') */
{
loader: 'ts-loader',
options: {
transpileOnly: true,
allowTsInNodeModules: true,
compilerOptions: {
sourceMap: true,
declaration: false
},
getCustomTransformers: function () { /* omitted long function */ }
}
}
]
},
/* config.module.rule('js') */
{
test: /\\\\.js$/,
exclude: [
/node_modules/
],
use: [
/* config.module.rule('js').use('babel-loader') */
{
loader: 'babel-loader',
options: {
generatorOpts: {
compact: false
}
}
}
]
},
/* config.module.rule('workers') */
{
test: /\\\\.(js|ts)$/,
exclude: [
/node_modules/
],
use: [
/* config.module.rule('workers').use('nativescript-worker-loader') */
{
loader: 'nativescript-worker-loader'
}
]
},
/* config.module.rule('css') */
{
test: /\\\\.css$/,
use: [
/* config.module.rule('css').use('apply-css-loader') */
{
loader: 'apply-css-loader'
},
/* config.module.rule('css').use('css2json-loader') */
{
loader: 'css2json-loader'
},
/* config.module.rule('css').use('postcss-loader') */
{
loader: 'postcss-loader',
options: {
postcssOptions: {
plugins: [
'postcss-import'
]
}
}
}
]
},
/* config.module.rule('scss') */
{
test: /\\\\.scss$/,
use: [
/* config.module.rule('scss').use('apply-css-loader') */
{
loader: 'apply-css-loader'
},
/* config.module.rule('scss').use('css2json-loader') */
{
loader: 'css2json-loader'
},
/* config.module.rule('scss').use('postcss-loader') */
{
loader: 'postcss-loader',
options: {
postcssOptions: {
plugins: [
'postcss-import'
]
}
}
},
/* config.module.rule('scss').use('sass-loader') */
{
loader: 'sass-loader'
}
]
},
/* config.module.rule('xml') */
{
test: /\\\\.xml$/,
use: [
/* config.module.rule('xml').use('xml-namespace-loader') */
{
loader: 'xml-namespace-loader'
}
]
},
/* config.module.rule('hmr-core') */
{
test: /\\\\.(js|ts)$/,
exclude: [
/node_modules/,
'__jest__/src/app.js'
],
use: [
/* config.module.rule('hmr-core').use('nativescript-hot-loader') */
{
loader: 'nativescript-hot-loader',
options: {
appPath: '__jest__/src'
}
}
]
}
]
},
optimization: {
splitChunks: {
cacheGroups: {
defaultVendor: {
test: /[\\\\\\\\/]node_modules[\\\\\\\\/]/,
priority: -10,
name: 'vendor',
chunks: 'all'
}
}
},
minimizer: [
/* config.optimization.minimizer('TerserPlugin') */
new TerserPlugin(
{
terserOptions: {
compress: {
collapse_vars: true,
sequences: true,
keep_infinity: true,
drop_console: false,
global_defs: {
__UGLIFIED__: true
}
},
keep_fnames: true,
keep_classnames: true
}
}
)
]
},
plugins: [
/* config.plugin('ForkTsCheckerWebpackPlugin') */
new ForkTsCheckerWebpackPlugin(
{
typescript: {
memoryLimit: 4096
}
}
),
/* config.plugin('PlatformSuffixPlugin') */
new PlatformSuffixPlugin(
{
platform: 'ios'
}
),
/* config.plugin('ContextExclusionPlugin|App_Resources') */
new ContextExclusionPlugin(
/(.*)App_Resources(.*)/
),
/* config.plugin('DefinePlugin') */
new DefinePlugin(
{
__DEV__: true,
__NS_WEBPACK__: true,
__NS_ENV_VERBOSE__: false,
__NS_DEV_HOST_IPS__: '[\\"127.0.0.1\\",\\"192.168.0.10\\"]',
__CSS_PARSER__: '\\"css-tree\\"',
__ANDROID__: false,
__IOS__: true,
'global.isAndroid': false,
'global.isIOS': true,
process: 'global.process'
}
),
/* config.plugin('CopyWebpackPlugin') */
new CopyPlugin(
{
patterns: [
{
from: 'assets/**',
context: '__jest__/src',
noErrorOnMissing: true,
globOptions: {
dot: false,
ignore: []
}
},
{
from: 'fonts/**',
context: '__jest__/src',
noErrorOnMissing: true,
globOptions: {
dot: false,
ignore: []
}
},
{
from: '**/*.+(jpg|png)',
context: '__jest__/src',
noErrorOnMissing: true,
globOptions: {
dot: false,
ignore: []
}
}
]
}
),
/* config.plugin('WatchStatePlugin') */
new WatchStatePlugin(),
/* config.plugin('ContextExclusionPlugin|__@nativescript_webpack_virtual_entry_typescript__') */
new ContextExclusionPlugin(
/__@nativescript_webpack_virtual_entry_typescript__.js$/
),
/* config.plugin('VirtualModulesPlugin') */
new VirtualModulesPlugin(
{
'__jest__/src/__@nativescript_webpack_virtual_entry_typescript__': '// VIRTUAL ENTRY START\\\\nrequire(\\\\'@nativescript/core/bundle-entry-points\\\\')\\\\nconst context = require.context(\\"~/\\", /* deep: */ true, /* filter: */ /\\\\\\\\.(xml|js|(?<!d\\\\\\\\.)ts|s?css)$/);\\\\nglobal.registerWebpackModules(context);\\\\n// VIRTUAL ENTRY END'
}
)
],
entry: {
bundle: [
'@nativescript/core/globals/index.js',
'__jest__/src/app.js',
'__jest__/src/__@nativescript_webpack_virtual_entry_typescript__'
],
'tns_modules/inspector_modules': [
'@nativescript/core/inspector_modules'
]
}
}"
`;

View File

@ -0,0 +1,668 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`vue configuration for android 1`] = `
"{
mode: 'development',
externals: [
'package.json',
'~/package.json'
],
devtool: 'inline-source-map',
target: 'node',
watchOptions: {
ignored: [
'__jest__/platforms/**',
'__jest__/App_Resources/**'
]
},
ignoreWarnings: [
/System.import\\\\(\\\\) is deprecated/
],
output: {
path: '__jest__/platforms/android/app/src/main/assets/app',
pathinfo: false,
publicPath: '',
libraryTarget: 'commonjs',
globalObject: 'global',
clean: true
},
resolve: {
symlinks: true,
alias: {
'~': '__jest__/src',
'@': '__jest__/src',
vue: 'nativescript-vue'
},
extensions: [
'.android.vue',
'.vue',
'.android.ts',
'.ts',
'.android.js',
'.js',
'.android.css',
'.css',
'.android.scss',
'.scss',
'.android.json',
'.json'
],
modules: [
'__jest__/node_modules',
'node_modules'
]
},
resolveLoader: {
modules: [
'__jest__/node_modules/@nativescript/webpack/dist/loaders',
'__jest__/node_modules',
'node_modules'
]
},
module: {
rules: [
/* config.module.rule('bundle') */
{
enforce: 'post',
test: '__jest__/src/app.js',
use: [
/* config.module.rule('bundle').use('app-css-loader') */
{
loader: 'app-css-loader',
options: {
platform: 'android'
}
},
/* config.module.rule('bundle').use('nativescript-hot-loader') */
{
loader: 'nativescript-hot-loader',
options: {
injectHMRRuntime: true
}
}
]
},
/* config.module.rule('ts') */
{
test: [
/\\\\.ts$/
],
use: [
/* config.module.rule('ts').use('ts-loader') */
{
loader: 'ts-loader',
options: {
transpileOnly: true,
allowTsInNodeModules: true,
compilerOptions: {
sourceMap: true,
declaration: false
},
getCustomTransformers: function () { /* omitted long function */ },
appendTsSuffixTo: [
'\\\\\\\\.vue$'
]
}
}
]
},
/* config.module.rule('js') */
{
test: /\\\\.js$/,
exclude: [
/node_modules/
],
use: [
/* config.module.rule('js').use('babel-loader') */
{
loader: 'babel-loader',
options: {
generatorOpts: {
compact: false
}
}
}
]
},
/* config.module.rule('workers') */
{
test: /\\\\.(js|ts)$/,
exclude: [
/node_modules/
],
use: [
/* config.module.rule('workers').use('nativescript-worker-loader') */
{
loader: 'nativescript-worker-loader'
}
]
},
/* config.module.rule('css') */
{
test: /\\\\.css$/,
use: [
/* config.module.rule('css').use('apply-css-loader') */
{
loader: 'apply-css-loader'
},
/* config.module.rule('css').use('css2json-loader') */
{
loader: 'css2json-loader'
},
/* config.module.rule('css').use('postcss-loader') */
{
loader: 'postcss-loader',
options: {
postcssOptions: {
plugins: [
'postcss-import'
]
}
}
}
]
},
/* config.module.rule('scss') */
{
test: /\\\\.scss$/,
use: [
/* config.module.rule('scss').use('apply-css-loader') */
{
loader: 'apply-css-loader'
},
/* config.module.rule('scss').use('css2json-loader') */
{
loader: 'css2json-loader'
},
/* config.module.rule('scss').use('postcss-loader') */
{
loader: 'postcss-loader',
options: {
postcssOptions: {
plugins: [
'postcss-import'
]
}
}
},
/* config.module.rule('scss').use('sass-loader') */
{
loader: 'sass-loader'
}
]
},
/* config.module.rule('vue') */
{
test: /\\\\.vue$/,
use: [
/* config.module.rule('vue').use('vue-loader') */
{
loader: 'vue-loader',
options: {
compiler: {
compile: function () { /* omitted long function */ },
compileToFunctions: function () { /* omitted long function */ },
parseComponent: function () { /* omitted long function */ },
registerElement: function () { /* omitted long function */ }
}
}
}
]
}
]
},
optimization: {
splitChunks: {
cacheGroups: {
defaultVendor: {
test: /[\\\\\\\\/]node_modules[\\\\\\\\/]/,
priority: -10,
name: 'vendor',
chunks: 'all'
}
}
},
minimizer: [
/* config.optimization.minimizer('TerserPlugin') */
new TerserPlugin(
{
terserOptions: {
compress: {
collapse_vars: false,
sequences: false,
keep_infinity: true,
drop_console: false,
global_defs: {
__UGLIFIED__: true
}
},
keep_fnames: true,
keep_classnames: true
}
}
)
]
},
plugins: [
/* config.plugin('VueLoaderPlugin') */
new VueLoaderPlugin(),
/* config.plugin('ForkTsCheckerWebpackPlugin') */
new ForkTsCheckerWebpackPlugin(
{
typescript: {
memoryLimit: 4096,
extensions: {
vue: {
enabled: true,
compiler: 'nativescript-vue-template-compiler'
}
}
}
}
),
/* config.plugin('PlatformSuffixPlugin') */
new PlatformSuffixPlugin(
{
platform: 'android'
}
),
/* config.plugin('ContextExclusionPlugin|App_Resources') */
new ContextExclusionPlugin(
/(.*)App_Resources(.*)/
),
/* config.plugin('DefinePlugin') */
new DefinePlugin(
{
__DEV__: true,
__NS_WEBPACK__: true,
__NS_ENV_VERBOSE__: false,
__NS_DEV_HOST_IPS__: '[\\"127.0.0.1\\",\\"192.168.0.10\\"]',
__CSS_PARSER__: '\\"css-tree\\"',
__ANDROID__: true,
__IOS__: false,
'global.isAndroid': true,
'global.isIOS': false,
process: 'global.process'
}
),
/* config.plugin('CopyWebpackPlugin') */
new CopyPlugin(
{
patterns: [
{
from: 'assets/**',
context: '__jest__/src',
noErrorOnMissing: true,
globOptions: {
dot: false,
ignore: []
}
},
{
from: 'fonts/**',
context: '__jest__/src',
noErrorOnMissing: true,
globOptions: {
dot: false,
ignore: []
}
},
{
from: '**/*.+(jpg|png)',
context: '__jest__/src',
noErrorOnMissing: true,
globOptions: {
dot: false,
ignore: []
}
}
]
}
),
/* config.plugin('WatchStatePlugin') */
new WatchStatePlugin()
],
entry: {
bundle: [
'@nativescript/core/globals/index.js',
'__jest__/src/app.js',
'@nativescript/core/ui/frame',
'@nativescript/core/ui/frame/activity'
]
}
}"
`;
exports[`vue configuration for ios 1`] = `
"{
mode: 'development',
externals: [
'package.json',
'~/package.json'
],
devtool: 'inline-source-map',
target: 'node',
watchOptions: {
ignored: [
'__jest__/platforms/**',
'__jest__/App_Resources/**'
]
},
ignoreWarnings: [
/System.import\\\\(\\\\) is deprecated/
],
output: {
path: '__jest__/platforms/ios/jest/app',
pathinfo: false,
publicPath: '',
libraryTarget: 'commonjs',
globalObject: 'global',
clean: true
},
resolve: {
symlinks: true,
alias: {
'~': '__jest__/src',
'@': '__jest__/src',
vue: 'nativescript-vue'
},
extensions: [
'.ios.vue',
'.vue',
'.ios.ts',
'.ts',
'.ios.js',
'.js',
'.ios.css',
'.css',
'.ios.scss',
'.scss',
'.ios.json',
'.json'
],
modules: [
'__jest__/node_modules',
'node_modules'
]
},
resolveLoader: {
modules: [
'__jest__/node_modules/@nativescript/webpack/dist/loaders',
'__jest__/node_modules',
'node_modules'
]
},
module: {
rules: [
/* config.module.rule('bundle') */
{
enforce: 'post',
test: '__jest__/src/app.js',
use: [
/* config.module.rule('bundle').use('app-css-loader') */
{
loader: 'app-css-loader',
options: {
platform: 'ios'
}
},
/* config.module.rule('bundle').use('nativescript-hot-loader') */
{
loader: 'nativescript-hot-loader',
options: {
injectHMRRuntime: true
}
}
]
},
/* config.module.rule('ts') */
{
test: [
/\\\\.ts$/
],
use: [
/* config.module.rule('ts').use('ts-loader') */
{
loader: 'ts-loader',
options: {
transpileOnly: true,
allowTsInNodeModules: true,
compilerOptions: {
sourceMap: true,
declaration: false
},
getCustomTransformers: function () { /* omitted long function */ },
appendTsSuffixTo: [
'\\\\\\\\.vue$'
]
}
}
]
},
/* config.module.rule('js') */
{
test: /\\\\.js$/,
exclude: [
/node_modules/
],
use: [
/* config.module.rule('js').use('babel-loader') */
{
loader: 'babel-loader',
options: {
generatorOpts: {
compact: false
}
}
}
]
},
/* config.module.rule('workers') */
{
test: /\\\\.(js|ts)$/,
exclude: [
/node_modules/
],
use: [
/* config.module.rule('workers').use('nativescript-worker-loader') */
{
loader: 'nativescript-worker-loader'
}
]
},
/* config.module.rule('css') */
{
test: /\\\\.css$/,
use: [
/* config.module.rule('css').use('apply-css-loader') */
{
loader: 'apply-css-loader'
},
/* config.module.rule('css').use('css2json-loader') */
{
loader: 'css2json-loader'
},
/* config.module.rule('css').use('postcss-loader') */
{
loader: 'postcss-loader',
options: {
postcssOptions: {
plugins: [
'postcss-import'
]
}
}
}
]
},
/* config.module.rule('scss') */
{
test: /\\\\.scss$/,
use: [
/* config.module.rule('scss').use('apply-css-loader') */
{
loader: 'apply-css-loader'
},
/* config.module.rule('scss').use('css2json-loader') */
{
loader: 'css2json-loader'
},
/* config.module.rule('scss').use('postcss-loader') */
{
loader: 'postcss-loader',
options: {
postcssOptions: {
plugins: [
'postcss-import'
]
}
}
},
/* config.module.rule('scss').use('sass-loader') */
{
loader: 'sass-loader'
}
]
},
/* config.module.rule('vue') */
{
test: /\\\\.vue$/,
use: [
/* config.module.rule('vue').use('vue-loader') */
{
loader: 'vue-loader',
options: {
compiler: {
compile: function () { /* omitted long function */ },
compileToFunctions: function () { /* omitted long function */ },
parseComponent: function () { /* omitted long function */ },
registerElement: function () { /* omitted long function */ }
}
}
}
]
}
]
},
optimization: {
splitChunks: {
cacheGroups: {
defaultVendor: {
test: /[\\\\\\\\/]node_modules[\\\\\\\\/]/,
priority: -10,
name: 'vendor',
chunks: 'all'
}
}
},
minimizer: [
/* config.optimization.minimizer('TerserPlugin') */
new TerserPlugin(
{
terserOptions: {
compress: {
collapse_vars: true,
sequences: true,
keep_infinity: true,
drop_console: false,
global_defs: {
__UGLIFIED__: true
}
},
keep_fnames: true,
keep_classnames: true
}
}
)
]
},
plugins: [
/* config.plugin('VueLoaderPlugin') */
new VueLoaderPlugin(),
/* config.plugin('ForkTsCheckerWebpackPlugin') */
new ForkTsCheckerWebpackPlugin(
{
typescript: {
memoryLimit: 4096,
extensions: {
vue: {
enabled: true,
compiler: 'nativescript-vue-template-compiler'
}
}
}
}
),
/* config.plugin('PlatformSuffixPlugin') */
new PlatformSuffixPlugin(
{
platform: 'ios'
}
),
/* config.plugin('ContextExclusionPlugin|App_Resources') */
new ContextExclusionPlugin(
/(.*)App_Resources(.*)/
),
/* config.plugin('DefinePlugin') */
new DefinePlugin(
{
__DEV__: true,
__NS_WEBPACK__: true,
__NS_ENV_VERBOSE__: false,
__NS_DEV_HOST_IPS__: '[\\"127.0.0.1\\",\\"192.168.0.10\\"]',
__CSS_PARSER__: '\\"css-tree\\"',
__ANDROID__: false,
__IOS__: true,
'global.isAndroid': false,
'global.isIOS': true,
process: 'global.process'
}
),
/* config.plugin('CopyWebpackPlugin') */
new CopyPlugin(
{
patterns: [
{
from: 'assets/**',
context: '__jest__/src',
noErrorOnMissing: true,
globOptions: {
dot: false,
ignore: []
}
},
{
from: 'fonts/**',
context: '__jest__/src',
noErrorOnMissing: true,
globOptions: {
dot: false,
ignore: []
}
},
{
from: '**/*.+(jpg|png)',
context: '__jest__/src',
noErrorOnMissing: true,
globOptions: {
dot: false,
ignore: []
}
}
]
}
),
/* config.plugin('WatchStatePlugin') */
new WatchStatePlugin()
],
entry: {
bundle: [
'@nativescript/core/globals/index.js',
'__jest__/src/app.js'
],
'tns_modules/inspector_modules': [
'@nativescript/core/inspector_modules'
]
}
}"
`;

View File

@ -0,0 +1,49 @@
import Config from 'webpack-chain';
import { resolve } from 'path';
import { additionalCopyRules } from '../../src/helpers/copyRules';
import { default as angular } from '../../src/configuration/angular';
import { init } from '../../src';
jest.mock(
'@ngtools/webpack',
() => {
class AngularCompilerPlugin {}
return {
AngularCompilerPlugin,
};
},
{ virtual: true }
);
describe('angular configuration', () => {
const platforms = ['ios', 'android'];
let fsExistsSyncSpy: jest.SpiedFunction<any>;
beforeAll(() => {
const fs = require('fs');
const original = fs.existsSync;
fsExistsSyncSpy = jest.spyOn(fs, 'existsSync');
fsExistsSyncSpy.mockImplementation((path) => {
if (path === '__jest__/tsconfig.json') {
return true;
}
return original.call(fs, path);
});
});
afterAll(() => {
fsExistsSyncSpy.mockRestore();
});
for (let platform of platforms) {
it(`for ${platform}`, () => {
init({
[platform]: true,
});
expect(angular(new Config()).toString()).toMatchSnapshot();
});
}
});

View File

@ -0,0 +1,103 @@
import Config from 'webpack-chain';
import fs from 'fs';
import base from '../../src/configuration/base';
import { init } from '../../src';
import { applyFileReplacements } from "../../src/helpers/fileReplacements";
import { additionalCopyRules } from "../../src/helpers/copyRules";
describe('base configuration', () => {
const platforms = ['ios', 'android'];
for (let platform of platforms) {
it(`for ${platform}`, () => {
init({
[platform]: true,
});
expect(base(new Config()).toString()).toMatchSnapshot();
});
}
it('supports dotenv', () => {
const fsSpy = jest.spyOn(fs, "existsSync")
fsSpy.mockReturnValue(true)
init({
ios: true,
});
const config = base(new Config());
expect(config.plugin('DotEnvPlugin')).toBeDefined();
config.plugin('DotEnvPlugin').tap((args) => {
expect(args[0].path).toEqual('__jest__/.env');
return args;
});
fsSpy.mockRestore()
});
it('supports env specific dotenv', () => {
const fsSpy = jest.spyOn(fs, "existsSync")
fsSpy.mockReturnValue(true)
init({
ios: true,
env: 'prod',
});
const config = base(new Config());
expect(fsSpy).toHaveBeenCalledWith('__jest__/.env.prod')
expect(fsSpy).toHaveBeenCalledTimes(1)
expect(config.plugin('DotEnvPlugin')).toBeDefined();
config.plugin('DotEnvPlugin').tap((args) => {
expect(args[0].path).toEqual('__jest__/.env.prod');
return args;
});
fsSpy.mockRestore()
});
it('falls back to default .env', () => {
const fsSpy = jest.spyOn(fs, "existsSync")
fsSpy
.mockReturnValueOnce(false)
.mockReturnValueOnce(true)
init({
ios: true,
env: 'prod',
});
const config = base(new Config());
expect(fsSpy).toHaveBeenCalledWith('__jest__/.env.prod')
expect(fsSpy).toHaveBeenCalledWith('__jest__/.env')
expect(fsSpy).toHaveBeenCalledTimes(2)
expect(config.plugin('DotEnvPlugin')).toBeDefined();
config.plugin('DotEnvPlugin').tap((args) => {
expect(args[0].path).toEqual('__jest__/.env');
return args;
});
fsSpy.mockRestore()
});
it('applies file replacements', () => {
const config = new Config();
applyFileReplacements(config, {
// should apply as an alias
'foo.ts': 'foo.replaced.ts',
'bar.js': 'bar.replaced.js',
// should apply as a file replacement using the copy plugin
'foo.json': 'foo.replaced.json'
})
expect(config.resolve.alias.get('foo.ts')).toBe('foo.replaced.ts')
expect(config.resolve.alias.get('bar.js')).toBe('bar.replaced.js')
expect(additionalCopyRules.length).toBe(1)
expect(additionalCopyRules[0]).toEqual({
from: 'foo.replaced.json',
to: 'foo.json',
force: true,
})
})
});

View File

@ -0,0 +1,17 @@
import Config from 'webpack-chain';
import javascript from '../../src/configuration/javascript';
import { init } from '../../src';
describe('javascript configuration', () => {
const platforms = ['ios', 'android'];
for (let platform of platforms) {
it(`for ${platform}`, () => {
init({
[platform]: true,
});
expect(javascript(new Config()).toString()).toMatchSnapshot();
});
}
});

View File

@ -0,0 +1,27 @@
import Config from 'webpack-chain';
import react from '../../src/configuration/react';
import { init } from '../../src';
describe('react configuration', () => {
const platforms = ['ios', 'android'];
for (let platform of platforms) {
describe(`> ${platform} >`, () => {
it(`base config`, () => {
init({
[platform]: true,
});
expect(react(new Config()).toString()).toMatchSnapshot();
});
it(`adds ReactRefreshWebpackPlugin when HMR enabled`, () => {
init({
[platform]: true,
hmr: true,
});
expect(react(new Config()).toString()).toMatchSnapshot();
});
});
}
});

View File

@ -0,0 +1,21 @@
import Config from 'webpack-chain';
import svelte from '../../src/configuration/svelte';
import { init } from '../../src';
jest.mock('__jest__/svelte.config.js', () => {
}, { virtual: true })
describe('svelte configuration', () => {
const platforms = ['ios', 'android'];
for (let platform of platforms) {
it(`for ${platform}`, () => {
init({
[platform]: true,
});
expect(svelte(new Config()).toString()).toMatchSnapshot();
});
}
});

View File

@ -0,0 +1,17 @@
import Config from 'webpack-chain';
import typescript from '../../src/configuration/typescript';
import { init } from '../../src';
describe('typescript configuration', () => {
const platforms = ['ios', 'android'];
for (let platform of platforms) {
it(`for ${platform}`, () => {
init({
[platform]: true,
});
expect(typescript(new Config()).toString()).toMatchSnapshot();
});
}
});

View File

@ -0,0 +1,17 @@
import Config from 'webpack-chain';
import vue from '../../src/configuration/vue';
import { init } from '../../src';
describe('vue configuration', () => {
const platforms = ['ios', 'android'];
for (let platform of platforms) {
it(`for ${platform}`, () => {
init({
[platform]: true,
});
expect(vue(new Config()).toString()).toMatchSnapshot();
});
}
});

View File

@ -0,0 +1,86 @@
import { getFileReplacementsFromEnv } from '../../src/helpers/fileReplacements';
describe('getFileReplacementsFromEnv', () => {
it('handles no replacements', () => {
const res = getFileReplacementsFromEnv({});
expect(res).toEqual({});
});
it('ignores invalid env', () => {
const res = getFileReplacementsFromEnv({
// @ts-ignore
replace: {},
});
expect(res).toEqual({});
});
it('resolves replacements relative to the project root', () => {
const res = getFileReplacementsFromEnv({
replace: './src/foo.ts:./src/bar.ts',
});
const entries = Object.entries(res);
expect(res).toBeDefined();
expect(entries.length).toBe(1);
expect(entries[0]).toEqual(['__jest__/src/foo.ts', '__jest__/src/bar.ts']);
});
it('ignores invalid replacements', () => {
const res = getFileReplacementsFromEnv({
replace: ['one', 'two:', 'three:four'],
});
const entries = Object.entries(res);
expect(res).toBeDefined();
expect(entries.length).toBe(1);
expect(entries[0]).toEqual(['__jest__/three', '__jest__/four']);
});
it('can parse replacements from a string', () => {
const res = getFileReplacementsFromEnv({
replace: 'one:two',
});
const entries = Object.entries(res);
expect(res).toBeDefined();
expect(entries.length).toBe(1);
expect(entries[0]).toEqual(['__jest__/one', '__jest__/two']);
});
it('can parse multiple replacements from a string', () => {
const res = getFileReplacementsFromEnv({
replace: 'one:two,three:four',
});
const entries = Object.entries(res);
expect(res).toBeDefined();
expect(entries.length).toBe(2);
expect(entries).toEqual([
['__jest__/one', '__jest__/two'],
['__jest__/three', '__jest__/four'],
]);
});
it('can parse replacements from an array', () => {
const res = getFileReplacementsFromEnv({
replace: ['one:two', 'three:four'],
});
const entries = Object.entries(res);
expect(res).toBeDefined();
expect(entries.length).toBe(2);
expect(entries).toEqual([
['__jest__/one', '__jest__/two'],
['__jest__/three', '__jest__/four'],
]);
});
it('can parse multiple replacements from an array', () => {
const res = getFileReplacementsFromEnv({
replace: ['one:two,three:four', 'five:six'],
});
const entries = Object.entries(res);
expect(res).toBeDefined();
expect(entries.length).toBe(3);
expect(entries).toEqual([
['__jest__/one', '__jest__/two'],
['__jest__/three', '__jest__/four'],
['__jest__/five', '__jest__/six'],
]);
});
});

View File

@ -0,0 +1,141 @@
describe('@nativescript/webpack', () => {
let webpack: typeof import('../src');
beforeEach(() => {
jest.resetModules();
webpack = require('../src');
});
it('exports the public api', () => {
expect(webpack.init).toBeInstanceOf(Function);
expect(webpack.useConfig).toBeInstanceOf(Function);
expect(webpack.chainWebpack).toBeInstanceOf(Function);
expect(webpack.mergeWebpack).toBeInstanceOf(Function);
expect(webpack.resolveChainableConfig).toBeInstanceOf(Function);
expect(webpack.resolveConfig).toBeInstanceOf(Function);
});
it('applies chain configs', () => {
webpack.useConfig(false);
const chainFn = jest.fn();
webpack.chainWebpack(chainFn);
// chainFn should not be called yet
expect(chainFn).not.toHaveBeenCalled();
// chainFn should only be called when
// resolving a chainable config
const config = webpack.resolveChainableConfig();
expect(chainFn).toHaveBeenCalledTimes(1);
expect(chainFn).toHaveBeenCalledWith(config, {});
});
it('applies chain configs in the right order', () => {
webpack.useConfig(false);
let lastCalled = false;
// this is registered before chainFnNormal
// however, should be called after chainFnNormal
const chainFnLast = jest.fn((config) => {
lastCalled = true;
expect(config.normal).toBe(true);
});
webpack.chainWebpack(chainFnLast, { order: 10 });
const chainFnNormal = jest.fn((config) => {
config.normal = true;
// chainFnLast should not have been called yet
expect(lastCalled).toBe(false);
});
webpack.chainWebpack(chainFnNormal);
webpack.resolveChainableConfig();
});
it('prints plugin name that has a chain function that throws an error', () => {
webpack.useConfig(false);
webpack.setCurrentPlugin('test-plugin');
const chainFn = jest.fn(() => {
throw new Error('something wrong');
});
webpack.chainWebpack(chainFn);
// should not throw
expect(() => webpack.resolveChainableConfig()).not.toThrow();
expect(
'Unable to apply chain function from: test-plugin'
).toHaveBeenWarned();
});
it('applies merge configs', () => {
const dummyEnv = { foo: true };
webpack.init(dummyEnv);
webpack.useConfig(false);
const mergeFn = jest.fn();
webpack.mergeWebpack(mergeFn);
// mergeFn should not be called yet
expect(mergeFn).not.toHaveBeenCalled();
const config = webpack.resolveChainableConfig();
// mergeFn should not be called yet
expect(mergeFn).not.toHaveBeenCalled();
// mergeFn should only be called when
// resolving the final config
webpack.resolveConfig();
expect(mergeFn).toHaveBeenCalledTimes(1);
expect(mergeFn).toHaveBeenCalledWith(config.toConfig(), dummyEnv);
});
it('merges mutate config', () => {
const dummyEnv = { foo: true };
webpack.init(dummyEnv);
webpack.useConfig(false);
webpack.mergeWebpack((config) => {
(config as any).mutated = true;
});
expect(webpack.resolveConfig()).toMatchObject({
mutated: true,
});
});
it('merges returned config', () => {
const dummyEnv = { foo: true };
webpack.init(dummyEnv);
webpack.useConfig(false);
webpack.mergeWebpack(() => {
return {
returned: true,
};
});
expect(webpack.resolveConfig()).toMatchObject({
returned: true,
});
});
it('merges objects', () => {
const dummyEnv = { foo: true };
webpack.init(dummyEnv);
webpack.useConfig(false);
webpack.mergeWebpack({
object: true,
} as any);
expect(webpack.resolveConfig()).toMatchObject({
object: true,
});
});
});

View File

@ -0,0 +1,426 @@
import dedent from 'ts-dedent';
import xmlNsLoader from '../../src/loaders/xml-namespace-loader';
const CODE_FILE = dedent`
<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,
{
resolveMap,
expectedDeps,
expectedRegs,
assureNoDeps,
ignore,
expectError,
expectWarnings,
}: TestSetup
) {
const actualDeps: string[] = [];
const actualWarnings: Error[] = [];
let callbackCalled = false;
return {
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 }) => {
expect(source).toContain(dedent`
global.registerModule(
'${name}',
() => require("${path}")
)
`);
});
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 = request.replace(/\\/g, '/');
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);
},
emitError: (err: Error) => {
//actualWarnings.push(err);
},
query: { ignore },
};
}
describe('xml-namespace-loader', () => {
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_modules/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 = dedent`
<?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 = dedent`
<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_modules/nativescript-ui-chart/ui-chart.js',
};
const expectedDeps = [];
const expectedRegs = [
{ name: 'nativescript-ui-chart', path: 'nativescript-ui-chart' },
{
name: 'nativescript-ui-chart/RadCartesianChart',
path: 'nativescript-ui-chart',
},
];
const testXml = `
<Page xmlns="http://www.nativescript.org/tns.xsd">
<StackLayout xmlns:chart="nativescript-ui-chart">
<TextField text="{{ var1 && var2 || var1 >= var2 || var2 <= var1 }}" />
<chart:RadCartesianChart></chart:RadCartesianChart>
</StackLayout>
</Page>`;
const loaderContext = getContext(done, {
resolveMap,
expectedDeps,
expectedRegs,
expectWarnings: 1,
});
xmlNsLoader.call(loaderContext, testXml);
});
});

View File

@ -0,0 +1,17 @@
module.exports = {
preset: 'ts-jest',
testEnvironment: 'node',
moduleNameMapper: {
'^@nativescript/webpack$': '<rootDir>/src'
},
setupFiles: [
'<rootDir>/scripts/jest.setup.ts'
],
setupFilesAfterEnv: [
'<rootDir>/scripts/jest.mockWarn.ts',
'<rootDir>/scripts/jest.copyRules.ts'
],
globals: {
__TEST__: true,
}
};

View File

@ -0,0 +1,77 @@
{
"name": "@nativescript/webpack",
"version": "5.0.0-dev",
"private": true,
"main": "dist/index.js",
"files": [
"dist"
],
"bin": {
"nativescript-webpack": "dist/bin/index.js"
},
"license": "Apache-2.0",
"scripts": {
"build": "tsc --project tsconfig.build.json",
"test": "jest",
"copy-stubs": "mkdirp dist/stubs && cp -R src/stubs/* dist/stubs",
"prepack": "npm test && npm run build && npm run copy-stubs && chmod +x dist/bin/index.js"
},
"dependencies": {
"@babel/core": "7.13.14",
"@pmmmwh/react-refresh-webpack-plugin": "0.4.3",
"babel-loader": "8.2.2",
"chalk": "4.1.0",
"cli-highlight": "2.1.11",
"commander": "7.2.0",
"copy-webpack-plugin": "8.1.0",
"css": "3.0.0",
"css-loader": "5.2.0",
"dotenv-webpack": "7.0.2",
"fork-ts-checker-webpack-plugin": "6.2.0",
"loader-utils": "2.0.0",
"lodash.get": "4.4.2",
"micromatch": "4.0.2",
"postcss": "8.2.8",
"postcss-import": "14.0.0",
"postcss-loader": "5.2.0",
"raw-loader": "4.0.2",
"react-refresh": "0.10.0",
"sass": "1.32.8",
"sass-loader": "11.0.1",
"sax": "1.2.4",
"source-map": "0.7.3",
"terser-webpack-plugin": "5.1.1",
"ts-dedent": "2.1.0",
"ts-loader": "8.1.0",
"vue-loader": "15.9.6",
"webpack": "5.28.0",
"webpack-bundle-analyzer": "4.4.0",
"webpack-chain": "6.5.1",
"webpack-cli": "4.6.0",
"webpack-merge": "5.7.3",
"webpack-virtual-modules": "0.4.2"
},
"devDependencies": {
"@types/lodash.get": "4.4.6",
"@types/sax": "1.2.1",
"@types/css": "0.0.31",
"@types/jest": "26.0.22",
"@types/loader-utils": "2.0.2",
"@types/micromatch": "4.0.1",
"@types/terser-webpack-plugin": "5.0.3",
"@types/webpack-virtual-modules": "0.1.1",
"jest": "26.6.3",
"jest-matcher-utils": "26.6.2",
"nativescript-vue-template-compiler": "2.8.4",
"ts-jest": "26.5.4",
"typescript": "4.2.3"
},
"peerDependencies": {
"nativescript-vue-template-compiler": "^2.8.1"
},
"peerDependenciesMeta": {
"nativescript-vue-template-compiler": {
"optional": true
}
}
}

View File

@ -0,0 +1,7 @@
import { copyRules, additionalCopyRules } from '../src/helpers/copyRules';
afterEach(() => {
// Clear copy rules
copyRules.clear();
additionalCopyRules.length = 0
});

View File

@ -0,0 +1,8 @@
// define test-specific globals here
declare namespace jest {
interface Matchers<R, T> {
toHaveBeenWarned(): R;
toHaveBeenPrinted(): R;
}
}

View File

@ -0,0 +1,64 @@
import { printExpected, printReceived } from 'jest-matcher-utils';
import dedent from 'ts-dedent';
expect.extend({
toHaveBeenWarned(received: string) {
asserted.add(received);
const passed = warnSpy.mock.calls
.map((args) => args[1])
.some((arg) => arg.indexOf(received) > -1);
if (passed) {
return {
pass: true,
message() {
return `expected ${printReceived(received)} not to have been warned`;
},
};
}
const warnings = warnSpy.mock.calls.map((args) => args[1]).join('\n\n');
return {
pass: false,
message() {
return dedent`
expected ${printExpected(received)} to have been warned.
Actual warnings:
${warnings}
`;
},
};
},
});
let warnSpy: any;
let asserted = new Set([]);
beforeEach(() => {
asserted.clear();
warnSpy = jest.spyOn(console, 'warn');
warnSpy.mockImplementation(() => {});
});
afterEach(() => {
const assertedArray = Array.from(asserted);
const nonAssertedWarns = warnSpy.mock.calls
.map((args) => args[1])
.filter((received) => {
return !assertedArray.some((assertedMessage) => {
return received.indexOf(assertedMessage) > -1;
});
});
warnSpy.mockRestore();
if (nonAssertedWarns.length) {
throw new Error(dedent`
Test case printed unexpected warnings:
${printReceived(nonAssertedWarns.join('\n\n'))}
`);
}
});

View File

@ -0,0 +1,100 @@
// we are mocking the cwd for the tests, since webpack needs absolute paths
// and we don't want them in tests
process.cwd = () => '__jest__';
jest.mock('cosmiconfig', () => ({
cosmiconfigSync(moduleName) {
return {
search() {
// no-op in tests
return null;
},
};
},
}));
jest.mock('../src/helpers/config.ts', () => ({
getValue(key, defaultValue) {
return defaultValue;
},
}));
jest.mock('os', () => {
const os = jest.requireActual('os');
return {
...os,
networkInterfaces() {
return {
in0: [
{
address: '127.0.0.1',
family: 'IPv4',
},
{
address: 'in0-ipv6-should-not-use',
family: 'IPv6',
},
],
in1: [
{
address: '192.168.0.10',
family: 'IPv4',
},
{
address: 'in1-ipv6-should-not-use',
family: 'IPv6',
},
],
};
},
};
});
jest.mock('path', () => {
const path = jest.requireActual('path');
return {
...path,
resolve(...args) {
if (args[0] === '__jest__') {
return path.join(...args.filter(Boolean));
}
const resolved = path.resolve(...args);
if (resolved.includes('__jest__')) {
const li = resolved.lastIndexOf('__jest__');
return resolved.substr(li);
}
// handle resolutions with __dirname
// used in base config's resolveLoader
const root = path.resolve(__dirname, '..');
if (resolved.startsWith(root)) {
const newPath = resolved.replace(root, '__jest__');
if (newPath.startsWith('__jest__/src')) {
return newPath.replace(
'__jest__/src',
'__jest__/node_modules/@nativescript/webpack/dist'
);
}
return newPath;
}
return resolved;
},
};
});
// a virtual mock for package.json
jest.mock(
'__jest__/package.json',
() => ({
main: 'src/app.js',
devDependencies: {
typescript: '*',
},
}),
{ virtual: true }
);

View File

@ -0,0 +1,56 @@
// import { createServer } from 'http'
//
// export interface IHMRStatusData {
// seq: number
// uuid: string,
// hash: string
// status: string
// }
//
// export function run() {
// createServer((req, res) => {
// if (req.url === '/ping') {
// console.log('PING -> PONG!')
// return res.end("Pong.");
// }
//
// if (req.method !== 'POST') {
// res.statusCode = 400;
// return res.end("Unsupported method.");
// }
//
// let data = "";
// req.on("data", chunk => {
// data += chunk;
// });
//
// req.on("end", () => {
// try {
// const signal = JSON.parse(data) as IHMRStatusData;
// // if (!statuses[signal.hash] || statuses[signal.hash].seq < signal.seq) {
// // statuses[signal.hash] = signal
// // }
// if (process.send) {
// process.send({
// type: 'hmr-status',
// version: 1,
// hash: signal.hash,
// data: signal
// }, (error) => {
// if (error) {
// console.error(`Process Send Error: `, error);
// }
//
// return null;
// });
// }
//
// res.end('ok.');
// } catch (e) {
// res.statusCode = 400;
// res.end("Invalid JSON.");
// }
// });
// }).listen(8238)
// }
//

View File

@ -0,0 +1,122 @@
#!/usr/bin/env node
import { redBright, green, greenBright } from 'chalk';
import { program } from 'commander';
import dedent from 'ts-dedent';
import webpack from 'webpack';
import path from 'path';
import fs from 'fs';
import { parseEnvFlags } from '../cli/parseEnvFlags';
const defaultConfig = path.resolve(
__dirname,
'../stubs/default.config.stub.js'
);
const tag = `[${green('@nativescript/webpack')}]`;
function error(message: string) {
console.error(`${tag} ${redBright(dedent(message))}`);
}
function info(message: string) {
console.info(`${tag} ${greenBright(dedent(message))}`);
}
program.enablePositionalOptions();
program
.command('init')
.description('Initialize a new webpack.config.js in the current directory.')
.action(() => {
const targetPath = path.resolve(process.cwd(), 'webpack.config.js');
if (fs.existsSync(targetPath)) {
return error(`File Already Exists: ${targetPath}`);
}
fs.copyFileSync(defaultConfig, targetPath);
info('Initialized config.');
});
program
.command('build')
.description('Build...')
.option('--env [name]', 'environment name')
.option('--config [path]', 'config path')
.option('--watch', 'watch for changes')
.allowUnknownOption()
.action((options, command) => {
const env = parseEnvFlags(command.args);
// add --env <val> into the env object
// for example if we use --env prod
// we'd have env.env = 'prod'
if (options.env) {
env['env'] = options.env;
}
const configPath = (() => {
if (options.config) {
return path.resolve(options.config);
}
return path.resolve(process.cwd(), 'webpack.config.js');
})();
// todo: validate config exists
// todo: guard against invalid config
let configuration: webpack.Configuration;
try {
configuration = require(configPath)(env);
} catch (err) {
console.log(err);
}
if (!configuration) {
console.log('No configuration!');
return;
}
const compiler = webpack(configuration);
const webpackCompilationCallback = (
err: webpack.WebpackError,
stats: webpack.Stats
) => {
if (err) {
// Do not keep cache anymore
compiler.purgeInputFileSystem();
console.error(err.stack || err);
if (err.details) {
console.error(err.details);
}
process.exitCode = 1;
return;
}
if (stats) {
console.log(
stats.toString({
chunks: false,
colors: true,
errorDetails: env.verbose,
})
);
}
};
if (options.watch) {
console.log('webpack is watching the files...');
compiler.watch(
configuration.watchOptions ?? {},
webpackCompilationCallback
);
} else {
compiler.run(webpackCompilationCallback);
}
});
program.parse(process.argv);

View File

@ -0,0 +1,39 @@
import type { IWebpackEnv } from '@nativescript/webpack';
const ENV_FLAG_RE = /--env\.(\w+)(?:=(.+))?/;
export function parseEnvFlags(flags: string[]): IWebpackEnv {
const envFlags = flags.filter((flag) => flag.includes('--env.'));
const env = {};
envFlags.map((flag) => {
let [_, name, v] = ENV_FLAG_RE.exec(flag);
let value: any = v;
// convert --env.foo to --env.foo=true
if (value === undefined) {
value = true;
}
// convert true/false to boolean
if (value === 'true' || value === 'false') {
value = value === 'true';
}
// convert numbers
if (!isNaN(value) && !isNaN(parseFloat(value))) {
value = +value;
}
// duplicate key/name - convert to array
if (name in env && value) {
const orig = Array.isArray(env[name]) ? env[name] : [env[name]];
env[name] = [...orig, value];
} else {
env[name] = value;
}
});
return env;
}

View File

@ -0,0 +1,116 @@
import Config from 'webpack-chain';
import { existsSync } from 'fs';
import { getProjectFilePath } from '../helpers/project';
import { env as _env, IWebpackEnv } from '../index';
import { getEntryPath } from '../helpers/platform';
import base from './base';
export default function (config: Config, env: IWebpackEnv = _env): Config {
base(config, env);
const tsConfigPath = [
getProjectFilePath('tsconfig.app.json'),
getProjectFilePath('tsconfig.json'),
].find((path) => existsSync(path));
// remove default ts rule
config.module.rules.delete('ts');
// remove fork ts checked as not needed
config.plugins.delete('ForkTsCheckerWebpackPlugin');
// explicitly define mainFields to make sure ngcc compiles as es2015 (module field)
// instead of umd (main field).
config.resolve.mainFields.add('module').add('main');
config.module
.rule('angular')
.test(/(?:\.ngfactory.js|\.ngstyle\.js|\.ts)$/)
.use('@ngtools/webpack')
.loader('@ngtools/webpack');
config.module
.rule('@angular/core')
.test(/[\/\\]@angular[\/\\]core[\/\\].+\.js$/)
.parser({ system: true });
// set up html
config.module
.rule('html')
.test(/\.html$/)
.use('raw-loader')
.loader('raw-loader');
// exclude component css files from the normal css rule
config.module.rule('css').exclude.add(/\.component\.css$/);
// and instead use raw-loader, since that's what angular expects
config.module
.rule('css|component')
.test(/\.component\.css$/)
.use('raw-loader')
.loader('raw-loader');
// get base postCSS options
const postCSSOptions = config.module
.rule('scss')
.uses.get('postcss-loader')
.get('options');
// exclude component css files from the normal css rule
config.module.rule('scss').exclude.add(/\.component\.scss$/);
// and instead use raw-loader, since that's what angular expects
config.module
.rule('scss|component')
.test(/\.component\.scss$/)
.use('raw-loader')
.loader('raw-loader')
.end()
.use('postcss-loader')
.loader('postcss-loader')
.options(postCSSOptions)
.end()
.use('sass-loader')
.loader('sass-loader');
config.plugin('AngularCompilerPlugin').use(getAngularCompilerPlugin(), [
{
tsConfigPath,
mainPath: getEntryPath(),
platformTransformers: [require('../transformers/NativeClass').default],
},
]);
// Filter common undesirable warnings
config.set(
'ignoreWarnings',
(config.get('ignoreWarnings') ?? []).concat([
/**
* This rule hides
* +-----------------------------------------------------------------------------------------+
* | WARNING in Zone.js does not support native async/await in ES2017+. |
* | These blocks are not intercepted by zone.js and will not triggering change detection. |
* | See: https://github.com/angular/zone.js/pull/1140 for more information. |
* +-----------------------------------------------------------------------------------------+
*/
/Zone\.js does not support native async\/await/,
/**
* This rule hides
* +-----------------------------------------------------------------------------------------+
* | WARNING in environment.*.ts is part of the TypeScript compilation but it's unused. |
* | Add only entry points to the 'files' or 'include' properties in your tsconfig. |
* +-----------------------------------------------------------------------------------------+
*/
/environment.(\w+).ts is part of the TypeScript compilation but it's unused/,
])
);
return config;
}
function getAngularCompilerPlugin() {
const { AngularCompilerPlugin } = require('@ngtools/webpack');
return AngularCompilerPlugin;
}

View File

@ -0,0 +1,374 @@
import {
ContextExclusionPlugin,
DefinePlugin,
HotModuleReplacementPlugin,
} from 'webpack';
import Config from 'webpack-chain';
import { resolve } from 'path';
import ForkTsCheckerWebpackPlugin from 'fork-ts-checker-webpack-plugin';
import { BundleAnalyzerPlugin } from 'webpack-bundle-analyzer';
import TerserPlugin from 'terser-webpack-plugin';
import { getProjectFilePath, getProjectRootPath } from '../helpers/project';
import { PlatformSuffixPlugin } from '../plugins/PlatformSuffixPlugin';
import { applyFileReplacements } from '../helpers/fileReplacements';
import { addCopyRule, applyCopyRules } from '../helpers/copyRules';
import { WatchStatePlugin } from '../plugins/WatchStatePlugin';
import { hasDependency } from '../helpers/dependencies';
import { applyDotEnvPlugin } from '../helpers/dotEnv';
import { env as _env, IWebpackEnv } from '../index';
import { getValue } from '../helpers/config';
import { getIPS } from '../helpers/host';
import {
getPlatformName,
getAbsoluteDistPath,
getEntryDirPath,
getEntryPath,
} from '../helpers/platform';
export default function (config: Config, env: IWebpackEnv = _env): Config {
const entryPath = getEntryPath();
const platform = getPlatformName();
const mode = env.production ? 'production' : 'development';
// set mode
config.mode(mode);
// config.stats({
// logging: 'verbose'
// })
// package.json is generated by the CLI with runtime options
// this ensures it's not included in the bundle, but rather
// resolved at runtime
config.externals(['package.json', '~/package.json']);
// todo: devtool
config.devtool('inline-source-map');
// todo: figure out easiest way to make "node" target work in ns
// rather than the custom ns target implementation that's hard to maintain
// appears to be working - but we still have to deal with HMR
config.target('node');
config
.entry('bundle')
// ensure we load nativescript globals first
.add('@nativescript/core/globals/index.js')
.add(entryPath);
// Add android app components to the bundle to SBG can generate the java classes
if (platform === 'android') {
const appComponents = env.appComponents || [];
appComponents.push('@nativescript/core/ui/frame');
appComponents.push('@nativescript/core/ui/frame/activity');
appComponents.map((component) => {
config.entry('bundle').add(component);
});
}
// inspector_modules
config.when(shouldIncludeInspectorModules(), (config) => {
config
.entry('tns_modules/inspector_modules')
.add('@nativescript/core/inspector_modules');
});
config.output
.path(getAbsoluteDistPath())
.pathinfo(false)
.publicPath('')
.libraryTarget('commonjs')
.globalObject('global')
.set('clean', true);
config.watchOptions({
ignored: [
`${getProjectFilePath('platforms')}/**`,
`${env.appResourcesPath ?? getProjectFilePath('App_Resources')}/**`,
],
});
// Set up Terser options
config.optimization.minimizer('TerserPlugin').use(TerserPlugin, [
{
terserOptions: {
compress: {
collapse_vars: platform !== 'android',
sequences: platform !== 'android',
keep_infinity: true,
drop_console: mode === 'production',
global_defs: {
__UGLIFIED__: true,
},
},
keep_fnames: true,
keep_classnames: true,
},
},
]);
config.optimization.splitChunks({
cacheGroups: {
defaultVendor: {
test: /[\\/]node_modules[\\/]/,
priority: -10,
name: 'vendor',
chunks: 'all',
},
},
});
// look for loaders in
// - node_modules/@nativescript/webpack/dist/loaders
// - node_modules/@nativescript/webpack/node_modules
// - node_modules
// allows for cleaner rules, without having to specify full paths to loaders
config.resolveLoader.modules
.add(resolve(__dirname, '../loaders'))
.add(resolve(__dirname, '../../node_modules'))
.add(getProjectFilePath('node_modules'))
.add('node_modules');
config.resolve.extensions
.add(`.${platform}.ts`)
.add('.ts')
.add(`.${platform}.js`)
.add('.js')
.add(`.${platform}.css`)
.add('.css')
.add(`.${platform}.scss`)
.add('.scss')
.add(`.${platform}.json`)
.add('.json');
// base aliases
config.resolve.alias.set('~', getEntryDirPath()).set('@', getEntryDirPath());
// resolve symlinks
config.resolve.symlinks(true);
// resolve modules in project node_modules first
// then fall-back to default node resolution (up the parent folder chain)
config.resolve.modules
.add(getProjectFilePath('node_modules'))
.add('node_modules');
config.module
.rule('bundle')
.enforce('post')
.test(entryPath)
.use('app-css-loader')
.loader('app-css-loader')
.options({
platform,
})
.end()
.use('nativescript-hot-loader')
.loader('nativescript-hot-loader')
.options({
injectHMRRuntime: true,
});
// set up ts support
config.module
.rule('ts')
.test([/\.ts$/])
.use('ts-loader')
.loader('ts-loader')
.options({
// todo: perhaps we can provide a default tsconfig
// and use that if the project doesn't have one?
// configFile: '',
transpileOnly: true,
allowTsInNodeModules: true,
compilerOptions: {
sourceMap: true,
declaration: false,
},
getCustomTransformers() {
return {
before: [require('../transformers/NativeClass').default],
};
},
});
// Use Fork TS Checker to do type checking in a separate non-blocking process
config.when(hasDependency('typescript'), (config) => {
config
.plugin('ForkTsCheckerWebpackPlugin')
.use(ForkTsCheckerWebpackPlugin, [
{
typescript: {
memoryLimit: 4096,
},
},
]);
});
// set up js
// todo: do we need babel-loader? It's useful to support it
config.module
.rule('js')
.test(/\.js$/)
.exclude.add(/node_modules/)
.end()
.use('babel-loader')
.loader('babel-loader')
.options({
generatorOpts: {
compact: false,
},
});
config.module
.rule('workers')
.test(/\.(js|ts)$/)
.exclude.add(/node_modules/)
.end()
.use('nativescript-worker-loader')
.loader('nativescript-worker-loader');
// default PostCSS options to use
// projects can change settings
// via postcss.config.js
const postCSSOptions = {
postcssOptions: {
plugins: [
// inlines @imported stylesheets
'postcss-import',
],
},
};
// set up css
config.module
.rule('css')
.test(/\.css$/)
.use('apply-css-loader')
.loader('apply-css-loader')
.end()
.use('css2json-loader')
.loader('css2json-loader')
.end()
.use('postcss-loader')
.loader('postcss-loader')
.options(postCSSOptions);
// set up scss
config.module
.rule('scss')
.test(/\.scss$/)
.use('apply-css-loader')
.loader('apply-css-loader')
.end()
.use('css2json-loader')
.loader('css2json-loader')
.end()
.use('postcss-loader')
.loader('postcss-loader')
.options(postCSSOptions)
.end()
.use('sass-loader')
.loader('sass-loader');
// config.plugin('NormalModuleReplacementPlugin').use(NormalModuleReplacementPlugin, [
// /.*/,
// request => {
// if (new RegExp(`\.${platform}\..+$`).test(request.request)) {
// request.rawRequest = request.rawRequest.replace(`.${platform}.`, '.')
// console.log(request)
// }
// }
// ])
config.plugin('PlatformSuffixPlugin').use(PlatformSuffixPlugin, [
{
platform,
},
]);
// Makes sure that require.context will never include
// App_Resources, regardless where they are located.
config
.plugin('ContextExclusionPlugin|App_Resources')
.use(ContextExclusionPlugin, [new RegExp(`(.*)App_Resources(.*)`)]);
// Filter common undesirable warnings
config.set(
'ignoreWarnings',
(config.get('ignoreWarnings') ?? []).concat([
/**
* This rule hides
* +-----------------------------------------------------------------------------------------+
* | WARNING in ./node_modules/@angular/core/fesm2015/core.js 29714:15-102 |
* | System.import() is deprecated and will be removed soon. Use import() instead. |
* | For more info visit https://webpack.js.org/guides/code-splitting/ |
* +-----------------------------------------------------------------------------------------+
*/
/System.import\(\) is deprecated/,
])
);
// todo: refine defaults
config.plugin('DefinePlugin').use(DefinePlugin, [
{
__DEV__: mode === 'development',
__NS_WEBPACK__: true,
__NS_ENV_VERBOSE__: !!env.verbose,
__NS_DEV_HOST_IPS__:
mode === 'development' ? JSON.stringify(getIPS()) : `[]`,
__CSS_PARSER__: JSON.stringify(getValue('cssParser', 'css-tree')),
__ANDROID__: platform === 'android',
__IOS__: platform === 'ios',
/* for compat only */ 'global.isAndroid': platform === 'android',
/* for compat only */ 'global.isIOS': platform === 'ios',
process: 'global.process',
// todo: ?!?!
// profile: '() => {}',
},
]);
// enable DotEnv
applyDotEnvPlugin(config);
// replacements
applyFileReplacements(config);
// set up default copy rules
addCopyRule('assets/**');
addCopyRule('fonts/**');
addCopyRule('**/*.+(jpg|png)');
applyCopyRules(config);
config.plugin('WatchStatePlugin').use(WatchStatePlugin);
config.when(env.hmr, (config) => {
config.plugin('HotModuleReplacementPlugin').use(HotModuleReplacementPlugin);
});
config.when(env.report, (config) => {
const projectRoot = getProjectRootPath();
config.plugin('BundleAnalyzerPlugin').use(BundleAnalyzerPlugin, [
{
analyzerMode: 'static',
generateStatsFile: true,
openAnalyzer: false,
reportFilename: resolve(projectRoot, 'report', 'report.html'),
statsFilename: resolve(projectRoot, 'report', 'stats.json'),
},
]);
});
return config;
}
function shouldIncludeInspectorModules(): boolean {
const platform = getPlatformName();
// todo: check if core modules are external
// todo: check if we are testing
return platform === 'ios';
}

View File

@ -0,0 +1,18 @@
import base from './base';
import angular from './angular';
import javascript from './javascript';
import react from './react';
import svelte from './svelte';
import typescript from './typescript';
import vue from './vue';
export const configs = {
base,
angular,
javascript,
react,
svelte,
typescript,
vue,
};

View File

@ -0,0 +1,49 @@
import Config from 'webpack-chain';
import { getEntryPath, getEntryDirPath } from '../helpers/platform';
import { addVirtualEntry } from '../helpers/virtualModules';
import { env as _env, IWebpackEnv } from '../index';
import base from './base';
export default function (config: Config, env: IWebpackEnv = _env): Config {
base(config, env);
const entryPath = getEntryPath();
const filterRE = '/.(xml|js|s?css)$/';
const virtualEntryPath = addVirtualEntry(
config,
'javascript',
`
// VIRTUAL ENTRY START
require('@nativescript/core/bundle-entry-points')
const context = require.context("~/", /* deep: */ true, /* filter: */ ${filterRE});
global.registerWebpackModules(context);
// VIRTUAL ENTRY END
`
);
config.entry('bundle').add(virtualEntryPath);
// config.resolve.extensions.add('.xml');
// set up xml
config.module
.rule('xml')
.test(/\.xml$/)
.use('xml-namespace-loader')
.loader('xml-namespace-loader');
// set up core HMR
config.module
.rule('hmr-core')
.test(/\.js$/)
.exclude.add(/node_modules/)
.add(entryPath)
.end()
.use('nativescript-hot-loader')
.loader('nativescript-hot-loader')
.options({
appPath: getEntryDirPath(),
});
return config;
}

View File

@ -0,0 +1,72 @@
import { merge } from 'webpack-merge';
import Config from 'webpack-chain';
import { getPlatformName } from '../helpers/platform';
import { env as _env, IWebpackEnv } from '../index';
import base from './base';
export default function (config: Config, env: IWebpackEnv = _env): Config {
base(config, env);
const platform = getPlatformName();
const mode = env.production ? 'production' : 'development';
const production = mode === 'production';
// todo: use env
let isAnySourceMapEnabled = true;
config.resolve.extensions.prepend('.tsx').prepend(`.${platform}.tsx`);
config.resolve.alias.set('react-dom', 'react-nativescript');
config.module
.rule('ts')
.test([...config.module.rule('ts').get('test'), /\.tsx$/]);
config.plugin('DefinePlugin').tap((args) => {
args[0] = merge(args[0], {
/** For various libraries in the React ecosystem. */
__TEST__: false,
/**
* Primarily for React Fast Refresh plugin, but technically the allowHmrInProduction option could be used instead.
* Worth including anyway, as there are plenty of Node libraries that use this flag.
*/
'process.env.NODE_ENV': JSON.stringify(mode),
});
return args;
});
// todo: env flag to forceEnable?
config.when(env.hmr && !production, (config) => {
config.module
.rule('ts')
.use('babel-loader|react-refresh')
.loader('babel-loader')
.before('ts-loader')
.options({
sourceMaps: isAnySourceMapEnabled ? 'inline' : false,
babelrc: false,
plugins: ['react-refresh/babel'],
});
config
.plugin('ReactRefreshPlugin')
.use(require('@pmmmwh/react-refresh-webpack-plugin'), [
{
/**
* Maybe one day we'll implement an Error Overlay, but the work involved is too daunting for now.
* @see https://github.com/pmmmwh/react-refresh-webpack-plugin/issues/79#issuecomment-644324557
*/
overlay: false,
/**
* If you (temporarily) want to enable HMR on a production build:
* 1) Set `forceEnable` to `true`
* 2) Remove the `!production` condition on `tsxRule` to ensure that babel-loader gets used.
*/
forceEnable: false,
},
]);
});
return config;
}

View File

@ -0,0 +1,65 @@
import Config from 'webpack-chain';
import { getProjectFilePath, getProjectRootPath } from '../helpers/project';
import { getPlatformName } from '../helpers/platform';
import { env as _env, IWebpackEnv } from '../index';
import { error } from '../helpers/log';
import base from './base';
export default function (config: Config, env: IWebpackEnv = _env): Config {
base(config, env);
const platform = getPlatformName();
const mode = env.production ? 'production' : 'development';
const production = mode === 'production';
// resolve .svelte files
// the order is reversed because we are using prepend!
config.resolve.extensions.prepend('.svelte').prepend(`.${platform}.svelte`);
// add a rule for .svelte files
config.module
.rule('svelte')
.test(/\.svelte$/)
.exclude.add(/node_modules/)
.end()
.use('svelte-loader-hot')
.loader('svelte-loader-hot')
.tap((options) => {
return {
...options,
dev: !production,
preprocess: getSvelteConfigPreprocessor(),
hotReload: !production,
hotOptions: {
injectCss: false,
native: true,
},
// Suppress A11y warnings
onwarn(warning, warn) {
if (!/A11y:/.test(warning.message)) {
warn(warning);
}
},
};
});
return config;
}
function getSvelteConfigPreprocessor(): any {
const config = getSvelteConfig();
return config?.preprocess;
}
interface ISvelteConfig {
preprocess: any;
}
function getSvelteConfig(): ISvelteConfig | undefined {
try {
return require(getProjectFilePath('svelte.config.js')) as ISvelteConfig;
} catch (err) {
error('Could not find svelte.config.js.', err);
}
}

View File

@ -0,0 +1,49 @@
import Config from 'webpack-chain';
import { getEntryDirPath, getEntryPath } from '../helpers/platform';
import { addVirtualEntry } from '../helpers/virtualModules';
import { env as _env, IWebpackEnv } from '../index';
import base from './base';
export default function (config: Config, env: IWebpackEnv = _env): Config {
base(config, env);
const entryPath = getEntryPath();
const filterRE = '/\\.(xml|js|(?<!d\\.)ts|s?css)$/';
const virtualEntryPath = addVirtualEntry(
config,
'typescript',
`
// VIRTUAL ENTRY START
require('@nativescript/core/bundle-entry-points')
const context = require.context("~/", /* deep: */ true, /* filter: */ ${filterRE});
global.registerWebpackModules(context);
// VIRTUAL ENTRY END
`
);
config.entry('bundle').add(virtualEntryPath);
// config.resolve.extensions.add('.xml');
// set up xml
config.module
.rule('xml')
.test(/\.xml$/)
.use('xml-namespace-loader')
.loader('xml-namespace-loader');
// set up core HMR
config.module
.rule('hmr-core')
.test(/\.(js|ts)$/)
.exclude.add(/node_modules/)
.add(entryPath)
.end()
.use('nativescript-hot-loader')
.loader('nativescript-hot-loader')
.options({
appPath: getEntryDirPath(),
});
return config;
}

View File

@ -0,0 +1,96 @@
import { VueLoaderPlugin } from 'vue-loader';
import { merge } from 'webpack-merge';
import Config from 'webpack-chain';
import fs from 'fs';
import { hasDependency } from '../helpers/dependencies';
import { getPlatformName } from '../helpers/platform';
import { env as _env, IWebpackEnv } from '../index';
import { error } from '../helpers/log';
import base from './base';
export default function (config: Config, env: IWebpackEnv = _env): Config {
base(config, env);
const platform = getPlatformName();
// we need to patch VueLoader if we want to enable hmr
if (env.hmr) {
patchVueLoaderForHMR();
}
// resolve .vue files
// the order is reversed because we are using prepend!
config.resolve.extensions.prepend('.vue').prepend(`.${platform}.vue`);
// add a rule for .vue files
config.module
.rule('vue')
.test(/\.vue$/)
.use('vue-loader')
.loader('vue-loader')
.tap((options) => {
return {
...options,
compiler: require('nativescript-vue-template-compiler'),
};
});
// set up ts support in vue files
config.module
.rule('ts')
.use('ts-loader')
.loader('ts-loader')
.tap((options = {}) => {
return merge(options, {
appendTsSuffixTo: ['\\.vue$'],
});
});
config.when(hasDependency('typescript'), (config) => {
config.plugin('ForkTsCheckerWebpackPlugin').tap((args) => {
args[0] = merge(args[0], {
typescript: {
extensions: {
vue: {
enabled: true,
compiler: 'nativescript-vue-template-compiler',
},
},
},
});
return args;
});
});
// add VueLoaderPlugin as the first plugin
config
.plugin('VueLoaderPlugin')
// @ts-ignore
.before(config.plugins.values()[0].name)
.use(VueLoaderPlugin);
// add an alias for vue, since some plugins may try to import it
config.resolve.alias.set('vue', 'nativescript-vue');
return config;
}
/**
* Patches source of vue-loader to set the isServer flag to false
* so hmr gets enabled.
*/
function patchVueLoaderForHMR() {
try {
const vueLoaderPath = require.resolve('vue-loader/lib/index.js');
const source = fs.readFileSync(vueLoaderPath).toString();
const patchedSource = source.replace(
/(isServer\s=\s)(target\s===\s'node')/g,
'$1false;'
);
fs.writeFileSync(vueLoaderPath, patchedSource);
delete require.cache[vueLoaderPath];
} catch (err) {
error('Failed to patch VueLoader - HMR may not work properly!');
}
}

1
packages/webpack5/src/globals.d.ts vendored Normal file
View File

@ -0,0 +1 @@
// define globals here

View File

@ -0,0 +1,34 @@
import { env } from '../index';
import { error, warnOnce } from './log';
function getCLILib() {
if (!env.nativescriptLibPath) {
warnOnce(
'getCLILib',
`
Cannot find NativeScript CLI path. Make sure --env.nativescriptLibPath is passed
`
);
return false;
}
return require(env.nativescriptLibPath);
}
/**
* Utility to get a value from the nativescript.config.ts file.
*
* @param {string} key The key to get from the config. Supports dot-notation.
* @param defaultValue The fallback value if the key is not set in the config.
*/
export function getValue<T = any>(key: string, defaultValue?: any): T {
const lib = getCLILib();
if (!lib) {
return defaultValue;
}
return (lib.projectConfigService as {
getValue(key: string, defaultValue?: any): T;
}).getValue(key, defaultValue);
}

View File

@ -0,0 +1,85 @@
import CopyWebpackPlugin from 'copy-webpack-plugin';
import { relative, resolve } from 'path';
import Config from 'webpack-chain';
import { getProjectRootPath } from './project';
import { getEntryDirPath } from './platform';
import { env } from '..';
/**
* @internal
*/
export let copyRules = new Set([]);
/**
* @internal
*/
export let additionalCopyRules = [];
/**
* Utility to add new copy rules. Accepts a glob or an object. For example
* - **\/*.html - copy all .html files found in any sub dir.
* - myFolder/* - copy all files from myFolder
*
* When passing an object - no additional processing is done, and it's
* applied as-is. Make sure to set every required property.
*
* The path is relative to the folder of the entry file
* (specified in the main field of the package.json)
*
* @param {string|object} globOrObject
*/
export function addCopyRule(globOrObject: string | object) {
if (typeof globOrObject === 'string') {
return copyRules.add(globOrObject);
}
additionalCopyRules.push(globOrObject);
}
/**
* Utility to remove a copy rule. The glob should be the exact glob
* to remove. For example
* - fonts/** - to remove the default copy rule for fonts
*
* @param {string} glob
*/
export function removeCopyRule(glob: string) {
copyRules.delete(glob);
}
/**
* @internal
*/
export function applyCopyRules(config: Config) {
const entryDir = getEntryDirPath();
const globOptions = {
dot: false,
ignore: [],
};
// todo: do we need to handle empty appResourcesPath?
// (the CLI should always pass the path - maybe not required)
if (env.appResourcesPath) {
const appResourcesFullPath = resolve(
getProjectRootPath(),
env.appResourcesPath
);
// ignore everything in App_Resources (regardless where they are located)
globOptions.ignore.push(`${relative(entryDir, appResourcesFullPath)}/**`);
}
config.plugin('CopyWebpackPlugin').use(CopyWebpackPlugin, [
{
patterns: Array.from(copyRules)
.map((glob) => ({
from: glob,
context: entryDir,
noErrorOnMissing: true,
globOptions,
}))
.concat(additionalCopyRules),
},
]);
}

View File

@ -0,0 +1,49 @@
import path from 'path';
import { getPackageJson, getProjectRootPath } from './project';
// todo: memoize
/**
* Utility to get all dependencies from the project package.json.
* The result combines dependencies and devDependencies
*
* @returns string[] dependencies
*/
export function getAllDependencies(): string[] {
const packageJSON = getPackageJson();
return [
...Object.keys(packageJSON.dependencies ?? {}),
...Object.keys(packageJSON.devDependencies ?? {}),
];
}
// todo: memoize
/**
* Utility to check if the project has a specific dependency
* in either dependencies or devDependencies.
*
* @param {string} dependencyName
* @returns boolean
*/
export function hasDependency(dependencyName: string) {
return getAllDependencies().includes(dependencyName);
}
// todo: memoize
/**
* Utility to get the path (usually nested in node_modules) of a dependency.
*
* @param dependencyName
*/
export function getDependencyPath(dependencyName: string): string | null {
try {
const resolvedPath = require.resolve(`${dependencyName}/package.json`, {
paths: [getProjectRootPath()],
});
return path.dirname(resolvedPath);
} catch (err) {
return null;
}
}

View File

@ -0,0 +1,49 @@
import DotEnvPlugin from 'dotenv-webpack';
import Config from 'webpack-chain';
import { existsSync } from 'fs';
import { resolve } from 'path';
import { getProjectRootPath } from './project';
import { env } from '..';
/**
* @internal
*/
export function applyDotEnvPlugin(config: Config) {
const path = getDotEnvPath();
config.when(path !== null, (config) => {
config.plugin('DotEnvPlugin').use(DotEnvPlugin, [
{
path,
silent: true, // hide any errors
},
]);
});
}
function getDotEnvFileName(): string {
if (env.env) {
return `.env.${env.env}`;
}
return '.env';
}
function getDotEnvPath(): string {
const dotEnvPath = resolve(getProjectRootPath(), '.env');
const dotEnvWithEnvPath = resolve(getProjectRootPath(), getDotEnvFileName());
// look for .env.<env>
if (existsSync(dotEnvWithEnvPath)) {
return dotEnvWithEnvPath;
}
// fall back to .env
if (existsSync(dotEnvPath)) {
return dotEnvPath;
}
// don't use .env
return null;
}

View File

@ -0,0 +1,49 @@
import path from 'path';
import fs from 'fs';
import { getAllDependencies, getDependencyPath } from './dependencies';
import { clearCurrentPlugin, setCurrentPlugin } from '../index';
import { info, warn } from './log';
import * as lib from '../index';
/**
* @internal
*/
export function applyExternalConfigs() {
getAllDependencies().forEach((dependency) => {
const packagePath = getDependencyPath(dependency);
if (!packagePath) {
return;
}
const configPath = path.join(packagePath, 'nativescript.webpack.js');
if (fs.existsSync(configPath)) {
info(`Discovered config: ${configPath}`);
setCurrentPlugin(dependency);
try {
const externalConfig = require(configPath);
if (typeof externalConfig === 'function') {
info('Applying external config...');
externalConfig(lib);
} else if (externalConfig) {
info('Merging external config...');
lib.mergeWebpack(externalConfig);
} else {
warn(
'Unsupported external config. The config must export a function or an object.'
);
}
} catch (err) {
warn(`
Unable to apply config: ${configPath}.
Error is: ${err}
`);
}
}
});
clearCurrentPlugin();
}

View File

@ -0,0 +1,68 @@
import { resolve } from 'path';
import { env as _env, IWebpackEnv } from '../index';
import { addCopyRule } from './copyRules';
import { getProjectRootPath } from './project';
interface IReplacementMap {
[_replace: string]: /* _with */ string;
}
/**
* @internal
*/
export function getFileReplacementsFromEnv(
env: IWebpackEnv = _env
): IReplacementMap {
const fileReplacements: IReplacementMap = {};
const entries: string[] = (() => {
if (Array.isArray(env.replace)) {
return env.replace;
}
if (typeof env.replace === 'string') {
return [env.replace];
}
return [];
})();
entries.forEach((replaceEntry) => {
replaceEntry.split(/,\s*/).forEach((r: string) => {
let [_replace, _with] = r.split(':');
if (!_replace || !_with) {
return;
}
// make sure to resolve replacements to a full path
// relative to the project root
_replace = resolve(getProjectRootPath(), _replace);
_with = resolve(getProjectRootPath(), _with);
fileReplacements[_replace] = _with;
});
});
return fileReplacements;
}
export function applyFileReplacements(
config,
fileReplacements: IReplacementMap = getFileReplacementsFromEnv()
) {
Object.entries(fileReplacements).forEach(([_replace, _with]) => {
// in case we are replacing source files - we'll use aliases
if (_replace.match(/\.(ts|js)$/)) {
return config.resolve.alias.set(_replace, _with);
}
// otherwise we will override the replaced file with the replacement
addCopyRule({
from: _with, // copy the replacement file
to: _replace, // to the original "to-be-replaced" file
force: true,
});
});
}

View File

@ -0,0 +1,47 @@
import { defaultConfigs } from '@nativescript/webpack';
import { getAllDependencies } from './dependencies';
import { error } from './log';
/**
* Utility to determine the project flavor based on installed dependencies
* (vue, angular, react, svelete, typescript, javascript...)
*/
export function determineProjectFlavor(): keyof typeof defaultConfigs | false {
const dependencies = getAllDependencies();
if (dependencies.includes('nativescript-vue')) {
return 'vue';
}
if (dependencies.includes('@nativescript/angular')) {
return 'angular';
}
if (dependencies.includes('react-nativescript')) {
return 'react';
}
if (dependencies.includes('svelte-native')) {
return 'svelte';
}
// the order is important - angular, react, and svelte also include these deps
// but should return prior to this condition!
if (
dependencies.includes('@nativescript/core') &&
dependencies.includes('typescript')
) {
return 'typescript';
}
if (dependencies.includes('@nativescript/core')) {
return 'javascript';
}
error(`
Could not determine project flavor.
Please use webpack.useConfig('<flavor>') to explicitly set the base config.
`);
return false;
}

View File

@ -0,0 +1,13 @@
import os from 'os';
export function getIPS() {
const interfaces = os.networkInterfaces();
return Object.keys(interfaces)
.map((name) => {
return interfaces[name].filter(
(binding: any) => binding.family === 'IPv4'
)[0];
})
.filter(Boolean)
.map((binding) => binding.address);
}

View File

@ -0,0 +1,78 @@
import { merge } from 'webpack-merge';
import {
getPackageJson,
getProjectRootPath,
getProjectFilePath,
} from './project';
import { addVirtualEntry, addVirtualModule } from './virtualModules';
import { applyFileReplacements } from './fileReplacements';
import { addCopyRule, removeCopyRule } from './copyRules';
import { error, info, warn, warnOnce } from './log';
import { determineProjectFlavor } from './flavor';
import { getValue } from './config';
import { getIPS } from './host';
import {
getAllDependencies,
hasDependency,
getDependencyPath,
} from './dependencies';
import {
addPlatform,
getAbsoluteDistPath,
getDistPath,
getEntryDirPath,
getEntryPath,
getPlatform,
getPlatformName,
} from './platform';
// intentionally populated manually
// as this generates nicer typings
// that show all the utils inline
// rather than imports to types
// todo: maybe use api-extractor instead
export default {
merge,
addCopyRule,
removeCopyRule,
applyFileReplacements,
config: {
getValue,
},
dependencies: {
getAllDependencies,
hasDependency,
getDependencyPath,
},
flavor: {
determineProjectFlavor,
},
host: {
getIPS,
},
log: {
error,
info,
warn,
warnOnce,
},
platform: {
addPlatform,
getAbsoluteDistPath,
getDistPath,
getEntryDirPath,
getEntryPath,
getPlatform,
getPlatformName,
},
project: {
getProjectFilePath,
getProjectRootPath,
getPackageJson,
},
virtualModules: {
addVirtualEntry,
addVirtualModule,
},
};

View File

@ -0,0 +1,57 @@
import dedent from 'ts-dedent';
import { env } from '@nativescript/webpack';
// de-indents strings so multi-line string literals can be used
function cleanup(data: any[]) {
return data.map((d) => {
if (typeof d === 'string') {
return dedent(d);
}
return d;
});
}
export function error(...data: any): Error {
console.warn(`[@nativescript/webpack] Error: \n`, ...cleanup(data));
// we return the error - the caller can throw or ignore
if (typeof data[0] === 'string') {
return new Error(
'\n\n[@nativescript/webpack]\n---\n\n' + dedent(data[0]) + '\n\n---\n'
);
}
return new Error('@nativescript/webpack ran into a problem...');
}
export function warn(...data: any): void {
console.warn(`[@nativescript/webpack] Warn: \n`, ...cleanup(data));
}
const warnedMap: any = {};
export function warnOnce(key: string, ...data: any): void {
if (warnedMap[key]) {
return;
}
warnedMap[key] = true;
warn(...data);
}
export function info(...data: any): void {
if (env.verbose) {
console.log(`[@nativescript/webpack] Info: \n`, ...cleanup(data));
}
}
// todo: refine
// export function error(message: string, info?: { possibleCauses?: string[] }) {
// console.error(`
// NativeScript Webpack encountered an error and cannot proceed with the build:
//
// ${message}
//
// Possible causes:
// ${info?.possibleCauses?.map((cause) => `- ${cause}`).join('\n')}
// `);
// }

View File

@ -0,0 +1,128 @@
import { dirname, resolve } from 'path';
import { getPackageJson, getProjectRootPath } from './project';
import { error, info, warnOnce } from './log';
import { env } from '../';
import AndroidPlatform from '../platforms/android';
import iOSPlatform from '../platforms/ios';
export interface INativeScriptPlatform {
getEntryPath?(): string;
getDistPath?(): string;
}
export type Platform = Extract<keyof typeof platforms, string>;
const platforms: {
[name: string]: INativeScriptPlatform;
} = {
android: AndroidPlatform,
ios: iOSPlatform,
};
/**
* Utility to register a new supported platform.
*
* @param {string} name The name of the platform (eg. web, desktop)
* @param platform A platform definition of the platform specifics
*/
export function addPlatform(name: string, platform: INativeScriptPlatform) {
info(`Adding platform ${name}`, platform);
platforms[name] = platform;
}
/**
* Utility to get the currently targeted platform definition
*/
export function getPlatform(): INativeScriptPlatform {
return platforms[getPlatformName()];
}
/**
* Utility to get the currently targeted platform name
*/
export function getPlatformName(): Platform {
if (env?.android) {
return 'android';
}
if (env?.ios) {
return 'ios';
}
// support custom platforms
if (env?.platform) {
if (platforms[env.platform]) {
return env.platform;
}
throw error(`
Invalid platform: ${env.platform}
Valid platforms: ${Object.keys(platforms).join(', ')}
`);
}
warnOnce(
'getPlatformName',
`
You need to provide a target platform!
Available platforms: ${Object.keys(platforms).join(', ')}
Use --env.platform=<platform> or --env.android, --env.ios to specify the target platform.
Defaulting to "ios".
`
);
return 'ios';
}
/**
* Utility to get the entry file path for the currently targeted platform
*/
export function getEntryPath() {
const platform = getPlatform();
// use platform specific entry path
if (platform.getEntryPath) {
return platform.getEntryPath();
}
// fallback to main field in package.json
const packageJson = getPackageJson();
return resolve(getProjectRootPath(), packageJson.main);
}
/**
* Utility to get the entry file directory path for the currently targeted platform
*/
export function getEntryDirPath() {
return dirname(getEntryPath());
}
/**
* Utility to get the dist file path for the currently targeted platform
*/
export function getDistPath() {
const platform = getPlatform();
// use platform specific entry path
if (platform.getDistPath) {
return platform.getDistPath();
}
// fallback to a generic platforms/<platform>/dist folder
return `platforms/${getPlatformName()}/dist`;
}
/**
* Utility to get the absolute dist file path for the currently targeted platform
*/
export function getAbsoluteDistPath() {
return resolve(getProjectRootPath(), getDistPath());
}

View File

@ -0,0 +1,50 @@
import { resolve } from 'path';
export function getProjectRootPath(): string {
return process.cwd();
}
interface IPackageJson {
main?: string;
dependencies?: {
[name: string]: string;
};
devDependencies?: {
[name: string]: string;
};
// todo: add additional fields as we require them
}
/**
* Utility function to get the contents of the project package.json
*/
export function getPackageJson() {
return require(getProjectFilePath('package.json')) as IPackageJson;
}
/**
* Utility to get project files relative to the project root.
* @param filePath path to get
*/
export function getProjectFilePath(filePath: string): string {
return resolve(getProjectRootPath(), filePath);
}
// unused helper, but keeping it here as we may need it
// todo: remove if unused for next few releases
// function findFile(fileName, currentDir): string | null {
// // console.log(`findFile(${fileName}, ${currentDir})`)
// const path = resolve(currentDir, fileName);
//
// if (existsSync(path)) {
// return path;
// }
//
// // bail if we reached the root dir
// if (currentDir === resolve('/')) {
// return null;
// }
//
// // traverse to the parent folder
// return findFile(fileName, resolve(currentDir, '..'));
// }

View File

@ -0,0 +1,49 @@
import { ContextExclusionPlugin } from 'webpack';
import Config from 'webpack-chain';
import { join } from 'path';
import VirtualModulesPlugin from 'webpack-virtual-modules';
import { getEntryDirPath } from './platform';
import dedent from 'ts-dedent';
export function addVirtualEntry(
config: Config,
name: string,
contents: string
): string {
return addVirtualModule(
config,
`__@nativescript_webpack_virtual_entry_${name}__`,
contents
);
}
export function addVirtualModule(
config: Config,
name: string,
contents: string
): string {
const virtualEntryPath = join(getEntryDirPath(), `${name}`);
// add the virtual entry to the context exclusions
// makes sure that require.context will never
// include the virtual entry.
config
.plugin(`ContextExclusionPlugin|${name}`)
.use(ContextExclusionPlugin, [new RegExp(`${name}\.js$`)]);
const options = {
[virtualEntryPath]: dedent(contents),
};
if (config.plugins.has('VirtualModulesPlugin')) {
config.plugin('VirtualModulesPlugin').tap((args) => {
Object.assign(args[0], options);
return args;
});
} else {
config.plugin('VirtualModulesPlugin').use(VirtualModulesPlugin, [options]);
}
return virtualEntryPath;
}

View File

@ -0,0 +1,216 @@
import { highlight } from 'cli-highlight';
import { merge } from 'webpack-merge';
import Config from 'webpack-chain';
import webpack from 'webpack';
import { applyExternalConfigs } from './helpers/externalConfigs';
import { determineProjectFlavor } from './helpers/flavor';
import { error, info } from './helpers/log';
import { configs } from './configuration';
import helpers from './helpers';
export interface IWebpackEnv {
[name: string]: any;
env?: string;
appPath?: string;
appResourcesPath?: string;
appComponents?: string[];
nativescriptLibPath?: string;
android?: boolean;
ios?: boolean;
// for custom platforms
platform?: string;
production?: boolean;
report?: boolean;
hmr?: boolean;
// enable verbose output
verbose?: boolean;
// misc
replace?: string[] | string;
}
interface IChainEntry {
chainFn: any;
order?: number;
plugin?: string;
}
let webpackChains: IChainEntry[] = [];
let webpackMerges: any[] = [];
let explicitUseConfig = false;
let hasInitialized = false;
let currentPlugin: string | undefined;
/**
* @internal
*/
export let env: IWebpackEnv = {};
/**
* @internal
*/
export function setCurrentPlugin(plugin: string) {
currentPlugin = plugin;
}
/**
* @internal
*/
export function clearCurrentPlugin() {
currentPlugin = undefined;
}
////// PUBLIC API
/**
* The default flavor specific configs
*/
export const defaultConfigs = configs;
/**
* Utilities to simplify various tasks
*/
export const Utils = helpers;
/**
* Initialize @nativescript/webpack with the webpack env.
* Must be called first.
*
* @param _env The webpack env
*/
export function init(_env: IWebpackEnv) {
hasInitialized = true;
if (_env) {
env = _env;
}
}
/**
* Explicitly specify the base config to use.
* Calling this will opt-out from automatic flavor detection.
*
* Useful when the flavor cannot be detected due to the project structure
* for example in a custom monorepo.
*
* @param config Name of the base config to use.
*/
export function useConfig(config: keyof typeof defaultConfigs | false) {
explicitUseConfig = true;
if (config) {
webpackChains.push({
order: -1,
chainFn: configs[config],
});
}
}
/**
* Add a new function to be called when building the internal config using webpack-chain.
*
* @param chainFn A function that accepts the internal chain config, and the current environment
* @param options Optional options to control the order in which the chain function should be applied.
*/
export function chainWebpack(
chainFn: (config: Config, env: IWebpackEnv) => any,
options?: { order?: number }
) {
webpackChains.push({
order: options?.order || 0,
chainFn,
plugin: currentPlugin,
});
}
/**
* Merge an object into the resolved chain config.
*
* @param mergeFn An object or a function that optionally returns an object (can mutate the object directly and return nothing)
*/
export function mergeWebpack(
mergeFn: (
config: Partial<webpack.Configuration>,
env: IWebpackEnv
) => any | Partial<webpack.Configuration>
) {
webpackMerges.push(mergeFn);
}
/**
* Resolve a new instance of the internal chain config with all chain functions applied.
*/
export function resolveChainableConfig(): Config {
const config = new Config();
if (!explicitUseConfig) {
useConfig(determineProjectFlavor());
}
// apply configs from dependencies
// todo: allow opt-out
applyExternalConfigs();
webpackChains
.splice(0)
.sort((a, b) => {
return a.order - b.order;
})
.forEach(({ chainFn, plugin }) => {
try {
chainFn(config, env);
} catch (err) {
if (plugin) {
// catch and print errors from plugins
return error(`
Unable to apply chain function from: ${plugin}.
Error is: ${err}
`);
}
// otherwise throw - as the error is likely from the user config
// or missing env flags (eg. missing platform)
throw err;
}
});
if (env.verbose) {
info('Resolved chainable config (before merges):');
info(highlight(config.toString(), { language: 'js' }));
}
return config;
}
/**
* Resolve a "final" configuration that has all chain functions and merges applied.
*
* @param chainableConfig Optional chain config to use.
*/
export function resolveConfig(
chainableConfig = resolveChainableConfig()
): webpack.Configuration {
if (!hasInitialized) {
throw error('resolveConfig() must be called after init()');
}
let config = chainableConfig.toConfig();
// this applies webpack merges
webpackMerges.forEach((mergeFn) => {
if (typeof mergeFn === 'function') {
// mergeFn is a function with optional return value
const res = mergeFn(config, env);
if (res) config = merge(config, res);
} else if (mergeFn) {
// mergeFn is a literal value (object)
config = merge(config, mergeFn);
}
});
// return a config usable by webpack
return config;
}

View File

@ -0,0 +1,28 @@
import { dedent } from 'ts-dedent';
import { basename } from 'path';
/**
* This loader tries to load an `app.scss` or and `app.css` relative to the main entry
*/
export default function loader(content: string, map: any) {
const { platform } = this.getOptions();
const callback = this.async();
const resolve = this.getResolve({
extensions: [`.${platform}.scss`, `.${platform}.css`, '.scss', '.css'],
});
resolve(this.context, './app', (err, res) => {
if (err || !res) {
// if we ran into an error or there's no css file found, we just return
// original content and not append any additional imports.
return callback(null, content, map);
}
const code = dedent`
// Added by app-css-loader
import "./${basename(res)}";
${content}
`;
callback(null, code, map);
});
}

View File

@ -0,0 +1,57 @@
import { dedent } from 'ts-dedent';
const cssLoaderWarning = dedent`
The apply-css-loader requires the file to be pre-processed by either css-loader or css2json-loader.
Make sure the appropriate loader is applied before apply-css-loader.
`;
export default function loader(content, map) {
const hasLoader = (loader: string) => {
return this.loaders
?.slice(this.loaderIndex)
.some(({ path }) => path.includes(loader));
};
// add a tag to the applied css
const tag = JSON.stringify(this.resourcePath);
const tagCode =
this.mode === 'development' ? `, ${JSON.stringify(this.resourcePath)}` : '';
const hmrCode = this.hot
? dedent`
if(module.hot) {
module.hot.accept()
module.hot.dispose(() => {
const { removeTaggedAdditionalCSS } = require("@nativescript/core/ui/styling/style-scope");
removeTaggedAdditionalCSS(${tag})
})
}
`
: ``;
if (hasLoader('css2json-loader')) {
content = dedent`
${content}
const { addTaggedAdditionalCSS } = require("@nativescript/core/ui/styling/style-scope");
addTaggedAdditionalCSS(___CSS2JSON_LOADER_EXPORT___${tagCode})
${hmrCode}
`;
} else if (hasLoader('css-loader')) {
content = dedent`
${content}
const { addTaggedAdditionalCSS } = 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
addTaggedAdditionalCSS(cssExport[1]${tagCode});
}
});
}
${hmrCode}
`;
} else {
this.emitWarning(new Error(cssLoaderWarning));
}
this.callback(null, content, null);
}

View File

@ -0,0 +1,81 @@
import { parse, Import, Stylesheet } from 'css';
import { urlToRequest } from 'loader-utils';
import { dedent } from 'ts-dedent';
const betweenQuotesPattern = /('|")(.*?)\1/;
const unpackUrlPattern = /url\(([^\)]+)\)/;
const inlineLoader = '!css2json-loader?useForImports!';
export default function loader(content: string, map: any) {
const options = this.getOptions() || {};
const inline = !!options.useForImports;
const requirePrefix = inline ? inlineLoader : '';
const ast = parse(content);
// todo: revise if this is necessary
// todo: perhaps use postCSS and just build imports into a single file?
let dependencies = [];
getAndRemoveImportRules(ast)
.map(extractUrlFromRule)
.map(createRequireUri)
.forEach(({ uri, requireURI }) => {
dependencies.push(`require("${requirePrefix}${requireURI}")`);
});
const str = JSON.stringify(ast, (k, v) => (k === 'position' ? undefined : v));
// map.mappings = map.mappings.replace(/;{2,}/, '')
const code = dedent`
/* CSS2JSON */
${dependencies.join('\n')}
const ___CSS2JSON_LOADER_EXPORT___ = ${str}
export default ___CSS2JSON_LOADER_EXPORT___
`;
this.callback(
null,
code, //`${dependencies.join('\n')}module.exports = ${str};`,
map
);
}
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
)
);
}
function getAndRemoveImportRules(ast: Stylesheet): Import[] {
const imports = getImportRules(ast);
ast.stylesheet.rules = ast.stylesheet.rules.filter(
(rule) => rule.type !== 'import'
);
return imports;
}
/**
* 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),
};
}

View File

@ -0,0 +1,114 @@
// @ts-nocheck
// This is a runtime module - included by nativescript-hot-loader
// this file should not include external dependencies
// ---
if (module.hot) {
let hash = __webpack_require__.h();
const logVerbose = (title: string, ...info: any) => {
if (__NS_ENV_VERBOSE__) {
console.log(`[HMR][Verbose] ${title}`);
if (info?.length) {
console.log(...info);
console.log('---');
}
}
};
const setStatus = (
hash: string,
status: 'success' | 'failure',
message?: string,
...info: any
): boolean => {
// format is important - CLI expects this exact format
console.log(`[HMR][${hash}] ${status} | ${message}`);
if (info?.length) {
logVerbose('Additional Info', info);
}
// return true if operation was successful
return status === 'success';
};
const applyOptions = {
ignoreUnaccepted: false,
ignoreDeclined: false,
ignoreErrored: false,
onDeclined(info) {
setStatus(hash, 'failure', 'A module has been declined.', info);
},
onUnaccepted(info) {
setStatus(hash, 'failure', 'A module has not been accepted.', info);
},
onAccepted(info) {
// console.log('accepted', info)
logVerbose('Module Accepted', info);
},
onDisposed(info) {
// console.log('disposed', info)
logVerbose('Module Disposed', info);
},
onErrored(info) {
setStatus(hash, 'failure', 'A module has errored.', info);
},
};
const checkAndApply = async () => {
hash = __webpack_require__.h();
const modules = await module.hot.check().catch((error) => {
return setStatus(
hash,
'failure',
'Failed to check.',
error.message || error.stack
);
});
if (!modules) {
logVerbose('No modules to apply.');
return false;
}
const appliedModules = await module.hot
.apply(applyOptions)
.catch((error) => {
return setStatus(
hash,
'failure',
'Failed to apply.',
error.message || error.stack
);
});
if (!appliedModules) {
logVerbose('No modules applied.');
return false;
}
return setStatus(hash, 'success', 'Successfully applied update.');
};
const hasUpdate = () => {
try {
__non_webpack_require__(`~/bundle.${__webpack_hash__}.hot-update.json`);
return true;
} catch (err) {
return false;
}
};
const originalOnLiveSync = global.__onLiveSync;
global.__onLiveSync = async function () {
logVerbose('LiveSync');
if (!hasUpdate()) {
return;
}
await checkAndApply();
originalOnLiveSync();
};
}

View File

@ -0,0 +1,44 @@
import { relative, resolve } from 'path';
import dedent from 'ts-dedent';
import fs from 'fs';
// note: this will bail even if module.hot appears in a comment
const MODULE_HOT_RE = /module\.hot/;
export default function loader(content: string, map: any) {
if (MODULE_HOT_RE.test(content)) {
// Code already handles HMR - we don't need to do anything
return this.callback(null, content, map);
}
const opts = this.getOptions();
// used to inject the HMR runtime into the entry file
if (opts.injectHMRRuntime) {
const hmrRuntimePath = resolve(__dirname, './hmr.runtime.js');
const hmrRuntime = fs
.readFileSync(hmrRuntimePath)
.toString()
.split('// ---')[1]
.replace('//# sourceMappingURL=hmr.runtime.js.map', '');
return this.callback(null, `${content}\n${hmrRuntime}`, map);
}
const relativePath = relative(
opts.appPath ?? this.rootContext,
this.resourcePath
).replace(/\\/g, '/');
const hmrCode = this.hot
? dedent`
/* NATIVESCRIPT-HOT-LOADER */
if(module.hot && global._isModuleLoadedForUI && global._isModuleLoadedForUI("./${relativePath}")) {
module.hot.accept()
}
`
: ``;
const source = `${content}\n${hmrCode}`;
this.callback(null, source, map);
}

View File

@ -0,0 +1,63 @@
const WorkerDependency = require('webpack/lib/dependencies/WorkerDependency');
const RuntimeGlobals = require('webpack/lib/RuntimeGlobals');
/**
* Patch WorkerDependency to change:
*
* new Worker(new URL(workerPath, baseUrl))
* ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
* to.
*
* new Worker('~/' + workerPath)
*
* Note: we are changing source **outside** of the dependency range, and this may
* break when the dependency range changes, for example if this PR is merged:
* - https://github.com/webpack/webpack/pull/12750
*/
WorkerDependency.Template.prototype.apply = function apply(
dependency,
source,
templateContext
) {
const { chunkGraph, moduleGraph, runtimeRequirements } = templateContext;
const dep = /** @type {WorkerDependency} */ dependency;
const block = /** @type {AsyncDependenciesBlock} */ moduleGraph.getParentBlock(
dependency
);
const entrypoint = /** @type {Entrypoint} */ chunkGraph.getBlockChunkGroup(
block
);
const chunk = entrypoint.getEntrypointChunk();
// runtimeRequirements.add(RuntimeGlobals.publicPath);
// runtimeRequirements.add(RuntimeGlobals.baseURI);
runtimeRequirements.add(RuntimeGlobals.getChunkScriptFilename);
/**
* new URL(
* ^^^^^^^^ = 8 characters, we subtract it from the dep.range[0]
*/
source.replace(
dep.range[0] - 8,
dep.range[1],
`/* worker import */ /* patched by nativescript-worker-loader */ '~/' + ${
RuntimeGlobals.getChunkScriptFilename
}(${JSON.stringify(chunk.id)})`
);
};
const NEW_WORKER_WITH_STRING_RE = /new\s+Worker\((['"`].+['"`])\)/;
/**
* Replaces
* new Worker('./somePath')
* with
* new Worker(new URL('./somePath', import.meta.url))
*/
export default function loader(content: string, map: any) {
const source = content.replace(
NEW_WORKER_WITH_STRING_RE,
'new Worker(new URL($1, import.meta.url))'
);
this.callback(null, source, map);
}

View File

@ -0,0 +1,238 @@
import { parse, join } from 'path';
import { promisify } from 'util';
import dedent from 'ts-dedent';
import { parser } from 'sax';
const noop = () => {};
const DEBUG = false;
interface NamespaceEntry {
name: string;
path: string;
}
interface ParseResult {
code: string;
}
export default function loader(content: string, map: any) {
const callback = this.async();
// parse content and dependencies async
parseXML
.bind(this)(content)
.then((res) => {
DEBUG && console.log({ res });
callback(null, res.code, map);
})
.catch((err) => {
DEBUG && console.log({ err });
callback(err);
});
}
async function parseXML(content: string): Promise<ParseResult> {
// wrap this.resolve into a promise
const resolveAsync = promisify(this.resolve);
const promises: Promise<any>[] = [];
const namespaces: NamespaceEntry[] = [];
const distinctNamespaces = new Map<string, string>();
const moduleRegisters: string[] = [];
const { ignore } = this.query;
const errors = [];
const saxParser = parser(true, { xmlns: true });
// // Register ios and android prefixes as namespaces to avoid "unbound xml namespace" errors
(saxParser as any).ns['ios'] = 'http://schemas.nativescript.org/tns.xsd';
(saxParser as any).ns['android'] = 'http://schemas.nativescript.org/tns.xsd';
(saxParser as any).ns['desktop'] = 'http://schemas.nativescript.org/tns.xsd';
(saxParser as any).ns['web'] = 'http://schemas.nativescript.org/tns.xsd';
const handleOpenTag = async (namespace: string, elementName: string) => {
if (!namespace) {
return;
}
if (namespace.startsWith('http')) {
return;
}
const moduleName = `${namespace}/${elementName}`;
if (namespaces.some((n) => n.name === moduleName)) {
return;
}
if (ignore && moduleName.match(ignore)) {
return;
}
const localNamespacePath = join(this.rootContext, namespace);
const localModulePath = join(localNamespacePath, elementName);
const resolvePaths = [
localNamespacePath,
localModulePath,
`${localModulePath}.xml`,
moduleName,
namespace,
];
DEBUG && console.log({ resolvePaths });
let resolvedPath;
for (const p of resolvePaths) {
resolvedPath = await resolveAsync(this.context, p).catch(noop);
// break on first match
if (resolvedPath) {
break;
}
}
DEBUG && console.log({ resolvedPath });
// bail if we haven't resolved a path
if (!resolvedPath) {
return;
}
const { dir, name } = parse(resolvedPath);
// register resolved path + short name
namespaces.push({ name: namespace, path: resolvedPath });
namespaces.push({ name: moduleName, path: resolvedPath });
this.addDependency(resolvedPath);
const noExtFilename = join(dir, name);
DEBUG &&
console.log({
noExtFilename,
});
// finally try resolving an XML file
await resolveAsync(this.context, `${noExtFilename}.xml`)
.then((xml) => {
this.addDependency(xml);
namespaces.push({ name: `${moduleName}.xml`, path: xml });
})
.catch(() => {
// if there is no XML file, fall back to namespace as the path
// will become require(<namespace>)
namespaces.push({ name: namespace, path: namespace });
namespaces.push({ name: moduleName, path: namespace });
});
// look for css files with the same name
await resolveAsync(this.context, `${noExtFilename}.css`)
.then((css) => {
this.addDependency(css);
namespaces.push({ name: `${moduleName}.css`, path: css });
})
.catch(noop);
};
saxParser.onopentag = (node) => {
if ('uri' in node) {
promises.push(handleOpenTag(node.uri, node.local));
}
};
saxParser.onerror = (error) => {
saxParser.error = null;
// Do only warning about invalid character "&"" for back-compatibility
// as it is common to use it in a binding expression
if (
error.message.includes('Invalid character') &&
error.message.includes('Char: &')
) {
return this.emitWarning(error);
}
errors.push(error);
};
saxParser.write(content).close();
await Promise.all(promises);
DEBUG && console.log({ namespaces });
namespaces.forEach(({ name, path }) => {
distinctNamespaces.set(name, path.replace(/\\/g, '/'));
});
distinctNamespaces.forEach((path, name) => {
moduleRegisters.push(dedent`
global.registerModule(
'${name}',
() => require("${path}")
)
`);
});
// 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 xml = JSON.stringify(content)
.replace(/\u2028/g, '\\u2028')
.replace(/\u2029/g, '\\u2029');
const hmrCode = this.hot
? dedent`
if(module.hot) {
module.hot.accept()
// module.hot.dispose(() => {})
}
`
: ``;
const code = dedent`
${moduleRegisters.join('\n')}
/* XML-NAMESPACE-LOADER */
const ___XML_NAMESPACE_LOADER_EXPORT___ = ${xml}
export default ___XML_NAMESPACE_LOADER_EXPORT___
${hmrCode}
`;
if (errors.length) {
errors.map(this.emitError);
// finally throw the first one
throw errors[0];
}
return {
code,
};
}
//
//
//
// function parseXML(xml: string) {
// const saxParser = parser(true, { xmlns: true });
//
// saxParser.onopentag = (node) => {
// if('ns' in node) {
// const uri = node.uri
// const tag = node.local
//
// DEBUG && console.log({
// uri,
// tag
// })
// }
// }
//
// saxParser.onerror = (err) => {
// DEBUG && console.log(err)
// }
//
// // Register ios and android prefixes as namespaces to avoid "unbound xml namespace" errors
// // saxParser.ns['ios'] = 'http://schemas.nativescript.org/tns.xsd';
// // saxParser.ns['android'] = 'http://schemas.nativescript.org/tns.xsd';
// // saxParser.ns['desktop'] = 'http://schemas.nativescript.org/tns.xsd';
// // saxParser.ns['web'] = 'http://schemas.nativescript.org/tns.xsd';
// saxParser.write(xml).close()
// }

View File

@ -0,0 +1,11 @@
import { INativeScriptPlatform } from "../helpers/platform";
function getDistPath() {
return `platforms/android/app/src/main/assets/app`;
}
const AndroidPlatform: INativeScriptPlatform = {
getDistPath,
}
export default AndroidPlatform;

View File

@ -0,0 +1,20 @@
import { basename } from "path";
import { INativeScriptPlatform } from "../helpers/platform";
import { getProjectRootPath } from "../helpers/project";
function sanitizeName(appName: string): string {
return appName.split("").filter((c) =>
/[a-zA-Z0-9]/.test(c)
).join("");
}
function getDistPath() {
const appName = sanitizeName(basename(getProjectRootPath()));
return `platforms/ios/${appName}/app`;
}
const iOSPlatform: INativeScriptPlatform = {
getDistPath,
}
export default iOSPlatform;

View File

@ -0,0 +1,161 @@
import { extname, resolve } from 'path';
import { existsSync } from 'fs';
const id = 'PlatformSuffixPlugin';
interface PlatformSuffixPluginOptions {
platform: string;
// extensions: string[] | (() => string[])
}
/**
* The platform suffix plugin will try to resolve files with a platform specifier (suffix)
* falling back to the non-platform-specific version.
*
* For example:
* import something from './something.js'
*
* will first look for './something.<platform>.js'
* and if not found look for './something.js'
*
*/
export class PlatformSuffixPlugin {
private readonly platform: string;
// private readonly extensions: string[]
constructor(options: PlatformSuffixPluginOptions) {
this.platform = options.platform;
// if (typeof options.extensions === "function") {
// this.extensions = options.extensions()
// } else {
// this.extensions = options.extensions
// }
}
apply(compiler: any) {
const platformRE = new RegExp(`\.${this.platform}\.`);
// require.context
compiler.hooks.contextModuleFactory.tap(id, (cmf) => {
// @ts-ignore
cmf.hooks.alternativeRequests.tap(id, (modules, options) => {
const additionalModules = [];
// we are looking for modules that are platform specific (something.<platform>.ext)
// and we are duplicating them without the platform suffix
// this allows using require.context with non-existent platformless filenames
// but mapped to the platform specific variant (done in the resolver hook below)
for (const module of modules) {
if (platformRE.test(module.request)) {
additionalModules.push({
...module,
request: module.request.replace(platformRE, '.'),
});
}
}
modules.push(...additionalModules);
});
});
compiler.resolverFactory.hooks.resolver
.for('normal')
.tap(id, (resolver) => {
// Object.keys(resolver.hooks).forEach(hook => {
// resolver.hooks[hook].tap(id, (request, resolveContext) => {
// if(
// request?.path?.includes('foo.xml') ||
// request?.request?.includes('foo.xml')
// ) {
// console.log(
// `>>> ${hook}: ${request.path}`,
// // request
// )
// }
// // callback();
// });
// })
resolver.hooks.normalResolve.tapAsync(
id,
(request_, resolveContext, callback) => {
const { path, request } = request_;
const ext = request && extname(request);
const platformExt = ext ? `.${this.platform}${ext}` : '';
if (path && request && ext && !request.includes(platformExt)) {
const platformRequest = request.replace(ext, platformExt);
const extPath = resolve(path, platformRequest);
// console.log({
// path,
// request,
// ext,
// extPath
// })
// if a file with the same + a platform suffix exists
// we want to resolve that file instead
if (existsSync(extPath)) {
const message = `resolving "${request}" to "${platformRequest}"`;
const hook = resolver.ensureHook('normalResolve');
console.log(message);
// here we are creating a new resolve object and replacing the path
// with the .<platform>.<ext> suffix
const obj = {
...request_,
path: resolver.join(path, platformRequest),
relativePath:
request_.relativePath &&
resolver.join(request_.relativePath, platformRequest),
request: undefined,
};
// we call to the actual resolver to do the resolving of this new file
return resolver.doResolve(
hook,
obj,
message,
resolveContext,
callback
);
}
}
callback();
}
);
// resolver.hooks.rawFile.tap(id, (request, resolveContext, callback) => {
// if(request.path && !/\.ios\..+$/.test(request.path)) {
// const { ext } = parse(request.path)
// const platformExtPath = request.path.replace(ext, `.${this.platform}${ext}`)
// // console.log({
// // p1: request.path,
// // p2: platformExtPath
// // })
// if(existsSync(platformExtPath)) {
// // request.path = platformExtPath
// // console.log('-'.repeat(100))
// // console.log(request)
// const obj = {
// ...request,
// path: platformExtPath,
// fullySpecified: false
// }
// return resolver.doResolve(
// 'raw-file',
// obj,
// `resolved ${request.path} to platform specific file: ${platformExtPath}`,
// resolveContext,
// (err, result) => {
// if(err) return callback(err);
// if(result) return callback(null, result);
// return callback();
// }
// )
// // return request
// }
// }
// });
});
}
}

View File

@ -0,0 +1,96 @@
import { env } from '../';
const id = 'WatchStatePlugin';
const version = 1;
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
* and send status updates through IPC to the {N} CLI.
*/
export class WatchStatePlugin {
apply(compiler: any) {
let isWatchMode = false;
let prevAssets = [];
compiler.hooks.watchRun.tapAsync(id, function (compiler, callback) {
callback();
if (isWatchMode) {
console.log(messages.changeDetected);
if (env.verbose) {
if (compiler.modifiedFiles) {
Array.from(compiler.modifiedFiles).forEach((file) => {
console.log(`[${id}][WatchTriggers] MODIFIED: ${file}`);
});
}
if (compiler.removedFiles) {
Array.from(compiler.removedFiles).forEach((file) => {
console.log(`[${id}][WatchTriggers] REMOVED: ${file}`);
});
}
}
}
isWatchMode = true;
});
compiler.hooks.afterEmit.tapAsync(id, function (compilation, callback) {
callback();
console.log(
isWatchMode ? messages.startWatching : messages.compilationComplete
);
// logic taken from CleanWebpackPlugin
const assets =
compilation.getStats().toJson(
{
assets: true,
},
true
).assets || [];
const assetList = assets.map((asset) => asset.name);
const emittedAssets = Array.from(compilation.emittedAssets);
const staleAssets = prevAssets.filter((asset) => {
return assetList.includes(asset) === false;
});
// store assets for next compilation
prevAssets = assetList.sort();
notify({
type: 'compilation',
version,
hash: compilation.hash,
data: {
emittedAssets,
staleAssets,
},
});
});
}
}
function notify(message: any) {
env.verbose && console.log(`[${id}] Notify: `, message);
if (!process.send) {
return;
}
process.send(message, (error) => {
if (error) {
console.error(`[${id}] Process Send Error: `, error);
}
return null;
});
}

View File

@ -0,0 +1,12 @@
const webpack = require("@nativescript/webpack");
module.exports = (env) => {
webpack.init(env);
// Learn how to customize:
// https://docs.nativescript.org/webpack
return webpack.resolveConfig();
};

View File

@ -0,0 +1 @@
// todo

View File

@ -0,0 +1,51 @@
import ts from 'typescript';
/**
* A TypeScript transform that compiles classes marked with @NativeClass as es5 & commonjs
*
* @param ctx
*/
export default function (ctx: ts.TransformationContext) {
function isNativeClassExtension(node: ts.ClassDeclaration) {
return (
node.decorators &&
node.decorators.filter((d) => {
const fullText = d.getFullText().trim();
return fullText.indexOf('@NativeClass') > -1;
}).length > 0
);
}
function visitNode(node: ts.Node): ts.Node {
if (ts.isClassDeclaration(node) && isNativeClassExtension(node)) {
return createHelper(node);
}
return ts.visitEachChild(node, visitNode, ctx);
}
function createHelper(node: ts.Node) {
// we remove the decorator for now!
return ts.createIdentifier(
ts
.transpileModule(
node.getText().replace(/@NativeClass(\((.|\n)*?\))?/gm, ''),
{
compilerOptions: {
noEmitHelpers: true,
module: ts.ModuleKind.CommonJS,
target: ts.ScriptTarget.ES5,
},
}
)
.outputText.replace(
/(Object\.defineProperty\(.*?{.*?)(enumerable:\s*false)(.*?}\))/gs,
'$1enumerable: true$3'
)
);
}
return (source: ts.SourceFile) =>
ts.updateSourceFileNode(
source,
ts.visitNodes(source.statements, visitNode)
);
}

View File

@ -0,0 +1,7 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
"rootDir": "./src"
},
"include": ["src"]
}

View File

@ -0,0 +1,25 @@
{
"compilerOptions": {
"rootDir": ".",
"baseUrl": ".",
"target": "es2017",
"module": "commonjs",
"outDir": "./dist",
"declaration": true,
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"lib": ["es2017"],
"sourceMap": true,
"skipLibCheck": true,
"skipDefaultLibCheck": true,
"diagnostics": true,
"paths": {
"@nativescript/webpack": ["src"]
},
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"stripInternal": true
},
"include": ["src", "scripts", "__tests__"],
"exclude": ["node_modules"]
}

View File

@ -115,6 +115,13 @@ module.exports = {
test: {
script: 'nx run webpack:test',
description: '@nativescript/webpack: Unit tests'
},
},
// @nativescript/webpack (5)
webpack5: {
build: {
script: 'nx run webpack5:build',
description: '@nativescript/webpack(5): Build for npm'
},
},
},

View File

@ -253,6 +253,31 @@
}
}
}
},
"webpack5": {
"root": "packages/webpack5",
"sourceRoot": "packages/webpack5",
"projectType": "library",
"schematics": {},
"architect": {
"lint": {
"builder": "@nrwl/linter:eslint",
"options": {
"lintFilePatterns": []
}
},
"build": {
"builder": "@nrwl/workspace:run-commands",
"outputs": ["dist/packages"],
"options": {
"commands": [
"npm run build"
],
"cwd": "packages/webpack5",
"parallel": false
}
}
}
}
},
"cli": {