UNPKG

@oeyoews/tiddlywiki-plugin-dev

Version:

[![](https://img.shields.io/badge/Join-TiddlyWiki_CN-blue)](https://github.com/tiddly-gittly)

332 lines (318 loc) 15.6 kB
"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;