diff --git a/packages/webpack5/.gitignore b/packages/webpack5/.gitignore index 24729192e..2081558f9 100644 --- a/packages/webpack5/.gitignore +++ b/packages/webpack5/.gitignore @@ -1,3 +1,4 @@ # dist coverage +*.tgz diff --git a/packages/webpack5/README.md b/packages/webpack5/README.md index d7c392220..73e509ce2 100644 --- a/packages/webpack5/README.md +++ b/packages/webpack5/README.md @@ -8,7 +8,7 @@ BREAKING CHANGES: For example (given we have a `src` directory where our app is): - `"main": "app.js"` becomes `"main": "src/app.js"` + `"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. diff --git a/packages/webpack5/__tests__/cli/parseEnvFlags.spec.ts b/packages/webpack5/__tests__/cli/parseEnvFlags.spec.ts new file mode 100644 index 000000000..c04dbe57d --- /dev/null +++ b/packages/webpack5/__tests__/cli/parseEnvFlags.spec.ts @@ -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); + }); +}); diff --git a/packages/webpack5/__tests__/configuration/__snapshots__/angular.spec.ts.snap b/packages/webpack5/__tests__/configuration/__snapshots__/angular.spec.ts.snap index fa0e44274..76cdb10fa 100644 --- a/packages/webpack5/__tests__/configuration/__snapshots__/angular.spec.ts.snap +++ b/packages/webpack5/__tests__/configuration/__snapshots__/angular.spec.ts.snap @@ -7,14 +7,35 @@ exports[`angular configuration for android 1`] = ` 'package.json', '~/package.json' ], + externalsPresets: { + node: false + }, 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/, + { + module: /@angular\\\\/core\\\\/(__ivy_ngcc__\\\\/)?fesm2015\\\\/core.js/, + message: /Critical dependency: the request of a dependency is an expression/ + }, + /core\\\\/profiling/, + /core\\\\/ui\\\\/styling/ + ], output: { path: '__jest__/platforms/android/app/src/main/assets/app', pathinfo: false, publicPath: '', libraryTarget: 'commonjs', - globalObject: 'global' + globalObject: 'global', + clean: true }, resolve: { symlinks: true, @@ -33,37 +54,79 @@ exports[`angular configuration for android 1`] = ` '.scss', '.android.json', '.json' + ], + mainFields: [ + 'module', + 'main' + ], + modules: [ + '__jest__/node_modules', + 'node_modules' ] }, resolveLoader: { modules: [ - 'node_modules/@nativescript/webpack/dist/loaders', + '__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/ - ], + ] + }, + /* config.module.rule('workers') */ + { + test: /\\\\.(js|ts)$/, use: [ - /* config.module.rule('js').use('babel-loader') */ + /* config.module.rule('workers').use('nativescript-worker-loader') */ { - loader: 'babel-loader', - options: { - generatorOpts: { - compact: false - } - } + loader: 'nativescript-worker-loader' + } + ] + }, + /* config.module.rule('xml') */ + { + test: /\\\\.xml$/, + use: [ + /* config.module.rule('xml').use('xml-namespace-loader') */ + { + loader: 'xml-namespace-loader' } ] }, /* config.module.rule('css') */ { test: /\\\\.css$/, + exclude: [ + /\\\\.component(\\\\.\\\\w+)?\\\\.css$/ + ], use: [ /* config.module.rule('css').use('apply-css-loader') */ { @@ -89,6 +152,9 @@ exports[`angular configuration for android 1`] = ` /* config.module.rule('scss') */ { test: /\\\\.scss$/, + exclude: [ + /\\\\.component(\\\\.\\\\w+)?\\\\.scss$/ + ], use: [ /* config.module.rule('scss').use('apply-css-loader') */ { @@ -141,10 +207,46 @@ exports[`angular configuration for android 1`] = ` loader: 'raw-loader' } ] + }, + /* config.module.rule('css|component') */ + { + test: /\\\\.component(\\\\.\\\\w+)?\\\\.css$/, + use: [ + /* config.module.rule('css|component').use('raw-loader') */ + { + loader: 'raw-loader' + } + ] + }, + /* config.module.rule('scss|component') */ + { + test: /\\\\.component(\\\\.\\\\w+)?\\\\.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: { + runtimeChunk: 'single', splitChunks: { cacheGroups: { defaultVendor: { @@ -162,43 +264,38 @@ exports[`angular configuration for android 1`] = ` terserOptions: { compress: { collapse_vars: false, - sequences: false + sequences: false, + keep_infinity: true, + drop_console: false, + global_defs: { + __UGLIFIED__: true + } }, - keep_fnames: true + keep_fnames: true, + keep_classnames: true } } ) ] }, plugins: [ - /* config.plugin('ForkTsCheckerWebpackPlugin') */ - new ForkTsCheckerWebpackPlugin( - { - typescript: { - memoryLimit: 4096 - } - } - ), - /* config.plugin('CleanWebpackPlugin') */ - new CleanWebpackPlugin( - { - cleanOnceBeforeBuildPatterns: [ - '__jest__/platforms/android/app/src/main/assets/app/**/*' - ], - verbose: false - } - ), /* 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, @@ -216,7 +313,8 @@ exports[`angular configuration for android 1`] = ` context: '__jest__/src', noErrorOnMissing: true, globOptions: { - dot: false + dot: false, + ignore: [] } }, { @@ -224,7 +322,8 @@ exports[`angular configuration for android 1`] = ` context: '__jest__/src', noErrorOnMissing: true, globOptions: { - dot: false + dot: false, + ignore: [] } }, { @@ -232,7 +331,8 @@ exports[`angular configuration for android 1`] = ` context: '__jest__/src', noErrorOnMissing: true, globOptions: { - dot: false + dot: false, + ignore: [] } } ] @@ -245,16 +345,27 @@ exports[`angular configuration for android 1`] = ` { tsConfigPath: '__jest__/tsconfig.json', mainPath: '__jest__/src/app.js', + forkTypeChecker: false, + hostReplacementPaths: function () { /* omitted long function */ }, platformTransformers: [ function () { /* omitted long function */ } ] } + ), + /* config.plugin('AngularWebpackPlugin') */ + new AngularWebpackPlugin( + { + tsconfig: '__jest__/tsconfig.json', + directTemplateLoading: false + } ) ], entry: { bundle: [ '@nativescript/core/globals/index.js', - '__jest__/src/app.js' + '__jest__/src/app.js', + '@nativescript/core/ui/frame', + '@nativescript/core/ui/frame/activity' ] } }" @@ -267,14 +378,35 @@ exports[`angular configuration for ios 1`] = ` 'package.json', '~/package.json' ], + externalsPresets: { + node: false + }, 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/, + { + module: /@angular\\\\/core\\\\/(__ivy_ngcc__\\\\/)?fesm2015\\\\/core.js/, + message: /Critical dependency: the request of a dependency is an expression/ + }, + /core\\\\/profiling/, + /core\\\\/ui\\\\/styling/ + ], output: { - path: '__jest__/platforms/ios/__jest__/app', + path: '__jest__/platforms/ios/jest/app', pathinfo: false, publicPath: '', libraryTarget: 'commonjs', - globalObject: 'global' + globalObject: 'global', + clean: true }, resolve: { symlinks: true, @@ -293,37 +425,79 @@ exports[`angular configuration for ios 1`] = ` '.scss', '.ios.json', '.json' + ], + mainFields: [ + 'module', + 'main' + ], + modules: [ + '__jest__/node_modules', + 'node_modules' ] }, resolveLoader: { modules: [ - 'node_modules/@nativescript/webpack/dist/loaders', + '__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/ - ], + ] + }, + /* config.module.rule('workers') */ + { + test: /\\\\.(js|ts)$/, use: [ - /* config.module.rule('js').use('babel-loader') */ + /* config.module.rule('workers').use('nativescript-worker-loader') */ { - loader: 'babel-loader', - options: { - generatorOpts: { - compact: false - } - } + loader: 'nativescript-worker-loader' + } + ] + }, + /* config.module.rule('xml') */ + { + test: /\\\\.xml$/, + use: [ + /* config.module.rule('xml').use('xml-namespace-loader') */ + { + loader: 'xml-namespace-loader' } ] }, /* config.module.rule('css') */ { test: /\\\\.css$/, + exclude: [ + /\\\\.component(\\\\.\\\\w+)?\\\\.css$/ + ], use: [ /* config.module.rule('css').use('apply-css-loader') */ { @@ -349,6 +523,9 @@ exports[`angular configuration for ios 1`] = ` /* config.module.rule('scss') */ { test: /\\\\.scss$/, + exclude: [ + /\\\\.component(\\\\.\\\\w+)?\\\\.scss$/ + ], use: [ /* config.module.rule('scss').use('apply-css-loader') */ { @@ -401,10 +578,46 @@ exports[`angular configuration for ios 1`] = ` loader: 'raw-loader' } ] + }, + /* config.module.rule('css|component') */ + { + test: /\\\\.component(\\\\.\\\\w+)?\\\\.css$/, + use: [ + /* config.module.rule('css|component').use('raw-loader') */ + { + loader: 'raw-loader' + } + ] + }, + /* config.module.rule('scss|component') */ + { + test: /\\\\.component(\\\\.\\\\w+)?\\\\.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: { + runtimeChunk: 'single', splitChunks: { cacheGroups: { defaultVendor: { @@ -422,43 +635,38 @@ exports[`angular configuration for ios 1`] = ` terserOptions: { compress: { collapse_vars: true, - sequences: true + sequences: true, + keep_infinity: true, + drop_console: false, + global_defs: { + __UGLIFIED__: true + } }, - keep_fnames: true + keep_fnames: true, + keep_classnames: true } } ) ] }, plugins: [ - /* config.plugin('ForkTsCheckerWebpackPlugin') */ - new ForkTsCheckerWebpackPlugin( - { - typescript: { - memoryLimit: 4096 - } - } - ), - /* config.plugin('CleanWebpackPlugin') */ - new CleanWebpackPlugin( - { - cleanOnceBeforeBuildPatterns: [ - '__jest__/platforms/ios/__jest__/app/**/*' - ], - verbose: false - } - ), /* 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, @@ -476,7 +684,8 @@ exports[`angular configuration for ios 1`] = ` context: '__jest__/src', noErrorOnMissing: true, globOptions: { - dot: false + dot: false, + ignore: [] } }, { @@ -484,7 +693,8 @@ exports[`angular configuration for ios 1`] = ` context: '__jest__/src', noErrorOnMissing: true, globOptions: { - dot: false + dot: false, + ignore: [] } }, { @@ -492,7 +702,8 @@ exports[`angular configuration for ios 1`] = ` context: '__jest__/src', noErrorOnMissing: true, globOptions: { - dot: false + dot: false, + ignore: [] } } ] @@ -505,10 +716,19 @@ exports[`angular configuration for ios 1`] = ` { tsConfigPath: '__jest__/tsconfig.json', mainPath: '__jest__/src/app.js', + forkTypeChecker: false, + hostReplacementPaths: function () { /* omitted long function */ }, platformTransformers: [ function () { /* omitted long function */ } ] } + ), + /* config.plugin('AngularWebpackPlugin') */ + new AngularWebpackPlugin( + { + tsconfig: '__jest__/tsconfig.json', + directTemplateLoading: false + } ) ], entry: { @@ -516,9 +736,32 @@ exports[`angular configuration for ios 1`] = ` '@nativescript/core/globals/index.js', '__jest__/src/app.js' ], - 'tns_modules/@nativescript/core/inspector_modules': [ + 'tns_modules/inspector_modules': [ '@nativescript/core/inspector_modules' ] } }" `; + +exports[`angular configuration loads polyfills.android.ts into the bundle entry if it exists 1`] = ` +Array [ + "__jest__/src/polyfills.android.ts", + "__jest__/src/app.js", + "@nativescript/core/ui/frame", + "@nativescript/core/ui/frame/activity", +] +`; + +exports[`angular configuration loads polyfills.ios.ts into the bundle entry if it exists 1`] = ` +Array [ + "__jest__/src/polyfills.ios.ts", + "__jest__/src/app.js", +] +`; + +exports[`angular configuration loads polyfills.ts into the bundle entry if it exists 1`] = ` +Array [ + "__jest__/src/polyfills.ts", + "__jest__/src/app.js", +] +`; diff --git a/packages/webpack5/__tests__/configuration/__snapshots__/base.spec.ts.snap b/packages/webpack5/__tests__/configuration/__snapshots__/base.spec.ts.snap new file mode 100644 index 000000000..584b94a3c --- /dev/null +++ b/packages/webpack5/__tests__/configuration/__snapshots__/base.spec.ts.snap @@ -0,0 +1,616 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`base configuration for android 1`] = ` +"{ + mode: 'development', + externals: [ + 'package.json', + '~/package.json' + ], + externalsPresets: { + node: false + }, + devtool: 'inline-source-map', + target: 'node', + watchOptions: { + ignored: [ + '__jest__/platforms/**', + '__jest__/custom_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/ + ] + }, + /* config.module.rule('workers') */ + { + test: /\\\\.(js|ts)$/, + use: [ + /* config.module.rule('workers').use('nativescript-worker-loader') */ + { + loader: 'nativescript-worker-loader' + } + ] + }, + /* config.module.rule('xml') */ + { + test: /\\\\.xml$/, + use: [ + /* config.module.rule('xml').use('xml-namespace-loader') */ + { + loader: 'xml-namespace-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: { + runtimeChunk: 'single', + 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: [ + '**/custom_app_resources/**' + ] + } + }, + { + from: 'fonts/**', + context: '__jest__/src', + noErrorOnMissing: true, + globOptions: { + dot: false, + ignore: [ + '**/custom_app_resources/**' + ] + } + }, + { + from: '**/*.+(jpg|png)', + context: '__jest__/src', + noErrorOnMissing: true, + globOptions: { + dot: false, + ignore: [ + '**/custom_app_resources/**' + ] + } + } + ] + } + ), + /* 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' + ], + externalsPresets: { + node: false + }, + devtool: 'inline-source-map', + target: 'node', + watchOptions: { + ignored: [ + '__jest__/platforms/**', + '__jest__/custom_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/ + ] + }, + /* config.module.rule('workers') */ + { + test: /\\\\.(js|ts)$/, + use: [ + /* config.module.rule('workers').use('nativescript-worker-loader') */ + { + loader: 'nativescript-worker-loader' + } + ] + }, + /* config.module.rule('xml') */ + { + test: /\\\\.xml$/, + use: [ + /* config.module.rule('xml').use('xml-namespace-loader') */ + { + loader: 'xml-namespace-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: { + runtimeChunk: 'single', + 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: [ + '**/custom_app_resources/**' + ] + } + }, + { + from: 'fonts/**', + context: '__jest__/src', + noErrorOnMissing: true, + globOptions: { + dot: false, + ignore: [ + '**/custom_app_resources/**' + ] + } + }, + { + from: '**/*.+(jpg|png)', + context: '__jest__/src', + noErrorOnMissing: true, + globOptions: { + dot: false, + ignore: [ + '**/custom_app_resources/**' + ] + } + } + ] + } + ), + /* config.plugin('WatchStatePlugin') */ + new WatchStatePlugin() + ], + entry: { + bundle: [ + '@nativescript/core/globals/index.js', + '__jest__/src/app.js' + ], + 'tns_modules/inspector_modules': [ + '@nativescript/core/inspector_modules' + ] + } +}" +`; diff --git a/packages/webpack5/__tests__/configuration/__snapshots__/javascript.spec.ts.snap b/packages/webpack5/__tests__/configuration/__snapshots__/javascript.spec.ts.snap index 0fd530dd6..4135f11db 100644 --- a/packages/webpack5/__tests__/configuration/__snapshots__/javascript.spec.ts.snap +++ b/packages/webpack5/__tests__/configuration/__snapshots__/javascript.spec.ts.snap @@ -7,14 +7,27 @@ exports[`javascript configuration for android 1`] = ` 'package.json', '~/package.json' ], + externalsPresets: { + node: false + }, 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' + globalObject: 'global', + clean: true }, resolve: { symlinks: true, @@ -32,18 +45,43 @@ exports[`javascript configuration for android 1`] = ` '.android.scss', '.scss', '.android.json', - '.json', - '.xml' + '.json' + ], + modules: [ + '__jest__/node_modules', + 'node_modules' ] }, resolveLoader: { modules: [ - 'node_modules/@nativescript/webpack/dist/loaders', + '__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: [ @@ -70,16 +108,25 @@ exports[`javascript configuration for android 1`] = ` test: /\\\\.js$/, exclude: [ /node_modules/ - ], + ] + }, + /* config.module.rule('workers') */ + { + test: /\\\\.(js|ts)$/, use: [ - /* config.module.rule('js').use('babel-loader') */ + /* config.module.rule('workers').use('nativescript-worker-loader') */ { - loader: 'babel-loader', - options: { - generatorOpts: { - compact: false - } - } + loader: 'nativescript-worker-loader' + } + ] + }, + /* config.module.rule('xml') */ + { + test: /\\\\.xml$/, + use: [ + /* config.module.rule('xml').use('xml-namespace-loader') */ + { + loader: 'xml-namespace-loader' } ] }, @@ -137,19 +184,27 @@ exports[`javascript configuration for android 1`] = ` } ] }, - /* config.module.rule('xml') */ + /* config.module.rule('hmr-core') */ { - test: /\\\\.xml$/, + test: /\\\\.js$/, + exclude: [ + /node_modules/, + '__jest__/src/app.js' + ], use: [ - /* config.module.rule('xml').use('xml-namespace-loader') */ + /* config.module.rule('hmr-core').use('nativescript-hot-loader') */ { - loader: 'xml-namespace-loader' + loader: 'nativescript-hot-loader', + options: { + appPath: '__jest__/src' + } } ] } ] }, optimization: { + runtimeChunk: 'single', splitChunks: { cacheGroups: { defaultVendor: { @@ -167,9 +222,15 @@ exports[`javascript configuration for android 1`] = ` terserOptions: { compress: { collapse_vars: false, - sequences: false + sequences: false, + keep_infinity: true, + drop_console: false, + global_defs: { + __UGLIFIED__: true + } }, - keep_fnames: true + keep_fnames: true, + keep_classnames: true } } ) @@ -184,26 +245,23 @@ exports[`javascript configuration for android 1`] = ` } } ), - /* config.plugin('CleanWebpackPlugin') */ - new CleanWebpackPlugin( - { - cleanOnceBeforeBuildPatterns: [ - '__jest__/platforms/android/app/src/main/assets/app/**/*' - ], - verbose: false - } - ), /* 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, @@ -221,7 +279,8 @@ exports[`javascript configuration for android 1`] = ` context: '__jest__/src', noErrorOnMissing: true, globOptions: { - dot: false + dot: false, + ignore: [] } }, { @@ -229,7 +288,8 @@ exports[`javascript configuration for android 1`] = ` context: '__jest__/src', noErrorOnMissing: true, globOptions: { - dot: false + dot: false, + ignore: [] } }, { @@ -237,7 +297,8 @@ exports[`javascript configuration for android 1`] = ` context: '__jest__/src', noErrorOnMissing: true, globOptions: { - dot: false + dot: false, + ignore: [] } } ] @@ -245,14 +306,14 @@ exports[`javascript configuration for android 1`] = ` ), /* config.plugin('WatchStatePlugin') */ new WatchStatePlugin(), - /* config.plugin('ContextExclusionPluginPlugin') */ + /* config.plugin('ContextExclusionPlugin|__@nativescript_webpack_virtual_entry_javascript__') */ new ContextExclusionPlugin( - /__virtual_entry__\\\\.js$/ + /__@nativescript_webpack_virtual_entry_javascript__.js$/ ), /* config.plugin('VirtualModulesPlugin') */ new VirtualModulesPlugin( { - '__jest__/src/__virtual_entry__.js': 'require(\\\\'@nativescript/core/bundle-entry-points\\\\')\\\\nconst context = require.context(\\"~/\\", /* deep: */ true, /* filter: */ /.(xml|js|s?css)$/);\\\\nglobal.registerWebpackModules(context);' + '__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' } ) ], @@ -260,7 +321,9 @@ exports[`javascript configuration for android 1`] = ` bundle: [ '@nativescript/core/globals/index.js', '__jest__/src/app.js', - '__jest__/src/__virtual_entry__.js' + '@nativescript/core/ui/frame', + '@nativescript/core/ui/frame/activity', + '__jest__/src/__@nativescript_webpack_virtual_entry_javascript__' ] } }" @@ -273,14 +336,27 @@ exports[`javascript configuration for ios 1`] = ` 'package.json', '~/package.json' ], + externalsPresets: { + node: false + }, 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', + path: '__jest__/platforms/ios/jest/app', pathinfo: false, publicPath: '', libraryTarget: 'commonjs', - globalObject: 'global' + globalObject: 'global', + clean: true }, resolve: { symlinks: true, @@ -298,18 +374,43 @@ exports[`javascript configuration for ios 1`] = ` '.ios.scss', '.scss', '.ios.json', - '.json', - '.xml' + '.json' + ], + modules: [ + '__jest__/node_modules', + 'node_modules' ] }, resolveLoader: { modules: [ - 'node_modules/@nativescript/webpack/dist/loaders', + '__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: [ @@ -336,16 +437,25 @@ exports[`javascript configuration for ios 1`] = ` test: /\\\\.js$/, exclude: [ /node_modules/ - ], + ] + }, + /* config.module.rule('workers') */ + { + test: /\\\\.(js|ts)$/, use: [ - /* config.module.rule('js').use('babel-loader') */ + /* config.module.rule('workers').use('nativescript-worker-loader') */ { - loader: 'babel-loader', - options: { - generatorOpts: { - compact: false - } - } + loader: 'nativescript-worker-loader' + } + ] + }, + /* config.module.rule('xml') */ + { + test: /\\\\.xml$/, + use: [ + /* config.module.rule('xml').use('xml-namespace-loader') */ + { + loader: 'xml-namespace-loader' } ] }, @@ -403,19 +513,27 @@ exports[`javascript configuration for ios 1`] = ` } ] }, - /* config.module.rule('xml') */ + /* config.module.rule('hmr-core') */ { - test: /\\\\.xml$/, + test: /\\\\.js$/, + exclude: [ + /node_modules/, + '__jest__/src/app.js' + ], use: [ - /* config.module.rule('xml').use('xml-namespace-loader') */ + /* config.module.rule('hmr-core').use('nativescript-hot-loader') */ { - loader: 'xml-namespace-loader' + loader: 'nativescript-hot-loader', + options: { + appPath: '__jest__/src' + } } ] } ] }, optimization: { + runtimeChunk: 'single', splitChunks: { cacheGroups: { defaultVendor: { @@ -433,9 +551,15 @@ exports[`javascript configuration for ios 1`] = ` terserOptions: { compress: { collapse_vars: true, - sequences: true + sequences: true, + keep_infinity: true, + drop_console: false, + global_defs: { + __UGLIFIED__: true + } }, - keep_fnames: true + keep_fnames: true, + keep_classnames: true } } ) @@ -450,26 +574,23 @@ exports[`javascript configuration for ios 1`] = ` } } ), - /* config.plugin('CleanWebpackPlugin') */ - new CleanWebpackPlugin( - { - cleanOnceBeforeBuildPatterns: [ - '__jest__/platforms/ios/__jest__/app/**/*' - ], - verbose: false - } - ), /* 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, @@ -487,7 +608,8 @@ exports[`javascript configuration for ios 1`] = ` context: '__jest__/src', noErrorOnMissing: true, globOptions: { - dot: false + dot: false, + ignore: [] } }, { @@ -495,7 +617,8 @@ exports[`javascript configuration for ios 1`] = ` context: '__jest__/src', noErrorOnMissing: true, globOptions: { - dot: false + dot: false, + ignore: [] } }, { @@ -503,7 +626,8 @@ exports[`javascript configuration for ios 1`] = ` context: '__jest__/src', noErrorOnMissing: true, globOptions: { - dot: false + dot: false, + ignore: [] } } ] @@ -511,14 +635,14 @@ exports[`javascript configuration for ios 1`] = ` ), /* config.plugin('WatchStatePlugin') */ new WatchStatePlugin(), - /* config.plugin('ContextExclusionPluginPlugin') */ + /* config.plugin('ContextExclusionPlugin|__@nativescript_webpack_virtual_entry_javascript__') */ new ContextExclusionPlugin( - /__virtual_entry__\\\\.js$/ + /__@nativescript_webpack_virtual_entry_javascript__.js$/ ), /* config.plugin('VirtualModulesPlugin') */ new VirtualModulesPlugin( { - '__jest__/src/__virtual_entry__.js': 'require(\\\\'@nativescript/core/bundle-entry-points\\\\')\\\\nconst context = require.context(\\"~/\\", /* deep: */ true, /* filter: */ /.(xml|js|s?css)$/);\\\\nglobal.registerWebpackModules(context);' + '__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' } ) ], @@ -526,9 +650,9 @@ exports[`javascript configuration for ios 1`] = ` bundle: [ '@nativescript/core/globals/index.js', '__jest__/src/app.js', - '__jest__/src/__virtual_entry__.js' + '__jest__/src/__@nativescript_webpack_virtual_entry_javascript__' ], - 'tns_modules/@nativescript/core/inspector_modules': [ + 'tns_modules/inspector_modules': [ '@nativescript/core/inspector_modules' ] } diff --git a/packages/webpack5/__tests__/configuration/__snapshots__/react.spec.ts.snap b/packages/webpack5/__tests__/configuration/__snapshots__/react.spec.ts.snap index fb450cd3a..e1e15a5f2 100644 --- a/packages/webpack5/__tests__/configuration/__snapshots__/react.spec.ts.snap +++ b/packages/webpack5/__tests__/configuration/__snapshots__/react.spec.ts.snap @@ -7,14 +7,27 @@ exports[`react configuration > android > adds ReactRefreshWebpackPlugin when HMR 'package.json', '~/package.json' ], + externalsPresets: { + node: false + }, 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' + globalObject: 'global', + clean: true }, resolve: { symlinks: true, @@ -36,16 +49,42 @@ exports[`react configuration > android > adds ReactRefreshWebpackPlugin when HMR '.scss', '.android.json', '.json' + ], + modules: [ + '__jest__/node_modules', + 'node_modules' ] }, resolveLoader: { modules: [ - 'node_modules/@nativescript/webpack/dist/loaders', + '__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: [ @@ -84,16 +123,25 @@ exports[`react configuration > android > adds ReactRefreshWebpackPlugin when HMR test: /\\\\.js$/, exclude: [ /node_modules/ - ], + ] + }, + /* config.module.rule('workers') */ + { + test: /\\\\.(js|ts)$/, use: [ - /* config.module.rule('js').use('babel-loader') */ + /* config.module.rule('workers').use('nativescript-worker-loader') */ { - loader: 'babel-loader', - options: { - generatorOpts: { - compact: false - } - } + loader: 'nativescript-worker-loader' + } + ] + }, + /* config.module.rule('xml') */ + { + test: /\\\\.xml$/, + use: [ + /* config.module.rule('xml').use('xml-namespace-loader') */ + { + loader: 'xml-namespace-loader' } ] }, @@ -154,6 +202,7 @@ exports[`react configuration > android > adds ReactRefreshWebpackPlugin when HMR ] }, optimization: { + runtimeChunk: 'single', splitChunks: { cacheGroups: { defaultVendor: { @@ -171,9 +220,15 @@ exports[`react configuration > android > adds ReactRefreshWebpackPlugin when HMR terserOptions: { compress: { collapse_vars: false, - sequences: false + sequences: false, + keep_infinity: true, + drop_console: false, + global_defs: { + __UGLIFIED__: true + } }, - keep_fnames: true + keep_fnames: true, + keep_classnames: true } } ) @@ -188,26 +243,23 @@ exports[`react configuration > android > adds ReactRefreshWebpackPlugin when HMR } } ), - /* config.plugin('CleanWebpackPlugin') */ - new CleanWebpackPlugin( - { - cleanOnceBeforeBuildPatterns: [ - '__jest__/platforms/android/app/src/main/assets/app/**/*' - ], - verbose: false - } - ), /* 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, @@ -227,7 +279,8 @@ exports[`react configuration > android > adds ReactRefreshWebpackPlugin when HMR context: '__jest__/src', noErrorOnMissing: true, globOptions: { - dot: false + dot: false, + ignore: [] } }, { @@ -235,7 +288,8 @@ exports[`react configuration > android > adds ReactRefreshWebpackPlugin when HMR context: '__jest__/src', noErrorOnMissing: true, globOptions: { - dot: false + dot: false, + ignore: [] } }, { @@ -243,7 +297,8 @@ exports[`react configuration > android > adds ReactRefreshWebpackPlugin when HMR context: '__jest__/src', noErrorOnMissing: true, globOptions: { - dot: false + dot: false, + ignore: [] } } ] @@ -266,7 +321,9 @@ exports[`react configuration > android > adds ReactRefreshWebpackPlugin when HMR entry: { bundle: [ '@nativescript/core/globals/index.js', - '__jest__/src/app.js' + '__jest__/src/app.js', + '@nativescript/core/ui/frame', + '@nativescript/core/ui/frame/activity' ] } }" @@ -279,14 +336,27 @@ exports[`react configuration > android > base config 1`] = ` 'package.json', '~/package.json' ], + externalsPresets: { + node: false + }, 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' + globalObject: 'global', + clean: true }, resolve: { symlinks: true, @@ -308,16 +378,42 @@ exports[`react configuration > android > base config 1`] = ` '.scss', '.android.json', '.json' + ], + modules: [ + '__jest__/node_modules', + 'node_modules' ] }, resolveLoader: { modules: [ - 'node_modules/@nativescript/webpack/dist/loaders', + '__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: [ @@ -345,16 +441,25 @@ exports[`react configuration > android > base config 1`] = ` test: /\\\\.js$/, exclude: [ /node_modules/ - ], + ] + }, + /* config.module.rule('workers') */ + { + test: /\\\\.(js|ts)$/, use: [ - /* config.module.rule('js').use('babel-loader') */ + /* config.module.rule('workers').use('nativescript-worker-loader') */ { - loader: 'babel-loader', - options: { - generatorOpts: { - compact: false - } - } + loader: 'nativescript-worker-loader' + } + ] + }, + /* config.module.rule('xml') */ + { + test: /\\\\.xml$/, + use: [ + /* config.module.rule('xml').use('xml-namespace-loader') */ + { + loader: 'xml-namespace-loader' } ] }, @@ -415,6 +520,7 @@ exports[`react configuration > android > base config 1`] = ` ] }, optimization: { + runtimeChunk: 'single', splitChunks: { cacheGroups: { defaultVendor: { @@ -432,9 +538,15 @@ exports[`react configuration > android > base config 1`] = ` terserOptions: { compress: { collapse_vars: false, - sequences: false + sequences: false, + keep_infinity: true, + drop_console: false, + global_defs: { + __UGLIFIED__: true + } }, - keep_fnames: true + keep_fnames: true, + keep_classnames: true } } ) @@ -449,26 +561,23 @@ exports[`react configuration > android > base config 1`] = ` } } ), - /* config.plugin('CleanWebpackPlugin') */ - new CleanWebpackPlugin( - { - cleanOnceBeforeBuildPatterns: [ - '__jest__/platforms/android/app/src/main/assets/app/**/*' - ], - verbose: false - } - ), /* 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, @@ -488,7 +597,8 @@ exports[`react configuration > android > base config 1`] = ` context: '__jest__/src', noErrorOnMissing: true, globOptions: { - dot: false + dot: false, + ignore: [] } }, { @@ -496,7 +606,8 @@ exports[`react configuration > android > base config 1`] = ` context: '__jest__/src', noErrorOnMissing: true, globOptions: { - dot: false + dot: false, + ignore: [] } }, { @@ -504,7 +615,8 @@ exports[`react configuration > android > base config 1`] = ` context: '__jest__/src', noErrorOnMissing: true, globOptions: { - dot: false + dot: false, + ignore: [] } } ] @@ -516,7 +628,9 @@ exports[`react configuration > android > base config 1`] = ` entry: { bundle: [ '@nativescript/core/globals/index.js', - '__jest__/src/app.js' + '__jest__/src/app.js', + '@nativescript/core/ui/frame', + '@nativescript/core/ui/frame/activity' ] } }" @@ -529,14 +643,27 @@ exports[`react configuration > ios > adds ReactRefreshWebpackPlugin when HMR ena 'package.json', '~/package.json' ], + externalsPresets: { + node: false + }, 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', + path: '__jest__/platforms/ios/jest/app', pathinfo: false, publicPath: '', libraryTarget: 'commonjs', - globalObject: 'global' + globalObject: 'global', + clean: true }, resolve: { symlinks: true, @@ -558,16 +685,42 @@ exports[`react configuration > ios > adds ReactRefreshWebpackPlugin when HMR ena '.scss', '.ios.json', '.json' + ], + modules: [ + '__jest__/node_modules', + 'node_modules' ] }, resolveLoader: { modules: [ - 'node_modules/@nativescript/webpack/dist/loaders', + '__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: [ @@ -606,16 +759,25 @@ exports[`react configuration > ios > adds ReactRefreshWebpackPlugin when HMR ena test: /\\\\.js$/, exclude: [ /node_modules/ - ], + ] + }, + /* config.module.rule('workers') */ + { + test: /\\\\.(js|ts)$/, use: [ - /* config.module.rule('js').use('babel-loader') */ + /* config.module.rule('workers').use('nativescript-worker-loader') */ { - loader: 'babel-loader', - options: { - generatorOpts: { - compact: false - } - } + loader: 'nativescript-worker-loader' + } + ] + }, + /* config.module.rule('xml') */ + { + test: /\\\\.xml$/, + use: [ + /* config.module.rule('xml').use('xml-namespace-loader') */ + { + loader: 'xml-namespace-loader' } ] }, @@ -676,6 +838,7 @@ exports[`react configuration > ios > adds ReactRefreshWebpackPlugin when HMR ena ] }, optimization: { + runtimeChunk: 'single', splitChunks: { cacheGroups: { defaultVendor: { @@ -693,9 +856,15 @@ exports[`react configuration > ios > adds ReactRefreshWebpackPlugin when HMR ena terserOptions: { compress: { collapse_vars: true, - sequences: true + sequences: true, + keep_infinity: true, + drop_console: false, + global_defs: { + __UGLIFIED__: true + } }, - keep_fnames: true + keep_fnames: true, + keep_classnames: true } } ) @@ -710,26 +879,23 @@ exports[`react configuration > ios > adds ReactRefreshWebpackPlugin when HMR ena } } ), - /* config.plugin('CleanWebpackPlugin') */ - new CleanWebpackPlugin( - { - cleanOnceBeforeBuildPatterns: [ - '__jest__/platforms/ios/__jest__/app/**/*' - ], - verbose: false - } - ), /* 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, @@ -749,7 +915,8 @@ exports[`react configuration > ios > adds ReactRefreshWebpackPlugin when HMR ena context: '__jest__/src', noErrorOnMissing: true, globOptions: { - dot: false + dot: false, + ignore: [] } }, { @@ -757,7 +924,8 @@ exports[`react configuration > ios > adds ReactRefreshWebpackPlugin when HMR ena context: '__jest__/src', noErrorOnMissing: true, globOptions: { - dot: false + dot: false, + ignore: [] } }, { @@ -765,7 +933,8 @@ exports[`react configuration > ios > adds ReactRefreshWebpackPlugin when HMR ena context: '__jest__/src', noErrorOnMissing: true, globOptions: { - dot: false + dot: false, + ignore: [] } } ] @@ -790,7 +959,7 @@ exports[`react configuration > ios > adds ReactRefreshWebpackPlugin when HMR ena '@nativescript/core/globals/index.js', '__jest__/src/app.js' ], - 'tns_modules/@nativescript/core/inspector_modules': [ + 'tns_modules/inspector_modules': [ '@nativescript/core/inspector_modules' ] } @@ -804,14 +973,27 @@ exports[`react configuration > ios > base config 1`] = ` 'package.json', '~/package.json' ], + externalsPresets: { + node: false + }, 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', + path: '__jest__/platforms/ios/jest/app', pathinfo: false, publicPath: '', libraryTarget: 'commonjs', - globalObject: 'global' + globalObject: 'global', + clean: true }, resolve: { symlinks: true, @@ -833,16 +1015,42 @@ exports[`react configuration > ios > base config 1`] = ` '.scss', '.ios.json', '.json' + ], + modules: [ + '__jest__/node_modules', + 'node_modules' ] }, resolveLoader: { modules: [ - 'node_modules/@nativescript/webpack/dist/loaders', + '__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: [ @@ -870,16 +1078,25 @@ exports[`react configuration > ios > base config 1`] = ` test: /\\\\.js$/, exclude: [ /node_modules/ - ], + ] + }, + /* config.module.rule('workers') */ + { + test: /\\\\.(js|ts)$/, use: [ - /* config.module.rule('js').use('babel-loader') */ + /* config.module.rule('workers').use('nativescript-worker-loader') */ { - loader: 'babel-loader', - options: { - generatorOpts: { - compact: false - } - } + loader: 'nativescript-worker-loader' + } + ] + }, + /* config.module.rule('xml') */ + { + test: /\\\\.xml$/, + use: [ + /* config.module.rule('xml').use('xml-namespace-loader') */ + { + loader: 'xml-namespace-loader' } ] }, @@ -940,6 +1157,7 @@ exports[`react configuration > ios > base config 1`] = ` ] }, optimization: { + runtimeChunk: 'single', splitChunks: { cacheGroups: { defaultVendor: { @@ -957,9 +1175,15 @@ exports[`react configuration > ios > base config 1`] = ` terserOptions: { compress: { collapse_vars: true, - sequences: true + sequences: true, + keep_infinity: true, + drop_console: false, + global_defs: { + __UGLIFIED__: true + } }, - keep_fnames: true + keep_fnames: true, + keep_classnames: true } } ) @@ -974,26 +1198,23 @@ exports[`react configuration > ios > base config 1`] = ` } } ), - /* config.plugin('CleanWebpackPlugin') */ - new CleanWebpackPlugin( - { - cleanOnceBeforeBuildPatterns: [ - '__jest__/platforms/ios/__jest__/app/**/*' - ], - verbose: false - } - ), /* 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, @@ -1013,7 +1234,8 @@ exports[`react configuration > ios > base config 1`] = ` context: '__jest__/src', noErrorOnMissing: true, globOptions: { - dot: false + dot: false, + ignore: [] } }, { @@ -1021,7 +1243,8 @@ exports[`react configuration > ios > base config 1`] = ` context: '__jest__/src', noErrorOnMissing: true, globOptions: { - dot: false + dot: false, + ignore: [] } }, { @@ -1029,7 +1252,8 @@ exports[`react configuration > ios > base config 1`] = ` context: '__jest__/src', noErrorOnMissing: true, globOptions: { - dot: false + dot: false, + ignore: [] } } ] @@ -1043,7 +1267,7 @@ exports[`react configuration > ios > base config 1`] = ` '@nativescript/core/globals/index.js', '__jest__/src/app.js' ], - 'tns_modules/@nativescript/core/inspector_modules': [ + 'tns_modules/inspector_modules': [ '@nativescript/core/inspector_modules' ] } diff --git a/packages/webpack5/__tests__/configuration/__snapshots__/svelte.spec.ts.snap b/packages/webpack5/__tests__/configuration/__snapshots__/svelte.spec.ts.snap index 161071f07..2066d45fa 100644 --- a/packages/webpack5/__tests__/configuration/__snapshots__/svelte.spec.ts.snap +++ b/packages/webpack5/__tests__/configuration/__snapshots__/svelte.spec.ts.snap @@ -7,14 +7,27 @@ exports[`svelte configuration for android 1`] = ` 'package.json', '~/package.json' ], + externalsPresets: { + node: false + }, 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' + globalObject: 'global', + clean: true }, resolve: { symlinks: true, @@ -35,16 +48,42 @@ exports[`svelte configuration for android 1`] = ` '.scss', '.android.json', '.json' + ], + modules: [ + '__jest__/node_modules', + 'node_modules' ] }, resolveLoader: { modules: [ - 'node_modules/@nativescript/webpack/dist/loaders', + '__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: [ @@ -71,16 +110,25 @@ exports[`svelte configuration for android 1`] = ` test: /\\\\.js$/, exclude: [ /node_modules/ - ], + ] + }, + /* config.module.rule('workers') */ + { + test: /\\\\.(js|ts)$/, use: [ - /* config.module.rule('js').use('babel-loader') */ + /* config.module.rule('workers').use('nativescript-worker-loader') */ { - loader: 'babel-loader', - options: { - generatorOpts: { - compact: false - } - } + loader: 'nativescript-worker-loader' + } + ] + }, + /* config.module.rule('xml') */ + { + test: /\\\\.xml$/, + use: [ + /* config.module.rule('xml').use('xml-namespace-loader') */ + { + loader: 'xml-namespace-loader' } ] }, @@ -150,12 +198,7 @@ exports[`svelte configuration for android 1`] = ` loader: 'svelte-loader-hot', options: { dev: true, - preprocess: [ - undefined, - { - markup: function () { /* omitted long function */ } - } - ], + preprocess: undefined, hotReload: true, hotOptions: { injectCss: false, @@ -169,6 +212,7 @@ exports[`svelte configuration for android 1`] = ` ] }, optimization: { + runtimeChunk: 'single', splitChunks: { cacheGroups: { defaultVendor: { @@ -186,9 +230,15 @@ exports[`svelte configuration for android 1`] = ` terserOptions: { compress: { collapse_vars: false, - sequences: false + sequences: false, + keep_infinity: true, + drop_console: false, + global_defs: { + __UGLIFIED__: true + } }, - keep_fnames: true + keep_fnames: true, + keep_classnames: true } } ) @@ -203,26 +253,23 @@ exports[`svelte configuration for android 1`] = ` } } ), - /* config.plugin('CleanWebpackPlugin') */ - new CleanWebpackPlugin( - { - cleanOnceBeforeBuildPatterns: [ - '__jest__/platforms/android/app/src/main/assets/app/**/*' - ], - verbose: false - } - ), /* 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, @@ -240,7 +287,8 @@ exports[`svelte configuration for android 1`] = ` context: '__jest__/src', noErrorOnMissing: true, globOptions: { - dot: false + dot: false, + ignore: [] } }, { @@ -248,7 +296,8 @@ exports[`svelte configuration for android 1`] = ` context: '__jest__/src', noErrorOnMissing: true, globOptions: { - dot: false + dot: false, + ignore: [] } }, { @@ -256,7 +305,8 @@ exports[`svelte configuration for android 1`] = ` context: '__jest__/src', noErrorOnMissing: true, globOptions: { - dot: false + dot: false, + ignore: [] } } ] @@ -268,7 +318,9 @@ exports[`svelte configuration for android 1`] = ` entry: { bundle: [ '@nativescript/core/globals/index.js', - '__jest__/src/app.js' + '__jest__/src/app.js', + '@nativescript/core/ui/frame', + '@nativescript/core/ui/frame/activity' ] } }" @@ -281,14 +333,27 @@ exports[`svelte configuration for ios 1`] = ` 'package.json', '~/package.json' ], + externalsPresets: { + node: false + }, 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', + path: '__jest__/platforms/ios/jest/app', pathinfo: false, publicPath: '', libraryTarget: 'commonjs', - globalObject: 'global' + globalObject: 'global', + clean: true }, resolve: { symlinks: true, @@ -309,16 +374,42 @@ exports[`svelte configuration for ios 1`] = ` '.scss', '.ios.json', '.json' + ], + modules: [ + '__jest__/node_modules', + 'node_modules' ] }, resolveLoader: { modules: [ - 'node_modules/@nativescript/webpack/dist/loaders', + '__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: [ @@ -345,16 +436,25 @@ exports[`svelte configuration for ios 1`] = ` test: /\\\\.js$/, exclude: [ /node_modules/ - ], + ] + }, + /* config.module.rule('workers') */ + { + test: /\\\\.(js|ts)$/, use: [ - /* config.module.rule('js').use('babel-loader') */ + /* config.module.rule('workers').use('nativescript-worker-loader') */ { - loader: 'babel-loader', - options: { - generatorOpts: { - compact: false - } - } + loader: 'nativescript-worker-loader' + } + ] + }, + /* config.module.rule('xml') */ + { + test: /\\\\.xml$/, + use: [ + /* config.module.rule('xml').use('xml-namespace-loader') */ + { + loader: 'xml-namespace-loader' } ] }, @@ -424,12 +524,7 @@ exports[`svelte configuration for ios 1`] = ` loader: 'svelte-loader-hot', options: { dev: true, - preprocess: [ - undefined, - { - markup: function () { /* omitted long function */ } - } - ], + preprocess: undefined, hotReload: true, hotOptions: { injectCss: false, @@ -443,6 +538,7 @@ exports[`svelte configuration for ios 1`] = ` ] }, optimization: { + runtimeChunk: 'single', splitChunks: { cacheGroups: { defaultVendor: { @@ -460,9 +556,15 @@ exports[`svelte configuration for ios 1`] = ` terserOptions: { compress: { collapse_vars: true, - sequences: true + sequences: true, + keep_infinity: true, + drop_console: false, + global_defs: { + __UGLIFIED__: true + } }, - keep_fnames: true + keep_fnames: true, + keep_classnames: true } } ) @@ -477,26 +579,23 @@ exports[`svelte configuration for ios 1`] = ` } } ), - /* config.plugin('CleanWebpackPlugin') */ - new CleanWebpackPlugin( - { - cleanOnceBeforeBuildPatterns: [ - '__jest__/platforms/ios/__jest__/app/**/*' - ], - verbose: false - } - ), /* 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, @@ -514,7 +613,8 @@ exports[`svelte configuration for ios 1`] = ` context: '__jest__/src', noErrorOnMissing: true, globOptions: { - dot: false + dot: false, + ignore: [] } }, { @@ -522,7 +622,8 @@ exports[`svelte configuration for ios 1`] = ` context: '__jest__/src', noErrorOnMissing: true, globOptions: { - dot: false + dot: false, + ignore: [] } }, { @@ -530,7 +631,8 @@ exports[`svelte configuration for ios 1`] = ` context: '__jest__/src', noErrorOnMissing: true, globOptions: { - dot: false + dot: false, + ignore: [] } } ] @@ -544,7 +646,7 @@ exports[`svelte configuration for ios 1`] = ` '@nativescript/core/globals/index.js', '__jest__/src/app.js' ], - 'tns_modules/@nativescript/core/inspector_modules': [ + 'tns_modules/inspector_modules': [ '@nativescript/core/inspector_modules' ] } diff --git a/packages/webpack5/__tests__/configuration/__snapshots__/typescript.spec.ts.snap b/packages/webpack5/__tests__/configuration/__snapshots__/typescript.spec.ts.snap new file mode 100644 index 000000000..b63248635 --- /dev/null +++ b/packages/webpack5/__tests__/configuration/__snapshots__/typescript.spec.ts.snap @@ -0,0 +1,660 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`typescript configuration for android 1`] = ` +"{ + mode: 'development', + externals: [ + 'package.json', + '~/package.json' + ], + externalsPresets: { + node: false + }, + 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/ + ] + }, + /* config.module.rule('workers') */ + { + test: /\\\\.(js|ts)$/, + use: [ + /* config.module.rule('workers').use('nativescript-worker-loader') */ + { + loader: 'nativescript-worker-loader' + } + ] + }, + /* config.module.rule('xml') */ + { + test: /\\\\.xml$/, + use: [ + /* config.module.rule('xml').use('xml-namespace-loader') */ + { + loader: 'xml-namespace-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('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: { + runtimeChunk: 'single', + 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|(? { + // in tests we mock both plugins so they both show up in the snapshot + // allowing us to verify the passed configuration is correct. class AngularCompilerPlugin {} + class AngularWebpackPlugin {} + return { AngularCompilerPlugin, + AngularWebpackPlugin, }; }, { virtual: true } ); -describe.only('angular configuration', () => { +describe('angular configuration', () => { const platforms = ['ios', 'android']; + let fsExistsSyncSpy: jest.SpiedFunction; + let polyfillsPath: string | boolean = false; + + beforeAll(() => { + const fs = require('fs'); + const original = fs.existsSync; + fsExistsSyncSpy = jest.spyOn(fs, 'existsSync'); + + fsExistsSyncSpy.mockImplementation((path) => { + if (path === '__jest__/tsconfig.json') { + return true; + } + + if (polyfillsPath && path === polyfillsPath) { + return true; + } + + return original.call(fs, path); + }); + }); + + afterAll(() => { + fsExistsSyncSpy.mockRestore(); + }); for (let platform of platforms) { it(`for ${platform}`, () => { @@ -24,5 +54,27 @@ describe.only('angular configuration', () => { }); expect(angular(new Config()).toString()).toMatchSnapshot(); }); + + it(`loads polyfills.${platform}.ts into the bundle entry if it exists `, () => { + polyfillsPath = `__jest__/src/polyfills.${platform}.ts`; + + init({ + [platform]: true, + }); + expect(angular(new Config()).entry('bundle').values()).toMatchSnapshot(); + + polyfillsPath = false; + }); } + + it(`loads polyfills.ts into the bundle entry if it exists `, () => { + polyfillsPath = `__jest__/src/polyfills.ts`; + + init({ + ios: true, + }); + expect(angular(new Config()).entry('bundle').values()).toMatchSnapshot(); + + polyfillsPath = false; + }); }); diff --git a/packages/webpack5/__tests__/configuration/base.spec.ts b/packages/webpack5/__tests__/configuration/base.spec.ts new file mode 100644 index 000000000..df76f6ffd --- /dev/null +++ b/packages/webpack5/__tests__/configuration/base.spec.ts @@ -0,0 +1,104 @@ +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, + + // only test in base config to make sure App_Resources + // are properly excluded from the copy-rules + appResourcesPath: 'custom_app_resources', + }); + 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, + }); + }); +}); diff --git a/packages/webpack5/__tests__/configuration/javascript.spec.ts b/packages/webpack5/__tests__/configuration/javascript.spec.ts index dcafbafc6..7b026f82e 100644 --- a/packages/webpack5/__tests__/configuration/javascript.spec.ts +++ b/packages/webpack5/__tests__/configuration/javascript.spec.ts @@ -1,8 +1,9 @@ import Config from 'webpack-chain'; + import javascript from '../../src/configuration/javascript'; import { init } from '../../src'; -describe.only('javascript configuration', () => { +describe('javascript configuration', () => { const platforms = ['ios', 'android']; for (let platform of platforms) { diff --git a/packages/webpack5/__tests__/configuration/react.spec.ts b/packages/webpack5/__tests__/configuration/react.spec.ts index 9283d58f5..74a10d111 100644 --- a/packages/webpack5/__tests__/configuration/react.spec.ts +++ b/packages/webpack5/__tests__/configuration/react.spec.ts @@ -1,4 +1,5 @@ import Config from 'webpack-chain'; + import react from '../../src/configuration/react'; import { init } from '../../src'; diff --git a/packages/webpack5/__tests__/configuration/svelte.spec.ts b/packages/webpack5/__tests__/configuration/svelte.spec.ts index b835cacba..78c903219 100644 --- a/packages/webpack5/__tests__/configuration/svelte.spec.ts +++ b/packages/webpack5/__tests__/configuration/svelte.spec.ts @@ -1,13 +1,11 @@ import Config from 'webpack-chain'; + import svelte from '../../src/configuration/svelte'; import { init } from '../../src'; -import { mockFile } from '../../scripts/jest.mockFiles'; -mockFile('./svelte.config.js', ''); -// jest.mock('__jest__/svelte.config.js', () => { -// }, { virtual: true }) +jest.mock('__jest__/svelte.config.js', () => {}, { virtual: true }); -describe.only('svelte configuration', () => { +describe('svelte configuration', () => { const platforms = ['ios', 'android']; for (let platform of platforms) { diff --git a/packages/webpack5/__tests__/configuration/typescript.spec.ts b/packages/webpack5/__tests__/configuration/typescript.spec.ts new file mode 100644 index 000000000..0fe124b57 --- /dev/null +++ b/packages/webpack5/__tests__/configuration/typescript.spec.ts @@ -0,0 +1,51 @@ +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(); + }); + } + + it('filter typescript declaration files', () => { + init({ + ios: true, + }); + + const tsConfig = typescript(new Config()); + let regex: RegExp; + + // Get the filterRE from the typescript configuration + tsConfig.plugin('VirtualModulesPlugin').tap((args) => { + const options = args[0]; + const virtualConfig: string = options[Object.keys(options)[0]]; + const filterLine = virtualConfig + .split('\n') + .find((v) => v.includes('filter')); + const matches = filterLine.match(/\/(?\S+)\//); + + if (matches) { + regex = new RegExp(matches.groups.filter); + } + + return args; + }); + + expect(regex).toBeDefined(); + + expect('test.ts').toMatch(regex); + expect('test.d.ts').not.toMatch(regex); + expect('tested.ts').toMatch(regex); + expect('tested.d.ts').not.toMatch(regex); + expect('test.d.tested.ts').toMatch(regex); + expect('test.d.tested.d.ts').not.toMatch(regex); + }); +}); diff --git a/packages/webpack5/__tests__/configuration/vue.spec.ts b/packages/webpack5/__tests__/configuration/vue.spec.ts index ec6a626f5..2234ab7bb 100644 --- a/packages/webpack5/__tests__/configuration/vue.spec.ts +++ b/packages/webpack5/__tests__/configuration/vue.spec.ts @@ -1,8 +1,9 @@ import Config from 'webpack-chain'; + import vue from '../../src/configuration/vue'; import { init } from '../../src'; -describe.only('vue configuration', () => { +describe('vue configuration', () => { const platforms = ['ios', 'android']; for (let platform of platforms) { diff --git a/packages/webpack5/__tests__/helpers/fileReplacements.spec.ts b/packages/webpack5/__tests__/helpers/fileReplacements.spec.ts new file mode 100644 index 000000000..621348d9f --- /dev/null +++ b/packages/webpack5/__tests__/helpers/fileReplacements.spec.ts @@ -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'], + ]); + }); +}); diff --git a/packages/webpack5/__tests__/index.spec.ts b/packages/webpack5/__tests__/index.spec.ts index 53814ce17..40931b186 100644 --- a/packages/webpack5/__tests__/index.spec.ts +++ b/packages/webpack5/__tests__/index.spec.ts @@ -72,7 +72,7 @@ describe('@nativescript/webpack', () => { }); it('applies merge configs', () => { - const dummyEnv = { env: true }; + const dummyEnv = { foo: true }; webpack.init(dummyEnv); webpack.useConfig(false); @@ -96,7 +96,7 @@ describe('@nativescript/webpack', () => { }); it('merges mutate config', () => { - const dummyEnv = { env: true }; + const dummyEnv = { foo: true }; webpack.init(dummyEnv); webpack.useConfig(false); @@ -110,7 +110,7 @@ describe('@nativescript/webpack', () => { }); it('merges returned config', () => { - const dummyEnv = { env: true }; + const dummyEnv = { foo: true }; webpack.init(dummyEnv); webpack.useConfig(false); @@ -126,7 +126,7 @@ describe('@nativescript/webpack', () => { }); it('merges objects', () => { - const dummyEnv = { env: true }; + const dummyEnv = { foo: true }; webpack.init(dummyEnv); webpack.useConfig(false); diff --git a/packages/webpack5/__tests__/loaders/xml-namespace-loader.spec.ts b/packages/webpack5/__tests__/loaders/xml-namespace-loader.spec.ts index 59136248e..adcde7dc9 100644 --- a/packages/webpack5/__tests__/loaders/xml-namespace-loader.spec.ts +++ b/packages/webpack5/__tests__/loaders/xml-namespace-loader.spec.ts @@ -1,6 +1,7 @@ -import xmlNsLoader from '../../src/loaders/xml-namespace-loader'; import dedent from 'ts-dedent'; +import xmlNsLoader from '../../src/loaders/xml-namespace-loader'; + const CODE_FILE = dedent` diff --git a/packages/webpack5/package.json b/packages/webpack5/package.json index 9ed0e6d97..da7b13e2a 100644 --- a/packages/webpack5/package.json +++ b/packages/webpack5/package.json @@ -1,7 +1,7 @@ { "name": "@nativescript/webpack", - "version": "4.0.0-dev", - "private": true, + "version": "5.0.0-dev", + "private": false, "main": "dist/index.js", "files": [ "dist" @@ -13,60 +13,67 @@ "scripts": { "build": "tsc --project tsconfig.build.json", "test": "jest", - "prepack": "npm run build && cp -R src/stubs dist/stubs && chmod +x dist/bin/index.js" + "copy-stubs": "mkdirp dist/stubs && cp -R src/stubs/* dist/stubs", + "prepack": "npm run test && npm run build && npm run copy-stubs && chmod +x dist/bin/index.js" }, "dependencies": { - "@babel/core": "^7.12.9", - "@pmmmwh/react-refresh-webpack-plugin": "^0.4.3", - "@types/sax": "^1.2.1", - "babel-loader": "^8.2.1", - "chalk": "^4.1.0", - "clean-webpack-plugin": "^3.0.0", - "cli-highlight": "^2.1.8", - "commander": "^6.2.0", - "copy-webpack-plugin": "^6.3.2", + "@babel/core": "^7.0.0", + "@pmmmwh/react-refresh-webpack-plugin": "^0.4.0", + "acorn": "^8.0.0", + "acorn-stage3": "^4.0.0", + "babel-loader": "^8.0.0", + "chalk": "^4.0.0", + "cli-highlight": "^2.0.0", + "commander": "^7.0.0", + "copy-webpack-plugin": "^9.0.0", "css": "^3.0.0", - "css-loader": "^5.0.1", - "fork-ts-checker-webpack-plugin": "^6.0.3", + "css-loader": "^5.0.0", + "dotenv-webpack": "^7.0.0", + "fork-ts-checker-webpack-plugin": "^6.0.0", "loader-utils": "^2.0.0", - "micromatch": "^4.0.2", - "postcss": "^8.1.13", - "postcss-import": "^13.0.0", - "postcss-loader": "^4.1.0", - "raw-loader": "^4.0.2", - "react-refresh": "^0.9.0", - "sass": "^1.29.0", - "sass-loader": "^10.1.0", - "sax": "^1.2.4", - "source-map": "^0.7.3", - "terser-webpack-plugin": "^5.0.3", + "lodash.get": "^4.0.0", + "micromatch": "^4.0.0", + "postcss": "^8.0.0", + "postcss-import": "^14.0.0", + "postcss-loader": "^5.0.0", + "raw-loader": "^4.0.0", + "react-refresh": "~0.8.3", + "sass": "^1.0.0", + "sass-loader": "^12.0.0", + "sax": "^1.0.0", + "source-map": "^0.7.0", + "terser-webpack-plugin": "^5.0.0", "ts-dedent": "^2.0.0", - "ts-loader": "^8.0.11", - "vue-loader": "^15.9.5", - "webpack": "^5.6.0", - "webpack-bundle-analyzer": "^4.1.0", - "webpack-chain": "^6.5.1", - "webpack-cli": "^4.2.0", - "webpack-merge": "^5.4.0", - "webpack-virtual-modules": "^0.4.1", - "worker-plugin": "^5.0.0" + "ts-loader": "^9.0.0", + "vue-loader": "^15.0.0", + "webpack": "^5.34.0", + "webpack-bundle-analyzer": "^4.0.0", + "webpack-chain": "^6.0.0", + "webpack-cli": "^4.0.0", + "webpack-merge": "^5.0.0", + "webpack-virtual-modules": "^0.4.0" }, "devDependencies": { - "@types/css": "^0.0.31", - "@types/jest": "^26.0.15", - "@types/loader-utils": "^2.0.1", - "@types/micromatch": "^4.0.1", - "@types/terser-webpack-plugin": "^5.0.2", - "@types/webpack-virtual-modules": "^0.1.0", - "jest": "^26.6.3", - "jest-matcher-utils": "^26.6.2", - "memfs": "^3.2.0", - "nativescript-vue-template-compiler": "^2.8.2", - "ts-jest": "^26.4.4", - "typescript": "^4.1.2", - "unionfs": "^4.4.0" + "@types/css": "0.0.31", + "@types/jest": "26.0.23", + "@types/loader-utils": "2.0.2", + "@types/lodash.get": "4.4.6", + "@types/micromatch": "4.0.1", + "@types/sax": "1.2.1", + "@types/terser-webpack-plugin": "5.0.3", + "@types/webpack-virtual-modules": "0.1.1", + "jest": "27.0.3", + "jest-matcher-utils": "27.0.2", + "nativescript-vue-template-compiler": "2.9.0", + "ts-jest": "27.0.2", + "typescript": "4.3.2" }, "peerDependencies": { "nativescript-vue-template-compiler": "^2.8.1" + }, + "peerDependenciesMeta": { + "nativescript-vue-template-compiler": { + "optional": true + } } } diff --git a/packages/webpack5/scripts/jest.copyRules.ts b/packages/webpack5/scripts/jest.copyRules.ts index 3699686ea..7aa7610a6 100644 --- a/packages/webpack5/scripts/jest.copyRules.ts +++ b/packages/webpack5/scripts/jest.copyRules.ts @@ -1,6 +1,7 @@ -import { copyRules } from '../src/helpers/copyRules'; +import { copyRules, additionalCopyRules } from '../src/helpers/copyRules'; afterEach(() => { // Clear copy rules copyRules.clear(); + additionalCopyRules.length = 0; }); diff --git a/packages/webpack5/scripts/jest.globals.d.ts b/packages/webpack5/scripts/jest.globals.d.ts index 07e2a0691..ede612063 100644 --- a/packages/webpack5/scripts/jest.globals.d.ts +++ b/packages/webpack5/scripts/jest.globals.d.ts @@ -6,7 +6,3 @@ declare namespace jest { toHaveBeenPrinted(): R; } } - -declare global { - function mockFile(path: string, content: string); -} diff --git a/packages/webpack5/scripts/jest.mockFiles.ts b/packages/webpack5/scripts/jest.mockFiles.ts deleted file mode 100644 index ac61a943a..000000000 --- a/packages/webpack5/scripts/jest.mockFiles.ts +++ /dev/null @@ -1,42 +0,0 @@ -import dedent from 'ts-dedent'; - -const mockedFiles: { [path: string]: string } = {}; - -export function mockFile(path, content) { - const unionFS = require('unionfs').default; - const Volume = require('memfs').Volume; - - // reset to fs - unionFS.reset(); - - // add mocked file - mockedFiles[path] = dedent(content); - - // create new volume - const vol = Volume.fromJSON(mockedFiles, '__jest__'); - - // use the new volume - unionFS.use(vol as any); -} - -// a virtual mock for package.json -jest.mock( - '__jest__/package.json', - () => ({ - main: 'src/app.js', - devDependencies: { - typescript: '*', - }, - }), - { virtual: true } -); - -jest.mock('fs', () => { - const fs = jest.requireActual('fs'); - const unionFS = require('unionfs').default; - unionFS.reset = () => { - unionFS.fss = [fs]; - }; - - return unionFS.use(fs); -}); diff --git a/packages/webpack5/scripts/jest.setup.ts b/packages/webpack5/scripts/jest.setup.ts index 3928cb8d0..65a90cfae 100644 --- a/packages/webpack5/scripts/jest.setup.ts +++ b/packages/webpack5/scripts/jest.setup.ts @@ -1,4 +1,3 @@ -import './jest.mockFiles'; // we are mocking the cwd for the tests, since webpack needs absolute paths // and we don't want them in tests process.cwd = () => '__jest__'; @@ -14,13 +13,51 @@ jest.mock('cosmiconfig', () => ({ }, })); +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); + return path.join(...args.filter(Boolean)); } const resolved = path.resolve(...args); @@ -29,7 +66,35 @@ jest.mock('path', () => { 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 } +); diff --git a/packages/webpack5/src/bin/devServer.ts b/packages/webpack5/src/bin/devServer.ts new file mode 100644 index 000000000..22dd01e44 --- /dev/null +++ b/packages/webpack5/src/bin/devServer.ts @@ -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) +// } +// diff --git a/packages/webpack5/src/bin/index.ts b/packages/webpack5/src/bin/index.ts index 9bdcf2261..fa530ffe9 100644 --- a/packages/webpack5/src/bin/index.ts +++ b/packages/webpack5/src/bin/index.ts @@ -1,11 +1,14 @@ -#!/user/bin/env node +#!/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' @@ -20,6 +23,8 @@ 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.') @@ -35,4 +40,83 @@ program 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 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); diff --git a/packages/webpack5/src/cli/parseEnvFlags.ts b/packages/webpack5/src/cli/parseEnvFlags.ts new file mode 100644 index 000000000..e11bb26d3 --- /dev/null +++ b/packages/webpack5/src/cli/parseEnvFlags.ts @@ -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; +} diff --git a/packages/webpack5/src/configuration/angular.ts b/packages/webpack5/src/configuration/angular.ts index 75e517a9f..21183500f 100644 --- a/packages/webpack5/src/configuration/angular.ts +++ b/packages/webpack5/src/configuration/angular.ts @@ -1,19 +1,36 @@ +import { extname, resolve } from 'path'; import Config from 'webpack-chain'; -import path from 'path'; +import { existsSync } from 'fs'; -import { getProjectRootPath } from '../helpers/project'; +import { getProjectFilePath } from '../helpers/project'; import { env as _env, IWebpackEnv } from '../index'; -import { getEntryPath } from '../helpers/platform'; +import { + getEntryDirPath, + getEntryPath, + getPlatformName, +} from '../helpers/platform'; import base from './base'; export default function (config: Config, env: IWebpackEnv = _env): Config { base(config, env); - const tsConfigPath = path.join(getProjectRootPath(), 'tsconfig.json'); + const platform = getPlatformName(); + + 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)$/) @@ -32,18 +49,178 @@ export default function (config: Config, env: IWebpackEnv = _env): Config { .use('raw-loader') .loader('raw-loader'); - config.plugin('AngularCompilerPlugin').use(getAngularCompilerPlugin(), [ - { - tsConfigPath, - mainPath: getEntryPath(), - platformTransformers: [require('../transformers/NativeClass').default], - }, - ]); + // exclude component css files from the normal css rule + config.module + .rule('css') + .exclude // exclude *.component.{platform}.css + .add(/\.component(\.\w+)?\.css$/); + + // and instead use raw-loader, since that's what angular expects + config.module + .rule('css|component') + .test(/\.component(\.\w+)?\.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 // exclude *.component.{platform}.scss + .add(/\.component(\.\w+)?\.scss$/); + + // and instead use raw-loader, since that's what angular expects + config.module + .rule('scss|component') + .test(/\.component(\.\w+)?\.scss$/) + .use('raw-loader') + .loader('raw-loader') + .end() + .use('postcss-loader') + .loader('postcss-loader') + .options(postCSSOptions) + .end() + .use('sass-loader') + .loader('sass-loader'); + + const angularCompilerPlugin = getAngularCompilerPlugin(); + if (angularCompilerPlugin) { + config.plugin('AngularCompilerPlugin').use(angularCompilerPlugin, [ + { + tsConfigPath, + mainPath: getEntryPath(), + // disable type checking in a forked process - it ignores + // the hostReplacementPaths and prints errors about + // platform suffixed files, even though they are + // working as expected. + forkTypeChecker: false, + hostReplacementPaths(path: string) { + const ext = extname(path); + const platformExt = `.${platform}${ext}`; + + // already includes a platform specific extension - ignore + if (path.includes(platformExt)) { + return path; + } + + const platformPath = path.replace(ext, platformExt); + // check if the same file exists with a platform suffix and return if it does. + if (existsSync(platformPath)) { + // console.log(`[hostReplacementPaths] resolving "${path}" to "${platformPath}"`); + return platformPath; + } + + // just return the original path otherwise + return path; + }, + platformTransformers: [require('../transformers/NativeClass').default], + }, + ]); + } + + const angularWebpackPlugin = getAngularWebpackPlugin(); + if (angularWebpackPlugin) { + // angular no longer supports transformers. + // so we patch their method until they do + // https://github.com/angular/angular-cli/pull/21046 + const originalCreateFileEmitter = + angularWebpackPlugin.prototype.createFileEmitter; + angularWebpackPlugin.prototype.createFileEmitter = function ( + ...args: any[] + ) { + let transformers = args[1] || {}; + if (!transformers.before) { + transformers.before = []; + } + transformers.before.push(require('../transformers/NativeClass').default); + args[1] = transformers; + return originalCreateFileEmitter.apply(this, args); + }; + config.plugin('AngularWebpackPlugin').use(angularWebpackPlugin, [ + { + tsconfig: tsConfigPath, + directTemplateLoading: false, + }, + ]); + + config.when(env.hmr, (config) => { + config.module + .rule('angular-hmr') + .enforce('post') + .test(getEntryPath()) + .use('angular-hot-loader') + .loader('angular-hot-loader'); + }); + } + + // look for platform specific polyfills first + // falling back to independent polyfills + const polyfillsPath = [ + resolve(getEntryDirPath(), `polyfills.${platform}.ts`), + resolve(getEntryDirPath(), `polyfills.ts`), + ].find((path) => existsSync(path)); + + if (polyfillsPath) { + const paths = config.entry('bundle').values(); + + // replace globals with the polyfills file which + // should handle loading the correct globals + // and any additional polyfills required. + if (paths.includes('@nativescript/core/globals/index.js')) { + paths[ + paths.indexOf('@nativescript/core/globals/index.js') + ] = polyfillsPath; + + // replace paths with the update paths + config.entry('bundle').clear().merge(paths); + } + } + + // 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/, + // Additional rules to suppress warnings that are safe to ignore + { + module: /@angular\/core\/(__ivy_ngcc__\/)?fesm2015\/core.js/, + message: /Critical dependency: the request of a dependency is an expression/, + }, + /core\/profiling/, + /core\/ui\/styling/, + ]) + ); return config; } -function getAngularCompilerPlugin() { +function getAngularCompilerPlugin(): any { const { AngularCompilerPlugin } = require('@ngtools/webpack'); return AngularCompilerPlugin; } + +function getAngularWebpackPlugin(): any { + const { AngularWebpackPlugin } = require('@ngtools/webpack'); + return AngularWebpackPlugin; +} diff --git a/packages/webpack5/src/configuration/base.ts b/packages/webpack5/src/configuration/base.ts index 160838bbe..bac29c123 100644 --- a/packages/webpack5/src/configuration/base.ts +++ b/packages/webpack5/src/configuration/base.ts @@ -1,21 +1,26 @@ -import { DefinePlugin, HotModuleReplacementPlugin } from 'webpack'; +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 { CleanWebpackPlugin } from 'clean-webpack-plugin'; import TerserPlugin from 'terser-webpack-plugin'; -// import { WatchStateLoggerPlugin } from '../plugins/WatchStateLoggerPlugin'; import { PlatformSuffixPlugin } from '../plugins/PlatformSuffixPlugin'; +import { applyFileReplacements } from '../helpers/fileReplacements'; import { addCopyRule, applyCopyRules } from '../helpers/copyRules'; import { WatchStatePlugin } from '../plugins/WatchStatePlugin'; -import { getValue } from '../helpers/config'; import { projectUsesCustomFlavor } from '../helpers/flavor'; -import { getProjectRootPath } from '../helpers/project'; +import { getProjectFilePath } from '../helpers/project'; import { hasDependency } from '../helpers/dependencies'; -import { IWebpackEnv } from '../index'; +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, @@ -23,7 +28,7 @@ import { getEntryPath, } from '../helpers/platform'; -export default function (config: Config, env: IWebpackEnv): Config { +export default function (config: Config, env: IWebpackEnv = _env): Config { const entryPath = getEntryPath(); const platform = getPlatformName(); const mode = env.production ? 'production' : 'development'; @@ -40,8 +45,39 @@ export default function (config: Config, env: IWebpackEnv): Config { // resolved at runtime config.externals(['package.json', '~/package.json']); - // todo: devtool - config.devtool('inline-source-map'); + // disable marking built-in node modules as external + // since they are not available at runtime and + // should be bundled (requires polyfills) + // for example `npm i --save url` to + // polyfill the node url module. + config.set('externalsPresets', { + node: false, + }); + + const getSourceMapType = (map: string | boolean): Config.DevTool => { + const defaultSourceMap = 'inline-source-map'; + + if (typeof map === 'undefined') { + // source-maps disabled in production by default + // enabled with --env.sourceMap= + if (mode === 'production') { + // todo: we may set up SourceMapDevToolPlugin to generate external maps in production + return false; + } + + return defaultSourceMap; + } + + // when --env.sourceMap=true is passed, use default + if (typeof map === 'boolean' && map) { + return defaultSourceMap; + } + + // pass any type of sourceMap with --env.sourceMap= + return map as Config.DevTool; + }; + + config.devtool(getSourceMapType(env.sourceMap)); // todo: figure out easiest way to make "node" target work in ns // rather than the custom ns target implementation that's hard to maintain @@ -54,10 +90,20 @@ export default function (config: Config, env: IWebpackEnv): Config { .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/@nativescript/core/inspector_modules') + .entry('tns_modules/inspector_modules') .add('@nativescript/core/inspector_modules'); }); @@ -66,7 +112,15 @@ export default function (config: Config, env: IWebpackEnv): Config { .pathinfo(false) .publicPath('') .libraryTarget('commonjs') - .globalObject('global'); + .globalObject('global') + .set('clean', true); + + config.watchOptions({ + ignored: [ + `${getProjectFilePath('platforms')}/**`, + `${getProjectFilePath(env.appResourcesPath ?? 'App_Resources')}/**`, + ], + }); // Set up Terser options config.optimization.minimizer('TerserPlugin').use(TerserPlugin, [ @@ -75,13 +129,20 @@ export default function (config: Config, env: IWebpackEnv): Config { compress: { collapse_vars: platform !== 'android', sequences: platform !== 'android', + keep_infinity: true, + drop_console: mode === 'production', + global_defs: { + __UGLIFIED__: true, + }, }, - // todo: move into vue.ts if not required in other flavors? keep_fnames: true, + keep_classnames: true, }, }, ]); + config.optimization.runtimeChunk('single'); + config.optimization.splitChunks({ cacheGroups: { defaultVendor: { @@ -95,10 +156,13 @@ export default function (config: Config, env: IWebpackEnv): Config { // 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('node_modules/@nativescript/webpack/dist/loaders') + .add(resolve(__dirname, '../loaders')) + .add(resolve(__dirname, '../../node_modules')) + .add(getProjectFilePath('node_modules')) .add('node_modules'); config.resolve.extensions @@ -119,6 +183,28 @@ export default function (config: Config, env: IWebpackEnv): Config { // 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') @@ -156,19 +242,25 @@ export default function (config: Config, env: IWebpackEnv): Config { }); // 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, - }, - }); + .end(); + + config.module + .rule('workers') + .test(/\.(js|ts)$/) + .use('nativescript-worker-loader') + .loader('nativescript-worker-loader'); + + // config.resolve.extensions.add('.xml'); + // set up xml + config.module + .rule('xml') + .test(/\.xml$/) + .use('xml-namespace-loader') + .loader('xml-namespace-loader'); // default PostCSS options to use // projects can change settings @@ -213,14 +305,6 @@ export default function (config: Config, env: IWebpackEnv): Config { .use('sass-loader') .loader('sass-loader'); - // items to clean - config.plugin('CleanWebpackPlugin').use(CleanWebpackPlugin, [ - { - cleanOnceBeforeBuildPatterns: [`${getAbsoluteDistPath()}/**/*`], - verbose: !!env.verbose, - }, - ]); - // config.plugin('NormalModuleReplacementPlugin').use(NormalModuleReplacementPlugin, [ // /.*/, // request => { @@ -237,6 +321,28 @@ export default function (config: Config, env: IWebpackEnv): Config { }, ]); + // 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, [ { @@ -244,7 +350,10 @@ export default function (config: Config, env: IWebpackEnv): Config { __NS_WEBPACK__: true, __UI_USE_XML_PARSER__: true, __UI_USE_EXTERNAL_RENDERER__: projectUsesCustomFlavor(), - __CSS_PARSER__: JSON.stringify(getValue('cssParser')), // todo: replace from config value + __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', @@ -256,6 +365,12 @@ export default function (config: Config, env: IWebpackEnv): Config { }, ]); + // enable DotEnv + applyDotEnvPlugin(config); + + // replacements + applyFileReplacements(config); + // set up default copy rules addCopyRule('assets/**'); addCopyRule('fonts/**'); @@ -263,8 +378,6 @@ export default function (config: Config, env: IWebpackEnv): Config { applyCopyRules(config); - // add the WatchStateLogger plugin used to notify the CLI of build state - // config.plugin('WatchStateLoggerPlugin').use(WatchStateLoggerPlugin); config.plugin('WatchStatePlugin').use(WatchStatePlugin); config.when(env.hmr, (config) => { @@ -272,14 +385,13 @@ export default function (config: Config, env: IWebpackEnv): Config { }); 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'), + reportFilename: getProjectFilePath('report/report.html'), + statsFilename: getProjectFilePath('report/stats.json'), }, ]); }); diff --git a/packages/webpack5/src/configuration/javascript.ts b/packages/webpack5/src/configuration/javascript.ts index e42d0b5d2..5820734e9 100644 --- a/packages/webpack5/src/configuration/javascript.ts +++ b/packages/webpack5/src/configuration/javascript.ts @@ -1,45 +1,42 @@ -import VirtualModulesPlugin from 'webpack-virtual-modules'; -import { ContextExclusionPlugin } from 'webpack'; import Config from 'webpack-chain'; -import dedent from 'ts-dedent'; -import { join } from 'path'; -import { getEntryDirPath } from '../helpers/platform'; +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 virtualEntryPath = join(getEntryDirPath(), '__virtual_entry__.js'); + 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 - .plugin('ContextExclusionPluginPlugin') - .use(ContextExclusionPlugin, [/__virtual_entry__\.js$/]); + // config.resolve.extensions.add('.xml'); - // Add a virtual entry module that will register all modules into - // the nativescript module loader/handler - config.plugin('VirtualModulesPlugin').use(VirtualModulesPlugin, [ - { - [virtualEntryPath]: dedent` - require('@nativescript/core/bundle-entry-points') - const context = require.context("~/", /* deep: */ true, /* filter: */ ${filterRE}); - global.registerWebpackModules(context); - `, - }, - ]); - - config.resolve.extensions.add('.xml'); - - // set up xml + // set up core HMR config.module - .rule('xml') - .test(/\.xml$/) - .use('xml-namespace-loader') - .loader('xml-namespace-loader'); + .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; } diff --git a/packages/webpack5/src/configuration/react.ts b/packages/webpack5/src/configuration/react.ts index d0da8cd54..be0a552dd 100644 --- a/packages/webpack5/src/configuration/react.ts +++ b/packages/webpack5/src/configuration/react.ts @@ -1,8 +1,8 @@ import { merge } from 'webpack-merge'; import Config from 'webpack-chain'; -import { env as _env, IWebpackEnv } from '../index'; 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 { diff --git a/packages/webpack5/src/configuration/svelte.ts b/packages/webpack5/src/configuration/svelte.ts index 6fecf2e7c..1f1dbba2a 100644 --- a/packages/webpack5/src/configuration/svelte.ts +++ b/packages/webpack5/src/configuration/svelte.ts @@ -1,6 +1,6 @@ import Config from 'webpack-chain'; -import { getProjectRootPath } from '../helpers/project'; +import { getProjectFilePath, getProjectRootPath } from '../helpers/project'; import { getPlatformName } from '../helpers/platform'; import { env as _env, IWebpackEnv } from '../index'; import { error } from '../helpers/log'; @@ -52,15 +52,14 @@ function getSvelteConfigPreprocessor(): any { return config?.preprocess; } -function getSvelteConfig(): { preprocess: any } | undefined { +interface ISvelteConfig { + preprocess: any; +} + +function getSvelteConfig(): ISvelteConfig | undefined { try { - const resolvedPath = require.resolve(`./svelte.config.js`, { - paths: [getProjectRootPath()], - }); - return require(resolvedPath); + return require(getProjectFilePath('svelte.config.js')) as ISvelteConfig; } catch (err) { - // todo: remove when jest supports mocking require.resolve - if (__TEST__) return; error('Could not find svelte.config.js.', err); } } diff --git a/packages/webpack5/src/configuration/typescript.ts b/packages/webpack5/src/configuration/typescript.ts index d3f7b73eb..71f1d7e4b 100644 --- a/packages/webpack5/src/configuration/typescript.ts +++ b/packages/webpack5/src/configuration/typescript.ts @@ -1,11 +1,42 @@ import Config from 'webpack-chain'; -import { IWebpackEnv } from '../index'; +import { getEntryDirPath, getEntryPath } from '../helpers/platform'; +import { addVirtualEntry } from '../helpers/virtualModules'; +import { env as _env, IWebpackEnv } from '../index'; import base from './base'; -// todo: add base configuration for core -export default function (config: Config, env: IWebpackEnv): Config { +export default function (config: Config, env: IWebpackEnv = _env): Config { base(config, env); + const entryPath = getEntryPath(); + const filterRE = '/\\.(xml|js|(? { - args[0] = merge(args[0], { - typescript: { - extensions: { - vue: { - enabled: true, - compiler: 'nativescript-vue-template-compiler', + 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; }); - return args; }); // add VueLoaderPlugin as the first plugin @@ -65,3 +90,22 @@ export default function (config: Config, env: IWebpackEnv = _env): Config { 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!'); + } +} diff --git a/packages/webpack5/src/globals.d.ts b/packages/webpack5/src/globals.d.ts index b861b6498..d2209bc77 100644 --- a/packages/webpack5/src/globals.d.ts +++ b/packages/webpack5/src/globals.d.ts @@ -1 +1 @@ -declare var __TEST__: boolean; +// define globals here diff --git a/packages/webpack5/src/helpers/config.ts b/packages/webpack5/src/helpers/config.ts index 74b04ad35..ae649ead9 100644 --- a/packages/webpack5/src/helpers/config.ts +++ b/packages/webpack5/src/helpers/config.ts @@ -1,11 +1,15 @@ import { env } from '../index'; -import { error } from './log'; +import { error, warnOnce } from './log'; function getCLILib() { if (!env.nativescriptLibPath) { - throw error(` + warnOnce( + 'getCLILib', + ` Cannot find NativeScript CLI path. Make sure --env.nativescriptLibPath is passed - `); + ` + ); + return false; } return require(env.nativescriptLibPath); @@ -15,11 +19,16 @@ function getCLILib() { * 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(key: string): T { +export function getValue(key: string, defaultValue?: any): T { const lib = getCLILib(); - return (lib.projectConfigService as { getValue(key: string): T }).getValue( - key - ); + if (!lib) { + return defaultValue; + } + + return (lib.projectConfigService as { + getValue(key: string, defaultValue?: any): T; + }).getValue(key, defaultValue); } diff --git a/packages/webpack5/src/helpers/copyRules.ts b/packages/webpack5/src/helpers/copyRules.ts index a7cbdd242..fe6dc932c 100644 --- a/packages/webpack5/src/helpers/copyRules.ts +++ b/packages/webpack5/src/helpers/copyRules.ts @@ -1,5 +1,5 @@ import CopyWebpackPlugin from 'copy-webpack-plugin'; -import { relative, resolve } from 'path'; +import { basename, relative, resolve } from 'path'; import Config from 'webpack-chain'; import { getProjectRootPath } from './project'; @@ -12,17 +12,29 @@ import { env } from '..'; export let copyRules = new Set([]); /** - * Utility to add new copy rules. Accepts a glob. For example + * @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} glob + * @param {string|object} globOrObject */ -export function addCopyRule(glob: string) { - copyRules.add(glob); +export function addCopyRule(globOrObject: string | object) { + if (typeof globOrObject === 'string') { + return copyRules.add(globOrObject); + } + + additionalCopyRules.push(globOrObject); } /** @@ -41,29 +53,30 @@ export function removeCopyRule(glob: string) { */ export function applyCopyRules(config: Config) { const entryDir = getEntryDirPath(); - // todo: handle empty appResourcesPath? - // (the CLI should always pass the path - maybe not required) - const appResourcesFullPath = resolve( - getProjectRootPath(), - env.appResourcesPath - ); - const globOptions = { dot: false, - ignore: [ - // ignore everything in App_Resources (regardless where they are located) - `${relative(entryDir, appResourcesFullPath)}/**`, - ], + ignore: [], }; + // todo: do we need to handle empty appResourcesPath? + // (the CLI should always pass the path - maybe not required) + if (env.appResourcesPath) { + const appResourcesFolderName = basename(env.appResourcesPath); + + // ignore everything in App_Resources (regardless where they are located) + globOptions.ignore.push(`**/${appResourcesFolderName}/**`); + } + config.plugin('CopyWebpackPlugin').use(CopyWebpackPlugin, [ { - patterns: Array.from(copyRules).map((glob) => ({ - from: glob, - context: entryDir, - noErrorOnMissing: true, - globOptions, - })), + patterns: Array.from(copyRules) + .map((glob) => ({ + from: glob, + context: entryDir, + noErrorOnMissing: true, + globOptions, + })) + .concat(additionalCopyRules), }, ]); } diff --git a/packages/webpack5/src/helpers/dependencies.ts b/packages/webpack5/src/helpers/dependencies.ts index 37f22e8f3..99828ec27 100644 --- a/packages/webpack5/src/helpers/dependencies.ts +++ b/packages/webpack5/src/helpers/dependencies.ts @@ -1,6 +1,7 @@ -import { getPackageJson, getProjectRootPath } from './project'; import path from 'path'; +import { getPackageJson, getProjectRootPath } from './project'; + // todo: memoize /** * Utility to get all dependencies from the project package.json. diff --git a/packages/webpack5/src/helpers/dotEnv.ts b/packages/webpack5/src/helpers/dotEnv.ts new file mode 100644 index 000000000..2403008b1 --- /dev/null +++ b/packages/webpack5/src/helpers/dotEnv.ts @@ -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. + if (existsSync(dotEnvWithEnvPath)) { + return dotEnvWithEnvPath; + } + + // fall back to .env + if (existsSync(dotEnvPath)) { + return dotEnvPath; + } + + // don't use .env + return null; +} diff --git a/packages/webpack5/src/helpers/externalConfigs.ts b/packages/webpack5/src/helpers/externalConfigs.ts index 518f8b97a..3dc2a5d83 100644 --- a/packages/webpack5/src/helpers/externalConfigs.ts +++ b/packages/webpack5/src/helpers/externalConfigs.ts @@ -2,9 +2,9 @@ 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'; -import { clearCurrentPlugin, setCurrentPlugin } from '../index'; /** * @internal diff --git a/packages/webpack5/src/helpers/fileReplacements.ts b/packages/webpack5/src/helpers/fileReplacements.ts new file mode 100644 index 000000000..f36f1b8a4 --- /dev/null +++ b/packages/webpack5/src/helpers/fileReplacements.ts @@ -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, + }); + }); +} diff --git a/packages/webpack5/src/helpers/host.ts b/packages/webpack5/src/helpers/host.ts new file mode 100644 index 000000000..5a909a196 --- /dev/null +++ b/packages/webpack5/src/helpers/host.ts @@ -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); +} diff --git a/packages/webpack5/src/helpers/index.ts b/packages/webpack5/src/helpers/index.ts index a06c98579..082f8a5e6 100644 --- a/packages/webpack5/src/helpers/index.ts +++ b/packages/webpack5/src/helpers/index.ts @@ -1,15 +1,22 @@ 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, projectUsesCustomFlavor } from './flavor'; -import { error, info, warn } from './log'; import { getValue } from './config'; +import { getIPS } from './host'; import { getAllDependencies, hasDependency, getDependencyPath, } from './dependencies'; -import { getPackageJson, getProjectRootPath } from './project'; import { addPlatform, getAbsoluteDistPath, @@ -29,6 +36,7 @@ export default { merge, addCopyRule, removeCopyRule, + applyFileReplacements, config: { getValue, }, @@ -39,16 +47,16 @@ export default { }, flavor: { determineProjectFlavor, - projectUsesCustomFlavor + projectUsesCustomFlavor, + }, + host: { + getIPS, }, log: { error, info, warn, - }, - project: { - getProjectRootPath, - getPackageJson, + warnOnce, }, platform: { addPlatform, @@ -59,4 +67,13 @@ export default { getPlatform, getPlatformName, }, + project: { + getProjectFilePath, + getProjectRootPath, + getPackageJson, + }, + virtualModules: { + addVirtualEntry, + addVirtualModule, + }, }; diff --git a/packages/webpack5/src/helpers/log.ts b/packages/webpack5/src/helpers/log.ts index f2dedb64f..ac70c163a 100644 --- a/packages/webpack5/src/helpers/log.ts +++ b/packages/webpack5/src/helpers/log.ts @@ -1,4 +1,5 @@ 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[]) { @@ -27,8 +28,20 @@ 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 { - console.log(`[@nativescript/webpack] Info: \n`, ...cleanup(data)); + if (env.verbose) { + console.log(`[@nativescript/webpack] Info: \n`, ...cleanup(data)); + } } // todo: refine diff --git a/packages/webpack5/src/helpers/platform.ts b/packages/webpack5/src/helpers/platform.ts index c8adde006..f59389af1 100644 --- a/packages/webpack5/src/helpers/platform.ts +++ b/packages/webpack5/src/helpers/platform.ts @@ -1,7 +1,7 @@ import { dirname, resolve } from 'path'; import { getPackageJson, getProjectRootPath } from './project'; -import { error } from './log'; +import { error, info, warnOnce } from './log'; import { env } from '../'; import AndroidPlatform from '../platforms/android'; @@ -29,7 +29,7 @@ const platforms: { * @param platform A platform definition of the platform specifics */ export function addPlatform(name: string, platform: INativeScriptPlatform) { - console.log('adding platform', name, platform); + info(`Adding platform ${name}`, platform); platforms[name] = platform; } @@ -65,13 +65,20 @@ export function getPlatformName(): Platform { `); } - throw error(` + warnOnce( + 'getPlatformName', + ` You need to provide a target platform! Available platforms: ${Object.keys(platforms).join(', ')} - Use --env=platform= or --env=android, --env=ios to specify the target platform. - `); + Use --env.platform= or --env.android, --env.ios to specify the target platform. + + Defaulting to "ios". + ` + ); + + return 'ios'; } /** diff --git a/packages/webpack5/src/helpers/project.ts b/packages/webpack5/src/helpers/project.ts index bdc874b0e..494735090 100644 --- a/packages/webpack5/src/helpers/project.ts +++ b/packages/webpack5/src/helpers/project.ts @@ -19,7 +19,32 @@ interface IPackageJson { * Utility function to get the contents of the project package.json */ export function getPackageJson() { - const packageJsonPath = resolve(getProjectRootPath(), 'package.json'); - - return require(packageJsonPath) as IPackageJson; + 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, '..')); +// } diff --git a/packages/webpack5/src/helpers/virtualModules.ts b/packages/webpack5/src/helpers/virtualModules.ts new file mode 100644 index 000000000..4e8a79b93 --- /dev/null +++ b/packages/webpack5/src/helpers/virtualModules.ts @@ -0,0 +1,65 @@ +import { ContextExclusionPlugin } from 'webpack'; +import Config from 'webpack-chain'; +import { dirname, join } from 'path'; +import { writeFileSync, mkdirSync } from 'fs'; + +import VirtualModulesPlugin from 'webpack-virtual-modules'; +import { getEntryDirPath } from './platform'; +import dedent from 'ts-dedent'; +import { getProjectFilePath } from './project'; + +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), + }; + + // AngularCompilerPlugin does not support virtual modules + // https://github.com/sysgears/webpack-virtual-modules/issues/96 + // This is only an issue on v11, which has experimental webpack 5 support + // AngularCompilerPlugin gets replaced by AngularWebpackPlugin on v12 + // todo: we can remove this special handling once we no longer support v11 + if (config.plugins.has('AngularCompilerPlugin')) { + const compatEntryPath = getProjectFilePath( + join('node_modules', '.nativescript', `${name}`) + ); + mkdirSync(dirname(compatEntryPath), { recursive: true }); + writeFileSync(compatEntryPath, options[virtualEntryPath]); + return compatEntryPath; + } + + 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; +} diff --git a/packages/webpack5/src/index.ts b/packages/webpack5/src/index.ts index e6d6e5e37..7c072e477 100644 --- a/packages/webpack5/src/index.ts +++ b/packages/webpack5/src/index.ts @@ -1,3 +1,13 @@ +// Make sure the Acorn Parser (used by Webpack) can parse ES-Stage3 code +// This must be at the top BEFORE webpack is loaded so that we can extend +// and replace the parser before webpack uses it +// Based on the issue: https://github.com/webpack/webpack/issues/10216 +import stage3 from 'acorn-stage3'; + +// we use require to be able to override the exports +const acorn = require('acorn'); +acorn.Parser = acorn.Parser.extend(stage3); + import { highlight } from 'cli-highlight'; import { merge } from 'webpack-merge'; import Config from 'webpack-chain'; @@ -12,8 +22,11 @@ import helpers from './helpers'; export interface IWebpackEnv { [name: string]: any; + env?: string; + appPath?: string; appResourcesPath?: string; + appComponents?: string[]; nativescriptLibPath?: string; @@ -22,13 +35,16 @@ export interface IWebpackEnv { // for custom platforms platform?: string; + sourceMap?: string | boolean; production?: boolean; report?: boolean; hmr?: boolean; // enable verbose output verbose?: boolean; - // todo: add others + + // misc + replace?: string[] | string; } interface IChainEntry { diff --git a/packages/webpack5/src/loaders/angular-hot-loader/hmr-accept.ts b/packages/webpack5/src/loaders/angular-hot-loader/hmr-accept.ts new file mode 100644 index 000000000..586a586c0 --- /dev/null +++ b/packages/webpack5/src/loaders/angular-hot-loader/hmr-accept.ts @@ -0,0 +1,117 @@ +import { + isDevMode, + ɵresetCompiledComponents, + // @ts-ignore +} from '@angular/core'; + +declare const __webpack_require__: any; +declare const ng: any; + +export default function (mod: any): void { + if (!mod['hot']) { + return; + } + + if (!isDevMode()) { + console.error( + `[NG HMR] Cannot use HMR when Angular is running in production mode. To prevent production mode, do not call 'enableProdMode()'.` + ); + + return; + } + + mod['hot'].accept(); + mod['hot'].dispose(() => { + if (typeof ng === 'undefined') { + console.warn( + `[NG HMR] Cannot find global 'ng'. Likely this is caused because scripts optimization is enabled.` + ); + + return; + } + + if (!ng.getInjector) { + // View Engine + return; + } + + // Reset JIT compiled components cache + ɵresetCompiledComponents(); + try { + if (global['__cleanup_ng_hot__']) global['__cleanup_ng_hot__'](); + } catch (e) { + console.error('[NG HMR] Error disposing previous module'); + console.error(e, e?.stack); + // HMR breaks when rejecting the main module dispose, so we manually trigger an HMR restart + const hash = __webpack_require__.h(); + console.log(`[HMR][${hash}] failure | Error disposing previous module`); + throw e; + } + }); +} + +// TODO: maybe restore form values! +// function restoreFormValues(oldInputs: any[], oldOptions: any[]): void { +// // Restore input that are not hidden +// const newInputs = document.querySelectorAll('input:not([type="hidden"]), textarea'); +// if (newInputs.length && newInputs.length === oldInputs.length) { +// console.log('[NG HMR] Restoring input/textarea values.'); +// for (let index = 0; index < newInputs.length; index++) { +// const newElement = newInputs[index]; +// const oldElement = oldInputs[index]; + +// switch (oldElement.type) { +// case 'button': +// case 'image': +// case 'submit': +// case 'reset': +// // These types don't need any value change. +// continue; +// case 'radio': +// case 'checkbox': +// newElement.checked = oldElement.checked; +// break; +// case 'color': +// case 'date': +// case 'datetime-local': +// case 'email': +// case 'file': +// case 'hidden': +// case 'month': +// case 'number': +// case 'password': +// case 'range': +// case 'search': +// case 'tel': +// case 'text': +// case 'textarea': +// case 'time': +// case 'url': +// case 'week': +// newElement.value = oldElement.value; +// break; +// default: +// console.warn('[NG HMR] Unknown input type ' + oldElement.type + '.'); +// continue; +// } + +// dispatchEvents(newElement); +// } +// } else if (oldInputs.length) { +// console.warn('[NG HMR] Cannot restore input/textarea values.'); +// } + +// // Restore option +// const newOptions = document.querySelectorAll('option'); +// if (newOptions.length && newOptions.length === oldOptions.length) { +// console.log('[NG HMR] Restoring selected options.'); +// for (let index = 0; index < newOptions.length; index++) { +// const newElement = newOptions[index]; +// newElement.selected = oldOptions[index].selected; + +// dispatchEvents(newElement); +// } +// } else if (oldOptions.length) { +// console.warn('[NG HMR] Cannot restore selected options.'); +// } +// } diff --git a/packages/webpack5/src/loaders/angular-hot-loader/index.ts b/packages/webpack5/src/loaders/angular-hot-loader/index.ts new file mode 100644 index 000000000..e87ae3de4 --- /dev/null +++ b/packages/webpack5/src/loaders/angular-hot-loader/index.ts @@ -0,0 +1,30 @@ +/** + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + +import { join } from 'path'; + +export const HmrLoader = __filename; +const hmrAcceptPath = join(__dirname, './hmr-accept.js').replace(/\\/g, '/'); + +export default function ( + this: any, + content: string, + // Source map types are broken in the webpack type definitions + map: any +): void { + const source = `${content} + + // HMR Accept Code + import ngHmrAccept from '${hmrAcceptPath}'; + ngHmrAccept(module); + `; + + this.callback(null, source, map); + + return; +} diff --git a/packages/webpack5/src/loaders/app-css-loader/index.ts b/packages/webpack5/src/loaders/app-css-loader/index.ts new file mode 100644 index 000000000..b754f7def --- /dev/null +++ b/packages/webpack5/src/loaders/app-css-loader/index.ts @@ -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); + }); +} diff --git a/packages/webpack5/src/loaders/apply-css-loader/index.ts b/packages/webpack5/src/loaders/apply-css-loader/index.ts index 966f894a2..78bb7c8ba 100644 --- a/packages/webpack5/src/loaders/apply-css-loader/index.ts +++ b/packages/webpack5/src/loaders/apply-css-loader/index.ts @@ -28,7 +28,7 @@ export default function loader(content, map) { ` : ``; - if (hasLoader('apply-css-loader')) { + if (hasLoader('css2json-loader')) { content = dedent` ${content} const { addTaggedAdditionalCSS } = require("@nativescript/core/ui/styling/style-scope"); @@ -53,5 +53,5 @@ export default function loader(content, map) { this.emitWarning(new Error(cssLoaderWarning)); } - this.callback(null, content, map); + this.callback(null, content, null); } diff --git a/packages/webpack5/src/loaders/css2json-loader/index.ts b/packages/webpack5/src/loaders/css2json-loader/index.ts index 677a5195e..7e5fe1b22 100644 --- a/packages/webpack5/src/loaders/css2json-loader/index.ts +++ b/packages/webpack5/src/loaders/css2json-loader/index.ts @@ -36,7 +36,7 @@ export default function loader(content: string, map: any) { this.callback( null, code, //`${dependencies.join('\n')}module.exports = ${str};`, - null + map ); } diff --git a/packages/webpack5/src/loaders/nativescript-hot-loader/hmr.runtime.ts b/packages/webpack5/src/loaders/nativescript-hot-loader/hmr.runtime.ts new file mode 100644 index 000000000..5434a0034 --- /dev/null +++ b/packages/webpack5/src/loaders/nativescript-hot-loader/hmr.runtime.ts @@ -0,0 +1,121 @@ +// @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 requireExists = (path) => { + try { + __non_webpack_require__(path); + return true; + } catch (err) { + return false; + } + }; + + const hasUpdate = () => { + return [ + `~/bundle.${__webpack_hash__}.hot-update.json`, + `~/runtime.${__webpack_hash__}.hot-update.json`, + ].some((path) => requireExists(path)); + }; + + const originalOnLiveSync = global.__onLiveSync; + global.__onLiveSync = async function () { + logVerbose('LiveSync'); + + if (!hasUpdate()) { + return; + } + + await checkAndApply(); + originalOnLiveSync(); + }; +} diff --git a/packages/webpack5/src/loaders/nativescript-hot-loader/index.ts b/packages/webpack5/src/loaders/nativescript-hot-loader/index.ts new file mode 100644 index 000000000..6a91088f8 --- /dev/null +++ b/packages/webpack5/src/loaders/nativescript-hot-loader/index.ts @@ -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); +} diff --git a/packages/webpack5/src/loaders/nativescript-worker-loader/index.ts b/packages/webpack5/src/loaders/nativescript-worker-loader/index.ts new file mode 100644 index 000000000..454d166bb --- /dev/null +++ b/packages/webpack5/src/loaders/nativescript-worker-loader/index.ts @@ -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); +} diff --git a/packages/webpack5/src/loaders/xml-namespace-loader/index.ts b/packages/webpack5/src/loaders/xml-namespace-loader/index.ts index d0966993b..f3bff1a83 100644 --- a/packages/webpack5/src/loaders/xml-namespace-loader/index.ts +++ b/packages/webpack5/src/loaders/xml-namespace-loader/index.ts @@ -178,11 +178,21 @@ async function parseXML(content: string): Promise { .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) { diff --git a/packages/webpack5/src/platforms/ios.ts b/packages/webpack5/src/platforms/ios.ts index 7fc648c38..dd2d3defb 100644 --- a/packages/webpack5/src/platforms/ios.ts +++ b/packages/webpack5/src/platforms/ios.ts @@ -3,8 +3,13 @@ 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 = basename(getProjectRootPath()); + const appName = sanitizeName(basename(getProjectRootPath())); return `platforms/ios/${appName}/app`; } diff --git a/packages/webpack5/src/plugins/PlatformSuffixPlugin.ts b/packages/webpack5/src/plugins/PlatformSuffixPlugin.ts index b9e437ed4..51c9ae5c6 100644 --- a/packages/webpack5/src/plugins/PlatformSuffixPlugin.ts +++ b/packages/webpack5/src/plugins/PlatformSuffixPlugin.ts @@ -34,14 +34,11 @@ export class PlatformSuffixPlugin { } apply(compiler: any) { - console.log( - // this.extensions, - this.platform - ); 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..ext) diff --git a/packages/webpack5/src/plugins/WatchStateLoggerPlugin.ts b/packages/webpack5/src/plugins/WatchStateLoggerPlugin.ts deleted file mode 100644 index 78c9a19e0..000000000 --- a/packages/webpack5/src/plugins/WatchStateLoggerPlugin.ts +++ /dev/null @@ -1,83 +0,0 @@ -import webpack from 'webpack'; - -const id = 'WatchStateLoggerPlugin'; - -export enum messages { - compilationComplete = 'Webpack compilation complete.', - startWatching = 'Webpack compilation complete. Watching for file changes.', - changeDetected = 'File change detected. Starting incremental webpack compilation...', -} - -/** - * This little plugin will report the webpack state through the console. - * So the {N} CLI can get some idea when compilation completes. - * @deprecated todo: remove soon - */ -export class WatchStateLoggerPlugin { - isRunningWatching: boolean; - - apply(compiler) { - const plugin = this; - - compiler.hooks.watchRun.tapAsync(id, function (compiler, callback) { - plugin.isRunningWatching = true; - - if (plugin.isRunningWatching) { - console.log(messages.changeDetected); - } - - notify(messages.changeDetected); - - callback(); - }); - - compiler.hooks.afterEmit.tapAsync(id, function (compilation, callback) { - callback(); - - if (plugin.isRunningWatching) { - console.log(messages.startWatching); - } else { - console.log(messages.compilationComplete); - } - - const emittedFiles = Array.from(compilation.emittedAssets); - const chunkFiles = getChunkFiles(compilation); - - notify(messages.compilationComplete); - - // Send emitted files so they can be LiveSynced if need be - notify({ emittedFiles, chunkFiles, hash: compilation.hash }); - }); - } -} - -function getChunkFiles(compilation: webpack.Compilation) { - const chunkFiles = []; - try { - compilation.chunks.forEach((chunk) => { - chunk.files.forEach((file) => { - if (file.indexOf('hot-update') === -1) { - chunkFiles.push(file); - } - }); - }); - } catch (e) { - console.log('Warning: Unable to find chunk files.'); - } - - return chunkFiles; -} - -function notify(message: any) { - if (!process.send) { - return; - } - - process.send(message, (error) => { - if (error) { - console.error(`[${id}] Process Send Error: `, error); - } - - return null; - }); -} diff --git a/packages/webpack5/src/plugins/WatchStatePlugin.ts b/packages/webpack5/src/plugins/WatchStatePlugin.ts index f6fb2d24a..c4a4671fa 100644 --- a/packages/webpack5/src/plugins/WatchStatePlugin.ts +++ b/packages/webpack5/src/plugins/WatchStatePlugin.ts @@ -1,3 +1,5 @@ +import { env } from '../'; + const id = 'WatchStatePlugin'; const version = 1; @@ -8,12 +10,10 @@ export enum messages { } /** - * This little plugin will report the webpack state through the console. - * So the {N} CLI can get some idea when compilation completes. + * This little plugin will report the webpack state through the console + * and send status updates through IPC to the {N} CLI. */ export class WatchStatePlugin { - isRunningWatching: boolean; - apply(compiler: any) { let isWatchMode = false; let prevAssets = []; @@ -21,8 +21,24 @@ export class WatchStatePlugin { 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; - console.log(messages.changeDetected); }); compiler.hooks.afterEmit.tapAsync(id, function (compilation, callback) { @@ -53,21 +69,23 @@ export class WatchStatePlugin { notify({ type: 'compilation', version, - - emittedAssets, - staleAssets, hash: compilation.hash, + + data: { + emittedAssets, + staleAssets, + }, }); }); } } function notify(message: any) { + env.verbose && console.log(`[${id}] Notify: `, message); if (!process.send) { return; } - console.log(`[${id}] Notify: `, message); process.send(message, (error) => { if (error) { console.error(`[${id}] Process Send Error: `, error); diff --git a/packages/webpack5/src/stubs/default.config.stub.js b/packages/webpack5/src/stubs/default.config.stub.js index 07275b43b..a561e0e2b 100644 --- a/packages/webpack5/src/stubs/default.config.stub.js +++ b/packages/webpack5/src/stubs/default.config.stub.js @@ -3,7 +3,8 @@ const webpack = require("@nativescript/webpack"); module.exports = (env) => { webpack.init(env); - // todo: comments for common usage + // Learn how to customize: + // https://docs.nativescript.org/webpack return webpack.resolveConfig(); };