breaking: Standalone builds (#9496)
Fixes: FRMW-2742
In this PR, we fix the build output of the backend source code, which eliminates a lot of magic between the development and production environments.
Right now, we only compile the source files from the `src` directory and write them within the `dist` directory.
**Here's how the `src` directory with a custom module looks like**
```
src
├── modules
│ └── hello
│ ├── index.ts
```
**Here's the build output**
```
dist
├── modules
│ └── hello
│ ├── index.js
```
Let's imagine a file at the root of your project (maybe the `medusa-config.js` file) that wants to import the `modules/hello/index` file. How can we ensure that the import will work in both the development and production environments?
If we write the import targeting the `src` directory, it will break in production because it should target the `dist` directory.
## Solution
The solution is to compile everything within the project and mimic the file structure in the build output, not just the `src` directory.
**Here's how the fixed output should look like**
```
dist
├── src
│ ├── modules
│ │ └── hello
│ │ ├── index.js
├── medusa-config.js
├── yarn.lock
├── package.json
```
If you notice carefully, we also have `medusa-config.js`, `yarn.lock`, and `package.json` within the `dist` directory. We do so to create a standalone built application, something you can copy/paste to your server and run without relying on the original source code.
- This results in small containers since you are not copying unnecessary files.
- Clear distinction between the development and the production code. If you want to run the production server, then `cd` into the `dist` directory and run it from there.
## Changes in the PR
- Breaking: Remove the `dist` and `build` folders. Instead, write them production artefacts within the `.medusa` directory as `.medusa/admin` and `.medusa/server`.
- Breaking: Change the output of the `.medusa/server` folder to mimic the root project structure.
- Refactor: Remove `Symbol.for("ts-node.register.instance")]` check to find from where to load the source code.
- Refactor: Use `tsc` for creating the production build. This ensures we respect `tsconfig` settings when creating the build and also perform type-checking.
Co-authored-by: Adrien de Peretti <25098370+adrien2p@users.noreply.github.com>
This commit is contained in:
@@ -7,6 +7,8 @@ describe("defineConfig", function () {
|
||||
{
|
||||
"admin": {
|
||||
"backendUrl": "http://localhost:9000",
|
||||
"outDir": ".medusa/admin",
|
||||
"path": "/app",
|
||||
},
|
||||
"featureFlags": {},
|
||||
"modules": {
|
||||
@@ -113,6 +115,8 @@ describe("defineConfig", function () {
|
||||
{
|
||||
"admin": {
|
||||
"backendUrl": "http://localhost:9000",
|
||||
"outDir": ".medusa/admin",
|
||||
"path": "/app",
|
||||
},
|
||||
"featureFlags": {},
|
||||
"modules": {
|
||||
@@ -222,6 +226,8 @@ describe("defineConfig", function () {
|
||||
{
|
||||
"admin": {
|
||||
"backendUrl": "http://localhost:9000",
|
||||
"outDir": ".medusa/admin",
|
||||
"path": "/app",
|
||||
},
|
||||
"featureFlags": {},
|
||||
"modules": {
|
||||
@@ -331,6 +337,8 @@ describe("defineConfig", function () {
|
||||
{
|
||||
"admin": {
|
||||
"backendUrl": "http://localhost:9000",
|
||||
"outDir": ".medusa/admin",
|
||||
"path": "/app",
|
||||
},
|
||||
"featureFlags": {},
|
||||
"modules": {
|
||||
|
||||
@@ -16,7 +16,13 @@ const DEFAULT_ADMIN_CORS =
|
||||
* make an application work seamlessly, but still provide you the ability
|
||||
* to override configuration as needed.
|
||||
*/
|
||||
export function defineConfig(config: Partial<ConfigModule> = {}): ConfigModule {
|
||||
export function defineConfig(
|
||||
config: Partial<
|
||||
Omit<ConfigModule, "admin"> & {
|
||||
admin: Partial<ConfigModule["admin"]>
|
||||
}
|
||||
> = {}
|
||||
): ConfigModule {
|
||||
const { http, ...restOfProjectConfig } = config.projectConfig || {}
|
||||
|
||||
/**
|
||||
@@ -42,6 +48,8 @@ export function defineConfig(config: Partial<ConfigModule> = {}): ConfigModule {
|
||||
*/
|
||||
const admin: ConfigModule["admin"] = {
|
||||
backendUrl: process.env.MEDUSA_BACKEND_URL || DEFAULT_ADMIN_URL,
|
||||
outDir: ".medusa/admin",
|
||||
path: "/app",
|
||||
...config.admin,
|
||||
}
|
||||
|
||||
|
||||
@@ -9,22 +9,27 @@ import { join } from "path"
|
||||
export function getConfigFile<TConfig = unknown>(
|
||||
rootDir: string,
|
||||
configName: string
|
||||
): { configModule: TConfig; configFilePath: string; error?: any } {
|
||||
):
|
||||
| { configModule: null; configFilePath: string; error: Error }
|
||||
| { configModule: TConfig; configFilePath: string; error: null } {
|
||||
const configPath = join(rootDir, configName)
|
||||
let configFilePath = ``
|
||||
let configModule
|
||||
let err
|
||||
|
||||
try {
|
||||
configFilePath = require.resolve(configPath)
|
||||
configModule = require(configFilePath)
|
||||
const configFilePath = require.resolve(configPath)
|
||||
const configExports = require(configFilePath)
|
||||
return {
|
||||
configModule:
|
||||
configExports && "default" in configExports
|
||||
? configExports.default
|
||||
: configExports,
|
||||
configFilePath,
|
||||
error: null,
|
||||
}
|
||||
} catch (e) {
|
||||
err = e
|
||||
return {
|
||||
configModule: null,
|
||||
configFilePath: "",
|
||||
error: e,
|
||||
}
|
||||
}
|
||||
|
||||
if (configModule && typeof configModule.default === "object") {
|
||||
configModule = configModule.default
|
||||
}
|
||||
|
||||
return { configModule, configFilePath, error: err }
|
||||
}
|
||||
|
||||
@@ -9,16 +9,16 @@ export function normalizeImportPathWithSource(
|
||||
): string {
|
||||
let normalizePath = path
|
||||
|
||||
/**
|
||||
* If the project is running on ts-node all relative module resolution
|
||||
* will target the src directory and otherwise the dist directory.
|
||||
* If the path is not relative, then we can safely import from it and let the resolution
|
||||
* happen under the hood.
|
||||
*/
|
||||
if (normalizePath?.startsWith("./")) {
|
||||
const sourceDir = process[Symbol.for("ts-node.register.instance")]
|
||||
? "src"
|
||||
: "dist"
|
||||
/**
|
||||
* If someone is using the correct path pointing to the "src" directory
|
||||
* then we are all good. Otherwise we will point to the "src" directory.
|
||||
*
|
||||
* In case of the production output. The app should be executed from within
|
||||
* the "./build" directory and the "./build" directory will have the
|
||||
* "./src" directory inside it.
|
||||
*/
|
||||
let sourceDir = normalizePath.startsWith("./src") ? "./" : "./src"
|
||||
normalizePath = join(process.cwd(), sourceDir, normalizePath)
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user