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.mzph.cn/news/789353.shtml

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

相关文章

ubuntu20.04 运行 lio-sam 流程记录

ubuntu20.04 运行 lio-sam 一、安装和编译1.1、安装 ROS11.2、安装 gtsam1.3、安装依赖1.4、下载源码1.5、修改文件1.6、编译和运行 二、官方数据集的运行2.1、casual_walk_2.bag2.2、outdoor.bag、west.bag2.3、park.bag 三、一些比较好的参考链接 记录流程&#xff0c;方便自…

dm8 开启归档模式

dm8 开启归档模式 1 命令行 [dmdbatest1 dm8]$ disql sysdba/Dameng123localhost:5237服务器[localhost:5237]:处于普通打开状态 登录使用时间 : 3.198(ms) disql V8 SQL> select name,status$,arch_mode from v$database;行号 NAME STATUS$ ARCH_MODE ----------…

Python中输出显示台的设置

效果: 前言 这种文字显示的方式很适合新手来学习,毕竟新手还学不到pygame做游戏的, Python入门我们一般都学的是输入输出的游戏,但是如果加上一些文字和背景的改善可能会更好. 如何改变字体颜色 字体颜色(跟他的变量名是一样的): #改变字体颜色 RED \033[91m GREEN \033…

EasyExcel 模板导出excel、合并单元格及单元格样式设置。 Freemarker导出word 合并单元格

xls文件&#xff1a; 后端代码&#xff1a; InputStream filePath this.getClass().getClassLoader().getResourceAsStream(templateFile);// 根据模板文件生成目标文件ExcelWriter excelWriter EasyExcel.write(orgInfo.getFilename()).excelType(ExcelTypeEnum.XLS).withTe…

c#仿ppt案例

画曲线 namespace ppt2024 {public partial class Form1 : Form{public Form1(){InitializeComponent();}//存放所有点的位置信息List<Point> lstPosition new List<Point>();//控制开始画的时机bool isDrawing false;//鼠标点击开始画private void Form1_MouseD…

【C语言基础】:自定义类型(一)--> 结构体

文章目录 一、内置类型与自定义类型1.1 内置类型&#xff08;基本数据类型&#xff09;1.2 自定义类型 二、结构体2.1 结构体的声明2.2 结构体变量的创建和初始化2.3 结构体的特殊声明2.4 结构体的自引用 三、结构体内存对齐3.1 对齐规则3.2 为什么存在内存对齐3.3 修改默认对齐…

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.内联…

如何优化TCP?TCP的可靠传输机制是什么?

在网络世界中&#xff0c;传输层协议扮演着至关重要的角色&#xff0c;特别是TCP协议&#xff0c;以其可靠的数据传输特性而广受青睐。然而&#xff0c;随着网络的发展和数据量的激增&#xff0c;传统的TCP协议在效率方面遭遇了挑战。小编将深入分析TCP的可靠性传输机制&#x…

基于springboot+vue+Mysql的实习管理系统

开发语言&#xff1a;Java框架&#xff1a;springbootJDK版本&#xff1a;JDK1.8服务器&#xff1a;tomcat7数据库&#xff1a;mysql 5.7&#xff08;一定要5.7版本&#xff09;数据库工具&#xff1a;Navicat11开发软件&#xff1a;eclipse/myeclipse/ideaMaven包&#xff1a;…

VScode-配置文件

导入配置文件 ShiftCtrlp 输入&#xff1a; import 选择文件 点击确认 导出配置文件 设置选择导出 确认导出 保存为本地文件 保存文件

黄金票据攻击

黄金票据攻击——域内横向移动技术 一、黄金票据攻击介绍&#xff1a; 黄金票据攻击是一种滥用Kerberos身份认证协议的攻击方式&#xff0c;它允许攻击者伪造域控krbtgt用户的TGT&#xff08;Ticket-Granting Ticket&#xff09;。通过这种方法&#xff0c;攻击者可以生成有效…

【原创】基于springboot+vue疫苗预约管理系统设计与实现

个人主页&#xff1a;程序猿小小杨 个人简介&#xff1a;从事开发多年&#xff0c;Java、Php、Python、前端开发均有涉猎 博客内容&#xff1a;Java项目实战、项目演示、技术分享 文末有作者名片&#xff0c;希望和大家一起共同进步&#xff0c;你只管努力&#xff0c;剩下的交…

element-ui tableData导出为xlsx文件

下载 npm i / yarn add file-saver、xlsx库 引入 import FileSaver from “file-saver”; import XLSX from “xlsx”; const simexport (data) > {// if (data.create_time && data.create_time.length > 0) {// data.start_time parseTime(data.create_tim…

Rust vs C++:2024,谁更懂错误处理?

讲动人的故事,写懂人的代码 「席双嘉,听说你的C++项目又因为忘了检查返回值导致内存泄漏,又加班了?」 周五中午,在国内某科技巨头熙熙攘攘的员工餐厅,贾克强半开玩笑地戳了戳坐在隔壁的席双嘉,眼神中满是戏谑。 贾克强,一个热衷于Rust的程序员,总是乐于挑战和探索新…

mfw-攻防世界

题目 点击about发现Git感觉是Git泄露 直接访问.git 本来用githack去扒源码但是成功了没文件一脸懵&#xff0c; 后面换一个工具githacker注意二个之间有区别 githack和githacker 然后去结果里查看文件 发现flag文件但是 没什么用 <?php // TODO // $FLAG ; ?> 然后…

QCC发射(TX SOURCE)USB发射A2DP音乐,实现MIC声音到主机

之前写过 CSR8670/8675 发射&#xff08;TX SOURCE&#xff09;USB发射A2DP音乐&#xff0c;实现MIC声音到主机的文章&#xff0c;目前把该方案移植到QCC方案&#xff08;QCC3040 QCC3056&#xff09;。 因 CSR8670/8675成本比较贵&#xff0c;现在移植到QCC平台。 由于众多游…

Pytorch for training1——read data/image

blog torch.utils.data.Dataset create dataset with class torch.utils.data.Dataset automaticly import torch from torch.utils.data import Datasetclass MyDataset(Dataset):def __init__(self, data):self.data datadef __getitem__(self, index):# 根据索引获取样本…

华为OD面试手撕算法-合并排序数组

题目描述 本题是leetcode一道简单题&#xff1a;合并两个有序数组&#xff0c;但是对于时间和空间复杂度面试官明确给出了限制。 // 给定两个排序后的数组 A 和 B&#xff0c;其中 A 的末端有足够的缓冲空间容纳 B。 编写一个方法&#xff0c;将 B 合并入 A 并排序。 // 初始化…

对 NGINX、Kong 和 Amazon 的 API 管理解决方案进行基准测试:它们能否交付实时 API?

原文作者&#xff1a;Alessandro Fael Garcia of F5 原文链接&#xff1a;对 NGINX、Kong 和 Amazon 的 API 管理解决方案进行基准测试&#xff1a;它们能否交付实时 API&#xff1f; 转载来源&#xff1a;NGINX 开源社区 NGINX 唯一中文官方社区 &#xff0c;尽在 nginx.org.c…

HAL STM32 硬件I2C方式读取AS5600磁编码器获取角度例程

HAL STM32 硬件I2C方式读取AS5600磁编码器获取角度例程 &#x1f4cd;相关篇《STM32 软件I2C方式读取AS5600磁编码器获取角度例程》 ✨stm32使用硬件I2C去读取角度数据&#xff0c;通过STM32CubeMX工具配置工程&#xff0c;读取角度数据&#xff0c;只需要调用一个函数&#xf…