Monaco Editor系列(二)Hello World 初体验

前言:上一篇文章我主要分享了从 Monaco Editor 入口文件以及官方提供的示例项目入手,对一部分源码进行剖析,以及分享了初始化阶段代码的大致执行步骤,这一篇了来讲一下我们要用 Monaco Editor 的时候该怎么用。其中会涉及到一些 API,我会对 API 的源码进行深入的解析,但不保证能完全看懂 😂。这种复杂的源码,不要着急,实在深入不下去,就换一个入口,继续探究,最后你学到的东西会呈网状交织在一起,覆盖到所有的代码。

一、创建新的路由页面

咱们还是基于之前的项目来做,在下面这个位置,放置咱们自己的页面
在这里插入图片描述
主要就是仿照前几个路由的配置。我也没有学过react,但是没关系,学习一样东西最快的方式就是模仿!

(一)创建路由

website/src/website/pages/routes.ts
在路由文件的最后一行,仿照上面创建路由的形式,新增一个路由

export const study = new Route("./study.html");

什么?找不到 ./monarch.html等页面文件? 其实这个路径是通过 webpack 的插件机制生成滴,上一篇文章也有详细的解释哦

(二)webpack 配置

website/webpack.config.ts 文件的 plugins 配置中增加一项配置,意思就是应用 index chunk 生成一个 study.html 页面,模版就使用 getHtml() 作为页面模版。仔细观察可以发现,其他的路由页面也都是这么配置的。

new HtmlWebpackPlugin({
    chunks: ["index"],
    filename: "study.html",
    templateContent: getHtml(),
}),

(三)路由注册和配置

website/src/website/pages/App.tsx
在应用文件中需要引入路由并且注册路由,以及定义路由和页面文件之间的对应关系。就仿照其他的路由页面写就行了

//...
// 引入 路由
import { docs, home, monarch, playground, study } from "./routes";
//...
// 引入 Study
import { StudyPage } from "./StudyPage";

export class App extends React.Component {
	// 根据路由返回指定的组件
	render() {
		if (home.isActive) {
			return <Home />;
		} else if (playground.isActive) {
			return <PlaygroundPage />;
		} else if (docs.isActive) {
			return <DocsPage />;
		} else if (monarch.isActive) {
			return <MonarchPage />;
		} else if (study.isActive) {
			return <StudyPage />
		}
		return <>Page does not exist</>;
	}
}

(四)创建路由页面

上面我们定义了study路由对应的是 ./StudyPage 页面,我们需要创建一个新的文件,里面写的简单一点,直接渲染一个 div 先,创建文件的目录也仿照其他页面就行
website/src/website/pages/StudyPage.tsx

import React = require("react");
export class StudyPage extends React.Component<{}, {}> {
    render() {
        return (
            <div>我是Study</div>
        );
    }
}

(五)路由链接

路由链接定义的位置可以全局搜索这个类名找哦,一下子就找到了
在这里插入图片描述
website/src/website/components/Nav.tsx

<Navbar.Collapse id="basic-navbar-nav" role="">
	<!--省略一万字-->
	<Nav.Link active={study.isActive} href={study.href}>
		Study
	</Nav.Link>
</Nav>

接下我们去页面看看效果吧!
在这里插入图片描述
点击路由就跳往 Study 页面啦
在这里插入图片描述
咦,这里为什么和别人不一样呢?是因为人家有用 Page自定义组件啦!

(六)使用 Page 自定义组件

import React = require("react");
import { Page } from "../components/Page";
export class StudyPage extends React.Component<{}, {}> {
    render() {
        return (
            <Page>
                <div>我是Study</div>
            </Page>
        );
    }
}

在这里插入图片描述
这样就哦了,保留了公共的页头

  • react 生命周期

先拐个弯儿,加深一下基础

