vite子应用vite.config.ts配置
// vite.config.ts
import qiankun from 'vite-plugin-qiankun';
export default {
// 这里的 'myMicroAppName' 是子应用名,主应用注册时AppName需保持一致
plugins: [qiankun('myMicroAppName')],
// 生产环境需要指定运行域名作为base
base: 'http://xxx.com/'
}
// vite.config.ts
import qiankun from 'vite-plugin-qiankun';
export default {
// 这里的 'myMicroAppName' 是子应用名,主应用注册时AppName需保持一致
plugins: [qiankun('myMicroAppName')],
// 生产环境需要指定运行域名作为base
base: 'http://xxx.com/'
}
查看vite-plugin-qiankun相应源码
大致浏览下即可,下面会逐步解析
import cheerio, { CheerioAPI, Element } from 'cheerio'
import { PluginOption } from 'vite'
const createQiankunHelper = (qiankunName: string) => `
const createDeffer = (hookName) => {
const d = new Promise((resolve, reject) => {
window.proxy && (window.proxy[\`vite\${hookName}\`] = resolve)
})
return props => d.then(fn => fn(props));
}
const bootstrap = createDeffer('bootstrap');
const mount = createDeffer('mount');
const unmount = createDeffer('unmount');
const update = createDeffer('update');
;(global => {
global.qiankunName = '${qiankunName}';
global['${qiankunName}'] = {
bootstrap,
mount,
unmount,
update
};
})(window);
`
// eslint-disable-next-line no-unused-vars
const replaceSomeScript = ($: CheerioAPI, findStr: string, replaceStr: string = '') => {
$('script').each((i, el) => {
if ($(el).html()?.includes(findStr)) {
$(el).html(replaceStr)
}
})
}
const createImportFinallyResolve = (qiankunName: string) => {
return `
const qiankunLifeCycle = window.moudleQiankunAppLifeCycles && window.moudleQiankunAppLifeCycles['${qiankunName}'];
if (qiankunLifeCycle) {
window.proxy.vitemount((props) => qiankunLifeCycle.mount(props));
window.proxy.viteunmount((props) => qiankunLifeCycle.unmount(props));
window.proxy.vitebootstrap(() => qiankunLifeCycle.bootstrap());
window.proxy.viteupdate((props) => qiankunLifeCycle.update(props));
}
`
}
export type MicroOption = {
useDevMode?: boolean
}
type PluginFn = (qiankunName: string, microOption?: MicroOption) => PluginOption;
const htmlPlugin: PluginFn = (qiankunName, microOption = {}) => {
let isProduction: boolean
let base = ''
const module2DynamicImport = ($: CheerioAPI, scriptTag: Element) => {
if (!scriptTag) {
return
}
const script$ = $(scriptTag)
const moduleSrc = script$.attr('src')
let appendBase = ''
if (microOption.useDevMode && !isProduction) {
appendBase = '(window.proxy ? (window.proxy.__INJECTED_PUBLIC_PATH_BY_QIANKUN__ + \'..\') : \'\') + '
}
script$.removeAttr('src')
script$.removeAttr('type')
script$.html(`import(${appendBase}'${moduleSrc}')`)
return script$
}
return {
name: 'qiankun-html-transform',
configResolved (config) {
isProduction = config.command === 'build' || config.isProduction
base = config.base
},
configureServer (server) {
return () => {
server.middlewares.use((req, res, next) => {
if (isProduction || !microOption.useDevMode) {
next()
return
}
const end = res.end.bind(res)
res.end = (...args: any[]) => {
let [htmlStr, ...rest] = args
if (typeof htmlStr === 'string') {
const $ = cheerio.load(htmlStr)
module2DynamicImport($, $(`script[src=${base}@vite/client]`).get(0))
htmlStr = $.html()
}
end(htmlStr, ...rest)
}
next()
})
}
},
transformIndexHtml (html: string) {
const $ = cheerio.load(html)
const moduleTags = $('body script[type=module], head script[crossorigin=""]')
if (!moduleTags || !moduleTags.length) {
return
}
const len = moduleTags.length
moduleTags.each((i, moduleTag) => {
const script$ = module2DynamicImport($, moduleTag)
if (len - 1 === i) {
script$?.html(`${script$.html()}.finally(() => {
${createImportFinallyResolve(qiankunName)}
})`)
}
})
$('body').append(`<script>${createQiankunHelper(qiankunName)}</script>`)
const output = $.html()
return output
}
}
}
export default htmlPlugin
import cheerio, { CheerioAPI, Element } from 'cheerio'
import { PluginOption } from 'vite'
const createQiankunHelper = (qiankunName: string) => `
const createDeffer = (hookName) => {
const d = new Promise((resolve, reject) => {
window.proxy && (window.proxy[\`vite\${hookName}\`] = resolve)
})
return props => d.then(fn => fn(props));
}
const bootstrap = createDeffer('bootstrap');
const mount = createDeffer('mount');
const unmount = createDeffer('unmount');
const update = createDeffer('update');
;(global => {
global.qiankunName = '${qiankunName}';
global['${qiankunName}'] = {
bootstrap,
mount,
unmount,
update
};
})(window);
`
// eslint-disable-next-line no-unused-vars
const replaceSomeScript = ($: CheerioAPI, findStr: string, replaceStr: string = '') => {
$('script').each((i, el) => {
if ($(el).html()?.includes(findStr)) {
$(el).html(replaceStr)
}
})
}
const createImportFinallyResolve = (qiankunName: string) => {
return `
const qiankunLifeCycle = window.moudleQiankunAppLifeCycles && window.moudleQiankunAppLifeCycles['${qiankunName}'];
if (qiankunLifeCycle) {
window.proxy.vitemount((props) => qiankunLifeCycle.mount(props));
window.proxy.viteunmount((props) => qiankunLifeCycle.unmount(props));
window.proxy.vitebootstrap(() => qiankunLifeCycle.bootstrap());
window.proxy.viteupdate((props) => qiankunLifeCycle.update(props));
}
`
}
export type MicroOption = {
useDevMode?: boolean
}
type PluginFn = (qiankunName: string, microOption?: MicroOption) => PluginOption;
const htmlPlugin: PluginFn = (qiankunName, microOption = {}) => {
let isProduction: boolean
let base = ''
const module2DynamicImport = ($: CheerioAPI, scriptTag: Element) => {
if (!scriptTag) {
return
}
const script$ = $(scriptTag)
const moduleSrc = script$.attr('src')
let appendBase = ''
if (microOption.useDevMode && !isProduction) {
appendBase = '(window.proxy ? (window.proxy.__INJECTED_PUBLIC_PATH_BY_QIANKUN__ + \'..\') : \'\') + '
}
script$.removeAttr('src')
script$.removeAttr('type')
script$.html(`import(${appendBase}'${moduleSrc}')`)
return script$
}
return {
name: 'qiankun-html-transform',
configResolved (config) {
isProduction = config.command === 'build' || config.isProduction
base = config.base
},
configureServer (server) {
return () => {
server.middlewares.use((req, res, next) => {
if (isProduction || !microOption.useDevMode) {
next()
return
}
const end = res.end.bind(res)
res.end = (...args: any[]) => {
let [htmlStr, ...rest] = args
if (typeof htmlStr === 'string') {
const $ = cheerio.load(htmlStr)
module2DynamicImport($, $(`script[src=${base}@vite/client]`).get(0))
htmlStr = $.html()
}
end(htmlStr, ...rest)
}
next()
})
}
},
transformIndexHtml (html: string) {
const $ = cheerio.load(html)
const moduleTags = $('body script[type=module], head script[crossorigin=""]')
if (!moduleTags || !moduleTags.length) {
return
}
const len = moduleTags.length
moduleTags.each((i, moduleTag) => {
const script$ = module2DynamicImport($, moduleTag)
if (len - 1 === i) {
script$?.html(`${script$.html()}.finally(() => {
${createImportFinallyResolve(qiankunName)}
})`)
}
})
$('body').append(`<script>${createQiankunHelper(qiankunName)}</script>`)
const output = $.html()
return output
}
}
}
export default htmlPlugin
htmlPluginh
函数
可以看到子应用调用的qiankun
函数就是htmlPlugin
函数。
transformIndexHtml
是vite插件钩子,传入的是vite启动项目的html页面代码
插件中传入的qiankunName
字段用于createImportFinallyResolve
和createQiankunHelper
函数使用
transformIndexHtml (html: string) {
const $ = cheerio.load(html)
const moduleTags = $('body script[type=module], head script[crossorigin=""]')
if (!moduleTags || !moduleTags.length) {
return
}
const len = moduleTags.length
moduleTags.each((i, moduleTag) => {
const script$ = module2DynamicImport($, moduleTag)
if (len - 1 === i) {
script$?.html(`${script$.html()}.finally(() => {
${createImportFinallyResolve(qiankunName)}
})`)
}
})
$('body').append(`<script>${createQiankunHelper(qiankunName)}</script>`)
const output = $.html()
return output
}
transformIndexHtml (html: string) {
const $ = cheerio.load(html)
const moduleTags = $('body script[type=module], head script[crossorigin=""]')
if (!moduleTags || !moduleTags.length) {
return
}
const len = moduleTags.length
moduleTags.each((i, moduleTag) => {
const script$ = module2DynamicImport($, moduleTag)
if (len - 1 === i) {
script$?.html(`${script$.html()}.finally(() => {
${createImportFinallyResolve(qiankunName)}
})`)
}
})
$('body').append(`<script>${createQiankunHelper(qiankunName)}</script>`)
const output = $.html()
return output
}
可以看到主要功能是往vite的script中插入了代码,'body script[type=module], head script[crossorigin=""]'
这一部分可以会随着vite源码变化而变化,后面有时间看看vite源码相应部分
module2DynamicImport
函数
const module2DynamicImport = ($: CheerioAPI, scriptTag: Element) => {
if (!scriptTag) {
return
}
const script$ = $(scriptTag)
const moduleSrc = script$.attr('src')
let appendBase = ''
if (microOption.useDevMode && !isProduction) {
appendBase = '(window.proxy ? (window.proxy.__INJECTED_PUBLIC_PATH_BY_QIANKUN__ + \'..\') : \'\') + '
}
script$.removeAttr('src')
script$.removeAttr('type')
script$.html(`import(${appendBase}'${moduleSrc}')`)
return script$
}
const module2DynamicImport = ($: CheerioAPI, scriptTag: Element) => {
if (!scriptTag) {
return
}
const script$ = $(scriptTag)
const moduleSrc = script$.attr('src')
let appendBase = ''
if (microOption.useDevMode && !isProduction) {
appendBase = '(window.proxy ? (window.proxy.__INJECTED_PUBLIC_PATH_BY_QIANKUN__ + \'..\') : \'\') + '
}
script$.removeAttr('src')
script$.removeAttr('type')
script$.html(`import(${appendBase}'${moduleSrc}')`)
return script$
}
module2DynamicImport
函数作用是对传入的 HTML 文档中的 <script>
标签进行处理。函数接受两个参数:$
是 Cheerio 库的实例,用于在 HTML 中进行选择和操作;scriptTag
是要处理的 <script>
标签元素。
获取了 scriptTag
的 src
属性值,即脚本文件的 URL 地址。根据代码中的条件判断,如果 microOption.useDevMode
为真且非生产环境,则在 URL 地址前添加一个相对路径,这个相对路径会根据当前环境中的 window.proxy.__INJECTED_PUBLIC_PATH_BY_QIANKUN__
的值进行动态注入。
这一步的目的是在开发模式下加载脚本时,根据 Qiankun 微前端框架的注入公共路径来处理脚本的路径。
接着,函数移除了 scriptTag
的 src
和 type
属性,然后将其内容重新设置为一个动态 import()
语句,用于按需动态加载脚本。在 import()
函数中,会根据 appendBase
变量拼接出完整的脚本路径,并进行动态加载。
最后,函数返回处理后的 script$
对象,即经过修改后的 <script>
标签的 jQuery 对象。
createImportFinallyResolve
函数与createQiankunHelper
函数
将window.moudleQiankunAppLifeCycles
上对应的子应用中注册的生命周期函数,挂载到 window.proxy
上
const createImportFinallyResolve = (qiankunName: string) => {
return `
const qiankunLifeCycle = window.moudleQiankunAppLifeCycles && window.moudleQiankunAppLifeCycles['${qiankunName}'];
if (qiankunLifeCycle) {
window.proxy.vitemount((props) => qiankunLifeCycle.mount(props));
window.proxy.viteunmount((props) => qiankunLifeCycle.unmount(props));
window.proxy.vitebootstrap(() => qiankunLifeCycle.bootstrap());
window.proxy.viteupdate((props) => qiankunLifeCycle.update(props));
}
`
}
const createImportFinallyResolve = (qiankunName: string) => {
return `
const qiankunLifeCycle = window.moudleQiankunAppLifeCycles && window.moudleQiankunAppLifeCycles['${qiankunName}'];
if (qiankunLifeCycle) {
window.proxy.vitemount((props) => qiankunLifeCycle.mount(props));
window.proxy.viteunmount((props) => qiankunLifeCycle.unmount(props));
window.proxy.vitebootstrap(() => qiankunLifeCycle.bootstrap());
window.proxy.viteupdate((props) => qiankunLifeCycle.update(props));
}
`
}
立即执行函数表达式(Immediately Invoked Function Expression,IIFE),也称为自执行函数。该函数在定义后立即执行,不需要通过函数名调用,常用于创建私有作用域,避免污染全局命名空间。
const createQiankunHelper = (qiankunName: string) => `
const createDeffer = (hookName) => {
const d = new Promise((resolve, reject) => {
window.proxy && (window.proxy[\`vite\${hookName}\`] = resolve)
})
return props => d.then(fn => fn(props));
}
const bootstrap = createDeffer('bootstrap');
const mount = createDeffer('mount');
const unmount = createDeffer('unmount');
const update = createDeffer('update');
;(global => {
global.qiankunName = '${qiankunName}';
global['${qiankunName}'] = {
bootstrap,
mount,
unmount,
update
};
})(window);
`
const createQiankunHelper = (qiankunName: string) => `
const createDeffer = (hookName) => {
const d = new Promise((resolve, reject) => {
window.proxy && (window.proxy[\`vite\${hookName}\`] = resolve)
})
return props => d.then(fn => fn(props));
}
const bootstrap = createDeffer('bootstrap');
const mount = createDeffer('mount');
const unmount = createDeffer('unmount');
const update = createDeffer('update');
;(global => {
global.qiankunName = '${qiankunName}';
global['${qiankunName}'] = {
bootstrap,
mount,
unmount,
update
};
})(window);
`