Make a Webpack app qiankun-ready
This guide adapts an existing Webpack application for qiankun's classic-script path. The application keeps its own build and development server, exports the qiankun lifecycles, and can be mounted explicitly with loadMicroApp. Both Webpack 4 and Webpack 5 are supported by the bundler plugin.
For Vite, see Make a Vite app qiankun-ready.
Install the plugins
Install the qiankun bundler plugin together with html-webpack-plugin:
npm install --save-dev @qiankunjs/bundler-plugin@rc html-webpack-pluginhtml-webpack-plugin produces the HTML entry and lets the qiankun plugin identify its entry script.
Configure Webpack
Add both plugins, use a stable packageName, and configure the development server for cross-origin loading:
const HtmlWebpackPlugin = require('html-webpack-plugin');
const { QiankunWebpackPlugin } = require('@qiankunjs/bundler-plugin');
module.exports = {
entry: './src/index.tsx',
plugins: [
new HtmlWebpackPlugin({ template: './src/index.html' }),
new QiankunWebpackPlugin({ packageName: 'my-webpack-app' }),
],
devServer: {
port: 7102,
headers: { 'Access-Control-Allow-Origin': '*' },
allowedHosts: 'all',
},
};The plugin configures the bundle as a browser global library and marks the entry script in the HTML generated by html-webpack-plugin. Leave output.library, output.libraryTarget, output.globalObject, and the Webpack 4 JSONP function to the plugin.
Choose a stable packageName
packageName is the global library name of the classic bundle. It defaults to the current project's package.json name. Pass it explicitly when that value is missing, generated, or likely to change.
The value must be non-empty and stable between builds. With the default sandbox: true, it does not have to equal the name passed to loadMicroApp:
packageNamenames the Webpack output library.loadMicroApp({ name })identifies the application to qiankun.
qiankun first resolves lifecycles from the entry script's exports or the global captured by the sandbox. Looking up window[name] is a final compatibility fallback, not the primary contract. If you set sandbox: false, the sandbox can no longer capture the entry export; unless the bundle assigns the lifecycles to window[name] itself, its global library key (normally packageName) must match the host-side name for that fallback to work.
Set the runtime public path
qiankun exposes the micro-app entry's base URL while the entry script runs. Connect it to Webpack's runtime public path so lazy-loaded chunks come from the micro-app's origin:
declare let __webpack_public_path__: string;
declare global {
interface Window {
__POWERED_BY_QIANKUN__?: boolean;
__INJECTED_PUBLIC_PATH_BY_QIANKUN__?: string;
}
}
if (window.__POWERED_BY_QIANKUN__ && window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__) {
__webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__;
}
export {};Import this module before the rest of the application entry. This works with both Webpack 4 and Webpack 5; when the app runs standalone, Webpack keeps its normal public path.
Export the lifecycle functions
Export bootstrap, mount, and unmount from the Webpack entry. This React example renders inside the HTMLElement supplied by qiankun and still works when opened on its own:
import './public-path';
import { StrictMode } from 'react';
import { createRoot, type Root } from 'react-dom/client';
import App from './App';
import './index.css';
type LifecycleProps = {
container?: HTMLElement;
};
let root: Root | undefined;
function render(props: LifecycleProps = {}) {
const element = props.container?.querySelector('#root') ?? document.getElementById('root');
if (!element) return;
root = createRoot(element);
root.render(
<StrictMode>
<App />
</StrictMode>,
);
}
export async function bootstrap() {
return Promise.resolve();
}
export async function mount(props: LifecycleProps) {
render(props);
}
export async function unmount() {
root?.unmount();
root = undefined;
}
if (!window.__POWERED_BY_QIANKUN__) {
void bootstrap().then(() => mount({}));
}Webpack publishes these entry exports through the global library configured by QiankunWebpackPlugin; do not assign the same library global by hand. unmount must completely release the framework root and any application-owned side effects.
The html-webpack-plugin template only needs the app's mount node. Let the plugins inject and mark the script:
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>My Webpack micro-app</title>
</head>
<body>
<div id="root"></div>
</body>
</html>Load it from the main app
The main app loads the HTML entry into an existing HTMLElement. Keep the returned MicroApp handle and unmount it when the owning view is removed:
import { loadMicroApp, type MicroApp } from 'qiankun';
let microApp: MicroApp | undefined;
export function showWebpackApp() {
if (microApp) return;
const container = document.getElementById('subapp-container');
if (!container) throw new Error('Missing #subapp-container');
microApp = loadMicroApp({
name: 'orders-panel',
entry: '//localhost:7102',
container,
});
}
export async function hideWebpackApp() {
await microApp?.unmount();
microApp = undefined;
}The different values, orders-panel and my-webpack-app, are intentional: the application identity and Webpack library name are separate. See loadMicroApp for props, configuration, and handle methods.
For applications activated entirely by URL rules, registerMicroApps with start is the route-driven alternative.
CORS and asset URLs
qiankun fetches the entry HTML and its assets from the main app's origin. QiankunWebpackPlugin does not configure webpack-dev-server, so the micro-app server must return Access-Control-Allow-Origin itself. External scripts and styles must also be available with suitable CORS headers.
The public-path.ts module above aligns lazy-loaded chunks with the micro-app's deployment origin. If your deployment uses a CDN or another asset base, verify that the injected or explicitly configured URL matches that environment.
Production checks
Before deploying the integration:
- Run the micro-app's production build and serve its output from the intended origin.
- Open the deployed HTML entry directly and confirm standalone rendering still works.
- Load that entry with
loadMicroApp, then unmount and remount it once. - Confirm entry HTML, JavaScript chunks, CSS, and external assets are served from the expected URLs with the required CORS headers.
- Keep
packageNamestable across releases and verify the build still contains exactly one marked entry script.
For every plugin option and Webpack-version detail, see @qiankunjs/bundler-plugin. For application responsibilities during cleanup, see Lifecycle and props.
