netjes 0.0.3 → 0.0.4
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/README.md +10 -8
- package/cli/info.mjs +268 -31
- package/cli/routes.mjs +35 -8
- package/dist/href.d.ts +7 -0
- package/dist/href.d.ts.map +1 -1
- package/dist/href.js +16 -5
- package/dist/href.js.map +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -1
- package/dist/index.js.map +1 -1
- package/dist/mounted.d.ts +55 -0
- package/dist/mounted.d.ts.map +1 -0
- package/dist/mounted.js +110 -0
- package/dist/mounted.js.map +1 -0
- package/dist/payload.d.ts +8 -0
- package/dist/payload.d.ts.map +1 -1
- package/dist/payload.js +28 -0
- package/dist/payload.js.map +1 -1
- package/dist/render-document.d.ts +18 -4
- package/dist/render-document.d.ts.map +1 -1
- package/dist/render-document.js +79 -16
- package/dist/render-document.js.map +1 -1
- package/dist/testing.d.ts +6 -0
- package/dist/testing.d.ts.map +1 -1
- package/dist/testing.js +1 -0
- package/dist/testing.js.map +1 -1
- package/package.json +1 -1
- package/sidecar/server.mjs +37 -10
- package/skills/netjes/SKILL.md +4 -3
- package/skills/netjes/references/data-and-actions.md +6 -2
- package/skills/netjes/references/routing.md +14 -3
- package/src/href.ts +16 -5
- package/src/index.ts +18 -1
- package/src/mounted.tsx +160 -0
- package/src/payload.ts +32 -0
- package/src/render-document.tsx +117 -20
- package/src/testing.tsx +7 -0
- package/src/virtual.d.ts +6 -1
- package/template/_AGENTS.md +4 -3
- package/vite/packages.mjs +345 -0
- package/vite/plugin.d.ts +7 -0
- package/vite/plugin.mjs +208 -30
|
@@ -0,0 +1,345 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
// Page packages: npm packages that bring pages for the app to mount, marked by a `netjes` field in their package.json.
|
|
3
|
+
// The Vite plugin, the development sidecar (through the plugin's api) and the CLI read them from here.
|
|
4
|
+
import { readFileSync, realpathSync, statSync } from 'node:fs';
|
|
5
|
+
import { readdir } from 'node:fs/promises';
|
|
6
|
+
import path from 'node:path';
|
|
7
|
+
import { satisfies } from '../cli/info.mjs';
|
|
8
|
+
import { findPackage } from '../cli/packages.mjs';
|
|
9
|
+
|
|
10
|
+
/** The names of the convention files, which the route table routes; every other file in a pages folder is colocated code. */
|
|
11
|
+
export const CONVENTION_FILES = ['page', 'layout', 'template', 'loading', 'error', 'not-found', 'forbidden', 'unauthorized', 'global-error', 'default'];
|
|
12
|
+
/** The extensions of convention files. */
|
|
13
|
+
export const CONVENTION_EXTENSIONS = ['.tsx', '.jsx'];
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* A page package as the app installed it.
|
|
17
|
+
* @typedef {object} PagePackage
|
|
18
|
+
* @property {string} name The npm name, which starts every file id of the package.
|
|
19
|
+
* @property {string} version
|
|
20
|
+
* @property {string} netjes Its `peerDependencies.netjes`: the Netjes versions it supports.
|
|
21
|
+
* @property {string} root The real path of its folder, with `/` separators: a link (a workspace, `npm link`) resolved.
|
|
22
|
+
* @property {string} pages The folder of its pages, relative to `root`.
|
|
23
|
+
* @property {string[]} sources The folders Tailwind scans for its classes, relative to `root`.
|
|
24
|
+
* @property {string | null} css A stylesheet the app's Tailwind build imports, relative to `root`.
|
|
25
|
+
* @property {string | null} nuget The NuGet package of its C# half.
|
|
26
|
+
* @property {string[]} dependencies The names in its `dependencies`.
|
|
27
|
+
* @property {boolean} linked Whether `root` lies outside every node_modules folder: a workspace or `npm link` links it,
|
|
28
|
+
* and its files may change while the dev server runs.
|
|
29
|
+
*/
|
|
30
|
+
|
|
31
|
+
/** @param {string} file */
|
|
32
|
+
const slashes = (file) => file.replaceAll('\\', '/');
|
|
33
|
+
|
|
34
|
+
/** npm's rule for the name of a new package: lowercase, URL-safe, and at most one scope. */
|
|
35
|
+
const PACKAGE_NAME = /^(?:@[a-z0-9-~][a-z0-9-._~]*\/)?[a-z0-9-~][a-z0-9-._~]*$/;
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Whether a name is one npm gives a new package, and so no path: no `.` or `..`, and no `/` but the scope's.
|
|
39
|
+
* @param {unknown} name
|
|
40
|
+
* @returns {name is string}
|
|
41
|
+
*/
|
|
42
|
+
export const isPackageName = (name) => typeof name === 'string' && PACKAGE_NAME.test(name);
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Whether a path from a package's `netjes` field is relative and stays inside the package: no `..` or `.`, no leading
|
|
46
|
+
* `/`, no `\`, no drive letter, no empty folder. The file ids of the package are built from it, so it has one spelling.
|
|
47
|
+
* @param {unknown} value
|
|
48
|
+
* @returns {value is string}
|
|
49
|
+
*/
|
|
50
|
+
function isInsidePath(value) {
|
|
51
|
+
return (
|
|
52
|
+
typeof value === 'string' &&
|
|
53
|
+
!value.includes('\\') &&
|
|
54
|
+
!value.includes(':') &&
|
|
55
|
+
value.split('/').every((segment) => segment !== '' && segment !== '.' && segment !== '..')
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/** @param {string} file */
|
|
60
|
+
function isDirectory(file) {
|
|
61
|
+
try {
|
|
62
|
+
return statSync(file).isDirectory();
|
|
63
|
+
} catch {
|
|
64
|
+
return false;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/** @param {string} file */
|
|
69
|
+
function isFile(file) {
|
|
70
|
+
try {
|
|
71
|
+
return statSync(file).isFile();
|
|
72
|
+
} catch {
|
|
73
|
+
return false;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Reads and checks the page packages the app lists, in the order it lists them. Each error is one line that names the
|
|
79
|
+
* package and what to do.
|
|
80
|
+
* @param {string} root The app's folder, from which the packages resolve.
|
|
81
|
+
* @param {readonly string[]} names
|
|
82
|
+
* @param {string} appDir The app's folder of pages, whose file ids a package's must not look like.
|
|
83
|
+
* @returns {PagePackage[]}
|
|
84
|
+
*/
|
|
85
|
+
export function resolvePagePackages(root, names, appDir) {
|
|
86
|
+
/** @type {PagePackage[]} */
|
|
87
|
+
const packages = [];
|
|
88
|
+
const seen = new Set();
|
|
89
|
+
for (const name of names) {
|
|
90
|
+
if (!isPackageName(name)) {
|
|
91
|
+
throw new Error(`netjes: ${JSON.stringify(name)} in netjes({ packages }) is not the name of an npm package`);
|
|
92
|
+
}
|
|
93
|
+
if (seen.has(name)) throw new Error(`netjes: '${name}' is listed twice in netjes({ packages })`);
|
|
94
|
+
seen.add(name);
|
|
95
|
+
// A package's file ids start with its name, and the app's with its folder of pages (`app`, `src/app`).
|
|
96
|
+
if (name === appDir || appDir.startsWith(`${name}/`)) {
|
|
97
|
+
throw new Error(`netjes: '${name}' in netjes({ packages }) is named like the app's folder of pages, ${appDir}, so its pages would be taken for the app's`);
|
|
98
|
+
}
|
|
99
|
+
packages.push(readPagePackage(root, name));
|
|
100
|
+
}
|
|
101
|
+
return packages;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* @param {string} root
|
|
106
|
+
* @param {string} name
|
|
107
|
+
* @returns {PagePackage}
|
|
108
|
+
*/
|
|
109
|
+
function readPagePackage(root, name) {
|
|
110
|
+
const manifestPath = findPackage(root, name);
|
|
111
|
+
if (!manifestPath) throw new Error(`netjes: '${name}' in netjes({ packages }) is not installed: npm install ${name}`);
|
|
112
|
+
const manifest = /** @type {Record<string, unknown>} */ (JSON.parse(readFileSync(manifestPath, 'utf8')));
|
|
113
|
+
const field = manifest.netjes;
|
|
114
|
+
if (typeof field !== 'object' || field === null || Array.isArray(field)) {
|
|
115
|
+
throw new Error(`netjes: '${name}' in netjes({ packages }) is not a page package: its package.json has no "netjes" field`);
|
|
116
|
+
}
|
|
117
|
+
const options = /** @type {Record<string, unknown>} */ (field);
|
|
118
|
+
const peers = manifest.peerDependencies;
|
|
119
|
+
const range = typeof peers === 'object' && peers !== null ? /** @type {Record<string, unknown>} */ (peers).netjes : undefined;
|
|
120
|
+
if (typeof range !== 'string' || range.trim() === '') {
|
|
121
|
+
throw new Error(`netjes: '${name}' has no peerDependencies.netjes in its package.json: a page package names the Netjes versions it supports there`);
|
|
122
|
+
}
|
|
123
|
+
const dir = slashes(realpathSync.native(path.dirname(manifestPath)));
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* @param {string} key
|
|
127
|
+
* @param {unknown} value
|
|
128
|
+
*/
|
|
129
|
+
const inside = (key, value) => {
|
|
130
|
+
if (!isInsidePath(value)) {
|
|
131
|
+
throw new Error(
|
|
132
|
+
`netjes: '${name}' has "${key}": ${JSON.stringify(value)} in the "netjes" field of its package.json: ` +
|
|
133
|
+
'a path there is relative and stays inside the package (no "..", no leading "/", no "\\")',
|
|
134
|
+
);
|
|
135
|
+
}
|
|
136
|
+
return value;
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
const pages = inside('pages', options.pages ?? 'app');
|
|
140
|
+
/** @type {string[]} */
|
|
141
|
+
let sources;
|
|
142
|
+
if (options.sources === undefined) {
|
|
143
|
+
sources = isDirectory(path.join(dir, 'src')) && pages !== 'src' ? [pages, 'src'] : [pages];
|
|
144
|
+
} else {
|
|
145
|
+
if (!Array.isArray(options.sources)) {
|
|
146
|
+
throw new Error(`netjes: '${name}' has "sources": ${JSON.stringify(options.sources)} in the "netjes" field of its package.json: it is a list of folders`);
|
|
147
|
+
}
|
|
148
|
+
sources = options.sources.map((source) => inside('sources', source));
|
|
149
|
+
for (const source of sources) {
|
|
150
|
+
if (!isDirectory(path.join(dir, source))) {
|
|
151
|
+
throw new Error(`netjes: '${name}' lists "${source}" in "sources" in the "netjes" field of its package.json, which is no folder in the package`);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
const css = options.css === undefined ? null : inside('css', options.css);
|
|
156
|
+
if (css !== null && !isFile(path.join(dir, css))) {
|
|
157
|
+
throw new Error(`netjes: '${name}' has "css": "${css}" in the "netjes" field of its package.json, which is no file in the package`);
|
|
158
|
+
}
|
|
159
|
+
// A package published without it (its `files` leave it out) would otherwise bring no pages, without a word.
|
|
160
|
+
if (!isDirectory(path.join(dir, pages))) {
|
|
161
|
+
throw new Error(
|
|
162
|
+
options.pages === undefined
|
|
163
|
+
? `netjes: '${name}' has no app folder, where a page package's pages are unless "pages" in the "netjes" field of its package.json names another`
|
|
164
|
+
: `netjes: '${name}' has "pages": "${pages}" in the "netjes" field of its package.json, which is no folder in the package`,
|
|
165
|
+
);
|
|
166
|
+
}
|
|
167
|
+
const nuget = typeof options.nuget === 'string' ? options.nuget : null;
|
|
168
|
+
const dependencies = typeof manifest.dependencies === 'object' && manifest.dependencies !== null ? Object.keys(manifest.dependencies) : [];
|
|
169
|
+
return {
|
|
170
|
+
name,
|
|
171
|
+
version: typeof manifest.version === 'string' ? manifest.version : '',
|
|
172
|
+
netjes: range,
|
|
173
|
+
root: dir,
|
|
174
|
+
pages,
|
|
175
|
+
sources,
|
|
176
|
+
css,
|
|
177
|
+
nuget,
|
|
178
|
+
dependencies,
|
|
179
|
+
linked: !dir.split('/').includes('node_modules'),
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* Why a package cannot run on this Netjes: its `peerDependencies.netjes`, read strictly as npm reads a range, leaves out
|
|
185
|
+
* `netjes`, the installed version. Null when it holds it. The .NET app says the same about the Netjes it runs.
|
|
186
|
+
* @param {PagePackage} pkg
|
|
187
|
+
* @param {string} netjes
|
|
188
|
+
* @returns {string | null}
|
|
189
|
+
*/
|
|
190
|
+
export function unsupportedNetjes(pkg, netjes) {
|
|
191
|
+
return satisfies(netjes, pkg.netjes, { strict: true }) ? null : `${pkg.name} ${pkg.version} supports Netjes ${pkg.netjes}; this app runs Netjes ${netjes}`;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* The files a package's `exports` names, in every condition.
|
|
196
|
+
* @param {unknown} exports
|
|
197
|
+
* @returns {string[]}
|
|
198
|
+
*/
|
|
199
|
+
const exportTargets = (exports) =>
|
|
200
|
+
typeof exports === 'string' ? [exports] : typeof exports === 'object' && exports !== null ? Object.values(exports).flatMap(exportTargets) : [];
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* Whether a package has JavaScript for Vite to pre-bundle: a script its `exports` names, or else its `main`
|
|
204
|
+
* (`index.js` by default), as Node resolves it. A package of types or of stylesheets has none.
|
|
205
|
+
* @param {string} dir
|
|
206
|
+
* @param {Record<string, unknown>} manifest
|
|
207
|
+
*/
|
|
208
|
+
function hasScript(dir, manifest) {
|
|
209
|
+
if (manifest.exports !== undefined) return exportTargets(manifest.exports).some((target) => /\.c?js$/.test(target));
|
|
210
|
+
const main = typeof manifest.main === 'string' && manifest.main !== '' ? manifest.main : 'index.js';
|
|
211
|
+
if (!['', '.js', '.cjs'].includes(path.extname(main))) return false;
|
|
212
|
+
return [main, `${main}.js`, path.join(main, 'index.js')].some((file) => isFile(path.join(dir, file)));
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* The package's dependencies that are CommonJS, as Vite has to pre-bundle them for the browser: JavaScript with neither
|
|
217
|
+
* `"type": "module"`, a `module` entry nor an `import` condition in its `exports`. A dependency that is not installed
|
|
218
|
+
* for the package, one with no JavaScript, and netjes, which Vite never pre-bundles, are left out.
|
|
219
|
+
* @param {PagePackage} pkg
|
|
220
|
+
* @returns {string[]}
|
|
221
|
+
*/
|
|
222
|
+
export function commonJsDependencies(pkg) {
|
|
223
|
+
return pkg.dependencies.filter((dependency) => {
|
|
224
|
+
if (dependency === 'netjes') return false;
|
|
225
|
+
const manifestPath = findPackage(pkg.root, dependency);
|
|
226
|
+
if (!manifestPath) return false;
|
|
227
|
+
const manifest = /** @type {Record<string, unknown>} */ (JSON.parse(readFileSync(manifestPath, 'utf8')));
|
|
228
|
+
if (manifest.type === 'module' || typeof manifest.module === 'string') return false;
|
|
229
|
+
if (/"import"\s*:|\.mjs"/.test(JSON.stringify(manifest.exports ?? null))) return false;
|
|
230
|
+
return hasScript(path.dirname(manifestPath), manifest);
|
|
231
|
+
});
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* A package's convention files, relative to its folder of pages, in ordinal order. Like the app's glob, this leaves out
|
|
236
|
+
* node_modules and hidden folders.
|
|
237
|
+
* @param {PagePackage} pkg
|
|
238
|
+
* @returns {Promise<string[]>}
|
|
239
|
+
*/
|
|
240
|
+
export async function pagePackageFiles(pkg) {
|
|
241
|
+
const dir = path.join(pkg.root, pkg.pages);
|
|
242
|
+
/** @type {import('node:fs').Dirent[]} */
|
|
243
|
+
let entries;
|
|
244
|
+
try {
|
|
245
|
+
entries = await readdir(dir, { recursive: true, withFileTypes: true });
|
|
246
|
+
} catch {
|
|
247
|
+
return [];
|
|
248
|
+
}
|
|
249
|
+
const files = entries
|
|
250
|
+
.filter((entry) => entry.isFile() && isConventionFile(entry.name))
|
|
251
|
+
.map((entry) => slashes(path.relative(dir, path.join(entry.parentPath, entry.name))))
|
|
252
|
+
.filter((file) => file.split('/').slice(0, -1).every((folder) => folder !== 'node_modules' && !folder.startsWith('.')));
|
|
253
|
+
// UTF-16 code unit order, as the route table's ordinal order.
|
|
254
|
+
return files.sort((a, b) => (a < b ? -1 : a > b ? 1 : 0));
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
/**
|
|
258
|
+
* Whether a file is a convention file, by its name.
|
|
259
|
+
* @param {string} name A file's name, with its extension.
|
|
260
|
+
*/
|
|
261
|
+
export function isConventionFile(name) {
|
|
262
|
+
const { name: base, ext } = path.parse(name);
|
|
263
|
+
return CONVENTION_FILES.includes(base) && CONVENTION_EXTENSIONS.includes(ext);
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
/**
|
|
267
|
+
* The id of a package's file: its npm name, its folder of pages and the file's path in that folder, as the payload,
|
|
268
|
+
* the route table and errors name it, wherever the package is installed.
|
|
269
|
+
* @param {PagePackage} pkg
|
|
270
|
+
* @param {string} file Relative to the package's folder of pages.
|
|
271
|
+
*/
|
|
272
|
+
export function fileId(pkg, file) {
|
|
273
|
+
return `${pkg.name}/${pkg.pages}/${file}`;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
/**
|
|
277
|
+
* The real path of a package's file, with `/` separators.
|
|
278
|
+
* @param {PagePackage} pkg
|
|
279
|
+
* @param {string} file Relative to the package's folder of pages.
|
|
280
|
+
*/
|
|
281
|
+
export function filePath(pkg, file) {
|
|
282
|
+
return `${pkg.root}/${pkg.pages}/${file}`;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
/** The app's import of Tailwind CSS, in either quotes or as a url(), with Tailwind's import options (`source(none)`). */
|
|
286
|
+
const TAILWIND_IMPORT = /@import\s+(?:url\(\s*)?(["'])tailwindcss\1\s*\)?[^;]*;?/;
|
|
287
|
+
/** The file each `@import` of a stylesheet names. */
|
|
288
|
+
const IMPORTS = /@import\s+(?:url\(\s*)?(["'])(.*?)\1/g;
|
|
289
|
+
|
|
290
|
+
/**
|
|
291
|
+
* A path from a stylesheet's folder, as Tailwind CSS resolves `@import` and `@source`: relative, with `/` separators,
|
|
292
|
+
* starting with `./` or `../`, so that no drive letter is in it on Windows, and nothing reads it as a package name.
|
|
293
|
+
* @param {string} dir
|
|
294
|
+
* @param {string} target
|
|
295
|
+
*/
|
|
296
|
+
function fromStylesheet(dir, target) {
|
|
297
|
+
const relative = slashes(path.relative(dir, target));
|
|
298
|
+
return relative.startsWith('../') || path.isAbsolute(relative) ? relative : `./${relative}`;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
/**
|
|
302
|
+
* Whether a stylesheet imports a package's own stylesheet already: by the package's name and the path in its `css`,
|
|
303
|
+
* or by a relative path to the file.
|
|
304
|
+
* @param {string} css
|
|
305
|
+
* @param {string} dir The stylesheet's folder.
|
|
306
|
+
* @param {PagePackage} pkg
|
|
307
|
+
*/
|
|
308
|
+
function importsPackageCss(css, dir, pkg) {
|
|
309
|
+
if (pkg.css === null) return false;
|
|
310
|
+
const own = `${pkg.root}/${pkg.css}`;
|
|
311
|
+
for (const [, , file = ''] of css.matchAll(IMPORTS)) {
|
|
312
|
+
if (file === `${pkg.name}/${pkg.css}`) return true;
|
|
313
|
+
if (!file.startsWith('./') && !file.startsWith('../')) continue;
|
|
314
|
+
try {
|
|
315
|
+
if (slashes(realpathSync.native(path.resolve(dir, file))) === own) return true;
|
|
316
|
+
} catch {
|
|
317
|
+
// A file that is not there is not the package's.
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
return false;
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
/**
|
|
324
|
+
* A stylesheet of the app with the packages added to its import of Tailwind CSS: an `@import` of each package's own
|
|
325
|
+
* stylesheet, unless it imports that already, and an `@source` for each of its source folders, which Tailwind would not
|
|
326
|
+
* scan otherwise (its automatic detection leaves out node_modules and every folder outside the app). They go on the
|
|
327
|
+
* line of the import, so the lines below keep their numbers. A stylesheet without that import, or one in a package, is
|
|
328
|
+
* returned as it is.
|
|
329
|
+
* @param {string} css
|
|
330
|
+
* @param {string} file The stylesheet's path, with `/` separators.
|
|
331
|
+
* @param {readonly PagePackage[]} packages
|
|
332
|
+
*/
|
|
333
|
+
export function withPackageStyles(css, file, packages) {
|
|
334
|
+
if (packages.some((pkg) => file.startsWith(`${pkg.root}/`))) return css;
|
|
335
|
+
const match = TAILWIND_IMPORT.exec(css);
|
|
336
|
+
if (!match) return css;
|
|
337
|
+
const dir = path.dirname(file);
|
|
338
|
+
const added = packages.flatMap((pkg) => [
|
|
339
|
+
...(pkg.css !== null && !importsPackageCss(css, dir, pkg) ? [`@import ${JSON.stringify(fromStylesheet(dir, `${pkg.root}/${pkg.css}`))};`] : []),
|
|
340
|
+
...pkg.sources.map((source) => `@source ${JSON.stringify(fromStylesheet(dir, `${pkg.root}/${source}`))};`),
|
|
341
|
+
]);
|
|
342
|
+
const end = match.index + match[0].length;
|
|
343
|
+
const statement = match[0].endsWith(';') ? match[0] : `${match[0]};`;
|
|
344
|
+
return `${css.slice(0, match.index)}${statement} ${added.join(' ')}${css.slice(end)}`;
|
|
345
|
+
}
|
package/vite/plugin.d.ts
CHANGED
|
@@ -11,6 +11,13 @@ export type NetjesPluginOptions = {
|
|
|
11
11
|
* Built asset URLs get it; links, forms, redirects and router calls get it from the server at runtime.
|
|
12
12
|
*/
|
|
13
13
|
basePath?: string;
|
|
14
|
+
/**
|
|
15
|
+
* Page packages, by npm name: installed packages whose `package.json` has a `netjes` field, and which the .NET app
|
|
16
|
+
* mounts at a prefix. Their convention files join the page module map and both builds, their sources and stylesheet
|
|
17
|
+
* join the app's Tailwind CSS build, and the client build records them in `dist/client/.vite/netjes.json`, where the
|
|
18
|
+
* .NET app finds them in production. Needs Tailwind CSS on.
|
|
19
|
+
*/
|
|
20
|
+
packages?: readonly string[];
|
|
14
21
|
/** Options for @vitejs/plugin-react, which netjes() includes. */
|
|
15
22
|
react?: ReactOptions;
|
|
16
23
|
/** Tailwind CSS through @tailwindcss/vite. Default true; `@import "tailwindcss"` in a CSS file a layout imports. */
|