UNPKG

@wuchale/vite-plugin

Version:

Protobuf-like i18n from normal code

194 lines 7.71 kB
// $$ cd ../.. && npm run test import { relative, resolve } from "node:path"; import { getConfig as getConfig } from "wuchale/config"; import { AdapterHandler } from "wuchale/handler"; import { Logger } from "wuchale/log"; import { catalogVarName } from "wuchale/runtime"; const pluginName = 'wuchale'; const virtualPrefix = `virtual:${pluginName}/`; const virtualResolvedPrefix = '\0'; class Plugin { name = pluginName; #config; #locales = []; #projectRoot = ''; #server; #adapters = {}; #adaptersByLoaderPath = {}; #adaptersByCatalogPath = {}; #lastHMRTime = 0; #log; #configPath; constructor(configPath) { this.#configPath = configPath; } #init = async (mode) => { this.#config = await getConfig(this.#configPath); this.#locales = [this.#config.sourceLocale, ...this.#config.otherLocales]; this.#log = new Logger(this.#config.messages); if (Object.keys(this.#config.adapters).length === 0) { throw Error('At least one adapter is needed.'); } for (const [key, adapter] of Object.entries(this.#config.adapters)) { const handler = new AdapterHandler(adapter, key, this.#config, mode, virtualPrefix, this.#projectRoot, this.#log); await handler.init(); this.#adapters[key] = handler; this.#adaptersByLoaderPath[resolve(handler.loaderPath)] = handler; for (const fname of Object.keys(handler.catalogPathsToLocales)) { this.#adaptersByCatalogPath[fname] = handler; } } }; configResolved = async (config) => { let mode; if (config.env.DEV) { mode = 'dev'; } else { mode = 'prod'; } this.#projectRoot = config.root; await this.#init(mode); }; configureServer = (server) => { this.#server = server; // initial load for (const [key, adapter] of Object.entries(this.#adapters)) { for (const loc of this.#locales) { const event = adapter.virtModEvent(loc, null); server.ws.on(event, (payload, client) => { const eventSend = adapter.virtModEvent(loc, payload.loadID); if (!this.#config.adapters[key].granularLoad) { client.send(eventSend, adapter.compiled[loc].items); return; } const compiled = adapter.granularStateByID[payload.loadID].compiled[loc]; client.send(eventSend, compiled.items); }); } } }; #sendUpdateToClient = (adapter, locales) => { if (!this.#server) { // maybe not in dev mode return; } for (const loc of locales) { if (!this.#config.adapters[adapter.key].granularLoad) { this.#server.ws.send(adapter.virtModEvent(loc, null), adapter.compiled[loc].items); return; } for (const [loadID, state] of Object.entries(adapter.granularStateByID)) { const eventName = adapter.virtModEvent(loc, loadID); this.#server.ws.send(eventName, state.compiled[loc].items); } } }; handleHotUpdate = async (ctx) => { if (!(ctx.file in this.#adaptersByCatalogPath)) { this.#lastHMRTime = performance.now(); return; } if (performance.now() - this.#lastHMRTime < 1000) { // too soon return; } // PO file edit -> JS HMR const adapter = this.#adaptersByCatalogPath[ctx.file]; const loc = adapter.catalogPathsToLocales[ctx.file]; await adapter.loadCatalogNCompile(loc); this.#sendUpdateToClient(adapter, [loc]); let invalidatedLoadIDs = [null]; if (this.#config.adapters[adapter.key].granularLoad) { invalidatedLoadIDs = Object.keys(adapter.granularStateByID); } const allModules = []; const invalidatedModules = new Set(); for (const loadID of invalidatedLoadIDs) { const moduleID = `${virtualResolvedPrefix}${adapter.virtModEvent(loc, loadID)}`; const modules = ctx.server.moduleGraph.getModulesByFile(moduleID); for (const module of modules) { ctx.server.moduleGraph.invalidateModule(module, invalidatedModules, ctx.timestamp, true); allModules.push(module); } } return allModules; }; resolveId = (source, importer) => { if (!source.startsWith(virtualPrefix)) { return null; } return `${virtualResolvedPrefix}${source}?importer=${importer}`; }; load = (id) => { const prefix = virtualResolvedPrefix + virtualPrefix; if (!id.startsWith(prefix)) { return null; } const [path, importer] = id.slice(prefix.length).split('?importer='); const [part, ...rest] = path.split('/'); if (part === 'catalog') { const [adapterKey, loadID, locale] = rest; const adapter = this.#adapters[adapterKey]; if (adapter == null) { this.#log.error(`Adapter not found for key: ${adapterKey}`); return null; } const module = adapter.loadDataModule(locale, loadID); if (this.#server == null) { // prod build return module; } const eventSend = adapter.virtModEvent(locale, loadID); const eventReceive = adapter.virtModEvent(locale, null); return ` ${module} if (import.meta.hot) { import.meta.hot.on('${eventSend}', newData => { for (let i = 0; i < newData.length; i++) { if (JSON.stringify(${catalogVarName}[i]) !== JSON.stringify(newData[i])) { ${catalogVarName}[i] = newData[i] } } }) import.meta.hot.send('${eventReceive}'${loadID == null ? '' : `, {loadID: '${loadID}'}`}) } `; } if (part === 'locales') { return `export const locales = ['${this.#locales.join("', '")}']`; } if (part !== 'proxy') { this.#log.error(`Unknown virtual request: ${id}`); return null; } // loader proxy const adapter = this.#adaptersByLoaderPath[importer]; if (adapter == null) { console.log(id); this.#log.error(`Adapter not found for filename: ${importer}`); return; } if (rest[0] === 'sync') { return adapter.getProxySync(); } return adapter.getProxy(); }; #transformHandler = async (code, id) => { if (!this.#config.hmr) { return {}; } const filename = relative(this.#projectRoot, id); for (const adapter of Object.values(this.#adapters)) { if (adapter.fileMatches(filename)) { const { catalogChanged, ...output } = await adapter.transform(code, filename); if (catalogChanged && this.#lastHMRTime > 0) { this.#sendUpdateToClient(adapter, this.#locales); } return output; } } return {}; }; transform = { order: 'pre', handler: this.#transformHandler }; } export const wuchale = (configPath) => new Plugin(configPath); //# sourceMappingURL=index.js.map