👉导读
👉目录
proxy@0.0.123 /app /
`-- tools@0.0.123 /
+-- grpc@0.27.0 /exporter-trace-otlp-
| `-- js@1.9.5 /grpc-
+-- polaris@0.4.3 /
| `-- js@1.9.5 deduped /grpc-
+-- rainbow@0.0.4 /tdocs-common-
| +-- sdk@0.2.56 /rainbow-node-admin-
| | +-- polaris@0.4.5 /
| | | `-- js@1.9.5 deduped /grpc-
| | `-- client@0.5.39 /trpc-rpc-
| | `-- naming@0.5.39 /trpc-rpc-
| | `-- polaris@0.3.36 /
| | `-- js@1.3.8 /grpc-
| `-- sdk@0.2.66 /rainbow-node-
| `-- polaris@0.5.2 /
| `-- js@1.9.5 deduped /grpc-
`-- client@0.7.3 /trpc-rpc-
`-- naming@0.7.3 /trpc-rpc-
`-- polaris@0.5.2 // ^0.5.2 /
`-- js@1.9.5 deduped /grpc-
根目录的 1.9.5 /grpc-js 的依赖就是
> tree -L 2 -I "node_modules"
.
├── lerna.json
├── package.json
├── packages -- 这里是四个微服务共用的包逻辑 /xxx
│ ├── deploy
│ ├── helpers
│ ├── middleware
│ ├── proto
│ ├── rpc
│ ├── tools
│ ├── types
│ └── utils
├── README.md
├── servers -- 四个微服务的服务入口 /xxx
│ ├── cgi
│ ├── edit
│ ├── proxy
│ └── static
├── tsconfig.base.json
├── tsconfig.build.json
├── tsconfig.json
└── yarn.lock
COPY . .
FROM base AS topdep
WORKDIR /
RUN mkdir node_modules packages
COPY node_modules/ node_modules
COPY packages/ packages
FROM topdep as svr
WORKDIR /usr/app
COPY ["pnpm-*.yaml", ".npmrc", "./"]
COPY "servers/${APP}/" .
tar --exclude='node_modules' --exclude='.git' -cf - ../../ | docker build -f servers/proxy/Dockerfile.context - -t contextproxysvr
// .... 这里只展示入口逻辑
async function main(cli) {
const projectPath = dirname(cli.dockerFile)
// https://pnpm.io/filtering
const [dependencyFiles, packageFiles, metaFiles] = await Promise.all([
getFilesFromPnpmSelector(`{${projectPath}}^...`, cli.root, {
extraPatterns: cli.extraPatterns
}),
getFilesFromPnpmSelector(`{${projectPath}}`, cli.root, {
// 本包目录下除了 Dockerfile 都复制
extraPatterns: cli.extraPatterns.concat([`!${cli.dockerFile}`])
}),
getMetafilesFromPnpmSelector(`{${projectPath}}...`, cli.root, {
extraPatterns: cli.extraPatterns
})
])
await withTmpDir(async (tmpdir) => {
await Promise.all([
fs.copyFile(cli.dockerFile, join(tmpdir, 'Dockerfile')),
copyFiles(dependencyFiles, join(tmpdir, 'deps')),
copyFiles(metaFiles, join(tmpdir, 'meta')),
copyFiles(packageFiles, join(tmpdir, 'pkg'))
])
const files = await getFiles(tmpdir)
if (cli.listFiles) {
for (const path of files) console.log(path)
} else {
await pipe(createTar({ gzip: true, cwd: tmpdir }, files), process.stdout)
}
})
}
// ...
# 复制构建后的代码
COPY ./meta .
COPY ./deps .
COPY ./pkg .
RUN --mount=type=cache,id=pnpm-store,target=/root/pnpm-store\
pnpm config set store-dir /root/pnpm-store && pnpm install --filter "{${PACKAGE_PATH}}..."\
--frozen-lockfile\
--prod\
--ignore-scripts\
--unsafe-perm
package-import-method=copy
由于依赖被打平,B 依赖被提升到 node_modules 的一级目录
node_modules
├── A@1.0.0
├── B@1.0.0
└── C@1.0.0
└── node_modules
└── B@2.0.0
项目只引用了 A 和 C,但是如果代码里面写了引用 B 的代码,也是可以引用到的。
{
"dependencies": {
"A": "^1.0.0",
"C": "^1.0.0"
}
}
// 类型信息是我根据 pnpm 源码找到添加上去的, 可以帮助编写逻辑
/**
* @typedef PackageInfo
* @type {object}
* @property {string} name
* @property {string} version
* @property {string} description
* @property {string} main
* @property {object} scripts
* @property {string} author
* @property {string} license
* @property {object} dependencies
* @property {object} devDependencies
* @property {object} optionalDependencies
* @property {object} peerDependencies
*/
/**
* @typedef LockfileProjectSnapshot Map 类型都不是真正的 Map,只是对象
* @type {object}
* @property {Map<string, string>} specifiers
* @property {Map<string, string>} dependencies
* @property {Map<string, string>} devDependencies
* @property {Map<string, string>} optionalDependencies
* @property {object} dependenciesMeta
* @property {string} publishDirectory
*/
/**
* @typedef Lockfile Map 类型都不是真正的 Map,只是对象
* @type {object}
* @property {Map<string, LockfileProjectSnapshot>} importers 引包入口
* @property {string | number} lockfileVersion
* @property {LockfileProjectSnapshot} packages
*/
/**
* @typedef ReadPackageContext
* @type {object}
* @property {function} log
*/
/**
* Will be called after parse package dependencies
* @param {PackageInfo} pkg
* @param {ReadPackageContext} context
* @returns
*/
function readPackage(pkg, context) {
// 解决直接依赖版本
if (pkg && pkg.dependencies && pkg.dependencies['@grpc/grpc-js']) {
const grpcVersion = pkg.dependencies['@grpc/grpc-js']
if (compareVersion(grpcVersion, '1.7.3') >= 1) {
pkg.dependencies['@grpc/grpc-js'] = '1.7.3'
context.log(`Modifying the @grpc/grpc-js package(which greater than 1.7.3) to be version 1.7.3 in ${pkg.name}`)
}
}
return pkg
}
/**
* Will be called before preinstall to modify lockfile to lock package version
* @param {Lockfile} lockfile
* @param {ReadPackageContext} context
* @returns
*/
function afterAllResolved(lockfile, context) {
// 直接依赖从上面 readPackage 里面来解决
// 这里是解决间接依赖的问题
/**
* 以下逻辑:
* 1. 检查所有包生产依赖有没有依赖到 @grpc/grpc-js,如有则判断版本是否超过 1.7.3,
* 如超过则强行修改依赖为 1.7.3(因为不希望引到超过这个版本,超过这个版本会有 TCP 占用问题)
* 2. 检查包本身的信息是否是 @grpc/grpc-js 这个包,如果超过则删除
* 3. 添加 @grpc/grpc-js/1.7.3 版本的 entry 信息
*/
if (lockfile.packages) {
const allPackageNames = Object.keys(lockfile.packages)
for (const packageName of allPackageNames) {
const packageInfo = lockfile.packages[packageName]
const packageDependencies = packageInfo.dependencies
if (packageName.includes('@grpc/grpc-js')) {
const grpcVersionMatchRes = packageName.match(/\\d+\\.\\d+\\.\\d+/)
const grpcVersion = grpcVersionMatchRes[0]
if (compareVersion(grpcVersion, '1.7.3') >= 1) {
delete lockfile.packages[packageName]
}
}
if (packageDependencies && packageDependencies['@grpc/grpc-js'] !== undefined) {
packageGRPCDependencyVersion = packageDependencies['@grpc/grpc-js']
// 如果版本比 1.7.3 低则不修改, 如果高于 1.7.3 版本则修改为 1.7.3
if (compareVersion(packageGRPCDependencyVersion, '1.7.3') >= 1) {
packageDependencies['@grpc/grpc-js'] = '1.7.3'
}
}
}
// 给 packages 添加 @grpc/grpc-js@1.7.3 版本 entry
lockfile.packages['/@grpc/grpc-js/1.7.3'] = grpcLockInfoEntry('1.7.3')
}
context.log('Modifying the @grpc/grpc-js package(which greater than 1.7.3) to be version 1.7.3')
return lockfile
}
...
module.exports = {
hooks: {
readPackage,
afterAllResolved
}
}