@oeyoews/tiddlywiki-plugin-dev
Version:
[](https://github.com/tiddly-gittly)
332 lines (318 loc) • 15.6 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.rebuild = void 0;
var _uniq2 = _interopRequireDefault(require("lodash/uniq"));
var _fs = _interopRequireDefault(require("fs"));
var _path = _interopRequireDefault(require("path"));
var _chalk = _interopRequireDefault(require("chalk"));
var _sha = _interopRequireDefault(require("sha256"));
var _esbuild = _interopRequireDefault(require("esbuild"));
var _uglifyJs = _interopRequireDefault(require("uglify-js"));
var _cleanCss = _interopRequireDefault(require("clean-css"));
var _cliProgress = _interopRequireDefault(require("cli-progress"));
var _browserslist = _interopRequireDefault(require("browserslist"));
var _esbuildStylePlugin = _interopRequireDefault(require("esbuild-style-plugin"));
var _tailwindcss = _interopRequireDefault(require("tailwindcss"));
var _autoprefixer = _interopRequireDefault(require("autoprefixer"));
var _esbuildPluginBrowserslist = require("esbuild-plugin-browserslist");
var _utils = require("./utils");
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); enumerableOnly && (symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; })), keys.push.apply(keys, symbols); } return keys; }
function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = null != arguments[i] ? arguments[i] : {}; i % 2 ? ownKeys(Object(source), !0).forEach(function (key) { _defineProperty(target, key, source[key]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)) : ownKeys(Object(source)).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } return target; }
function _defineProperty(obj, key, value) { key = _toPropertyKey(key); if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
function _toPropertyKey(arg) { var key = _toPrimitive(arg, "string"); return typeof key === "symbol" ? key : String(key); }
function _toPrimitive(input, hint) { if (typeof input !== "object" || input === null) return input; var prim = input[Symbol.toPrimitive]; if (prim !== undefined) { var res = prim.call(input, hint || "default"); if (typeof res !== "object") return res; throw new TypeError("@@toPrimitive must return a primitive value."); } return (hint === "string" ? String : Number)(input); }
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 injectPath = _path.default.resolve(__dirname, 'esbuild-inject.js');
const rootPath = process.cwd();
// 插件构建缓存
const pluginCache = {};
const UglifyJSOption = {
warnings: false,
v8: true,
ie: true,
webkit: true
};
const cleanCSS = new _cleanCss.default({
compatibility: 'ie9',
level: 2
});
const minifyTiddler = tiddler => {
const {
text,
type
} = tiddler;
try {
if (type === 'application/javascript') {
const minified = _uglifyJs.default.minify(text, UglifyJSOption).code;
if (minified !== undefined) {
return _objectSpread(_objectSpread({}, tiddler), {}, {
text: minified
});
}
} else if (type === 'text/css') {
const minified = cleanCSS.minify(text).styles;
if (minified !== undefined) {
return _objectSpread(_objectSpread({}, 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.default.resolve(pluginsDir);
if (!_fs.default.existsSync(baseDir)) {
return [];
}
// Touch TailwindCss config file
const tailwindConfigPath = _path.default.resolve('.', 'tailwind.config.js');
if (!_fs.default.existsSync(tailwindConfigPath)) {
_fs.default.writeFileSync(tailwindConfigPath, ['module.exports = {', " content: ['./src/**/*.{mjs,cjs,js,ts,jsx,tsx}'],", ' theme: { extend: {} },', ' plugins: [],', '};'].join('\n'), 'utf-8');
}
// eslint-disable-next-line no-console
console.log(_chalk.default.green.bold('Compiling...'));
const bar = new _cliProgress.default.SingleBar({
format: `${_chalk.default.green('{bar}')} {percentage}% | {plugin}`,
stopOnComplete: true
}, _cliProgress.default.Presets.shades_classic);
const updateDirs = (0, _uniq2.default)(updatePaths.filter(file => file).map(file => _path.default.resolve(_path.default.dirname(file))));
const pluginDirs = _fs.default.readdirSync(baseDir).map(dirname => _path.default.resolve(baseDir, dirname)).filter(dir => _fs.default.statSync(dir).isDirectory());
bar.start(pluginDirs.length, 0);
const plugins = await Promise.all(pluginDirs.map(async (dir, index) => {
var _plugin$ModernTiddly, _plugin$ModernTiddly2, _plugin$ModernTiddly3, _plugin$ModernTiddly4, _plugin$ModernTiddly5, _plugin$ModernTiddly6;
bar.update(index, {
plugin: _path.default.basename(dir)
});
// 检查插件是否被修改过,缓存
const update = !pluginCache.hasOwnProperty(dir) || updateDirs.length === 0 || updateDirs.some(updateDir => updateDir.startsWith(dir));
if (!update) {
bar.update(index + 1, {
plugin: _path.default.basename(dir)
});
return pluginCache[dir];
}
// 读取非编译内容
if (!_fs.default.existsSync(_path.default.resolve(dir, 'plugin.info'))) {
return undefined;
}
const plugin = $tw.loadPluginFolder(dir);
// 过滤空插件
if (!(plugin !== null && plugin !== void 0 && plugin.title)) {
return undefined;
}
// 筛选插件
if (excludeFilter && $tw.wiki.filterTiddlers(`[[${plugin.title}]] +${excludeFilter}`).length > 0) {
return undefined;
}
// 编译选项
const browserslistStr = (_plugin$ModernTiddly = plugin['Modern.TiddlyDev#BrowsersList']) !== null && _plugin$ModernTiddly !== void 0 ? _plugin$ModernTiddly : '>0.25%, not ie 11, not op_mini all';
const externalModules = $tw.utils.parseStringArray((_plugin$ModernTiddly2 = plugin['Modern.TiddlyDev#ExternalModules']) !== null && _plugin$ModernTiddly2 !== void 0 ? _plugin$ModernTiddly2 : '');
const sourceMap = ((_plugin$ModernTiddly3 = plugin['Modern.TiddlyDev#SourceMap']) === null || _plugin$ModernTiddly3 === void 0 ? void 0 : (_plugin$ModernTiddly4 = _plugin$ModernTiddly3.toLowerCase) === null || _plugin$ModernTiddly4 === void 0 ? void 0 : _plugin$ModernTiddly4.call(_plugin$ModernTiddly3)) === 'true';
const minifyPlugin = ((_plugin$ModernTiddly5 = plugin['Modern.TiddlyDev#Minify']) === null || _plugin$ModernTiddly5 === void 0 ? void 0 : (_plugin$ModernTiddly6 = _plugin$ModernTiddly5.toLowerCase) === null || _plugin$ModernTiddly6 === void 0 ? void 0 : _plugin$ModernTiddly6.call(_plugin$ModernTiddly5)) !== 'false';
const tiddlers = JSON.parse(plugin.text).tiddlers;
// 删除之前可能存在于 Wiki 的同名插件,以免被旧的覆盖掉
$tw.wiki.deleteTiddler(plugin.title);
// 过滤没有 .meta 且不带原信息的文件,这些文件的 title 都是其绝对路径,*nix/bsd(macos)是/.*, win是\w:/
Object.keys(tiddlers).forEach(title => {
if (_fs.default.existsSync(title) && !_fs.default.existsSync(`${title}.meta`)) {
delete tiddlers[title];
}
});
// 检索编译入口
const entryPoints = [];
const metaMap = new Map();
(0, _utils.walkFilesSync)(dir, filepath => {
let meta = $tw.loadMetadataForFile(filepath);
if (!meta) {
return;
}
metaMap.set(filepath, meta);
if (['.ts', '.tsx', '.cjs', '.mjs', '.jsx'].includes(_path.default.extname(filepath).toLowerCase())) {
if (meta['Modern.TiddlyDev#IncludeSource'] === 'true') {
tiddlers[meta.title] = _objectSpread(_objectSpread({}, meta), {}, {
text: _fs.default.readFileSync(filepath, 'utf-8'),
'module-type': undefined
});
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 = _objectSpread(_objectSpread({}, meta), {}, {
title: titlePath.join('/')
});
} else {
// 不编译 + 保留原文件
// do nothing
}
} 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 = _objectSpread(_objectSpread({}, meta), {}, {
title: titlePath.join('/')
});
} else {
// 不编译 + 不保留原文件
// do nothing
}
}
}
metaMap.set(filepath, meta);
});
// 编译
const {
outputFiles,
metafile
} = await _esbuild.default.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: ['$:/*', ...nodejsBuiltinModules, ...(externalModules !== null && externalModules !== void 0 ? 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
(0, _esbuildPluginBrowserslist.esbuildPluginBrowserslist)((0, _browserslist.default)(browserslistStr), {
printUnknownTargets: false
}), (0, _esbuildStylePlugin.default)({
postcss: {
plugins: [_tailwindcss.default, _autoprefixer.default]
}
})]
});
// 格式化并保存编译结果
outputFiles.forEach(file => {
// esbuild 的 matadata 路径无论是 windows 还是 POSIX 都是以 / 为分隔符,因此要额外处理
const output = metafile.outputs[_path.default.relative(rootPath, file.path).split(_path.default.sep).join('/')];
let meta = {};
if (output.entryPoint) {
// 入口,一定是源代码文件
const resolved = _path.default.resolve(output.entryPoint);
const relatived = _path.default.relative(dir, output.entryPoint);
if (metaMap.has(resolved)) {
meta = _objectSpread(_objectSpread({}, metaMap.get(resolved)), {}, {
type: 'application/javascript',
'Modern.TiddlyDev#Origin': relatived
});
} else {
// 应该不存在这种情况
return;
}
} else {
// 不是入口却被导出了,说明是资源文件
const name = Object.keys(output.inputs)[0];
if (name) {
var _$tw$config$fileExten, _$tw$config$fileExten2, _metaMap$get;
const resolved = _path.default.resolve(name);
const relatived = _path.default.relative(dir, name);
const type = (_$tw$config$fileExten = (_$tw$config$fileExten2 = $tw.config.fileExtensionInfo[_path.default.extname(file.path).toLowerCase()]) === null || _$tw$config$fileExten2 === void 0 ? void 0 : _$tw$config$fileExten2.type) !== null && _$tw$config$fileExten !== void 0 ? _$tw$config$fileExten : '';
meta = _objectSpread(_objectSpread({
title: '',
tags: type === 'text/css' ? ['$:/tags/Stylesheet'] : []
}, (_metaMap$get = metaMap.get(resolved)) !== null && _metaMap$get !== void 0 ? _metaMap$get : {}), {}, {
type,
'Modern.TiddlyDev#Origin': relatived
});
}
if (!meta.title) {
const parsed = _path.default.parse(_path.default.relative(dir, file.path));
const tmp = _path.default.join(plugin.title, parsed.dir, parsed.name);
if (tiddlers.hasOwnProperty(`${tmp}${parsed.ext}`)) {
let id = 1;
while (tiddlers.hasOwnProperty(`${tmp}${id}${parsed.ext}`)) {
id++;
}
meta.title = tiddlers.hasOwnProperty(`${tmp}${id}${parsed.ext}`);
} else {
meta.title = `${tmp}${parsed.ext}`;
}
}
}
tiddlers[meta.title] = _objectSpread(_objectSpread({}, 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 = _objectSpread(_objectSpread({}, 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'] = (0, _sha.default)(JSON.stringify(pluginCache[dir]));
}
bar.update(index + 1, {
plugin: _path.default.basename(dir)
});
return pluginCache[dir];
}));
// eslint-disable-next-line no-console
console.log('');
return plugins.filter(plugin => plugin !== undefined);
};
/* eslint-enable max-lines */
exports.rebuild = rebuild;