nativescript-schematics icon indicating copy to clipboard operation
nativescript-schematics copied to clipboard

Nativescript Angular code sharing project doesn't build with webpack version 5

Open justintoth opened this issue 4 years ago • 0 comments

Environment

{
  "name": "housters",
  "main": "main.tns.js",
  "version": "7.00.01",
  "scripts": {
    "ng start": "ng serve",
    "ng build": "ng build",
    "lint": "eslint --fix src/ && stylelint \"src/**/*.scss\" --fix && prettier --write src/",
    "android": "tns run android --no-hmr",
    "ios": "tns run ios --provision hstr-dv --no-hmr",
    "ios-phone": "tns run ios --provision hstr-dv --no-hmr --device `node build-scripts/find-identifier.js \"iPhone\"`",
    "ios-tablet": "tns run ios --provision hstr-dv --no-hmr --device `node build-scripts/find-identifier.js \"iPad\"",
    "ios-connected-device": "tns run ios --provision hstr-dv --no-hmr --device `node build-scripts/find-identifier.js first-connected-device`",
    "preview": "tns preview",
    "sync": "gaffer-generator generate"
  },
  "private": true,
  "lint-staged": {
    "*.(ts|js)": [
      "eslint --fix",
      "prettier --write"
    ],
    "*.(css|scss)": [
      "stylelint --fix",
      "prettier --write"
    ]
  },
  "dependencies": {
    "@angular/animations": "^12.2.5",
    "@angular/common": "^12.2.5",
    "@angular/compiler": "^12.2.5",
    "@angular/core": "^12.2.5",
    "@angular/forms": "^12.2.5",
    "@angular/platform-browser": "^12.2.5",
    "@angular/platform-browser-dynamic": "^12.2.5",
    "@angular/router": "^12.2.5",
    "@codelab/nativescript-multi-select": "^2.0.1",
    "@nativescript/angular": "^12.2.0",
    "@nativescript/background-http": "^5.0.2",
    "@nativescript/camera": "^5.0.9",
    "@nativescript/core": "^8.1.5",
    "@nativescript/datetimepicker": "^2.1.9",
    "@nativescript/firebase": "^11.1.3",
    "@nativescript/imagepicker": "^1.0.5",
    "@nativescript/theme": "^3.0.2",
    "@triniwiz/nativescript-pager": "^13.0.2",
    "@triniwiz/nativescript-stripe": "^7.0.1",
    "@types/crypto-js": "^4.0.2",
    "bootstrap": "^4.6.1",
    "core-js": "^2.6.12",
    "crypto-js": "3.3.0",
    "dayjs": "^1.10.7",
    "jquery": "^3.5.1",
    "nativescript-drop-down": "^6.0.0",
    "nativescript-fingerprint-auth": "^7.0.2",
    "nativescript-iqkeyboardmanager": "^1.5.1",
    "nativescript-localstorage": "^2.0.2",
    "nativescript-purchase": "^2.0.14",
    "nativescript-ratings": "^1.0.1",
    "nativescript-ui-chart": "^8.0.2",
    "nativescript-ui-listview": "^9.1.4",
    "ngx-device-detector": "^2.1.1",
    "node-fetch": "^2.6.6",
    "reflect-metadata": "~0.1.13",
    "rxjs": "~7.3.0",
    "sass": "^1.43.4",
    "tslib": "~2.1.0",
    "zone.js": "~0.11.3"
  },
  "devDependencies": {
    "@angular/cli": "~11.1.3",
    "@angular/compiler-cli": "^12.2.5",
    "@nativescript/android": "~8.1.1",
    "@nativescript/ios": "~8.1.0",
    "@nativescript/webpack": "^5.0.1",
    "@types/jasmine": "^3.10.2",
    "@types/jasminewd2": "^2.0.10",
    "@types/node": "^14.17.33",
    "@typescript-eslint/eslint-plugin": "^4.33.0",
    "@typescript-eslint/parser": "^4.33.0",
    "codelyzer": "^6.0.2",
    "dotenv": "^8.6.0",
    "eslint": "^7.32.0",
    "eslint-plugin-nativescript": "0.0.0",
    "eslint-plugin-prettier": "^3.4.1",
    "gaffer-generator": "^1.2.5",
    "jasmine-core": "~3.6.0",
    "jasmine-spec-reporter": "~6.0.0",
    "karma": "~6.1.0",
    "karma-chrome-launcher": "~3.1.0",
    "karma-coverage-istanbul-reporter": "~3.0.3",
    "karma-jasmine": "~4.0.1",
    "karma-jasmine-html-reporter": "^1.7.0",
    "lint-staged": "^10.5.4",
    "lodash": "^4.17.20",
    "prettier": "2.2.1",
    "protractor": "~7.0.0",
    "stylelint": "^13.13.1",
    "stylelint-config-sass-guidelines": "^8.0.0",
    "stylelint-config-standard": "^20.0.0",
    "stylelint-declaration-use-variable": "^1.7.3",
    "ts-node": "~9.1.1",
    "typescript": "~4.3.5",
    "@ngtools/webpack": "~10.0.0"
  }
}

