NativeScript
NativeScript copied to clipboard
Angular Module error when updating my app and module not found using `[hash]` in filename
Environment
- CLI: 7.2.1
- Cross-platform modules: 7.1.4
- Android Runtime: 7.0.1
- iOS Runtime: 7.2.0
- XCode Version: 12.4 & 13
- Plugin(s): lots. Will add on request. Core:
"@angular/core": "11.2.6",
"@nativescript/angular": "11.0.1",
"@nativescript/theme": "3.0.1",
"@nativescript/types": "7.3.0",
"@nativescript/webpack": "4.1.0",
"webpack": "4.46.0",
Describe the bug
Issue with a bunch of moving parts. We have lazy loaded module routes in an app.module.ts, when updating my app on testflight or the app store we had reports of users not being able to navigate in our app. I'm not too well versed on the bundle extraction process in nativescript so I'm unsure what the root cause is.
Upon inspection the error was:
[ReferenceError: Cannot access 'MyModule' before initialization]
and the code where this error threw was in a try/catch around a standard routerExtensions.navigate call.
Since this worked if the app was uninstalled/reinstalled but not when updating I started looking at what could be a problem. It appears that for my scenario (NS+Angular+Webpack default config) some old app files can be used in the new app, particularly when using lazy loaded routes which chunk out as 1.js, 2.js, 3.js and so-on.
Workaround Initial workaround was to add file hashing to the webpack config but that lead to a "Module not found: ." error on boot.
Our workaround so far has been to add chunk hashing to the webpack config, however this involved a bit of a hack becasue the main entry in dist/package.json . It would be nice if this were easily doable or the default in these kinds of projects.
Posting the workaround here in case anyone has the same sort of pain that I did:
webpack.custom.config.js:
const defaultConfig = require('./webpack.config');
const nsWebpack = require('@nativescript/webpack');
const path = require('path');
const { WebpackManifestPlugin } = require('webpack-manifest-plugin');
const WebpackManifestReplace = require('webpack-manifest-replaces');
// ...
module.exports = (env) => {
let config = defaultConfig(env);
config = addCacheBusting(env, config);
return config;
};
function addCacheBusting(env, config) {
const platform = env && ((env.android && 'android') || (env.ios && 'ios'));
const projectRoot = __dirname;
const dist = path.resolve(projectRoot, nsWebpack.getAppPath(platform, projectRoot));
// Set output.filename to ensure hashes change
config.output.filename = '[name].[hash:8].js';
config.output.chunkFilename = '[name].[hash:8].js';
// Replace the original GenerateEntryPoints plugin to include the .js extension
config.plugins.forEach((plugin) => {
if(plugin.constructor && plugin.constructor.name === 'GenerateNativeScriptEntryPointsPlugin') {
plugin.appEntryName = 'bundle.js';
}
})
// Add a manifest.json
config.plugins.push(new WebpackManifestPlugin());
// Replace the contents of package.json's main entry with bundle.[hash].js
config.plugins.push(
new WebpackManifestReplace({
basedir: dist,
src: 'package.json',
manifestFilename: 'manifest.json',
}),
);
return config;
}
nativescript.config.ts:
import { NativeScriptConfig } from '@nativescript/core';
export default {
// ...
webpackConfigPath: 'webpack.custom.config.js',fig;
};
To Reproduce Create an NS-angular project with lazy loaded routes. Update the child modules & update on iOS. Navigate to the route that loads the child module.
Expected behavior The app should smoothly update to new code, without leaving old code behind.
Sample project Unavailable in public domain, can discuss privately upon request.
Additional context Awaiting update to NS 8.1.* because we can't go live with this issue in play: https://github.com/NativeScript/NativeScript/issues/9263
I have the same issue with nativescript 8.8.2. If you need an updated version for webpack 5.
The new version doesn't have the GenerateNativeScriptEntryPointsPlugin anymore instead using the CLI for generating the package.json.
For reference you can find the package.json generation here: https://github.com/NativeScript/nativescript-cli/blob/main/lib/controllers/prepare-controller.ts#L434
Until we have the extra check on the end my solution will work.
Overall would be nice if someone would check why is this happening. Previously we didn't have any issue with this and after an upgrade it started to happen.
const webpack = require('@nativescript/webpack');
module.exports = env => {
webpack.init(env);
webpack.chainWebpack(config => {
// Set filename and chunkFilename with contenthash for better caching
config.output
.filename('[name].[contenthash:8].js')
.chunkFilename('[name].[contenthash:8].js');
// Dynamically update the package.json after Webpack build
config.plugin('UpdateMainField').use(function () {
return {
apply: (compiler) => {
compiler.hooks.thisCompilation.tap('UpdateMainFieldPlugin', (compilation) => {
console.log(`Add package.json`);
compilation.hooks.processAssets.tap(
{
name: 'AddAssetPlugin',
stage: compiler.webpack.Compilation.PROCESS_ASSETS_STAGE_ADDITIONAL,
},
() => {
const content = JSON.stringify({ main: 'bundle' });
const filename = 'package.json';
// Add the asset to the compilation
compilation.emitAsset(
filename,
new compiler.webpack.sources.RawSource(content)
);
}
);
});
compiler.hooks.emit.tap('UpdateMainFieldPlugin', compilation => {
let newMain = 'bundle';
for (const chunk of compilation.chunks) {
for (const file of chunk.files) {
if (chunk.name === 'bundle') {
console.log(`New name: ${file}`);
newMain = file;
}
}
}
console.log(`Main: ${newMain}`);
const content = JSON.stringify({ main: newMain });
const filename = 'package.json';
compilation.updateAsset(filename, new compiler.webpack.sources.RawSource(content));
})
},
};
});
});
// Learn how to customize:
// https://docs.nativescript.org/webpack
return webpack.resolveConfig();
};