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/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": { diff --git a/packages/types-builder/test/build.test.ts b/packages/types-builder/test/build.test.ts index 55f7e9f..7634476 100644 --- a/packages/types-builder/test/build.test.ts +++ b/packages/types-builder/test/build.test.ts @@ -1,5 +1,6 @@ import { describe, it } from 'node:test' import { strict as assert } from 'assert' +import { readFileSync } from 'node:fs' import * as simpleObject from './types/simple-object/index.js' import * as objectWithReference from './types/object-with-reference/index.js' @@ -34,4 +35,16 @@ describe('build.js script', () => { } }) }) + + it('should reuse the vjsf layout for the compiledLayout export', async () => { + // site-patch exports both vjsf and compiledLayout: the compiledLayout export reuses the + // layout already compiled+serialized for the vjsf component, so both must stay in sync + const compiledLayoutCode = readFileSync(import.meta.dirname + '/types/site-patch/.type/compiled-layout-fr.js', 'utf8') + .replace('/* eslint-disable */\n// @ts-nocheck\n\n', '') + .replace('export const compiledLayout =', 'const compiledLayout =') + const vjsfCode = readFileSync(import.meta.dirname + '/vjsf/vjsf-site-patch.vue', 'utf8') + + assert.ok(compiledLayoutCode.includes('const compiledLayout =')) + assert.ok(vjsfCode.includes(compiledLayoutCode), 'the compiledLayout export should be the very layout embedded in the vjsf component') + }) }) diff --git a/packages/types-builder/test/types/site-patch/schema.json b/packages/types-builder/test/types/site-patch/schema.json index 1f0db7d..5e02d6f 100644 --- a/packages/types-builder/test/types/site-patch/schema.json +++ b/packages/types-builder/test/types/site-patch/schema.json @@ -1,6 +1,6 @@ { "$id": "https://github.com/data-fair/simple-directory/site-patch", - "x-exports": ["types", "validate", "resolvedSchema", "vjsf"], + "x-exports": ["types", "validate", "resolvedSchema", "vjsf", "compiledLayout"], "title": "site-patch", "type":"object", "additionalProperties":false, 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) ) +