Describe the bug When upgrading from @nativescript/webpack v4 to v5, the code sharing webpack.config.js script no longer builds. Many of the imported functions no longer exist.

To Reproduce Here is the webpack.config.js that I was using that worked with v4:

const dotenvResult = require('dotenv').config();

const { join, relative, resolve, sep, dirname } = require('path');
const fs = require('fs');

const webpack = require('webpack');
const nsWebpack = require('@nativescript/webpack');
const nativescriptTarget = require('@nativescript/webpack/nativescript-target');
const { nsSupportHmrNg } = require('@nativescript/webpack/transformers/ns-support-hmr-ng');
const { nsTransformNativeClassesNg } = require('@nativescript/webpack/transformers/ns-transform-native-classes-ng');
const { parseWorkspaceConfig, hasConfigurations } = require('@nativescript/webpack/helpers/angular-config-parser');
const { getMainModulePath } = require('@nativescript/webpack/utils/ast-utils');
const { getNoEmitOnErrorFromTSConfig, getCompilerOptionsFromTSConfig } = require('@nativescript/webpack/utils/tsconfig-utils');
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
const CopyWebpackPlugin = require('copy-webpack-plugin');
const { BundleAnalyzerPlugin } = require('webpack-bundle-analyzer');
const { NativeScriptWorkerPlugin } = require('nativescript-worker-loader/NativeScriptWorkerPlugin');
const TerserPlugin = require('terser-webpack-plugin');
const { getAngularCompilerPlugin } = require('@nativescript/webpack/plugins/NativeScriptAngularCompilerPlugin');
const hashSalt = Date.now().toString();

