|
|
@@ -0,0 +1,313 @@
|
|
|
+/**
|
|
|
+ * Copyright (c) 2013-Now https://jeesite.com All rights reserved.
|
|
|
+ * No deletion without permission, or be held responsible to law.
|
|
|
+ * @author ThinkGem
|
|
|
+ */
|
|
|
+import { HtmlTagDescriptor, Connect, ResolvedConfig, PluginOption } from 'vite';
|
|
|
+import fs from 'fs';
|
|
|
+import path from 'path';
|
|
|
+import esbuild from 'esbuild';
|
|
|
+
|
|
|
+export function configMonacoEditorPlugin(): PluginOption {
|
|
|
+ return monacoEditorPluginJeeSite({
|
|
|
+ languageWorkers: ['editorWorkerService', 'json', 'html'],
|
|
|
+ customDistPath: (root, buildOutDir) => `${buildOutDir}/monaco`,
|
|
|
+ publicPath: 'monaco',
|
|
|
+ });
|
|
|
+}
|
|
|
+
|
|
|
+// Copyright (c) 2021 vdesjs, thinkgem
|
|
|
+//
|
|
|
+// This file contains code derived from an MIT-licensed project.
|
|
|
+// The MIT License applies **only to the content of this file** and does not
|
|
|
+// extend to the rest of the project.
|
|
|
+//
|
|
|
+// Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
|
+// of this software and associated documentation files (the "Software"), to deal
|
|
|
+// in the Software without restriction, including without limitation the rights
|
|
|
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
|
+// copies of the Software, and to permit persons to whom the Software is
|
|
|
+// furnished to do so, subject to the following conditions:
|
|
|
+//
|
|
|
+// The above copyright notice and this permission notice shall be included in all
|
|
|
+// copies or substantial portions of the Software.
|
|
|
+//
|
|
|
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
|
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
|
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
|
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
|
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
|
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
|
+// SOFTWARE.
|
|
|
+
|
|
|
+// src/languageWork.ts
|
|
|
+
|
|
|
+interface IWorkerDefinition {
|
|
|
+ label: string;
|
|
|
+ entry: string;
|
|
|
+}
|
|
|
+
|
|
|
+const languageWorkAttr: IWorkerDefinition[] = [
|
|
|
+ {
|
|
|
+ label: 'editorWorkerService',
|
|
|
+ entry: 'monaco-editor/esm/vs/editor/editor.worker.js',
|
|
|
+ },
|
|
|
+ {
|
|
|
+ label: 'css',
|
|
|
+ entry: 'monaco-editor/esm/vs/language/css/css.worker.js',
|
|
|
+ },
|
|
|
+ {
|
|
|
+ label: 'html',
|
|
|
+ entry: 'monaco-editor/esm/vs/language/html/html.worker.js',
|
|
|
+ },
|
|
|
+ {
|
|
|
+ label: 'json',
|
|
|
+ entry: 'monaco-editor/esm/vs/language/json/json.worker.js',
|
|
|
+ },
|
|
|
+ {
|
|
|
+ label: 'typescript',
|
|
|
+ entry: 'monaco-editor/esm/vs/language/typescript/ts.worker.js',
|
|
|
+ },
|
|
|
+];
|
|
|
+
|
|
|
+const languageWorksByLabel: { [language: string]: IWorkerDefinition } = {};
|
|
|
+languageWorkAttr.forEach((languageWork) => (languageWorksByLabel[languageWork.label] = languageWork));
|
|
|
+
|
|
|
+type EditorLanguageWorks = 'css' | 'html' | 'json' | 'typescript' | 'editorWorkerService';
|
|
|
+
|
|
|
+// src/workerMiddleware.ts
|
|
|
+
|
|
|
+function getFilenameByEntry(entry: string) {
|
|
|
+ entry = path.basename(entry, '.js');
|
|
|
+ return entry + '.bundle.js';
|
|
|
+}
|
|
|
+
|
|
|
+const cacheDir = 'node_modules/.monaco/';
|
|
|
+
|
|
|
+function getWorkPath(works: IWorkerDefinition[], options: IMonacoEditorOpts, config: ResolvedConfig) {
|
|
|
+ const workerPaths: Record<string, any> = {};
|
|
|
+ for (const work of works) {
|
|
|
+ if (isCDN(options.publicPath ?? '')) {
|
|
|
+ workerPaths[work.label] = options.publicPath + '/' + getFilenameByEntry(work.entry);
|
|
|
+ } else {
|
|
|
+ workerPaths[work.label] = config.base + options.publicPath + '/' + getFilenameByEntry(work.entry);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ if (workerPaths['typescript']) {
|
|
|
+ // javascript shares the same worker
|
|
|
+ workerPaths['javascript'] = workerPaths['typescript'];
|
|
|
+ }
|
|
|
+ if (workerPaths['css']) {
|
|
|
+ // scss and less share the same worker
|
|
|
+ workerPaths['less'] = workerPaths['css'];
|
|
|
+ workerPaths['scss'] = workerPaths['css'];
|
|
|
+ }
|
|
|
+ if (workerPaths['html']) {
|
|
|
+ // handlebars, razor and html share the same worker
|
|
|
+ workerPaths['handlebars'] = workerPaths['html'];
|
|
|
+ workerPaths['razor'] = workerPaths['html'];
|
|
|
+ }
|
|
|
+
|
|
|
+ return workerPaths;
|
|
|
+}
|
|
|
+
|
|
|
+function workerMiddleware(middlewares: Connect.Server, config: ResolvedConfig, options: IMonacoEditorOpts): void {
|
|
|
+ const works = getWorks(options);
|
|
|
+ if (!works) {
|
|
|
+ throw new Error('No work definition found.');
|
|
|
+ }
|
|
|
+ // clear cacheDir
|
|
|
+
|
|
|
+ if (fs.existsSync(cacheDir)) {
|
|
|
+ fs.rmSync(cacheDir, { recursive: true, force: true } as fs.RmDirOptions);
|
|
|
+ }
|
|
|
+
|
|
|
+ for (const work of works) {
|
|
|
+ middlewares.use(config.base + options.publicPath + '/' + getFilenameByEntry(work.entry), function (req, res, next) {
|
|
|
+ if (!fs.existsSync(cacheDir + getFilenameByEntry(work.entry))) {
|
|
|
+ esbuild.buildSync({
|
|
|
+ entryPoints: [resolveMonacoPath(work.entry)],
|
|
|
+ bundle: true,
|
|
|
+ outfile: cacheDir + getFilenameByEntry(work.entry),
|
|
|
+ });
|
|
|
+ }
|
|
|
+ const contentBuffer = fs.readFileSync(cacheDir + getFilenameByEntry(work.entry));
|
|
|
+ res.setHeader('Content-Type', 'text/javascript');
|
|
|
+ res.end(contentBuffer);
|
|
|
+ });
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// src/index.ts
|
|
|
+
|
|
|
+/**
|
|
|
+ * Return a resolved path for a given Monaco file.
|
|
|
+ */
|
|
|
+function resolveMonacoPath(filePath: string): string {
|
|
|
+ try {
|
|
|
+ const fullFilePath = path.resolve(path.join(process.cwd(), 'node_modules', filePath));
|
|
|
+ if (fs.existsSync(fullFilePath)) {
|
|
|
+ return fullFilePath;
|
|
|
+ }
|
|
|
+ return filePath;
|
|
|
+ } catch (err) {
|
|
|
+ throw err;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+function getWorks(options: IMonacoEditorOpts) {
|
|
|
+ let works: IWorkerDefinition[] | undefined = options.languageWorkers?.map((work) => languageWorksByLabel[work]);
|
|
|
+
|
|
|
+ works && options.customWorkers && works.push(...options.customWorkers);
|
|
|
+
|
|
|
+ return works;
|
|
|
+}
|
|
|
+
|
|
|
+interface IMonacoEditorOpts {
|
|
|
+ /**
|
|
|
+ * include only a subset of the languageWorkers supported.
|
|
|
+ */
|
|
|
+ languageWorkers?: EditorLanguageWorks[];
|
|
|
+
|
|
|
+ customWorkers?: IWorkerDefinition[];
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Override the public path from which files generated by this plugin will be served.
|
|
|
+ * This wins out over Webpack's dynamic runtime path and can be useful to avoid attempting to load workers cross-
|
|
|
+ * origin when using a CDN for other static resources.
|
|
|
+ * Use e.g. '/' if you want to load your resources from the current origin.
|
|
|
+ */
|
|
|
+ publicPath?: string;
|
|
|
+
|
|
|
+ customDistPath?: (root: string, buildOutDir: string, base: string) => string;
|
|
|
+
|
|
|
+ forceBuildCDN?: boolean;
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Specify whether the editor API should be exposed through a global `monaco` object or not. This
|
|
|
+ * option is applicable to `0.22.0` and newer version of `monaco-editor`. Since `0.22.0`, the ESM
|
|
|
+ * version of the monaco editor does no longer define a global `monaco` object unless
|
|
|
+ * `global.MonacoEnvironment = { globalAPI: true }` is set ([change
|
|
|
+ * log](https://github.com/microsoft/monaco-editor/blob/main/CHANGELOG.md#0220-29012021)).
|
|
|
+ */
|
|
|
+ globalAPI?: boolean;
|
|
|
+}
|
|
|
+
|
|
|
+function monacoEditorPluginJeeSite(options?: IMonacoEditorOpts): PluginOption {
|
|
|
+ const languageWorkers = options?.languageWorkers ?? (Object.keys(languageWorksByLabel) as EditorLanguageWorks[]);
|
|
|
+ const publicPath = options?.publicPath ?? 'monacoeditorwork';
|
|
|
+ const globalAPI = options?.globalAPI ?? false;
|
|
|
+ const customWorkers = options?.customWorkers ?? [];
|
|
|
+ const forceBuildCDN = options?.forceBuildCDN ?? false;
|
|
|
+
|
|
|
+ options = {
|
|
|
+ ...options,
|
|
|
+ languageWorkers,
|
|
|
+ publicPath,
|
|
|
+ globalAPI,
|
|
|
+ customWorkers,
|
|
|
+ forceBuildCDN,
|
|
|
+ };
|
|
|
+
|
|
|
+ let resolvedConfig: ResolvedConfig;
|
|
|
+
|
|
|
+ return {
|
|
|
+ name: 'vite-plugin-moncao-editor-jeesite',
|
|
|
+ configResolved(getResolvedConfig) {
|
|
|
+ resolvedConfig = getResolvedConfig;
|
|
|
+ },
|
|
|
+ configureServer(server) {
|
|
|
+ if (isCDN(publicPath)) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ workerMiddleware(server.middlewares, resolvedConfig, options);
|
|
|
+ },
|
|
|
+ transformIndexHtml(html) {
|
|
|
+ const works = getWorks(options);
|
|
|
+ if (!works) {
|
|
|
+ throw new Error('No work definition found.');
|
|
|
+ }
|
|
|
+ const workerPaths = getWorkPath(works, options, resolvedConfig);
|
|
|
+
|
|
|
+ const globals = {
|
|
|
+ MonacoEnvironment: `(function (paths) {
|
|
|
+ return {
|
|
|
+ globalAPI: ${globalAPI},
|
|
|
+ getWorkerUrl : function (moduleId, label) {
|
|
|
+ var result = paths[label];
|
|
|
+ if (/^((http:)|(https:)|(file:)|(\\/\\/))/.test(result)) {
|
|
|
+ var currentUrl = String(window.location);
|
|
|
+ var currentOrigin = currentUrl.substr(0, currentUrl.length - window.location.hash.length - window.location.search.length - window.location.pathname.length);
|
|
|
+ if (result.substring(0, currentOrigin.length) !== currentOrigin) {
|
|
|
+ var js = '/*' + label + '*/importScripts("' + result + '");';
|
|
|
+ var blob = new Blob([js], { type: 'application/javascript' });
|
|
|
+ return URL.createObjectURL(blob);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return result;
|
|
|
+ }
|
|
|
+ };
|
|
|
+ })(${JSON.stringify(workerPaths, null, 2)})`,
|
|
|
+ } as Record<string, any>;
|
|
|
+
|
|
|
+ const descriptor: HtmlTagDescriptor[] = [
|
|
|
+ {
|
|
|
+ tag: 'script',
|
|
|
+ children: Object.keys(globals)
|
|
|
+ .map((key) => `self[${JSON.stringify(key)}] = ${globals[key]};`)
|
|
|
+ .join('\n'),
|
|
|
+ injectTo: 'head-prepend',
|
|
|
+ },
|
|
|
+ ];
|
|
|
+ return descriptor;
|
|
|
+ },
|
|
|
+
|
|
|
+ writeBundle() {
|
|
|
+ // 是cdn地址并且没有强制构建worker cdn则返回
|
|
|
+ if (isCDN(publicPath) && !forceBuildCDN) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ const works = getWorks(options);
|
|
|
+
|
|
|
+ const distPath = options.customDistPath
|
|
|
+ ? options.customDistPath(resolvedConfig.root, resolvedConfig.build.outDir, resolvedConfig.base)
|
|
|
+ : path.join(resolvedConfig.root, resolvedConfig.build.outDir, resolvedConfig.base, options.publicPath ?? '');
|
|
|
+
|
|
|
+ // console.log("distPath", distPath)
|
|
|
+
|
|
|
+ // write publicPath
|
|
|
+ if (!fs.existsSync(distPath)) {
|
|
|
+ fs.mkdirSync(distPath, {
|
|
|
+ recursive: true,
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
+ if (!works) {
|
|
|
+ throw new Error('No work definition found.');
|
|
|
+ }
|
|
|
+ for (const work of works) {
|
|
|
+ if (!fs.existsSync(cacheDir + getFilenameByEntry(work.entry))) {
|
|
|
+ const entryPath = resolveMonacoPath(work.entry);
|
|
|
+ esbuild.buildSync({
|
|
|
+ entryPoints: [entryPath],
|
|
|
+ bundle: true,
|
|
|
+ outfile: cacheDir + getFilenameByEntry(work.entry),
|
|
|
+ });
|
|
|
+ }
|
|
|
+ const contentBuffer = fs.readFileSync(cacheDir + getFilenameByEntry(work.entry));
|
|
|
+ const workDistPath = path.resolve(distPath, getFilenameByEntry(work.entry));
|
|
|
+ fs.writeFileSync(workDistPath, contentBuffer);
|
|
|
+ }
|
|
|
+ },
|
|
|
+ };
|
|
|
+}
|
|
|
+
|
|
|
+function isCDN(publicPath: string) {
|
|
|
+ if (/^((http:)|(https:)|(file:)|(\/\/))/.test(publicPath)) {
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+ return false;
|
|
|
+}
|