@ckeditor/ckeditor5-dev-manual-server 61.0.0 → 61.2.0
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 +25 -2
- package/dist/index.d.ts +1 -0
- package/dist/index.js +75 -103
- package/dist/preserve-css-import-order-plugin/plugin.d.ts +22 -0
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -10,8 +10,8 @@ More information about development tools packages can be found at the following
|
|
|
10
10
|
|
|
11
11
|
## Loading translations
|
|
12
12
|
|
|
13
|
-
Pass a language code to `manualTestsPlugin()` to load and combine that language's TypeScript translation files from the configured
|
|
14
|
-
|
|
13
|
+
Pass a language code to `manualTestsPlugin()` to load and combine that language's TypeScript translation files from the configured package paths. The selected language becomes the default UI and content language for editors and contexts created by manual tests.
|
|
14
|
+
|
|
15
15
|
An explicit language in an editor or context configuration still takes precedence.
|
|
16
16
|
|
|
17
17
|
```ts
|
|
@@ -23,6 +23,29 @@ manualTestsPlugin( {
|
|
|
23
23
|
|
|
24
24
|
For example, start the manual test server in Polish using `CK_LANGUAGE=pl pnpm manual`.
|
|
25
25
|
|
|
26
|
+
## Preserving production CSS order
|
|
27
|
+
|
|
28
|
+
Vite may order extracted stylesheets differently from their source imports in multi-page production builds. Add `preserveCssImportOrderPlugin()` to keep the CSS cascade consistent with the bundled development server:
|
|
29
|
+
|
|
30
|
+
```ts
|
|
31
|
+
import { defineConfig } from 'vite';
|
|
32
|
+
import {
|
|
33
|
+
manualTestsPlugin,
|
|
34
|
+
preserveCssImportOrderPlugin
|
|
35
|
+
} from '@ckeditor/ckeditor5-dev-manual-server';
|
|
36
|
+
|
|
37
|
+
export default defineConfig( {
|
|
38
|
+
plugins: [
|
|
39
|
+
preserveCssImportOrderPlugin(),
|
|
40
|
+
manualTestsPlugin( {
|
|
41
|
+
paths: [ 'packages/*' ]
|
|
42
|
+
} )
|
|
43
|
+
]
|
|
44
|
+
} );
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
The plugin enables Rolldown's strict module execution order in development and production, which can increase the generated bundle size. During production builds, it additionally rewrites plain CSS imports into ordered JavaScript side effects.
|
|
48
|
+
|
|
26
49
|
## Changelog
|
|
27
50
|
|
|
28
51
|
See the [`CHANGELOG.md`](https://github.com/ckeditor/ckeditor5-dev/blob/master/packages/ckeditor5-dev-manual-server/CHANGELOG.md) file.
|
package/dist/index.d.ts
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
* For licensing, see LICENSE.md.
|
|
4
4
|
*/
|
|
5
5
|
export { manualTestsPlugin, type ManualTestsPluginOptions } from "./manual-test-plugin/plugin.js";
|
|
6
|
+
export { preserveCssImportOrderPlugin } from "./preserve-css-import-order-plugin/plugin.js";
|
|
6
7
|
export { ckDebugPlugin } from "./debug-plugin/plugin.js";
|
|
7
8
|
export { refreshPlugin } from "./refresh-plugin/plugin.js";
|
|
8
9
|
export { rawSvgPlugin } from "./svg-plugin/plugin.js";
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { existsSync, globSync, readFileSync, realpathSync } from "node:fs";
|
|
2
2
|
import { fileURLToPath } from "node:url";
|
|
3
3
|
import { basename, isAbsolute, posix, relative, resolve } from "node:path";
|
|
4
|
+
import { createHash } from "node:crypto";
|
|
4
5
|
//#region src/utils.ts
|
|
5
6
|
/**
|
|
6
7
|
* @license Copyright (c) 2003-2026, CKSource Holding sp. z o.o. All rights reserved.
|
|
@@ -183,10 +184,7 @@ function manualTestsPlugin(options) {
|
|
|
183
184
|
name: "ckeditor5-manual-tests",
|
|
184
185
|
config(config = {}) {
|
|
185
186
|
workspaceRoot = resolve(config.root || process.cwd());
|
|
186
|
-
return {
|
|
187
|
-
input: getManualBuildInputs(),
|
|
188
|
-
build: { rolldownOptions: { output: { strictExecutionOrder: true } } }
|
|
189
|
-
};
|
|
187
|
+
return { input: getManualBuildInputs() };
|
|
190
188
|
},
|
|
191
189
|
configResolved(config) {
|
|
192
190
|
workspaceRoot = config.root;
|
|
@@ -199,7 +197,6 @@ function manualTestsPlugin(options) {
|
|
|
199
197
|
rewriteCatalogRequest(request, getManualCatalogPublicPath());
|
|
200
198
|
next();
|
|
201
199
|
});
|
|
202
|
-
keepManualHtmlSourceFresh(server.environments.client.bundledDev?.memoryFiles, getManualPages, workspaceRoot);
|
|
203
200
|
},
|
|
204
201
|
resolveId(source) {
|
|
205
202
|
if (source == MANUAL_ENTRIES_VIRTUAL_ID) return resolvedVirtualModuleId;
|
|
@@ -248,77 +245,6 @@ function manualTestsPlugin(options) {
|
|
|
248
245
|
};
|
|
249
246
|
}
|
|
250
247
|
/**
|
|
251
|
-
* Keeps the served manual test HTML in sync with the source file while the dev server runs.
|
|
252
|
-
*
|
|
253
|
-
* Under `experimental.bundledDev`, Vite serves each manual test's HTML from an in-memory bundle
|
|
254
|
-
* produced by the rolldown dev engine. That engine emits the HTML output only during the initial
|
|
255
|
-
* build and never regenerates it when the source `.html` changes: its bundle state reports no
|
|
256
|
-
* stale output for HTML entries, `devEngine.invalidate()` throws on a non-JS module, and even a
|
|
257
|
-
* forced full build leaves the HTML memory file untouched (verified against Vite 8.2.1). A `.html`
|
|
258
|
-
* edit still triggers a full page reload, so without this the browser reloads into the same stale
|
|
259
|
-
* HTML until the server is restarted.
|
|
260
|
-
*
|
|
261
|
-
* The built-in-memory HTML rewrites the entry script, injects the asset tags and manual chrome,
|
|
262
|
-
* and copies the source `<head>` — all inside `<head>`. The markup after `</head>` is a verbatim
|
|
263
|
-
* copy of the source. So on every request we keep the freshly built `<head>` and splice in the
|
|
264
|
-
* current post-`</head>` markup read from disk. Splicing unconditionally (rather than only after a
|
|
265
|
-
* detected change) also covers sources that changed between the initial build and the first request
|
|
266
|
-
* for the page, and it is idempotent for unchanged sources. This reflects edits to the test body
|
|
267
|
-
* without re-running Vite's HTML transform or parsing the document. Any edit that would change
|
|
268
|
-
* what belongs in `<head>` is not picked up this way and still needs a server restart, because
|
|
269
|
-
* reproducing it would require re-running the build pipeline the dev engine refuses to run for HTML
|
|
270
|
-
* entries. That covers edits confined to the `<head>` (styles, meta, extra scripts) as well as
|
|
271
|
-
* adding or removing `<ck-manual-header>` in the body: toggling it changes the injected header
|
|
272
|
-
* chrome (`<meta>` and the component script) in `<head>`, so the chrome only updates after a
|
|
273
|
-
* restart even though the body splice already reflects the element itself.
|
|
274
|
-
*
|
|
275
|
-
* When `bundledDev` is not enabled the store is absent and this is a no-op: Vite's normal dev
|
|
276
|
-
* pipeline already serves fresh HTML on every request.
|
|
277
|
-
*/
|
|
278
|
-
function keepManualHtmlSourceFresh(memoryFiles, getManualPages, workspaceRoot) {
|
|
279
|
-
if (!memoryFiles) return;
|
|
280
|
-
const getBundledFile = memoryFiles.get.bind(memoryFiles);
|
|
281
|
-
memoryFiles.get = (filePath) => {
|
|
282
|
-
const file = getBundledFile(filePath);
|
|
283
|
-
if (!file || !getManualPages().has(toPublicSpecifier(filePath))) return file;
|
|
284
|
-
try {
|
|
285
|
-
const sourceFilePath = resolve(workspaceRoot, stripLeadingSlash(filePath));
|
|
286
|
-
const freshHtml = composeFreshManualHtml(file.source, readFileSync(sourceFilePath, "utf8"));
|
|
287
|
-
return freshHtml == null ? file : { source: freshHtml };
|
|
288
|
-
} catch {
|
|
289
|
-
return file;
|
|
290
|
-
}
|
|
291
|
-
};
|
|
292
|
-
}
|
|
293
|
-
/**
|
|
294
|
-
* Combines the freshly built `<head>` (asset tags and injected manual chrome) with the current
|
|
295
|
-
* post-`</head>` markup from the source file. Returns `null` when either document is missing a
|
|
296
|
-
* `</head>`, so the caller can fall back to the built output.
|
|
297
|
-
*/
|
|
298
|
-
function composeFreshManualHtml(builtHtml, sourceHtml) {
|
|
299
|
-
const builtHeadEnd = findHeadCloseTagIndex(builtHtml);
|
|
300
|
-
const sourceHeadEnd = findHeadCloseTagIndex(sourceHtml);
|
|
301
|
-
if (builtHeadEnd == -1 || sourceHeadEnd == -1) return null;
|
|
302
|
-
return builtHtml.slice(0, builtHeadEnd + 7) + sourceHtml.slice(sourceHeadEnd + 7);
|
|
303
|
-
}
|
|
304
|
-
/**
|
|
305
|
-
* Finds the index of the `</head>` closing tag, or `-1` when the document has none. A `</head>`
|
|
306
|
-
* literal may also appear earlier inside a `<head>` comment or an inline script string, so of all
|
|
307
|
-
* the occurrences preceding the `<body>` tag the last one is taken. Without a `<body>` tag (or
|
|
308
|
-
* when every occurrence follows it) the first occurrence wins, matching the pre-heuristic
|
|
309
|
-
* behavior for such malformed documents.
|
|
310
|
-
*/
|
|
311
|
-
function findHeadCloseTagIndex(html) {
|
|
312
|
-
const firstHeadCloseIndex = /<\/head>/i.exec(html)?.index ?? -1;
|
|
313
|
-
const bodyMatch = /<body[\s>]/i.exec(html);
|
|
314
|
-
if (!bodyMatch) return firstHeadCloseIndex;
|
|
315
|
-
const headClosePattern = /<\/head>/gi;
|
|
316
|
-
let lastHeadCloseIndex = -1;
|
|
317
|
-
let match;
|
|
318
|
-
while ((match = headClosePattern.exec(html)) && match.index < bodyMatch.index) lastHeadCloseIndex = match.index;
|
|
319
|
-
return lastHeadCloseIndex == -1 ? firstHeadCloseIndex : lastHeadCloseIndex;
|
|
320
|
-
}
|
|
321
|
-
/**
|
|
322
248
|
* Injection contract for the `<ck-manual-header>` component, added to `<head>` only when the
|
|
323
249
|
* page source contains the element:
|
|
324
250
|
* - a `<meta>` carrying the package name and the base-aware catalog href the component reads;
|
|
@@ -374,6 +300,78 @@ function parseRequestUrl(requestUrl) {
|
|
|
374
300
|
return URL.parse(requestUrl || "", "http://localhost");
|
|
375
301
|
}
|
|
376
302
|
//#endregion
|
|
303
|
+
//#region src/preserve-css-import-order-plugin/plugin.ts
|
|
304
|
+
/**
|
|
305
|
+
* @license Copyright (c) 2003-2026, CKSource Holding sp. z o.o. All rights reserved.
|
|
306
|
+
* For licensing, see LICENSE.md.
|
|
307
|
+
*/
|
|
308
|
+
const INJECTED_CSS_VIRTUAL_ID_PREFIX = "\0virtual:ckeditor5-preserve-css-import-order:";
|
|
309
|
+
const INJECTED_CSS_VIRTUAL_ID_SUFFIX = ".js";
|
|
310
|
+
const CSS_FILE_REGEXP = /^[^?#]+\.css$/;
|
|
311
|
+
/**
|
|
312
|
+
* Preserves the source execution order of CSS imports in manual development and production builds.
|
|
313
|
+
*
|
|
314
|
+
* Vite extracts imported CSS into assets and orders those assets by the generated chunk graph.
|
|
315
|
+
* In a multi-page build that order may differ from the source module execution order, changing
|
|
316
|
+
* the cascade between CKEditor stylesheets. This is a known Vite limitation:
|
|
317
|
+
* https://github.com/vitejs/vite/issues/4890.
|
|
318
|
+
*
|
|
319
|
+
* Until Vite handles this natively (https://github.com/vitejs/vite/pull/22541), plain CSS imports
|
|
320
|
+
* in production builds are replaced with virtual JavaScript modules. Each module loads the
|
|
321
|
+
* processed CSS through Vite's `?inline` query and appends it as a `<style>` side effect.
|
|
322
|
+
* Rolldown's `strictExecutionOrder` keeps both these production effects and Vite's native bundled
|
|
323
|
+
* development CSS effects in source order.
|
|
324
|
+
*
|
|
325
|
+
* Query-bearing CSS imports retain their explicit Vite semantics and pass through unchanged.
|
|
326
|
+
*/
|
|
327
|
+
function preserveCssImportOrderPlugin() {
|
|
328
|
+
let rewriteCssImports = false;
|
|
329
|
+
const cssFilePaths = /* @__PURE__ */ new Map();
|
|
330
|
+
return {
|
|
331
|
+
name: "ckeditor5-preserve-css-import-order",
|
|
332
|
+
config(_config, { command }) {
|
|
333
|
+
rewriteCssImports = command == "build";
|
|
334
|
+
return { build: { rolldownOptions: { output: { strictExecutionOrder: true } } } };
|
|
335
|
+
},
|
|
336
|
+
resolveId: {
|
|
337
|
+
order: "pre",
|
|
338
|
+
filter: { id: CSS_FILE_REGEXP },
|
|
339
|
+
async handler(source, importer, options) {
|
|
340
|
+
if (!rewriteCssImports) return null;
|
|
341
|
+
const resolved = await this.resolve(source, importer, {
|
|
342
|
+
...options,
|
|
343
|
+
skipSelf: true
|
|
344
|
+
});
|
|
345
|
+
if (!resolved || resolved.external) return null;
|
|
346
|
+
const cssIdHash = createHash("sha256").update(resolved.id).digest("hex");
|
|
347
|
+
const virtualId = `${INJECTED_CSS_VIRTUAL_ID_PREFIX}${cssIdHash}${INJECTED_CSS_VIRTUAL_ID_SUFFIX}`;
|
|
348
|
+
cssFilePaths.set(virtualId, resolved.id);
|
|
349
|
+
return virtualId;
|
|
350
|
+
}
|
|
351
|
+
},
|
|
352
|
+
load: {
|
|
353
|
+
filter: { id: new RegExp(`^${INJECTED_CSS_VIRTUAL_ID_PREFIX}`) },
|
|
354
|
+
handler(id) {
|
|
355
|
+
const cssFilePath = cssFilePaths.get(id);
|
|
356
|
+
if (!cssFilePath) return null;
|
|
357
|
+
return {
|
|
358
|
+
code: createCssInjectionModule(cssFilePath),
|
|
359
|
+
moduleSideEffects: true
|
|
360
|
+
};
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
};
|
|
364
|
+
}
|
|
365
|
+
function createCssInjectionModule(cssFilePath) {
|
|
366
|
+
return `
|
|
367
|
+
import css from ${JSON.stringify(`${cssFilePath}?inline`)};
|
|
368
|
+
|
|
369
|
+
const style = document.createElement( 'style' );
|
|
370
|
+
style.textContent = css;
|
|
371
|
+
document.head.appendChild( style );
|
|
372
|
+
`;
|
|
373
|
+
}
|
|
374
|
+
//#endregion
|
|
377
375
|
//#region src/debug-plugin/plugin.ts
|
|
378
376
|
/**
|
|
379
377
|
* @license Copyright (c) 2003-2026, CKSource Holding sp. z o.o. All rights reserved.
|
|
@@ -423,7 +421,6 @@ function refreshPlugin() {
|
|
|
423
421
|
configureServer(server) {
|
|
424
422
|
const { bundledDev } = server.environments.client;
|
|
425
423
|
wrapBundledDevClientSend(bundledDev);
|
|
426
|
-
wrapBundledDevFullReloads(bundledDev, server.config.root);
|
|
427
424
|
},
|
|
428
425
|
hotUpdate({ file, modules }) {
|
|
429
426
|
if (file.endsWith(".manual.html")) return modules;
|
|
@@ -444,31 +441,6 @@ function wrapBundledDevClientSend(bundledDev) {
|
|
|
444
441
|
return setupIfNeeded(client, clientId);
|
|
445
442
|
};
|
|
446
443
|
}
|
|
447
|
-
function wrapBundledDevFullReloads(bundledDev, workspaceRoot) {
|
|
448
|
-
const handleHmrOutput = bundledDev.handleHmrOutput.bind(bundledDev);
|
|
449
|
-
bundledDev.handleHmrOutput = (client, files, hmrOutput) => {
|
|
450
|
-
if (hmrOutput.type != "FullReload") return handleHmrOutput(client, files, hmrOutput);
|
|
451
|
-
if (!shouldShowManualRefreshPrompt(files)) {
|
|
452
|
-
reloadClientAfterLatestBuildOutput(bundledDev, client, files, workspaceRoot);
|
|
453
|
-
return;
|
|
454
|
-
}
|
|
455
|
-
ensureLatestBuildOutput(bundledDev);
|
|
456
|
-
client.send({
|
|
457
|
-
type: "custom",
|
|
458
|
-
event: MANUAL_REFRESH_EVENT_NAME
|
|
459
|
-
});
|
|
460
|
-
};
|
|
461
|
-
}
|
|
462
|
-
async function reloadClientAfterLatestBuildOutput(bundledDev, client, files, workspaceRoot) {
|
|
463
|
-
try {
|
|
464
|
-
await bundledDev.devEngine.ensureLatestBuildOutput();
|
|
465
|
-
} catch {}
|
|
466
|
-
const htmlFile = files.find((file) => isHtmlFile(file));
|
|
467
|
-
client.send({
|
|
468
|
-
type: "full-reload",
|
|
469
|
-
path: htmlFile ? toPublicFilePath(htmlFile, workspaceRoot) : void 0
|
|
470
|
-
});
|
|
471
|
-
}
|
|
472
444
|
function ensureLatestBuildOutput(bundledDev) {
|
|
473
445
|
try {
|
|
474
446
|
bundledDev.devEngine.ensureLatestBuildOutput().catch(() => {});
|
|
@@ -527,4 +499,4 @@ function rawSvgPlugin() {
|
|
|
527
499
|
* For licensing, see LICENSE.md.
|
|
528
500
|
*/
|
|
529
501
|
//#endregion
|
|
530
|
-
export { ckDebugPlugin, manualTestsPlugin, rawSvgPlugin, refreshPlugin, stringifyValues };
|
|
502
|
+
export { ckDebugPlugin, manualTestsPlugin, preserveCssImportOrderPlugin, rawSvgPlugin, refreshPlugin, stringifyValues };
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license Copyright (c) 2003-2026, CKSource Holding sp. z o.o. All rights reserved.
|
|
3
|
+
* For licensing, see LICENSE.md.
|
|
4
|
+
*/
|
|
5
|
+
import type { Plugin } from "vite";
|
|
6
|
+
/**
|
|
7
|
+
* Preserves the source execution order of CSS imports in manual development and production builds.
|
|
8
|
+
*
|
|
9
|
+
* Vite extracts imported CSS into assets and orders those assets by the generated chunk graph.
|
|
10
|
+
* In a multi-page build that order may differ from the source module execution order, changing
|
|
11
|
+
* the cascade between CKEditor stylesheets. This is a known Vite limitation:
|
|
12
|
+
* https://github.com/vitejs/vite/issues/4890.
|
|
13
|
+
*
|
|
14
|
+
* Until Vite handles this natively (https://github.com/vitejs/vite/pull/22541), plain CSS imports
|
|
15
|
+
* in production builds are replaced with virtual JavaScript modules. Each module loads the
|
|
16
|
+
* processed CSS through Vite's `?inline` query and appends it as a `<style>` side effect.
|
|
17
|
+
* Rolldown's `strictExecutionOrder` keeps both these production effects and Vite's native bundled
|
|
18
|
+
* development CSS effects in source order.
|
|
19
|
+
*
|
|
20
|
+
* Query-bearing CSS imports retain their explicit Vite semantics and pass through unchanged.
|
|
21
|
+
*/
|
|
22
|
+
export declare function preserveCssImportOrderPlugin(): Plugin;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ckeditor/ckeditor5-dev-manual-server",
|
|
3
|
-
"version": "61.
|
|
3
|
+
"version": "61.2.0",
|
|
4
4
|
"description": "Used to extend Vite to create a manual test server for CKEditor 5.",
|
|
5
5
|
"keywords": [],
|
|
6
6
|
"author": "CKSource (http://cksource.com/)",
|
|
@@ -25,6 +25,6 @@
|
|
|
25
25
|
],
|
|
26
26
|
"dependencies": {
|
|
27
27
|
"@ckeditor/ckeditor5-inspector": "^5.0.1",
|
|
28
|
-
"vite": "^8.2.
|
|
28
|
+
"vite": "^8.2.2"
|
|
29
29
|
}
|
|
30
30
|
}
|