执行阶段函数名称执行时机
创建阶段constructor初始化state中的数据, 可以为事件绑定this
创建阶段render每次组件渲染(初次渲染组件和更新组件)都会被触发,作用是渲染UI; 注意不能够调用 setState,因为setState会更新数据,这样会导致递归渲染数据
创建阶段componentDidMountDOM已经渲染完成了;可以进行DOM操作和网络请求
更新阶段render有三种情况会导致组件的更新-触发render函数:① 组件接收到一个新的属性;② 调用setState();③ 调用forceUpdate()方法
更新阶段componentDidUpdate当组件中的数据更新完成后会触发
卸载阶段componentWillUnmount组件将要卸载的时候会被触发,可以做清除定时器。

二、创建 Monaco Editor

咱们先看一下 Monaco 这个路由,这个路由下面其实就是有两个 Monaco 编辑器实例,那么我们就先看一下这个页面是怎么创建 Monaco Editor 实例的
在这里插入图片描述
我们一起去往项目代码中,这个路由对应的组件,website/src/website/pages/MonarchPage.tsx
可以看到 Monaco Editor 其实是在 iframe 里面

<Page>
	<iframe
		frameBorder={0}
		className="full-iframe"
		src="./monarch-static.html"
	/>
</Page>

我们使用搜索路径的方式,搜索 monaco-static,idea中的快捷键是 【shift+shift】,就可以找到这个文件的定义位置
website/static/monarch-static.html
这里我们可以发现,这个文件的路径也是经过处理的,并不是真的在 ./ 目录下,其实这也是 webpack 处理的,

new CopyPlugin({
    patterns: [{from: "./static", to: "./"}],
}),

CopyPlugin 插件用于将文件或目录从源位置复制到构建目录中,这样就可以通过 ./ 获取文件了
那么我们仿照这种创建文件的方式,也新建一个 StudyPage 使用的 iframe
其实 monarch-static.html 中就包含了我们需要的内容,咱们只需要其中的创建新建 Monaco Editor 的部分,写一个最简单的示例,为了更好的模块化,把 js 仿照 monarch 放到另外的目录里面
website/static/study-static.html

<!DOCTYPE html>
<html>

<head>
	<title>Hello World Monaco Editor</title>
	<meta http-equiv="Content-Type" content="text/html;charset=utf-8" />
</head>

<body>
<h2>Hello World Monaco Editor</h2>
<div id="container" style="width: 800px; height: 600px; border: 1px solid grey">
</div>
<script
		src="https://cdnjs.cloudflare.com/ajax/libs/jquery/1.9.1/jquery.min.js"
		integrity="sha256-wS9gmOZBqsqWxgIVgA8Y9WcQOa7PgSIX+rPA0VL2rbQ="
		crossorigin="anonymous"
></script>
<script
		src="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/2.3.0/bootstrap.min.js"
		integrity="sha256-u+l2mGjpmGK/mFgUncmMcFKdMijvV+J3odlDJZSNUu8="
		crossorigin="anonymous"
></script>

<script>
	var require = {
		paths: { vs: "./node_modules/monaco-editor/dev/vs" },
	};
</script>
<script src="./node_modules/monaco-editor/dev/vs/loader.js"></script>
<script src="./node_modules/monaco-editor/dev/vs/editor/editor.main.nls.js"></script>
<script src="./node_modules/monaco-editor/dev/vs/editor/editor.main.js"></script>

<script data-inline="yes-please" src="./study/study.js"></script>
</body>
</html>

website/static/study/study.js

require(['vs/editor/editor.main'], function () {
    var editor = monaco.editor.create(document.getElementById('container'), {
        value: ['function x() {', '\tconsole.log("Hello world!");', '}'].join('\n'),
        language: 'javascript'
    });
});

最后别忘了把 StudyPage.tsx 中iframe的src改为 ./study-static.html
历经千辛万苦,我们终于创建出来了一个自己的 Monaco Editor 实例,亲爱的们,先去跳个舞奖励一下自己吧💃🏻💃🏻💃🏻💃🏻
在这里插入图片描述

