Ver3.0 ๐ ํ๋ฌ๊ทธ์ธ ์ํคํ ์ฒ๋ก ๋ง๋๋ ํ์ฅ ๊ฐ๋ฅํ ์ฑ ์ค๊ณ

๐ ํ๋ฌ๊ทธ์ธ ์ํคํ ์ฒ๋ก ๋ง๋๋ ํ์ฅ ๊ฐ๋ฅํ ์ฑ ์ค๊ณ
๐ฏ ํ๋ฌ๊ทธ์ธ ์ํคํ ์ฒ, ์ ํ์ํ ๊น?
์น๊ตฌ์ผ, ํ๋ก๊ทธ๋จ ๊ฐ๋ฐํ๋ค ๋ณด๋ฉด ์ด๋ฐ ๊ณ ๋ฏผ ํ ๋ฒ์ฏค ํด๋ดค์ ๊ฑฐ์ผ. "์ด ๊ธฐ๋ฅ ์ถ๊ฐํ๋ ค๋ฉด ๋ ์ ์ฒด ์ฝ๋๋ฅผ ๋ค ๋ฏ์ด๊ณ ์ณ์ผ ํ๋?" ๐ซ
์์ํด๋ด. ๋ ๊ณ ๋ธ๋ก์ฒ๋ผ ํ์ํ ๊ธฐ๋ฅ์ ์์ ๋ผ์ ๋ฃ๊ณ ๋นผ๊ณ ํ ์ ์๋ค๋ฉด? ๊ทธ๊ฒ ๋ฐ๋ก ํ๋ฌ๊ทธ์ธ ์ํคํ
์ฒ์ ๋งค๋ ฅ์ด์ผ!
์์ฆ ์ฑ๊ณตํ ํ๋ก๊ทธ๋จ๋ค์ ๋ณด๋ฉด ๊ฑฐ์ ๋ค ํ๋ฌ๊ทธ์ธ ์์คํ
์ ์ฑํํ๊ณ ์์ด. VS Code, Chrome ๋ธ๋ผ์ฐ์ , WordPress... ์ด๋ค์ ๊ณตํต์ ์ด ๋ญ๊น? ๋ฐ๋ก ์ฌ์ฉ์๊ฐ ์ํ๋ ๊ธฐ๋ฅ์ ์์ ๋กญ๊ฒ ์ถ๊ฐํ ์ ์๋ค๋ ๊ฑฐ์ง! ๐
๐๏ธ ํ๋ฌ๊ทธ์ธ ์ํคํ ์ฒ์ ๊ธฐ๋ณธ ๊ตฌ์กฐ
ํ๋ฌ๊ทธ์ธ ์ํคํ
์ฒ๋ฅผ ์ดํดํ๋ ค๋ฉด ๋จผ์ ๊ธฐ๋ณธ ๊ตฌ์กฐ๋ฅผ ์์์ผ ํด. ํฌ๊ฒ ์ธ ๊ฐ์ง ํต์ฌ ์์๋ก ๊ตฌ์ฑ๋์ด ์์ด.
1๏ธโฃ ์ฝ์ด ์์คํ (Core System)
์ฝ์ด ์์คํ
์ ์ ํ๋ฆฌ์ผ์ด์
์ ์ฌ์ฅ์ด์ ๋ผ๋์ผ. ์ต์ํ์ ๊ธฐ๋ฅ๋ง ์ ๊ณตํ๋ฉด์ ํ๋ฌ๊ทธ์ธ๋ค์ด ๋์ํ ์ ์๋ ํ๊ฒฝ์ ๋ง๋ค์ด์ฃผ์ง.
์ฝ์ด ์์คํ
์ด ๋ด๋นํ๋ ์ฃผ์ ์ญํ ์ ์ด๋:
โข ์๋ช ์ฃผ๊ธฐ ๊ด๋ฆฌ: ํ๋ฌ๊ทธ์ธ์ ๋ก๋ฉ, ์ด๊ธฐํ, ์คํ, ์ข ๋ฃ ์ ์ด
โข ํต์ ์ธํฐํ์ด์ค ์ ๊ณต: ํ๋ฌ๊ทธ์ธ ๊ฐ ๋๋ ์ฝ์ด์ ํ๋ฌ๊ทธ์ธ ๊ฐ ๋ฐ์ดํฐ ๊ตํ
โข ์์กด์ฑ ํด๊ฒฐ: ํ๋ฌ๊ทธ์ธ ๊ฐ ์์กด ๊ด๊ณ ํ์ ๋ฐ ๋ก๋ฉ ์์ ๊ฒฐ์
โข ๋ณด์ ๋ฐ ๊ฒฉ๋ฆฌ: ํ๋ฌ๊ทธ์ธ์ด ์์คํ ์ ํด๋ฅผ ๋ผ์น์ง ๋ชปํ๋๋ก ์๋๋ฐ์ค ์ ๊ณต
2๏ธโฃ ํ๋ฌ๊ทธ์ธ ์ธํฐํ์ด์ค (Plugin Interface)
ํ๋ฌ๊ทธ์ธ ์ธํฐํ์ด์ค๋ ์ฝ์ด์ ํ๋ฌ๊ทธ์ธ ์ฌ์ด์ ๊ณ์ฝ์๋ผ๊ณ ์๊ฐํ๋ฉด ๋ผ. ์ด ์ธํฐํ์ด์ค๋ฅผ ํตํด ํ๋ฌ๊ทธ์ธ์ด ์ด๋ค ํํ๋ก ๋ง๋ค์ด์ ธ์ผ ํ๋์ง ์ ์ํ์ง.
// Java ์์
public interface Plugin {
String getName();
String getVersion();
void initialize(PluginContext context);
void execute();
void shutdown();
}
// TypeScript ์์
interface IPlugin {
name: string;
version: string;
initialize(context: PluginContext): Promise<void>;
execute(): void;
shutdown(): void;
}
์ด๋ ๊ฒ ์ธํฐํ์ด์ค๋ฅผ ์ ์ํ๋ฉด ๋๊ตฌ๋ ์ด ๊ท์น๋ง ๋ฐ๋ผ์ ํ๋ฌ๊ทธ์ธ์ ๋ง๋ค ์ ์์ด. ๋ง์น USB ํฌํธ์ฒ๋ผ ํ์ค๋ง ๋ง์ผ๋ฉด ์ด๋ค ์ฅ์น๋ ์ฐ๊ฒฐํ ์ ์๋ ๊ฒ์ฒ๋ผ! ๐
3๏ธโฃ ํ๋ฌ๊ทธ์ธ ๋ชจ๋ (Plugin Modules)
์ค์ ๊ธฐ๋ฅ์ ๊ตฌํํ ๋
๋ฆฝ์ ์ธ ๋ชจ๋๋ค์ด์ผ. ๊ฐ ํ๋ฌ๊ทธ์ธ์ ์์ ๋ง์ ์ญํ ์ ์ํํ๋ฉด์๋ ์ฝ์ด ์์คํ
๊ณผ๋ ๋์จํ๊ฒ ๊ฒฐํฉ๋์ด ์์ง.
๐จ ํ๋ฌ๊ทธ์ธ ์ํคํ ์ฒ ์ค๊ณ ํจํด
ํ๋ฌ๊ทธ์ธ ์ํคํ ์ฒ๋ฅผ ๊ตฌํํ๋ ๋ฐฉ๋ฒ์ ์ฌ๋ฌ ๊ฐ์ง๊ฐ ์์ด. ํ๋ก์ ํธ์ ํน์ฑ๊ณผ ์๊ตฌ์ฌํญ์ ๋ฐ๋ผ ์ ์ ํ ํจํด์ ์ ํํ๋ฉด ๋ผ!
๐ธ ๋ ์ง์คํธ๋ฆฌ ๊ธฐ๋ฐ ํจํด (Registry-based Pattern)
์ค์ ๋ ์ง์คํธ๋ฆฌ์ ํ๋ฌ๊ทธ์ธ์ ๋ฑ๋กํ๊ณ ๊ด๋ฆฌํ๋ ๋ฐฉ์์ด์ผ. ๊ฐ์ฅ ์ผ๋ฐ์ ์ด๊ณ ์ง๊ด์ ์ธ ํจํด์ด์ง.
class PluginRegistry {
private plugins: Map<string, Plugin> = new Map();
register(plugin: Plugin): void {
if (this.plugins.has(plugin.name)) {
throw new Error(`Plugin ${plugin.name} already registered`);
}
this.plugins.set(plugin.name, plugin);
plugin.initialize(this.createContext());
}
unregister(pluginName: string): void {
const plugin = this.plugins.get(pluginName);
if (plugin) {
plugin.shutdown();
this.plugins.delete(pluginName);
}
}
getPlugin(name: string): Plugin | undefined {
return this.plugins.get(name);
}
getAllPlugins(): Plugin[] {
return Array.from(this.plugins.values());
}
}
์ด ํจํด์ ์ฅ์ ์ ํ๋ฌ๊ทธ์ธ ๊ด๋ฆฌ๊ฐ ์ค์ํ๋์ด ์์ด์ ์ถ์ ํ๊ธฐ ์ฝ๋ค๋ ๊ฑฐ์ผ. ์ด๋ค ํ๋ฌ๊ทธ์ธ์ด ํ์ฑํ๋์ด ์๋์ง ํ๋์ ๋ณผ ์ ์์ง! ๐
๐ธ ์ด๋ฒคํธ ๊ธฐ๋ฐ ํจํด (Event-driven Pattern)
ํ๋ฌ๊ทธ์ธ๋ค์ด ํน์ ์ด๋ฒคํธ๋ฅผ ๊ตฌ๋
ํ๊ณ , ์ด๋ฒคํธ๊ฐ ๋ฐ์ํ๋ฉด ์๋์ผ๋ก ์คํ๋๋ ๋ฐฉ์์ด์ผ. ๋์จํ ๊ฒฐํฉ์ ๊ทน๋ํํ ์ ์๋ ํจํด์ด์ง.
class EventBus {
private listeners: Map<string, Function[]> = new Map();
on(event: string, callback: Function): void {
if (!this.listeners.has(event)) {
this.listeners.set(event, []);
}
this.listeners.get(event)!.push(callback);
}
emit(event: string, data?: any): void {
const callbacks = this.listeners.get(event);
if (callbacks) {
callbacks.forEach(callback => callback(data));
}
}
off(event: string, callback: Function): void {
const callbacks = this.listeners.get(event);
if (callbacks) {
const index = callbacks.indexOf(callback);
if (index > -1) {
callbacks.splice(index, 1);
}
}
}
}
// ํ๋ฌ๊ทธ์ธ ์ฌ์ฉ ์์
class LoggerPlugin implements Plugin {
initialize(context: PluginContext): void {
context.eventBus.on('user.login', (data) => {
console.log(`User logged in: ${data.username}`);
});
}
}
์ด๋ฒคํธ ๊ธฐ๋ฐ ํจํด์ ํนํ ์ค์๊ฐ ๋ฐ์์ด ํ์ํ ์ ํ๋ฆฌ์ผ์ด์ ์์ ๋น์ ๋ฐํด. ์ฑํ ์ฑ์ด๋ ํ์ ๋๊ตฌ ๊ฐ์ ๊ณณ์์ ๋ง์ด ์ฌ์ฉํ์ง! ๐ฌ
๐ธ ์์กด์ฑ ์ฃผ์ ํจํด (Dependency Injection Pattern)
ํ๋ฌ๊ทธ์ธ์ด ํ์๋ก ํ๋ ์์กด์ฑ์ ์ธ๋ถ์์ ์ฃผ์
๋ฐ๋ ๋ฐฉ์์ด์ผ. ํ
์คํธํ๊ธฐ ์ฝ๊ณ ์ ์ฐํ ๊ตฌ์กฐ๋ฅผ ๋ง๋ค ์ ์์ด.
interface PluginDependencies {
logger: Logger;
database: Database;
config: Configuration;
}
class PaymentPlugin implements Plugin {
private logger: Logger;
private database: Database;
constructor(dependencies: PluginDependencies) {
this.logger = dependencies.logger;
this.database = dependencies.database;
}
async execute(): Promise<void> {
this.logger.info('Processing payment...');
await this.database.save(/* payment data */);
}
}
โข ์ด๋ฒคํธ ๊ธฐ๋ฐ: ๋น๋๊ธฐ ์ฒ๋ฆฌ๊ฐ ๋ง๊ณ ํ๋ฌ๊ทธ์ธ ๊ฐ ํต์ ์ด ๋น๋ฒํ ๋
โข ์์กด์ฑ ์ฃผ์ : ํ ์คํธ ๊ฐ๋ฅ์ฑ๊ณผ ์ ์ง๋ณด์์ฑ์ ์ต์ฐ์ ์ผ๋ก ํ ๋
โ๏ธ ํ๋ฌ๊ทธ์ธ ์๋ช ์ฃผ๊ธฐ ๊ด๋ฆฌ
ํ๋ฌ๊ทธ์ธ๋ ์๋ช ์ด ์์ด! ๐ฑ ํ์ด๋๊ณ (๋ก๋ฉ), ํ๋ํ๊ณ (์คํ), ์ฌ๊ณ (์ผ์์ ์ง), ์ฃฝ๋(์ธ๋ก๋ฉ) ๊ณผ์ ์ ๊ฑฐ์น์ง. ์ด ์๋ช ์ฃผ๊ธฐ๋ฅผ ์ ๋๋ก ๊ด๋ฆฌํ๋ ๊ฒ ์์ ์ ์ธ ์์คํ ์ ํต์ฌ์ด์ผ.
๐ ๊ฐ ๋จ๊ณ๋ณ ์์ธ ์ค๋ช
ํน์ง: ์ต์ํ์ ๋ฉ๋ชจ๋ฆฌ ์ฌ์ฉ, ๋น ๋ฅธ ์์ ์๊ฐ
ํ๋ฌ๊ทธ์ธ์ ์ฝ๋๋ฅผ ๋ฉ๋ชจ๋ฆฌ์ ๋ก๋ํ๊ณ ํ์ํ ๋ฆฌ์์ค๋ฅผ ์ค๋นํ๋ ๋จ๊ณ์ผ. ์์กด์ฑ ์ฒดํฌ์ ์ด๊ธฐํ ์์ ์ด ์ด๋ฃจ์ด์ง์ง.
async loadPlugin(pluginPath: string): Promise<Plugin> {
try {
// ํ๋ฌ๊ทธ์ธ ํ์ผ ๋ก๋
const pluginModule = await import(pluginPath);
// ์์กด์ฑ ๊ฒ์ฆ
this.validateDependencies(pluginModule);
// ์ธ์คํด์ค ์์ฑ
const plugin = new pluginModule.default();
// ๋ฉํ๋ฐ์ดํฐ ๊ฒ์ฆ
if (!plugin.name || !plugin.version) {
throw new Error('Invalid plugin metadata');
}
return plugin;
} catch (error) {
console.error(`Failed to load plugin: ${error}`);
throw error;
}
}
์ฃผ์ ์์ : ์ด๋ฒคํธ ๋ฆฌ์ค๋ ๋ฑ๋ก, ๋ฆฌ์์ค ํ ๋น, ์๋น์ค ์์
ํ๋ฌ๊ทธ์ธ์ด ์ผ์์ ์ผ๋ก ๋นํ์ฑํ๋ ์ํ์ผ. ๋ฉ๋ชจ๋ฆฌ์๋ ๋จ์์์ง๋ง ๊ธฐ๋ฅ์ ์ํํ์ง ์์. ํ์ํ ๋ ๋น ๋ฅด๊ฒ ์ฌ์์ํ ์ ์์ง.
stopPlugin(pluginName: string): void {
const plugin = this.getPlugin(pluginName);
if (plugin && plugin.state === PluginState.Active) {
// ์ด๋ฒคํธ ๋ฆฌ์ค๋ ํด์
this.eventBus.removeAllListeners(pluginName);
// ๋ฆฌ์์ค ์ ๋ฆฌ
plugin.cleanup();
// ์ํ ๋ณ๊ฒฝ
plugin.state = PluginState.Stopped;
this.emit('plugin.stopped', { name: pluginName });
}
}
โข ์ํ ์์กด์ฑ: ํ๋ฌ๊ทธ์ธ ๊ฐ ์ํ ์ฐธ์กฐ๊ฐ ์๊ธฐ์ง ์๋๋ก ์ฃผ์
โข ์๋ฌ ์ฒ๋ฆฌ: ํ ํ๋ฌ๊ทธ์ธ์ ์ค๋ฅ๊ฐ ์ ์ฒด ์์คํ ์ ์ํฅ์ ์ฃผ์ง ์๋๋ก ๊ฒฉ๋ฆฌ
โข ํ์์์ ์ค์ : ๋ก๋ฉ์ด๋ ์ด๊ธฐํ๊ฐ ๋๋ฌด ์ค๋ ๊ฑธ๋ฆฌ๋ฉด ๊ฐ์ ์ข ๋ฃ
๐ ํ๋ฌ๊ทธ์ธ ๋ณด์๊ณผ ์๋๋ฐ์ฑ
ํ๋ฌ๊ทธ์ธ ์์คํ
์ ๊ฐ์ฅ ํฐ ์ํ์ ์
์์ ์ธ ํ๋ฌ๊ทธ์ธ์ด ์์คํ
์ ์ฒด๋ฅผ ๋ง๊ฐ๋จ๋ฆด ์ ์๋ค๋ ๊ฑฐ์ผ. ๐ฑ ๊ทธ๋์ ๋ณด์์ด ์ ๋ง ์ค์ํด!
๐ก๏ธ ์๋๋ฐ์ค ํ๊ฒฝ ๊ตฌ์ถ
์๋๋ฐ์ค๋ ํ๋ฌ๊ทธ์ธ์ ๊ฒฉ๋ฆฌ๋ ํ๊ฒฝ์์ ์คํ์์ผ์ ์์คํ ์ ์ง์ ์ ๊ทผํ์ง ๋ชปํ๊ฒ ํ๋ ๊ธฐ์ ์ด์ผ. ๋ง์น ๋์ดํฐ์ ๋ชจ๋์์์ฒ๋ผ ์์ ํ ๊ณต๊ฐ์ ๋ง๋ค์ด์ฃผ๋ ๊ฑฐ์ง!
class PluginSandbox {
private allowedAPIs: Set<string>;
private resourceLimits: ResourceLimits;
constructor() {
this.allowedAPIs = new Set([
'console.log',
'setTimeout',
'fetch'
]);
this.resourceLimits = {
maxMemory: 50 * 1024 * 1024, // 50MB
maxCPUTime: 5000, // 5์ด
maxFileSize: 10 * 1024 * 1024 // 10MB
};
}
execute(plugin: Plugin, context: PluginContext): Promise<any> {
return new Promise((resolve, reject) => {
const worker = new Worker(plugin.scriptPath);
// ๋ฉ๋ชจ๋ฆฌ ์ฌ์ฉ๋ ๋ชจ๋ํฐ๋ง
const memoryCheck = setInterval(() => {
if (this.getMemoryUsage(worker) > this.resourceLimits.maxMemory) {
worker.terminate();
reject(new Error('Memory limit exceeded'));
}
}, 1000);
// CPU ์๊ฐ ์ ํ
const timeout = setTimeout(() => {
worker.terminate();
reject(new Error('CPU time limit exceeded'));
}, this.resourceLimits.maxCPUTime);
worker.onmessage = (event) => {
clearInterval(memoryCheck);
clearTimeout(timeout);
resolve(event.data);
};
worker.onerror = (error) => {
clearInterval(memoryCheck);
clearTimeout(timeout);
reject(error);
};
// ์ ํ๋ ์ปจํ
์คํธ ์ ๋ฌ
worker.postMessage(this.createRestrictedContext(context));
});
}
private createRestrictedContext(context: PluginContext): any {
return {
// ํ์ฉ๋ API๋ง ๋
ธ์ถ
log: context.logger.log.bind(context.logger),
fetch: this.createRestrictedFetch(),
storage: this.createRestrictedStorage()
};
}
}
๐ ๊ถํ ๊ด๋ฆฌ ์์คํ
ํ๋ฌ๊ทธ์ธ๋ง๋ค ํ์ํ ๊ถํ์ ๋ช ์ํ๊ณ , ์ฌ์ฉ์๊ฐ ์น์ธํ ๊ถํ๋ง ๋ถ์ฌํ๋ ์์คํ ์ด ํ์ํด. ์ค๋งํธํฐ ์ฑ์ ๊ถํ ์์ฒญ๊ณผ ๋น์ทํ ๊ฐ๋ ์ด์ผ! ๐ฑ
{
"name": "awesome-plugin",
"version": "1.0.0",
"permissions": [
"network.fetch",
"storage.local",
"ui.notification"
],
"requiredAPIs": [
"core.events@^2.0.0",
"core.storage@^1.5.0"
],
"sandbox": {
"allowEval": false,
"allowFileSystem": false,
"maxMemory": "50MB",
"maxCPUTime": "5s"
}
}
๊ถํ ๊ฒ์ฆ ๋ก์ง๋ ๊ตฌํํด์ผ ํด:
class PermissionManager {
private grantedPermissions: Map<string, Set<string>> = new Map();
async requestPermission(
pluginName: string,
permission: string
): Promise<boolean> {
// ์ด๋ฏธ ์น์ธ๋ ๊ถํ์ธ์ง ํ์ธ
if (this.hasPermission(pluginName, permission)) {
return true;
}
// ์ฌ์ฉ์์๊ฒ ๊ถํ ์์ฒญ
const granted = await this.showPermissionDialog(pluginName, permission);
if (granted) {
this.grantPermission(pluginName, permission);
}
return granted;
}
hasPermission(pluginName: string, permission: string): boolean {
const permissions = this.grantedPermissions.get(pluginName);
return permissions ? permissions.has(permission) : false;
}
checkPermission(pluginName: string, permission: string): void {
if (!this.hasPermission(pluginName, permission)) {
throw new PermissionDeniedError(
`Plugin ${pluginName} does not have ${permission} permission`
);
}
}
}
๐ ํ๋ฌ๊ทธ์ธ ๋์ค์ปค๋ฒ๋ฆฌ์ ๋ก๋ฉ ์ ๋ต
ํ๋ฌ๊ทธ์ธ์ ์ด๋ป๊ฒ ์ฐพ๊ณ ์ธ์ ๋ก๋ํ ์ง๋ ์ค์ํ ์ค๊ณ ํฌ์ธํธ์ผ. ํจ์จ์ ์ธ ๋ก๋ฉ ์ ๋ต์ผ๋ก ์ฑ๋ฅ์ ํฌ๊ฒ ๊ฐ์ ํ ์ ์๊ฑฐ๋ ! โก
๐ ํ๋ฌ๊ทธ์ธ ๋์ค์ปค๋ฒ๋ฆฌ ๋ฐฉ๋ฒ
| ๋ฐฉ๋ฒ | ์ค๋ช | ์ฅ์ | ๋จ์ |
|---|---|---|---|
| ํ์ผ ์์คํ ์ค์บ | ํน์ ๋๋ ํ ๋ฆฌ๋ฅผ ์ค์บํ์ฌ ํ๋ฌ๊ทธ์ธ ๋ฐ๊ฒฌ | ๊ฐ๋จํ๊ณ ์ง๊ด์ | ์์ ์๊ฐ ์ฆ๊ฐ |
| ๋งค๋ํ์คํธ ๊ธฐ๋ฐ | ์ค์ ๋งค๋ํ์คํธ ํ์ผ์ ํ๋ฌ๊ทธ์ธ ๋ชฉ๋ก ๊ด๋ฆฌ | ๋น ๋ฅธ ๋ก๋ฉ | ์๋ ๊ด๋ฆฌ ํ์ |
| ๋ ์ง์คํธ๋ฆฌ ์๋ฒ | ์๊ฒฉ ์๋ฒ์์ ํ๋ฌ๊ทธ์ธ ์ ๋ณด ์กฐํ | ์ค์ ๊ด๋ฆฌ ์ฉ์ด | ๋คํธ์ํฌ ์์กด์ฑ |
| ์๋ ๋ฑ๋ก | ํ๋ฌ๊ทธ์ธ์ด ์ค์ค๋ก ์์คํ ์ ๋ฑ๋ก | ์ ์ฐ์ฑ ๋์ | ๋ณด์ ์ํ |
โก ์ง์ฐ ๋ก๋ฉ (Lazy Loading)
๋ชจ๋ ํ๋ฌ๊ทธ์ธ์ ์ฒ์๋ถํฐ ๋ก๋ํ๋ฉด ์์ ์๊ฐ์ด ์์ฒญ ๊ธธ์ด์ ธ. ๊ทธ๋์ ํ์ํ ๋๋ง ๋ก๋ํ๋ ์ง์ฐ ๋ก๋ฉ ์ ๋ต์ด ์ค์ํด!
class LazyPluginLoader {
private pluginRegistry: Map<string, PluginMetadata> = new Map();
private loadedPlugins: Map<string, Plugin> = new Map();
// ์ด๊ธฐํ ์์๋ ๋ฉํ๋ฐ์ดํฐ๋ง ๋ก๋
async initialize(): Promise<void> {
const pluginFiles = await this.scanPluginDirectory();
for (const file of pluginFiles) {
const metadata = await this.loadMetadata(file);
this.pluginRegistry.set(metadata.name, metadata);
}
console.log(`Discovered ${this.pluginRegistry.size} plugins`);
}
// ์ค์ ์ฌ์ฉ ์์ ์ ๋ก๋
async getPlugin(name: string): Promise<Plugin> {
// ์ด๋ฏธ ๋ก๋๋ ํ๋ฌ๊ทธ์ธ์ ์ฌ์ฌ์ฉ
if (this.loadedPlugins.has(name)) {
return this.loadedPlugins.get(name)!;
}
// ๋ฉํ๋ฐ์ดํฐ ํ์ธ
const metadata = this.pluginRegistry.get(name);
if (!metadata) {
throw new Error(`Plugin ${name} not found`);
}
// ํ๋ฌ๊ทธ์ธ ๋ก๋
console.log(`Loading plugin: ${name}`);
const plugin = await this.loadPlugin(metadata);
// ์บ์์ ์ ์ฅ
this.loadedPlugins.set(name, plugin);
return plugin;
}
// ์ฌ์ฉํ์ง ์๋ ํ๋ฌ๊ทธ์ธ ์ธ๋ก๋
async unloadUnusedPlugins(): Promise<void> {
const now = Date.now();
const unusedThreshold = 5 * 60 * 1000; // 5๋ถ
for (const [name, plugin] of this.loadedPlugins) {
if (now - plugin.lastUsed > unusedThreshold) {
console.log(`Unloading unused plugin: ${name}`);
await plugin.shutdown();
this.loadedPlugins.delete(name);
}
}
}
}
๐ฏ ์ฐ์ ์์ ๊ธฐ๋ฐ ๋ก๋ฉ
์ค์ํ ํ๋ฌ๊ทธ์ธ์ ๋จผ์ ๋ก๋ํ๊ณ , ๋ ์ค์ํ ๊ฑด ๋์ค์ ๋ก๋ํ๋ ์ ๋ต์ด์ผ. ์ฌ์ฉ์ ๊ฒฝํ์ ํฌ๊ฒ ๊ฐ์ ํ ์ ์์ง!
enum LoadPriority {
CRITICAL = 0, // ์์คํ
ํ์
HIGH = 1, // ํต์ฌ ๊ธฐ๋ฅ
NORMAL = 2, // ์ผ๋ฐ ๊ธฐ๋ฅ
LOW = 3 // ๋ถ๊ฐ ๊ธฐ๋ฅ
}
class PriorityPluginLoader {
async loadPluginsByPriority(): Promise<void> {
const plugins = Array.from(this.pluginRegistry.values());
// ์ฐ์ ์์๋ณ๋ก ๊ทธ๋ฃนํ
const grouped = plugins.reduce((acc, plugin) => {
const priority = plugin.priority || LoadPriority.NORMAL;
if (!acc[priority]) acc[priority] = [];
acc[priority].push(plugin);
return acc;
}, {} as Record<LoadPriority, PluginMetadata[]>);
// ์ฐ์ ์์ ์์๋๋ก ๋ก๋
for (let priority = LoadPriority.CRITICAL; priority <= LoadPriority.LOW; priority++) {
const pluginsToLoad = grouped[priority] || [];
// ๊ฐ์ ์ฐ์ ์์๋ ๋ณ๋ ฌ ๋ก๋
await Promise.all(
pluginsToLoad.map(metadata => this.loadPlugin(metadata))
);
console.log(`Loaded ${pluginsToLoad.length} plugins with priority ${priority}`);
}
}
}
๐ ํ๋ฌ๊ทธ์ธ ๊ฐ ํต์ ๊ณผ ๋ฐ์ดํฐ ๊ณต์
ํ๋ฌ๊ทธ์ธ๋ค์ด ์๋ก ํ๋ ฅํ๋ ค๋ฉด ํต์ ๋ฉ์ปค๋์ฆ์ด ํ์ํด. ํ์ง๋ง ์ง์ ์ฐธ์กฐํ๋ฉด ๊ฒฐํฉ๋๊ฐ ๋์์ง๋๊น ์ค๊ฐ ๋งค๊ฐ์ฒด๋ฅผ ์ฌ์ฉํ๋ ๊ฒ ์ข์! ๐
๐จ ๋ฉ์์ง ๋ฒ์ค ํจํด
๋ฉ์์ง ๋ฒ์ค๋ ํ๋ฌ๊ทธ์ธ ๊ฐ ํต์ ์ ์ค์ ํ๋ธ ์ญํ ์ ํด. ๋ฐํ-๊ตฌ๋ (Pub-Sub) ํจํด์ ์ฌ์ฉํ๋ฉด ๊น๋ํ๊ฒ ๊ตฌํํ ์ ์์ด.
class MessageBus {
private channels: Map<string, Set<MessageHandler>> = new Map();
private messageQueue: Message[] = [];
private processing: boolean = false;
// ๋ฉ์์ง ๊ตฌ๋
subscribe(channel: string, handler: MessageHandler): Subscription {
if (!this.channels.has(channel)) {
this.channels.set(channel, new Set());
}
this.channels.get(channel)!.add(handler);
// ๊ตฌ๋
์ทจ์ ํจ์ ๋ฐํ
return {
unsubscribe: () => {
this.channels.get(channel)?.delete(handler);
}
};
}
// ๋ฉ์์ง ๋ฐํ
async publish(channel: string, data: any): Promise<void> {
const message: Message = {
id: this.generateMessageId(),
channel,
data,
timestamp: Date.now()
};
this.messageQueue.push(message);
if (!this.processing) {
await this.processQueue();
}
}
// ๋ฉ์์ง ํ ์ฒ๋ฆฌ
private async processQueue(): Promise<void> {
this.processing = true;
while (this.messageQueue.length > 0) {
const message = this.messageQueue.shift()!;
const handlers = this.channels.get(message.channel);
if (handlers) {
// ๋ชจ๋ ํธ๋ค๋ฌ์๊ฒ ๋ฉ์์ง ์ ๋ฌ
await Promise.all(
Array.from(handlers).map(handler =>
this.safeExecute(handler, message)
)
);
}
}
this.processing = false;
}
// ์๋ฌ ๊ฒฉ๋ฆฌ
private async safeExecute(
handler: MessageHandler,
message: Message
): Promise<void> {
try {
await handler(message.data);
} catch (error) {
console.error(`Error in message handler: ${error}`);
// ํ ํธ๋ค๋ฌ์ ์๋ฌ๊ฐ ๋ค๋ฅธ ํธ๋ค๋ฌ์ ์ํฅ์ ์ฃผ์ง ์์
}
}
}
์ค์ ์ฌ์ฉ ์์๋ฅผ ๋ณผ๊น?
// ํ๋ฌ๊ทธ์ธ A: ์ฌ์ฉ์ ์ธ์ฆ
class AuthPlugin implements Plugin {
initialize(context: PluginContext): void {
// ๋ก๊ทธ์ธ ์ฑ๊ณต ์ ๋ฉ์์ง ๋ฐํ
context.messageBus.subscribe('auth.login', async (credentials) => {
const user = await this.authenticate(credentials);
if (user) {
// ๋ค๋ฅธ ํ๋ฌ๊ทธ์ธ๋ค์๊ฒ ์๋ฆผ
await context.messageBus.publish('user.authenticated', {
userId: user.id,
username: user.username,
roles: user.roles
});
}
});
}
}
// ํ๋ฌ๊ทธ์ธ B: ์ฌ์ฉ์ ํ๋กํ
class ProfilePlugin implements Plugin {
initialize(context: PluginContext): void {
// ์ธ์ฆ ์๋ฃ ๋ฉ์์ง ๊ตฌ๋
context.messageBus.subscribe('user.authenticated', async (userData) => {
// ํ๋กํ ์ ๋ณด ๋ก๋
const profile = await this.loadProfile(userData.userId);
// UI ์
๋ฐ์ดํธ ๋ฉ์์ง ๋ฐํ
await context.messageBus.publish('ui.update', {
component: 'profile',
data: profile
});
});
}
}
// ํ๋ฌ๊ทธ์ธ C: ๋ถ์
class AnalyticsPlugin implements Plugin {
initialize(context: PluginContext): void {
// ๋ชจ๋ ์ฌ์ฉ์ ํ๋ ์ถ์
context.messageBus.subscribe('user.authenticated', (userData) => {
this.trackEvent('login', {
userId: userData.userId,
timestamp: Date.now()
});
});
}
}
โข ํ์ฅ์ฑ: ์๋ก์ด ํ๋ฌ๊ทธ์ธ์ ์ฝ๊ฒ ์ถ๊ฐํ ์ ์์ด
โข ํ ์คํธ ์ฉ์ด: ๊ฐ ํ๋ฌ๊ทธ์ธ์ ๋ ๋ฆฝ์ ์ผ๋ก ํ ์คํธ ๊ฐ๋ฅ
โข ์๋ฌ ๊ฒฉ๋ฆฌ: ํ ํ๋ฌ๊ทธ์ธ์ ์ค๋ฅ๊ฐ ๋ค๋ฅธ ํ๋ฌ๊ทธ์ธ์ ์ ํ๋์ง ์์
๐พ ๊ณต์ ๋ฐ์ดํฐ ์ ์ฅ์
ํ๋ฌ๊ทธ์ธ๋ค์ด ๋ฐ์ดํฐ๋ฅผ ๊ณต์ ํด์ผ ํ ๋๋ ์ค์ ์ ์ฅ์๋ฅผ ์ฌ์ฉํ๋ ๊ฒ ์ข์. ํ์ง๋ง ๋ค์์คํ์ด์ค๋ฅผ ์ ๊ด๋ฆฌํด์ ์ถฉ๋์ ๋ฐฉ์งํด์ผ ํด!
class SharedDataStore {
private data: Map<string, any> = new Map();
private watchers: Map<string, Set<DataWatcher>> = new Map();
// ๋ค์์คํ์ด์ค๋ฅผ ํฌํจํ ํค ์์ฑ
private createKey(pluginName: string, key: string): string {
return `${pluginName}:${key}`;
}
// ๋ฐ์ดํฐ ์ ์ฅ
set(pluginName: string, key: string, value: any): void {
const fullKey = this.createKey(pluginName, key);
const oldValue = this.data.get(fullKey);
this.data.set(fullKey, value);
// ๋ณ๊ฒฝ ๊ฐ์ง์๋ค์๊ฒ ์๋ฆผ
this.notifyWatchers(fullKey, value, oldValue);
}
// ๋ฐ์ดํฐ ์กฐํ
get(pluginName: string, key: string): any {
const fullKey = this.createKey(pluginName, key);
return this.data.get(fullKey);
}
// ๋ค๋ฅธ ํ๋ฌ๊ทธ์ธ์ ๋ฐ์ดํฐ ์ฝ๊ธฐ (์ฝ๊ธฐ ์ ์ฉ)
getShared(targetPlugin: string, key: string): any {
const fullKey = this.createKey(targetPlugin, key);
// ๊น์ ๋ณต์ฌ๋ก ์๋ณธ ๋ณดํธ
return JSON.parse(JSON.stringify(this.data.get(fullKey)));
}
// ๋ฐ์ดํฐ ๋ณ๊ฒฝ ๊ฐ์ง
watch(
pluginName: string,
key: string,
callback: DataWatcher
): Subscription {
const fullKey = this.createKey(pluginName, key);
if (!this.watchers.has(fullKey)) {
this.watchers.set(fullKey, new Set());
}
this.watchers.get(fullKey)!.add(callback);
return {
unsubscribe: () => {
this.watchers.get(fullKey)?.delete(callback);
}
};
}
private notifyWatchers(key: string, newValue: any, oldValue: any): void {
const watchers = this.watchers.get(key);
if (watchers) {
watchers.forEach(watcher => {
try {
watcher(newValue, oldValue);
} catch (error) {
console.error(`Error in data watcher: ${error}`);
}
});
}
}
}
๐จ ์ค์ ์์ : ํ ์คํธ ์๋ํฐ ํ๋ฌ๊ทธ์ธ ์์คํ
์ด๋ก ๋ง ๋ณด๋ฉด ์ฌ๋ฏธ์์ผ๋๊น ์ค์ ๋ก ๋์ํ๋ ํ ์คํธ ์๋ํฐ ํ๋ฌ๊ทธ์ธ ์์คํ ์ ๋ง๋ค์ด๋ณผ๊น? VS Code ๊ฐ์ ์๋ํฐ๋ฅผ ์์ํ๋ฉด ๋ผ! โ๏ธ
๐ ์ฝ์ด ์๋ํฐ ๊ตฌํ
class TextEditor {
private content: string = '';
private pluginManager: PluginManager;
private eventBus: EventBus;
private commandRegistry: CommandRegistry;
constructor() {
this.eventBus = new EventBus();
this.commandRegistry = new CommandRegistry();
this.pluginManager = new PluginManager(this.createPluginContext());
}
private createPluginContext(): PluginContext {
return {
editor: {
getContent: () => this.content,
setContent: (text: string) => this.setContent(text),
insertText: (text: string, position: number) =>
this.insertText(text, position),
getSelection: () => this.getSelection(),
},
eventBus: this.eventBus,
commands: this.commandRegistry,
ui: {
showNotification: (message: string) =>
this.showNotification(message),
createPanel: (options: PanelOptions) =>
this.createPanel(options),
}
};
}
setContent(text: string): void {
const oldContent = this.content;
this.content = text;
// ๋ณ๊ฒฝ ์ด๋ฒคํธ ๋ฐํ
this.eventBus.emit('content.changed', {
oldContent,
newContent: text,
timestamp: Date.now()
});
}
insertText(text: string, position: number): void {
this.content =
this.content.slice(0, position) +
text +
this.content.slice(position);
this.eventBus.emit('text.inserted', {
text,
position,
timestamp: Date.now()
});
}
async loadPlugin(pluginPath: string): Promise<void> {
await this.pluginManager.loadPlugin(pluginPath);
}
}
๐ ์์ ํ๋ฌ๊ทธ์ธ 1: ์๋ ์ ์ฅ
class AutoSavePlugin implements Plugin {
name = 'auto-save';
version = '1.0.0';
private saveInterval: number = 30000; // 30์ด
private timer: any;
private isDirty: boolean = false;
initialize(context: PluginContext): void {
// ๋ด์ฉ ๋ณ๊ฒฝ ๊ฐ์ง
context.eventBus.on('content.changed', () => {
this.isDirty = true;
});
// ์ฃผ๊ธฐ์ ์ผ๋ก ์ ์ฅ
this.timer = setInterval(async () => {
if (this.isDirty) {
await this.save(context);
this.isDirty = false;
}
}, this.saveInterval);
// ๋ช
๋ น์ด ๋ฑ๋ก
context.commands.register('autoSave.toggle', () => {
this.toggle(context);
});
context.ui.showNotification('Auto-save enabled');
}
private async save(context: PluginContext): Promise<void> {
const content = context.editor.getContent();
try {
await this.saveToFile(content);
context.ui.showNotification('โ Auto-saved');
context.eventBus.emit('file.saved', {
timestamp: Date.now(),
size: content.length
});
} catch (error) {
context.ui.showNotification('โ Auto-save failed');
}
}
private toggle(context: PluginContext): void {
if (this.timer) {
clearInterval(this.timer);
this.timer = null;
context.ui.showNotification('Auto-save disabled');
} else {
this.initialize(context);
}
}
shutdown(): void {
if (this.timer) {
clearInterval(this.timer);
}
}
}
๐จ ์์ ํ๋ฌ๊ทธ์ธ 2: ๋ฌธ๋ฒ ํ์ด๋ผ์ดํ
class SyntaxHighlightPlugin implements Plugin {
name = 'syntax-highlight';
version = '1.0.0';
private languages: Map<string, LanguageDefinition> = new Map();
initialize(context: PluginContext): void {
// ์ธ์ด ์ ์ ๋ก๋
this.loadLanguageDefinitions();
// ๋ด์ฉ ๋ณ๊ฒฝ ์ ํ์ด๋ผ์ดํ
์ ์ฉ
context.eventBus.on('content.changed', (data) => {
this.highlight(context, data.newContent);
});
// ๋ช
๋ น์ด ๋ฑ๋ก
context.commands.register('syntax.setLanguage', (language: string) => {
this.setLanguage(context, language);
});
}
private highlight(context: PluginContext, content: string): void {
const language = this.detectLanguage(content);
const tokens = this.tokenize(content, language);
// ํ์ด๋ผ์ดํ
์ ๋ณด ๋ฐํ
context.eventBus.emit('syntax.highlighted', {
tokens,
language
});
}
private tokenize(content: string, language: string): Token[] {
const definition = this.languages.get(language);
if (!definition) return [];
const tokens: Token[] = [];
let position = 0;
// ์ ๊ท์ ๊ธฐ๋ฐ ํ ํฐํ
for (const rule of definition.rules) {
const regex = new RegExp(rule.pattern, 'g');
let match;
while ((match = regex.exec(content)) !== null) {
tokens.push({
type: rule.type,
value: match[0],
start: match.index,
end: match.index + match[0].length
});
}
}
return tokens.sort((a, b) => a.start - b.start);
}
private loadLanguageDefinitions(): void {
// JavaScript ์ธ์ด ์ ์
this.languages.set('javascript', {
name: 'JavaScript',
rules: [
{ pattern: '\\b(function|const|let|var|return|if|else)\\b', type: 'keyword' },
{ pattern: '\\b(true|false|null|undefined)\\b', type: 'constant' },
{ pattern: '"[^"]*"', type: 'string' },
{ pattern: "'[^']*'", type: 'string' },
{ pattern: '//.*$', type: 'comment' },
{ pattern: '/\\*[\\s\\S]*?\\*/', type: 'comment' },
{ pattern: '\\b\\d+\\b', type: 'number' }
]
});
}
}
๐ ์์ ํ๋ฌ๊ทธ์ธ 3: ๊ฒ์ ๋ฐ ์นํ
class SearchReplacePlugin implements Plugin {
name = 'search-replace';
version = '1.0.0';
private searchHistory: string[] = [];
private currentMatches: SearchMatch[] = [];
initialize(context: PluginContext): void {
// ๊ฒ์ ๋ช
๋ น์ด
context.commands.register('search.find', (query: string) => {
this.find(context, query);
});
// ์นํ ๋ช
๋ น์ด
context.commands.register('search.replace', (options: ReplaceOptions) => {
this.replace(context, options);
});
// ์ ์ฒด ์นํ ๋ช
๋ น์ด
context.commands.register('search.replaceAll', (options: ReplaceOptions) => {
this.replaceAll(context, options);
});
// UI ํจ๋ ์์ฑ
context.ui.createPanel({
id: 'search-panel',
title: 'Search & Replace',
position: 'right',
content: this.createSearchUI()
});
}
private find(context: PluginContext, query: string): void {
const content = context.editor.getContent();
this.currentMatches = [];
// ์ ๊ท์ ๋๋ ์ผ๋ฐ ๋ฌธ์์ด ๊ฒ์
const regex = new RegExp(query, 'gi');
let match;
while ((match = regex.exec(content)) !== null) {
this.currentMatches.push({
start: match.index,
end: match.index + match[0].length,
text: match[0]
});
}
// ๊ฒ์ ๊ฒฐ๊ณผ ๋ฐํ
context.eventBus.emit('search.completed', {
query,
matches: this.currentMatches,
count: this.currentMatches.length
});
// ๊ฒ์ ํ์คํ ๋ฆฌ ์ ์ฅ
this.searchHistory.push(query);
context.ui.showNotification(
`Found ${this.currentMatches.length} matches`
);
}
private replace(
context: PluginContext,
options: ReplaceOptions
): void {
if (this.currentMatches.length === 0) {
context.ui.showNotification('No matches to replace');
return;
}
const match = this.currentMatches[options.index || 0];
const content = context.editor.getContent();
const newContent =
content.slice(0, match.start) +
options.replacement +
content.slice(match.end);
context.editor.setContent(newContent);
// ๋งค์น ๋ชฉ๋ก ์
๋ฐ์ดํธ
this.currentMatches.splice(options.index || 0, 1);
context.eventBus.emit('search.replaced', {
position: match.start,
oldText: match.text,
newText: options.replacement
});
}
private replaceAll(
context: PluginContext,
options: ReplaceOptions
): void {
let content = context.editor.getContent();
let replacedCount = 0;
// ๋ค์์๋ถํฐ ์นํ (์ธ๋ฑ์ค ๋ณ๊ฒฝ ๋ฐฉ์ง)
for (let i = this.currentMatches.length - 1; i >= 0; i--) {
const match = this.currentMatches[i];
content =
content.slice(0, match.start) +
options.replacement +
content.slice(match.end);
replacedCount++;
}
context.editor.setContent(content);
this.currentMatches = [];
context.ui.showNotification(
`Replaced ${replacedCount} occurrences`
);
}
}
โข ๋ช ๋ น์ด ์์คํ : ์ฌ์ฉ์ ์ก์ ์ ํ๋ฌ๊ทธ์ธ ๊ธฐ๋ฅ๊ณผ ์ฐ๊ฒฐ
โข UI ํ์ฅ: ํ๋ฌ๊ทธ์ธ์ด ์๋ํฐ UI๋ฅผ ํ์ฅ
โข ์ํ ๊ด๋ฆฌ: ๊ฐ ํ๋ฌ๊ทธ์ธ์ด ๋ ๋ฆฝ์ ์ผ๋ก ์ํ ์ ์ง
โข ์๋ฌ ๊ฒฉ๋ฆฌ: ํ ํ๋ฌ๊ทธ์ธ์ ์ค๋ฅ๊ฐ ๋ค๋ฅธ ํ๋ฌ๊ทธ์ธ์ ์ํฅ ์์
๐ ํ๋ฌ๊ทธ์ธ ์ฑ๋ฅ ๋ชจ๋ํฐ๋ง๊ณผ ์ต์ ํ
ํ๋ฌ๊ทธ์ธ์ด ๋ง์์ง๋ฉด ์ฑ๋ฅ ๋ฌธ์ ๊ฐ ์๊ธธ ์ ์์ด. ๊ทธ๋์ ๋ชจ๋ํฐ๋ง๊ณผ ์ต์ ํ๊ฐ ์ ๋ง ์ค์ํด! ๐
โฑ๏ธ ์ฑ๋ฅ ๋ฉํธ๋ฆญ ์์ง
class PluginPerformanceMonitor {
private metrics: Map<string, PluginMetrics> = new Map();
startMeasure(pluginName: string, operation: string): PerformanceMark {
const markId = `${pluginName}:${operation}:${Date.now()}`;
performance.mark(`${markId}-start`);
return {
end: () => {
performance.mark(`${markId}-end`);
performance.measure(
markId,
`${markId}-start`,
`${markId}-end`
);
const measure = performance.getEntriesByName(markId)[0];
this.recordMetric(pluginName, operation, measure.duration);
// ๋ฉ๋ชจ๋ฆฌ ์ฌ์ฉ๋๋ ๊ธฐ๋ก
if (performance.memory) {
this.recordMemory(pluginName, performance.memory.usedJSHeapSize);
}
}
};
}
private recordMetric(
pluginName: string,
operation: string,
duration: number
): void {
if (!this.metrics.has(pluginName)) {
this.metrics.set(pluginName, {
name: pluginName,
operations: new Map(),
totalTime: 0,
callCount: 0,
memoryUsage: 0
});
}
const pluginMetrics = this.metrics.get(pluginName)!;
if (!pluginMetrics.operations.has(operation)) {
pluginMetrics.operations.set(operation, {
count: 0,
totalDuration: 0,
avgDuration: 0,
maxDuration: 0
});
}
const opMetrics = pluginMetrics.operations.get(operation)!;
opMetrics.count++;
opMetrics.totalDuration += duration;
opMetrics.avgDuration = opMetrics.totalDuration / opMetrics.count;
opMetrics.maxDuration = Math.max(opMetrics.maxDuration, duration);
pluginMetrics.totalTime += duration;
pluginMetrics.callCount++;
}
getReport(): PerformanceReport {
const plugins = Array.from(this.metrics.values());
// ์ฑ๋ฅ ์์ผ๋ก ์ ๋ ฌ
plugins.sort((a, b) => b.totalTime - a.totalTime);
return {
timestamp: Date.now(),
plugins,
summary: {
totalPlugins: plugins.length,
totalTime: plugins.reduce((sum, p) => sum + p.totalTime, 0),
slowestPlugin: plugins[0]?.name,
recommendations: this.generateRecommendations(plugins)
}
};
}
private generateRecommendations(plugins: PluginMetrics[]): string[] {
const recommendations: string[] = [];
for (const plugin of plugins) {
// ํ๊ท ์คํ ์๊ฐ์ด 100ms ์ด์์ธ ํ๋ฌ๊ทธ์ธ
const avgTime = plugin.totalTime / plugin.callCount;
if (avgTime > 100) {
recommendations.push(
`Plugin "${plugin.name}" has high average execution time (${avgTime.toFixed(2)}ms)`
);
}
// ๋ฉ๋ชจ๋ฆฌ ์ฌ์ฉ๋์ด 50MB ์ด์์ธ ํ๋ฌ๊ทธ์ธ
if (plugin.memoryUsage > 50 * 1024 * 1024) {
recommendations.push(
`Plugin "${plugin.name}" uses excessive memory (${(plugin.memoryUsage / 1024 / 1024).toFixed(2)}MB)`
);
}
}
return recommendations;
}
}
๐ ์ฑ๋ฅ ์ต์ ํ ๊ธฐ๋ฒ
๋น๋ฒํ ์ด๋ฒคํธ๋ฅผ ์ ์ดํ์ฌ ๋ถํ์ํ ์คํ์ ์ค์ฌ:
class OptimizedPlugin implements Plugin {
private debouncedSave: Function;
private throttledUpdate: Function;
initialize(context: PluginContext): void {
// ๋๋ฐ์ด์ฑ: ๋ง์ง๋ง ํธ์ถ ํ ์ผ์ ์๊ฐ ๋๊ธฐ
this.debouncedSave = this.debounce(() => {
this.save(context);
}, 1000);
// ์ฐ๋กํ๋ง: ์ผ์ ์๊ฐ๋ง๋ค ํ ๋ฒ๋ง ์คํ
this.throttledUpdate = this.throttle(() => {
this.update(context);
}, 100);
context.eventBus.on('content.changed', () => {
this.debouncedSave();
this.throttledUpdate();
});
}
private debounce(func: Function, wait: number): Function {
let timeout: any;
return (...args: any[]) => {
clearTimeout(timeout);
timeout = setTimeout(() => func(...args), wait);
};
}
private throttle(func: Function, limit: number): Function {
let inThrottle: boolean;
return (...args: any[]) => {
if (!inThrottle) {
func(...args);
inThrottle = true;
setTimeout(() => inThrottle = false, limit);
}
};
}
}
๋๋์ ๋ฐ์ดํฐ๋ฅผ ๋ค๋ฃฐ ๋๋ ๋ณด์ด๋ ๋ถ๋ถ๋ง ๋ ๋๋ง:
class VirtualScrollPlugin implements Plugin {
private visibleRange = { start: 0, end: 50 };
private itemHeight = 30;
private totalItems = 10000;
initialize(context: PluginContext): void {
context.eventBus.on('scroll', (scrollTop: number) => {
this.updateVisibleRange(scrollTop);
this.renderVisibleItems(context);
});
}
private updateVisibleRange(scrollTop: number): void {
const start = Math.floor(scrollTop / this.itemHeight);
const end = start + 50; // ํ๋ฉด์ ๋ณด์ด๋ ์์ดํ
์
this.visibleRange = { start, end };
}
private renderVisibleItems(context: PluginContext): void {
const items = this.getItemsInRange(
this.visibleRange.start,
this.visibleRange.end
);
// ๋ณด์ด๋ ์์ดํ
๋ง ๋ ๋๋ง
context.eventBus.emit('render.items', {
items,
offset: this.visibleRange.start * this.itemHeight
});
}
}
๋ฌด๊ฑฐ์ด ์์ ์ ๋ณ๋ ์ค๋ ๋์์ ์ฒ๋ฆฌ:
class HeavyProcessPlugin implements Plugin {
private worker: Worker;
initialize(context: PluginContext): void {
this.worker = new Worker('heavy-process-worker.js');
this.worker.onmessage = (event) => {
context.eventBus.emit('process.completed', event.data);
};
context.eventBus.on('process.start', (data) => {
// ๋ฉ์ธ ์ค๋ ๋๋ฅผ ๋ธ๋กํ์ง ์๊ณ ์ฒ๋ฆฌ
this.worker.postMessage(data);
});
}
shutdown(): void {
this.worker.terminate();
}
}
// heavy-process-worker.js
self.onmessage = function(event) {
const result = performHeavyCalculation(event.data);
self.postMessage(result);
};
function performHeavyCalculation(data) {
// ๋ณต์กํ ๊ณ์ฐ ์ํ
let result = 0;
for (let i = 0; i < 1000000; i++) {
result += Math.sqrt(i) * Math.sin(i);
}
return result;
}
๐งช ํ๋ฌ๊ทธ์ธ ํ ์คํธ ์ ๋ต
ํ๋ฌ๊ทธ์ธ ์์คํ ์ ํ ์คํธํ๊ธฐ ๊น๋ค๋ก์. ํ์ง๋ง ์ ๋๋ก ๋ ํ ์คํธ ์ ๋ต์ด ์์ผ๋ฉด ์์ ์ฑ์ ํฌ๊ฒ ๋์ผ ์ ์์ด! ๐ฏ
โ ๋จ์ ํ ์คํธ (Unit Testing)
describe('AutoSavePlugin', () => {
let plugin: AutoSavePlugin;
let mockContext: PluginContext;
beforeEach(() => {
plugin = new AutoSavePlugin();
mockContext = createMockContext();
});
afterEach(() => {
plugin.shutdown();
});
test('should save content after interval', async () => {
plugin.initialize(mockContext);
// ๋ด์ฉ ๋ณ๊ฒฝ ์๋ฎฌ๋ ์ด์
mockContext.eventBus.emit('content.changed', {
newContent: 'test content'
});
// ์ ์ฅ ๋๊ธฐ
await new Promise(resolve => setTimeout(resolve, 31000));
// ์ ์ฅ ์ด๋ฒคํธ ๋ฐ์ ํ์ธ
expect(mockContext.eventBus.emit).toHaveBeenCalledWith(
'file.saved',
expect.any(Object)
);
});
test('should not save if content unchanged', async () => {
plugin.initialize(mockContext);
await new Promise(resolve => setTimeout(resolve, 31000));
// ์ ์ฅ ์ด๋ฒคํธ๊ฐ ๋ฐ์ํ์ง ์์์ผ ํจ
expect(mockContext.eventBus.emit).not.toHaveBeenCalledWith(
'file.saved',
expect.any(Object)
);
});
});
function createMockContext(): PluginContext {
return {
editor: {
getContent: jest.fn(() => ''),
setContent: jest.fn(),
insertText: jest.fn(),
getSelection: jest.fn()
},
eventBus: {
on: jest.fn(),
emit: jest.fn(),
off: jest.fn()
},
commands: {
register: jest.fn(),
execute: jest.fn()
},
ui: {
showNotification: jest.fn(),
createPanel: jest.fn()
}
};
}
๐ ํตํฉ ํ ์คํธ (Integration Testing)
describe('Plugin Integration', () => {
let editor: TextEditor;
let pluginManager: PluginManager;
beforeEach(async () => {
editor = new TextEditor();
pluginManager = editor.getPluginManager();
// ์ฌ๋ฌ ํ๋ฌ๊ทธ์ธ ๋ก๋
await pluginManager.loadPlugin('./plugins/auto-save');
await pluginManager.loadPlugin('./plugins/syntax-highlight');
await pluginManager.loadPlugin('./plugins/search-replace');
});
test('plugins should work together', async () => {
// ๋ด์ฉ ์
๋ ฅ
editor.setContent('function test() { return true; }');
// ๋ฌธ๋ฒ ํ์ด๋ผ์ดํ
์ ์ฉ ํ์ธ
const highlightEvent = await waitForEvent(
editor.eventBus,
'syntax.highlighted'
);
expect(highlightEvent.tokens).toHaveLength(greaterThan(0));
// ๊ฒ์ ๊ธฐ๋ฅ ํ
์คํธ
await editor.executeCommand('search.find', 'function');
const searchEvent = await waitForEvent(
editor.eventBus,
'search.completed'
);
expect(searchEvent.count).toBe(1);
// ์๋ ์ ์ฅ ํ์ธ
await new Promise(resolve => setTimeout(resolve, 31000));
const saveEvent = await waitForEvent(
editor.eventBus,
'file.saved'
);
expect(saveEvent).toBeDefined();
});
test('plugin failure should not affect others', async () => {
// ํ ํ๋ฌ๊ทธ์ธ์์ ์๋ฌ ๋ฐ์
const faultyPlugin = pluginManager.getPlugin('syntax-highlight');
jest.spyOn(faultyPlugin, 'execute').mockImplementation(() => {
throw new Error('Plugin error');
});
editor.setContent('test');
// ๋ค๋ฅธ ํ๋ฌ๊ทธ์ธ๋ค์ ์ ์ ๋์ํด์ผ ํจ
await new Promise(resolve => setTimeout(resolve, 31000));
const saveEvent = await waitForEvent(
editor.eventBus,
'file.saved'
);
expect(saveEvent).toBeDefined();
});
});
async function waitForEvent(
eventBus: EventBus,
eventName: string,
timeout: number = 5000
): Promise<any> {
return new Promise((resolve, reject) => {
const timer = setTimeout(() => {
reject(new Error(`Event ${eventName} not received within ${timeout}ms`));
}, timeout);
eventBus.once(eventName, (data) => {
clearTimeout(timer);
resolve(data);
});
});
}
๐ญ E2E ํ ์คํธ (End-to-End Testing)
describe('Editor E2E Tests', () => {
let browser: Browser;
let page: Page;
beforeAll(async () => {
browser = await puppeteer.launch();
page = await browser.newPage();
await page.goto('http://localhost:3000');
});
afterAll(async () => {
await browser.close();
});
test('user can install and use plugin', async () => {
// ํ๋ฌ๊ทธ์ธ ๋ง์ผํ๋ ์ด์ค ์ด๊ธฐ
await page.click('#plugin-marketplace');
await page.waitForSelector('.plugin-list');
// ํ๋ฌ๊ทธ์ธ ๊ฒ์
await page.type('#plugin-search', 'auto-save');
await page.waitForSelector('.plugin-item');
// ํ๋ฌ๊ทธ์ธ ์ค์น
await page.click('.plugin-item .install-button');
await page.waitForSelector('.notification.success');
// ํ๋ฌ๊ทธ์ธ ํ์ฑํ ํ์ธ
const notification = await page.$eval(
'.notification',
el => el.textContent
);
expect(notification).toContain('Auto-save enabled');
// ์๋ํฐ์ ํ
์คํธ ์
๋ ฅ
await page.type('.editor-content', 'Hello, World!');
// ์๋ ์ ์ฅ ๋๊ธฐ
await page.waitForTimeout(31000);
// ์ ์ฅ ์๋ฆผ ํ์ธ
const saveNotification = await page.$eval(
'.notification',
el => el.textContent
);
expect(saveNotification).toContain('Auto-saved');
});
});
๐ ํ๋ฌ๊ทธ์ธ ๋ฐฐํฌ์ ๋ฒ์ ๊ด๋ฆฌ
ํ๋ฌ๊ทธ์ธ์ ๋ง๋ค์์ผ๋ฉด ๋ค๋ฅธ ์ฌ๋๋ค๊ณผ ๊ณต์ ํด์ผ๊ฒ ์ง? ๋ฐฐํฌ์ ๋ฒ์ ๊ด๋ฆฌ ์ ๋ต๋ ์ค์ํด! ๐ฆ
๐ ํ๋ฌ๊ทธ์ธ ๋งค๋ํ์คํธ ์ค๊ณ
{
"name": "awesome-plugin",
"version": "2.1.0",
"description": "An awesome plugin for text editing",
"author": {
"name": "John Doe",
"email": "john@example.com",
"url": "https://johndoe.com"
},
"license": "MIT",
"repository": {
"type": "git",
"url": "https://github.com/johndoe/awesome-plugin"
},
"main": "dist/index.js",
"engines": {
"editor": ">=3.0.0"
},
"dependencies": {
"lodash": "^4.17.21"
},
"peerDependencies": {
"core-api": "^2.0.0"
},
"permissions": [
"network.fetch",
"storage.local",
"ui.notification"
],
"contributes": {
"commands": [
{
"command": "awesome.doSomething",
"title": "Do Something Awesome",
"category": "Awesome"
}
],
"keybindings": [
{
"command": "awesome.doSomething",
"key": "ctrl+shift+a",
"when": "editorTextFocus"
}
],
"menus": {
"editor/context": [
{
"command": "awesome.doSomething",
"group": "awesome"
}
]
}
},
"scripts": {
"build": "webpack --mode production",
"test": "jest",
"publish": "npm publish"
}
}
๐ ์๋งจํฑ ๋ฒ์ ๋ (Semantic Versioning)
๋ฒ์ ๋ฒํธ๋ MAJOR.MINOR.PATCH ํ์์ ๋ฐ๋ผ์ผ ํด:
โข MAJOR: ํ์ ํธํ์ฑ์ด ๊นจ์ง๋ ๋ณ๊ฒฝ
โข MINOR: ํ์ ํธํ์ฑ์ ์ ์งํ๋ฉด์ ๊ธฐ๋ฅ ์ถ๊ฐ
โข PATCH: ํ์ ํธํ์ฑ์ ์ ์งํ๋ฉด์ ๋ฒ๊ทธ ์์
class VersionManager {
parseVersion(version: string): Version {
const [major, minor, patch] = version.split('.').map(Number);
return { major, minor, patch };
}
isCompatible(required: string, current: string): boolean {
const req = this.parseVersion(required);
const cur = this.parseVersion(current);
// Major ๋ฒ์ ์ด ๋ค๋ฅด๋ฉด ํธํ ๋ถ๊ฐ
if (req.major !== cur.major) {
return false;
}
// Minor ๋ฒ์ ํ์ธ
if (cur.minor < req.minor) {
return false;
}
// Minor๊ฐ ๊ฐ์ผ๋ฉด Patch ํ์ธ
if (cur.minor === req.minor && cur.patch < req.patch) {
return false;
}
return true;
}
checkDependencies(plugin: PluginManifest): DependencyCheckResult {
const issues: string[] = [];
// ์์ง ๋ฒ์ ํ์ธ
if (!this.isCompatible(plugin.engines.editor, this.currentVersion)) {
issues.push(
`Plugin requires editor version ${plugin.engines.editor}, ` +
`but current version is ${this.currentVersion}`
);
}
// ์์กด์ฑ ํ์ธ
for (const [dep, version] of Object.entries(plugin.dependencies || {})) {
const installed = this.getInstalledVersion(dep);
if (!installed) {
issues.push(`Missing dependency: ${dep}@${version}`);
} else if (!this.isCompatible(version, installed)) {
issues.push(
`Incompatible dependency: ${dep}@${version} ` +
`(installed: ${installed})`
);
}
}
return {
compatible: issues.length === 0,
issues
};
}
}
๐ ํ๋ฌ๊ทธ์ธ ๋ง์ผํ๋ ์ด์ค
ํ๋ฌ๊ทธ์ธ์ ์ฝ๊ฒ ์ฐพ๊ณ ์ค์นํ ์ ์๋ ๋ง์ผํ๋ ์ด์ค๋ฅผ ๋ง๋ค๋ฉด ์ํ๊ณ๊ฐ ํ์ฑํ๋ผ!
class PluginMarketplace {
private apiUrl = 'https://marketplace.example.com/api';
async search(query: string, options?: SearchOptions): Promise<PluginInfo[]> {
const params = new URLSearchParams({
q: query,
sort: options?.sort || 'relevance',
category: options?.category || 'all',
page: String(options?.page || 1)
});
const response = await fetch(`${this.apiUrl}/search?${params}`);
const data = await response.json();
return data.plugins;
}
async install(pluginName: string, version?: string): Promise<void> {
// ํ๋ฌ๊ทธ์ธ ์ ๋ณด ์กฐํ
const info = await this.getPluginInfo(pluginName);
const targetVersion = version || info.latestVersion;
// ์์กด์ฑ ํ์ธ
const manifest = await this.getManifest(pluginName, targetVersion);
const depCheck = this.versionManager.checkDependencies(manifest);
if (!depCheck.compatible) {
throw new Error(
`Cannot install ${pluginName}: ${depCheck.issues.join(', ')}`
);
}
// ํ๋ฌ๊ทธ์ธ ๋ค์ด๋ก๋
const pluginData = await this.download(pluginName, targetVersion);
// ์๋ช
๊ฒ์ฆ
if (!await this.verifySignature(pluginData)) {
throw new Error('Plugin signature verification failed');
}
// ์ค์น
await this.pluginManager.install(pluginData);
console.log(`Successfully installed ${pluginName}@${targetVersion}`);
}
async update(pluginName: string): Promise<void> {
const current = this.pluginManager.getPlugin(pluginName);
if (!current) {
throw new Error(`Plugin ${pluginName} is not installed`);
}
const info = await this.getPluginInfo(pluginName);
if (this.versionManager.isNewer(info.latestVersion, current.version)) {
await this.install(pluginName, info.latestVersion);
console.log(
`Updated ${pluginName} from ${current.version} to ${info.latestVersion}`
);
} else {
console.log(`${pluginName} is already up to date`);
}
}
async uninstall(pluginName: string): Promise<void> {
// ์์กดํ๋ ๋ค๋ฅธ ํ๋ฌ๊ทธ์ธ ํ์ธ
const dependents = this.findDependentPlugins(pluginName);
if (dependents.length > 0) {
throw new Error(
`Cannot uninstall ${pluginName}: ` +
`Required by ${dependents.join(', ')}`
);
}
await this.pluginManager.uninstall(pluginName);
console.log(`Successfully uninstalled ${pluginName}`);
}
}
๐ผ ์ค๋ฌด์์์ ํ๋ฌ๊ทธ์ธ ์ํคํ ์ฒ ํ์ฉ
์ด๋ก ์ ์ถฉ๋ถํ ๋ดค์ผ๋ ์ค๋ฌด์์ ์ด๋ป๊ฒ ํ์ฉํ๋์ง ์์๋ณผ๊น? ์ค์ ํ๋ก์ ํธ์ ์ ์ฉํ ๋ ๊ณ ๋ คํด์ผ ํ ์ ๋ค์ด์ผ! ๐ก
๐ฏ ์ธ์ ํ๋ฌ๊ทธ์ธ ์ํคํ ์ฒ๋ฅผ ์ฌ์ฉํด์ผ ํ ๊น?
โข ๋ค์ํ ์๊ตฌ์ฌํญ: ๋ชจ๋ ์ฌ์ฉ์์๊ฒ ๋ชจ๋ ๊ธฐ๋ฅ์ด ํ์ํ์ง ์์ ๋
โข ์๋ํํฐ ํตํฉ: ์ธ๋ถ ๊ฐ๋ฐ์๋ค์ด ๊ธฐ๋ฅ์ ์ถ๊ฐํ ์ ์์ด์ผ ํ ๋
โข ๋ชจ๋ํ๋ ๊ฐ๋ฐ: ํ์ด ๋ ๋ฆฝ์ ์ผ๋ก ๊ธฐ๋ฅ์ ๊ฐ๋ฐํด์ผ ํ ๋
โข ์ ์ง์ ๊ธฐ๋ฅ ์ถ๊ฐ: ์๊ฐ์ ๋ฐ๋ผ ๊ธฐ๋ฅ์ ์ถ๊ฐํด์ผ ํ ๋
โข ์ฑ๋ฅ์ด ์ต์ฐ์ : ์ค๋ฒํค๋๋ฅผ ์ต์ํํด์ผ ํ ๋
โข ๋ณด์์ด ๊ทน๋๋ก ์ค์: ์ธ๋ถ ์ฝ๋ ์คํ์ด ์ํํ ๋
โข ์์ ํ: ๋ณต์กํ ์ํคํ ์ฒ๋ฅผ ๊ด๋ฆฌํ ๋ฆฌ์์ค๊ฐ ๋ถ์กฑํ ๋
๐ข ๊ธฐ์ ํ๊ฒฝ์์์ ๊ณ ๋ ค์ฌํญ
๊ธฐ์ ์์ ํ๋ฌ๊ทธ์ธ ์์คํ ์ ๋์ ํ ๋๋ ์ถ๊ฐ์ ์ธ ๊ณ ๋ ค์ฌํญ๋ค์ด ์์ด:
class EnterprisePluginManager extends PluginManager {
private auditLog: AuditLogger;
private complianceChecker: ComplianceChecker;
private licenseManager: LicenseManager;
async loadPlugin(pluginPath: string): Promise<Plugin> {
// 1. ๋ผ์ด์ ์ค ํ์ธ
const license = await this.licenseManager.verify(pluginPath);
if (!license.valid) {
throw new Error('Invalid or expired license');
}
// 2. ์ปดํ๋ผ์ด์ธ์ค ์ฒดํฌ
const compliance = await this.complianceChecker.check(pluginPath);
if (!compliance.passed) {
this.auditLog.warning('Compliance check failed', {
plugin: pluginPath,
issues: compliance.issues
});
throw new Error('Plugin does not meet compliance requirements');
}
// 3. ๋ณด์ ์ค์บ
const securityScan = await this.scanForVulnerabilities(pluginPath);
if (securityScan.hasVulnerabilities) {
this.auditLog.error('Security vulnerabilities detected', {
plugin: pluginPath,
vulnerabilities: securityScan.vulnerabilities
});
throw new Error('Plugin has security vulnerabilities');
}
// 4. ๊ฐ์ฌ ๋ก๊ทธ ๊ธฐ๋ก
this.auditLog.info('Plugin loading initiated', {
plugin: pluginPath,
user: this.getCurrentUser(),
timestamp: Date.now()
});
// 5. ํ๋ฌ๊ทธ์ธ ๋ก๋
const plugin = await super.loadPlugin(pluginPath);
// 6. ๋ก๋ ์ฑ๊ณต ๊ธฐ๋ก
this.auditLog.info('Plugin loaded successfully', {
plugin: plugin.name,
version: plugin.version
});
return plugin;
}
private async scanForVulnerabilities(
pluginPath: string
): Promise<SecurityScanResult> {
// ์ ์ ๋ถ์
const staticAnalysis = await this.performStaticAnalysis(pluginPath);
// ์์กด์ฑ ์ทจ์ฝ์ ์ฒดํฌ
const dependencyCheck = await this.checkDependencyVulnerabilities(pluginPath);
// ์
์ฑ ์ฝ๋ ํจํด ๊ฒ์ฌ
const malwareCheck = await this.scanForMalware(pluginPath);
return {
hasVulnerabilities:
staticAnalysis.issues.length > 0 ||
dependencyCheck.vulnerabilities.length > 0 ||
malwareCheck.detected,
vulnerabilities: [
...staticAnalysis.issues,
...dependencyCheck.vulnerabilities,
...(malwareCheck.detected ? ['Malware detected'] : [])
]
};
}
}
๐ ํ์ฅ์ฑ๊ณผ ์ฑ๋ฅ์ ๊ท ํ
ํ๋ฌ๊ทธ์ธ ์์คํ ์ ํ์ฅ์ฑ์ ์ ๊ณตํ์ง๋ง ์ฑ๋ฅ ์ค๋ฒํค๋๋ ์์ด. ๊ท ํ์ ๋ง์ถ๋ ๊ฒ ์ค์ํด!
| ์ ๋ต | ์ฅ์ | ๋จ์ | ์ ์ฉ ์๋๋ฆฌ์ค |
|---|---|---|---|
| ์ฆ์ ๋ก๋ฉ | ๋น ๋ฅธ ์๋ต ์๊ฐ | ๊ธด ์์ ์๊ฐ | ํต์ฌ ํ๋ฌ๊ทธ์ธ |
| ์ง์ฐ ๋ก๋ฉ | ๋น ๋ฅธ ์์ | ์ฒซ ์ฌ์ฉ ์ ์ง์ฐ | ์ ํ์ ๊ธฐ๋ฅ |
| ๋ฐฑ๊ทธ๋ผ์ด๋ ๋ก๋ฉ | ์ฌ์ฉ์ ๊ฒฝํ ๊ฐ์ | ๋ณต์กํ ๊ตฌํ | ๋์ฉ๋ ํ๋ฌ๊ทธ์ธ |
| ์จ๋๋งจ๋ ๋ก๋ฉ | ์ต์ ๋ฉ๋ชจ๋ฆฌ ์ฌ์ฉ | ์์ธก ๋ถ๊ฐ๋ฅํ ์ฑ๋ฅ | ๋๋ฌผ๊ฒ ์ฌ์ฉ๋๋ ๊ธฐ๋ฅ |
๐ ํ๋ฌ๊ทธ์ธ ๊ฐ๋ฐ์๋ฅผ ์ํ ๊ฐ์ด๋๋ผ์ธ
ํ๋ฌ๊ทธ์ธ ์ํ๊ณ๋ฅผ ๊ฑด๊ฐํ๊ฒ ์ ์งํ๋ ค๋ฉด ๊ฐ๋ฐ์๋ค์ ์ํ ๋ช ํํ ๊ฐ์ด๋๋ผ์ธ์ด ํ์ํด! ๐
โจ ์ข์ ํ๋ฌ๊ทธ์ธ์ ํน์ง
ํ๋ฌ๊ทธ์ธ์ ํ๋์ ๋ช ํํ ๋ชฉ์ ์ ๊ฐ์ ธ์ผ ํด:
// โ ๋์ ์: ๋๋ฌด ๋ง์ ๊ธฐ๋ฅ
class SwissArmyKnifePlugin implements Plugin {
// ํ์ผ ๊ด๋ฆฌ
saveFile() { }
loadFile() { }
// ๋คํธ์ํฌ ํต์
sendRequest() { }
// UI ๋ ๋๋ง
renderComponent() { }
// ๋ฐ์ดํฐ ๋ถ์
analyzeData() { }
}
// โ
์ข์ ์: ๋ช
ํํ ๋จ์ผ ๋ชฉ์
class FileManagerPlugin implements Plugin {
saveFile() { }
loadFile() { }
deleteFile() { }
renameFile() { }
}
์ฌ์ฉํ๊ธฐ ์ฝ๊ณ ์ง๊ด์ ์ธ API๋ฅผ ์ ๊ณตํด์ผ ํด:
// โ
์ข์ API ์ค๊ณ
class ThemePlugin implements Plugin {
// ๋ช
ํํ ๋ฉ์๋ ์ด๋ฆ
setTheme(themeName: string): void { }
getTheme(): string { }
getAvailableThemes(): string[] { }
// ์ด๋ฒคํธ ๊ธฐ๋ฐ ์๋ฆผ
onThemeChanged(callback: (theme: string) => void): void { }
// ์ ํจ์ฑ ๊ฒ์ฌ
isValidTheme(themeName: string): boolean { }
// ์๋ฌ ์ฒ๋ฆฌ
async loadTheme(themeName: string): Promise<Theme> {
if (!this.isValidTheme(themeName)) {
throw new InvalidThemeError(`Theme "${themeName}" not found`);
}
// ...
}
}
README์ API ๋ฌธ์๋ฅผ ์ถฉ์คํ ์์ฑํด์ผ ํด:
/**
* ThemePlugin - ์ ํ๋ฆฌ์ผ์ด์
ํ
๋ง๋ฅผ ๊ด๋ฆฌํ๋ ํ๋ฌ๊ทธ์ธ
*
* @example
* ```typescript
* const themePlugin = new ThemePlugin();
* await themePlugin.initialize(context);
*
* // ํ
๋ง ๋ณ๊ฒฝ
* await themePlugin.setTheme('dark');
*
* // ํ
๋ง ๋ณ๊ฒฝ ๊ฐ์ง
* themePlugin.onThemeChanged((theme) => {
* console.log(`Theme changed to: ${theme}`);
* });
* ```
*
* @see https://docs.example.com/plugins/theme
*/
class ThemePlugin implements Plugin {
/**
* ํ
๋ง๋ฅผ ๋ณ๊ฒฝํฉ๋๋ค
*
* @param themeName - ์ ์ฉํ ํ
๋ง ์ด๋ฆ ('light', 'dark', 'auto')
* @throws {InvalidThemeError} ์ ํจํ์ง ์์ ํ
๋ง ์ด๋ฆ์ธ ๊ฒฝ์ฐ
* @fires ThemePlugin#themeChanged
*/
async setTheme(themeName: string): Promise<void> {
// ๊ตฌํ...
}
}
๐ ๏ธ ํ๋ฌ๊ทธ์ธ ๊ฐ๋ฐ ๋๊ตฌ
๊ฐ๋ฐ์ ๊ฒฝํ์ ๊ฐ์ ํ๋ ๋๊ตฌ๋ค์ ์ ๊ณตํ๋ฉด ์ํ๊ณ๊ฐ ํ์ฑํ๋ผ:
// ํ๋ฌ๊ทธ์ธ ์์ฑ CLI ๋๊ตฌ
class PluginCLI {
async create(name: string, options: CreateOptions): Promise<void> {
console.log(`Creating plugin: ${name}`);
// ํ๋ก์ ํธ ๊ตฌ์กฐ ์์ฑ
await this.createProjectStructure(name);
// ํ
ํ๋ฆฟ ํ์ผ ๋ณต์ฌ
await this.copyTemplates(name, options.template);
// package.json ์์ฑ
await this.createPackageJson(name, options);
// ์์กด์ฑ ์ค์น
await this.installDependencies(name);
console.log(`โ Plugin ${name} created successfully!`);
console.log(`\nNext steps:`);
console.log(` cd ${name}`);
console.log(` npm run dev`);
}
async dev(pluginPath: string): Promise<void> {
// ๊ฐ๋ฐ ์๋ฒ ์์
const watcher = this.createFileWatcher(pluginPath);
watcher.on('change', async (file) => {
console.log(`File changed: ${file}`);
// ์๋ ๋น๋
await this.build(pluginPath);
// ํซ ๋ฆฌ๋ก๋
await this.hotReload(pluginPath);
});
console.log('Development server started');
console.log('Watching for changes...');
}
async test(pluginPath: string): Promise<void> {
// ํ
์คํธ ์คํ
const results = await this.runTests(pluginPath);
// ์ปค๋ฒ๋ฆฌ์ง ๋ฆฌํฌํธ
await this.generateCoverageReport(results);
// ๊ฒฐ๊ณผ ์ถ๋ ฅ
this.printTestResults(results);
}
async publish(pluginPath: string): Promise<void> {
// ๋น๋
await this.build(pluginPath);
// ํ
์คํธ
await this.test(pluginPath);
// ๋ฒ์ ํ์ธ
const version = await this.promptVersion();
// ํจํค์ง
const packagePath = await this.package(pluginPath, version);
// ๋ง์ผํ๋ ์ด์ค์ ์
๋ก๋
await this.uploadToMarketplace(packagePath);
console.log(`โ Plugin published successfully!`);
}
}
๐ ์ฑ๊ณต ์ฌ๋ก์ ๋ฒ ์คํธ ํ๋ํฐ์ค
์ค์ ๋ก ํ๋ฌ๊ทธ์ธ ์ํคํ ์ฒ๋ฅผ ์ฑ๊ณต์ ์ผ๋ก ํ์ฉํ ์ฌ๋ก๋ค์ ๋ณด๋ฉด์ ๋ฐฐ์๋ณด์! ๐ฏ
๐ป Visual Studio Code
VS Code๋ ํ๋ฌ๊ทธ์ธ ์ํคํ
์ฒ์ ๊ต๊ณผ์ ๊ฐ์ ์์์ผ. ํต์ฌ ์๋ํฐ๋ ์ต์ํ์ผ๋ก ์ ์งํ๊ณ , ๊ฑฐ์ ๋ชจ๋ ๊ธฐ๋ฅ์ ํ์ฅ์ผ๋ก ์ ๊ณตํ์ง.
์ฃผ์ ํน์ง:
โข Extension API๋ฅผ ํตํ ๋ช
ํํ ์ธํฐํ์ด์ค
โข ์ธ์ด ์๋ฒ ํ๋กํ ์ฝ(LSP)๋ก ์ธ์ด ์ง์ ํ์คํ
โข ๋ง์ผํ๋ ์ด์ค๋ฅผ ํตํ ์ฌ์ด ๋ฐฐํฌ์ ์ค์น
โข ์๋๋ฐ์ค ํ๊ฒฝ์์ ํ์ฅ ์คํ
๐จ Figma
Figma์ ํ๋ฌ๊ทธ์ธ ์์คํ
์ ์น ๊ธฐ๋ฐ ์ ํ๋ฆฌ์ผ์ด์
์์๋ ๊ฐ๋ ฅํ ํ์ฅ์ฑ์ ์ ๊ณตํ ์ ์๋ค๋ ๊ฑธ ๋ณด์ฌ์ค.
์ฃผ์ ํน์ง:
โข ์น ์์ปค๋ฅผ ํ์ฉํ ์์ ํ ํ๋ฌ๊ทธ์ธ ์คํ
โข ์ ํ๋ API๋ก ์ฑ๋ฅ๊ณผ ๋ณด์ ์ ์ง
โข ์ค์๊ฐ ํ์
ํ๊ฒฝ์์๋ ๋์ํ๋ ํ๋ฌ๊ทธ์ธ
๐ง WordPress
WordPress๋ ํ๋ฌ๊ทธ์ธ๊ณผ ํ
๋ง ์์คํ
์ผ๋ก ์ ์ธ๊ณ ์น์ฌ์ดํธ์ 40% ์ด์์ ์ง์ํ๊ณ ์์ด.
์ฃผ์ ํน์ง:
โข ํ
(Hook) ์์คํ
์ผ๋ก ์ฝ์ด ์์ ์์ด ๊ธฐ๋ฅ ํ์ฅ
โข ๋ฐฉ๋ํ ํ๋ฌ๊ทธ์ธ ์ํ๊ณ
โข ํ์ ํธํ์ฑ ์ ์ง
2. ํ๋ฐํ ์ปค๋ฎค๋ํฐ: ์ง๋ฌธํ๊ณ ๋ฐฐ์ธ ์ ์๋ ํ๊ฒฝ
3. ์ข์ ๊ฐ๋ฐ์ ๋๊ตฌ: ๋๋ฒ๊น ๊ณผ ํ ์คํธ๋ฅผ ์ฝ๊ฒ
4. ํ์ง ๊ด๋ฆฌ: ๋ฆฌ๋ทฐ์ ๊ฒ์ฆ ํ๋ก์ธ์ค
5. ์ง์์ ์ธ ๊ฐ์ : ํผ๋๋ฐฑ์ ๋ฐ์ํ API ๋ฐ์
๐ ๋ฏธ๋์ ํ๋ฌ๊ทธ์ธ ์ํคํ ์ฒ
๊ธฐ์ ์ ๊ณ์ ๋ฐ์ ํ๊ณ ์์ด. ์์ผ๋ก ํ๋ฌ๊ทธ์ธ ์ํคํ ์ฒ๋ ์ด๋ป๊ฒ ์งํํ ๊น? ๐ฎ
๐ค AI ๊ธฐ๋ฐ ํ๋ฌ๊ทธ์ธ
AI๊ฐ ํ๋ฌ๊ทธ์ธ ๊ฐ๋ฐ๊ณผ ๊ด๋ฆฌ๋ฅผ ๋์์ฃผ๋ ์๋๊ฐ ์ค๊ณ ์์ด:
class AIPluginAssistant {
async suggestPlugins(context: UserContext): Promise<PluginSuggestion[]> {
// ์ฌ์ฉ์์ ์์
ํจํด ๋ถ์
const patterns = await this.analyzeUserBehavior(context);
// AI ๋ชจ๋ธ๋ก ์ ํฉํ ํ๋ฌ๊ทธ์ธ ์ถ์ฒ
const suggestions = await this.aiModel.predict({
patterns,
installedPlugins: context.installedPlugins,
preferences: context.preferences
});
return suggestions;
}
async generatePlugin(description: string): Promise<Plugin> {
// ์์ฐ์ด ์ค๋ช
์ ํ๋ฌ๊ทธ์ธ ์ฝ๋๋ก ๋ณํ
const code = await this.aiModel.generateCode(description);
// ์์ฑ๋ ์ฝ๋ ๊ฒ์ฆ
const validation = await this.validateGeneratedCode(code);
if (!validation.safe) {
throw new Error('Generated code failed safety checks');
}
return this.compilePlugin(code);
}
}
๐ WebAssembly ํ๋ฌ๊ทธ์ธ
WebAssembly๋ฅผ ์ฌ์ฉํ๋ฉด ๋ค์ํ ์ธ์ด๋ก ์์ฑ๋ ํ๋ฌ๊ทธ์ธ์ ์คํํ ์ ์์ด:
class WasmPluginLoader {
async loadWasmPlugin(wasmPath: string): Promise<Plugin> {
// WASM ๋ชจ๋ ๋ก๋
const wasmModule = await WebAssembly.instantiateStreaming(
fetch(wasmPath),
{
env: this.createWasmEnvironment()
}
);
// ํ๋ฌ๊ทธ์ธ ์ธํฐํ์ด์ค ๋ํ
return this.wrapWasmModule(wasmModule);
}
private createWasmEnvironment(): any {
return {
// ์ ํ๋ ์์คํ
ํธ์ถ๋ง ์ ๊ณต
log: (ptr: number, len: number) => {
const message = this.readString(ptr, len);
console.log(message);
},
// ๋ฉ๋ชจ๋ฆฌ ์์ ์ฑ ๋ณด์ฅ
malloc: (size: number) => this.allocate(size),
free: (ptr: number) => this.deallocate(ptr)
};
}
}
โ๏ธ ํด๋ผ์ฐ๋ ๊ธฐ๋ฐ ํ๋ฌ๊ทธ์ธ
ํ๋ฌ๊ทธ์ธ์ด ํด๋ผ์ฐ๋์์ ์คํ๋์ด ๋ก์ปฌ ๋ฆฌ์์ค๋ฅผ ์ ์ฝํ ์ ์์ด:
class CloudPluginExecutor {
async executeRemote(
pluginName: string,
method: string,
args: any[]
): Promise<any> {
// ํด๋ผ์ฐ๋ ํจ์ ํธ์ถ
const response = await fetch(`${this.cloudUrl}/execute`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${this.token}`
},
body: JSON.stringify({
plugin: pluginName,
method,
args
})
});
if (!response.ok) {
throw new Error('Remote execution failed');
}
return await response.json();
}
// ๋ก์ปฌ๊ณผ ์๊ฒฉ ์คํ์ ์๋์ผ๋ก ์ ํ
async smartExecute(
plugin: Plugin,
method: string,
args: any[]
): Promise<any> {
// ํ๋ฌ๊ทธ์ธ ํฌ๊ธฐ์ ๋ณต์ก๋ ๋ถ์
const complexity = this.analyzeComplexity(plugin, method);
// ๋คํธ์ํฌ ์ํ ํ์ธ
const networkSpeed = await this.checkNetworkSpeed();
// ์ต์ ์ ์คํ ๋ฐฉ์ ๊ฒฐ์
if (complexity.high && networkSpeed.good) {
return this.executeRemote(plugin.name, method, args);
} else {
return plugin[method](...args);
}
}
}
๐ ํ์ต ๋ฆฌ์์ค์ ์ปค๋ฎค๋ํฐ
ํ๋ฌ๊ทธ์ธ ์ํคํ
์ฒ๋ฅผ ๋ ๊น์ด ๋ฐฐ์ฐ๊ณ ์ถ๋ค๋ฉด? ์ด๋ฐ ๋ฆฌ์์ค๋ค์ ํ์ฉํด๋ด! ๐
ํน์ ํ๋ฌ๊ทธ์ธ ๊ฐ๋ฐ์ด๋ ์ํคํ
์ฒ ์ค๊ณ์ ๋ํด ๋ ๋ฐฐ์ฐ๊ณ ์ถ๋ค๋ฉด, ์ฌ๋ฅ๋ท์์ ๊ด๋ จ ์ ๋ฌธ๊ฐ๋ค์ ์ฐพ์๋ณผ ์ ์์ด. ์ค๋ฌด ๊ฒฝํ์ด ํ๋ถํ ๊ฐ๋ฐ์๋ค์ด ๋ฉํ ๋ง๋ ํด์ฃผ๊ณ , ์ฝ๋ ๋ฆฌ๋ทฐ๋ ๋์์ค ๊ฑฐ์ผ! ๐ฏ
โข "Clean Architecture" - Robert C. Martin
โข "Building Microservices" - Sam Newman
โข "Software Architecture Patterns" - Mark Richards
โข Chrome Extension ๊ฐ๋ฐ ๊ฐ์ด๋
โข WordPress Plugin Handbook
โข Eclipse Plugin Development Environment
โข Reddit์ r/programming ์ปค๋ฎค๋ํฐ
โข GitHub์ ์คํ์์ค ํ๋ฌ๊ทธ์ธ ํ๋ก์ ํธ๋ค
โข ๊ฐ ํ๋ซํผ๋ณ ๊ฐ๋ฐ์ ํฌ๋ผ
๊ทธ๋ฆฌ๊ณ ์ฌ๋ฅ๋ท ๊ฐ์ ํ๋ซํผ์์๋ ์ค์ ํ๋ก์ ํธ ๊ฒฝํ์ ๊ฐ์ง ๊ฐ๋ฐ์๋ค๊ณผ ์ง์ ์ํตํ ์ ์์ด. ์ด๋ก ๋ง์ผ๋ก๋ ๋ถ์กฑํ ์ค๋ฌด ๋ ธํ์ฐ๋ฅผ ๋ฐฐ์ธ ์ ์๋ ์ข์ ๊ธฐํ์ง! ๐ช
๐ฌ ๋ง๋ฌด๋ฆฌํ๋ฉฐ
์, ์ฌ๊ธฐ๊น์ง ํ๋ฌ๊ทธ์ธ ์ํคํ
์ฒ์ ๋ํด ๊น์ด ์๊ฒ ์์๋ดค์ด! ๐
ํ๋ฌ๊ทธ์ธ ์ํคํ
์ฒ๋ ๋จ์ํ ์ฝ๋๋ฅผ ๋ชจ๋ํํ๋ ๊ฒ ์ด์์ ์๋ฏธ๋ฅผ ๊ฐ์ ธ. ์ฌ์ฉ์์๊ฒ ์ ํ๊ถ์ ์ฃผ๊ณ , ๊ฐ๋ฐ์์๊ฒ ์ฐฝ์์ฑ์ ๋ฐํํ ๊ธฐํ๋ฅผ ์ฃผ๋ฉฐ, ์ ํ์ ๋ฌดํํ ํ์ฅ ๊ฐ๋ฅ์ฑ์ ๋ถ์ฌํ๋ ๊ฐ๋ ฅํ ํจํด์ด์ผ.
๋ฌผ๋ก ์ฒ์๋ถํฐ ์๋ฒฝํ ํ๋ฌ๊ทธ์ธ ์์คํ
์ ๋ง๋ค ํ์๋ ์์ด. ์๊ฒ ์์ํด์ ์ ์ง์ ์ผ๋ก ๊ฐ์ ํด๋๊ฐ๋ฉด ๋ผ. ์ค์ํ ๊ฑด ๋ช
ํํ ์ธํฐํ์ด์ค์ ์ข์ ๋ฌธ์ํ, ๊ทธ๋ฆฌ๊ณ ๊ฐ๋ฐ์ ์ปค๋ฎค๋ํฐ๋ฅผ ๋ง๋๋ ๊ฑฐ์ผ! ๐ฑ
๊ธฐ์ตํด! ์ข์ ํ๋ฌ๊ทธ์ธ ์ํคํ
์ฒ๋:
โข ์ฌ์ฉํ๊ธฐ ์ฝ๊ณ (Easy to use) ๐
โข ํ์ฅํ๊ธฐ ์ฝ๊ณ (Easy to extend) ๐ง
โข ์ ์ง๋ณด์ํ๊ธฐ ์ฝ๊ณ (Easy to maintain) ๐ ๏ธ
โข ์์ ํ๊ณ (Secure) ๐
โข ์ฑ๋ฅ์ด ์ข์์ผ ํด (Performant) โก
์ด์ ๋๋ ๋ฉ์ง ํ๋ฌ๊ทธ์ธ ์์คํ
์ ์ค๊ณํ ์ ์์ ๊ฑฐ์ผ. ํ์ดํ
! ๐โจ
์ฌ๋ฅ๋ท์์ ์ ๋ฌธ๊ฐ๋ค๊ณผ ํจ๊ป ํ๋ก์ ํธ๋ฅผ ์งํํด๋ณด์ธ์!
ํจ๊ป ์ฑ์ฅํ๋ ๊ฐ๋ฐ์ ์ปค๋ฎค๋ํฐ, ์ฌ๋ฅ๋ท ๐ก
๊ด๋ จ ํค์๋
๋๊ธ ์์ฑ
์ด ๊ธ์ ๋ํ ์ฌ๋ฌ๋ถ์ ์๊ฐ์ ๋ค๋ ค์ฃผ์ธ์
๋ก๊ทธ์ธ์ด ํ์ํฉ๋๋ค
๋๊ธ์ ์์ฑํ๋ ค๋ฉด ๋จผ์ ๋ก๊ทธ์ธํด์ฃผ์ธ์.
๋๊ธ 0
์ง์์ธ์ ์ฒ - ์ง์ ์ฌ์ฐ๊ถ ๋ณดํธ ๊ณ ์ง
์ง์ ์ฌ์ฐ๊ถ ๋ณดํธ ๊ณ ์ง
- ์ ์๊ถ ๋ฐ ์์ ๊ถ: ๋ณธ ์ปจํ ์ธ ๋ ์ฌ๋ฅ๋ท์ ๋ ์ AI ๊ธฐ์ ๋ก ์์ฑ๋์์ผ๋ฉฐ, ๋ํ๋ฏผ๊ตญ ์ ์๊ถ๋ฒ ๋ฐ ๊ตญ์ ์ ์๊ถ ํ์ฝ์ ์ํด ๋ณดํธ๋ฉ๋๋ค.
- AI ์์ฑ ์ปจํ ์ธ ์ ๋ฒ์ ์ง์: ๋ณธ AI ์์ฑ ์ปจํ ์ธ ๋ ์ฌ๋ฅ๋ท์ ์ง์ ์ฐฝ์๋ฌผ๋ก ์ธ์ ๋๋ฉฐ, ๊ด๋ จ ๋ฒ๊ท์ ๋ฐ๋ผ ์ ์๊ถ ๋ณดํธ๋ฅผ ๋ฐ์ต๋๋ค.
- ์ฌ์ฉ ์ ํ: ์ฌ๋ฅ๋ท์ ๋ช ์์ ์๋ฉด ๋์ ์์ด ๋ณธ ์ปจํ ์ธ ๋ฅผ ๋ณต์ , ์์ , ๋ฐฐํฌ, ๋๋ ์์ ์ ์ผ๋ก ํ์ฉํ๋ ํ์๋ ์๊ฒฉํ ๊ธ์ง๋ฉ๋๋ค.
- ๋ฐ์ดํฐ ์์ง ๊ธ์ง: ๋ณธ ์ปจํ ์ธ ์ ๋ํ ๋ฌด๋จ ์คํฌ๋ํ, ํฌ๋กค๋ง, ๋ฐ ์๋ํ๋ ๋ฐ์ดํฐ ์์ง์ ๋ฒ์ ์ ์ฌ์ ๋์์ด ๋ฉ๋๋ค.
- AI ํ์ต ์ ํ: ์ฌ๋ฅ๋ท์ AI ์์ฑ ์ปจํ ์ธ ๋ฅผ ํ AI ๋ชจ๋ธ ํ์ต์ ๋ฌด๋จ ์ฌ์ฉํ๋ ํ์๋ ๊ธ์ง๋๋ฉฐ, ์ด๋ ์ง์ ์ฌ์ฐ๊ถ ์นจํด๋ก ๊ฐ์ฃผ๋ฉ๋๋ค.
์์ง ๋๊ธ์ด ์์ต๋๋ค.