@matthesketh/utopia-vite-plugin 0.0.1
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/LICENSE +21 -0
- package/README.md +61 -0
- package/dist/index.cjs +282 -0
- package/dist/index.d.cts +61 -0
- package/dist/index.d.ts +61 -0
- package/dist/index.js +247 -0
- package/package.json +54 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Matt Hesketh
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# @matthesketh/utopia-vite-plugin
|
|
2
|
+
|
|
3
|
+
Vite plugin for UtopiaJS. Transforms `.utopia` single-file components, extracts and injects CSS through Vite's virtual module pipeline, provides granular HMR support (style-only hot updates), and handles the SSR runtime swap.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pnpm add -D @matthesketh/utopia-vite-plugin
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
Requires `vite` ^6.0.0 as a peer dependency.
|
|
12
|
+
|
|
13
|
+
## Usage
|
|
14
|
+
|
|
15
|
+
```ts
|
|
16
|
+
// vite.config.ts
|
|
17
|
+
import { defineConfig } from '@matthesketh/utopia-vite-plugin';
|
|
18
|
+
|
|
19
|
+
export default defineConfig();
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
Or with the plugin directly:
|
|
23
|
+
|
|
24
|
+
```ts
|
|
25
|
+
// vite.config.ts
|
|
26
|
+
import { defineConfig } from 'vite';
|
|
27
|
+
import utopia from '@matthesketh/utopia-vite-plugin';
|
|
28
|
+
|
|
29
|
+
export default defineConfig({
|
|
30
|
+
plugins: [utopia()],
|
|
31
|
+
});
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## API
|
|
35
|
+
|
|
36
|
+
| Export | Description |
|
|
37
|
+
|--------|-------------|
|
|
38
|
+
| `default (utopiaPlugin)` | The Vite plugin factory. Accepts `UtopiaPluginOptions`. |
|
|
39
|
+
| `defineConfig(userConfig?)` | Create a Vite config pre-configured for UtopiaJS (plugin, extensions, SSR settings). |
|
|
40
|
+
|
|
41
|
+
**UtopiaPluginOptions:**
|
|
42
|
+
|
|
43
|
+
| Option | Type | Default | Description |
|
|
44
|
+
|--------|------|---------|-------------|
|
|
45
|
+
| `include` | `FilterPattern` | `'**/*.utopia'` | Glob patterns to include |
|
|
46
|
+
| `exclude` | `FilterPattern` | -- | Glob patterns to exclude |
|
|
47
|
+
| `sourceMap` | `boolean` | `true` (dev) | Generate source maps |
|
|
48
|
+
|
|
49
|
+
**Features:**
|
|
50
|
+
|
|
51
|
+
- Compiles `.utopia` files via `@matthesketh/utopia-compiler`
|
|
52
|
+
- Extracts CSS to virtual modules processed by Vite's CSS pipeline
|
|
53
|
+
- Granular HMR: style-only changes skip component re-render
|
|
54
|
+
- SSR alias: swaps `@matthesketh/utopia-runtime` to `@matthesketh/utopia-server/ssr-runtime` in SSR builds and dev SSR
|
|
55
|
+
- Resolves `.utopia` as a file extension for bare imports
|
|
56
|
+
|
|
57
|
+
See [docs/architecture.md](../../docs/architecture.md) and [docs/ssr.md](../../docs/ssr.md) for details.
|
|
58
|
+
|
|
59
|
+
## License
|
|
60
|
+
|
|
61
|
+
MIT
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,282 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __export = (target, all) => {
|
|
9
|
+
for (var name in all)
|
|
10
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
11
|
+
};
|
|
12
|
+
var __copyProps = (to, from, except, desc) => {
|
|
13
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
+
for (let key of __getOwnPropNames(from))
|
|
15
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
16
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
17
|
+
}
|
|
18
|
+
return to;
|
|
19
|
+
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
28
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
|
+
|
|
30
|
+
// src/index.ts
|
|
31
|
+
var index_exports = {};
|
|
32
|
+
__export(index_exports, {
|
|
33
|
+
default: () => utopiaPlugin,
|
|
34
|
+
defineConfig: () => defineConfig
|
|
35
|
+
});
|
|
36
|
+
module.exports = __toCommonJS(index_exports);
|
|
37
|
+
var import_utopia_compiler = require("@matthesketh/utopia-compiler");
|
|
38
|
+
var import_vite = require("vite");
|
|
39
|
+
var import_node_path = __toESM(require("path"), 1);
|
|
40
|
+
var UTOPIA_EXT = ".utopia";
|
|
41
|
+
var CSS_SUFFIX = ".css";
|
|
42
|
+
var VIRTUAL_PREFIX = "\0";
|
|
43
|
+
var cssCache = /* @__PURE__ */ new Map();
|
|
44
|
+
function toCssId(utopiaId) {
|
|
45
|
+
return utopiaId + CSS_SUFFIX;
|
|
46
|
+
}
|
|
47
|
+
function isVirtualCssId(id) {
|
|
48
|
+
return id.endsWith(UTOPIA_EXT + CSS_SUFFIX);
|
|
49
|
+
}
|
|
50
|
+
function stripVirtualPrefix(id) {
|
|
51
|
+
return id.startsWith(VIRTUAL_PREFIX) ? id.slice(VIRTUAL_PREFIX.length) : id;
|
|
52
|
+
}
|
|
53
|
+
function cssIdToUtopiaId(cssId) {
|
|
54
|
+
const raw = stripVirtualPrefix(cssId);
|
|
55
|
+
return raw.slice(0, -CSS_SUFFIX.length);
|
|
56
|
+
}
|
|
57
|
+
function utopiaPlugin(options = {}) {
|
|
58
|
+
const {
|
|
59
|
+
include = `**/*${UTOPIA_EXT}`,
|
|
60
|
+
exclude
|
|
61
|
+
} = options;
|
|
62
|
+
let filter;
|
|
63
|
+
let server;
|
|
64
|
+
const prevDescriptors = /* @__PURE__ */ new Map();
|
|
65
|
+
return {
|
|
66
|
+
name: "utopia",
|
|
67
|
+
/**
|
|
68
|
+
* Enforce the plugin to run before Vite's internal transforms so that
|
|
69
|
+
* `.utopia` files are compiled before any further processing.
|
|
70
|
+
*/
|
|
71
|
+
enforce: "pre",
|
|
72
|
+
// -------------------------------------------------------------------
|
|
73
|
+
// Config — SSR alias resolution
|
|
74
|
+
// -------------------------------------------------------------------
|
|
75
|
+
config(userConfig, env) {
|
|
76
|
+
if (env.isSsrBuild) {
|
|
77
|
+
return {
|
|
78
|
+
resolve: {
|
|
79
|
+
alias: {
|
|
80
|
+
"@matthesketh/utopia-runtime": "@matthesketh/utopia-server/ssr-runtime"
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
},
|
|
86
|
+
configResolved() {
|
|
87
|
+
filter = (0, import_vite.createFilter)(include, exclude);
|
|
88
|
+
},
|
|
89
|
+
// -------------------------------------------------------------------
|
|
90
|
+
// Dev server – store reference for HMR
|
|
91
|
+
// -------------------------------------------------------------------
|
|
92
|
+
configureServer(_server) {
|
|
93
|
+
server = _server;
|
|
94
|
+
},
|
|
95
|
+
// -------------------------------------------------------------------
|
|
96
|
+
// Resolve virtual CSS modules + SSR runtime alias
|
|
97
|
+
// -------------------------------------------------------------------
|
|
98
|
+
resolveId(id, importer, options2) {
|
|
99
|
+
if (options2?.ssr && id === "@matthesketh/utopia-runtime") {
|
|
100
|
+
return this.resolve("@matthesketh/utopia-server/ssr-runtime", importer, {
|
|
101
|
+
skipSelf: true,
|
|
102
|
+
...options2
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
if (isVirtualCssId(id)) {
|
|
106
|
+
if (import_node_path.default.isAbsolute(id)) {
|
|
107
|
+
return VIRTUAL_PREFIX + id;
|
|
108
|
+
}
|
|
109
|
+
if (importer) {
|
|
110
|
+
const dir = import_node_path.default.dirname(importer);
|
|
111
|
+
const resolved = import_node_path.default.resolve(dir, id);
|
|
112
|
+
return VIRTUAL_PREFIX + resolved;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
return void 0;
|
|
116
|
+
},
|
|
117
|
+
// -------------------------------------------------------------------
|
|
118
|
+
// Load virtual CSS modules
|
|
119
|
+
// -------------------------------------------------------------------
|
|
120
|
+
load(id) {
|
|
121
|
+
if (!id.startsWith(VIRTUAL_PREFIX)) return void 0;
|
|
122
|
+
const raw = stripVirtualPrefix(id);
|
|
123
|
+
if (isVirtualCssId(raw)) {
|
|
124
|
+
const utopiaId = cssIdToUtopiaId(raw);
|
|
125
|
+
const css = cssCache.get(utopiaId) ?? "";
|
|
126
|
+
return css;
|
|
127
|
+
}
|
|
128
|
+
return void 0;
|
|
129
|
+
},
|
|
130
|
+
// -------------------------------------------------------------------
|
|
131
|
+
// Transform .utopia files
|
|
132
|
+
// -------------------------------------------------------------------
|
|
133
|
+
transform(code, id) {
|
|
134
|
+
if (!id.endsWith(UTOPIA_EXT)) return void 0;
|
|
135
|
+
if (!filter(id)) return void 0;
|
|
136
|
+
const result = (0, import_utopia_compiler.compile)(code, {
|
|
137
|
+
filename: id
|
|
138
|
+
});
|
|
139
|
+
if (result.css) {
|
|
140
|
+
cssCache.set(id, result.css);
|
|
141
|
+
} else {
|
|
142
|
+
cssCache.delete(id);
|
|
143
|
+
}
|
|
144
|
+
try {
|
|
145
|
+
const descriptor = (0, import_utopia_compiler.parse)(code, id);
|
|
146
|
+
prevDescriptors.set(id, descriptor);
|
|
147
|
+
} catch {
|
|
148
|
+
}
|
|
149
|
+
let output = result.code;
|
|
150
|
+
if (result.css) {
|
|
151
|
+
const cssImportId = toCssId(id);
|
|
152
|
+
output += `
|
|
153
|
+
import ${JSON.stringify(cssImportId)};
|
|
154
|
+
`;
|
|
155
|
+
}
|
|
156
|
+
return {
|
|
157
|
+
code: output,
|
|
158
|
+
map: null
|
|
159
|
+
};
|
|
160
|
+
},
|
|
161
|
+
// -------------------------------------------------------------------
|
|
162
|
+
// HMR
|
|
163
|
+
// -------------------------------------------------------------------
|
|
164
|
+
handleHotUpdate(ctx) {
|
|
165
|
+
const { file, read, server: hmrServer, modules } = ctx;
|
|
166
|
+
if (!file.endsWith(UTOPIA_EXT)) return void 0;
|
|
167
|
+
return (async () => {
|
|
168
|
+
const source = await read();
|
|
169
|
+
let newDescriptor;
|
|
170
|
+
try {
|
|
171
|
+
newDescriptor = (0, import_utopia_compiler.parse)(source, file);
|
|
172
|
+
} catch {
|
|
173
|
+
return void 0;
|
|
174
|
+
}
|
|
175
|
+
const oldDescriptor = prevDescriptors.get(file);
|
|
176
|
+
prevDescriptors.set(file, newDescriptor);
|
|
177
|
+
const templateChanged = didBlockChange(
|
|
178
|
+
oldDescriptor?.template,
|
|
179
|
+
newDescriptor.template
|
|
180
|
+
);
|
|
181
|
+
const scriptChanged = didBlockChange(
|
|
182
|
+
oldDescriptor?.script,
|
|
183
|
+
newDescriptor.script
|
|
184
|
+
);
|
|
185
|
+
const styleChanged = didBlockChange(
|
|
186
|
+
oldDescriptor?.style,
|
|
187
|
+
newDescriptor.style
|
|
188
|
+
);
|
|
189
|
+
if (styleChanged && !templateChanged && !scriptChanged) {
|
|
190
|
+
const result = (0, import_utopia_compiler.compile)(source, {
|
|
191
|
+
filename: file
|
|
192
|
+
});
|
|
193
|
+
if (result.css) {
|
|
194
|
+
cssCache.set(file, result.css);
|
|
195
|
+
} else {
|
|
196
|
+
cssCache.delete(file);
|
|
197
|
+
}
|
|
198
|
+
const cssId = VIRTUAL_PREFIX + toCssId(file);
|
|
199
|
+
const cssModule = hmrServer.moduleGraph.getModuleById(cssId);
|
|
200
|
+
if (cssModule) {
|
|
201
|
+
hmrServer.moduleGraph.invalidateModule(cssModule);
|
|
202
|
+
return [cssModule];
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
const affectedModules = [];
|
|
206
|
+
for (const mod of modules) {
|
|
207
|
+
hmrServer.moduleGraph.invalidateModule(mod);
|
|
208
|
+
affectedModules.push(mod);
|
|
209
|
+
}
|
|
210
|
+
if (styleChanged) {
|
|
211
|
+
const result = (0, import_utopia_compiler.compile)(source, {
|
|
212
|
+
filename: file
|
|
213
|
+
});
|
|
214
|
+
if (result.css) {
|
|
215
|
+
cssCache.set(file, result.css);
|
|
216
|
+
} else {
|
|
217
|
+
cssCache.delete(file);
|
|
218
|
+
}
|
|
219
|
+
const cssId = VIRTUAL_PREFIX + toCssId(file);
|
|
220
|
+
const cssModule = hmrServer.moduleGraph.getModuleById(cssId);
|
|
221
|
+
if (cssModule) {
|
|
222
|
+
hmrServer.moduleGraph.invalidateModule(cssModule);
|
|
223
|
+
affectedModules.push(cssModule);
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
return affectedModules.length > 0 ? affectedModules : void 0;
|
|
227
|
+
})();
|
|
228
|
+
}
|
|
229
|
+
};
|
|
230
|
+
}
|
|
231
|
+
function didBlockChange(oldBlock, newBlock) {
|
|
232
|
+
if (!oldBlock && !newBlock) return false;
|
|
233
|
+
if (!oldBlock || !newBlock) return true;
|
|
234
|
+
return oldBlock.content !== newBlock.content;
|
|
235
|
+
}
|
|
236
|
+
function defineConfig(userConfig = {}) {
|
|
237
|
+
const {
|
|
238
|
+
plugins: userPlugins = [],
|
|
239
|
+
resolve: userResolve,
|
|
240
|
+
optimizeDeps: userOptimizeDeps,
|
|
241
|
+
...rest
|
|
242
|
+
} = userConfig;
|
|
243
|
+
const hasUtopiaPlugin = userPlugins.some(
|
|
244
|
+
(p) => p && typeof p === "object" && "name" in p && p.name === "utopia"
|
|
245
|
+
);
|
|
246
|
+
const plugins = hasUtopiaPlugin ? userPlugins : [utopiaPlugin(), ...userPlugins];
|
|
247
|
+
return {
|
|
248
|
+
...rest,
|
|
249
|
+
plugins,
|
|
250
|
+
resolve: {
|
|
251
|
+
...userResolve,
|
|
252
|
+
// Ensure `.utopia` is resolvable as an extension so bare imports work
|
|
253
|
+
// (e.g. `import App from './App'` resolves to `./App.utopia`).
|
|
254
|
+
extensions: mergeUnique(
|
|
255
|
+
userResolve?.extensions ?? [".mjs", ".js", ".mts", ".ts", ".jsx", ".tsx", ".json"],
|
|
256
|
+
[UTOPIA_EXT]
|
|
257
|
+
)
|
|
258
|
+
},
|
|
259
|
+
optimizeDeps: {
|
|
260
|
+
...userOptimizeDeps,
|
|
261
|
+
// Exclude UtopiaJS packages from Vite's dependency pre-bundling so
|
|
262
|
+
// they go through the normal plugin pipeline.
|
|
263
|
+
exclude: mergeUnique(
|
|
264
|
+
userOptimizeDeps?.exclude ?? [],
|
|
265
|
+
["@matthesketh/utopia-core", "@matthesketh/utopia-runtime", "@matthesketh/utopia-router", "@matthesketh/utopia-server"]
|
|
266
|
+
)
|
|
267
|
+
},
|
|
268
|
+
ssr: {
|
|
269
|
+
// Ensure UtopiaJS packages are bundled during SSR builds so the
|
|
270
|
+
// runtime swap alias is applied correctly.
|
|
271
|
+
noExternal: ["@matthesketh/utopia-core", "@matthesketh/utopia-runtime", "@matthesketh/utopia-router", "@matthesketh/utopia-server"]
|
|
272
|
+
}
|
|
273
|
+
};
|
|
274
|
+
}
|
|
275
|
+
function mergeUnique(base, additions) {
|
|
276
|
+
const set = /* @__PURE__ */ new Set([...base, ...additions]);
|
|
277
|
+
return [...set];
|
|
278
|
+
}
|
|
279
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
280
|
+
0 && (module.exports = {
|
|
281
|
+
defineConfig
|
|
282
|
+
});
|
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { FilterPattern, Plugin, UserConfig } from 'vite';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Options for the UtopiaJS Vite plugin.
|
|
5
|
+
*/
|
|
6
|
+
interface UtopiaPluginOptions {
|
|
7
|
+
/**
|
|
8
|
+
* Glob patterns to include when transforming `.utopia` files.
|
|
9
|
+
* @default '**\/*.utopia'
|
|
10
|
+
*/
|
|
11
|
+
include?: FilterPattern;
|
|
12
|
+
/**
|
|
13
|
+
* Glob patterns to exclude from transformation.
|
|
14
|
+
* @default undefined
|
|
15
|
+
*/
|
|
16
|
+
exclude?: FilterPattern;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Vite plugin for UtopiaJS.
|
|
20
|
+
*
|
|
21
|
+
* Transforms `.utopia` single-file components using `@matthesketh/utopia-compiler`,
|
|
22
|
+
* extracts and injects CSS through Vite's virtual module pipeline, and
|
|
23
|
+
* provides granular HMR support (style-only hot updates when only the
|
|
24
|
+
* `<style>` block changes).
|
|
25
|
+
*
|
|
26
|
+
* @example
|
|
27
|
+
* ```ts
|
|
28
|
+
* // vite.config.ts
|
|
29
|
+
* import utopia from '@matthesketh/utopia-vite-plugin'
|
|
30
|
+
*
|
|
31
|
+
* export default {
|
|
32
|
+
* plugins: [utopia()],
|
|
33
|
+
* }
|
|
34
|
+
* ```
|
|
35
|
+
*
|
|
36
|
+
* @param options - Optional configuration.
|
|
37
|
+
* @returns A Vite plugin object.
|
|
38
|
+
*/
|
|
39
|
+
declare function utopiaPlugin(options?: UtopiaPluginOptions): Plugin;
|
|
40
|
+
/**
|
|
41
|
+
* Create a Vite configuration pre-configured for an UtopiaJS project.
|
|
42
|
+
*
|
|
43
|
+
* Merges the Utopia Vite plugin and sensible defaults into an optional
|
|
44
|
+
* user-provided Vite configuration.
|
|
45
|
+
*
|
|
46
|
+
* @example
|
|
47
|
+
* ```ts
|
|
48
|
+
* // vite.config.ts
|
|
49
|
+
* import { defineConfig } from '@matthesketh/utopia-vite-plugin'
|
|
50
|
+
*
|
|
51
|
+
* export default defineConfig({
|
|
52
|
+
* // your overrides here
|
|
53
|
+
* })
|
|
54
|
+
* ```
|
|
55
|
+
*
|
|
56
|
+
* @param userConfig - Optional Vite `UserConfig` to merge.
|
|
57
|
+
* @returns A complete Vite `UserConfig` ready to use.
|
|
58
|
+
*/
|
|
59
|
+
declare function defineConfig(userConfig?: UserConfig): UserConfig;
|
|
60
|
+
|
|
61
|
+
export { type UtopiaPluginOptions, utopiaPlugin as default, defineConfig };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { FilterPattern, Plugin, UserConfig } from 'vite';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Options for the UtopiaJS Vite plugin.
|
|
5
|
+
*/
|
|
6
|
+
interface UtopiaPluginOptions {
|
|
7
|
+
/**
|
|
8
|
+
* Glob patterns to include when transforming `.utopia` files.
|
|
9
|
+
* @default '**\/*.utopia'
|
|
10
|
+
*/
|
|
11
|
+
include?: FilterPattern;
|
|
12
|
+
/**
|
|
13
|
+
* Glob patterns to exclude from transformation.
|
|
14
|
+
* @default undefined
|
|
15
|
+
*/
|
|
16
|
+
exclude?: FilterPattern;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Vite plugin for UtopiaJS.
|
|
20
|
+
*
|
|
21
|
+
* Transforms `.utopia` single-file components using `@matthesketh/utopia-compiler`,
|
|
22
|
+
* extracts and injects CSS through Vite's virtual module pipeline, and
|
|
23
|
+
* provides granular HMR support (style-only hot updates when only the
|
|
24
|
+
* `<style>` block changes).
|
|
25
|
+
*
|
|
26
|
+
* @example
|
|
27
|
+
* ```ts
|
|
28
|
+
* // vite.config.ts
|
|
29
|
+
* import utopia from '@matthesketh/utopia-vite-plugin'
|
|
30
|
+
*
|
|
31
|
+
* export default {
|
|
32
|
+
* plugins: [utopia()],
|
|
33
|
+
* }
|
|
34
|
+
* ```
|
|
35
|
+
*
|
|
36
|
+
* @param options - Optional configuration.
|
|
37
|
+
* @returns A Vite plugin object.
|
|
38
|
+
*/
|
|
39
|
+
declare function utopiaPlugin(options?: UtopiaPluginOptions): Plugin;
|
|
40
|
+
/**
|
|
41
|
+
* Create a Vite configuration pre-configured for an UtopiaJS project.
|
|
42
|
+
*
|
|
43
|
+
* Merges the Utopia Vite plugin and sensible defaults into an optional
|
|
44
|
+
* user-provided Vite configuration.
|
|
45
|
+
*
|
|
46
|
+
* @example
|
|
47
|
+
* ```ts
|
|
48
|
+
* // vite.config.ts
|
|
49
|
+
* import { defineConfig } from '@matthesketh/utopia-vite-plugin'
|
|
50
|
+
*
|
|
51
|
+
* export default defineConfig({
|
|
52
|
+
* // your overrides here
|
|
53
|
+
* })
|
|
54
|
+
* ```
|
|
55
|
+
*
|
|
56
|
+
* @param userConfig - Optional Vite `UserConfig` to merge.
|
|
57
|
+
* @returns A complete Vite `UserConfig` ready to use.
|
|
58
|
+
*/
|
|
59
|
+
declare function defineConfig(userConfig?: UserConfig): UserConfig;
|
|
60
|
+
|
|
61
|
+
export { type UtopiaPluginOptions, utopiaPlugin as default, defineConfig };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
// src/index.ts
|
|
2
|
+
import { compile, parse } from "@matthesketh/utopia-compiler";
|
|
3
|
+
import { createFilter } from "vite";
|
|
4
|
+
import path from "path";
|
|
5
|
+
var UTOPIA_EXT = ".utopia";
|
|
6
|
+
var CSS_SUFFIX = ".css";
|
|
7
|
+
var VIRTUAL_PREFIX = "\0";
|
|
8
|
+
var cssCache = /* @__PURE__ */ new Map();
|
|
9
|
+
function toCssId(utopiaId) {
|
|
10
|
+
return utopiaId + CSS_SUFFIX;
|
|
11
|
+
}
|
|
12
|
+
function isVirtualCssId(id) {
|
|
13
|
+
return id.endsWith(UTOPIA_EXT + CSS_SUFFIX);
|
|
14
|
+
}
|
|
15
|
+
function stripVirtualPrefix(id) {
|
|
16
|
+
return id.startsWith(VIRTUAL_PREFIX) ? id.slice(VIRTUAL_PREFIX.length) : id;
|
|
17
|
+
}
|
|
18
|
+
function cssIdToUtopiaId(cssId) {
|
|
19
|
+
const raw = stripVirtualPrefix(cssId);
|
|
20
|
+
return raw.slice(0, -CSS_SUFFIX.length);
|
|
21
|
+
}
|
|
22
|
+
function utopiaPlugin(options = {}) {
|
|
23
|
+
const {
|
|
24
|
+
include = `**/*${UTOPIA_EXT}`,
|
|
25
|
+
exclude
|
|
26
|
+
} = options;
|
|
27
|
+
let filter;
|
|
28
|
+
let server;
|
|
29
|
+
const prevDescriptors = /* @__PURE__ */ new Map();
|
|
30
|
+
return {
|
|
31
|
+
name: "utopia",
|
|
32
|
+
/**
|
|
33
|
+
* Enforce the plugin to run before Vite's internal transforms so that
|
|
34
|
+
* `.utopia` files are compiled before any further processing.
|
|
35
|
+
*/
|
|
36
|
+
enforce: "pre",
|
|
37
|
+
// -------------------------------------------------------------------
|
|
38
|
+
// Config — SSR alias resolution
|
|
39
|
+
// -------------------------------------------------------------------
|
|
40
|
+
config(userConfig, env) {
|
|
41
|
+
if (env.isSsrBuild) {
|
|
42
|
+
return {
|
|
43
|
+
resolve: {
|
|
44
|
+
alias: {
|
|
45
|
+
"@matthesketh/utopia-runtime": "@matthesketh/utopia-server/ssr-runtime"
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
},
|
|
51
|
+
configResolved() {
|
|
52
|
+
filter = createFilter(include, exclude);
|
|
53
|
+
},
|
|
54
|
+
// -------------------------------------------------------------------
|
|
55
|
+
// Dev server – store reference for HMR
|
|
56
|
+
// -------------------------------------------------------------------
|
|
57
|
+
configureServer(_server) {
|
|
58
|
+
server = _server;
|
|
59
|
+
},
|
|
60
|
+
// -------------------------------------------------------------------
|
|
61
|
+
// Resolve virtual CSS modules + SSR runtime alias
|
|
62
|
+
// -------------------------------------------------------------------
|
|
63
|
+
resolveId(id, importer, options2) {
|
|
64
|
+
if (options2?.ssr && id === "@matthesketh/utopia-runtime") {
|
|
65
|
+
return this.resolve("@matthesketh/utopia-server/ssr-runtime", importer, {
|
|
66
|
+
skipSelf: true,
|
|
67
|
+
...options2
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
if (isVirtualCssId(id)) {
|
|
71
|
+
if (path.isAbsolute(id)) {
|
|
72
|
+
return VIRTUAL_PREFIX + id;
|
|
73
|
+
}
|
|
74
|
+
if (importer) {
|
|
75
|
+
const dir = path.dirname(importer);
|
|
76
|
+
const resolved = path.resolve(dir, id);
|
|
77
|
+
return VIRTUAL_PREFIX + resolved;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
return void 0;
|
|
81
|
+
},
|
|
82
|
+
// -------------------------------------------------------------------
|
|
83
|
+
// Load virtual CSS modules
|
|
84
|
+
// -------------------------------------------------------------------
|
|
85
|
+
load(id) {
|
|
86
|
+
if (!id.startsWith(VIRTUAL_PREFIX)) return void 0;
|
|
87
|
+
const raw = stripVirtualPrefix(id);
|
|
88
|
+
if (isVirtualCssId(raw)) {
|
|
89
|
+
const utopiaId = cssIdToUtopiaId(raw);
|
|
90
|
+
const css = cssCache.get(utopiaId) ?? "";
|
|
91
|
+
return css;
|
|
92
|
+
}
|
|
93
|
+
return void 0;
|
|
94
|
+
},
|
|
95
|
+
// -------------------------------------------------------------------
|
|
96
|
+
// Transform .utopia files
|
|
97
|
+
// -------------------------------------------------------------------
|
|
98
|
+
transform(code, id) {
|
|
99
|
+
if (!id.endsWith(UTOPIA_EXT)) return void 0;
|
|
100
|
+
if (!filter(id)) return void 0;
|
|
101
|
+
const result = compile(code, {
|
|
102
|
+
filename: id
|
|
103
|
+
});
|
|
104
|
+
if (result.css) {
|
|
105
|
+
cssCache.set(id, result.css);
|
|
106
|
+
} else {
|
|
107
|
+
cssCache.delete(id);
|
|
108
|
+
}
|
|
109
|
+
try {
|
|
110
|
+
const descriptor = parse(code, id);
|
|
111
|
+
prevDescriptors.set(id, descriptor);
|
|
112
|
+
} catch {
|
|
113
|
+
}
|
|
114
|
+
let output = result.code;
|
|
115
|
+
if (result.css) {
|
|
116
|
+
const cssImportId = toCssId(id);
|
|
117
|
+
output += `
|
|
118
|
+
import ${JSON.stringify(cssImportId)};
|
|
119
|
+
`;
|
|
120
|
+
}
|
|
121
|
+
return {
|
|
122
|
+
code: output,
|
|
123
|
+
map: null
|
|
124
|
+
};
|
|
125
|
+
},
|
|
126
|
+
// -------------------------------------------------------------------
|
|
127
|
+
// HMR
|
|
128
|
+
// -------------------------------------------------------------------
|
|
129
|
+
handleHotUpdate(ctx) {
|
|
130
|
+
const { file, read, server: hmrServer, modules } = ctx;
|
|
131
|
+
if (!file.endsWith(UTOPIA_EXT)) return void 0;
|
|
132
|
+
return (async () => {
|
|
133
|
+
const source = await read();
|
|
134
|
+
let newDescriptor;
|
|
135
|
+
try {
|
|
136
|
+
newDescriptor = parse(source, file);
|
|
137
|
+
} catch {
|
|
138
|
+
return void 0;
|
|
139
|
+
}
|
|
140
|
+
const oldDescriptor = prevDescriptors.get(file);
|
|
141
|
+
prevDescriptors.set(file, newDescriptor);
|
|
142
|
+
const templateChanged = didBlockChange(
|
|
143
|
+
oldDescriptor?.template,
|
|
144
|
+
newDescriptor.template
|
|
145
|
+
);
|
|
146
|
+
const scriptChanged = didBlockChange(
|
|
147
|
+
oldDescriptor?.script,
|
|
148
|
+
newDescriptor.script
|
|
149
|
+
);
|
|
150
|
+
const styleChanged = didBlockChange(
|
|
151
|
+
oldDescriptor?.style,
|
|
152
|
+
newDescriptor.style
|
|
153
|
+
);
|
|
154
|
+
if (styleChanged && !templateChanged && !scriptChanged) {
|
|
155
|
+
const result = compile(source, {
|
|
156
|
+
filename: file
|
|
157
|
+
});
|
|
158
|
+
if (result.css) {
|
|
159
|
+
cssCache.set(file, result.css);
|
|
160
|
+
} else {
|
|
161
|
+
cssCache.delete(file);
|
|
162
|
+
}
|
|
163
|
+
const cssId = VIRTUAL_PREFIX + toCssId(file);
|
|
164
|
+
const cssModule = hmrServer.moduleGraph.getModuleById(cssId);
|
|
165
|
+
if (cssModule) {
|
|
166
|
+
hmrServer.moduleGraph.invalidateModule(cssModule);
|
|
167
|
+
return [cssModule];
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
const affectedModules = [];
|
|
171
|
+
for (const mod of modules) {
|
|
172
|
+
hmrServer.moduleGraph.invalidateModule(mod);
|
|
173
|
+
affectedModules.push(mod);
|
|
174
|
+
}
|
|
175
|
+
if (styleChanged) {
|
|
176
|
+
const result = compile(source, {
|
|
177
|
+
filename: file
|
|
178
|
+
});
|
|
179
|
+
if (result.css) {
|
|
180
|
+
cssCache.set(file, result.css);
|
|
181
|
+
} else {
|
|
182
|
+
cssCache.delete(file);
|
|
183
|
+
}
|
|
184
|
+
const cssId = VIRTUAL_PREFIX + toCssId(file);
|
|
185
|
+
const cssModule = hmrServer.moduleGraph.getModuleById(cssId);
|
|
186
|
+
if (cssModule) {
|
|
187
|
+
hmrServer.moduleGraph.invalidateModule(cssModule);
|
|
188
|
+
affectedModules.push(cssModule);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
return affectedModules.length > 0 ? affectedModules : void 0;
|
|
192
|
+
})();
|
|
193
|
+
}
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
function didBlockChange(oldBlock, newBlock) {
|
|
197
|
+
if (!oldBlock && !newBlock) return false;
|
|
198
|
+
if (!oldBlock || !newBlock) return true;
|
|
199
|
+
return oldBlock.content !== newBlock.content;
|
|
200
|
+
}
|
|
201
|
+
function defineConfig(userConfig = {}) {
|
|
202
|
+
const {
|
|
203
|
+
plugins: userPlugins = [],
|
|
204
|
+
resolve: userResolve,
|
|
205
|
+
optimizeDeps: userOptimizeDeps,
|
|
206
|
+
...rest
|
|
207
|
+
} = userConfig;
|
|
208
|
+
const hasUtopiaPlugin = userPlugins.some(
|
|
209
|
+
(p) => p && typeof p === "object" && "name" in p && p.name === "utopia"
|
|
210
|
+
);
|
|
211
|
+
const plugins = hasUtopiaPlugin ? userPlugins : [utopiaPlugin(), ...userPlugins];
|
|
212
|
+
return {
|
|
213
|
+
...rest,
|
|
214
|
+
plugins,
|
|
215
|
+
resolve: {
|
|
216
|
+
...userResolve,
|
|
217
|
+
// Ensure `.utopia` is resolvable as an extension so bare imports work
|
|
218
|
+
// (e.g. `import App from './App'` resolves to `./App.utopia`).
|
|
219
|
+
extensions: mergeUnique(
|
|
220
|
+
userResolve?.extensions ?? [".mjs", ".js", ".mts", ".ts", ".jsx", ".tsx", ".json"],
|
|
221
|
+
[UTOPIA_EXT]
|
|
222
|
+
)
|
|
223
|
+
},
|
|
224
|
+
optimizeDeps: {
|
|
225
|
+
...userOptimizeDeps,
|
|
226
|
+
// Exclude UtopiaJS packages from Vite's dependency pre-bundling so
|
|
227
|
+
// they go through the normal plugin pipeline.
|
|
228
|
+
exclude: mergeUnique(
|
|
229
|
+
userOptimizeDeps?.exclude ?? [],
|
|
230
|
+
["@matthesketh/utopia-core", "@matthesketh/utopia-runtime", "@matthesketh/utopia-router", "@matthesketh/utopia-server"]
|
|
231
|
+
)
|
|
232
|
+
},
|
|
233
|
+
ssr: {
|
|
234
|
+
// Ensure UtopiaJS packages are bundled during SSR builds so the
|
|
235
|
+
// runtime swap alias is applied correctly.
|
|
236
|
+
noExternal: ["@matthesketh/utopia-core", "@matthesketh/utopia-runtime", "@matthesketh/utopia-router", "@matthesketh/utopia-server"]
|
|
237
|
+
}
|
|
238
|
+
};
|
|
239
|
+
}
|
|
240
|
+
function mergeUnique(base, additions) {
|
|
241
|
+
const set = /* @__PURE__ */ new Set([...base, ...additions]);
|
|
242
|
+
return [...set];
|
|
243
|
+
}
|
|
244
|
+
export {
|
|
245
|
+
utopiaPlugin as default,
|
|
246
|
+
defineConfig
|
|
247
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@matthesketh/utopia-vite-plugin",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"description": "Vite plugin for UtopiaJS .utopia files",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"author": "Matt <matt@matthesketh.pro>",
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "https://github.com/wrxck/utopiajs.git",
|
|
11
|
+
"directory": "packages/vite-plugin"
|
|
12
|
+
},
|
|
13
|
+
"homepage": "https://github.com/wrxck/utopiajs/tree/main/packages/vite-plugin",
|
|
14
|
+
"keywords": [
|
|
15
|
+
"vite",
|
|
16
|
+
"plugin",
|
|
17
|
+
"hmr",
|
|
18
|
+
"transform",
|
|
19
|
+
"utopiajs"
|
|
20
|
+
],
|
|
21
|
+
"engines": {
|
|
22
|
+
"node": ">=20.0.0"
|
|
23
|
+
},
|
|
24
|
+
"publishConfig": {
|
|
25
|
+
"access": "public"
|
|
26
|
+
},
|
|
27
|
+
"sideEffects": false,
|
|
28
|
+
"main": "./dist/index.cjs",
|
|
29
|
+
"module": "./dist/index.js",
|
|
30
|
+
"types": "./dist/index.d.ts",
|
|
31
|
+
"exports": {
|
|
32
|
+
".": {
|
|
33
|
+
"types": "./dist/index.d.ts",
|
|
34
|
+
"import": "./dist/index.js",
|
|
35
|
+
"require": "./dist/index.cjs"
|
|
36
|
+
}
|
|
37
|
+
},
|
|
38
|
+
"files": [
|
|
39
|
+
"dist"
|
|
40
|
+
],
|
|
41
|
+
"dependencies": {
|
|
42
|
+
"@matthesketh/utopia-compiler": "0.0.1"
|
|
43
|
+
},
|
|
44
|
+
"peerDependencies": {
|
|
45
|
+
"vite": "^6.0.0 || ^7.0.0"
|
|
46
|
+
},
|
|
47
|
+
"devDependencies": {
|
|
48
|
+
"vite": "^7.3.1"
|
|
49
|
+
},
|
|
50
|
+
"scripts": {
|
|
51
|
+
"build": "tsup src/index.ts --format esm,cjs --dts",
|
|
52
|
+
"dev": "tsup src/index.ts --format esm,cjs --dts --watch"
|
|
53
|
+
}
|
|
54
|
+
}
|