vite-plugin-travelm-agency
Version:
vite plugin to automatically run travelm-agency on build and watch
302 lines (301 loc) • 13.4 kB
JavaScript
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();
});
},
};
}