版權(quán)說明:本文檔由用戶提供并上傳,收益歸屬內(nèi)容提供方,若內(nèi)容存在侵權(quán),請(qǐng)進(jìn)行舉報(bào)或認(rèn)領(lǐng)
文檔簡(jiǎn)介
第微前端架構(gòu)ModuleFederationPlugin源碼解析目錄序言背景MF基本介紹應(yīng)用場(chǎng)景微前端架構(gòu)服務(wù)化的library和componentsModuleFederationPlugin源碼解析入口源碼ExposesRemotesShared小結(jié)總結(jié)
序言
本文是WebpackModuleFederationPlugin(后面簡(jiǎn)稱MF)源碼解析文章中的第一篇,在此系列文章中,我將帶領(lǐng)大家抽絲剝繭、一步步地去解析MF源碼。當(dāng)然為了幫助大家理解,可能中間也會(huì)涉及到Webpack源碼中的其它實(shí)現(xiàn),我會(huì)根據(jù)情況或淺或深的一并進(jìn)行講解。因?yàn)榭碬ebpack源碼需要掌握的知識(shí)量非常大,所以為了更好理解文章中的內(nèi)容,你最好有如下Webpack相關(guān)的背景知識(shí):
對(duì)Webpack核心的數(shù)據(jù)結(jié)構(gòu):Dependency、Module、Chunk等有基本的認(rèn)識(shí)了解Webpack中的插件機(jī)制,對(duì)基于tabpable的Hooks機(jī)制有一定的了解,如果寫過Webpack插件就更好了看過MF的官方文檔,對(duì)其帶來的關(guān)鍵性作用有基本的認(rèn)識(shí),如果了解一些其應(yīng)用場(chǎng)景就更好了
話不多說,讓我們開始正文。
背景
先簡(jiǎn)單說一下為什么要去閱讀MF的源碼,我個(gè)人理解閱讀源碼有兩個(gè)原因:
一,它的實(shí)現(xiàn)非常優(yōu)秀,通過閱讀源碼能學(xué)習(xí)一些設(shè)計(jì)思想和編程技巧;
二,工作或者自己的項(xiàng)目使用到了,但是官方給的文檔不太夠,遇到問題無論最后有沒有解決,都有點(diǎn)摸不著頭腦,閱讀源碼是為了更好地了解其內(nèi)部實(shí)現(xiàn),遇到問題更容易debug。
而我閱讀MF的源碼,主要是出于第二種目的,當(dāng)然我個(gè)人對(duì)Webpack也是非常感興趣。目前我們部門B端的產(chǎn)品是基于MF實(shí)現(xiàn)的微前端架構(gòu),而我主要負(fù)責(zé)B端的開發(fā)以及參與B端性能優(yōu)化專項(xiàng),今年大部分時(shí)間都是跟MF搏斗。
雖然到目前為止,性能優(yōu)化已經(jīng)獲得了一些階段性的勝利,但是實(shí)際上在這個(gè)過程中,我們還是走了很多彎路。這些彎路不少是由于對(duì)MF內(nèi)部的實(shí)現(xiàn)細(xì)節(jié)不夠了解導(dǎo)致的,當(dāng)然除此之外,我們還需要建立一套規(guī)范的MF標(biāo)準(zhǔn)化開發(fā)流程。所以,閱讀MF源碼對(duì)于我個(gè)人來說非常有必要。
首先,我們先簡(jiǎn)單了解下MF相關(guān)知識(shí)。
MF基本介紹
首先,MF是一個(gè)Webpack的官方插件,在Webpack生態(tài)中有茫茫多的插件中,好像一個(gè)插件有點(diǎn)微不足道。但是,MF的作者稱其為Agame-changerinJavaScriptarchitecture,當(dāng)然從構(gòu)建工具的角度來講,有點(diǎn)言過其實(shí),因?yàn)樗荒苡糜赪ebpack中。
但是從它帶來的JavaScript架構(gòu)設(shè)計(jì)上的理念:遠(yuǎn)程依賴共享(復(fù)用組件或者其它邏輯),我覺得其實(shí)是給前端帶來新的思考視角。
以前我們復(fù)用組件或者邏輯主要的方式有:
抽離一個(gè)NPM包,從維護(hù)性和復(fù)用性角度來講,是目前最常見的方式。缺點(diǎn)在于,在微前端架構(gòu)中,如果fix了一個(gè)NPM包問題,那么每一個(gè)應(yīng)用都需要升級(jí)版本,重新構(gòu)建打包部署上線,多團(tuán)隊(duì)開發(fā)的時(shí)候非常低效;將產(chǎn)物打包成UMD的格式,然后通過CDN的方式能一定程度解決重新構(gòu)建打包上線的問題,但是隨著復(fù)用的組件和邏輯越多,可能會(huì)引入很多多余的chunk問題(如果對(duì)性能有很高的要求)。比如A和B組件同時(shí)依賴了lodash,那么打包成UMD格式有多余的lodashchunk,沒法復(fù)用。
我們來看下MF是怎么解決這個(gè)問題的。首先看一個(gè)簡(jiǎn)單的MF使用的例子,假設(shè)我們現(xiàn)在有兩個(gè)應(yīng)用app1和app2:
//app1webpack.config.js
module.exports={
//省略其它配置
plugins:[
newModuleFederationPlugin({
name:'app1',
filename:'remoteEntry.js',
remotes:{
app2:'app2@http://localhost:3002/remoteEntry.js',
exposes:{
'./input':'./src/components/Input'
shared:{
'react':{
singleton:true,
requiredVersion:require('./package.json').dependencies.react
'react-dom':{
singleton:true,
requiredVersion:require('./package.json').dependencies['react-dom']
'lodash':{
requiredVersion:require('./package.json').dependencies['lodash'],
singleton:true,
//app1src/components/Input.tsx
import*asReactfrom'react'
import{Input}from'antd'
exportdefaultfunctionWrapperInput(){
return(
div
app1input:Input/
/div
//app1src/App.tsx
import{Input}from'antd';
import*asReactfrom'react';
constRemoteButton=React.lazy(()=import('app2/Button'));
constApp=()=(
div
h1Typescript/h1
h2App1/h2
React.Suspensefallback="LoadingButton"
RemoteButton/
/React.Suspense
div
Input/
/div
/div
exportdefaultApp;
app2的部分代碼:
//app2webpack.config.js
module.exports={
//省略其它配置
plugins:[
newModuleFederationPlugin({
name:'app2',
filename:'remoteEntry.js',
exposes:{
'./Button':'./src/Button',
remotes:{
app1:'app1@http://localhost:3001/remoteEntry.js',
//app2src/Button.tsx
import*asReactfrom'react';
constButton=()=buttonApp3Button/button
exportdefaultButton;
//app2src/App.tsx
import*asReactfrom'react';
importLocalButtonfrom'./Button';
importRemoteInputfrom'app1/input';
constApp=()=(
div
h1Typescript/h1
h2App3/h2
LocalButton/
React.Suspensefallback={null}
RemoteInput/
/React.Suspense
/div
exportdefaultApp;
最后實(shí)現(xiàn)的效果:
簡(jiǎn)單的Webpack配置,我們就可以實(shí)現(xiàn)app1和app2兩個(gè)應(yīng)用之間的組件遠(yuǎn)程共享,從代碼看,我們知道app1依賴了app2的Button組件,而app2依賴了app1的Input組件。
當(dāng)然不止如此,MF還可以做到:
依賴復(fù)用,app1和app2同時(shí)依賴了react和react-dom,那我們可以在雙方的Webpack配置中,將兩個(gè)依賴配置成shared,而且通過requiredVersion指定版本;微前端架構(gòu),微前端架構(gòu)有很多實(shí)現(xiàn)的方式,比如iframe、web-component等,但是MF的出現(xiàn),使得實(shí)現(xiàn)一套微前端的架構(gòu)更加簡(jiǎn)單,也能非常容易解決微前端架構(gòu)中的一些組件復(fù)用問題、頻繁構(gòu)建部署上線問題;支持服務(wù)端渲染,MF的實(shí)現(xiàn)不依賴瀏覽器,同樣的代碼,只需要將Webpack配置中的target改成
node,那么構(gòu)建的產(chǎn)物就能支持SSR。
到這里,讀者已經(jīng)對(duì)MF的使用和定位有了基本的印象,根據(jù)MF帶來的全新的復(fù)用能力,我們可以做一些應(yīng)用場(chǎng)景的思考。
應(yīng)用場(chǎng)景
微前端架構(gòu)
微前端是這幾年比較火的一個(gè)前端應(yīng)用架構(gòu)方案,其中比較核心的一點(diǎn)是各子應(yīng)用之間要做到獨(dú)立開發(fā),獨(dú)立構(gòu)建部署上線。從上一節(jié)對(duì)MF的介紹中,我們發(fā)現(xiàn)它天然就已經(jīng)有這個(gè)優(yōu)勢(shì),因此為了設(shè)計(jì)一個(gè)基于MF的微前端架構(gòu),我們要解決的第一點(diǎn)是子應(yīng)用之間需要有個(gè)類似中心化的服務(wù),將其它子應(yīng)用的服務(wù)地址下發(fā)給需要消費(fèi)的子應(yīng)用;第二點(diǎn),我們要解決子應(yīng)用之間的一些通信問題,例如共享的一些用戶狀態(tài)。當(dāng)然還有一些其它問題,例如UI一致性問題。
基于以上的問題,我們可以很容易想到一種非常經(jīng)典的微前端架構(gòu)方案,那就是基于一個(gè)基座服務(wù)的中心化的架構(gòu)方式。
每個(gè)APP都是一個(gè)子應(yīng)用,這里可以有兩種方式:如果完全不需要依賴基座的狀態(tài),則可以做成一個(gè)更加通用的前端服務(wù),只作為提供方,在MF中也稱為remotes應(yīng)用。如果需要依賴主應(yīng)用的狀態(tài),或者說只導(dǎo)出路由讓基座幫忙注冊(cè),這樣就可以共享基座的所有狀態(tài),這種方式與我們現(xiàn)在B端的架構(gòu)方式類似。這樣的架構(gòu)方式,也能通過MFshared機(jī)制鎖定UI庫(kù)的版本,保證所有子應(yīng)用UI的一致性。
服務(wù)化的library和components
跳出微前端架構(gòu),假設(shè)我們現(xiàn)在的場(chǎng)景是維護(hù)一個(gè)巨型前端應(yīng)用,我們發(fā)現(xiàn)隨著頁(yè)面和依賴的第三方依賴逐漸增多,那么每次開發(fā)構(gòu)建部署上線的時(shí)長(zhǎng)也會(huì)不斷增加。雖然Webpackv5+版本已經(jīng)做了很多優(yōu)化例如本地緩存,但是對(duì)于巨型應(yīng)用,我們還是發(fā)現(xiàn)構(gòu)建還是非常低效。于是,基于MF的能力,我們可以做這樣的一個(gè)架構(gòu)設(shè)計(jì):
我們可以將平時(shí)使用的第三方庫(kù)和組件庫(kù),分別做成一個(gè)單獨(dú)的服務(wù),如果部門技術(shù)棧統(tǒng)一的項(xiàng)目可以通過MF插件遠(yuǎn)程使用這兩個(gè)服務(wù),這樣無論是開發(fā)時(shí)還是上線構(gòu)建都可以省掉這部分的構(gòu)建時(shí)間,一定程度上提高了開發(fā)效率。
MF的使用姿勢(shì)非常靈活,你可以根據(jù)開發(fā)需要,充分挖掘更多的使用場(chǎng)景。MF介紹的部分就到這里,下面我們正式進(jìn)入源碼解析的內(nèi)容。
ModuleFederationPlugin源碼解析
入口源碼
MF插件相關(guān)的源碼放在lib/container下,我們首先看下lib/container/ModuleFedration.js的代碼:
//省略一些import代碼
classModuleFederationPlugin{
*@param{ModuleFederationPluginOptions}optionsoptions
constructor(options){
validate(options);
this._options=options;
*Applytheplugin
*@param{Compiler}compilerthecompilerinstance
*@returns{void}
apply(compiler){
const{_options:options}=this;
//expose模塊編譯產(chǎn)物導(dǎo)出的類型,選項(xiàng)有var、umd、commonjs、module等,跟output配置中的library作用是一樣的
//var代表輸出的模塊是掛在window對(duì)象上
constlibrary=options.library||{type:"var",name:};
//remote模的類型,選項(xiàng)有var、umd、commonjs、module等,跟output配置中的library作用是一樣的
constremoteType=
options.remoteType||
(options.libraryisValidExternalsType(options.library.type)
/**@type{ExternalsType}*/(options.library.type)
:"script");
//enabledLibraryTypes專門存儲(chǔ)entry需要輸出的library類型,然后被EnableLibraryPlugin插件消費(fèi),
if(
library
!compiler.options.output.enabledLibraryTypes.includes(library.type)
compiler.options.output.enabledLibraryTypes.push(library.type);
//在完成所有內(nèi)部插件注冊(cè)后處理MF插件
compiler.hooks.afterPlugins.tap("ModuleFederationPlugin",()={
if(
options.exposes
(Array.isArray(options.exposes)
options.exposes.length0
:Object.keys(options.exposes).length0)
//如果有expose配置,則注冊(cè)一個(gè)ContainerPlugin
newContainerPlugin({
name:,
library,
filename:options.filename,
runtime:options.runtime,
shareScope:options.shareScope,
exposes:options.exposes
}).apply(compiler);
if(
options.remotes
(Array.isArray(options.remotes)
options.remotes.length0
:Object.keys(options.remotes).length0)
//如果有expose配置,則初始化一個(gè)ContainerReferencePlugin
newContainerReferencePlugin({
remoteType,
shareScope:options.shareScope,
remotes:options.remotes
}).apply(compiler);
if(options.shared){
//如果有shared配置,則初始化一個(gè)SharePlugin
newSharePlugin({
shared:options.shared,
shareScope:options.shareScope
}).apply(compiler);
從代碼中可以看出,MF插件入口的代碼其實(shí)不復(fù)雜,核心的代碼不到100行,我們首先把焦點(diǎn)放在插件初始化的options參數(shù)上,它的類型為ModuleFederationPluginOptions。
這里有個(gè)細(xì)節(jié)可以注意下,因?yàn)閃ebpack的源碼是用純JS寫的,為了彌補(bǔ)如像TypeScript的類型注釋的優(yōu)勢(shì)使得源碼更加可讀的問題,Webpack使用了JSDoc配合VSCode,在大多數(shù)場(chǎng)景下也能起到類型注釋的效果,而Webpack根目錄下的declarations目錄使用了TS定義了核心的一些數(shù)據(jù)類型,然后導(dǎo)出給其它JS文件在使用JSDoc時(shí)使用。
我們回到主題,我們看下ModuleFederationPluginOptions的類型定義:
exportinterfaceModuleFederationPluginOptions{
*container應(yīng)用導(dǎo)出的模塊配置,一般是一個(gè)對(duì)象
exposes:Exposes;
*打包產(chǎn)物的文件名稱
filename:string;
*構(gòu)建產(chǎn)物的類型,里面的type配置可以是umd、commonjs、var等類型
library:LibraryOptions;
*container的名稱
name:string;
*依賴的remote應(yīng)用library類型,配置的值可以是umd、commonjs、script、var等
remoteType:ExternalsType;
*container應(yīng)用依賴的遠(yuǎn)程應(yīng)用
remotes:Remotes;
*配置了該選項(xiàng),會(huì)為模塊split一個(gè)以該名稱命名的chunk
runtime:EntryRuntime;
*所有共享模塊的作用域名稱,默認(rèn)為default,很少會(huì)修改
shareScope:string;
*應(yīng)用之間需要共享的模塊
shared:Shared;
每個(gè)選項(xiàng)我都用注釋做了簡(jiǎn)單的介紹,我們重點(diǎn)關(guān)注幾個(gè)常用的配置,對(duì)于library、runtime、
remoteType等配置平時(shí)很少使用,這里先不過多介紹,后面看到相關(guān)的源碼可以再回顧。
filename和name比較好理解,以上一小節(jié)的app1的Webpack配置為例,我們可以看到其配置如下:
newModuleFederationPlugin({
name:'app1',
filename:'remoteEntry.js',
//省略其它配置
如果這樣配置,app1正好expose了一些模塊給其它應(yīng)用消費(fèi),例如app2,則app2首先要通過app1的
name去找到它,也就是會(huì)在remotes的配置中添加app1的指向,這里等會(huì)介紹remotes選項(xiàng)時(shí)再細(xì)說。而app2在運(yùn)行時(shí)就會(huì)加載到app1的構(gòu)建產(chǎn)物remoteEntry.js,訪問app2的服務(wù),打開network,我們可以看到其加載了app1的remoteEntry.js:
我們重點(diǎn)介紹下exposes、remotes、shared等選項(xiàng)。
在上面的MF插件源碼中,其核心的幾行代碼就是,在afterPluginshook觸發(fā)后(完成其它所有內(nèi)置插件初始化后),根據(jù)是否有上面三個(gè)配置,來決定是否要注冊(cè)ContainerPlugin、
ContainerReferencePlugin、SharePlugin等插件。所以更加核心的實(shí)現(xiàn),是分別交給了上面三個(gè)插件去完成。
Exposes
exposes的配置是告訴Webpack當(dāng)前應(yīng)用導(dǎo)出給其它應(yīng)用消費(fèi)的模塊,首先我們來看下exposes配置的類型定義:
exporttypeExposes=(ExposesItem|ExposesObject)[]|ExposesObject;
exporttypeExposesItem=string;
exporttypeExposesItems=ExposesItem[];
exportinterfaceExposesObject{
[k:string]:ExposesConfig|ExposesItem|ExposesItems;
exportinterfaceExposesConfig{
import:ExposesItem|ExposesItems;
name:string;
上面的類型定義相對(duì)來說比較簡(jiǎn)單,只是套娃比較多,還是以上面app1的Webpack配置為例,據(jù)我平時(shí)了解到的,最常見的配置方式還是:
exposes:{
'./input':'./src/components/Input'
但是你也可以配置:
exposes:{
'./input':{
name:'input',
import:'./src/components/Input'
這種方式配置會(huì)有什么不一樣了?這里會(huì)留一個(gè)懸念,在看后續(xù)的源碼中,我們?cè)僭敿?xì)介紹。
Remotes
remotes配置是告訴Webpack當(dāng)前應(yīng)用依賴了哪些遠(yuǎn)程應(yīng)用,我們來看下其類型定義:
exporttypeRemotes=(RemotesItem|RemotesObject)[]|RemotesObject;
exporttypeRemotesItem=string;
exporttypeRemotesItems=RemotesItem[];
exportinterfaceRemotesObject{
[k:string]:RemotesConfig|RemotesItem|RemotesItems;
exportinterfaceRemotesConfig{
*共享模塊需要依賴的其它模塊
external:RemotesItem|RemotesItems;
//共享作用域的名稱,默認(rèn)為default
shareScope:string;
還是以app1為例,我們回顧其remote的配置:
remotes:{
app2:'app2@http://localhost:3002/remoteEntry.js',
告訴了Webpack如果需要消費(fèi)app2導(dǎo)出的模塊,那么則需要加載app2服務(wù)的remoteEntry.js文件,所以app1在初始化的時(shí)候就會(huì)加載此文件,然后通過下面的方式加載app2導(dǎo)出的模塊:
importRemoteButtonfrom'app2/Button';
是不是有點(diǎn)神奇,這里面的實(shí)現(xiàn)用了什么黑魔法,簡(jiǎn)單的幾個(gè)配置,然后啟動(dòng)服務(wù),就能消費(fèi)其它遠(yuǎn)程應(yīng)用的模塊。保持耐心,后續(xù)我們將慢慢揭開其神秘的面紗。
Shared
MF關(guān)于shared配置部分是我個(gè)人覺得最復(fù)雜的部分,當(dāng)然SharedPlugin的實(shí)現(xiàn)也是相對(duì)來說比較復(fù)雜,因?yàn)檫@里牽扯到一些需要shared配置延伸出的例如單例問題。先留個(gè)懸念,稍后解釋單例問題,我們還是先看shared配置類型定義:
exporttypeShared=(SharedItem|SharedObject)[]|SharedObject;
exporttypeSharedItem=string;
exportinterfaceSharedObject{
[k:string]:SharedConfig|SharedItem;
exportinterfaceSharedConfig{
//配置了eager是告訴webpack該模塊是作為一個(gè)initialchunk,無論怎么樣,初始化都需要加載該模塊
eager:boolean;
//共享模塊依賴的模塊
import:false|SharedItem;
//共享模塊的包名
packageName:string;
//共享模塊的版本
requiredVersion:false|string;
//如果配置了key,查找共享模塊的時(shí)候,會(huì)在當(dāng)前共享作用域查找配置的key
shareKey:string;
//共享作用域
shareScope:string;
//是否需要保持單例
singleton:boolean;
//是否需要嚴(yán)格校驗(yàn)共享模塊的版本,只有配置了requiredVersion配置該選型才有效
strictVersion:boolean;
//指定提供的模塊的版本,將會(huì)替代低版本的模塊,但是不會(huì)替代版本更好的模塊
version:false|string;
從SharedConfig類型我們就可以看到shared配置有很多的場(chǎng)景需要適配,每個(gè)配置我都做了簡(jiǎn)單注釋來介紹。當(dāng)然可能這個(gè)時(shí)候,不熟悉MF的小伙伴看到這些配置可能是懵逼的狀態(tài)。不用著急,這些配置項(xiàng),在后面更加具體的源碼使用場(chǎng)景,我會(huì)再進(jìn)行介紹,這里先留個(gè)印象。
我們還是看下app1的配置:
shared:{
'react':{
singleton:true,
requiredVersion:require('./package.json').dependencies.react
'react-dom':{
singleton:true,
requiredVersion:require('./package.json').dependencies['react-dom']
'lodash':{
requiredVersion:require('./package.json').dependencies['lodash'],
singleton:true,
這里分別將react、react-dom、lodash等三
溫馨提示
- 1. 本站所有資源如無特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請(qǐng)下載最新的WinRAR軟件解壓。
- 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請(qǐng)聯(lián)系上傳者。文件的所有權(quán)益歸上傳用戶所有。
- 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁(yè)內(nèi)容里面會(huì)有圖紙預(yù)覽,若沒有圖紙預(yù)覽就沒有圖紙。
- 4. 未經(jīng)權(quán)益所有人同意不得將文件中的內(nèi)容挪作商業(yè)或盈利用途。
- 5. 人人文庫(kù)網(wǎng)僅提供信息存儲(chǔ)空間,僅對(duì)用戶上傳內(nèi)容的表現(xiàn)方式做保護(hù)處理,對(duì)用戶上傳分享的文檔內(nèi)容本身不做任何修改或編輯,并不能對(duì)任何下載內(nèi)容負(fù)責(zé)。
- 6. 下載文件中如有侵權(quán)或不適當(dāng)內(nèi)容,請(qǐng)與我們聯(lián)系,我們立即糾正。
- 7. 本站不保證下載資源的準(zhǔn)確性、安全性和完整性, 同時(shí)也不承擔(dān)用戶因使用這些下載資源對(duì)自己和他人造成任何形式的傷害或損失。
最新文檔
- 技術(shù)要領(lǐng):網(wǎng)站性能優(yōu)化關(guān)鍵點(diǎn)
- 2026年公共關(guān)系管理情境模擬題媒體溝通策略題目
- 2026年審計(jì)專業(yè)認(rèn)證試題GJB與ISO雙重標(biāo)準(zhǔn)下的審計(jì)題
- 2026年綠色能源市場(chǎng)與投資策略試題集
- 2026年烹飪技能競(jìng)賽經(jīng)典菜肴制作標(biāo)準(zhǔn)題
- 2026年會(huì)員營(yíng)銷策略有效性測(cè)試題
- 2026年測(cè)試工程師基礎(chǔ)知識(shí)與進(jìn)階知識(shí)測(cè)試題
- 2026年外語(yǔ)翻譯技能與教學(xué)方法試題集
- 2026年建筑師執(zhí)業(yè)資格考試題庫(kù)建筑設(shè)計(jì)與實(shí)踐操作指南
- 2025 小學(xué)二年級(jí)道德與法治上冊(cè)友好交流使用禮貌用語(yǔ)對(duì)話更和諧更有禮課件
- 深圳大疆在線測(cè)評(píng)行測(cè)題庫(kù)
- 金屬?gòu)S生產(chǎn)制度
- 2026安徽淮北市特種設(shè)備監(jiān)督檢驗(yàn)中心招聘專業(yè)技術(shù)人員4人參考題庫(kù)及答案1套
- 2025年航空行業(yè)空客智能制造報(bào)告
- 蒙牛乳業(yè)股份有限公司盈利能力分析
- 2025民航西藏空管中心社會(huì)招聘14人(第1期)筆試參考題庫(kù)附帶答案詳解(3卷合一版)
- (新教材)2026年人教版八年級(jí)下冊(cè)數(shù)學(xué) 21.2.1 平行四邊形及其性質(zhì) 課件
- 設(shè)備保養(yǎng)維護(hù)規(guī)程
- 2025年?yáng)|營(yíng)中考物理真題及答案
- DL-T+5860-2023+電化學(xué)儲(chǔ)能電站可行性研究報(bào)告內(nèi)容深度規(guī)定
- GB/T 46425-2025煤矸石山生態(tài)修復(fù)技術(shù)規(guī)范
評(píng)論
0/150
提交評(píng)論