新闻详情

新闻详情

首页 / 资讯中心 / 详情

Angular 应用构建系统迁移指南:从 webpack `browser` 构建器升级到 `application` 新构建体系

发布时间:2026/9/8 21:59:24来源:尧图网络
Angular 应用构建系统迁移指南:从 webpack `browser` 构建器升级到 `application` 新构建体系
Angular 应用构建系统迁移指南从 webpackbrowser构建器升级到application新构建体系【免费下载链接】angularDeliver web apps with confidence 项目地址: https://gitcode.com/GitHub_Trending/an/angular本文以 Angular 官方文档 build-system-migration.md 为核心系统讲解自 v17 起引入的现代应用构建系统它基于 ESM、esbuild 与 Vite内置 SSR 与预渲染能力并将逐步取代旧的 webpack 构建系统。文章面向正在使用browser构建器的存量项目给出自动迁移与手动迁移两条路线、application构建器选项调整清单、开发服务器HMR、预打包行为差异以及define、loader 自定义、导入/导出条件等新增能力与已知问题帮助你在angular.json层面完成一次低风险的构建系统切换。新构建系统是什么在 v17 及更高版本中Angular 提供了全新的应用构建系统旨在以更现代化的方式构建 Angular 应用。这套新构建系统包含以下核心特性现代化输出格式基于 ESM并利用动态import()表达式支持懒加载模块更快的构建性能无论首次构建还是增量重建构建耗时都有显著改善新一代 JavaScript 生态工具底层采用 esbuild 与 Vite内置 SSR 与预渲染能力服务端渲染与构建期预渲染被集成进同一个构建器样式表热替换全局样式与组件样式均可自动进行热更新。新构建系统已经稳定并完全支持用于 Angular 应用。凡是使用browser构建器的应用都可以迁移到新构建系统如果项目使用的是自定义构建器则需要查阅该构建器自身的文档以确认可行的迁移方案。重要基于 webpack 的旧构建系统与browser构建器已经弃用。应用仍可在升级期间暂时沿用browser构建器项目也可以选择暂不迁移但 Angular 团队推荐尽快迁移到新构建系统。新应用开箱即用对于通过ng new创建的新应用新构建系统是默认选项对应的构建器名为application。以本仓库中 tutorial 示例工程展示的典型angular.json配置为例见 angular.json其buildtarget 直接使用angular/build:application且配置形态已经体现了新体系的关键约定入口字段为browser、polyfills是数组、输出目录为dist/project/browser、开发服务器使用angular/build:dev-serverbuild: { builder: angular/build:application, options: { outputPath: dist/first-app, index: src/index.html, browser: src/main.ts, polyfills: [zone.js], tsConfig: tsconfig.app.json, styles: [src/styles.css], scripts: [] } }, serve: { builder: angular/build:dev-server }这段真实配置直观呈现了迁移的目标形态application构建器已成为新工程的标准模板。存量应用的迁移路线存量应用可以走自动化或手动两条路线。从v18开始ng update更新流程会询问你是否通过自动迁移将存量应用切换到新构建系统。迁移前建议先阅读文末 已知问题 一节其中可能包含与你的项目相关的注意事项。提示如果应用使用 SSR请先移除服务端代码中对 CommonJS 模块作用域CommonJS module scope的假设例如require、__filename、__dirname等构造。所有应用代码应当兼容 ESM。此要求不适用于第三方依赖。自动迁移推荐自动迁移会同时调整angular.json中的应用配置以及代码与样式表以移除原先针对 webpack 特性的用法。虽然绝大多数自动化改动完成后无需额外处理但每个应用都有其独特性因此仍可能存在需要手工修正的地方。迁移完成后请务必执行一次构建——新系统可能抛出需要调整代码的错误错误信息在可能的情况下会尽量给出解决方案本文后续章节也会描述一些常见情形。在 v18 升级时执行ng update即可被询问是否运行该迁移。该迁移对 v18 而言完全可选也可在任意时刻手动执行ng update angular/cli --name use-application-builder该迁移会依次完成以下工作将现有的browser或browser-esbuildtarget 转换为application移除旧的 SSR 相关构建器这些能力已并入application相应地更新配置将tsconfig.server.json合并进tsconfig.app.json并添加 TypeScript 选项esModuleInterop: true以确保express之类的导入符合 ESM 规范其原因见 ESM 默认导入与命名空间导入 一节更新应用服务端代码使其使用新的引导方式bootstrapping与输出目录结构移除 webpack 特有的样式表用法如import/url()中的~波浪号或^脱字符前缀并更新配置以提供等价行为若未发现其他angular-devkit/build-angular用法则转为使用依赖更少的angular/buildNode.js 包。手动迁移存量项目也可以按应用粒度手动切换到新构建器共有两种方案二者都被 Angular 团队视为稳定且完全支持。选择哪种方案取决于你愿意承担多少改动量以及希望在项目中启用哪些新特性browser-esbuild构建器只构建应用的浏览器端 bundle其设计目标是与现有browser构建器保持兼容。它提供等价的构建选项在多数场景下可以作为存量browser应用的“即插即用”替代品。application构建器覆盖整个应用包括浏览器端 bundle还可选择性地构建服务端用于 SSR并在构建期预渲染静态页面。通常更推荐application构建器它能改进服务端渲染SSR构建并让纯客户端渲染项目未来更容易接入 SSR。代价是迁移工作量稍大尤其对已有的 SSR 应用而言如果完全手动迁移。若application构建器在你的项目中落地困难browser-esbuild是更平滑的中间方案——它带来大部分构建性能收益同时破坏性改动更少。手动迁移到兼容构建器 browser-esbuildangular-devkit/build-angular包Angular CLI 生成的应用中已包含提供了名为browser-esbuild的构建器供使用browser构建器的应用尝试新构建系统。兼容方案的目标是把首次迁移所需的改动量降到最低因此你只需修改buildtarget 的builder字段迁移前应用angular.json中通常是这样的... architect: { build: { builder: angular-devkit/build-angular:browser, ...只需将builder字段改为... architect: { build: { builder: angular-devkit/build-angular:browser-esbuild, ...这是唯一需要的改动。手动迁移到新 application 构建器application构建器同样位于angular-devkit/build-angular包中且是ng new新建应用的默认构建器。典型存量配置如下... architect: { build: { builder: angular-devkit/build-angular:browser, ...第一步同样是修改builder字段... architect: { build: { builder: angular-devkit/build-angular:application, ...改完构建器名称后buildtarget 中的选项需要逐一对照调整。以下是所有需要调整的browser构建器选项清单browser旧选项处理方式说明main重命名为browser应用入口字段改名polyfills改为数组从单个文件改为字符串数组buildOptimizer移除该能力已并入optimization选项resourcesOutputPath移除资源输出目录现在固定为mediavendorChunk移除曾经的性能优化项已不再需要commonChunk移除曾经的性能优化项已不再需要deployUrl移除不支持优先改用base href详见 deployment 文档 的--deploy-url一节。--deploy-url需在构建期硬编码进应用而base href可在运行时单点定义因此后者更优ngswConfigPath重命名为serviceWorkerService Worker 配置路径选项改名如果应用当前尚未使用 SSR完成上述调整后ng build即可正常工作。首次执行ng build时可能会因为行为差异或应用中残留的 webpack 特性用法而出现新的警告或错误多数警告会附带修复建议。若你认为某个警告不正确或无法理解其修复思路可到 angular-cli 仓库 提交 issue本文后续章节也给出了若干具体案例与当前已知问题。对刚开始接触 SSR 的应用Angular SSR 指南 提供了完整的接入步骤说明。对已经在使用 SSR 的应用还需要额外调整应用服务端以适配新构建器内置的 SSR 能力。SSR 应用的特殊处理application构建器将以下既有构建器的功能全部整合在了一起app-shellprerenderserverssr-dev-serverng update流程会自动移除原先承载部分此类构建器的nguniversal作用域包并自动新增angular/ssr包同时调整相应配置与代码。angular/ssr包同时支持browser构建器与application构建器。整合之后原先散落在ng run脚本中的 SSR / 预渲染命令可以精简掉——这些能力现在直接由ng build承载。执行构建配置更新完毕后构建方式与过去一致仍使用ng build。根据你所选择的迁移路线部分命令行选项可能有所不同若构建命令写在了npm脚本或其他脚本中请记得复查并更新。迁移到application构建器并使用 SSR 或预渲染的应用还可以考虑从脚本中移除多余的ng run命令ng build启动开发服务器开发服务器会自动检测到新构建系统并使用它来构建应用因此dev-server构建器的配置与命令行都无需任何改动ng serve你可以继续使用过去用过的开发服务器命令行选项。提示开发服务器在启动初始化阶段可能出现短暂的“无样式内容闪烁”Flash of Unstyled ContentFOUC。这是因为它会尽可能延迟处理样式表以换取更快的重建速度。此现象只出现在开发服务器中正式构建产物不会有该问题。热模块替换HMR热模块替换Hot Module Replacement让开发服务器在应用仅局部变更时避免整页重载多数情况下变更可以立即呈现在浏览器中从而显著改善开发时的“编辑—刷新”循环体验。虽然通用的基于 JavaScript 的 HMR 目前尚不支持但以下几种更具体的 HMR 已经可用全局样式表styles构建选项组件样式内联与基于文件的均可组件模板内联与基于文件的均可HMR 能力自动启用无需任何代码或配置改动。Angular 对基于文件的templateUrl/styleUrl/styleUrls与内联的template/styles组件样式与模板均提供 HMR 支持。当构建系统检测到仅样式表发生变化时会尝试只编译和处理最小限度的应用代码。如不希望启用 HMR可将开发服务器的hmr选项设为false也可通过命令行关闭ng serve --no-hmrVite 作为开发服务器Angular CLI 中的 Vite 目前仅承担“开发服务器”这一角色。即便不使用 Vite 的完整构建体系Vite 也能提供一个功能完备、且被打包进低依赖 npm 包的客户端开发服务器非常适合承载开发服务器的职责。当前开发服务器进程的工作方式是先用新构建系统在内存中生成一份开发构建产物再交由 Vite 对外提供。与 webpack 时代类似Vite 被封装在 Angular CLI 的dev-server构建器内部目前无法被直接配置。依赖预打包Prebundling预打包prebundling能改善开发服务器下的构建与重建速度。Vite 提供依赖预打包能力在使用 Angular CLI 时默认开启。其过程是在开发服务器首次执行时分析项目中的全部第三方依赖并统一预打包从而避免每次重建或重启开发服务器时都重复打包项目依赖。多数情况下无需额外定制。但在以下场景可能需要调整需要自定义依赖内导入的加载器行为例如loader选项通过npm link把某个依赖软链到本地源码进行开发需要绕过某个依赖在预打包过程中出现的错误。预打包可以通过dev-server构建器的prebundle选项完全关闭或单独排除某些依赖。使用prebundle.exclude排除特定依赖serve: { builder: angular/build:dev-server, options: { prebundle: { exclude: [some-dep] } },prebundle默认值为true可设为false以完全禁用serve: { builder: angular/build:dev-server, options: { prebundle: false },不过更推荐仅排除特定依赖而不是整体关闭因为禁用预打包会显著增加重建耗时。新构建系统带来的新特性application构建系统的主要收益之一是构建与重建速度的提升但除此之外它还提供了若干browser构建器不具备的新特性。重要本节描述的application构建器新特性与karma测试构建器默认不兼容因为后者内部仍在使用browser构建器。如需使用可将karma构建器的builderMode选项设为application显式接入。该选项目前处于developer preview阶段遇到问题可在 angular-cli 仓库 反馈。构建期值替换definedefine选项允许在构建期把代码中的标识符替换为另一个值行为类似 Webpack 的DefinePlugin以往需要借助自定义 webpack 配置或第三方构建器才能实现。该选项既可写在angular.json配置文件中也可通过命令行传入。当替换值是常量且可以签入源码管理时写入angular.json更合适。在配置文件中define是一个对象键为待替换的标识符值为对应的替换值。示例build: { builder: angular/build:application, options: { ... define: { SOME_NUMBER: 5, ANOTHER: this is a string literal, note the extra single quotes, REFERENCE: globalThis.someValue.noteTheAbsentSingleQuotes } } }提示配置文件中所有替换值都以字符串形式书写。若希望替换结果是真正的字符串字面量需要额外加上一对单引号。这样设计既允许使用任意合法 JSON 类型作为替换值也允许用另一个标识符作为替换结果不写引号即视为引用其他标识符或表达式。对于每次构建都可能变化的值如 git 提交哈希或环境变量命令行用法更合适。CLI 会把命令行传入的--define值与angular.json中的define值合并同一标识符同时出现时命令行取值优先。命令行使用IDENTIFIERVALUE的格式ng build --define SOME_NUMBER5 --define ANOTHERthese will overwrite existing环境变量也可以选择性地注入构建。下面的例子假设使用 bash 类 shell其他 shell 也有类似做法非 Windows shell 下如果哈希字面量需要引号可以直接用反斜杠转义export MY_APP_API_HOSThttp://example.com export API_RETRY3 ng build --define API_HOST\$MY_APP_API_HOST\ --define API_RETRY$API_RETRY无论采用哪种用法都需要让 TypeScript 感知这些标识符的类型以避免构建期类型检查报错。可以在应用源码目录下新增类型声明文件例如src/types.d.ts内容类似declare const SOME_NUMBER: number; declare const ANOTHER: string; declare const GIT_HASH: string; declare const API_HOST: string; declare const API_RETRY: number;默认的项目配置会自动引入项目源码目录下现有的类型声明文件如果你的tsconfig做过自定义调整可能需要显式引用这个新声明文件。重要define不会替换 Angular 元数据如 Component、Directive 装饰器中出现的标识符。文件扩展名加载器自定义仅application构建器重要该特性仅对application构建器可用。有些项目需要控制“某种特定扩展名的所有文件”如何被加载并打进 bundle。使用application构建器时可通过loader选项解决这类需求为指定扩展名定义加载器类型之后应用代码即可通过 import 语句或动态 import 表达式使用该扩展名的文件。可用的加载器类型如下加载器行为text将内容内联为string作为默认导出提供binary将内容内联为Uint8Array作为默认导出提供file将文件输出到应用产物路径并以默认导出形式给出文件运行时位置dataurl将内容内联为 data URLbase64将内容内联为 Base64 编码字符串empty视内容为空不打包进 bundleempty虽不常用但在兼容某些第三方库时很有价值——例如库代码中存在仅面向打包器的副作用导入需要被剔除。一个典型场景是import my.css;这类副作用导入CSS 在浏览器中并无副作用。此时可用empty忽略这类导入改把 CSS 文件加入styles构建选项或采用其他注入方式。loader是对象形式的选项键为文件扩展名值为加载器类型。例如将 SVG 文件内容内联进 bundlebuild: { builder: angular/build:application, options: { ... loader: { .svg: text } } }之后即可正常导入该 SVGimport contents from ./some-file.svg; console.log(contents); // svg.../svg同样地TypeScript 需要感知该模块的类型以免构建期报错。可在应用源码目录新增类型声明文件如src/types.d.ts写入类似内容declare module *.svg { const content: string; export default content; }默认项目配置会自动引入源码目录中的.d.ts文件若自定义过 tsconfig则可能需要显式引用该声明文件。导入属性Import Attribute加载器自定义如果只需要让某些特定文件以特定方式加载可以通过loader导入属性import attribute实现它同时适用于 import 语句与 import 表达式。该属性的优先级高于一切其他加载行为包括 JS/TS 处理与loader构建选项。若要对某个不支持文件类型的所有文件做统一加载仍推荐使用loader构建选项。导入属性支持的加载器取值如下text将内容内联为string作为默认导出提供binary将内容内联为Uint8Array作为默认导出提供file将文件输出到产物路径默认导出为文件的运行时位置dataurl内联为 data URLbase64内联为 Base64 编码字符串。使用导入属性的另一个前提是TypeScript 的module选项必须设为esnext构建才能成功。待 TypeScript 支持ES2025后这一改动将不再需要。目前 TypeScript 尚不支持基于导入属性值的类型定义因此需要使用ts-expect-error/ts-ignore或为文件单独提供类型声明文件前提是该文件始终以同一加载器属性被导入。示例以文本方式导入 SVG 文件// ts-expect-error TypeScript cannot provide types based on attributes yet import contents from ./some-file.svg with {loader: text};同样的效果也可以在异步函数里用 import 表达式实现async function loadSvg(): Promisestring { // ts-expect-error TypeScript cannot provide types based on attributes yet return import(./some-file.svg, {with: {loader: text}}).then((m) m.default); }对于 import 表达式loader值必须是字符串字面量才能被静态分析若传入非字面量值会触发警告。file加载器适合那些将在运行时才被加载的文件——例如通过fetch()获取、赋给img的src或其他类似用法// ts-expect-error TypeScript cannot provide types based on attributes yet import imagePath from ./image.webp with {loader: file}; console.log(imagePath); // media/image-ULK2SIIB.webpbase64加载器适合需要直接把文件作为编码字符串嵌入 bundle、稍后再用来拼装 Data URL 的场景// ts-expect-error TypeScript cannot provide types based on attributes yet import logo from ./logo.png with {loader: base64}; console.log(logo); // iVBORw0KGgoAAAANSUhEUgAA...dataurl加载器则把资源完整内联为 Data URL// ts-expect-error TypeScript cannot provide types based on attributes yet import icon from ./icon.svg with {loader: dataurl}; console.log(icon); // data:image/svgxml;...如上方注释所示生产构建中file加载器会为产物路径自动附加哈希值以支持长期缓存。提示在开发服务器中若用loader属性导入某个 Node.js 包内的文件需要先通过开发服务器的prebundle选项把该包排除出预打包流程。导入/导出条件Import/Export Conditions部分项目需要依据构建类型把某些导入路径映射到不同文件——典型场景是ng serve使用带调试/开发信息的代码而ng build使用不含任何开发特性或调试信息的代码。为支撑这类需求新构建系统会自动应用若干导入/导出条件优化构建启用production条件非优化构建启用development条件浏览器端输出代码启用browser条件。“是否优化构建”由optimization选项决定当optimization为true或更精确地说optimization.scripts为true时该构建即被视为优化构建。此判定同时适用于ng build与ng serve。新项目里ng build默认优化、ng serve默认非优化。一个实用的用法是把这些条件与 Node.js 的子路径导入subpath imports结合。应用代码中写import {verboseLogging} from #logger;然后在package.json的imports字段按条件切换实现文件{ ... imports: { #logger: { development: ./src/logging/debug.ts, default: ./src/logging/noop.ts } } }对同时使用 SSR 的应用还可以用browser条件在浏览器代码与服务端代码间切换{ ... imports: { #crashReporter: { browser: ./src/browser-logger.ts, default: ./src/server-logger.ts } } }这些条件同样作用于 Node.js 包及其package.json中定义的exports条件导出。提示如果你正在使用fileReplacements构建选项本特性可能可以取代它的部分用途。已知问题Known Issues新构建系统目前仍存在若干已知问题下面的列表会持续更新。如果其中某些问题阻碍了你试用新构建系统可以稍后再回来查看——问题可能已被修复。Web Worker 代码的类型检查与嵌套 Worker 处理应用代码可以使用与browser构建器相同的语法new Worker(new URL(workerfile, import.meta.url))来创建 Web Worker。但当前Worker 内的代码不会被 TypeScript 编译器做类型检查——TypeScript 代码可以运行只是不做类型检查。此外嵌套 Worker即在一个 Worker 文件内部再实例化另一个 Worker目前不会被构建系统处理。ESM 默认导入与命名空间导入TypeScript 默认允许把默认导出当作命名空间导入然后在调用表达式中使用——这与 ECMAScript 规范相悖。新构建系统底层的打包器esbuild期望的是符合规范的 ESM 代码因此当应用以错误的导入方式引入某个包时构建会发出警告。为了让 TypeScript 接受正确写法需要在应用tsconfig中开启esModuleInterop选项——它能更好地对齐 ECMAScript 规范TypeScript 团队也推荐启用。开启后可把相关包的导入改写成符合 ECMAScript 的形式。以moment包为例下面这类应用代码会导致运行时错误import * as moment from moment; console.log(moment().format());构建会生成类似如下的警告提示你存在潜在问题▲ [WARNING] Calling moment will crash at run-time because its an import namespace object, not a function [call-import-namespace] src/main.ts:2:12: 2 │ console.log(moment().format()); ╵ ~~~~~~ Consider changing moment to a default import instead: src/main.ts:1:7: 1 │ import * as moment from moment; │ ~~~~~~~~~~~ ╵ moment为避免运行时错误与警告请为应用启用esModuleInteropTypeScript 选项并把导入改为import moment from moment; console.log(moment().format());懒加载模块中顺序相关的副作用导入某些依赖特定执行顺序的 import 语句如果被多个懒加载模块使用可能导致顶层语句的执行顺序错乱。该现象并不常见——它与副作用模块的使用方式有关且不适用于polyfills选项。问题根因是底层打包器 esbuild 的一个缺陷未来版本会修复。重要无论使用何种构建系统都建议尽量避免使用具有非局部副作用polyfills 除外的模块——这类模块对应用体积与运行时性能也有负面影响且可从根本上规避此问题。输出位置变化默认情况下application构建器成功构建后产物位于dist/project-name/browser目录而browser构建器输出在dist/project-name。这可能会破坏依赖旧产物路径的工具链。此时可以通过配置输出路径outputPath按需调整——该选项既可以是字符串作为base值也可以是对象以进行更细粒度的配置。Bug 反馈新构建系统的问题与特性请求请提交到 angular-cli 仓库。提交时请尽量附带最小可复现示例minimal reproduction以便团队高效定位与修复问题。【免费下载链接】angularDeliver web apps with confidence 项目地址: https://gitcode.com/GitHub_Trending/an/angular创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
网站建设高端定制企业官网
RELATED