三、monaco.editor.create

创建 Monaco Editor 的方法就是 monaco.editor.create(),咱们可以打印一下 monaco.editor,总之里面各种方法属性,截图都放不下,其中这几个就是跟创建编辑器相关的
在这里插入图片描述
通过打断点的方式,我们可以看到 create() 方法执行的地方
在这里插入图片描述
根据我红圈圈 圈出的地方,进行全局搜索。如果你的源码项目使用了 git 管理,你可能会和我一样,找不到!费劲九牛二虎之力终于找到了这个类的定义,原来是在 git 忽视的文件夹中,所以搜不到
在这里插入图片描述
就在这个文件里面:monaco-editor/out/monaco-editor/esm/vs/platform/instantiation/common/instantiationService.js
记得在 .gitignore 文件中把 out 文件夹注释掉哦
然后搜索 const instantiationService = StandaloneServices.initialize(override || {}); 这句代码
out/monaco-editor/esm/vs/editor/standalone/browser/standaloneEditor.js 这个文件里面
这个 out 文件夹是咋生成的?这就说来话长了,其实是我们执行最开始最开始的 npm run build-monaco-editor 来生成本地的项目的时候生成的,这个过程我会在第四章讲到哦。那么咱们现在主要来分析代码,看看 create() 方法究竟是如何创建 Monaco Editor 的!
out/monaco-editor/esm/vs/editor/standalone/browser/standaloneEditor.js

export function create(domElement, options, override) {
    // 初始化StandaloneServices
    const instantiationService = StandaloneServices.initialize(override || {});
    // StandaloneEditor:类
    // 内部就是根据 StandaloneEditor 创建一个实例
    return instantiationService.createInstance(StandaloneEditor, domElement, options);
}

instantiationService.createInstance() 方法里面其实挺复杂的,但是在这里就不发散太多了,发散太多就忘记最初的目的了。作用其实就是创建 StandaloneEditor 类的实例
然后我们来看一下 StandaloneEditor 类的定义吧,这个类里面的代码其实并不多,因为它是继承别的类的,并且有好几层继承🤣,我浅浅的分析了一下 constructor 方法的执行,但是说实话,实在是太复杂了,实例化过程其实还是通过 super() 交给父级实现的。但是到这儿我觉得没有继续深入看的必要了,因为成本有点高,很难理解,并且对编辑器的功能不了解,看源码也理解不了它到底在干啥🤣。所以咱们先知道方法在哪里定义的,然后呢,继续探索其他功能吧!
out/monaco-editor/esm/vs/editor/standalone/browser/standaloneCodeEditor.js

constructor(domElement, _options, instantiationService, codeEditorService, commandService, contextKeyService, keybindingService, themeService, notificationService, configurationService, accessibilityService, modelService, languageService, languageConfigurationService, languageFeaturesService) {
        // 拷贝 options
        const options = { ..._options };
        // 更新配置服务的配置
        updateConfigurationService(configurationService, options, false);
        // 将 domElement 注册为编辑器容器
        const themeDomRegistration = themeService.registerEditorContainer(domElement);
        if (typeof options.theme === 'string') {
            // 设置主题
            themeService.setTheme(options.theme);
        }
        if (typeof options.autoDetectHighContrast !== 'undefined') {
            // 是否自动检测高对比度主题
            themeService.setAutoDetectHighContrast(Boolean(options.autoDetectHighContrast));
        }
        const _model = options.model;
        delete options.model;
        // 使用 super 来调用父类的构造函数,将实例化过程委托给父类构造函数完成
        super(domElement, options, instantiationService, codeEditorService, commandService, contextKeyService, keybindingService, themeService, notificationService, accessibilityService, languageConfigurationService, languageFeaturesService);
        this._configurationService = configurationService;
        this._standaloneThemeService = themeService;
        this._register(themeDomRegistration);
        let model;
        if (typeof _model === 'undefined') {
            // 获取语言标识符,如果没有语言标识符就标记为纯文本
            const languageId = languageService.getLanguageIdByMimeType(options.language) || options.language || PLAINTEXT_LANGUAGE_ID;
            // 创建文本模型
            model = createTextModel(modelService, languageService, options.value || '', languageId, undefined);
            // 表明编辑器实例已经拥有这个模型
            this._ownsModel = true;
        }
        else {
            // 使用给定的模型
            model = _model;
            this._ownsModel = false;
        }
        // 将model附加到编辑器上
        this._attachModel(model);
        if (model) {
            const e = {
                oldModelUrl: null,
                newModelUrl: model.uri
            };
            this._onDidChangeModel.fire(e);
        }
    }

