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><iframeframeBorder={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>
<scriptsrc="https://cdnjs.cloudflare.com/ajax/libs/jquery/1.9.1/jquery.min.js"integrity="sha256-wS9gmOZBqsqWxgIVgA8Y9WcQOa7PgSIX+rPA0VL2rbQ="crossorigin="anonymous"
></script>
<scriptsrc="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) {// 初始化StandaloneServicesconst 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) {// 拷贝 optionsconst 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 APIignore: ['node_modules/monaco-editor-core/esm/vs/editor/editor.api.d.ts']});// 给所有的使用 import 导入模块的地方添加 `.js` 后缀ESM_addImportSuffix(coreFiles);// 整合组件模块,构建esm/vs/editor/editor.main.jsESM_addPluginContribs(coreFiles);// 将库文件写到目录中writeFiles(coreFiles, `out/monaco-editor/esm`);// 添加依赖文件 vs/editor/editor.apiESM_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中生命周期的讲解

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/588818.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

LeetCode 热题 100 | 动态规划(一)

目录 1 70. 爬楼梯 1.1 基本思路 1.2 官方题解 2 118. 杨辉三角 3 198. 打家劫舍 菜鸟做题&#xff0c;语言是 C 1 70. 爬楼梯 核心思想&#xff1a;把总问题拆解为若干子问题。 总问题&#xff1a;上到 5 楼的方式有多少种子问题&#xff1a;上到 4 楼的方式有多…

炫我科技:云渲染领域的佼佼者

随着数字化时代的来临&#xff0c;云渲染技术正逐渐成为影视、游戏、动画等创意产业的重要支柱。在这一领域中&#xff0c;炫我科技凭借其卓越的技术实力、优质的服务以及不断创新的精神&#xff0c;已然成为了云渲染行业的佼佼者。 炫我科技自成立之初&#xff0c;便以打造高…

SV学习笔记(二)

接口 什么是接口&#xff1f; 接口 主要用作验证 &#xff0c;国外有些团队会使用sv进行设计&#xff0c;那么接口就会用作设计。验证环境中&#xff0c;接口可以 使连接变得简洁而不易出错 。interface和module的使用性质很像&#xff0c; 可以定义端口&#xff0c;也可以定…

基于单片机的炉温控制系统设计

**单片机设计介绍&#xff0c;基于单片机的炉温控制系统设计 文章目录 一 概要二、功能设计设计思路 三、 软件设计原理图 五、 程序六、 文章目录 一 概要 基于单片机的炉温控制系统设计是一个融合了单片机技术、温度传感技术、控制算法以及硬件电路设计等多个领域的综合性项…

在ChatGPT中,能用DALL·E 3编辑图片啦!

4月3日&#xff0c;OpenAI开始向部分用户&#xff0c;提供在ChatGPT中的DALLE 3图片编辑功能。 DALLE 3是OpenAI在2023年9月20日发布的一款文生图模型&#xff0c;其生成的图片效果可以与Midjourney、leonardo、ideogram等顶级产品媲美&#xff0c;随后被融合到ChatGPT中增强其…

【BlossomConfig】SpringCloud项目是如何对bootstrap配置文件进行加载的?

文章目录 bootstrap配置文件的读取什么是配置中心&#xff1f;以及如何实现一个配置中心&#xff1f;SpringBoot如何实现配置的管控&#xff1f;SpringCloud项目是如何对bootstrap配置文件进行加载的&#xff1f;Nacos是如何实现配置文件的读取加载的&#xff1f;开发配置中心前…

一次普通的漏洞挖掘思路分享

No.0 前言 一名web安全小白&#xff0c;自己仅学了一点思路&#xff0c;直接实战&#xff0c;运气不错&#xff0c;碰到了管理员弱口令&#xff0c;进入后台后&#xff0c;继续测试自己学会的思路挖掘深一点的漏洞&#xff0c;这里与各位分享一下&#xff0c;如果有更多的思路…

MMU关闭时Cache的缓存策略是怎样的

快速链接: 【精选】ARMv8/ARMv9架构入门到精通-[目录] &#x1f448;&#x1f448;&#x1f448; 在学习MMU章节时&#xff0c;我们发现在页表的entry中&#xff0c;BIT[4:2]指向了MAIR寄存器&#xff0c;该寄存器描述了内存属性&#xff0c;其实就是cache的缓存策略属性&#…

洛谷-P1706 全排列问题(DFS)

目录 题目链接&#xff1a; 思路&#xff1a; 代码&#xff1a; 题目链接&#xff1a; P1706 全排列问题 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn) 思路&#xff1a; 如果n比较小&#xff0c;可以写n个for循环输出全排列。但是这种简单方法只能用于较小的n&#xff0…

Vue依赖注入,详细解析

Prop 逐级透传问题​ 通常情况下&#xff0c;当我们需要从父组件向子组件传递数据时&#xff0c;会使用 props。想象一下这样的结构&#xff1a;有一些多层级嵌套的组件&#xff0c;形成了一颗巨大的组件树&#xff0c;而某个深层的子组件需要一个较远的祖先组件中的部分数据。…

C++心决之内联函数+auto关键字+指针空值

目录 7.内联函数 7.1 概念 7.2 特性 8. auto关键字(C11) 8.1 类型别名思考 8.2 auto简介 8.3 auto的使用细则 8.4 auto不能推导的场景 9. 基于范围的for循环(C11) 9.1 范围for的语法 9.2 范围for的使用条件 10. 指针空值nullptr(C11) 10.1 C98中的指针空值 7.内联…

水质监测站:守护水源,筑牢水质安全屏障

TH-LSZ06水质监测站&#xff0c;作为现代水质监测技术的创新成果&#xff0c;以其高效、便捷的特点&#xff0c;在水源保护、环境监测等领域发挥着越来越重要的作用。它不仅能够实时监测水质变化&#xff0c;提供准确的数据支持&#xff0c;还能为水质安全管理提供科学依据&…