相关资讯

更多精彩内容,欢迎继续阅读

较早相关资讯

最新相关资讯

Puppeteer MouseMoveOptions 深度解析:用 steps 精确控制鼠标移动插值 2026/9/8 22:44:31

Puppeteer MouseMoveOptions 深度解析:用 steps 精确控制鼠标移动插值

Puppeteer MouseMoveOptions 深度解析:用 steps 精确控制鼠标移动插值 【免费下载链接】puppeteer JavaScript API for Chrome and Firefox 项目地址: https://gitcode.com/GitHub_Trending/puppeteer1/puppeteer MouseMoveOptions 是 Puppeteer(…

阅读更多 →
深度学习恶意软件检测实战:Python源码与完整数据全解析 2026/9/8 22:44:31

深度学习恶意软件检测实战:Python源码与完整数据全解析

简介:面向Python与深度学习初学者的恶意软件检测完整项目,以真实样本数据为基础,覆盖数据增强、数据分析和检测实现等关键环节。资源包共163个文件、34.66MB,核心包含18个Python脚本与4个Jupyter Notebook,提供训练、检…

阅读更多 →
Storybook 组件故事元数据(meta)默认导出全指南:Component Story Format 中的 default export 与 title/component 用法 2026/9/8 22:44:31

Storybook 组件故事元数据(meta)默认导出全指南:Component Story Format 中的 default export 与 title/component 用法

