rollup-plugin-monkey
Version:
rollup plugin for GM_Script
249 lines (231 loc) • 6.69 kB
JavaScript
/* eslint-disable */
import fastify from 'fastify';
import fastifyCors from '@fastify/cors';
import fp from 'fastify-plugin';
import staticPlugin from '@fastify/static';
import path, { resolve } from 'path';
import wsPlugin from '@fastify/websocket';
import { fileURLToPath } from 'url';
import { createRequire } from 'node:module';
import fs from 'fs';
import chokidar from 'chokidar';
const serverDefaults = Object.freeze({
ignoreTrailingSlash: true,
disableRequestLogging: true,
});
const pluginServer = Object.freeze({
logger: {
transport: {
target: '#pinoPretty',
// target: "pino-pretty",
options: {
colorize: true,
},
},
},
});
const defConfig = {
cors: {
origin: '*',
methods: ['GET'],
},
static: {
basePath: undefined,
dirs: [''],
},
listen: {
host: 'localhost',
port: 3000,
},
server: {
...pluginServer,
...serverDefaults,
},
watch: {
exclusions: [],
dirs: '',
},
livereload: true,
force: false,
extend: undefined,
onListen: undefined,
};
var fastifyStatic = fp(async (server, { basePath, dirs }) => {
const prefix = basePath;
const root = dirs.map(dir => resolve(dir));
server.register(staticPlugin, { prefix, root });
});
const onRefresh = (server, filepath) => {
const { websocketServer } = server;
const data = JSON.stringify({
command: 'reload',
path: filepath,
});
websocketServer.clients.forEach(socket => {
if (socket.readyState === 1) {
socket.send(data);
}
});
};
var fastifyWS = fp(async (server) => {
await server.register(wsPlugin);
server.get('/livereload', { websocket: true }, (connection, req) => {
server.log.info('「livereload」 connection');
connection.socket.on('message', message => {
try {
const request = JSON.parse(message.toString());
server.log.info(`「livereload」 ${request.command}`);
if (request.command === 'hello') {
const data = JSON.stringify({
command: 'hello',
protocols: ['http://livereload.com/protocols/official-7', 'http://livereload.com/protocols/official-8', 'http://livereload.com/protocols/official-9', 'http://livereload.com/protocols/2.x-origin-version-negotiation', 'http://livereload.com/protocols/2.x-remote-control'],
serverName: 'node-livereload',
});
connection.socket.send(data);
}
} catch (err) {
server.log.warn({ err }, '「livereload」 invalid message');
}
});
});
});
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const require = createRequire(import.meta.url);
const monkeyPath = {
base: path.resolve(__dirname, '../'),
liveJS: require.resolve('livereload-js'),
devJS: require.resolve('#dev'),
};
// 一个函数,判断输入字符串同时含有数组中的所有元素
const indexOfAll = (str, arr) => {
let bolRlt = true;
arr.forEach((s) => {
if (str.indexOf(s) === -1) bolRlt = false;
});
return bolRlt
};
class Server {
async init(config = defConfig) {
this.config = config;
this.server = fastify(config.server);
if (config.livereload) {
this.server.register(fastifyWS);
this.livereload();
}
this.server.register(fastifyStatic, config.static);
this.server.register(fastifyCors, config.cors);
await this.server.ready();
return this
}
start() {
this.server.listen({
host: this.config.listen.host,
port: this.config.listen.port,
}, (err, address) => {
if (err) {
this.server.log.error(err);
// process.exit(1)
} else {
if (this.config.onListen) {
this.config.onListen(this);
}
}
});
}
livereload() {
this.server.get('/livereload.js', function (req, reply) {
// console.log(monkeyPath.liveJS)
// reply.type('text/javascript').sendFile('livereload.js', monkeyPath.livePath)
fs.readFile(monkeyPath.liveJS, (err, fileBuffer) => {
if (err) {
reply.code(500).type('text/plain').send('Failed to load livereload.js');
return
}
reply.type('text/javascript').send(fileBuffer);
});
});
}
}
const defaultExclusions = [/\.git\//, /\.svn\//, /\.hg\//, /node_modules\//];
function watcher (config, act = () => { }) {
const exclusions = (config.exclusions || []).concat(defaultExclusions);
const dirs = config.dirs || '';
let paths;
if (Array.isArray(dirs)) {
paths = dirs.map(item => resolve(process.cwd(), item));
} else {
paths = resolve(process.cwd(), dirs);
}
return chokidar.watch(paths, {
ignoreInitial: true,
usePolling: false,
ignored: exclusions,
}).on('all', (event, path) => {
if (event === 'add' || event === 'change' || event === 'unlink') {
act(event, path);
}
})
}
const isPlainObject = (value) => {
return Object.prototype.toString.call(value) === '[object Object]'
};
const mergeConfig = (base, extra) => {
const merged = { ...base };
Object.keys(extra || {}).forEach((key) => {
const baseValue = base?.[key];
const extraValue = extra[key];
if (isPlainObject(baseValue) && isPlainObject(extraValue)) {
merged[key] = mergeConfig(baseValue, extraValue);
return
}
merged[key] = extraValue;
});
return merged
};
var main = (opts = {}) => {
let booted = false;
return {
name: 'dev-monkey',
async writeBundle() {
if (booted) return
booted = true;
try {
const config = mergeConfig(defConfig, opts);
if (!this.meta.watchMode) {
if (!config.force) return
else this.warn('Starting dev-monkey even though we\'re not in watch mode');
}
const web = new Server();
await web.init(config);
web.start();
if (config.livereload) {
watcher(config.watch, (event, path) => {
// const isInfo = path.indexOf('__info') > -1
const isDevMain = indexOfAll(path, ["dev", "main.js"]);
web.server.log.info(`${event} ${path}`);
if (isDevMain) {
onRefresh(web.server, path);
}
});
}
} catch (err) {
this.error(err);
}
}
}
};
const monkeyRequire = (arrOpts) => {
const entryList = [];
const apiList = [];
arrOpts.forEach((s) => {
entryList.push(s.url);
apiList.push(s.func);
});
return {
gm_entry: JSON.stringify(entryList),
gm_api: JSON.stringify(apiList),
gm_require: arrOpts.map((s) => `// @require ${s.url}`).join('\n'),
}
};
export { main as default, monkeyPath, monkeyRequire };