问题描述
今天遇到这样的项目结构很复杂
Ran into a complication today with project structure like this
packages
/app
pages/
package.json
/ui-kit
pages/
package.json
/shared
.babelrc
package.json
root lvl package json 定义 workspaces: [packages/*]
其中 app
和 ui-kit
都是 nextjs 应用程序.
root lvl package json defines workspaces: [packages/*]
where app
and ui-kit
are both nextjs apps.
我在根 lvl package.json 中有以下脚本
I have following script in root lvl package.json
"dev:app": "next packages/app",
"dev:ui-kit": "next packages/ui-kit"
在我引入 shared
文件夹之前,这两种方法都可以正常工作,该文件夹本质上包含一些功能/组件等......在包之间重复使用.一旦我将它包含到 app
或 ui-kit
中,我就会收到这样的错误
both of these worked fine until I introduced shared
folder, which essentially contains some functions / components etc... that are re-used between packages. As soon as I include it into either app
or ui-kit
I get error like this
在 ./packages/shared/index.js
in ./packages/shared/index.js
模块解析失败:意外令牌 (4:21) 您可能需要一个适当的加载器来处理这种文件类型.|从导入反应'反应' ||导出默认()=>你好共享!|
所以看起来 nextjs 没有将任何加载器应用于它所指向的文件夹之外的任何内容.有没有办法以某种方式解决这个问题?即从根文件夹开始下一步,但基于不同的脚本命令以某种方式指向不同的入口文件?
So it looks like nextjs is not applying any loaders to anything outside the folder where it was pointed at. Is there a solution to fix this somehow? i.e. start next from root folder but point it to different entry files somehow based on different script commands?
推荐答案
从 NextJs 11 开始,有一个新的实验选项,称为 externalDir 效果很好,不需要使用 next-transpile-modules.
Since NextJs 11, there's a new experimental option called externalDir which works pretty well and does not require the use of next-transpile-modules.
为了清楚起见,让我们一步一步地做,它可能看起来很长,但是一旦你掌握了它,它就很容易(实际上是 3 个步骤)
For clarity let's make a step by step howto, it might look a long process but once you get it, it's pretty easy (actually 3 steps)
为了改善体验,我建议将 yarn 升级到 v3+ (yarn set version 3.0.2 && yarn plugin import workspace-tools
) 并编辑生成的配置.yarnrc.yml
类似于这个:
For improved experience I suggest to upgrade yarn to v3+ (yarn set version 3.0.2 && yarn plugin import workspace-tools
) and edit the generated config .yarnrc.yml
similar to this one:
# Yarn 2+ supports pnp or regular node_modules installs. Use node-modules one.
nodeLinker: node-modules
nmMode: hardlinks-local
plugins:
- path: .yarn/plugins/@yarnpkg/plugin-workspace-tools.cjs
spec: "@yarnpkg/plugin-workspace-tools"
yarnPath: .yarn/releases/yarn-3.0.2.cjs
PS:您可能也想将其添加到 .gitignore
PS: you might want to add this to .gitignore
too
.yarn/*
!.yarn/patches
!.yarn/releases
!.yarn/plugins
!.yarn/sdks
!.yarn/versions
.pnp.*
为什么?因为您将有可能使用 workspace: 别名协议.(也可以在 pnpm 中使用)
Why ? Cause you'll get the possibility to use the workspace: alias protocol. (available in pnpm too)
我建议严格限制软件包所依赖的内容(有明确的界限).这不是绝对要求,而是一种可以避免难以调试情况的良好做法.
I suggest to be strict about what packages depends on (to have clear boundaries). It's not an absolute requirement, but a good practice that might avoid hard to debug situations.
为了帮助包管理器,我建议正确声明每个应用程序/包的依赖项及其边界.
To help the package manager, I suggest to properly declare your dependencies and their boundaries per app/packages.
换句话说,每个包/应用程序都有自己的 package.json,您可以在其中显式添加他们需要的 deps(不在根 package.json 中)
In other words each package/app had its own package.json where you explicitly add the deps they need (not in the root package.json)
按照你的例子,
apps/
packages
/app
package.json (app depend on ui-kit through yarn workspace: alias)
tsconfig.json (we will add typescript path aliases there too)
next.config.js
/ui-kit
package.json
package.json (do not put nextjs as dep here, only in app)
根 package.json
{
"name": "monorepo",
"private": true,
"workspaces": [
"packages/*" // Enable package discovery in packages/* directory.
],
"devDependencies": {
"husky": "7.0.2", // Only what's needed for monorepo management
}
packages/app/package.json
{
"name": "my-app",
"devDependencies": {
"@types/node": "16.10.1",
"@types/react": "17.0.29",
"@types/react-dom": "17.0.9",
"typescript": "4.4.4"
},
"dependencies": {
// Assuming the name of packages/ui-kit is ui-kit,
// we explicitly declare the dependency on it through
// workspace: alias (package-manager perspective)
"ui-kit": "workspace:*",
"next": "11.1.2",
"react": "17.0.2",
"react-dom": "17.0.2",
}
}
为什么?这样你就不会陷入与部门冲突的奇怪问题.
Why ? That way you won't fall into weird problems with conflicting deps.
即使您不使用 typescript,NextJs 也会读取 tsconfig.json
并查找 typescript 路径映射 配置.如果您不知道它是什么......它只是一个您声明(再一次)您的部门的配置.Nextjs 会将它们转换为它在后台编译 deps 时使用的内容(即:babel-plugin-module-resolver 可能是后来的 swc).
Even if you're not using typescript, NextJs will read the tsconfig.json
and look for typescript path mapping configuration. If you're not aware of what it is... it's simply a configuration where you declare (one more time) your deps. Nextjs will convert them to what it uses under the hood to compile deps (ie: babel-plugin-module-resolver and probably later swc).
按照您的示例,只需以这种方式编辑 ./packages/app/tsconfig.json
Following your example, just edit a ./packages/app/tsconfig.json
in this way
{
"compilerOptions": {
// here baseUrl is set at ./src (good practive), can
// be set to '.'
"baseUrl": "./src",
"paths": {
// Declare deps here (keep them in sync with what
// you defined in the package.json)
// PS: path are relative to baseUrl
"ui-kit/*": ["../../ui-kit/src/*"],
// if you have a barrel in ui-lib
"ui-kit": ["../../ui-kit/src/index"],
}
},
}
为什么?更多工具之间的限制(包管理器和路径有不同的视角)
Why ? More a limitation between tooling (package managers and paths have different perspectives)
在 packages/app/nextjs.config.js
中,启用 externalDir 配置(目前处于实验阶段,但效果很好,此处提供反馈线程)
In packages/app/nextjs.config.js
, enable the externalDir config (currently in experimental but works pretty well, feedback thread here)
const nextConfig = {
experimental: {
// this will allow nextjs to resolve files (js, ts, css)
// outside packages/app directory.
externalDir: true,
},
};
export default nextConfig;
PS:对于较旧的 nextjs 版本,完全可以通过自定义 webpack 配置来做同样的事情.询问您是否需要示例.
PS: for older nextjs versions, it's totally possible to do the same through custom webpack config. Ask if you need an example.
在您的应用中,您应该能够像这样导入您的 ui-kit:
In your app you should be able to import your ui-kit like this:
import { Button } from 'ui-kit';
// or
import Avatar from 'ui-kit/components/Avatar'
它的美妙之处在于快速刷新可以开箱即用(无需构建).它很快,你不需要 NX(+ 昂贵的 nx.cloud)、rush 或任何东西......
The beauty of it is that fast refresh will work out of the box (no build necessary). It's fast, you don't need NX (+ the pricey nx.cloud), rush or anything...
Nextjs 将简单地导入文件,按需构建它们,甚至将它们缓存在它自己的优化缓存中(尤其是在 webpack 5 中速度很快,并且也可以在 CI 上启用)...
Nextjs will simply import the files, build them on demand and even cache them in it's own optimized cache (especially fast with webpack 5 and can be enabled on CI too)...
如果您想了解更多信息,我会维护一个示例存储库,该存储库将在此存储库上提供完整的生命周期视角(ci、github 操作、linters、部署...):https://github.com/belgattitude/nextjs-monorepo-example.
If you like more information, I maintain an example repository will a full lifecycle perspective (ci, github action, linters, deploys...) on this repo: https://github.com/belgattitude/nextjs-monorepo-example.
PS:也关注 yarn 3+ 开发和版本这里,他们现在做得很好.
PS: Follow also yarn 3+ development and versions here, they're doing a great job nowadays.
这篇关于将 next.js 与纱线工作区一起使用的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!