tiddlywiki-plugin-dev
Version:
[](https://github.com/tiddly-gittly)
351 lines (350 loc) • 11.7 kB
JavaScript
import fs from "fs";
import path from "path";
import { fileURLToPath } from "url";
import chalk from "chalk";
import sha256 from "sha256";
import esbuild from "esbuild";
import UglifyJS from "uglify-js";
import CleanCSS from "clean-css";
import cliProgress from "cli-progress";
import browserslist from "browserslist";
import postCssPlugin from "esbuild-style-plugin";
import tailwindcssPostcss from "@tailwindcss/postcss";
import autoprefixer from "autoprefixer";
import esbuildSvelte from "esbuild-svelte";
import sveltePreprocess from "svelte-preprocess";
import { esbuildPluginBrowserslist } from "esbuild-plugin-browserslist";
import { walkFilesSync } from "./utils.js";
const nodejsBuiltinModules = [
"assert",
"buffer",
"child_process",
"cluster",
"crypto",
"dgram",
"dns",
"domain",
"events",
"fs",
"fsevents",
"http",
"https",
"net",
"os",
"path",
"punycode",
"querystring",
"readline",
"stream",
"string_decoder",
"timers",
"tls",
"tty",
"url",
"util",
"v8",
"vm",
"zlib"
];
const moduleDir = path.dirname(fileURLToPath(import.meta.url));
const injectPath = path.resolve(moduleDir, "esbuild-inject.js");
const rootPath = process.cwd();
const pluginCache = {};
const UglifyJSOption = {
warnings: false,
v8: true,
ie: true,
webkit: true
};
const cleanCSS = new CleanCSS({
compatibility: "ie9",
level: 2
});
const minifyTiddler = (tiddler) => {
const { text, type } = tiddler;
try {
if (type === "application/javascript") {
const minified = UglifyJS.minify(text, UglifyJSOption).code;
if (minified !== void 0) {
return {
...tiddler,
text: minified
};
}
} else if (type === "text/css") {
const minified = cleanCSS.minify(text).styles;
if (minified !== void 0) {
return {
...tiddler,
text: minified
};
}
}
} catch (e) {
console.error(e);
console.error(`Failed to minify ${tiddler.title}.`);
}
return tiddler;
};
const rebuild = async ($tw, pluginsDir, updatePaths = [], devMode = true, excludeFilter) => {
const baseDir = path.resolve(pluginsDir);
if (!fs.existsSync(baseDir)) {
return [];
}
const tailwindConfigPath = path.resolve(".", "tailwind.config.js");
if (!fs.existsSync(tailwindConfigPath)) {
fs.writeFileSync(
tailwindConfigPath,
[
"module.exports = {",
" content: ['./src/**/*.{mjs,cjs,js,ts,jsx,tsx}'],",
" theme: { extend: {} },",
" plugins: [],",
"};"
].join("\n"),
"utf-8"
);
}
console.log(chalk.green.bold("Compiling..."));
const bar = new cliProgress.SingleBar(
{
format: `${chalk.green("{bar}")} {percentage}% | {plugin}`,
stopOnComplete: true
},
cliProgress.Presets.shades_classic
);
const updateDirs = Array.from(
new Set(
updatePaths.filter((file) => file).map((file) => path.resolve(path.dirname(file)))
)
);
const pluginDirs = fs.readdirSync(baseDir).map((dirname) => path.resolve(baseDir, dirname)).filter((dir) => fs.statSync(dir).isDirectory());
bar.start(pluginDirs.length, 0);
const plugins = await Promise.all(
pluginDirs.map(async (dir, index) => {
bar.update(index, { plugin: path.basename(dir) });
const update = !Object.prototype.hasOwnProperty.call(pluginCache, dir) || updateDirs.length === 0 || updateDirs.some((updateDir) => updateDir.startsWith(dir));
if (!update) {
bar.update(index + 1, { plugin: path.basename(dir) });
return pluginCache[dir];
}
if (!fs.existsSync(path.resolve(dir, "plugin.info"))) {
return void 0;
}
const plugin = $tw.loadPluginFolder(dir);
if (!plugin?.title) {
return void 0;
}
if (excludeFilter && $tw.wiki.filterTiddlers(`[[${plugin.title}]] +${excludeFilter}`).length > 0) {
return void 0;
}
const browserslistStr = plugin["Modern.TiddlyDev#BrowsersList"] ?? ">0.25%, not ie 11, not op_mini all";
const externalModules = $tw.utils.parseStringArray(
plugin["Modern.TiddlyDev#ExternalModules"] ?? ""
);
const nodeBuildInModulesToNotExternal = $tw.utils.parseStringArray(
plugin["Modern.TiddlyDev#NodeModulesNotExternal"] ?? ""
);
const sourceMap = plugin["Modern.TiddlyDev#SourceMap"]?.toLowerCase?.() === "true";
const minifyPlugin = plugin["Modern.TiddlyDev#Minify"]?.toLowerCase?.() !== "false";
const tiddlers = JSON.parse(plugin.text).tiddlers;
$tw.wiki.deleteTiddler(plugin.title);
Object.keys(tiddlers).forEach((title) => {
if (fs.existsSync(title) && !fs.existsSync(`${title}.meta`)) {
delete tiddlers[title];
}
});
const entryPoints = [];
const metaMap = /* @__PURE__ */ new Map();
walkFilesSync(dir, (filepath) => {
let meta = $tw.loadMetadataForFile(filepath);
if (!meta) {
return;
}
metaMap.set(filepath, meta);
if ([".ts", ".tsx", ".cjs", ".mjs", ".jsx"].includes(
path.extname(filepath).toLowerCase()
)) {
if (meta["Modern.TiddlyDev#IncludeSource"] === "true") {
tiddlers[meta.title] = {
...meta,
text: fs.readFileSync(filepath, "utf-8"),
"module-type": void 0
};
if (meta["Modern.TiddlyDev#NoCompile"] !== "true") {
entryPoints.push(filepath);
const titlePath = meta.title.split("/");
const parts = titlePath[titlePath.length - 1].split(".");
if (parts.length < 2 || parts[parts.length - 1].toLowerCase() === "js" || !["ts", "tsx", "cjs", "mjs", "jsx"].includes(
parts[parts.length - 1].toLowerCase()
)) {
parts.push("js");
} else {
parts[parts.length - 1] = "js";
}
titlePath[titlePath.length - 1] = parts.join(".");
meta = {
...meta,
title: titlePath.join("/")
};
} else {
}
} else {
delete tiddlers[meta.title];
if (meta["Modern.TiddlyDev#NoCompile"] !== "true") {
entryPoints.push(filepath);
const titlePath = meta.title.split("/");
const parts = titlePath[titlePath.length - 1].split(".");
if (parts.length < 2 || !["js", "ts", "tsx", "cjs", "mjs", "jsx"].includes(
parts[parts.length - 1].toLowerCase()
)) {
parts.push("js");
} else {
parts[parts.length - 1] = "js";
}
titlePath[titlePath.length - 1] = parts.join(".");
meta = {
...meta,
title: titlePath.join("/")
};
} else {
}
}
}
metaMap.set(filepath, meta);
});
const { outputFiles, metafile } = await esbuild.build({
entryPoints,
bundle: true,
// 为什么不用 ESbuild 的压缩:UglifyJS 的压缩效率更好
// 参考:https://github.com/privatenumber/minification-benchmarks
minify: false,
write: false,
allowOverwrite: true,
// incremental: true,
outdir: baseDir,
outbase: baseDir,
sourcemap: devMode || sourceMap ? "inline" : false,
// https://esbuild.github.io/api/#format
format: "cjs",
// https://esbuild.github.io/api/#tree-shaking
treeShaking: true,
// https://esbuild.github.io/api/#platform
platform: "browser",
// https://esbuild.github.io/api/#external
external: [
"$:/*",
// allow whitelist some node build-in modules
...nodejsBuiltinModules.filter(
(name) => !nodeBuildInModulesToNotExternal.includes(name)
),
...externalModules ?? []
],
inject: [injectPath],
// https://esbuild.github.io/api/#analyze
metafile: true,
banner: {
js: "/* Compiled by Modern.TiddlyDev: https://github.com/tiddly-gittly/Modern.TiddlyDev */",
css: "/* Compiled by Modern.TiddlyDev: https://github.com/tiddly-gittly/Modern.TiddlyDev */"
},
loader: {
".png": "dataurl",
".woff": "dataurl",
".woff2": "dataurl",
".eot": "dataurl",
".ttf": "dataurl",
".svg": "dataurl"
},
plugins: [
// http://browserl.ist/?q=%3E0.25%25%2C+not+ie+11%2C+not+op_mini+all
esbuildPluginBrowserslist(browserslist(browserslistStr), {
printUnknownTargets: false
}),
postCssPlugin({
postcss: {
plugins: [tailwindcssPostcss(), autoprefixer]
}
}),
esbuildSvelte({
preprocess: sveltePreprocess()
})
]
});
outputFiles.forEach((file) => {
const output = metafile.outputs[path.relative(rootPath, file.path).split(path.sep).join("/")];
let meta = {};
if (output.entryPoint) {
const resolved = path.resolve(output.entryPoint);
const relatived = path.relative(dir, output.entryPoint);
if (metaMap.has(resolved)) {
meta = {
...metaMap.get(resolved),
type: "application/javascript",
"Modern.TiddlyDev#Origin": relatived
};
} else {
return;
}
} else {
const name = Object.keys(output.inputs)[0];
if (name) {
const resolved = path.resolve(name);
const relatived = path.relative(dir, name);
const type = $tw.config.fileExtensionInfo[path.extname(file.path).toLowerCase()]?.type ?? "";
meta = {
title: "",
tags: type === "text/css" ? ["$:/tags/Stylesheet"] : [],
...metaMap.get(resolved) ?? {},
type,
"Modern.TiddlyDev#Origin": relatived
};
}
if (!meta.title) {
const parsed = path.parse(path.relative(dir, file.path));
const tmp = path.join(plugin.title, parsed.dir, parsed.name);
if (Object.prototype.hasOwnProperty.call(tiddlers, `${tmp}${parsed.ext}`)) {
let id = 1;
while (Object.prototype.hasOwnProperty.call(tiddlers, `${tmp}${id}${parsed.ext}`)) {
id++;
}
meta.title = `${tmp}${id}${parsed.ext}`;
} else {
meta.title = `${tmp}${parsed.ext}`;
}
}
}
tiddlers[meta.title] = {
...meta,
text: file.text
};
});
if (!devMode && minifyPlugin) {
Object.keys(tiddlers).forEach((title) => {
if (tiddlers[title]["Modern.TiddlyDev#Minify"] !== "false") {
tiddlers[title] = minifyTiddler(tiddlers[title]);
}
});
}
const t = {
...plugin,
text: JSON.stringify({ tiddlers })
};
pluginCache[dir] = {};
for (const key of Object.keys(t).sort()) {
pluginCache[dir][key] = t[key];
}
if (!devMode) {
pluginCache[dir]["Modern.TiddlyDev#SHA256-Hashed"] = sha256(
JSON.stringify(pluginCache[dir])
);
}
bar.update(index + 1, { plugin: path.basename(dir) });
return pluginCache[dir];
})
);
console.log("");
return plugins.filter((plugin) => plugin !== void 0);
};
export {
rebuild
};