四、项目构建过程

我们下载完 Monaco Editor 项目之后,第一步是运行 npm i 安装依赖,第二步是运行 npm run build-monaco-editor 生成本地的项目,这个命令的定义在Monaco的根目录的 package.json

"build": "ts-node ./build/build-languages",
"build-monaco-editor": "npm run build && ts-node ./build/build-monaco-editor"

可以看到,这一句 npm run build-monaco-editor 命令其实背后执行了两个步骤,一个是 npm run build,另一个是 ts-node ./build/build-monaco-editor,而 npm run build 实际执行的就是上一句指定的 ts-node ./build/build-languages
ts-node 命令就是用来运行后面紧跟着的 ts 文件的。那么我们分别来看一下这两个 ts 文件里面都干了什么吧!

1、./build/build-languages

这个文件里面的代码,总的来说就是重新构建 out/languages 目录

① 删除 out/languages 目录
import { copyFile, removeDir } from './fs';
removeDir(`out/languages`);

这里的 fs 并不是Node.js提供的 fs 模块,而是二次封装的 fs 模块。这个方法的定义我们可以点进去瞅瞅。其中我已经注释好了,就是递归删除所有的子文件,然后删除文件夹
build/fs.ts

export function removeDir(_dirPath: string, keep?: (filename: string) => boolean) {
	if (typeof keep === 'undefined') {
		keep = () => false;
	}
	const dirPath = path.join(REPO_ROOT, _dirPath);
	// fs.existsSync:检查路径是否存在
	if (!fs.existsSync(dirPath)) {
		return;
	}
	rmDir(dirPath, _dirPath);
	console.log(`Deleted ${_dirPath}`);

	function rmDir(dirPath: string, relativeDirPath: string): boolean {
		let keepsFiles = false;
		// readdirSync 是 Node.js 中 fs 模块的一个方法,用于同步地读取指定目录下的文件和子目录。
		const entries = fs.readdirSync(dirPath);
		for (const entry of entries) {
			const filePath = path.join(dirPath, entry);
			const relativeFilePath = path.join(relativeDirPath, entry);
			// !是非空断言,此处keep不可能为空
			if (keep!(relativeFilePath)) {
				// 如果调用方法的时候传进来了keep函数,那么就可以
				// 通过keep函数设置哪些文件保留
				// 如果不传递keep函数,则删除所有文件
				keepsFiles = true;
				continue;
			}

			// fs.statSync 获取指定路径的文件状态信息
			// isFile() 就是判断目标是不是文件
			if (fs.statSync(filePath).isFile()) {
				// 删除指定文件
				fs.unlinkSync(filePath);
			} else {
				// 递归删除子文件
				keepsFiles = rmDir(filePath, relativeFilePath) || keepsFiles;
			}
		}
		if (!keepsFiles) {
			// 如果子文件都被删除了,就删除文件夹
			fs.rmdirSync(dirPath);
		}
		return keepsFiles;
	}
}
② 生成 out/languages/amd-tsc 文件夹

这个文件夹中的代码,存放的是我们在编写不同编程语言的时候的一些关键词和格式化规则等
就是这条代码的工作啦 runTsc(src/tsconfig.json); !我们来看一下具体的执行
build/utils.ts

