UNPKG

@bithero/monaco-editor-vite-plugin

Version:

Vite plugin to include & bundle monaco-editor

193 lines (192 loc) 6.68 kB
import metadata from 'monaco-editor/esm/metadata.js'; import path from 'node:path'; /** * Little helper to ensure that a list of elements only contains truthy values * * @param list the list to filter * @returns the filtered list */ function filterNull(list) { return list.filter(Boolean); } /** * Resolves all languages * * @param languages the languages configuration * @returns the resolved feature definitions */ function resolveLanguages(languages, customLanguages) { if (languages === '*' || languages === 'all') { return filterNull(metadata.languages.concat(customLanguages)); } if (languages.length <= 0) { return filterNull(customLanguages); } const langById = {}; metadata.languages.forEach((l) => langById[l.label] = l); function resolveLanguage(name) { const lang = langById[name]; if (!lang) { console.error("[bithero-monaco] unknown language:", name); return null; } return lang; } return filterNull(languages.map(resolveLanguage).concat(customLanguages)); } /** * Resolves all features * * @param features the features configuration * @returns the resolved feature definitions */ function resolveFeatures(features) { if (!features) { return metadata.features; } if (features === '*' || features === 'all') { return metadata.features; } const featureById = {}; metadata.features.forEach((f) => featureById[f.label] = f); featureById['codicons'] = { label: 'codicons', entry: 'vs/base/browser/ui/codicons/codiconStyles.js', }; function resolveFeature(name) { const feature = featureById[name]; if (!feature) { console.error("[bithero-monaco] unknown feature:", name); return null; } return feature; } const excluded = features.filter((f) => f[0] === '!').map((f) => f.slice(1)); if (excluded.length > 0) { return filterNull(Object.keys(featureById) .filter((f) => !excluded.includes(f)) .map(resolveFeature)); } return filterNull(features.map(resolveFeature)); } /** * Static entry for the editorWorkerService that always needs to be present. */ const editor_module = { label: 'editorWorkerService', entry: undefined, worker: { id: 'vs/editor/editor', entry: 'vs/editor/editor.worker', }, }; /** * Resolves all workers, and also makes sure we're having the editorWorkerService. * * @param languages the resolved languages * @param features the resolved features * @returns list of all workers */ function resolveWorkers(languages, features) { const modules = [editor_module].concat(languages).concat(features); const workers = []; modules.forEach((mod) => { if (mod.worker) { workers.push({ label: mod.label, id: mod.worker.id, entry: mod.worker.entry, }); } }); return workers; } /** * Resolves an module path by utilising `import.meta.resolve`, but making sure we're returning * the string path instead of an URL'ish thing. * * @param file the filepath to resolve * @return the resolved path */ function resolveModule(file) { const url = import.meta.resolve(file).toString(); return url.replace(/^file:\/\//, ''); } /** * Resolves an file path either against the monaco-editor esm package, or by itself. * * @param file the filepath to resolve * @returns the resolved path */ function resolveMonacoPath(file) { try { return resolveModule(path.join('monaco-editor/esm', file)); } catch (e) { } try { return resolveModule(path.join(process.cwd(), 'node_modules/monaco-editor/esm', file)); } catch (e) { } return resolveModule(file); } /** * Plugin to control monaco-editor bundeling * * @param options options for the plugin * @returns a new plugin instance */ export function monaco(options) { const languages = resolveLanguages(options?.languages || [], options?.customLanguages || []); const features = resolveFeatures(options?.features); const workers = resolveWorkers(languages, features); return { name: 'bithero-monaco', enforce: 'pre', config(config) { if (!config.optimizeDeps) { config.optimizeDeps = {}; } const optimizeDeps = config.optimizeDeps; if (!optimizeDeps.exclude) { optimizeDeps.exclude = []; } optimizeDeps.exclude.push('monaco-editor'); if (optimizeDeps.include) { console.log("[bithero-monaco] removed 'monaco-editor' from the optimizeDeps.include setting."); optimizeDeps.include = optimizeDeps.include.filter((i) => i === 'monaco-editor'); } }, load(id) { if (id.match(/esm[/\\]vs[/\\]editor[/\\]editor.main.js/)) { const workerPaths = (workers.map((worker) => { return `"${worker.label}": () => new ${worker.label}()`; })); const workerPathsJson = '{' + workerPaths.join(',') + '}'; const result = [ ...workers.map((worker) => { return `import ${worker.label} from '${resolveMonacoPath(worker.entry)}?worker'`; }), `self['MonacoEnvironment'] = (function (paths) { return { globalAPI: ${options?.globalAPI || false}, getWorker: function (moduleId, label) { var result = paths[label]; return result(); }, }; })(${workerPathsJson});`, ...features.flatMap((feature) => feature.entry).map((entry) => `import "${resolveMonacoPath(entry)}";`), ...languages.flatMap((lang) => lang.entry).map((entry) => `import "${resolveMonacoPath(entry)}";`), "export * from './editor.api.js';" ].join('\n'); return result; } else if (id.match(/esm[/\\]vs[/\\]editor[/\\]editor.all.js/)) { return 'throw "Please use esm/vs/editor.main.js or monaco-editor directly instead!"'; } else if (id.match(/esm[/\\]vs[/\\]editor[/\\]edcore.main.js/)) { return 'throw "Please use esm/vs/editor.main.js or monaco-editor directly instead!"'; } }, }; }