vue3+qiankun+Typescript开发微前端项目
路由为
hash
模式
一、主应用配置
1、安装 qiankun
yarn add qiankun 或者 npm i qiankun -S
2、注册微应用并启动
import { registerMicroApps, start } from 'qiankun';
const isDev = process.env.NODE_ENV === "development";
const apps = [
{
name: 'FirstMicroApp',
entry: isDev ? '//localhost:8082' : '/first-micro-app/',
container: '#microAppContainer',
activeRule: '#/exhibitionHall',
},
];
registerMicroApps(apps, {
beforeLoad: [
// app => {
// console.log("before load", app);
// }
],
beforeMount: [
// app => {
// console.log("before mount", app);
// }
],
afterUnmount: [
// app => {
// console.log("after unMount", app);
// }
],
});
// 启动 qiankun
start();
二、微应用配置
1、在 src
目录新增 public-path.js
/* eslint-disable */
if (window.__POWERED_BY_QIANKUN__) {
__webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__;
}
2、入口文件 main.ts
修改
import './public-path';
import { createApp } from 'vue';
import Antd from 'ant-design-vue';
import App from './App.vue';
import router from './router';
import store from './store';
import 'ant-design-vue/dist/antd.css';
// createApp(App).use(store).use(router).mount('#app');
type Props = {
container?: HTMLElement;
}
let app: any = null;
function render(props: Props = {}) {
const { container } = props;
app = createApp(App);
app.use(router);
app.use(store);
app.use(Antd);
app.mount(container ? container.querySelector('#app') : '#app');
}
// 独立运行时
// eslint-disable-next-line
if (!window.__POWERED_BY_QIANKUN__) {
render();
}
export async function bootstrap(): Promise {
console.log('vue app bootstraped');
}
export async function mount(props: Props): Promise {
render(props);
// 注册全局主应用路由 mainRouter
// const { mainRouter } = props;
// app.config.globalProperties.$mainRouter = mainRouter;
}
export async function unmount(): Promise {
app.unmount();
app = null;
}
3、打包配置 vue.config.js
修改
const { name } = require('./package.json');
const port = 8082;
const isDev = process.env.NODE_ENV === 'development';
module.exports = {
publicPath: dev ? '/' : '/first-micro-app/',
devServer: {
port,
headers: {
'Access-Control-Allow-Origin': '*',
},
},
configureWebpack: {
output: {
library: `${name}-[name]`,
libraryTarget: 'umd',
jsonpFunction: `webpackJsonp_${name}`,
},
},
}
三、批量打包
1、在主应用与微应用同级新增 package.json
文件
{
"name": "qiankun-vue3",
"version": "1.0.0",
"private": true,
"scripts": {
"install:all": "node scripts/index.js --serial install",
"serve:all": "node scripts/index.js --parallel serve",
"build:all": "yarn run buildApp && yarn run cpApp",
"buildApp": "node scripts/index.js --serial build",
"cpApp": "node scripts/build.js"
}
}
2、在主应用与微应用同级新增 scripts
文件夹
2.1 批量 install、serve、build
在
scripts
文件夹中新增spawn.js
文件
const crossSpawn = require("child_process").spawn;
function kill() {
const platform = process.platform;
if (platform === "win32") {
crossSpawn("taskkill", ["/F", "/T", "/PID", this.pid]);
} else {
crossSpawn.kill(this.pid);
}
}
module.exports = function spawn(command, args, options) {
const child = crossSpawn(command, args, options);
child.kill = kill;
return child;
}
在
scripts
文件夹中新增index.js
文件
/**
* argv[1]为 --parallel / --serial
* argv[2]为 install / serve / build
*/
const path = require('path');
const fs = require('fs');
const spawn = require('./spawn');
const entry = path.resolve();
const microApps = fs.readdirSync(entry).filter(app => /app$/.test(app));
const remove = (array, x) => {
const index = array.indexOf(x);
if (index > -1) {
array.splice(index, 1);
}
};
const runApp = (app, task) => {
let cp = null;
const promise = new Promise((resolve, reject) => {
const command = 'npm';
const args = [];
if (task === 'install') {
args.push(task);
} else {
args.push('run', task);
}
const { stdin, stdout, stderr } = process;
const options = {
stdio: [stdin, stdout, stderr],
};
if (app) {
options.cwd = path.resolve(app);
}
cp = spawn(command, args, options);
cp.on("error", (err) => {
cp = null;
reject(err);
});
cp.on("close", (code) => {
cp = null;
resolve({ app, code });
});
});
// kill pid
promise.abort = () => {
if (cp) {
cp.kill();
cp = null;
}
};
return promise;
};
const runApps = (apps) => {
return new Promise((resolve, reject) => {
if (apps.length === 0) {
resolve([]);
return;
}
const results = apps.map(app => ({ name: app, code: undefined }))
const queue = apps.map((app, index) => ({ name: app, index }))
const promises = [];
let error = null;
let aborted = false;
const done = () => {
if (error) {
reject(error);
}
resolve(results);
};
const abort = () => {
if (aborted) {
return;
}
aborted = true;
if (promises.length) {
for (const p of promises) {
p.abort();
}
Promise.all(promises).then(done, reject);
} else {
done();
}
};
const next = (task) => {
if (aborted) {
return;
}
if (!queue.length) {
if (!promises.length) {
done();
}
return
}
const app = queue.shift();
const promise = runApp(app.name, task);
promises.push(promise);
promise.then(
result => {
remove(promises, promise);
if (aborted) {
return;
}
results[app.index].code = result.code
if (result.code) {
error = {
name: result.app,
code: result.code,
results: results,
};
abort();
return;
}
next(task);
},
err => {
remove(promises, promise);
error = err;
abort();
return;
},
);
};
const [mode, task = ''] = process.argv.slice(2);
if (!['--parallel', '--serial'].includes(mode)) {
const error = 'process.argv第三个参数只能为--parallel、--serial其中之一';
return reject(error);
}
if (!['install', 'serve', 'build'].includes(task)) {
const error = 'process.argv第四个参数只能为install、serve、build其中之一';
return reject(error);
}
const len = mode === '--parallel' ? apps.length : 1;
for (let i = 0; i < len; i++) {
next(task);
}
});
};
// console.log('microApps', microApps);
runApps(microApps)
.then(result => {
// console.log('ok');
})
.catch(error => {
console.error('error: ', error);
});
2.2 批量拷贝 build
后的 dist
文件夹到 外层 dist
目录
/**
* build
* 先build到各自文件夹下的dist目录,再拷贝到最外层dist目录
* 1、先删除上次的dist目录 rm -rf dist
* 2、拷贝主应用dist到最外层dist cp -rf 20f6e232--cloud--FirstMainMicroApp/dist dist
* 3、拷贝微应用dist到最外层dist中,且改名为对应微应用名称 cp -rf 20f6e232--cloud--FirstSubMicroApp/dist dist/FirstSubMicroApp
*/
// const { resolve, resolve: pathResolve } = require('path');
const { resolve: pathResolve } = require('path');
const { access, constants, readdirSync } = require('fs');
const { spawn } = require('child_process');
const entry = pathResolve();
// const microApps = readdirSync(entry).filter(app => /app$/.test(app));
const mainAppFolderName = 'main-app';
const microAppFolderName = 'micro-app';
const appNameRegex = new RegExp(`(${mainAppFolderName}|${microAppFolderName})$`)
console.log('appNameRegex', appNameRegex);
const microApps = readdirSync(entry).filter(app => appNameRegex.test(app));
console.log('microApps', appNameRegex, microApps);
// 文件夹是否存在
const isExist = (dir) => {
return new Promise((resolve, reject) => {
access(dir, constants.F_OK, err => {
// err为null时表示存在文件夹dir
console.log('isExist', err);
resolve(err);
})
})
};
// 删除最外层目录下文件夹
const removeDir = (dir) => {
return new Promise((resolve, reject) => {
console.log('removedir', dir);
const cp = spawn('rm', ['-rf', dir], { cwd: entry });
cp.on('error', err => {
console.error(`removeDir err: ${err}`);
reject(err);
});
cp.on('close', code => {
// 此时code为0
console.log(`removeDir exited with code ${code}`);
resolve({ code });
});
});
};
// 拷贝文件夹
// cp -rf 20f6e232--cloud--FirstMainMicroApp/dist dist
// cp -rf 20f6e232--cloud--FirstSubMicroApp/dist dist/FirstSubMicroApp
const copyDir = (src, dst) => {
return new Promise((resolve, reject) => {
const cp = spawn('cp', ['-rf', src, dst], { cwd: entry });
cp.on('error', err => {
reject(err);
});
cp.on('close', code => {
// 此时code为0
resolve({ code });
});
})
};
// 先拷贝主应用dist到最外层目录
const copyMainDir = (dist) => {
return new Promise((resolve, reject) => {
const mainApp = microApps.find(app => app.includes(mainAppFolderName));
const src = pathResolve(mainApp, dist);
copyDir(src, dist)
.then(res => resolve(res))
.catch(err => reject(err));
});
};
// 再拷贝微应用dist到主应用dist中, 且给微应用dist重命名
const copySubDir = (dist) => {
const promises = [];
const subApps = microApps.filter(app => app.includes(microAppFolderName));
console.log('subApps: ', subApps);
subApps.forEach(app => {
let rename = app;
if (app.includes('--')) {
const appNames = app.split('--');
const len = appNames.length;
rename = appNames[len - 1];
}
const src = pathResolve(app, dist);
const dst = pathResolve(dist, rename);
const promise = copyDir(src, dst);
promises.push(promise);
});
return promises;
};
// 拷贝主应用与微应用所有dist目录
const copyDirs = (dir) => {
copyMainDir(dir)
.then(res => {
Promise.all(copySubDir(dir))
.then(res => {
console.log('复制dist目录成功');
})
.catch(err => {
console.log('复制微应用dist目录失败', err);
});
})
.catch(err => {
console.log('复制主应用dist目录失败', err);
});
};
const buildMicroApps = async (dir) => {
try {
const isNull = await isExist(pathResolve(dir));
if (!isNull) {
const removeRes = await removeDir(dir);
console.log('removeRes', removeRes);
if (!removeRes.code) {
copyDirs(dir);
}
} else {
copyDirs(dir);
}
} catch(err) {
console.log(err);
}
};
buildMicroApps('dist');