最新公告
  • 欢迎您光临起源地模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • 【译】配置 Monorepo 的几种工具 lerna、npm、yarn 及其性能对比

    正文概述 掘金(nactran)   2021-02-10   1019

    Source: Sebastian Weber. Why Lerna and Yarn Workspaces is a Perfect Match for Building Mono-Repos – A Close Look at Features and Performance. March 18, 2019.

    原标题:为什么Lerna和Yarn Workspaces是构建Monorepo的完美搭配——一个对功能和性能的近距离观察

    这篇文章是我对Mono-Repo这个话题的看法。在简单介绍了Mono-Repos和与Multi-Repos的比较之后,我将开始介绍建立Mono-Repos的工具。

    我并不准备详细评估哪种存储库类型在哪种情况下更好。然而,本文的目标是关于 Mono-Repos 以及 lerna、npm 和yarn Workspace 如何帮助你管理 Mono-Repos。结合使用这些工具也是有意义的。尤其是 lerna 和 yarn Workspace 可以在一个项目中和平共处。如何做到这一点呢?我们马上就会知道。

    什么是 Monorepo 以及它与 Multi-repo 的比较

    像 lerna 和 yarn 工作空间这样的工具是一个决定性的因素,其结果是,在一个单一的 repo 中管理你的代码库(也就是 Mono-Repo )在最近一两年内获得了一些吸引力。关于这个话题,人们已经写了很多文章、在会议上做了很多演讲。

    简而言之,所谓Mono-Repo就是一个git 仓库,里面存放着多个项目。这样的项目被称为工作空间或包。与此相反,使用多个仓库,每个仓库只存放一个项目,称为Multi-Repo方式。当然,两种方法的结合也是可能的。在我目前的工作中,我们组成了多个团队,每个团队都有自己的资源库。有的团队追求Multi-Repo方法,有的团队信奉Multi-Repo格言(译者:两处都是 multi-repo ,难道是笔误?)。另外,还存在同时利用这两种方法的团队,因为作为仓库一部分的技术也是决策时需要考虑的因素(例如,每个Java微服务都是自己git repo的一部分)。

    要了解Mono-Repos和Multi-Repos的区别以及利弊,我推荐Markus Oberlehner的文章 Monorepos in the Wild。

    Monorepos 的工具链

    一个 Mono-Repo 托管一个或多个项目或包。这些包我们管它叫 Mini-Repos,它们可以独立地进行版本调整、构建和发布。因此,每个包都包含自己的package.json文件,因为每个包都是一个完整的项目。包之间可能有相互依赖的关系。管理这些依赖关系是通过符号链接来实现的。

    正如我们后面所看到的,lerna和yarn工作空间给我们提供了在一个repo中构建库和应用的能力,而不我们不必发布到npm或其他注册表。这些技术背后的美妙之处在于,它们可以通过分析位于每个项目根目录下的package.json文件找到包的依赖。因此,这些工具使得手动创建符号链接或直接使用 "低级 "npm链接变得过时了

    lerna和yarn工作空间共同改善了开发者在Mono-Repo中管理多个包的体验。

    npm、yarn、yarn工作空间和Lerna之间的关联

    我想对npm、yarn、yarn workpaces和lerna如何涉及到Mono-repos的话题进行一些杂乱的说明。看一下下面的 "韦恩图"。

    【译】配置 Monorepo 的几种工具 lerna、npm、yarn 及其性能对比

    它描绘了三个主角,以及他们之间的关联。对了,不要太关注每个圆圈的大小。这张图的目的只是给人一个印象,让人知道这些东西是如何联系在一起的。

    npm(用1标记)和yarn(2)都是原生包管理器,它们有很多共同的特点(3)。举个例子,两者都利用package.json的概念作为容器来进行依赖性管理,这是npm当年引入的。更多共享的概念和功能是依赖管理、发布,或者使用锁文件来 "冻结 "依赖版本。甚至还有更多源自npm的功能也被yarn利用,比如发布到npm注册表。

    当初创建yarn的原因之一是性能问题--在大型项目中使用npm安装依赖关系的时间太长。另一个方面是缺少一些功能,比如冻结版本的复杂概念、离线功能,或者在依赖关系解决方面的确定性行为。虽然,随着时间的推移,npm的许多差距已经消失了,现在这两种技术的功能越来越多。

    仍然只属于npm(1)或yarn(2)的东西分别是package-lock.json文件或yarn.lock文件。不过,对于我们这些应用开发者来说,锁文件的不同实现其实并不重要。实际上,在版本管理的处理方式上,npm和yarn是平分秋色的。

    yarn独有的一个大功能是yarn工作空间(4),它是一年前加入yarn的。它通过原生的Mono-Repo功能扩展了yarn的功能。下一节将更多地介绍Mono-Repo的功能。

    Monorepo 相关技术 —— 哪个是原生的,哪些是用户空间上的?

    请考虑下一张图,它描述了Mono-Repo环境中的技术是如何相互连接的。 【译】配置 Monorepo 的几种工具 lerna、npm、yarn 及其性能对比

    标为红色的是提供Mono-Repo功能的技术。所有这些技术都是基于npm或yarn的。而 npm 和 yarn 除了 npm link 或 yarn link 之外,不提供构建Mono-Repo的高级功能。

    yarn workspaces 是唯一一个原生暴露 Mono-Repo 功能的代表。 lerna已经存在了相当长的时间,甚至在yarn workspaces还没有出现之前就已经出现了。

    lerna利用语义链接来实现这一目的。**它还允许使用yarn工作空间,然后,将整个Mono-Repo方面的工作完全交给yarn工作空间的本地实现的功能。**此外,lerna提供了复杂的发布和版本管理功能,甚至可以独立发布项目。简而言之,lerna提供了许多Mono-Repo管理之外的功能。另一方面,yarn工作空间的唯一目的是简化Mono-Repo工作流程。所以,你不必为它们中的任何一方做决定。使用lerna与yarn workspaces是完全有意义的。

    bolt是一个相对较新的项目,基于yarn工作空间。受lerna的启发,它的目标是在此基础上增加更多有用的命令。然而,我没有任何经验,因为我还没有完成让bolt在我的试验场中运行。另外,我也意识到,它最近提交的次数比较少。所以,我在本文中不做更深层次的阐述。

    配置Monorepo的不同方法

    本节的目标是快速概述如何通过不同的工具组合来配置monorepo。你可以把下文中的截图理解为一种 "小抄"。重点是不同方法的配置部分,以及它们之间的差异。

    我创建了一个小仓库来演示不同的变体。只要克隆演示项目repo,并为不同的变体切换分支即可。README.md文件描述了如何引导和使用(即构建和运行虚拟应用程序)特定变体。本节和演示项目的另一个目标是提供一个简单的试验场,从不同的角度来观察不同变体的运行情况:需要哪些配置步骤,构建和使用子项目(即包)需要哪些步骤,依赖性管理是如何工作的,或者对引导的时间影响是什么。

    1. 自己动手

    我跳过这一节,但请随时查看分支1-do-it-yourself。基本上,你使用npm链接工作,必须创建语义链接并手动安装所有子项目。我希望你能想象这种方案对于现实世界的项目是多么的繁琐和不切实际。

    2. learna + npm

    为了获得对方法1这种手动任务的自动化支持,引入了lerna。你需要在根目录下建立一个lerna.json文件。按照惯例,lerna默认使用npm。

    正如你在下一张截图中所看到的,你基本上需要编辑两个文件来让lerna启动和运行:lerna.json和package.json。在 lerna.json 中,你需要指定 lerna 在哪里寻找包。

    要引导所有的子项目,你需要通过调用下面的npm脚本来执行lerna bootstrap。

    $ npm run bootstrap
    

    这条命令的基本作用是进入所有包的根目录并执行npm安装。看看这三个包,你会发现lerna让npm为每个包都创建了一个node_modules文件夹【译】配置 Monorepo 的几种工具 lerna、npm、yarn 及其性能对比

    3. lerna + yarn

    这和方法2的设置是一样的,唯一不同的是,你必须在lerna.json文件中用 npmClient属性指定yarn为客户端。引导也是由lerna执行的。

    与方法1相比,有什么不同呢?实际上没什么区别。主要是口味的问题,因为唯一的区别就是lerna是利用npm还是yarn作为依赖管理器。要回答这个问题,选择哪种方法,可以归结为以下几个问题。

    • 我更喜欢哪种语法 npm run vs _yarn
    • 我应该坚持"准"标准(quasi-standard)还是喜欢Facebook的努力?
    • 我真的关心引导时间吗?如果是,请看下一章,其中提供了一些性能基准。

    【译】配置 Monorepo 的几种工具 lerna、npm、yarn 及其性能对比

    4. yarn workspaces

    对于这种方法,你不需要lerna。yarn工作空间具有内置的Mono-Repo功能。要使用yarn工作空间,你需要yarn 1.0或更高版本。正如你在下面的截图中所看到的,你不需要一个专门的配置文件。根目录下的package.json文件需要是私有的,并且必须有一个 "workspaces "属性,告诉yarn在哪里可以找到子项目(yarn把它叫workspaces)。

    要用所有的工作空间来引导项目,你只需要使用yarn就可以了,因为yarn工作空间本身就提供了这个功能。

    $ yarn install
    

    或者再短一点

    $ yarn
    

    这结合了方法1和方法2的两个步骤:安装根文件夹的依赖关系和引导所有包的依赖关系。

    【译】配置 Monorepo 的几种工具 lerna、npm、yarn 及其性能对比

    与方法1和方法2相比,一个最大的区别是yarn工作空间只创建一个node_modules文件夹。所有的依赖关系都会被提升到根目录下。备注。同时,这种行为也可以通过使用 -hoist 标志在 lerna 中实现 (不实用 yarn workspaces)。

    5. lerna + yarn workspaces

    要用yarn工作空间配置lerna,你必须在根目录下的package.json中进行与方法4中描述的相同配置。然而,你也需要在根目录下提供一个 lerna.json 文件,在那里,你需要告诉 lerna 使用 yarn 工作空间。不幸的是,你必须在 lerna.json 中多余地指定子项目的位置。要引导项目,不需要使用 lerna 引导,你只需要使用方法 4 中描述的 yarn install。此时执行 lerna bootstrap 的意义不大,因为它只是调用yarn install本身罢了。

    【译】配置 Monorepo 的几种工具 lerna、npm、yarn 及其性能对比

    通过这种设置,lerna 完全将依赖的设置和引导的工作流交给yarn工作空间。所以,我们好像配置了更多的东西,实现的效果和却前面的方法一样?那为什么你要使用这种方式而不是方法4呢?嗯,想想看——同时使用lerna和yarn工作空间是完全有意义的。它们在Mono-Repo项目中可以和平共处。

    在这样的情况下:

    • 你只在Mono-Repo工作流中使用yarn工作空间。
    • 你使用lerna的实用命令来优化多个包的管理,例如,选择性地执行npm脚本进行测试。
    • 您使用 lerna 发布软件包,因为 lerna 的版本和发布命令提供了复杂的功能。

    (译者注:大概就是用lerna测试、发包,其他的工作可以交给yarn)

    lerna 和 yarn workspaces

    上一节让大家快速了解如何通过不同的配置来设置Mono-Repos。本节的重点更多的是关于lerna和yarn工作空间的功能。

    yarn workspaces

    到目前为止,yarn工作空间是唯一一个为Mono-Repos提供原生功能的技术。与 lerna 不同的是,你不需要执行一个单独的步骤来引导包的依赖关系,yarn install 通过安装根文件夹的依赖关系,然后为每个包安装。

    与 lerna 相比,yarn workspaces 除了多项目设置的依赖管理外,并没有额外的功能。由于它的基础是yarn,所以你手上有yarn的所有功能。

    为了使用yarn工作空间,Facebook引入了一些额外的命令,这些命令只有在Mono-Repos的情况下才有意义。

    下面的命令将显示你当前项目的工作空间依赖树。

    $ yarn workspaces info
    

    下一个指令可以让你在选定的工作空间(即包)中运行所选的yarn命令。

    $ yarn workspace <package-name> <command>
    

    举个例子,使用下面的命令,react会被添加到名为 "awesome-package "的包/工作空间中,作为开发依赖(你也可以使用-D来代替-dev)。

    $ yarn workspace awesome-package add react --dev
    

    接下来是一个从特定包中删除依赖关系的例子。

    $ yarn workspace web-project remove some-package --save
    

    如果你想为所有的包添加一个共同的依赖关系,进入项目的根目录并使用-W (或-ignore-workspace-root-check) 标志。

    $ yarn add some-package -W
    

    不然的话 yarn 会报错。 【译】配置 Monorepo 的几种工具 lerna、npm、yarn 及其性能对比

    通过下面的命令,我将自己的一个包("awesome-components")添加到另一个包("awesome-app")中作为依赖。我发现添加本地包时应该指定一个版本号,否则yarn会试图在注册表中找到依赖关系。

    $ yarn workspace @doppelmutzi/awesome-app add @doppelmutzi/awesome-components@0.1.0 -D
    

    使用工作空间功能,yarn 不会将依赖关系添加到任何一个包的 node_modules 目录中--只有在根级,即 yarn 将所有的依赖关系提升到根级。因此,yarn 只在项目中包含一次依赖关系。

    你必须利用yarn工作空间的noHoist功能来使用在Mono-Repo环境中工作的不兼容的第三方依赖关系。你必须在项目根目录下的package.json中指定这个功能,如下例所示。

    // package.json
    {
      //...
      "workspaces": {
        "packages": ["packages/*"],
        "nohoist": [
          "**/react-native"
        ]
      }
      //...
    }
    

    更多信息请看ConnectDotz的演示项目。

    lerna

    与yarn工作空间一样,lerna为前端项目添加了Mono-Rep功能。然而,如上所述,lerna是在 "用户空间 "上运行的,不能在原生层面上添加这样的功能。

    如果你把lerna配置成使用yarn工作空间,那么lerna就会把整个依赖管理交给yarn工作空间。如果你把 lerna 配置成使用 npm 或 yarn,那么 lerna 就会利用符号链接来提供自己的 Mono-Repo 功能。在这种情况下,你必须使用lerna bootstrap来初始化所有包的依赖关系。

    John Tucker写了一篇很棒的文章,介绍了如何使用lerna的命令来初始化项目和管理依赖关系。

    要把react作为依赖项安装到所有包中,你可以使用下面的命令。

    $ lerna add react
    

    如果你想只把react作为一个特定包的依赖关系来安装,请执行以下命令。

    $ lerna add react --scope my-package
    

    如果你为每个包都安装了react,但只想为某个包升级/降级到特定的版本,那么你可以这样做。

    $ lerna add react@16.0.0 --scope my-package
    

    lerna有几个标志。它们构成了 lerna 子命令中需要过滤的选项。

    考虑下面这个名为 "test "的npm脚本。下面的两个 shell 命令展示了如何通过使用 -scope 标志和 globs 来只在特定的包上执行测试,lerna 尝试为每个匹配的包执行 yarn 测试。

    // package.json
    {
      ...
      "scripts": {
        "test": "lerna exec yarn test“
      }
      ...
    }
    $ yarn test --scope @my-company-services/*
    $ yarn test --scope @my-company/web-*
    

    根据文档,lerna 也提供了将共享的依赖关系提升到根目录的功能,就像 yarn 工作空间的默认行为一样。因此,你必须使用 -hoist 标志。

    $ lerna add react -D --hoist
    

    如果你使用lerna,一个问题是选择npm还是yarn。从上一节的 "小抄 "可以看出,你可以随心所欲地在不同的包管理器之间轻松切换。

    端工作流特性和命令的高级玩法

    即使你选择yarn工作空间进行依赖性管理,也最好使用lerna。原因是 lerna 提供了实用的命令来优化多个包的管理。例如,通过一个 lerna 命令,你可以遍历所有或特定的包,在每个包上运行一系列的操作(如过滤、测试和构建)。因此,它与yarn工作空间相得益彰,接管了依赖性管理过程。

    在根文件夹内使用lerna进行测试或linting,比从每个包文件夹中手动调用所有操作要快。John Tucker的博客文章详细介绍了如何使用lerna进行测试。

    版本管理和发布是重要的开发主题,在这里,lerna也大放异彩。lerna允许你使用两种版本管理模式。

    • 固定/锁定模式。每个包的版本可以在一个点上管理(在 lerna.json 文件中)。如果一个包在上次发布后被更新过,它将被更新到新的版本。因此,任何一个包的重大变化都会导致所有包有一个新的主要版本。

    • 独立模式。软件包的版本可以相互独立地递增。因此,lerna.json里面的 "版本 "键需要设置为 "独立"。这种方式提供了更多的灵活性,对于具有松散耦合组件的项目特别有用。

    你可以发布自上一个版本以来发生变化的包。

    $ lerna publish
    

    在独立模式下,存在不同的选项来影响发布命令的版本颠簸。除了使用 semver 关键字之外,你还可以利用以下的版本升级标志之一:from-git 或 from-package。

    下面的命令在使用传统的提交标准的情况下发布到npm注册表。

    $ lerna publish --conventional-commits --yes
    

    上面的命令也会生成changelog文件,根据lerna的文档,存在不同的changelog预设,比如angular或者bitbucket。根据 lerna 的文档,存在不同的 changelog 预设,比如 angular 或 bitbucket。对了,yes标志会跳过所有的确认提示。

    在 lerna.json 中,你可以全局定义传统的提交必须使用,而不使用标志。

    // lerna.json
    //...
    "command": {
        "publish": {
           "conventionalCommits": true,
           "yes": true
        }
    }
    //...
    

    @jsilvax 解释了使用 lerna 的传统提交是如何工作的(译者注:中文版请参考我的这篇文章),以及如何通过 commitlint 来执行。

    由于版本管理和发布是个复杂的话题,上面的部分只展示了lerna的一小部分可能性。我不做更多的详细介绍,因为这将超出本文的范围。

    不同方法的耗时对比

    人们坚持使用yarn而不是npm的主要原因之一是安装依赖关系的时间。最初,yarn的开发是因为npm安装依赖关系的时间太长了(此外,npm还缺乏一些重要的功能)。同时,npm在第6版的时候,为了消除这个差距,付出了很多努力。

    因为你可以用多种方式实现Mono-Repo,让我们来看看这些不同的方法的表现。在本节的剩余部分,我将介绍我的性能实验结果。我克隆了Babel项目(大约在2018年10月),因为它代表了一个现实生活中的Mono-Repo,有很多包(准确的说是142个)。有趣的是,Babel的原始设置利用了lerna,其配置是将yarn指定为npmClient(没有yarn工作空间),并停用yarn的锁文件生成。

    对于每个方法(2-5),我都做了以下工作。

    • 我改变了相应方法所需的配置(即,如果需要,调整package.json和lerna.json)。
    • 我测量了安装依赖关系和专门的引导步骤(如果需要)的时间。
    • 我测量了3个不同用例的时间。对于每个用例,我都进行了3次测量。

    上述用例(UC)是。

    1)我清空npm或yarn缓存,我删除所有node_modules文件夹,我删除所有package-lock.json或yarn.lock文件。2) 缓存存在,我删除了所有的node_modules文件夹,并删除了所有的package-lock.json或yarn.lock文件。3)缓存存在,package-lock.json或yarn.lock文件存在,我删除所有node_modules文件夹。

    为了清除缓存,我根据使用的npm客户端执行了以下命令之一。

    $ npm cache clean --force
    

    $ yarn cache clean
    

    作为删除锁文件和node_modules文件夹的助手,我在Babel的根目录下添加了一个名为cleanup.sh的脚本。

    find . -type f -name 'yarn.lock' -exec rm {} +
    find . -type f -name 'package-lock.json' -exec rm {} +
    find . -name "node_modules" -type d -prune -exec rm -rf '{}' +
    

    根据用例,我最终注释了前两行。

    为了测量安装和引导依赖步骤的执行时间,我使用了gnomon。下面的命令构成了方法2(使用npm的lerna)和UC 1(空缓存,没有node_modules文件夹,没有锁文件作为前提条件)的例子,说明我是如何测量经过时间的。

    $ npm cache clean --force && ./cleanup.sh && npm i | gnomon && npm run bootstrap | gnomon
    

    下面,你会发现不同的测量结果。我在一段时间内进行了这些测量,所以我玩了不同的node、npm、yarn和lerna版本,以了解不同版本是否有不同的性能影响。

    为了切换node和npm版本,我利用了nvm。下面的例子首先安装并使用node的v9,然后安装npm的v5.7.1。

    $ nvm install v9
    $ nvm use v9
    $ npm i -g npm@5.7.1
    

    方法 2 (lerna with npm) – Node v10.12.0 / npm v6.4.1 / lerna 2.11.0

    【译】配置 Monorepo 的几种工具 lerna、npm、yarn 及其性能对比 备注。老实说,我不知道为什么最后两个条目的偏差如此之大,也许是我的Macbook的工作量太大?

    方法 2 (lerna with npm) – Node v9.10.0 / npm v5.6.0 / lerna 2.11.0

    【译】配置 Monorepo 的几种工具 lerna、npm、yarn 及其性能对比

    方法 3 (lerna with yarn) – Node v10.12.0 / yarn 1.10.1 / lerna 2.11.0

    【译】配置 Monorepo 的几种工具 lerna、npm、yarn 及其性能对比

    方法 3 (lerna with yarn) – Node v9.10.0 / yarn 1.10.1 / lerna 2.11.0

    【译】配置 Monorepo 的几种工具 lerna、npm、yarn 及其性能对比

    方法 4 (yarn workspaces) – Node v10.12.0 / yarn 1.10.1

    不需要 "bootstrap "的步骤,因为yarn install会在下面做这些工作。 【译】配置 Monorepo 的几种工具 lerna、npm、yarn 及其性能对比

    方法 4 (yarn workspaces) – Node v11.2.0 / yarn 1.10.1

    【译】配置 Monorepo 的几种工具 lerna、npm、yarn 及其性能对比

    方法 5 (lena with yarn workspaces) – Node v11.6.0 (npm v6.5.0-next.0) / yarn 1.12.3 / lerna 3.8.0

    通过这种方法,我试图找出使用yarn工作空间作为lerna配置的一部分是否会对方法4产生任何差异。因为不需要使用lerna引导,所以相应的列是空的。

    但正如我所预料的那样,由于 lerna 不参与依赖性安装/引导过程,因此与方法 4 没有区别。 【译】配置 Monorepo 的几种工具 lerna、npm、yarn 及其性能对比

    方法 6 (lerna + npm ci + Audit) – Node v10.12.0 / npm v6.4.1 / lerna 3.4.3

    在这个方法中,我使用了lerna和npm ci,这构成了npm install在持续集成环境下的一个替代。从lerna的第3版开始,npm ci是默认的安装命令。但是,你可以选择不使用。

    对于这种方法,package-lock.json文件必须存在。node_modules文件夹应该已经被删除,否则你会收到打印到终端的警告。因此,UC 3是不可能的。 【译】配置 Monorepo 的几种工具 lerna、npm、yarn 及其性能对比

    方法 6 (lerna + npm ci) – Node v9 / npm v5.7.1 / lerna 3.4.3

    使用这个确切的npm版本,npm ci命令是可用的,但没有审计功能。我想测试一下这个设置,看看在不审计依赖关系的情况下是否有任何性能影响。同样,在这种情况下,UC 3是不可能的。 【译】配置 Monorepo 的几种工具 lerna、npm、yarn 及其性能对比

    结论

    根据我的测量,我没有看到npm和yarn工作空间在性能上没有什么明显的区别。从功能上看,两者也没有区别。对我来说,利用哪个包管理器是个品味问题。此外,它们可以随时交换,也可以结合使用。

    目前,我更倾向于使用yarn工作空间作为Mono-Repo技术,因为我喜欢它的提升(hoisting)功能。另一方面,使用lerna和它的-hoist标志也是可以的。在我看来,yarn workpaces 和 lerna 是一个很好的搭配。配置lerna,把依赖管理留给yarn工作空间,而使用它的实用命令(utility commands)。


    起源地下载网 » 【译】配置 Monorepo 的几种工具 lerna、npm、yarn 及其性能对比

    常见问题FAQ

    免费下载或者VIP会员专享资源能否直接商用?
    本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
    提示下载完但解压或打开不了?
    最常见的情况是下载不完整: 可对比下载完压缩包的与网盘上的容量,若小于网盘提示的容量则是这个原因。这是浏览器下载的bug,建议用百度网盘软件或迅雷下载。若排除这种情况,可在对应资源底部留言,或 联络我们.。
    找不到素材资源介绍文章里的示例图片?
    对于PPT,KEY,Mockups,APP,网页模版等类型的素材,文章内用于介绍的图片通常并不包含在对应可供下载素材包内。这些相关商业图片需另外购买,且本站不负责(也没有办法)找到出处。 同样地一些字体文件也是这种情况,但部分素材会在素材包内有一份字体下载链接清单。
    模板不会安装或需要功能定制以及二次开发?
    请QQ联系我们

    发表评论

    还没有评论,快来抢沙发吧!

    如需帝国cms功能定制以及二次开发请联系我们

    联系作者

    请选择支付方式

    ×
    迅虎支付宝
    迅虎微信
    支付宝当面付
    余额支付
    ×
    微信扫码支付 0 元