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

Build failing with An @import loop has been found after upgrading to NS 7 and Angular 10

Open jeanpaulattard opened this issue 4 years ago • 2 comments

Environment Provide version numbers for the following components (information can be retrieved by running tns info in your project folder or by inspecting the package.json of the project):

  • CLI: 7.0.10
  • Cross-platform modules: 7.0.11
  • Android Runtime: 7.0.0
  • iOS Runtime: 7.0.1
  • NativeScript-Angular: 10.0.3
  • Angular: 10.1.6

Describe the bug I've upgraded from NS 6.5 to NS 7 via tns migrate I've upgraded from Angular 8 to Angular 9 to Angular 10 via ng update

After the upgrade, the build is failing with the following error

ERROR in Module build failed (from ../node_modules/sass-loader/dist/cjs.js):
SassError: An @import loop has been found:
           src/app/+setup/init/init.component.scss imports src/app/+setup/init/init.component.scss
        on line 1 of src/app/+setup/init/init.component.scss
>> @import "../../../assets/styles/variables";

   ^

Executing webpack failed with exit code 2.

After troubleshooting the issue, I've found out that this issue is resulting by platform specific stylings.

I have

  • init.component.scss
  • init.component.android.scss (imports init.component.scss)
  • init.component.ios.scss (imports init.component.scss)

In my component I have

styleUrls:['./init.component.scss']

After removing the imports of init.component.scss from the two platform specific scss files, the build passed.

Below is my webpack 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 {
  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 targetEnv = env && (env.prod && "prod" || env.stg && "stg");

  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
    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 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 tsConfigTnsName = 'tsconfig.tns.json';
  let tsConfigPath = resolve(projectRoot, tsConfigName);
  const tsConfigTnsPath = resolve(projectRoot, tsConfigTnsName);
  if (fs.existsSync(tsConfigTnsPath)) {
    // still support shared angular app configurations 
    tsConfigName = tsConfigTnsName;
    tsConfigPath = tsConfigTnsPath;
  }
  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}},
  ];

  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"
      },
      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}
            },

jeanpaulattard avatar Oct 16 '20 07:10 jeanpaulattard

any updates on this issue ?

AndriiYalovenko avatar Nov 02 '20 16:11 AndriiYalovenko

@AndriiYalovenko As a workaround I did:

  • init.component.common.scss
  • init.component.android.scss (imports init.component.common.scss)
  • init.component.ios.scss (imports init.component.common.scss)

standevo avatar Nov 02 '20 16:11 standevo