Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Open
justintoth opened this issue Nov 15, 2021 · 0 comments

Comments

@justintoth
Copy link

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant