Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

编译与构建配置

当前项目统一在根目录 oak.config.ts 中描述 Web、微信小程序和 Server 的编译行为:

import { CreateCompilerConfig } from '@xuchangzju/oak-cli/lib/createConfig';

export default CreateCompilerConfig({
    vite: {
        common: {},
        web: {},
        mp: {},
    },
    server: {},
});

vite.common 是共享层,vite.webvite.mp 是目标覆盖层。alias、dedupe、插件和其它配置会按框架规则合并;目标配置优先级更高。不要同时维护一份旧 configuration/compiler.js 和一份相互矛盾的 oak.config.ts

插件层次

每个 Vite 目标的插件分成三类:

plugins: {
    pre: [],
    buildin: {},
    post: [],
}
  • pre:项目插件,在 Oak 内置插件前执行;
  • buildin:配置 Oak 已提供的 CDN、PWA、Node polyfill、图片压缩等能力;
  • post:项目插件,在 Oak 内置插件后执行。

优先使用 buildin 的结构化选项。只有框架没有提供相应能力时才增加项目插件,并确认插件在 Vite 8 / Rolldown 下可运行。

Web CDN

Web CDN 是显式 opt-in:只有 staging / production build 且配置了 vite.web.plugins.buildin.cdn 才会 external 对应依赖。未配置时 React、ReactDOM 等依赖正常进入 bundle。

CDN 配置支持多个候选源、超时、依赖顺序以及 global/UMD、ESM、CJS 模块。配置时至少确认:

  • package key 与真实 import 一致;
  • global/UMD 模块声明正确的全局变量;
  • ESM 源允许跨域并能被 import map 或 preload 使用;
  • 多源 fallback 不会执行同一个有副作用脚本两次;
  • 生产页面在 CDN 全部失败时有明确降级行为。

Desktop production 会强制保持离线 bundle,不消费 Web CDN external。

一个带依赖关系和双源竞速的最小配置如下:

export default CreateCompilerConfig({
    vite: {
        web: {
            plugins: {
                buildin: {
                    cdn: {
                        moduleStrategy: 'parallel',
                        sourceStrategy: 'race',
                        timeout: 8000,
                        react: {
                            version: '19.2.4',
                            format: 'umd',
                            global: 'React',
                            sources: [
                                'https://cdn-a.example/react@{version}.js',
                                'https://cdn-b.example/react@{version}.js',
                            ],
                        },
                        'react-dom/client': {
                            package: 'react-dom',
                            external: ['react-dom', 'react-dom/client'],
                            version: '19.2.4',
                            format: 'umd',
                            global: 'ReactDOM',
                            dependsOn: ['react'],
                            sources: [
                                'https://cdn-a.example/react-dom@{version}.js',
                                'https://cdn-b.example/react-dom@{version}.js',
                            ],
                        },
                    },
                },
            },
        },
    },
});

moduleStrategy: 'parallel' 只并行加载彼此无依赖的模块。dependsOn 使用 CDN 配置 key,不是 npm 包名猜测;未知 key 和循环依赖会在构建配置阶段失败。

sourceStrategy: 'race' 会同时准备同一模块的候选源,但只执行最先成功的一个。global/UMD/IIFE 与 ESM 使用 preload 竞速,CJS 使用 fetch 竞速;它会增加瞬时请求量,适合跨 CDN 容灾,不应无条件开启。也可以只在某个模块上配置 sourceStrategy,并为 root、module 或单个 source 分别设置 timeout

React 19 官方包不再提供传统 UMD 产物。使用 UMD 时必须确认 CDN 提供的真实文件和全局变量;使用官方 CJS 产物时要显式声明 dependsOn。构建成功不代表浏览器运行成功,至少要在 production 页面验证裸 import、CJS require、fallback 和全部 CDN 失败路径。

Web PWA

Vite Web 保留了可选 PWA 接入,但当前模板不会安装 vite-plugin-pwa:现有发布版与 Vite 8 baseline 存在 peer dependency 冲突。缺少该包时,production / staging 构建会提示 PWA 被跳过,不影响普通 Web 构建。

项目不使用 PWA 时,建议显式关闭并消除提示:

export default CreateCompilerConfig({
    vite: {
        web: {
            plugins: {
                buildin: {
                    pwa: false,
                },
            },
        },
    },
});

当前实现只可靠消费 pwa: false{ enabled: false } 这个关闭语义;其它 pwa 对象字段尚未传给 VitePWA(...)。不要在文档或项目配置中把 manifest/workbox 对象当作已经生效的合同。确实需要 PWA 时,应先确认一个与 Vite 8 兼容的插件版本,并用项目 pre / post 插件显式接入和验证 service worker、manifest、scope、start URL、图标及缓存策略。开发模式不会注册内置 PWA service worker,避免缓存干扰 HMR。

小程序 Node polyfill

Vite MP 使用 vite-plugin-node-polyfills 兼容部分第三方包。默认保留 processglobalBuffernode: protocol import,但不自动引入体积和真机风险较高的 cryptoassert 完整兼容链。

确实需要 Node built-in 时使用完整白名单:

export default CreateCompilerConfig({
    vite: {
        mp: {
            plugins: {
                buildin: {
                    nodePolyfills: {
                        include: ['path', 'util'],
                    },
                },
            },
        },
    },
});

非空 include 是完整白名单,不是“在默认集合上追加”。加入 cryptoassert 前,应检查最终主包依赖图,并在真机验证随机数、加密、正则和启动阶段;微信开发者工具不能覆盖所有 JS 引擎差异。

小程序图片压缩

图片压缩作用于最终小程序 dist,因此能覆盖主包、分包和复制后的资源:

npm install -D sharp
export default CreateCompilerConfig({
    vite: {
        mp: {
            plugins: {
                buildin: {
                    compressImage: {
                        quality: 82,
                        include: ['assets/', /images/],
                        minSize: 4096,
                        skipIfLarger: true,
                    },
                },
            },
        },
    },
});

支持 PNG、JPEG、WebP、AVIF。sharp 必须安装在消费项目中;未安装时构建只给 warning 并跳过。保留 skipIfLarger: true,避免压缩结果反而扩大包体。

小程序 route map

小程序 Vite 构建会把 package.config.ts、页面配置和分包信息编译成 virtual:oak-mp-route-map。Oak navigator 使用 canonical route,例如:

this.features.navigator.navigateTo({
    url: '/frontend/order/detail',
});

构建器负责把它解析为真实主包或分包路径。不要把生成路径保存进实体、菜单或共享常量。显式 route map 存在时,缺失 route 会报错或 warning,不再猜测 /pages/.../index

Bundle 分析

Web 和小程序构建均支持:

oak-cli build --target web --mode production --vite --analyze
oak-cli build --target mp --mode production --vite --analyze

报告包含 bundle treemap、源码目录、NPM 依赖和反向依赖图;小程序还会显示主包、分包统计。先从报告确认大模块的真实 importer,再决定拆包、按需导入或调整 polyfill,不要只根据包名猜测。

Server bundle

oak.config.tsserver 只服务 opt-in 的 esbuild runtime bundle:

export default CreateCompilerConfig({
    server: {
        nodeModules: {
            bundle: false,
        },
        esbuild: {
            sourcemap: true,
        },
    },
});

server.nodeModules.bundle: false 时,普通第三方依赖保持裸 package import,部署目录需要安装生产依赖;设为 true 才会尝试把第三方运行依赖带入 bundle。数据库驱动是动态装载项,构建后必须确认目标环境需要的 mysql2pgbetter-sqlite3 已包含或可从外部解析。

Server bundle 的完整发布方式见部署

严格 TypeScript 构建

当前模板的 build:es 默认执行:

oak-cli build --target tsc --configFile tsconfig.es.json --noEmit --enable-xml-check --emit-injection-types --check-style-less

这些检查分别覆盖 XML/WXML、编译器推导的 render props 声明、Less Module class 与真实 JSX/XML 作用域。报错应回到 propertiesformDatamethods、模板表达式和真实样式结构修复,不要通过扩大手写 props、空样式或关闭检查掩盖合同错误。

tscBuilder compiler plugin

oak-domain 的 tscBuilder 还提供程序化 TscCompilerPlugin,供 oak-cli 这类编译工具在创建 TypeScript Program 前替换 host、增加 rootNames 或映射 diagnostics:

import type { TscCompilerPlugin } from 'oak-domain/lib/compiler/tscBuilder';

const plugin: TscCompilerPlugin = {
    prepareProgram(context) {
        return {
            host: context.host,
            rootNames: context.rootNames,
            mapDiagnostic(diagnostic) {
                return diagnostic;
            },
        };
    },
};

这个接口属于编译工具集成,不是普通 Oak 应用的 Vite 插件配置。普通项目不应为了隐藏诊断注册一个返回 undefined 的 mapper;需要扩展时,应保持 host、rootNames、source map 和 watch 行为可复现,并用独立 fixture 同时验证一次性构建和 watch。