export function runTsc(_projectPath: string) {
	const projectPath = path.join(REPO_ROOT, _projectPath);
	console.log(`Launching compiler at ${_projectPath}...`);
	// 1、process.execPath 是 Node.js 中的一个属性,它返回启动当前 Node.js 进程的可执行文件的绝对路径。
	// 类似于:/usr/local/bin/node
	// 第二个参数是传递给 /usr/local/bin/node 命令的参数
	// 也就是说,使用 node XX 命令运行 XX 文件
	// 2、'../node_modules/typescript/lib/tsc.js' 是 TypeScript 的官方命令行工具 tsc 的入口点,
	// 用于将 TypeScript 代码编译为 JavaScript 代码。
	// `-p 路径` 是给tsc的参数,指定 TypeScript 项目的配置文件路径
	// 3、stdio 配置选项设置为 'inherit',子进程继承了父进程的标准输入输出。
	// 总的来说,就是执行命令: `node tsc -p ../src/tsconfig.json`
	const res = cp.spawnSync(
		process.execPath,
		[path.join(__dirname, '../node_modules/typescript/lib/tsc.js'), '-p', projectPath],
		{ stdio: 'inherit' }
	);
	console.log(`Compiled ${_projectPath}`);
	if (res.status !== 0) {
		process.exit(res.status);
	}
}

主要是将一个配置文件传递给了 tsc 工具,执行 tsc 命令。我们具体来看一下配置文件的内容吧!

