NativeScript / nativescript-dev-webpack

A package to help with webpacking NativeScript apps.
Apache License 2.0
97 stars 49 forks source link

uglify option prevent the generation of application java class. #564

Closed RadouaneRoufid closed 6 years ago

RadouaneRoufid commented 6 years ago

Issue Checklist

Tell us about the problem

The issue 559 results in a problem in using --env.uglify option when using a custom application. In fact, the custom application Java Class is not generated causing an error on app start: ClassNotFoundException

with a -- env.uglify option, the build succeed without console errors. But when starting the app on device or emulator, the classNotFoundExcetion occurs. I tried to understand why it does not work and I realized the java class of the@JavaProxy is not generated as you can see below

image

while without --env.uglify option, a MainActivity.java exists under org.bricool as below :

image

Local environment

Project data

- [x] Webpack configuration:

const { join, relative, resolve, sep } = require("path");

const webpack = require("webpack"); const nsWebpack = require("nativescript-dev-webpack"); const nativescriptTarget = require("nativescript-dev-webpack/nativescript-target"); 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 UglifyJsPlugin = require("uglifyjs-webpack-plugin");

module.exports = env => { // Add your custom Activities, Services and other Android app components here. const appComponents = [ "tns-core-modules/ui/frame", "tns-core-modules/ui/frame/activity", resolve(__dirname, "app/MainActivity.android.ts") ];

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

const platforms = ["ios", "android"];
const projectRoot = __dirname;
nsWebpack.loadAdditionalPlugins({ projectDir: projectRoot });

// Default destination inside platforms/<platform>/...
const dist = resolve(projectRoot, nsWebpack.getAppPath(platform, projectRoot));
const appResourcesPlatformDir = platform === "android" ? "Android" : "iOS";

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

    // You can provide the following flags when running 'tns run android|ios'
    aot, // --env.aot
    snapshot, // --env.snapshot
    uglify, // --env.uglify
    report, // --env.report
} = env;

const appFullPath = resolve(projectRoot, appPath);
const appResourcesFullPath = resolve(projectRoot, appResourcesPath);

const entryModule = aot ?
    nsWebpack.getAotEntryModule(appFullPath) : 
    `${nsWebpack.getEntryModule(appFullPath)}.ts`;
const entryPath = `.${sep}${entryModule}`;

const config = {
    mode: uglify ? "production" : "development",
    context: appFullPath,
    watchOptions: {
        ignored: [
            appResourcesFullPath,
            // Don't watch hidden files
            "**/.*",
        ]
    },
    target: nativescriptTarget,
    entry: {
        bundle: entryPath,
        MainActivity: "./MainActivity"
    },
    output: {
        pathinfo: false,
        path: dist,
        libraryTarget: "commonjs2",
        filename: "[name].js",
        globalObject: "global",
    },
    resolve: {
        extensions: [".ts", ".js", ".scss", ".css"],
        // Resolve {N} system modules from tns-core-modules
        modules: [
            resolve(__dirname, "node_modules/tns-core-modules"),
            resolve(__dirname, "node_modules"),
            "node_modules/tns-core-modules",
            "node_modules",
        ],
        alias: {
            '~': appFullPath
        },
        symlinks: true
    },
    resolveLoader: {
        symlinks: true
    },
    node: {
        // Disable node shims that conflict with NativeScript
        "http": false,
        "timers": false,
        "setImmediate": false,
        "fs": "empty",
        "__dirname": false,
    },
    devtool: "none",
    optimization: {
        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 UglifyJsPlugin({
                uglifyOptions: {
                    parallel: true,
                    cache: true,
                    output: {
                        comments: false,
                    },
                    compress: {
                        // The Android SBG has problems parsing the output
                        // when these options are enabled
                        'collapse_vars': platform !== "android",
                        sequences: platform !== "android",
                    }
                }
            })
        ],
    },
    module: {
        rules: [
            {
                test: new RegExp(entryPath),
                use: [
                    // Require all Android app components
                    platform === "android" && {
                        loader: "nativescript-dev-webpack/android-app-components-loader",
                        options: { modules: appComponents }
                    },

                    {
                        loader: "nativescript-dev-webpack/bundle-config-loader",
                        options: {
                            registerPages: false,
                            loadCss: !snapshot, // load the application css if in debug mode
                        }
                    },
                ].filter(loader => !!loader)
            },

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

            // tns-core-modules reads the app.css and its imports using css-loader
            {
                test: /[\/|\\]app\.css$/,
                use: {
                    loader: "css-loader",
                    options: { minimize: false, url: false },
                }
            },
            {
                test: /[\/|\\]app\.scss$/,
                use: [
                    { loader: "css-loader", options: { minimize: false, url: false } },
                    "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"] },

            // Compile TypeScript files with ahead-of-time compiler.
            {
                test: /.ts$/, use: [
                    "nativescript-dev-webpack/moduleid-compat-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",
        }),
        // Remove all files from the out dir.
        new CleanWebpackPlugin([ `${dist}/**/*` ]),
        // Copy native app resources to out dir.
        new CopyWebpackPlugin([
            {
                from: `${appResourcesFullPath}/${appResourcesPlatformDir}`,
                to: `${dist}/App_Resources/${appResourcesPlatformDir}`,
                context: projectRoot
            },
        ]),
        // Copy assets to out dir. Add your own globs as needed.
        new CopyWebpackPlugin([
            { from: "fonts/**" },
            { from: "assets/i18n/**" },
            { from: "**/*.jpg" },
            { from: "**/*.png" },
        ], { ignore: [`${relative(appPath, appResourcesFullPath)}/**`] }),
        // Generate a bundle starter script and activate it in package.json
        new nsWebpack.GenerateBundleStarterPlugin([
            "./vendor",
            "./bundle",
        ]),
        // For instructions on how to set up workers with webpack
        // check out https://github.com/nativescript/worker-loader
        new NativeScriptWorkerPlugin(),
        // AngularCompilerPlugin with augmented NativeScript filesystem to handle platform specific resource resolution.
        new nsWebpack.NativeScriptAngularCompilerPlugin({
            entryModule: resolve(appPath, "app.module#AppModule"),
            tsConfigPath: join(__dirname, "tsconfig.esm.json"),
            skipCodeGeneration: !aot,
            platformOptions: {
                platform,
                platforms,
            },
        }),
        // 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",
        requireModules: [
            "reflect-metadata",
            "@angular/platform-browser",
            "@angular/core",
            "@angular/common",
            "@angular/router",
            "nativescript-angular/platform-static",
            "nativescript-angular/router",
        ],
        projectRoot,
        webpackConfig: config,
    }));
}

