Skip to content

Commit cc40232

Browse files
fix(quasar): use native webpack MFP to avoid BUILD-001 in CI
The Enhanced ModuleFederationPlugin's ContainerEntryModule has a fatal process.exit(1) check (BUILD-001) that fails under @quasar/app-webpack's compilation environment in CI, where expose module resolution timing differs from standard webpack setups. Switch to native webpack container.ModuleFederationPlugin with bootstrap entry files for the async boundary. Resolve webpack from @quasar/app-webpack's dependency tree to avoid class identity mismatches in pnpm. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1 parent e8ad5ef commit cc40232

File tree

4 files changed

+43
-51
lines changed

4 files changed

+43
-51
lines changed

quasar-cli-vue3-webpack-javascript/app-exposes/quasar.config.js

Lines changed: 19 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,18 @@
99
// https://v2.quasar.dev/quasar-cli-webpack/quasar-config-js
1010

1111
const path = require('path');
12-
const { ModuleFederationPlugin } = require('@module-federation/enhanced/webpack');
12+
// Resolve webpack from @quasar/app-webpack's dependency tree to avoid
13+
// class identity mismatches (pnpm can hoist a different webpack instance).
14+
let webpack;
15+
try {
16+
const quasarAppWebpackReal = require('fs').realpathSync(
17+
path.resolve(__dirname, 'node_modules/@quasar/app-webpack'),
18+
);
19+
webpack = require(require.resolve('webpack', { paths: [quasarAppWebpackReal] }));
20+
} catch (e) {
21+
webpack = require('webpack');
22+
}
23+
const { container } = webpack;
1324
const ESLintPlugin = require('eslint-webpack-plugin');
1425
const dependencies = require('./package.json').dependencies;
1526
// Prefer `sass-embedded` when available (better compatibility with some modern
@@ -63,47 +74,16 @@ module.exports = configure(function (ctx) {
6374
scssLoaderOptions: { implementation: sassImpl },
6475

6576
extendWebpack(cfg) {
66-
// Quasar doesn't set cfg.context, so webpack falls back to process.cwd().
67-
// The Enhanced MFP's ContainerPlugin uses compilation.options.context for
68-
// resolving expose entries. Without it, pnpm's strict resolution in CI
69-
// prevents ContainerExposedDependencies from being resolved.
70-
if (!cfg.context) {
71-
cfg.context = __dirname;
72-
}
73-
74-
// CI-only: verify context reaches the compiler and intercept module build failures
75-
if (process.env.CI) {
76-
cfg.plugins.push({
77-
apply(compiler) {
78-
console.error('[CTX] compiler.context:', compiler.context);
79-
console.error('[CTX] compiler.options.context:', compiler.options.context);
80-
compiler.hooks.compilation.tap('MFCtxCheck', (compilation, { normalModuleFactory }) => {
81-
console.error('[CTX] compilation.options.context:', compilation.options.context);
82-
// Intercept module factory failures
83-
normalModuleFactory.hooks.resolve.tap('MFResolveCheck', (resolveData) => {
84-
if (resolveData.request && resolveData.request.includes('exposes')) {
85-
console.error('[RESOLVE] request:', resolveData.request, 'context:', resolveData.context);
86-
}
87-
});
88-
compilation.hooks.failedModule.tap('MFFailCheck', (module, error) => {
89-
console.error('[FAIL] Module failed:', module.identifier?.() || module, 'Error:', error.message);
90-
});
91-
compilation.hooks.succeedModule.tap('MFSuccessCheck', (module) => {
92-
if (module.identifier && module.identifier().includes('exposes')) {
93-
console.error('[SUCCESS] Module built:', module.identifier());
94-
}
95-
});
96-
});
97-
},
98-
});
99-
}
77+
// Use native webpack ModuleFederationPlugin here. The Enhanced MFP's
78+
// ContainerEntryModule has a fatal BUILD-001 check (process.exit(1)) that
79+
// fails under @quasar/app-webpack's compilation environment in CI, where
80+
// expose module resolution timing differs from standard webpack setups.
81+
// A bootstrap entry provides the async boundary for shared module negotiation.
82+
cfg.entry = path.resolve(__dirname, './src/mf-bootstrap.js');
10083

10184
cfg.plugins.push(
102-
new ModuleFederationPlugin({
85+
new container.ModuleFederationPlugin({
10386
name: 'app_exposes',
104-
manifest: false,
105-
shareStrategy: 'loaded-first',
106-
experiments: { asyncStartup: true },
10787
filename: 'remoteEntry.js',
10888
exposes: {
10989
'./HomePage.vue': path.resolve(__dirname, 'src/exposes/HomePage.js'),
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
// Async bootstrap entry for Quasar + Module Federation.
2+
// The dynamic import creates the async boundary needed for shared module
3+
// negotiation when using native webpack ModuleFederationPlugin.
4+
import('../.quasar/client-entry');

quasar-cli-vue3-webpack-javascript/app-general/quasar.config.js

Lines changed: 16 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,18 @@
99
// https://v2.quasar.dev/quasar-cli-webpack/quasar-config-js
1010

1111
const path = require('path');
12-
const { ModuleFederationPlugin } = require('@module-federation/enhanced/webpack');
12+
// Resolve webpack from @quasar/app-webpack's dependency tree to avoid
13+
// class identity mismatches (pnpm can hoist a different webpack instance).
14+
let webpack;
15+
try {
16+
const quasarAppWebpackReal = require('fs').realpathSync(
17+
path.resolve(__dirname, 'node_modules/@quasar/app-webpack'),
18+
);
19+
webpack = require(require.resolve('webpack', { paths: [quasarAppWebpackReal] }));
20+
} catch (e) {
21+
webpack = require('webpack');
22+
}
23+
const { container } = webpack;
1324
const ESLintPlugin = require('eslint-webpack-plugin');
1425
const dependencies = require('./package.json').dependencies;
1526
// Prefer `sass-embedded` when available (better compatibility with some modern
@@ -63,20 +74,13 @@ module.exports = configure(function (ctx) {
6374
scssLoaderOptions: { implementation: sassImpl },
6475

6576
extendWebpack(cfg) {
66-
// Quasar doesn't set cfg.context, so webpack falls back to process.cwd().
67-
// The Enhanced MFP's ContainerPlugin uses compilation.options.context for
68-
// resolving expose entries. Without it, pnpm's strict resolution in CI
69-
// prevents ContainerExposedDependencies from being resolved.
70-
if (!cfg.context) {
71-
cfg.context = __dirname;
72-
}
77+
// Use native webpack ModuleFederationPlugin (see app-exposes for rationale).
78+
// A bootstrap entry provides the async boundary for shared module negotiation.
79+
cfg.entry = path.resolve(__dirname, './src/mf-bootstrap.js');
7380

7481
cfg.plugins.push(
75-
new ModuleFederationPlugin({
82+
new container.ModuleFederationPlugin({
7683
name: 'app_general',
77-
manifest: false,
78-
shareStrategy: 'loaded-first',
79-
experiments: { asyncStartup: true },
8084
filename: 'remoteEntry.js',
8185
exposes: {},
8286
remotes: {
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
// Async bootstrap entry for Quasar + Module Federation.
2+
// The dynamic import creates the async boundary needed for shared module
3+
// negotiation when using native webpack ModuleFederationPlugin.
4+
import('../.quasar/client-entry');

0 commit comments

Comments
 (0)