{
  "compilerOptions": {
    // 表示生成对应的 .d.ts 类型声明文件
    "declaration": true,
    // lib 选项指定要包含的类型声明文件列表,以便正确地进行类型检查和类型推断。
    // dom:包含了 DOM 相关的类型信息,用于在 TypeScript 代码中进行浏览器 DOM 操作的类型检查。
    // es5:包含了 ES5 标准库的类型信息,用于在 TypeScript 代码中使用 ES5 标准库的类型检查。
    // es2015.collection:包含了 ES2015 集合类型的类型信息,如 Map、Set 等。
    // es2015.promise:包含了 ES2015 Promise 类型的类型信息,用于在 TypeScript 代码中进行 Promise 相关操作的类型检查。
    // es2015.iterable:包含了 ES2015 迭代器类型的类型信息,用于在 TypeScript 代码中进行迭代操作的类型检查。
    "lib": [
      "dom",
      "es5",
      "es2015.collection",
      "es2015.promise",
      "es2015.iterable"
    ],
    // 指定要使用的模块系统
    "module": "amd",
    // 指定模块解析策略
    "moduleResolution": "node",
    // 指定编译输出的目录
    "outDir": "../out/languages/amd-tsc",
    // 启用严格的类型检查和更严格的编译选项
    "strict": true,
    // 指定编译后的 JavaScript 代码的目标 ECMAScript 版本
    "target": "es5",
    // 表示生成源映射文件(.map)
    "sourceMap": true,
    // 表示允许编译器编译 JavaScript 文件(.js)
    "allowJs": true,
    // 表示禁用对 JavaScript 文件的类型检查
    "checkJs": false
  }

tsc编译会自动对配置文件同级的文件夹进行编译。由上述配置项可以得知,使用 tsc 编译之后就会生成 out/languages/amd-tsc 目录。其实就是 src 目录下的这几个文件夹,编译后放到了 out/amd-tsc 文件夹中
在这里插入图片描述
只不过编译后的文件是有好几个版本的
在这里插入图片描述

③ 生成 out/languages/bundled 文件夹’

这个文件夹里面的代码是用来定义 html、css、js、json、typescript 这几个语言的格式化以及提示规则啊等等
主要靠 buildESM() 这个方法。这个方法执行了好几次,咱们就看一下第一个执行的过程

buildESM({
	base: 'language/typescript',
	entryPoints: [
		'src/language/typescript/monaco.contribution.ts',
		'src/language/typescript/tsMode.ts',
		'src/language/typescript/ts.worker.ts'
	],
	external: ['monaco-editor-core', '*/tsMode', '*/monaco.contribution']
});

方法的定义在 build/utils.ts 文件中

export function build(options: import('esbuild').BuildOptions) {
	// esbuild.build 是 esbuild 构建工具提供的一个方法,用于构建 JavaScript 或 TypeScript
	// 项目。
	// esbuild 是一个快速的、低配置的 JavaScript/TypeScript 构建工具,
	// 旨在提供高性能的构建和打包功能。它能够将源代码转换为浏览器可执行的 JavaScript,
	// 同时支持代码压缩和优化等功能。
	// esbuild.build 方法用于配置和执行构建过程。它接受一个配置对象作为参数,
	// 该对象描述了构建的输入和输出等信息。
	esbuild.build(options).then((result) => {
		if (result.errors.length > 0) {
			console.error(result.errors);
		}
		if (result.warnings.length > 0) {
			console.error(result.warnings);
		}
	});
}

export function buildESM(options: { base: string; entryPoints: string[]; external: string[] }) {
	build({
		entryPoints: options.entryPoints, // 构建的入口文件路径
		bundle: true,  // 是否将所有模块打包到一个输出文件中
		target: 'esnext',  // 构建的目标 JavaScript 版本,表示目标是 ESNext 版本
		format: 'esm',  // 输出文件的模块格式,表示输出文件采用 ES 模块的格式
		drop: ['debugger'], // 指定需要从输出文件中删除的代码或语句,表示删除所有 debugger 语句
		define: {  // 定义全局常量
			AMD: 'false'
		},
		banner: {  // 用于在输出文件的开头插入注释、版权声明或其他自定义信息,以标识生成的文件或提供额外的说明
			js: bundledFileHeader  // 用于 JavaScript 文件的自定义内容
		},
		external: options.external,  // 需要排除的外部依赖模块,不会被打包进输出文件中
		outbase: `src/${options.base}`,  // 指定输出文件相对于源文件的基础路径
		outdir: `out/languages/bundled/esm/vs/${options.base}/`,  // 指定输出文件的目录路径
		plugins: [  // 配置插件,用于在构建过程中进行额外的处理
			alias({
				'vscode-nls': path.join(__dirname, 'fillers/vscode-nls.ts')
			})
		]
	});
}

这里执行了几次构建,结果就是下面这个文件夹的内容
在这里插入图片描述
下面的几个 .d.ts 文件是类型定义,上面的几个文件夹分别是开发环境下amd模式的未压缩的 js 代码、压缩版本的amd模式代码、esm模式代码

2、./build/build-monaco-editor

这个文件就稍微有一丢丢长了。
令人感动的是,每一步的注释都非常的清楚,所以说真正优秀的项目,不仅功能优秀,注释也要到位啊! 咱们先不看具体方法,先看一下大致都做了什么操作

// 删除文件夹 out/monaco-editor
removeDir(`out/monaco-editor`);

// dev folder
// AMD开发环境打包
AMD_releaseOne('dev');

// min folder
// AMD压缩版本打包
AMD_releaseOne('min');

// esm folder
// esm 模式打包
ESM_release();

// monaco.d.ts, editor.api.d.ts
// 生成文件 monaco.d.ts, editor.api.d.ts
releaseDTS();

// 生成 ThirdPartyNotices.txt
releaseThirdPartyNotices();

// 生成 esm/metadata.d.ts, esm/metadata.js
generateMetadata();

// package.json
// 生成 out/monaco-editor/package.json
(() => {
	const packageJSON = readFiles('package.json', { base: '' })[0];
	const json = JSON.parse(packageJSON.contents.toString());

	json.private = false;
	delete json.scripts['postinstall'];

	packageJSON.contents = Buffer.from(JSON.stringify(json, null, '  '));
	writeFiles([packageJSON], `out/monaco-editor`);
})();

// 生成README.md、CHANGELOG.md、LICENSE文件
(() => {
	/** @type {IFile[]} */
	let otherFiles = [];

	otherFiles = otherFiles.concat(readFiles('README.md', { base: '' }));
	otherFiles = otherFiles.concat(readFiles('CHANGELOG.md', { base: '' }));
	otherFiles = otherFiles.concat(
		readFiles('node_modules/monaco-editor-core/min-maps/**/*', {
			base: 'node_modules/monaco-editor-core/'
		})
	);
	otherFiles = otherFiles.concat(
		readFiles('node_modules/monaco-editor-core/LICENSE', {
			base: 'node_modules/monaco-editor-core/'
		})
	);

	writeFiles(otherFiles, `out/monaco-editor`);
})();

第一步是删除 out/monaco 目录,咱们在上一小节已经看过了,那么一起看一下往下的代码是怎么实现的吧!

① AMD模式代码构建

AMD_releaseOne('dev');AMD_releaseOne('dev'); 两句代码,分别执行的是AMD模式下开发环境代码构建和压缩版本代码构建

function AMD_releaseOne(type: 'dev' | 'min') {
	// 读取库文件
	const coreFiles = readFiles(`node_modules/monaco-editor-core/${type}/**/*`, {
		base: `node_modules/monaco-editor-core/${type}`
	});
	// 1、读取库文件的内容,整合组件模块,拼接组装成 editor.main.js
	// 2、追加 monaco.contribution 模块
	AMD_addPluginContribs(type, coreFiles);
	// 写到 min或者dev 文件夹中
	writeFiles(coreFiles, `out/monaco-editor/${type}`);

	// 读取配置文件
	const pluginFiles = readFiles(`out/languages/bundled/amd-${type}/**/*`, {
		base: `out/languages/bundled/amd-${type}`,
		ignore: ['**/monaco.contribution.js']
	});
	// 将配置文件写到 min或者dev 文件夹中
	writeFiles(pluginFiles, `out/monaco-editor/${type}`);
}

② ESM模式代码构建
function ESM_release() {
	// 读取库文件中的 esm 模式源码
	const coreFiles = readFiles(`node_modules/monaco-editor-core/esm/**/*`, {
		base: 'node_modules/monaco-editor-core/esm',
		// we will create our own editor.api.d.ts which also contains the plugins API
		ignore: ['node_modules/monaco-editor-core/esm/vs/editor/editor.api.d.ts']
	});
	// 给所有的使用 import 导入模块的地方添加 `.js` 后缀
	ESM_addImportSuffix(coreFiles);
	// 整合组件模块,构建esm/vs/editor/editor.main.js
	ESM_addPluginContribs(coreFiles);
	// 将库文件写到目录中
	writeFiles(coreFiles, `out/monaco-editor/esm`);

	// 添加依赖文件 vs/editor/editor.api
	ESM_releasePlugins();
}

下面的几个方法的实现,其实都是差不多的内容,需要读取上一小节中生成的 out/languages 文件夹中的内容,还有库文件源码内容,然后可能需要对文件进行重命名,或者拼接上注释,或者读取文件内容,进行整合生成新的文件。

其中反复使用的有一个方法就是 build/utils.ts 中的 writeFiles() 方法

export function writeFiles(files: IFile[], dest: string) {
	for (const file of files) {
		// 获取完整路径
		const fullPath = path.join(REPO_ROOT, dest, file.path);
		// path.dirname 用于获取指定文件路径的目录部分(即去除文件名后的路径)。
		// 用来创建目录,确保逐层的目录都村子
		ensureDir(path.dirname(fullPath));
		fs.writeFileSync(fullPath, file.contents);
	}
}

ensureDir() 方法用来是用来创建目录的,使用一个 Set 保存已经存在的目录,避免重复创建。
如果当前文件夹比根目录更长,就将目录放到 dirs 数组中,然后去除当前层级路径,获取父级,并循环判断。这样获取的 dirs 数组,是从子级到父级的目录,将数组反转过来,就可以保证目录层级是从上到下的。
然后再使用 fs 库提供的 fs.mkdirSync() 方法创建目录。
build/fs.ts

const REPO_ROOT = path.join(__dirname, '../');
// 存在的文件夹缓存
const existingDirCache = new Set();

export function ensureDir(dirname: string) {
	/** @type {string[]} */
	const dirs = [];

	// 根目录不需要创建
	while (dirname.length > REPO_ROOT.length) {
		dirs.push(dirname);
		// 去除当前层级路径,即获取父级
		dirname = path.dirname(dirname);
	}
	// 反转数组,保证文件夹顺序从上到下,即先创建父级文件夹
	dirs.reverse();
	dirs.forEach((dir) => {
		if (!existingDirCache.has(dir)) {
			try {
				// 创建目录
				fs.mkdirSync(dir);
			} catch (err) {}
			existingDirCache.add(dir);
		}
	});
}

也就是说,npm run build-monaco-editor 做的事情,就是创建 out 目录!
所以说,第三章提到的 Monaco 实例的创建 create() 方法,其实是从库文件源码node_modules/monaco-editor-core中获取的

参考文章
1、React中生命周期的讲解

  • 27
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Monaco Editor简介 Monaco Editor是由微软推出的一款跨平台的代码编辑器,它最初用于VS Code,是其核心组成部分之一,目前也已经可以用于web上构建的编辑器应用中。 Monaco Editor的特点 1. 支持多种语言:Monaco Editor支持约70种编程语言,其中包括JavaScript、Python、C++、Java等常用语言,开发者只需要根据实际需要进行语言设置即可。 2. 提供高级功能:代码高亮、自动补全、错误提示、智能代码重构等是Monaco Editor最为重要的高级功能,其可以让开发者更加方便地进行代码编辑与维护。 3. 极佳的性能表现:Monaco Editor的性能表现非常出色,它的代码编辑速度十分快速,界面的响应速度也非常快。 4. 可定制化:Monaco Editor提供了一种强大的自定义选项,可以让开发者对编辑器进行更深度的个性定制。 Monaco Editor的使用方法 1. 首先,需要带上monaco-editor库。常规的方式可以带上CDN链接同时可以下载到本地使用。 ```html <script src="https://unpkg.com/monaco-editor@0.31.1/min/vs/loader.js"></script> <!-- 这里可以自行下载到本地 --> ``` 2. 创建编辑容器,用于嵌入monaco-editor。 ```html <div id="editor-container"></div> ``` 3. 定义编辑器的配置项。 ```javascript // 配置项 var editorOptions = { value: '', language: 'javascript',//支持的语言 lineNumbers: true,//是否在编辑器中展示行号 roundedSelection: false, scrollBeyondLastLine: false, readOnly: false, theme: 'vs-dark',//主题 //其他参数 ... }; ``` 4. 初始化编辑器,即创建Monaco编辑器实例。 ```javascript // 初始化编辑器 var editor = monaco.editor.create(document.getElementById('editor-container'), editorOptions); ``` 5. 设置编辑器内容。 ```javascript // 设置编辑器内容 editor.getModel().setValue('Hello World!'); ``` 6. 获取编辑器内容。 ```javascript // 获取编辑器内容 editor.getModel().getValue(); ``` Monaco Editor教程总结 Monaco Editor是一款跨平台的代码编辑器,具有多种语言支持、高级功能、极佳的性能表现、可定制化等特点,可以广泛应用于web端代码编辑器的开发中。在使用Monaco Editor编写网页时,需要通过引入monaco-editor库、定义编辑容器、编辑器的配置项、初始化编辑器、设置编辑器内容和获取编辑器内容等步骤来创建并使用Monaco Editor

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值