return config;

};



[NativeScript Forum]: http://forum.nativescript.org
[issues]: https://github.com/nativescript/nativescript-dev-webpack/issues?utf8=✓&q=is%3Aissue
[demo apps]: ../demo
[documentation]: https://docs.nativescript.org/best-practices/bundling-with-webpack
RoyiNamir commented 6 years ago

There are few differences between your code VS the demo's code . Just saying .

Are you sure you use the .ts version ?

image

RadouaneRoufid commented 6 years ago

For resolve(__dirname, "app/MainActivity.android.js"), I tried with both .ts and .js. Got the error with the two.

I had to prefix MainActivity with ./ otherwise webpack didn't find the file.

For symlink, I tried to play with the option. I reverted it. It does not solve the problem.

jibon57 commented 6 years ago

@vchimev any work around so far regarding application extend?

vchimev commented 6 years ago

Hey Guys,

Let me first refer to this pull request addressing the issue where an absolute path in appComponents can't be resolved on Windows and pay attention that adding a custom android app component to the array on the top of the webpack.config.js file should be resolved to an absolute path, i.e. resolve(__dirname, "app/activity.android.ts") in order to be included in the common vendor.js chunk. Adding it by a relative path is only a workaround for the issue above.

I updated all the demos to extend android activity and application in the same branch, and managed to execute them successfully with:

tns run android --bundle
tns run android --bundle --env.aot --env.uglify --env.snapshot --release ...

Extend Android Activity

In regard to extend android activity, it should be added only to the appComponents array by an absolute path as mentioned above.

const appComponents = [
    "tns-core-modules/ui/frame",
    "tns-core-modules/ui/frame/activity",
    resolve(__dirname, "app/activity.android.ts"),
];

In this way and with the default config, these components get in the common vendor.js chunk and are required in the bundle by android-app-comopnents-loader.

Extend Android Application

In regard to extend android application, it should be added only as an entry.

entry: {
    bundle: entryPath,
    application: "./application.android",
},

In this way, the source code of application.android.js|ts is bundled separately as application.js file which is loaded from the native Application.java class on launch.

The application.js bundle file is independent of the bundle.js and vendor.js files and the reason for it is that bundle.js and vendor.js are not loaded so early in the application launch. That's why the logic in application.android.js|ts is needed to be bundled separately in order to be loaded by the native Application.java as early as needed on launch.

RoyiNamir commented 6 years ago

@vchimev @jibon57 @RadouaneRoufid Hello. Can you please explain why Vasil used in here :

const appComponents = [
    "tns-core-modules/ui/frame",
    "tns-core-modules/ui/frame/activity",
    resolve(__dirname, "app/activity.android.ts"),
];

1) .ts in "app/activity.android.ts" --------> isn't it suppose to be JS ? I mean it looks like as if the TS file will be included and not js included. ( I know that TS is transpiles to JS) - but still - why ts ?

2) .android in "app/activity.android.ts" --------> isn't it suppose to be without .android ? as it is removed.

Can you please shed some light about the steps that occur here ?

RadouaneRoufid commented 6 years ago

It may help https://github.com/NativeScript/nativescript-dev-webpack/issues/568#issuecomment-398023948

vchimev commented 6 years ago

Hey @RoyiNamir,

  1. This is because of the loaders and plugins in use to handle .ts files in TypeScript and Angular projects.
  2. Files with .android.|.ios. are prepared for the specific platform on building without bundling. Then these platform specific files go to the specific native projects and the corresponding extension is removed. For bundling, they are handled through the loaders/plugins mentioned in 1.
vchimev commented 6 years ago

Hey @RadouaneRoufid and @jibon57,

I'm closing this as demos and docs were updated. Please, let me know in case of any other issues.

RoyiNamir commented 6 years ago

Hey @vchimev . Docs here you mean ?

found it . Thanks https://github.com/NativeScript/docs/commit/14224ddd1d894ab94d5b12a31431a063afdd4cb2#diff-bb3b3b05f6b7838c7b1041431d9b9f93

and this : https://docs.nativescript.org/core-concepts/android-runtime/advanced-topics/extend-application-activity

RadouaneRoufid commented 5 years ago

Any news on this ? Problem still present in tns 6.1.2

RadouaneRoufid commented 5 years ago

I found the solution. "noEmitHelpers": true, must be present in compilerOptions in tsconfig.json while build with env.uglify with custom activity.