UNPKG

rollup-plugin-monkey

Version:
249 lines (231 loc) 6.69 kB
/* 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 };