Blazing fast build speed
The core compilation module is based on the Rust front-end toolchain, providing a more ultimate development experience.
diff --git a/404.html b/404.html new file mode 100644 index 00000000..314020ea --- /dev/null +++ b/404.html @@ -0,0 +1,17 @@ + + + +
+ + + + +一个典型的命令配置。
+本地开发时启动。
+如果链接的服务是一个本地的目录,你也可以把该服务跑起来快速的开发调试。
构建生产代码
+有三个产物,分别是 client、server、node。
当前服务如果有对外导出模块时使用。
+gez build
命令,构建生产产物。npm run build:dts
命令,将类型输出到 dist/server/src
目录,本地开发时,可以得到类型提示。gez release
命令,将 dist/client
和 dist/server
目录生成 zip 压缩文件,放到 dist/client/versions
目录中。dist
目录的代码,部署到生产环境中。npm install
命令,触发 postinstall
钩子,再执行 gez install
命令下载可以封装一个 build
命令,将多个命令封装到一起。
+
等同于执行 gez build && gez start
运行生产环境代码
+开发环境中,所依赖的外部服务代码变更,总是会获得热更新,但是在生产环境中是没有热更新的。
如果依赖的服务发布更新了,你需要手动重启一下服务,或者编写一个脚本,监听版本发布来重启服务。
安装远程依赖到本地
+在 postinstall
钩子中,执行 npm install --production
安装生产依赖无效。
+
一个大型项目,总是会拆分成组件库、工具库、业务模块等。它们总是会写在不同的地方,以独立的仓库、monorepo 包等形式存在,但是最终都需要系统的主程序链接这些模块。Gez 的核心功能就是帮你把这些不同地方的模块,快速的链接到一起。实现一个服务发布,其它服务同时更新。
+Gez 默认是支持 SSR 的,你也可以把它当成 CSR 来使用。
由于第三方依赖,都被指向到了基础服务,不再需要重复打包,这会让 Rspack 的编译速度,再提升一个台阶。
传统的 SSR 程序在构建目标为 node
时,会将 node_modules
的模块设置为外部依赖,但是 Gez 会把全部代码都打包成 ESM 模块来进行链接。所以在使用一些第三方依赖的时候,尽可能的选择支持 ESM 的包,否则你可能会遇到一些问题。
+构建完成后,通常你可以看到这样的目录结构。
使用 [contenthash]
可以让我们生成基于内容哈希的文件名,这样我们的静态资产文件就可以放心的设置为强缓存了。
+
在服务渲染时注入所有服务的 /[服务名]/importmap.[contenthash].js
文件,将模块的哈希映射信息写入到 globalThis.__importmap__
对象中,最终将该变量值写入到 <script type="importmap"></script>
标签中。
在 package.json
中有一个 hash
字段,等同于 importmap.[contenthash].js
文件的哈希值。如果你只想做客户端渲染,可以编写一个脚本,读取每个服务的 dist/client/package.json
来生成一个静态的 index.html
。可以参考一下 ServerContext 的实现。
+
1、在开发阶段时,我们可以设置一个远程的依赖地址。
+2、程序会根据你配置的本地路径,计算出一个所有服务可以共同访问的 node_modules
路径,并自动创建软链接。
在生产环境中,你应该使用本地链接,而不是远程链接,这样能提高应用程序的启动速度。如果你使用 Docker,可以通过使用持久卷,将不同服务的产物组织到一个目录中。 +
配置 postinstall
钩子执行 gez install
命令。在安装开发依赖时,就会将远程依赖下载到你配置的 'root:../ssr-dep/dist'
目录中。
这个需要在构建时,提供对应的版本才能下载。更多请查看 gez release 命令说明。 +
基础服务,提供了所有的第三方依赖。
+1、在一个服务中,对外导出的软件包,总是使用 npm:包名
这样的格式,然后在其它服务使用 导出的服务名/npm/包名
。
+2、在一个服务中,对外导出的文件,总是使用 root:src/文件名
这样的格式,然后在其它服务使用 导出的服务名/src/文件名
。
+3、以往我们会针对业务来封装一些组件、工具函数,常常会使用 monorepo 的形式组织,如果不想对外,还需要搭建自己的 npm 私有镜像源。
+4、现在你可以直接在基础服务中封装好库对外导出。
+
在 package.json
中配置别名。
在 src/entry.node.ts
文件中配置对外导出。
在对应的服务将 query-string
模块链接到你需要的版本。
对外提供本服务的路由配置文件,并且将 vue
和 vue-router
模块指向到 ssr-dep
导出的模块
在主服务,可以使用
+import Layout from 'ssr-dep/src/components/layout.vue'
+import routes from 'ssr-module-auth/src/routes
+import Vue from 'vue'
+import Vue from 'ssr-dep/npm/vue'
Gez 是 Genesis 迭代的第三个大版本,v1.0 是通过 HTTP 请求来实现的远程组件,v2.0 是通过 Module Federation v1.0
+实现的远程组件。随着主流浏览器都已经支持 ESM,这使得设计一款基于 ESM 的模块链接变成了可能。随着 Rspack v1.0 的发布,提供了对 ESM 更加友好的支持,这使得我们可以将可能变成了现实。于是,我们将 v3.0 版本重命名为 Gez
。
目前社区类微服务的解决方案基本可以分为 iframe、micro-app、module federation 三种代表。其中 iframe 和 micro-app 这种模式只适合对已有的老项目进行缝合,是以降低程序运行效率所做的一种妥协,而 module federation 的接入成本较高,里面又是一个黑盒子,一旦出了问题,都十分难以排查。
+Gez 完全是基于 ESM 模块系统进行设计,默认支持 SSR,每个服务都可以对外导出模块,也可以使用外部模块,整个过程简单透明,能够精准的控制依赖管理。在客户端可以使用 importmap
将不同服务的导出映射成具有内容哈希缓存的文件。
import vue from 'vue'
替换成 ssr-npm/npm/vue.[contenthash].js
,这样静态文件就可以设置为强缓存了。对于不支持 importmap 的浏览器,es-module-shims 提供了降级的方案node --experimental-vm-modules --experimental-import-meta-resolve
来实现它。v3.0
版本。Gez 的定位并不是成为 Next.js 或 Nuxt.js 那样大而全的框架,而是成为一个具有 Typescript、ESM、SSR、模块链接特性的基础设施,你可以在这个基础上来构建属于你自己的 Next.js
。如果你需要定制化实现,它会很适合你。
所有的主流浏览器都已经支持,针对一些低版本的浏览器,可以提供一个升级的页面来引导用户升级它的浏览器。
+从 v1.0
、v2.0
到现在的 v3.0
,已经走过了将近 5 年的时光,支持起了公司内部数十个业务的项目,并且不断地推动业务项目的升级。
这是一个与框架无关的例子,采用原生的 HTML 来开发项目
+在 package.json 文件添加
+总是应该将生产依赖和开发依赖区分,会使 node_modules
在生产环境中更小。
+
安装生产依赖
+安装开发依赖
+在 package.json 文件添加
+基本结构
+模拟水合,更新当前时间
+模拟框架的 SSR API,渲染出 HTML 内容返回
+创建一个 web 服务器,来处理客户请求
++浏览器打开:http://localhost:3005
+
Write something to build your own docs! 🎁
The core compilation module is based on the Rust front-end toolchain, providing a more ultimate development experience.
MDX is a powerful way to write content, allowing you to use React components in Markdown.
Automatically generates a full-text search index for you during construction, providing out-of-the-box full-text search capabilities.
With the built-in I18n solution, you can easily provide multi-language support for documents or components.
In production, it automatically builds into static HTML files, which can be easily deployed anywhere.
Through its extension mechanism, you can easily extend theme UI and build process.
URL: ${params.url}
\n \n ${script}\n\n\n`;\n};\n\n'})}),"\n",(0,s.jsxs)(n.h3,{id:"srcentrynodets",children:["src/entry.node.ts",(0,s.jsx)(n.a,{className:"header-anchor","aria-hidden":"true",href:"#srcentrynodets",children:"#"})]}),"\n",(0,s.jsx)(n.p,{children:"创建一个 web 服务器,来处理客户请求"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-ts",children:"import http from 'node:http';\nimport type { GezOptions } from '@gez/core';\n\nexport default {\n // 设置应用的唯一名字,如果有多个项目,则名字不能重复\n name: 'ssr-html',\n // 本地执行 dev 和 build 时会使用\n async createDevApp(gez) {\n return import('@gez/rspack').then((m) =>\n m.createApp(gez, (buildContext) => {\n // 可以在这里修改 Rspack 编译的配置\n })\n );\n },\n async createServer(gez) {\n const server = http.createServer((req, res) => {\n // 静态文件处理\n gez.middleware(req, res, async () => {\n // 传入渲染的参数\n const ctx = await gez.render({\n url: req.url\n });\n // 响应 HTML 内容\n res.end(ctx.html);\n });\n });\n // 监听端口\n server.listen(3005, () => {\n console.log('http://localhost:3005');\n });\n }\n} satisfies GezOptions;\n\n"})}),"\n",(0,s.jsxs)(n.h3,{id:"启动项目",children:["启动项目",(0,s.jsx)(n.a,{className:"header-anchor","aria-hidden":"true",href:"#启动项目",children:"#"})]}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-bash",children:"npm run dev\n"})}),"\n",(0,s.jsxs)(n.blockquote,{children:["\n",(0,s.jsxs)(n.p,{children:["浏览器打开:",(0,s.jsx)(n.a,{href:"http://localhost:3005",target:"_blank",rel:"noopener noreferrer",children:"http://localhost:3005"})]}),"\n"]})]})}function d(){let e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{},{wrapper:n}=Object.assign({},(0,t.ah)(),e.components);return n?(0,s.jsx)(n,{...e,children:(0,s.jsx)(c,{...e})}):c(e)}let i=d;d.__RSPRESS_PAGE_META={},d.__RSPRESS_PAGE_META["guide%2Fstart%2Fgetting-started.mdx"]={toc:[{text:"创建项目",id:"创建项目",depth:2},{text:"将项目设置为 module",id:"将项目设置为-module",depth:2},{text:"安装依赖",id:"安装依赖",depth:2},{text:"添加脚本",id:"添加脚本",depth:2},{text:"入口文件",id:"入口文件",depth:2},{text:"src/entry.client.ts",id:"srcentryclientts",depth:3},{text:"src/entry.server.ts",id:"srcentryserverts",depth:3},{text:"src/entry.node.ts",id:"srcentrynodets",depth:3},{text:"启动项目",id:"启动项目",depth:3}],title:"快速开始",frontmatter:{}}}}]); \ No newline at end of file diff --git a/static/js/async/565.424aa473.js b/static/js/async/565.424aa473.js new file mode 100644 index 00000000..12963c0f --- /dev/null +++ b/static/js/async/565.424aa473.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunkrspress_doc_template=self.webpackChunkrspress_doc_template||[]).push([["565"],{9751:function(e,s,n){n.r(s),n.d(s,{default:function(){return c}});var r=n(651),d=n(6971);function i(e){let s=Object.assign({h1:"h1",a:"a",p:"p",div:"div",h2:"h2",ul:"ul",li:"li",code:"code",pre:"pre",br:"br",h3:"h3",h4:"h4"},(0,d.ah)(),e.components);return(0,r.jsxs)(r.Fragment,{children:[(0,r.jsxs)(s.h1,{id:"模块链接",children:["模块链接",(0,r.jsx)(s.a,{className:"header-anchor","aria-hidden":"true",href:"#模块链接",children:"#"})]}),"\n",(0,r.jsx)(s.p,{children:"一个大型项目,总是会拆分成组件库、工具库、业务模块等。它们总是会写在不同的地方,以独立的仓库、monorepo 包等形式存在,但是最终都需要系统的主程序链接这些模块。Gez 的核心功能就是帮你把这些不同地方的模块,快速的链接到一起。实现一个服务发布,其它服务同时更新。"}),"\n",(0,r.jsxs)(s.div,{className:"rspress-directive tip",children:[(0,r.jsx)(s.div,{className:"rspress-directive-title",children:"TIP"}),(0,r.jsx)(s.div,{className:"rspress-directive-content",children:(0,r.jsx)(s.p,{children:"Gez 默认是支持 SSR 的,你也可以把它当成 CSR 来使用。"})})]}),"\n",(0,r.jsxs)(s.h2,{id:"设计理念",children:["设计理念",(0,r.jsx)(s.a,{className:"header-anchor","aria-hidden":"true",href:"#设计理念",children:"#"})]}),"\n",(0,r.jsxs)(s.ul,{children:["\n",(0,r.jsx)(s.li,{children:"我们应该设计一个基础服务,由基础服务提供所有的第三方依赖。"}),"\n",(0,r.jsx)(s.li,{children:"由基础服务统一维护第三方依赖的更新,一次发布,所有业务系统生效。"}),"\n",(0,r.jsx)(s.li,{children:"业务服务仅构建业务代码,所有的第三方依赖,应指向到基础服务中。"}),"\n"]}),"\n",(0,r.jsxs)(s.div,{className:"rspress-directive tip",children:[(0,r.jsx)(s.div,{className:"rspress-directive-title",children:"TIP"}),(0,r.jsx)(s.div,{className:"rspress-directive-content",children:(0,r.jsx)(s.p,{children:"由于第三方依赖,都被指向到了基础服务,不再需要重复打包,这会让 Rspack 的编译速度,再提升一个台阶。"})})]}),"\n",(0,r.jsxs)(s.h2,{id:"构建",children:["构建",(0,r.jsx)(s.a,{className:"header-anchor","aria-hidden":"true",href:"#构建",children:"#"})]}),"\n",(0,r.jsxs)(s.p,{children:["传统的 SSR 程序在构建目标为 ",(0,r.jsx)(s.code,{children:"node"})," 时,会将 ",(0,r.jsx)(s.code,{children:"node_modules"})," 的模块设置为外部依赖,但是 Gez 会把全部代码都打包成 ESM 模块来进行链接。所以在使用一些第三方依赖的时候,尽可能的选择支持 ESM 的包,否则你可能会遇到一些问题。\n构建完成后,通常你可以看到这样的目录结构。"]}),"\n",(0,r.jsx)(s.pre,{children:(0,r.jsx)(s.code,{children:"- dist/ # 构建输出目录\n - client/ # 客户端构建输出\n - chunks/ # 当前服务抽离的公共代码\n - [name].[contenthash].js\n - npm/ # 对外导出的 node_modules 包\n - [name].[contenthash].js\n - src/ # 对外导出的 src 目录下的文件\n - [name].[contenthash].js\n - versions/ # 执行 gez release 命令,会将 client 和 server 的代码打包到这里\n - [contenthash].zip # 压缩文件\n - [contenthash].json # 当前压缩的版本号\n - latest.json # 最新的版本号\n - entry.[contenthash].js # 入口文件\n - importmap.js # 不可缓存文件,执行后往 globalThis 注入 __importmap__ \n - importmap.[contenthash].js # 可缓存文件,执行后往 globalThis 注入 __importmap__ \n - package.json # 声明模块的基本导出信息\n - server/ # 服务端构建输出\n - ... # 除了缺少 versions 目录,其它和 client 目录一致\n"})}),"\n",(0,r.jsxs)(s.div,{className:"rspress-directive tip",children:[(0,r.jsx)(s.div,{className:"rspress-directive-title",children:"TIP"}),(0,r.jsx)(s.div,{className:"rspress-directive-content",children:(0,r.jsxs)(s.p,{children:["使用 ",(0,r.jsx)(s.code,{children:"[contenthash]"})," 可以让我们生成基于内容哈希的文件名,这样我们的静态资产文件就可以放心的设置为强缓存了。",(0,r.jsx)(s.br,{}),"\n"]})})]}),"\n",(0,r.jsxs)(s.h2,{id:"客户端链接",children:["客户端链接",(0,r.jsx)(s.a,{className:"header-anchor","aria-hidden":"true",href:"#客户端链接",children:"#"})]}),"\n",(0,r.jsxs)(s.p,{children:["在服务渲染时注入所有服务的 ",(0,r.jsx)(s.code,{children:"/[服务名]/importmap.[contenthash].js"})," 文件,将模块的哈希映射信息写入到 ",(0,r.jsx)(s.code,{children:"globalThis.__importmap__"})," 对象中,最终将该变量值写入到 ",(0,r.jsx)(s.code,{children:'