UNPKG

vite-plugin-travelm-agency

Version:

vite plugin to automatically run travelm-agency on build and watch

302 lines (301 loc) 13.4 kB
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; import fs from "fs/promises"; import path from "path"; import * as T from "travelm-agency"; import acceptLanguage from "accept-language"; const getTranslationFilePaths = (dir) => __awaiter(void 0, void 0, void 0, function* () { const files = yield fs.readdir(dir); return files.map((file) => path.resolve(dir, file)); }); function addDefaults(options) { var _a, _b; const translationDir = options.translationDir || "translations"; const elmPath = options.elmPath || "src/Translations.elm"; const generatorMode = options.generatorMode || "inline"; const i18nArgFirst = !!options.i18nArgFirst; const addContentHash = options.addContentHash === undefined ? true : options.addContentHash; const jsonPath = options.jsonPath || "i18n"; const prefixFileIdentifier = !!options.prefixFileIdentifier; const customHtmlModule = (_a = options.customHtmlModule) !== null && _a !== void 0 ? _a : "Html"; const customHtmlAttributesModule = (_b = options.customHtmlAttributesModule) !== null && _b !== void 0 ? _b : `${customHtmlModule}.Attributes`; return { translationDir, elmPath, generatorMode, i18nArgFirst, addContentHash, jsonPath, prefixFileIdentifier, defaultLanguage: options.defaultLanguage, customHtmlModule, customHtmlAttributesModule, }; } function toTravelmAgencyOptions({ generatorMode, translationDir, elmPath, i18nArgFirst, addContentHash, prefixFileIdentifier, jsonPath, customHtmlModule, customHtmlAttributesModule, }) { return generatorMode === "inline" ? { translationDir, elmPath, generatorMode, i18nArgFirst, addContentHash, devMode: true, prefixFileIdentifier, customHtmlModule, customHtmlAttributesModule, } : { translationDir, elmPath, generatorMode, i18nArgFirst, addContentHash, jsonPath, devMode: true, prefixFileIdentifier, customHtmlModule, customHtmlAttributesModule, }; } export function travelmAgencyPlugin(...options) { const withDefaults = options.map(addDefaults); const virtualModuleId = "virtual:travelm-agency"; const resolvedVirtualModuleId = "\0" + virtualModuleId; const jsonFiles = new Map(); const fileNameToReqPaths = new Map(); const htmls = new Map(); let triggerReload = () => { }; let activeLanguage; function languageFromJsonFilePath(path) { const segments = path.split("/"); const lastSegment = segments[segments.length - 1]; return lastSegment.split(".")[1]; } function isTranslationFileActive(path) { return activeLanguage === languageFromJsonFilePath(path); } function runTravelmAgency(opts, translationFilePaths, emitFile) { return __awaiter(this, void 0, void 0, function* () { const devMode = emitFile === undefined; const makeSureDirExists = fs.mkdir(path.dirname(opts.elmPath), { recursive: true, }); const travelmAgency = T.createInstance(); yield travelmAgency.sendTranslations(translationFilePaths, devMode); const responseContent = yield travelmAgency.finishModule(Object.assign(Object.assign({}, toTravelmAgencyOptions(opts)), { devMode })); yield makeSureDirExists; const shouldBeWritten = yield fs .readFile(opts.elmPath, { encoding: "utf-8", }) .then((data) => data !== responseContent.elmFile) .catch(() => true); if (shouldBeWritten) { yield fs.writeFile(opts.elmPath, responseContent.elmFile); } if (opts.generatorMode === "dynamic") { responseContent.optimizedJson.forEach((file) => { const expectedRequestPath = "/" + path .normalize(`${opts.jsonPath}/${file.filename}`) .replace(path.sep, "/"); if (opts.addContentHash) { const [identifier, language, _hash, _ext] = file.filename.split("."); const fileNameWithoutHash = [identifier, language].join("."); const oldReqPath = fileNameToReqPaths.get(fileNameWithoutHash); oldReqPath && jsonFiles.delete(oldReqPath[0]); fileNameToReqPaths.set(fileNameWithoutHash, [ expectedRequestPath, opts, ]); } else { const [identifier, language, _ext] = file.filename.split("."); const fileNameWithoutHash = [identifier, language].join("."); fileNameToReqPaths.set(fileNameWithoutHash, [ expectedRequestPath, opts, ]); } jsonFiles.set(expectedRequestPath, file.content); if (emitFile !== undefined) { emitFile(file); } }); } }); } let languages; function getLanguages() { if (languages) { return languages; } const temp = new Set(Array.from(fileNameToReqPaths.keys()) .map((filename) => filename.split(".")) .map((segments) => segments[segments.length - 1])); languages = temp; const defaultLanguage = options[0].defaultLanguage; if (defaultLanguage !== undefined) { acceptLanguage.languages([defaultLanguage, ...temp]); } return temp; } return { name: "travelm-agency-plugin", buildStart: function () { return __awaiter(this, void 0, void 0, function* () { for (const opts of withDefaults) { const filePaths = yield getTranslationFilePaths(opts.translationDir); function emitFile(file) { if (opts.generatorMode === "inline") { throw new Error("We somehow tried to emit a file in inline mode. This should not happen."); } this.emitFile({ type: "asset", fileName: path.join(opts.jsonPath, file.filename), source: file.content, }); } const emit = this.meta.watchMode ? undefined : emitFile.bind(this); yield runTravelmAgency(opts, filePaths, emit); } }); }, handleHotUpdate: function (_a) { return __awaiter(this, arguments, void 0, function* ({ file }) { for (const opts of withDefaults) { const relativePath = path.relative(path.resolve(opts.translationDir), file); if (relativePath.startsWith("..")) { return; } const filePaths = yield getTranslationFilePaths(opts.translationDir); yield runTravelmAgency(opts, filePaths); if (isTranslationFileActive(relativePath)) { triggerReload(); } // Multiple apps should not overlap break; } }); }, resolveId(id) { if (id !== virtualModuleId) { return; } return resolvedVirtualModuleId; }, load(id) { if (id !== resolvedVirtualModuleId) { return; } return `export default { ${[...fileNameToReqPaths.entries()] .map(([key, value]) => `'${key}': '${value}'`) .join(",")} }`; }, transformIndexHtml: { order: "post", handler(html, { path: htmlPath }) { const regexp = /__TRAVELM_AGENCY_([^_]*(_[^_]+)*)__/g; const matches = Array.from(html.matchAll(regexp)); if (matches.length == 0) { return; } const languages = getLanguages(); const baseDir = path.dirname(htmlPath); let defaultHtml = ""; for (const language of languages.keys()) { let langFileName = path.join(language, baseDir, path.basename(htmlPath)); let newHtml = ""; let lastIndex = 0; let options = undefined; for (const match of matches) { newHtml += html.slice(lastIndex, match.index); const bundleName = match[1]; const reqPath = fileNameToReqPaths.get(`${bundleName}.${language}`); if (!reqPath) { throw new Error(`Unknown bundleName/language '${bundleName}'/'${language}'. Correct syntax is __TRAVELM_AGENCY_bundleName__.`); } if (options && reqPath[1].elmPath !== options.elmPath) { throw new Error(`Attempted to include bundles from two seperate applications: ${options.elmPath}, ${reqPath[1].elmPath}`); } options = reqPath[1]; const json = jsonFiles.get(reqPath[0]); if (!json) { throw new Error(`Could not find generated json for path ${reqPath}`); } newHtml += json; lastIndex = match.index + match[0].length; } if (!options) { throw new Error("Should be impossible because we checked for matches.length before"); } newHtml += html.slice(lastIndex); if (language === (activeLanguage !== null && activeLanguage !== void 0 ? activeLanguage : options.defaultLanguage)) { defaultHtml = newHtml; } else { htmls.set(langFileName, newHtml); } } if (defaultHtml === "") { const someLanguage = languages.keys().next().value; throw new Error(`Please set the "defaultLanguage" plugin option to one of your languages, e.g. to '${someLanguage}'`); } return defaultHtml; }, }, generateBundle: { order: "post", handler() { return __awaiter(this, void 0, void 0, function* () { htmls.forEach((html, path) => { this.emitFile({ type: "asset", fileName: path, source: html, }); }); }); }, }, configureServer(server) { triggerReload = () => server.ws.send({ type: "full-reload", path: "*" }); server.middlewares.use((req, res, next) => { var _a; if (!req.url) { next(); return; } const i18nFileContent = jsonFiles.get(req.url); if (i18nFileContent) { activeLanguage = languageFromJsonFilePath(req.url); res.setHeader("content-type", "application/json"); res.write(i18nFileContent); res.end(); return; } const pathSplitBySlash = req.url.split("/"); const language = pathSplitBySlash[1]; if (getLanguages().has(language)) { activeLanguage = language; req.url = req.url.replace("/" + language + "/", "/"); } else { activeLanguage = (_a = acceptLanguage.get(req.headers["accept-language"])) !== null && _a !== void 0 ? _a : undefined; } next(); }); }, }; }