const { join, relative, resolve, sep } = require('path'); const fs = require('fs'); const webpack = require('webpack'); const nsWebpack = require('@nativescript/webpack'); const nativescriptTarget = require('@nativescript/webpack/nativescript-target'); const { getNoEmitOnErrorFromTSConfig } = require('@nativescript/webpack/utils/tsconfig-utils'); const { CleanWebpackPlugin } = require('clean-webpack-plugin'); const CopyWebpackPlugin = require('copy-webpack-plugin'); const ForkTsCheckerWebpackPlugin = require('fork-ts-checker-webpack-plugin'); const { BundleAnalyzerPlugin } = require('webpack-bundle-analyzer'); const { NativeScriptWorkerPlugin } = require('nativescript-worker-loader/NativeScriptWorkerPlugin'); const TerserPlugin = require('terser-webpack-plugin'); const hashSalt = Date.now().toString(); const preprocessConfig = require('./svelte.config.js'); const svelteNativePreprocessor = require('svelte-native-preprocessor'); module.exports = (env) => { const platform = env && ((env.android && 'android') || (env.ios && 'ios') || env.platform); if (!platform) { throw new Error('You need to provide a target platform!'); } const platforms = ['ios', 'android']; const projectRoot = __dirname; if (env.platform) { platforms.push(env.platform); } // Default destination inside platforms//... const dist = resolve(projectRoot, nsWebpack.getAppPath(platform, projectRoot)); const { // The 'appPath' and 'appResourcesPath' values are fetched from // the nsconfig.json configuration file. appPath = 'src', appResourcesPath = 'App_Resources', // You can provide the following flags when running 'tns run android|ios' snapshot, // --env.snapshot production, // --env.production uglify, // --env.uglify report, // --env.report sourceMap, // --env.sourceMap hiddenSourceMap, // --env.hiddenSourceMap hmr, // --env.hmr, unitTesting, // --env.unitTesting, testing, // --env.testing verbose, // --env.verbose ci, // --env.ci snapshotInDocker, // --env.snapshotInDocker skipSnapshotTools, // --env.skipSnapshotTools compileSnapshot, // --env.compileSnapshot appComponents = [], entries = {}, } = env; const useLibs = compileSnapshot; const isAnySourceMapEnabled = !!sourceMap || !!hiddenSourceMap; const externals = nsWebpack.getConvertedExternals(env.externals); let appFullPath = resolve(projectRoot, appPath); if (!fs.existsSync(appFullPath)) { // some apps use 'app' directory appFullPath = resolve(projectRoot, 'app'); } const hasRootLevelScopedModules = nsWebpack.hasRootLevelScopedModules({ projectDir: projectRoot }); let coreModulesPackageName = 'tns-core-modules'; const alias = env.alias || {}; alias['~/package.json'] = resolve(projectRoot, 'package.json'); alias['~'] = appFullPath; if (hasRootLevelScopedModules) { coreModulesPackageName = '@nativescript/core'; alias['tns-core-modules'] = coreModulesPackageName; } const appResourcesFullPath = resolve(projectRoot, appResourcesPath); const copyIgnore = { ignore: [`${relative(appPath, appResourcesFullPath)}/**`] }; const entryModule = nsWebpack.getEntryModule(appFullPath, platform); const entryPath = `.${sep}${entryModule}.ts`; Object.assign(entries, { bundle: entryPath }, entries); const tsConfigPath = resolve(projectRoot, 'tsconfig.json'); const areCoreModulesExternal = Array.isArray(env.externals) && env.externals.some((e) => e.indexOf('@nativescript') > -1); if (platform === 'ios' && !areCoreModulesExternal && !testing) { entries['tns_modules/inspector_modules'] = '@nativescript/core/inspector_modules'; } let sourceMapFilename = nsWebpack.getSourceMapFilename(hiddenSourceMap, __dirname, dist); const itemsToClean = [`${dist}/**/*`]; if (platform === 'android') { itemsToClean.push(`${join(projectRoot, 'platforms', 'android', 'app', 'src', 'main', 'assets', 'snapshots')}`); itemsToClean.push(`${join(projectRoot, 'platforms', 'android', 'app', 'build', 'configurations', 'nativescript-android-snapshot')}`); } const noEmitOnErrorFromTSConfig = getNoEmitOnErrorFromTSConfig(tsConfigPath); // Add your custom Activities, Services and other android app components here. appComponents.push('@nativescript/core/ui/frame', '@nativescript/core/ui/frame/activity'); nsWebpack.processAppComponents(appComponents, platform); const config = { mode: production ? 'production' : 'development', context: appFullPath, externals, watchOptions: { ignored: [ appResourcesFullPath, // Don't watch hidden files '**/.*', ], }, target: nativescriptTarget, entry: entries, output: { pathinfo: false, path: dist, sourceMapFilename, libraryTarget: 'commonjs2', filename: '[name].js', globalObject: 'global', hashSalt, }, resolve: { extensions: ['.ts', '.mjs', '.js', '.svelte', '.scss', '.css'], // Resolve {N} system modules from @nativescript/core modules: [resolve(__dirname, `node_modules/${coreModulesPackageName}`), resolve(__dirname, 'node_modules'), `node_modules/${coreModulesPackageName}`, 'node_modules'], alias, // resolve symlinks to symlinked modules symlinks: true, }, resolveLoader: { // don't resolve symlinks to symlinked loaders symlinks: false, }, node: { // Disable node shims that conflict with NativeScript http: false, timers: false, setImmediate: false, fs: 'empty', __dirname: false, }, devtool: hiddenSourceMap ? 'hidden-source-map' : sourceMap ? 'inline-source-map' : 'none', optimization: { runtimeChunk: 'single', noEmitOnErrors: noEmitOnErrorFromTSConfig, splitChunks: { cacheGroups: { vendor: { name: 'vendor', chunks: 'all', test: (module, chunks) => { const moduleName = module.nameForCondition ? module.nameForCondition() : ''; return /[\\/]node_modules[\\/]/.test(moduleName) || appComponents.some((comp) => comp === moduleName); }, enforce: true, }, }, }, minimize: !!uglify, minimizer: [ new TerserPlugin({ parallel: true, cache: !ci, sourceMap: isAnySourceMapEnabled, terserOptions: { output: { comments: false, semicolons: !isAnySourceMapEnabled, }, compress: { // The Android SBG has problems parsing the output // when these options are enabled collapse_vars: platform !== 'android', sequences: platform !== 'android', // For v8 Compatibility keep_infinity: true, // for V8 reduce_funcs: false, // for V8 // custom drop_console: production, drop_debugger: true, global_defs: { __UGLIFIED__: true, }, }, // Required for Element Level CSS, Observable Events, & Android Frame keep_classnames: true, }, }), ], }, module: { rules: [ { include: join(appFullPath, entryPath), use: [ // Require all Android app components platform === 'android' && { loader: '@nativescript/webpack/helpers/android-app-components-loader', options: { modules: appComponents }, }, { loader: '@nativescript/webpack/bundle-config-loader', options: { loadCss: !snapshot, // load the application css if in debug mode unitTesting, appFullPath, projectRoot, ignoredFiles: nsWebpack.getUserDefinedEntries(entries, platform), }, }, ].filter((loader) => !!loader), }, { test: /\.(ts|css|scss|html|xml)$/, use: '@nativescript/webpack/hmr/hot-loader', }, { test: /\.(html|xml)$/, use: '@nativescript/webpack/helpers/xml-namespace-loader' }, { test: /\.css$/, use: '@nativescript/webpack/helpers/css2json-loader', }, { test: /\.scss$/, use: ['@nativescript/webpack/helpers/css2json-loader', 'sass-loader'], }, { test: /\.mjs$/, type: 'javascript/auto', }, { test: /\.ts$/, use: { loader: 'ts-loader', options: { configFile: tsConfigPath, // https://github.com/TypeStrong/ts-loader/blob/ea2fcf925ec158d0a536d1e766adfec6567f5fb4/README.md#faster-builds // https://github.com/TypeStrong/ts-loader/blob/ea2fcf925ec158d0a536d1e766adfec6567f5fb4/README.md#hot-module-replacement transpileOnly: true, allowTsInNodeModules: true, compilerOptions: { sourceMap: isAnySourceMapEnabled, declaration: false, }, getCustomTransformers: (program) => ({ before: [require('@nativescript/webpack/transformers/ns-transform-native-classes').default], }), }, }, }, { test: /\.svelte$/, exclude: /node_modules/, use: [ { loader: 'svelte-loader-hot', options: { dev: env.production ? false : true, preprocess: [preprocessConfig.preprocess, svelteNativePreprocessor()], hotReload: env.production ? false : true, hotOptions: { injectCss: false, native: true, }, }, }, ], }, ], }, plugins: [ // Define useful constants like TNS_WEBPACK new webpack.DefinePlugin({ 'global.TNS_WEBPACK': 'true', 'global.isAndroid': platform === 'android', 'global.isIOS': platform === 'ios', process: 'global.process', }), // Remove all files from the out dir. new CleanWebpackPlugin({ cleanOnceBeforeBuildPatterns: itemsToClean, verbose: !!verbose, }), // Copy assets new CopyWebpackPlugin([{ from: { glob: 'assets/**', dot: false } }, { from: { glob: 'fonts/**', dot: false } }, { from: { glob: '**/*.jpg', dot: false } }, { from: { glob: '**/*.png', dot: false } }], copyIgnore), new nsWebpack.GenerateNativeScriptEntryPointsPlugin('bundle'), // For instructions on how to set up workers with webpack // check out https://github.com/nativescript/worker-loader new NativeScriptWorkerPlugin(), new nsWebpack.PlatformFSPlugin({ platform, platforms, }), // Does IPC communication with the {N} CLI to notify events when running in watch mode. new nsWebpack.WatchStateLoggerPlugin(), // https://github.com/TypeStrong/ts-loader/blob/ea2fcf925ec158d0a536d1e766adfec6567f5fb4/README.md#faster-builds // https://github.com/TypeStrong/ts-loader/blob/ea2fcf925ec158d0a536d1e766adfec6567f5fb4/README.md#hot-module-replacement new ForkTsCheckerWebpackPlugin({ async: false, typescript: { configFile: tsConfigPath, memoryLimit: 4096, diagnosticOptions: { syntactic: true, semantic: true, }, }, }), ], }; if (report) { // Generate report files for bundles content config.plugins.push( new BundleAnalyzerPlugin({ analyzerMode: 'static', openAnalyzer: false, generateStatsFile: true, reportFilename: resolve(projectRoot, 'report', `report.html`), statsFilename: resolve(projectRoot, 'report', `stats.json`), }) ); } if (snapshot) { config.plugins.push( new nsWebpack.NativeScriptSnapshotPlugin({ chunk: 'vendor', requireModules: ['@nativescript/core/bundle-entry-points'], projectRoot, webpackConfig: config, snapshotInDocker, skipSnapshotTools, useLibs, }) ); } if (hmr) { config.plugins.push(new webpack.HotModuleReplacementPlugin()); } return config; };