@swissjs/swite 0.4.1 → 0.4.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +7 -0
- package/__tests__/import-rewriter-bug.test.ts +122 -122
- package/__tests__/security-r001-r002.test.ts +190 -190
- package/dist/cli.js +0 -0
- package/dist/dev-engine/hmr/hmr-client-template.js +111 -111
- package/dist/dev-engine/middleware/middleware-setup.js +4 -3
- package/docs/architecture/build-pipeline.md +97 -97
- package/docs/architecture/dev-server.md +87 -87
- package/docs/architecture/hmr.md +78 -78
- package/docs/architecture/import-rewriting.md +101 -101
- package/docs/architecture/index.md +16 -16
- package/docs/architecture/python-integration.md +93 -93
- package/docs/architecture/resolution.md +92 -92
- package/docs/cli/build.md +78 -78
- package/docs/cli/dev.md +90 -90
- package/docs/cli/index.md +15 -15
- package/docs/cli/start.md +45 -45
- package/docs/development/contributing.md +74 -74
- package/docs/development/index.md +12 -12
- package/docs/development/internals.md +101 -101
- package/docs/guide/configuration.md +89 -89
- package/docs/guide/index.md +13 -13
- package/docs/guide/project-structure.md +75 -75
- package/docs/guide/quickstart.md +113 -113
- package/docs/index.md +16 -16
- package/package.json +10 -9
- package/src/config/env.ts +98 -98
- package/src/dev-engine/handlers/ui-handler.ts +30 -30
- package/src/dev-engine/handlers/uix-handler.ts +21 -21
- package/src/dev-engine/hmr/hmr-client-template.ts +122 -122
- package/src/dev-engine/middleware/middleware-setup.ts +354 -354
- package/src/dev-engine/middleware/static-files.ts +813 -813
- package/src/resolution/cdn/cdn-fallback.ts +40 -40
- package/src/resolution/path/path-fixup.ts +27 -27
- package/src/resolution/rewriting/import-rewriter.ts +237 -237
- package/src/resolution/symlink-registry.ts +114 -114
|
@@ -1,92 +1,92 @@
|
|
|
1
|
-
<!--
|
|
2
|
-
Copyright (c) 2024 Themba Mzumara
|
|
3
|
-
SWITE - SWISS Development Server
|
|
4
|
-
Licensed under the MIT License.
|
|
5
|
-
-->
|
|
6
|
-
|
|
7
|
-
# Import Resolution
|
|
8
|
-
|
|
9
|
-
SWITE resolves every bare module specifier to a browser-accessible URL before serving compiled source. This page describes the full resolution pipeline.
|
|
10
|
-
|
|
11
|
-
---
|
|
12
|
-
|
|
13
|
-
## ModuleResolver
|
|
14
|
-
|
|
15
|
-
`ModuleResolver` (`src/resolution/resolver.ts`) is the central resolution object. Each `SwiteServer` creates one instance per app root. It is injected into all file handlers and the import rewriter.
|
|
16
|
-
|
|
17
|
-
`ModuleResolver.resolve(specifier, importer)` returns a URL string. Specifiers fall into four categories:
|
|
18
|
-
|
|
19
|
-
1. **Import map hit** — If a pre-generated `.swite/import-map.json` is loaded and the specifier appears in its `imports` map, that URL is returned immediately without further resolution.
|
|
20
|
-
2. **Bare import** — Starts with a letter or `@`. Delegated to `resolveBareImport`.
|
|
21
|
-
3. **Absolute path** — Starts with `/`. Returned as-is.
|
|
22
|
-
4. **Relative import** — Starts with `.`. Resolved relative to the importer's directory on disk, then converted to a URL via `toUrl`.
|
|
23
|
-
|
|
24
|
-
### Variable reference detection
|
|
25
|
-
|
|
26
|
-
Before delegating to `resolveBareImport`, the resolver checks whether the specifier looks like a variable reference rather than a module path. Specifiers containing a `.` but not starting with `@` (e.g. `def.componentUrl`) are returned unchanged with a warning. Specifiers that do not match the pattern `^[@a-zA-Z][a-zA-Z0-9_/@-]*(\.(js|ts|ui|uix|mjs|cjs|jsx|tsx))?$` are also returned unchanged.
|
|
27
|
-
|
|
28
|
-
---
|
|
29
|
-
|
|
30
|
-
## Bare import resolution
|
|
31
|
-
|
|
32
|
-
`resolveBareImport` (`src/resolution/bare-import-resolver.ts`) resolves a bare specifier by searching for the package's `package.json` in multiple `node_modules` locations:
|
|
33
|
-
|
|
34
|
-
1. `<appRoot>/node_modules/<pkg>`
|
|
35
|
-
2. `<parentOfAppRoot>/node_modules/<pkg>`
|
|
36
|
-
3. `<workspaceRoot>/node_modules/<pkg>`
|
|
37
|
-
4. `<frameworkMonorepo>/node_modules/<pkg>`
|
|
38
|
-
|
|
39
|
-
If a `package.json` is found, the resolver reads the `exports` field (preferring the `import` condition), or falls back to `module` then `main`. In development, if the resolved path is inside a `dist/` directory, the resolver attempts to substitute `src/` and `.ts` extension and serves from source.
|
|
40
|
-
|
|
41
|
-
---
|
|
42
|
-
|
|
43
|
-
## Workspace package resolution
|
|
44
|
-
|
|
45
|
-
When a package is not found in any `node_modules`, `resolveWorkspacePackage` (`src/resolution/workspace-package-resolver.ts`) is called. It uses `PackageRegistry` (a process-wide singleton) to scan the workspace for `package.json` files. The registry recursively scans up to 15 directory levels deep, skipping `node_modules`, `dist`, `.git`, and `.swite`.
|
|
46
|
-
|
|
47
|
-
If the package is still not found, the registry calls `rescan()` in case the package was added after the initial scan.
|
|
48
|
-
|
|
49
|
-
---
|
|
50
|
-
|
|
51
|
-
## toUrl: converting filesystem paths to browser URLs
|
|
52
|
-
|
|
53
|
-
`toUrl` (`src/resolution/url-resolver.ts`) converts an absolute filesystem path to a browser-relative URL. The resolution order is:
|
|
54
|
-
|
|
55
|
-
1. **Symlink registry** — Looks up the path in the startup-built registry. If the path (or its `fs.realpath` equivalent) is within a registered symlink target, the corresponding `/node_modules/<pkg>` URL prefix is returned.
|
|
56
|
-
2. **node_modules path** — If the normalized path contains `/node_modules/`, the substring from `/node_modules/` onward is used as the URL.
|
|
57
|
-
3. **Workspace path** — If the path is inside the workspace root, the relative path from the workspace root is used.
|
|
58
|
-
4. **App root path** — If the path is inside the app root, the relative path from the app root is used.
|
|
59
|
-
5. **Framework monorepo packages** — If the path is inside the co-located framework monorepo's `packages/` directory, it is served under `/swiss-packages/`.
|
|
60
|
-
|
|
61
|
-
---
|
|
62
|
-
|
|
63
|
-
## CDN fallback
|
|
64
|
-
|
|
65
|
-
`shouldUseCdnFallback` (`src/resolution/cdn/cdn-fallback.ts`) determines whether a package may be redirected to jsDelivr when not found locally:
|
|
66
|
-
|
|
67
|
-
- Unscoped packages (e.g. `react`, `reflect-metadata`) always allow CDN fallback.
|
|
68
|
-
- Scoped packages (e.g. `@swissjs/core`) do not allow CDN fallback by default, since they may be private.
|
|
69
|
-
- Scoped packages can be opted in by setting `SWITE_CDN_FALLBACK_SCOPES` to a comma-separated list of scopes (e.g. `@types,@tanstack`).
|
|
70
|
-
|
|
71
|
-
CDN fallback URLs use the form `https://cdn.jsdelivr.net/npm/{pkg}/+esm`, which serves an ESM build.
|
|
72
|
-
|
|
73
|
-
---
|
|
74
|
-
|
|
75
|
-
## Import map
|
|
76
|
-
|
|
77
|
-
At dev server startup, `setupMiddleware` attempts to load `.swite/import-map.json`. If it exists, `ModuleResolver.setImportMap()` is called and the import map becomes the fast path for all bare import resolutions.
|
|
78
|
-
|
|
79
|
-
The import map is generated by `generateImportMap` (`src/internal/generate-import-map.ts`), which:
|
|
80
|
-
|
|
81
|
-
1. Uses `PackageRegistry` to discover all workspace packages.
|
|
82
|
-
2. Calls `resolver.resolve(pkg.name, "")` for each package.
|
|
83
|
-
3. Also resolves common subpaths: `/components`, `/tokens`, `/context`, `/shell`, `/jsx-runtime`, `/jsx-dev-runtime`.
|
|
84
|
-
4. Saves the result as `{ version, generated, imports: { "@pkg/name": "/url" } }`.
|
|
85
|
-
|
|
86
|
-
The import map is also injected into `public/index.html` by the SPA fallback handler. If HTML already contains a `<script type="importmap">`, SWITE merges the generated entries in, with existing HTML entries taking priority.
|
|
87
|
-
|
|
88
|
-
---
|
|
89
|
-
|
|
90
|
-
## Path fixup
|
|
91
|
-
|
|
92
|
-
`fixSwissLibPaths` (`src/resolution/path/path-fixup.ts`) is applied to every compiled output before import rewriting. It rewrites `/swiss-lib/packages/` to `/swiss-packages/` and `/swiss-lib/` to `/swiss-packages/`. This corrects paths emitted by older versions of `@swissjs/compiler` that referenced a previous directory structure.
|
|
1
|
+
<!--
|
|
2
|
+
Copyright (c) 2024 Themba Mzumara
|
|
3
|
+
SWITE - SWISS Development Server
|
|
4
|
+
Licensed under the MIT License.
|
|
5
|
+
-->
|
|
6
|
+
|
|
7
|
+
# Import Resolution
|
|
8
|
+
|
|
9
|
+
SWITE resolves every bare module specifier to a browser-accessible URL before serving compiled source. This page describes the full resolution pipeline.
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
## ModuleResolver
|
|
14
|
+
|
|
15
|
+
`ModuleResolver` (`src/resolution/resolver.ts`) is the central resolution object. Each `SwiteServer` creates one instance per app root. It is injected into all file handlers and the import rewriter.
|
|
16
|
+
|
|
17
|
+
`ModuleResolver.resolve(specifier, importer)` returns a URL string. Specifiers fall into four categories:
|
|
18
|
+
|
|
19
|
+
1. **Import map hit** — If a pre-generated `.swite/import-map.json` is loaded and the specifier appears in its `imports` map, that URL is returned immediately without further resolution.
|
|
20
|
+
2. **Bare import** — Starts with a letter or `@`. Delegated to `resolveBareImport`.
|
|
21
|
+
3. **Absolute path** — Starts with `/`. Returned as-is.
|
|
22
|
+
4. **Relative import** — Starts with `.`. Resolved relative to the importer's directory on disk, then converted to a URL via `toUrl`.
|
|
23
|
+
|
|
24
|
+
### Variable reference detection
|
|
25
|
+
|
|
26
|
+
Before delegating to `resolveBareImport`, the resolver checks whether the specifier looks like a variable reference rather than a module path. Specifiers containing a `.` but not starting with `@` (e.g. `def.componentUrl`) are returned unchanged with a warning. Specifiers that do not match the pattern `^[@a-zA-Z][a-zA-Z0-9_/@-]*(\.(js|ts|ui|uix|mjs|cjs|jsx|tsx))?$` are also returned unchanged.
|
|
27
|
+
|
|
28
|
+
---
|
|
29
|
+
|
|
30
|
+
## Bare import resolution
|
|
31
|
+
|
|
32
|
+
`resolveBareImport` (`src/resolution/bare-import-resolver.ts`) resolves a bare specifier by searching for the package's `package.json` in multiple `node_modules` locations:
|
|
33
|
+
|
|
34
|
+
1. `<appRoot>/node_modules/<pkg>`
|
|
35
|
+
2. `<parentOfAppRoot>/node_modules/<pkg>`
|
|
36
|
+
3. `<workspaceRoot>/node_modules/<pkg>`
|
|
37
|
+
4. `<frameworkMonorepo>/node_modules/<pkg>`
|
|
38
|
+
|
|
39
|
+
If a `package.json` is found, the resolver reads the `exports` field (preferring the `import` condition), or falls back to `module` then `main`. In development, if the resolved path is inside a `dist/` directory, the resolver attempts to substitute `src/` and `.ts` extension and serves from source.
|
|
40
|
+
|
|
41
|
+
---
|
|
42
|
+
|
|
43
|
+
## Workspace package resolution
|
|
44
|
+
|
|
45
|
+
When a package is not found in any `node_modules`, `resolveWorkspacePackage` (`src/resolution/workspace-package-resolver.ts`) is called. It uses `PackageRegistry` (a process-wide singleton) to scan the workspace for `package.json` files. The registry recursively scans up to 15 directory levels deep, skipping `node_modules`, `dist`, `.git`, and `.swite`.
|
|
46
|
+
|
|
47
|
+
If the package is still not found, the registry calls `rescan()` in case the package was added after the initial scan.
|
|
48
|
+
|
|
49
|
+
---
|
|
50
|
+
|
|
51
|
+
## toUrl: converting filesystem paths to browser URLs
|
|
52
|
+
|
|
53
|
+
`toUrl` (`src/resolution/url-resolver.ts`) converts an absolute filesystem path to a browser-relative URL. The resolution order is:
|
|
54
|
+
|
|
55
|
+
1. **Symlink registry** — Looks up the path in the startup-built registry. If the path (or its `fs.realpath` equivalent) is within a registered symlink target, the corresponding `/node_modules/<pkg>` URL prefix is returned.
|
|
56
|
+
2. **node_modules path** — If the normalized path contains `/node_modules/`, the substring from `/node_modules/` onward is used as the URL.
|
|
57
|
+
3. **Workspace path** — If the path is inside the workspace root, the relative path from the workspace root is used.
|
|
58
|
+
4. **App root path** — If the path is inside the app root, the relative path from the app root is used.
|
|
59
|
+
5. **Framework monorepo packages** — If the path is inside the co-located framework monorepo's `packages/` directory, it is served under `/swiss-packages/`.
|
|
60
|
+
|
|
61
|
+
---
|
|
62
|
+
|
|
63
|
+
## CDN fallback
|
|
64
|
+
|
|
65
|
+
`shouldUseCdnFallback` (`src/resolution/cdn/cdn-fallback.ts`) determines whether a package may be redirected to jsDelivr when not found locally:
|
|
66
|
+
|
|
67
|
+
- Unscoped packages (e.g. `react`, `reflect-metadata`) always allow CDN fallback.
|
|
68
|
+
- Scoped packages (e.g. `@swissjs/core`) do not allow CDN fallback by default, since they may be private.
|
|
69
|
+
- Scoped packages can be opted in by setting `SWITE_CDN_FALLBACK_SCOPES` to a comma-separated list of scopes (e.g. `@types,@tanstack`).
|
|
70
|
+
|
|
71
|
+
CDN fallback URLs use the form `https://cdn.jsdelivr.net/npm/{pkg}/+esm`, which serves an ESM build.
|
|
72
|
+
|
|
73
|
+
---
|
|
74
|
+
|
|
75
|
+
## Import map
|
|
76
|
+
|
|
77
|
+
At dev server startup, `setupMiddleware` attempts to load `.swite/import-map.json`. If it exists, `ModuleResolver.setImportMap()` is called and the import map becomes the fast path for all bare import resolutions.
|
|
78
|
+
|
|
79
|
+
The import map is generated by `generateImportMap` (`src/internal/generate-import-map.ts`), which:
|
|
80
|
+
|
|
81
|
+
1. Uses `PackageRegistry` to discover all workspace packages.
|
|
82
|
+
2. Calls `resolver.resolve(pkg.name, "")` for each package.
|
|
83
|
+
3. Also resolves common subpaths: `/components`, `/tokens`, `/context`, `/shell`, `/jsx-runtime`, `/jsx-dev-runtime`.
|
|
84
|
+
4. Saves the result as `{ version, generated, imports: { "@pkg/name": "/url" } }`.
|
|
85
|
+
|
|
86
|
+
The import map is also injected into `public/index.html` by the SPA fallback handler. If HTML already contains a `<script type="importmap">`, SWITE merges the generated entries in, with existing HTML entries taking priority.
|
|
87
|
+
|
|
88
|
+
---
|
|
89
|
+
|
|
90
|
+
## Path fixup
|
|
91
|
+
|
|
92
|
+
`fixSwissLibPaths` (`src/resolution/path/path-fixup.ts`) is applied to every compiled output before import rewriting. It rewrites `/swiss-lib/packages/` to `/swiss-packages/` and `/swiss-lib/` to `/swiss-packages/`. This corrects paths emitted by older versions of `@swissjs/compiler` that referenced a previous directory structure.
|
package/docs/cli/build.md
CHANGED
|
@@ -1,78 +1,78 @@
|
|
|
1
|
-
<!--
|
|
2
|
-
Copyright (c) 2024 Themba Mzumara
|
|
3
|
-
SWITE - SWISS Development Server
|
|
4
|
-
Licensed under the MIT License.
|
|
5
|
-
-->
|
|
6
|
-
|
|
7
|
-
# swite build
|
|
8
|
-
|
|
9
|
-
Compiles the application for production.
|
|
10
|
-
|
|
11
|
-
```bash
|
|
12
|
-
swite build
|
|
13
|
-
```
|
|
14
|
-
|
|
15
|
-
---
|
|
16
|
-
|
|
17
|
-
## What it does
|
|
18
|
-
|
|
19
|
-
`swite build` runs `SwiteBuilder` with a fixed configuration derived from the project root:
|
|
20
|
-
|
|
21
|
-
| Parameter | Value |
|
|
22
|
-
|-----------|-------|
|
|
23
|
-
| Entry point | `<root>/src/index.ui` |
|
|
24
|
-
| Output directory | `<root>/dist` |
|
|
25
|
-
| Format | ESM |
|
|
26
|
-
| Target | `es2020` |
|
|
27
|
-
| Minify | `true` |
|
|
28
|
-
| Sourcemap | `false` |
|
|
29
|
-
|
|
30
|
-
These defaults are not yet configurable via `swiss.config.ts`; the builder reads config only for future extensibility.
|
|
31
|
-
|
|
32
|
-
---
|
|
33
|
-
|
|
34
|
-
## Build pipeline
|
|
35
|
-
|
|
36
|
-
The build runs in three phases:
|
|
37
|
-
|
|
38
|
-
### Phase 1: Compile Swiss files
|
|
39
|
-
|
|
40
|
-
All `.ui` and `.uix` files in `src/` are passed through `@swissjs/compiler` (`UiCompiler.compileAsync`), which outputs TypeScript/JSX. The resulting code is written to a temporary `.swite-build/` directory as `.tsx` files. Plain `.ts` files are copied as-is, with `.ui`/`.uix` import references rewritten to `.tsx`. CSS files are copied verbatim.
|
|
41
|
-
|
|
42
|
-
Workspace dependencies declared as `workspace:*` in `package.json` are discovered and compiled into the same temp directory, preserving the workspace directory structure so esbuild can resolve cross-package imports.
|
|
43
|
-
|
|
44
|
-
### Phase 2: Bundle with esbuild
|
|
45
|
-
|
|
46
|
-
esbuild is invoked with `bundle: true`, targeting the compiled entry point in `.swite-build/`. Three custom plugins are active:
|
|
47
|
-
|
|
48
|
-
- `js-tsx-fallback` — redirects `.js` import resolutions to `.tsx` when the `.tsx` file exists in the temp directory (handles UiCompiler emitting `.js` references)
|
|
49
|
-
- `css-stub` — stubs all `.css` imports with `export {}` so they do not block the bundle
|
|
50
|
-
- `workspace-resolver` — resolves `@scope/pkg` imports to compiled files in `.swite-build/`, using `package.json` exports fields and falling back to `src/index.js`
|
|
51
|
-
|
|
52
|
-
Node built-in modules (`fs`, `path`, `os`, etc., including `node:` prefixed forms) are marked external.
|
|
53
|
-
|
|
54
|
-
### Phase 3: Copy public assets
|
|
55
|
-
|
|
56
|
-
The contents of `public/` are copied verbatim to `dist/`.
|
|
57
|
-
|
|
58
|
-
---
|
|
59
|
-
|
|
60
|
-
## Output
|
|
61
|
-
|
|
62
|
-
`dist/` contains the bundled JavaScript (one or more chunks for ESM splitting), copied public assets, and any CSS files the build chose to emit. The `.swite-build/` temporary directory is removed regardless of whether the build succeeded or failed.
|
|
63
|
-
|
|
64
|
-
Build stats (file count and sizes) are printed to stdout after a successful bundle.
|
|
65
|
-
|
|
66
|
-
---
|
|
67
|
-
|
|
68
|
-
## Workspace dependencies
|
|
69
|
-
|
|
70
|
-
The builder scans `package.json` `dependencies`, `devDependencies`, and `peerDependencies` for `workspace:*` entries and also scans source files for `@scope/pkg` import patterns to discover transitive workspace dependencies. For each discovered package, it looks in these directories under the workspace root (in order):
|
|
71
|
-
|
|
72
|
-
```
|
|
73
|
-
lib/<pkgName>
|
|
74
|
-
packages/<pkgName>
|
|
75
|
-
packages/runtime/<pkgName>
|
|
76
|
-
packages/plugins/<pkgName>
|
|
77
|
-
packages/domain/<pkgName>
|
|
78
|
-
```
|
|
1
|
+
<!--
|
|
2
|
+
Copyright (c) 2024 Themba Mzumara
|
|
3
|
+
SWITE - SWISS Development Server
|
|
4
|
+
Licensed under the MIT License.
|
|
5
|
+
-->
|
|
6
|
+
|
|
7
|
+
# swite build
|
|
8
|
+
|
|
9
|
+
Compiles the application for production.
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
swite build
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
## What it does
|
|
18
|
+
|
|
19
|
+
`swite build` runs `SwiteBuilder` with a fixed configuration derived from the project root:
|
|
20
|
+
|
|
21
|
+
| Parameter | Value |
|
|
22
|
+
|-----------|-------|
|
|
23
|
+
| Entry point | `<root>/src/index.ui` |
|
|
24
|
+
| Output directory | `<root>/dist` |
|
|
25
|
+
| Format | ESM |
|
|
26
|
+
| Target | `es2020` |
|
|
27
|
+
| Minify | `true` |
|
|
28
|
+
| Sourcemap | `false` |
|
|
29
|
+
|
|
30
|
+
These defaults are not yet configurable via `swiss.config.ts`; the builder reads config only for future extensibility.
|
|
31
|
+
|
|
32
|
+
---
|
|
33
|
+
|
|
34
|
+
## Build pipeline
|
|
35
|
+
|
|
36
|
+
The build runs in three phases:
|
|
37
|
+
|
|
38
|
+
### Phase 1: Compile Swiss files
|
|
39
|
+
|
|
40
|
+
All `.ui` and `.uix` files in `src/` are passed through `@swissjs/compiler` (`UiCompiler.compileAsync`), which outputs TypeScript/JSX. The resulting code is written to a temporary `.swite-build/` directory as `.tsx` files. Plain `.ts` files are copied as-is, with `.ui`/`.uix` import references rewritten to `.tsx`. CSS files are copied verbatim.
|
|
41
|
+
|
|
42
|
+
Workspace dependencies declared as `workspace:*` in `package.json` are discovered and compiled into the same temp directory, preserving the workspace directory structure so esbuild can resolve cross-package imports.
|
|
43
|
+
|
|
44
|
+
### Phase 2: Bundle with esbuild
|
|
45
|
+
|
|
46
|
+
esbuild is invoked with `bundle: true`, targeting the compiled entry point in `.swite-build/`. Three custom plugins are active:
|
|
47
|
+
|
|
48
|
+
- `js-tsx-fallback` — redirects `.js` import resolutions to `.tsx` when the `.tsx` file exists in the temp directory (handles UiCompiler emitting `.js` references)
|
|
49
|
+
- `css-stub` — stubs all `.css` imports with `export {}` so they do not block the bundle
|
|
50
|
+
- `workspace-resolver` — resolves `@scope/pkg` imports to compiled files in `.swite-build/`, using `package.json` exports fields and falling back to `src/index.js`
|
|
51
|
+
|
|
52
|
+
Node built-in modules (`fs`, `path`, `os`, etc., including `node:` prefixed forms) are marked external.
|
|
53
|
+
|
|
54
|
+
### Phase 3: Copy public assets
|
|
55
|
+
|
|
56
|
+
The contents of `public/` are copied verbatim to `dist/`.
|
|
57
|
+
|
|
58
|
+
---
|
|
59
|
+
|
|
60
|
+
## Output
|
|
61
|
+
|
|
62
|
+
`dist/` contains the bundled JavaScript (one or more chunks for ESM splitting), copied public assets, and any CSS files the build chose to emit. The `.swite-build/` temporary directory is removed regardless of whether the build succeeded or failed.
|
|
63
|
+
|
|
64
|
+
Build stats (file count and sizes) are printed to stdout after a successful bundle.
|
|
65
|
+
|
|
66
|
+
---
|
|
67
|
+
|
|
68
|
+
## Workspace dependencies
|
|
69
|
+
|
|
70
|
+
The builder scans `package.json` `dependencies`, `devDependencies`, and `peerDependencies` for `workspace:*` entries and also scans source files for `@scope/pkg` import patterns to discover transitive workspace dependencies. For each discovered package, it looks in these directories under the workspace root (in order):
|
|
71
|
+
|
|
72
|
+
```
|
|
73
|
+
lib/<pkgName>
|
|
74
|
+
packages/<pkgName>
|
|
75
|
+
packages/runtime/<pkgName>
|
|
76
|
+
packages/plugins/<pkgName>
|
|
77
|
+
packages/domain/<pkgName>
|
|
78
|
+
```
|
package/docs/cli/dev.md
CHANGED
|
@@ -1,90 +1,90 @@
|
|
|
1
|
-
<!--
|
|
2
|
-
Copyright (c) 2024 Themba Mzumara
|
|
3
|
-
SWITE - SWISS Development Server
|
|
4
|
-
Licensed under the MIT License.
|
|
5
|
-
-->
|
|
6
|
-
|
|
7
|
-
# swite dev
|
|
8
|
-
|
|
9
|
-
Starts the SWITE development server.
|
|
10
|
-
|
|
11
|
-
```bash
|
|
12
|
-
swite dev
|
|
13
|
-
```
|
|
14
|
-
|
|
15
|
-
---
|
|
16
|
-
|
|
17
|
-
## What it does
|
|
18
|
-
|
|
19
|
-
1. Loads `swiss.config.ts` from the project root via esbuild transpilation.
|
|
20
|
-
2. If `services.python.autoStart` is `true` in the config, spawns the Python process at `python3 {entry}` (or `python` on Windows), passes `PORT={port}` plus any `env` fields, and polls the health endpoint until it responds 200 or the 15-second timeout is reached.
|
|
21
|
-
3. Registers `SIGINT` and `process.exit` handlers to send `SIGTERM` to the Python process when Node shuts down.
|
|
22
|
-
4. Starts `SwiteServer` on the configured port and host, then begins the HMR WebSocket server on port 24678 (or the next available port).
|
|
23
|
-
|
|
24
|
-
---
|
|
25
|
-
|
|
26
|
-
## Python autostart
|
|
27
|
-
|
|
28
|
-
When `autoStart: true` is set, the Python process is started before the Node HTTP server. SWITE streams Python stdout prefixed with `[python]` (cyan) and stderr prefixed with `[python]` (yellow). If the health check times out, SWITE kills the Python process and exits with an error.
|
|
29
|
-
|
|
30
|
-
```typescript
|
|
31
|
-
// swiss.config.ts
|
|
32
|
-
export default defineConfig({
|
|
33
|
-
services: {
|
|
34
|
-
python: {
|
|
35
|
-
entry: 'server/main.py',
|
|
36
|
-
port: 8000,
|
|
37
|
-
autoStart: true,
|
|
38
|
-
healthCheck: '/health',
|
|
39
|
-
env: { ENVIRONMENT: 'development' },
|
|
40
|
-
},
|
|
41
|
-
},
|
|
42
|
-
});
|
|
43
|
-
```
|
|
44
|
-
|
|
45
|
-
---
|
|
46
|
-
|
|
47
|
-
## HMR
|
|
48
|
-
|
|
49
|
-
The HMR WebSocket server starts on port 24678. If that port is occupied, SWITE probes the OS for the next free port. The actual port is embedded into the HMR client script served at `/__swite_hmr_client`.
|
|
50
|
-
|
|
51
|
-
Every `.ui`, `.uix`, `.ts`, and `.js` file under the project root is watched by chokidar (excluding `node_modules/`, `.git/`, and `dist/`). When a file changes, SWITE broadcasts a JSON message to all connected WebSocket clients:
|
|
52
|
-
|
|
53
|
-
```json
|
|
54
|
-
{ "type": "update", "path": "/abs/path/to/file", "updateType": "hot|reload|style", "timestamp": 1234567890 }
|
|
55
|
-
```
|
|
56
|
-
|
|
57
|
-
Update type classification:
|
|
58
|
-
|
|
59
|
-
| Update type | Condition |
|
|
60
|
-
|-------------|-----------|
|
|
61
|
-
| `style` | `.css`, `.scss`, or `.sass` file |
|
|
62
|
-
| `hot` | `.js` or `.ts` file under `components/` or `pages/` |
|
|
63
|
-
| `reload` | everything else |
|
|
64
|
-
|
|
65
|
-
The browser client handles `style` by cache-busting stylesheet `href` attributes. It handles `hot` by re-importing the module and calling `instance.update()` on registered instances. Everything else triggers `window.location.reload()`.
|
|
66
|
-
|
|
67
|
-
---
|
|
68
|
-
|
|
69
|
-
## Development headers
|
|
70
|
-
|
|
71
|
-
All compiled source files are served with aggressive no-cache headers:
|
|
72
|
-
|
|
73
|
-
```
|
|
74
|
-
Cache-Control: no-store, no-cache, must-revalidate, proxy-revalidate
|
|
75
|
-
Pragma: no-cache
|
|
76
|
-
Expires: 0
|
|
77
|
-
```
|
|
78
|
-
|
|
79
|
-
This prevents browsers from serving stale compiled output between reloads.
|
|
80
|
-
|
|
81
|
-
---
|
|
82
|
-
|
|
83
|
-
## Diagnostic endpoints
|
|
84
|
-
|
|
85
|
-
| Endpoint | Description |
|
|
86
|
-
|----------|-------------|
|
|
87
|
-
| `/__swite_hmr_client` | HMR client JavaScript (injected by index.html) |
|
|
88
|
-
| `/__swite_routes` | JSON array of route definitions from the file router |
|
|
89
|
-
| `/__swite_diagnose?url=<path>` | Fetches the given path and reports bare imports found in the response |
|
|
90
|
-
| `/__swite_clear_cache` | HTML page that clears browser caches and service workers, then redirects to `/` |
|
|
1
|
+
<!--
|
|
2
|
+
Copyright (c) 2024 Themba Mzumara
|
|
3
|
+
SWITE - SWISS Development Server
|
|
4
|
+
Licensed under the MIT License.
|
|
5
|
+
-->
|
|
6
|
+
|
|
7
|
+
# swite dev
|
|
8
|
+
|
|
9
|
+
Starts the SWITE development server.
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
swite dev
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
## What it does
|
|
18
|
+
|
|
19
|
+
1. Loads `swiss.config.ts` from the project root via esbuild transpilation.
|
|
20
|
+
2. If `services.python.autoStart` is `true` in the config, spawns the Python process at `python3 {entry}` (or `python` on Windows), passes `PORT={port}` plus any `env` fields, and polls the health endpoint until it responds 200 or the 15-second timeout is reached.
|
|
21
|
+
3. Registers `SIGINT` and `process.exit` handlers to send `SIGTERM` to the Python process when Node shuts down.
|
|
22
|
+
4. Starts `SwiteServer` on the configured port and host, then begins the HMR WebSocket server on port 24678 (or the next available port).
|
|
23
|
+
|
|
24
|
+
---
|
|
25
|
+
|
|
26
|
+
## Python autostart
|
|
27
|
+
|
|
28
|
+
When `autoStart: true` is set, the Python process is started before the Node HTTP server. SWITE streams Python stdout prefixed with `[python]` (cyan) and stderr prefixed with `[python]` (yellow). If the health check times out, SWITE kills the Python process and exits with an error.
|
|
29
|
+
|
|
30
|
+
```typescript
|
|
31
|
+
// swiss.config.ts
|
|
32
|
+
export default defineConfig({
|
|
33
|
+
services: {
|
|
34
|
+
python: {
|
|
35
|
+
entry: 'server/main.py',
|
|
36
|
+
port: 8000,
|
|
37
|
+
autoStart: true,
|
|
38
|
+
healthCheck: '/health',
|
|
39
|
+
env: { ENVIRONMENT: 'development' },
|
|
40
|
+
},
|
|
41
|
+
},
|
|
42
|
+
});
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
---
|
|
46
|
+
|
|
47
|
+
## HMR
|
|
48
|
+
|
|
49
|
+
The HMR WebSocket server starts on port 24678. If that port is occupied, SWITE probes the OS for the next free port. The actual port is embedded into the HMR client script served at `/__swite_hmr_client`.
|
|
50
|
+
|
|
51
|
+
Every `.ui`, `.uix`, `.ts`, and `.js` file under the project root is watched by chokidar (excluding `node_modules/`, `.git/`, and `dist/`). When a file changes, SWITE broadcasts a JSON message to all connected WebSocket clients:
|
|
52
|
+
|
|
53
|
+
```json
|
|
54
|
+
{ "type": "update", "path": "/abs/path/to/file", "updateType": "hot|reload|style", "timestamp": 1234567890 }
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
Update type classification:
|
|
58
|
+
|
|
59
|
+
| Update type | Condition |
|
|
60
|
+
|-------------|-----------|
|
|
61
|
+
| `style` | `.css`, `.scss`, or `.sass` file |
|
|
62
|
+
| `hot` | `.js` or `.ts` file under `components/` or `pages/` |
|
|
63
|
+
| `reload` | everything else |
|
|
64
|
+
|
|
65
|
+
The browser client handles `style` by cache-busting stylesheet `href` attributes. It handles `hot` by re-importing the module and calling `instance.update()` on registered instances. Everything else triggers `window.location.reload()`.
|
|
66
|
+
|
|
67
|
+
---
|
|
68
|
+
|
|
69
|
+
## Development headers
|
|
70
|
+
|
|
71
|
+
All compiled source files are served with aggressive no-cache headers:
|
|
72
|
+
|
|
73
|
+
```
|
|
74
|
+
Cache-Control: no-store, no-cache, must-revalidate, proxy-revalidate
|
|
75
|
+
Pragma: no-cache
|
|
76
|
+
Expires: 0
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
This prevents browsers from serving stale compiled output between reloads.
|
|
80
|
+
|
|
81
|
+
---
|
|
82
|
+
|
|
83
|
+
## Diagnostic endpoints
|
|
84
|
+
|
|
85
|
+
| Endpoint | Description |
|
|
86
|
+
|----------|-------------|
|
|
87
|
+
| `/__swite_hmr_client` | HMR client JavaScript (injected by index.html) |
|
|
88
|
+
| `/__swite_routes` | JSON array of route definitions from the file router |
|
|
89
|
+
| `/__swite_diagnose?url=<path>` | Fetches the given path and reports bare imports found in the response |
|
|
90
|
+
| `/__swite_clear_cache` | HTML page that clears browser caches and service workers, then redirects to `/` |
|
package/docs/cli/index.md
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
|
-
<!--
|
|
2
|
-
Copyright (c) 2024 Themba Mzumara
|
|
3
|
-
SWITE - SWISS Development Server
|
|
4
|
-
Licensed under the MIT License.
|
|
5
|
-
-->
|
|
6
|
-
|
|
7
|
-
# CLI
|
|
8
|
-
|
|
9
|
-
SWITE is invoked as `swite <command>`. There are three commands:
|
|
10
|
-
|
|
11
|
-
- [swite dev](./dev.md) — Start the development server with HMR and optional Python autostart
|
|
12
|
-
- [swite build](./build.md) — Build the application for production using esbuild
|
|
13
|
-
- [swite start](./start.md) — Start the development server in production mode (no HMR, no Python autostart)
|
|
14
|
-
|
|
15
|
-
All three commands read `swiss.config.ts` (or `swiss.config.js`) from the current working directory before doing anything else. The current working directory is the project root; SWITE does not accept a path argument.
|
|
1
|
+
<!--
|
|
2
|
+
Copyright (c) 2024 Themba Mzumara
|
|
3
|
+
SWITE - SWISS Development Server
|
|
4
|
+
Licensed under the MIT License.
|
|
5
|
+
-->
|
|
6
|
+
|
|
7
|
+
# CLI
|
|
8
|
+
|
|
9
|
+
SWITE is invoked as `swite <command>`. There are three commands:
|
|
10
|
+
|
|
11
|
+
- [swite dev](./dev.md) — Start the development server with HMR and optional Python autostart
|
|
12
|
+
- [swite build](./build.md) — Build the application for production using esbuild
|
|
13
|
+
- [swite start](./start.md) — Start the development server in production mode (no HMR, no Python autostart)
|
|
14
|
+
|
|
15
|
+
All three commands read `swiss.config.ts` (or `swiss.config.js`) from the current working directory before doing anything else. The current working directory is the project root; SWITE does not accept a path argument.
|
package/docs/cli/start.md
CHANGED
|
@@ -1,45 +1,45 @@
|
|
|
1
|
-
<!--
|
|
2
|
-
Copyright (c) 2024 Themba Mzumara
|
|
3
|
-
SWITE - SWISS Development Server
|
|
4
|
-
Licensed under the MIT License.
|
|
5
|
-
-->
|
|
6
|
-
|
|
7
|
-
# swite start
|
|
8
|
-
|
|
9
|
-
Starts the SWITE server in production mode.
|
|
10
|
-
|
|
11
|
-
```bash
|
|
12
|
-
swite start
|
|
13
|
-
```
|
|
14
|
-
|
|
15
|
-
---
|
|
16
|
-
|
|
17
|
-
## What it does
|
|
18
|
-
|
|
19
|
-
`swite start` starts the same `SwiteServer` as `swite dev` but with one critical difference: it calls `setProductionMode()` before the server starts. Production mode changes how `proxyToPython` resolves the Python service URL:
|
|
20
|
-
|
|
21
|
-
- **Dev mode**: if `PYTHON_SERVICE_URL` is not set, `proxyToPython` falls back to `http://localhost:{python.port}` from the config.
|
|
22
|
-
- **Production mode**: if `PYTHON_SERVICE_URL` is not set, `proxyToPython` throws immediately with an error rather than attempting a localhost connection.
|
|
23
|
-
|
|
24
|
-
`swite start` does not spawn the Python process. It does not start HMR. The HMR WebSocket server is still initialized (as part of `SwiteServer`), but since no file watcher is started and the client script is still served, the client will connect and wait — which is harmless in a production container.
|
|
25
|
-
|
|
26
|
-
---
|
|
27
|
-
|
|
28
|
-
## PYTHON_SERVICE_URL warning
|
|
29
|
-
|
|
30
|
-
If `services.python` is configured in `swiss.config.ts` and `PYTHON_SERVICE_URL` is not set in the environment at startup, SWITE prints a warning:
|
|
31
|
-
|
|
32
|
-
```
|
|
33
|
-
[swite] WARNING: services.python is configured but PYTHON_SERVICE_URL is not set.
|
|
34
|
-
Proxy calls to Python will fail. Set PYTHON_SERVICE_URL to the running service URL.
|
|
35
|
-
```
|
|
36
|
-
|
|
37
|
-
The server starts regardless. The warning is there because proxy calls that happen before `PYTHON_SERVICE_URL` is set will throw at request time.
|
|
38
|
-
|
|
39
|
-
---
|
|
40
|
-
|
|
41
|
-
## Production deployment notes
|
|
42
|
-
|
|
43
|
-
`swite start` serves source files through the same compilation and import-rewriting middleware as `swite dev`. It is not a static file server for pre-built output. If you want to serve the output of `swite build`, use a regular static HTTP server pointed at `dist/`.
|
|
44
|
-
|
|
45
|
-
`swite start` is intended for fullstack deployments where the Node server handles server-side logic and proxies to a separately deployed Python service, while continuing to serve the SwissJS frontend from source.
|
|
1
|
+
<!--
|
|
2
|
+
Copyright (c) 2024 Themba Mzumara
|
|
3
|
+
SWITE - SWISS Development Server
|
|
4
|
+
Licensed under the MIT License.
|
|
5
|
+
-->
|
|
6
|
+
|
|
7
|
+
# swite start
|
|
8
|
+
|
|
9
|
+
Starts the SWITE server in production mode.
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
swite start
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
## What it does
|
|
18
|
+
|
|
19
|
+
`swite start` starts the same `SwiteServer` as `swite dev` but with one critical difference: it calls `setProductionMode()` before the server starts. Production mode changes how `proxyToPython` resolves the Python service URL:
|
|
20
|
+
|
|
21
|
+
- **Dev mode**: if `PYTHON_SERVICE_URL` is not set, `proxyToPython` falls back to `http://localhost:{python.port}` from the config.
|
|
22
|
+
- **Production mode**: if `PYTHON_SERVICE_URL` is not set, `proxyToPython` throws immediately with an error rather than attempting a localhost connection.
|
|
23
|
+
|
|
24
|
+
`swite start` does not spawn the Python process. It does not start HMR. The HMR WebSocket server is still initialized (as part of `SwiteServer`), but since no file watcher is started and the client script is still served, the client will connect and wait — which is harmless in a production container.
|
|
25
|
+
|
|
26
|
+
---
|
|
27
|
+
|
|
28
|
+
## PYTHON_SERVICE_URL warning
|
|
29
|
+
|
|
30
|
+
If `services.python` is configured in `swiss.config.ts` and `PYTHON_SERVICE_URL` is not set in the environment at startup, SWITE prints a warning:
|
|
31
|
+
|
|
32
|
+
```
|
|
33
|
+
[swite] WARNING: services.python is configured but PYTHON_SERVICE_URL is not set.
|
|
34
|
+
Proxy calls to Python will fail. Set PYTHON_SERVICE_URL to the running service URL.
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
The server starts regardless. The warning is there because proxy calls that happen before `PYTHON_SERVICE_URL` is set will throw at request time.
|
|
38
|
+
|
|
39
|
+
---
|
|
40
|
+
|
|
41
|
+
## Production deployment notes
|
|
42
|
+
|
|
43
|
+
`swite start` serves source files through the same compilation and import-rewriting middleware as `swite dev`. It is not a static file server for pre-built output. If you want to serve the output of `swite build`, use a regular static HTTP server pointed at `dist/`.
|
|
44
|
+
|
|
45
|
+
`swite start` is intended for fullstack deployments where the Node server handles server-side logic and proxies to a separately deployed Python service, while continuing to serve the SwissJS frontend from source.
|