module.exports = env => {
    // Add your custom Activities, Services and other Android app components here.
    const appComponents = ['@nativescript/core/ui/frame', '@nativescript/core/ui/frame/activity'];

    const platform = env && ((env.android && 'android') || (env.ios && 'ios'));
    if (!platform) {
        throw new Error('You need to provide a target platform!');
    }

    const AngularCompilerPlugin = getAngularCompilerPlugin(platform);
    const projectRoot = __dirname;

    // Default destination inside platforms/<platform>/...
    const dist = resolve(projectRoot, nsWebpack.getAppPath(platform, projectRoot));

    const {
        // The 'appPath' and 'appResourcesPath' values are fetched from
        // the nsconfig.json configuration file
        // when bundling with `tns run android|ios --bundle`.
        appPath = 'src',
        appResourcesPath = 'App_Resources',

        // You can provide the following flags when running 'tns run android|ios'
        snapshot, // --env.snapshot,
        production, // --env.production
        configuration, // --env.configuration (consistent with angular cli usage)
        projectName, // --env.projectName (drive configuration through angular projects)
        uglify, // --env.uglify
        report, // --env.report
        sourceMap, // --env.sourceMap
        hiddenSourceMap, // --env.hiddenSourceMap
        hmr, // --env.hmr,
        unitTesting, // --env.unitTesting
        testing, // --env.testing
        verbose, // --env.verbose
        ci, // --env.ci
        snapshotInDocker, // --env.snapshotInDocker
        skipSnapshotTools, // --env.skipSnapshotTools
        compileSnapshot, // --env.compileSnapshot
    } = env;

    const { fileReplacements, copyReplacements } = parseWorkspaceConfig(platform, configuration, projectName);

    const useLibs = compileSnapshot;
    const isAnySourceMapEnabled = !!sourceMap || !!hiddenSourceMap;
    const externals = nsWebpack.getConvertedExternals(env.externals);
    const appFullPath = resolve(projectRoot, appPath);
    const appResourcesFullPath = resolve(projectRoot, appResourcesPath);
    let tsConfigName = 'tsconfig.json';
    let tsConfigPath = resolve(projectRoot, tsConfigName);
    const tsConfigTnsName = 'tsconfig.tns.json';
    const tsConfigTnsPath = resolve(projectRoot, tsConfigTnsName);
    if (fs.existsSync(tsConfigTnsPath)) {
        // support shared angular app configurations
        tsConfigName = tsConfigTnsName;
        tsConfigPath = tsConfigTnsPath;
    }
    const tsConfigEnvName = 'tsconfig.env.json';
    const tsConfigEnvPath = resolve(projectRoot, tsConfigEnvName);
    if (hasConfigurations(configuration) && fs.existsSync(tsConfigEnvPath)) {
        // when configurations are used, switch to environments supported config
        tsConfigName = tsConfigEnvName;
        tsConfigPath = tsConfigEnvPath;
    }
    const entryModule = `${nsWebpack.getEntryModule(appFullPath, platform)}.ts`;
    const entryPath = `.${sep}${entryModule}`;
    const entries = { bundle: entryPath };
    const areCoreModulesExternal = Array.isArray(env.externals) && env.externals.some(e => e.indexOf('@nativescript') > -1);
    if (platform === 'ios' && !areCoreModulesExternal && !testing) {
        entries['tns_modules/@nativescript/core/inspector_modules'] = 'inspector_modules';
    }

    const compilerOptions = getCompilerOptionsFromTSConfig(tsConfigPath);
    nsWebpack.processTsPathsForScopedModules({ compilerOptions });
    nsWebpack.processTsPathsForScopedAngular({ compilerOptions });

    const ngCompilerTransformers = [nsTransformNativeClassesNg];
    const additionalLazyModuleResources = [];

    const copyIgnore = { ignore: [`${relative(appPath, appResourcesFullPath)}/**`] };
    const copyTargets = [
        { from: 'assets/**', noErrorOnMissing: true, globOptions: { dot: false, ...copyIgnore } },
        { from: 'fonts/**', noErrorOnMissing: true, globOptions: { dot: false, ...copyIgnore } },
        ...copyReplacements,
    ];

    if (!production) {
        // for development purposes only
        // for example, include mock json folder
        // copyTargets.push({ from: 'tools/mockdata', to: 'assets/mockdata' });

        if (hmr) {
            ngCompilerTransformers.push(nsSupportHmrNg);
        }
    }

    // when "@angular/core" is external, it's not included in the bundles. In this way, it will be used
    // directly from node_modules and the Angular modules loader won't be able to resolve the lazy routes
    // fixes https://github.com/NativeScript/nativescript-cli/issues/4024
    if (env.externals && env.externals.indexOf('@angular/core') > -1) {
        const appModuleRelativePath = getMainModulePath(resolve(appFullPath, entryModule), tsConfigName);
        if (appModuleRelativePath) {
            const appModuleFolderPath = dirname(resolve(appFullPath, appModuleRelativePath));
            // include the new lazy loader path in the allowed ones
            additionalLazyModuleResources.push(appModuleFolderPath);
        }
    }

    const ngCompilerPlugin = new AngularCompilerPlugin({
        hostReplacementPaths: nsWebpack.getResolver([platform, 'tns']),
        platformTransformers: ngCompilerTransformers.map(t => t(() => ngCompilerPlugin, resolve(appFullPath, entryModule), projectRoot)),
        mainPath: join(appFullPath, entryModule),
        tsConfigPath,
        skipCodeGeneration: false,
        sourceMap: !!isAnySourceMapEnabled,
        additionalLazyModuleResources: additionalLazyModuleResources,
        compilerOptions: { paths: compilerOptions.paths },
    });

    let sourceMapFilename = nsWebpack.getSourceMapFilename(hiddenSourceMap, __dirname, dist);

    const itemsToClean = [`${dist}/**/*`];
    if (platform === 'android') {
        itemsToClean.push(`${join(projectRoot, 'platforms', 'android', 'app', 'src', 'main', 'assets', 'snapshots')}`);
        itemsToClean.push(
            `${join(projectRoot, 'platforms', 'android', 'app', 'build', 'configurations', 'nativescript-android-snapshot')}`,
        );
    }

    const noEmitOnErrorFromTSConfig = getNoEmitOnErrorFromTSConfig(tsConfigName);

    nsWebpack.processAppComponents(appComponents, platform);
    const config = {
        mode: production ? 'production' : 'development',
        context: appFullPath,
        externals,
        watchOptions: {
            ignored: [
                appResourcesFullPath,
                // Don't watch hidden files
                '**/.*',
            ],
        },
        target: nativescriptTarget,
        entry: entries,
        output: {
            pathinfo: false,
            path: dist,
            sourceMapFilename,
            libraryTarget: 'commonjs2',
            filename: '[name].js',
            globalObject: 'global',
            hashSalt,
        },
        resolve: {
            extensions: ['.ts', '.js', '.scss', '.css'],
            // Resolve {N} system modules from @nativescript/core
            modules: [
                resolve(__dirname, 'node_modules/@nativescript/core'),
                resolve(__dirname, 'node_modules'),
                'node_modules/@nativescript/core',
                'node_modules',
            ],
            alias: {
                '~/package.json': resolve(projectRoot, 'package.json'),
                '~': appFullPath,
                'tns-core-modules': '@nativescript/core',
                'nativescript-angular': '@nativescript/angular',
                ...fileReplacements,
            },
            symlinks: true,
        },
        resolveLoader: {
            symlinks: false,
        },
        node: {
            // Disable node shims that conflict with NativeScript
            http: false,
            timers: false,
            setImmediate: false,
            fs: 'empty',
            __dirname: false,
        },
        devtool: hiddenSourceMap ? 'hidden-source-map' : sourceMap ? 'inline-source-map' : 'none',
        optimization: {
            runtimeChunk: 'single',
            noEmitOnErrors: noEmitOnErrorFromTSConfig,
            splitChunks: {
                cacheGroups: {
                    vendor: {
                        name: 'vendor',
                        chunks: 'all',
                        test: (module, chunks) => {
                            const moduleName = module.nameForCondition ? module.nameForCondition() : '';
                            return /[\\/]node_modules[\\/]/.test(moduleName) || appComponents.some(comp => comp === moduleName);
                        },
                        enforce: true,
                    },
                },
            },
            minimize: !!uglify,
            minimizer: [
                new TerserPlugin({
                    parallel: true,
                    cache: !ci,
                    sourceMap: isAnySourceMapEnabled,
                    terserOptions: {
                        output: {
                            comments: false,
                            semicolons: !isAnySourceMapEnabled,
                        },
                        compress: {
                            // The Android SBG has problems parsing the output
                            // when these options are enabled
                            collapse_vars: platform !== 'android',
                            sequences: platform !== 'android',
                            // custom
                            drop_console: true,
                            drop_debugger: true,
                            ecma: 6,
                            keep_infinity: platform === 'android', // for Chrome/V8
                            reduce_funcs: platform !== 'android', // for Chrome/V8
                            global_defs: {
                                __UGLIFIED__: true,
                            },
                        },
                        // custom
                        ecma: 6,
                        safari10: platform !== 'android',
                    },
                }),
            ],
        },
        module: {
            rules: [
                {
                    include: join(appFullPath, entryPath),
                    use: [
                        // Require all Android app components
                        platform === 'android' && {
                            loader: '@nativescript/webpack/helpers/android-app-components-loader',
                            options: { modules: appComponents },
                        },

                        {
                            loader: '@nativescript/webpack/bundle-config-loader',
                            options: {
                                angular: true,
                                loadCss: !snapshot, // load the application css if in debug mode
                                unitTesting,
                                appFullPath,
                                projectRoot,
                                ignoredFiles: nsWebpack.getUserDefinedEntries(entries, platform),
                            },
                        },
                    ].filter(loader => !!loader),
                },

                { test: /\.html$|\.xml$/, use: 'raw-loader' },

                {
                    test: /[\/|\\]app\.css$/,
                    use: [
                        '@nativescript/webpack/helpers/style-hot-loader',
                        {
                            loader: '@nativescript/webpack/helpers/css2json-loader',
                            options: { useForImports: true },
                        },
                    ],
                },
                {
                    test: /[\/|\\]app\.scss$/,
                    use: [
                        '@nativescript/webpack/helpers/style-hot-loader',
                        {
                            loader: '@nativescript/webpack/helpers/css2json-loader',
                            options: { useForImports: true },
                        },
                        'sass-loader',
                    ],
                },

                // Angular components reference css files and their imports using raw-loader
                { test: /\.css$/, exclude: /[\/|\\]app\.css$/, use: 'raw-loader' },
                {
                    test: /\.scss$/,
                    exclude: /[\/|\\]app\.scss$/,
                    use: ['raw-loader', 'resolve-url-loader', 'sass-loader'],
                },

                {
                    test: /(?:\.ngfactory\.js|\.ngstyle\.js|\.ts)$/,
                    use: [
                        '@nativescript/webpack/helpers/moduleid-compat-loader',
                        '@nativescript/webpack/helpers/lazy-ngmodule-hot-loader',
                        '@ngtools/webpack',
                    ],
                },

                // Mark files inside `@angular/core` as using SystemJS style dynamic imports.
                // Removing this will cause deprecation warnings to appear.
                {
                    test: /[\/\\]@angular[\/\\]core[\/\\].+\.js$/,
                    parser: { system: true },
                },
            ],
        },
        plugins: [
            // Define useful constants like TNS_WEBPACK
            new webpack.DefinePlugin({
                'global.TNS_WEBPACK': 'true',
                'global.isAndroid': platform === 'android',
                'global.isIOS': platform === 'ios',
                'global.env': JSON.stringify(dotenvResult.parsed),
                process: 'global.process',
            }),
            // Remove all files from the out dir.
            new CleanWebpackPlugin({
                cleanOnceBeforeBuildPatterns: itemsToClean,
                verbose: !!verbose,
            }),
            // Copy assets
            new CopyWebpackPlugin({
                patterns: copyTargets,
            }),
            new nsWebpack.GenerateNativeScriptEntryPointsPlugin('bundle'),
            // For instructions on how to set up workers with webpack
            // check out https://github.com/nativescript/worker-loader
            new NativeScriptWorkerPlugin(),
            ngCompilerPlugin,
            // Does IPC communication with the {N} CLI to notify events when running in watch mode.
            new nsWebpack.WatchStateLoggerPlugin(),
        ],
    };

    if (report) {
        // Generate report files for bundles content
        config.plugins.push(
            new BundleAnalyzerPlugin({
                analyzerMode: 'static',
                openAnalyzer: false,
                generateStatsFile: true,
                reportFilename: resolve(projectRoot, 'report', `report.html`),
                statsFilename: resolve(projectRoot, 'report', `stats.json`),
            }),
        );
    }

    if (snapshot) {
        config.plugins.push(
            new nsWebpack.NativeScriptSnapshotPlugin({
                chunk: 'vendor',
                angular: true,
                requireModules: [
                    'reflect-metadata',
                    '@angular/platform-browser',
                    '@angular/core',
                    '@angular/common',
                    '@angular/router',
                    '@nativescript/angular',
                ],
                projectRoot,
                webpackConfig: config,
                snapshotInDocker,
                skipSnapshotTools,
                useLibs,
            }),
        );
    }

    if (!production && hmr) {
        config.plugins.push(new webpack.HotModuleReplacementPlugin());
    }

    return config;
};

Expected behavior

That nativescript angular code sharing would work with webpack version 5. Basically, we need a new sample webpack.config.js file that works with v5.

justintoth avatar Nov 15 '21 03:11 justintoth