Storybook 组件故事元数据(meta)默认导出全指南:Component Story Format 中的 default export 与 title/component 用法 【免费下载链接】storybook Storybook is the industry standard workshop for building, documenting, and testing UI…

阅读更多 →
工业串口选型:多串口主板 vs 串口服务器的硬核决策逻辑 2026/9/8 22:44:31

工业串口选型:多串口主板 vs 串口服务器的硬核决策逻辑

1. 这不是“插个串口卡”就能解决的问题:工业现场的通信链路从来就不是单点故障我第一次在某水泥厂DCS改造项目里栽跟头,是在调试窑尾温度传感器组网时。现场有12路RS-485热电偶变送器,原计划用一块带8个串口的工控主板直接接线,结…

阅读更多 →
遥感图像识别算法对比:SVM、KNN、CNN与LSTM实战解析 2026/9/8 22:44:31

遥感图像识别算法对比:SVM、KNN、CNN与LSTM实战解析

简介:面向计算机、自动化等专业学生的期末课设/毕设资源,分别基于支持向量机(SVM)、KNN、CNN、LSTM四种算法实现遥感图像识别,并配套详细说明文档。项目采用武汉大学WHU-RS19数据集,涵盖机场、海滩、农田、…

阅读更多 →
freeCodeCamp JavaScript 基础:用 for 循环遍历数组(Iterate Through an Array with a For Loop)深度解析 2026/9/8 22:41:31

freeCodeCamp JavaScript 基础:用 for 循环遍历数组(Iterate Through an Array with a For Loop)深度解析

freeCodeCamp JavaScript 基础:用 for 循环遍历数组(Iterate Through an Array with a For Loop)深度解析 【免费下载链接】freeCodeCamp freeCodeCamp.orgs open-source codebase and curriculum. Learn math, programming, and computer sc…

阅读更多 →

今日资讯

本周资讯

本月资讯

看完文章仍有疑问?

联系尧图顾问,获取一对一建站咨询

立即免费咨询 📞 400-888-8888
📞