From 13ad45083ef69784c61ff0e7446bc7dc80a7a229 Mon Sep 17 00:00:00 2001 From: BatLeDev Date: Thu, 2 Jul 2026 10:31:51 +0200 Subject: [PATCH 1/3] perf(types-builder): reuse vjsf layout for compiledLayout export A schema exporting both `vjsf` and `compiledLayout` compiled and serialized its layout twice. The vjsf compiler now exposes the serialized compiled layout via compileParts(), so cache it per locale during the vjsf export and reuse it for the compiledLayout export instead of compiling again. Output is byte-identical. Requires @koumoul/vjsf-compiler >=1.5.0 (compileParts); peer dependency bumped. --- packages/types-builder/build.ts | 56 +++++++++++++++++------------ packages/types-builder/package.json | 2 +- 2 files changed, 35 insertions(+), 23 deletions(-) diff --git a/packages/types-builder/build.ts b/packages/types-builder/build.ts index e2e00dc..31007d7 100755 --- a/packages/types-builder/build.ts +++ b/packages/types-builder/build.ts @@ -127,6 +127,9 @@ const main = async (dir: string, options: TypesBuilderOptions) => { let importsCode = '/* eslint-disable */\n\n' let code = '' let dtsCode = '' + // serialized compiled layouts produced by the vjsf export (keyed by locale), so the + // compiledLayout export can reuse them instead of compiling+serializing a second time + const builtVjsfLayoutCodes: Record = {} if (existsSync(path.join(dir, '.type'))) rmSync(path.join(dir, '.type'), { recursive: true }) mkdirSync(path.join(dir, '.type')) @@ -282,7 +285,7 @@ export declare function returnValid(data: any, options?: import('${validationImp let compName = key const vjsfDir = path.resolve(options.vjsfDir) - const compileVjsf = (await import('@koumoul/vjsf-compiler')).compile + const { compileParts } = await import('@koumoul/vjsf-compiler') const { resolveXI18n } = await import('@json-layout/core') for (const locale of vjsfLocales) { @@ -301,7 +304,8 @@ export declare function returnValid(data: any, options?: import('${validationImp if (schema.$id) delete otherSchemas[schema.$id] schemaVjsfOpts.ajvOptions = { schemas: otherSchemas } - const vjsfCode = await compileVjsf(schema, { locale, ...schemaVjsfOpts }) + const { code: vjsfCode, compiledLayoutCode } = await compileParts(schema, { locale, ...schemaVjsfOpts }) + builtVjsfLayoutCodes[locale] = compiledLayoutCode const vjsfFilePath = path.join(vjsfDir, vjsfLocales.length > 1 ? `vjsf-${compName}-${locale}.vue` : `vjsf-${compName}.vue`) console.log(` vjsf ${locale} component path : ${vjsfFilePath}`) writeFileSync(vjsfFilePath, vjsfCode) @@ -359,27 +363,35 @@ const emit = defineEmits(emits) const { resolveXI18n } = await import('@json-layout/core') for (const locale of vjsfLocales) { - const schemaVjsfOpts = { ...schema['x-vjsf'] } - delete schemaVjsfOpts.compName - console.log(` compiledLayout ${locale} options: ${JSON.stringify(schemaVjsfOpts)}`) - const otherSchemas = { ...schemas } - for (const [key, otherSchema] of Object.entries(schemas)) { - if (key === schema.$id) continue - otherSchemas[key] = clone(otherSchema) - resolveXI18n(otherSchemas[key], locale) - } - if (schema.$id) delete otherSchemas[schema.$id] - schemaVjsfOpts.ajvOptions = { schemas: otherSchemas } - - const fullOptions = { pluginsImports: [] as string[], webmcp: false, locale, ...schemaVjsfOpts } - for (const pluginImport of fullOptions.pluginsImports) { - const componentInfo = (await import(pluginImport + '/info.js')).default - fullOptions.components = fullOptions.components ?? {} - fullOptions.components[componentInfo.name] = componentInfo - } + // reuse the layout already compiled+serialized by the vjsf export when present + // (the vjsf compiler embeds the very same serialized compiled layout), otherwise + // compile it now + let compiledLayoutCode = builtVjsfLayoutCodes[locale] + if (compiledLayoutCode) { + console.log(` compiledLayout ${locale}: reusing the layout compiled for the vjsf export`) + } else { + const schemaVjsfOpts = { ...schema['x-vjsf'] } + delete schemaVjsfOpts.compName + console.log(` compiledLayout ${locale} options: ${JSON.stringify(schemaVjsfOpts)}`) + const otherSchemas = { ...schemas } + for (const [key, otherSchema] of Object.entries(schemas)) { + if (key === schema.$id) continue + otherSchemas[key] = clone(otherSchema) + resolveXI18n(otherSchemas[key], locale) + } + if (schema.$id) delete otherSchemas[schema.$id] + schemaVjsfOpts.ajvOptions = { schemas: otherSchemas } - const compiledLayout = compileLayout(schema, fullOptions) - let compiledLayoutCode = await serializeCompiledLayout(compiledLayout) + const fullOptions = { pluginsImports: [] as string[], webmcp: false, locale, ...schemaVjsfOpts } + for (const pluginImport of fullOptions.pluginsImports) { + const componentInfo = (await import(pluginImport + '/info.js')).default + fullOptions.components = fullOptions.components ?? {} + fullOptions.components[componentInfo.name] = componentInfo + } + + const compiledLayout = compileLayout(schema, fullOptions) + compiledLayoutCode = await serializeCompiledLayout(compiledLayout) + } // The serialized code declares `const compiledLayout = {...}`. // Make it an export. compiledLayoutCode = compiledLayoutCode.replace('const compiledLayout =', 'export const compiledLayout =') diff --git a/packages/types-builder/package.json b/packages/types-builder/package.json index 54e44be..2c2102d 100644 --- a/packages/types-builder/package.json +++ b/packages/types-builder/package.json @@ -9,7 +9,7 @@ "build": "cd .. && npm run build" }, "peerDependencies": { - "@koumoul/vjsf-compiler": "1" + "@koumoul/vjsf-compiler": "^1.5.0" }, "peerDependenciesMeta": { "@koumoul/vjsf-compiler": { From f0295301422364ffcb0cdace1be06fc3a08d12b7 Mon Sep 17 00:00:00 2001 From: BatLeDev Date: Mon, 20 Jul 2026 16:09:59 +0200 Subject: [PATCH 2/3] chore(deps): bump @koumoul/vjsf-compiler to ^1.5.0 The types-builder peer dependency was raised to ^1.5.0 for `compileParts()`, but the root dev dependency and the lockfile still resolved 1.2.3, so an `npm ci` install made the build fail with `compileParts is not a function`. Regenerates the vjsf test fixture, whose i18n messages changed with the new compiler. --- package-lock.json | 29 ++++++++-------- package.json | 2 +- .../test/vjsf/vjsf-site-patch.vue | 33 +++++++------------ 3 files changed, 28 insertions(+), 36 deletions(-) diff --git a/package-lock.json b/package-lock.json index 27d3ee0..06b23ff 100644 --- a/package-lock.json +++ b/package-lock.json @@ -22,7 +22,7 @@ "devDependencies": { "@commitlint/cli": "^18.2.0", "@commitlint/config-conventional": "^18.1.0", - "@koumoul/vjsf-compiler": "^1.0.0", + "@koumoul/vjsf-compiler": "^1.5.0", "@types/cookie": "^0.6.0", "@types/debug": "^4.1.10", "@types/eslint": "^9.6.1", @@ -793,9 +793,9 @@ "license": "MIT" }, "node_modules/@json-layout/core": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/@json-layout/core/-/core-2.4.0.tgz", - "integrity": "sha512-Hv+rlY4RLn5x412bh6zHHwF/52zfMCVm9t8k61MoC9pd27cvc+V8ymLXIp8nwN4jqUQKzCxXNfj2Fe42+spA9Q==", + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/@json-layout/core/-/core-2.8.1.tgz", + "integrity": "sha512-pOn7MaCmDGdvkN+j8FyjIiot1L1Rjx/zc7x6HkgiaJj/nxHsP10kdE4d0BgE60/H9FwgMxsFaRf34s0CzixIMg==", "devOptional": true, "license": "MIT", "dependencies": { @@ -804,12 +804,13 @@ "ajv-formats": "^3.0.1", "ajv-i18n": "^4.2.0", "debug": "^4.3.4", + "fast-deep-equal": "^3.1.3", "immer": "^10.0.3", "magicast": "^0.3.3", "marked": "^15.0.7" }, "peerDependencies": { - "@json-layout/vocabulary": "^2.12.0" + "@json-layout/vocabulary": "^2.13.1" } }, "node_modules/@json-layout/core/node_modules/marked": { @@ -826,9 +827,9 @@ } }, "node_modules/@json-layout/vocabulary": { - "version": "2.12.0", - "resolved": "https://registry.npmjs.org/@json-layout/vocabulary/-/vocabulary-2.12.0.tgz", - "integrity": "sha512-cykB9IDukrMGp87JcABmrzJ/56EK5k1OtnbZfDd+derOwDVUr+6sR1WxMZ9tCLBlf7k9AwtkiA1499u9CwEO3g==", + "version": "2.13.1", + "resolved": "https://registry.npmjs.org/@json-layout/vocabulary/-/vocabulary-2.13.1.tgz", + "integrity": "sha512-DYEPXu4MmPJkok5wOnezKIGM3tLU2iuyQ+YaiLw6j6RFiY2iBJapOIBVV3+BTiAeH+dTbn95Glf/W12tv9OS0w==", "devOptional": true, "license": "MIT", "dependencies": { @@ -839,14 +840,14 @@ } }, "node_modules/@koumoul/vjsf-compiler": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/@koumoul/vjsf-compiler/-/vjsf-compiler-1.2.3.tgz", - "integrity": "sha512-fsN0+paFY3v0iZk/O4mPREKWInkAnrLBBfqgcoSxIgworq77t/J8AbCby3lFtwSChrEZ4lUBEgfJ0drxe5wN2w==", + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@koumoul/vjsf-compiler/-/vjsf-compiler-1.5.0.tgz", + "integrity": "sha512-EpqwL2XFSWKohpjYoqq1LpSLBlidnjUjV27cYzhv7hmu6wsKDTw5TUZE9DWqbV6O6XAaOTbyLdN2Bx8EN1P3+w==", "devOptional": true, "license": "MIT", "dependencies": { - "@json-layout/core": "^2.0.0", - "@json-layout/vocabulary": "^2.8.0", + "@json-layout/core": "~2.8.1", + "@json-layout/vocabulary": "~2.13.1", "debug": "^4.3.4", "ejs": "^3.1.9" } @@ -8473,7 +8474,7 @@ "df-build-types": "build.js" }, "peerDependencies": { - "@koumoul/vjsf-compiler": "1" + "@koumoul/vjsf-compiler": "^1.5.0" }, "peerDependenciesMeta": { "@koumoul/vjsf-compiler": { diff --git a/package.json b/package.json index 28b1395..d21f819 100644 --- a/package.json +++ b/package.json @@ -31,7 +31,7 @@ "devDependencies": { "@commitlint/cli": "^18.2.0", "@commitlint/config-conventional": "^18.1.0", - "@koumoul/vjsf-compiler": "^1.0.0", + "@koumoul/vjsf-compiler": "^1.5.0", "@types/cookie": "^0.6.0", "@types/debug": "^4.1.10", "@types/eslint": "^9.6.1", diff --git a/packages/types-builder/test/vjsf/vjsf-site-patch.vue b/packages/types-builder/test/vjsf/vjsf-site-patch.vue index 26a1190..e18a37d 100644 --- a/packages/types-builder/test/vjsf/vjsf-site-patch.vue +++ b/packages/types-builder/test/vjsf/vjsf-site-patch.vue @@ -762,7 +762,7 @@ return errors === 0; validate27.evaluated = {"dynamicProps":true,"dynamicItems":false}; const schema40 = {"$id":"export2","$ref":"https://github.com/data-fair/simple-directory/site-patch#"}; -const schema41 = {"$id":"https://github.com/data-fair/simple-directory/site-patch","x-exports":["types","validate","resolvedSchema","vjsf"],"title":"site-patch","type":"object","additionalProperties":false,"required":["_id","authMode"],"properties":{"_id":{"readOnly":true,"$ref":"https://github.com/data-fair/simple-directory/site#/properties/_id","__pointer":"https://github.com/data-fair/simple-directory/site-patch#/properties/_id","errorMessage":{}},"authMode":{"$ref":"https://github.com/data-fair/simple-directory/site#/properties/authMode","__pointer":"https://github.com/data-fair/simple-directory/site-patch#/properties/authMode","errorMessage":{"oneOf":"choisissez une valeur"}},"authProviders":{"$ref":"https://github.com/data-fair/simple-directory/site#/properties/authProviders","__pointer":"https://github.com/data-fair/simple-directory/site-patch#/properties/authProviders","errorMessage":{}}},"__pointer":"https://github.com/data-fair/simple-directory/site-patch#","errorMessage":{"required":{"_id":"information obligatoire","authMode":"information obligatoire"}}}; +const schema41 = {"$id":"https://github.com/data-fair/simple-directory/site-patch","x-exports":["types","validate","resolvedSchema","vjsf","compiledLayout"],"title":"site-patch","type":"object","additionalProperties":false,"required":["_id","authMode"],"properties":{"_id":{"readOnly":true,"$ref":"https://github.com/data-fair/simple-directory/site#/properties/_id","__pointer":"https://github.com/data-fair/simple-directory/site-patch#/properties/_id","errorMessage":{}},"authMode":{"$ref":"https://github.com/data-fair/simple-directory/site#/properties/authMode","__pointer":"https://github.com/data-fair/simple-directory/site-patch#/properties/authMode","errorMessage":{"oneOf":"choisissez une valeur"}},"authProviders":{"$ref":"https://github.com/data-fair/simple-directory/site#/properties/authProviders","__pointer":"https://github.com/data-fair/simple-directory/site-patch#/properties/authProviders","errorMessage":{}}},"__pointer":"https://github.com/data-fair/simple-directory/site-patch#","errorMessage":{"required":{"_id":"information obligatoire","authMode":"information obligatoire"}}}; const schema42 = {"type":"string","__pointer":"https://github.com/data-fair/simple-directory/site#/properties/_id","errorMessage":{}}; const schema43 = {"default":"onlyBackOffice","title":"Mode d'authentification","type":"string","oneOf":[{"const":"onlyLocal","title":"uniquement sur le site lui même"},{"const":"onlyBackOffice","title":"uniquement sur le back-office"},{"const":"ssoBackOffice","title":"sur le site et sur le back-office par SSO"}],"__pointer":"https://github.com/data-fair/simple-directory/site#/properties/authMode","errorMessage":{"oneOf":"choisissez une valeur"}}; const schema44 = {"type":"array","title":"Fournisseurs d'identité (SSO)","items":{"type":"object","required":["title","type"],"properties":{"title":{"type":"string","title":"Nom","__pointer":"https://github.com/data-fair/simple-directory/site#/properties/authProviders/items/properties/title","errorMessage":{}},"color":{"type":"string","title":"Couleur","x-display":"color-picket","__pointer":"https://github.com/data-fair/simple-directory/site#/properties/authProviders/items/properties/color","errorMessage":{}},"img":{"type":"string","title":"Lien vers logo (petite taille)","__pointer":"https://github.com/data-fair/simple-directory/site#/properties/authProviders/items/properties/img","errorMessage":{}}},"oneOf":[{"type":"object","title":"OpenID Connect","required":["discovery","client"],"properties":{"type":{"type":"string","title":"Type de founisseur","const":"oidc","__pointer":"https://github.com/data-fair/simple-directory/site#/properties/authProviders/items/oneOf/0/properties/type","errorMessage":{}},"discovery":{"type":"string","title":"URL de découverte OICD","description":"probablement de la forme http://mon-fournisseur/.well-known/openid-configuration","__pointer":"https://github.com/data-fair/simple-directory/site#/properties/authProviders/items/oneOf/0/properties/discovery","errorMessage":{}},"client":{"type":"object","required":["id","secret"],"properties":{"id":{"type":"string","title":"Identifiant du client","__pointer":"https://github.com/data-fair/simple-directory/site#/properties/authProviders/items/oneOf/0/properties/client/properties/id","errorMessage":{}},"secret":{"type":"string","title":"Secret","x-display":"textarea","writeOnly":true,"__pointer":"https://github.com/data-fair/simple-directory/site#/properties/authProviders/items/oneOf/0/properties/client/properties/secret","errorMessage":{}}},"__pointer":"https://github.com/data-fair/simple-directory/site#/properties/authProviders/items/oneOf/0/properties/client","errorMessage":{"required":{"id":"information obligatoire","secret":"information obligatoire"}}}},"__pointer":"https://github.com/data-fair/simple-directory/site#/properties/authProviders/items/oneOf/0","errorMessage":{"required":{"discovery":"information obligatoire","client":"information obligatoire"}}}],"__pointer":"https://github.com/data-fair/simple-directory/site#/properties/authProviders/items","errorMessage":{"oneOf":"choisissez une valeur","required":{"title":"information obligatoire"}}},"__pointer":"https://github.com/data-fair/simple-directory/site#/properties/authProviders","errorMessage":{}}; @@ -2018,31 +2018,13 @@ const compiledLayout = { confirm: "Confirmer", close: "Fermer", duplicate: "Dupliquer", + insertAfter: "Insérer après", copy: "Copier", paste: "Coller", sort: "Trier", up: "Décaler vers le haut", down: "Décaler vers le bas", showHelp: "Afficher un message d'aide", - mdeLink1: "[titre du lien", - mdeLink2: "](adresse du lien)", - mdeImg1: "![](", - mdeImg2: "adresse de l'image)", - mdeTable1: "", - mdeTable2: "\n\n| Colonne 1 | Colonne 2 | Colonne 3 |\n| -------- | -------- | -------- |\n| Texte | Texte | Texte |\n\n", - bold: "Gras", - italic: "Italique", - heading: "Titre", - quote: "Citation", - unorderedList: "Liste à puce", - orderedList: "Liste numérotée", - createLink: "Créer un lien", - insertImage: "Insérer une image", - createTable: "Créer un tableau", - preview: "Preview", - mdeGuide: "Syntax documentation", - undo: "Défaire", - redo: "Refaire", default: "défaut : ", name: "nom : ", examples: "Exemples : ", @@ -2117,7 +2099,7 @@ const compiledLayout = { items: { type: "string", - enum: ["add", "edit", "delete", "sort", "duplicate", "copy", "paste"] + enum: ["add", "edit", "delete", "sort", "duplicate", "insertAfter", "copy", "paste"] } }, @@ -2166,6 +2148,10 @@ const compiledLayout = { type: "string" }, + insertAfter: { + type: "string" + }, + sort: { type: "string" } @@ -2452,6 +2438,10 @@ const compiledLayout = { type: "boolean" }, + autocomplete: { + type: "boolean" + }, + oneOfItems: { type: "array", @@ -2505,6 +2495,7 @@ const { el, statefulLayout, stateTree } = useVjsf( null, computed(() => compiledLayout) ) +