前言:上一篇文章我主要分享了从 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会更新数据,这样会导致递归渲染数据 |
创建阶段 | componentDidMount | DOM已经渲染完成了;可以进行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中生命周期的讲解