angularmonaco-editorangular16nomachine-nx

How to Integrate Monaco Editor in an Angular 16 Nx Project?


I want to install the Monaco project in an Angular 16 environment within an Nx project. After researching, I understand that it's necessary to adjust the executor in the project.json of the Angular project to @angular-devkit/custom-webpack:browser and set the options->customWebpackConfig, with the path pointing to the specified webpack.config.js. My webpack.config.js configuration is as follows:

const path = require('path');
const MonacoWebpackPlugin = require('monaco-editor-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
module.exports = (config, context) => {
    console.log('默认配置', JSON.stringify(config, null, 2));
    const newConfig = {
        ...config,
        ...{
            plugins: [
                new MonacoWebpackPlugin(),
                new MiniCssExtractPlugin({
                    filename: '[name].css',
                    runtime: false
                })
            ],
            module: {
                rules: [
                    {
                        test: /\.ts$/,
                        use: 'ts-loader',
                        exclude: /node_modules/
                    },
                    {
                        test: /\.s?css$/i,
                        use: [
                            MiniCssExtractPlugin.loader,
                            'css-loader',
                            {
                                loader: 'postcss-loader',
                                options: {
                                    plugins: () => [require('autoprefixer')()]
                                }
                            },
                            {
                                loader: 'sass-loader',
                                options: {
                                    sourceMap: true
                                }
                            }
                        ]
                    },
                    {
                        test: /\.ttf$/,
                        include: path.resolve(__dirname, '../node_modules/monaco-editor'),
                        use: ['file-loader']
                    }
                ]
            }
        }
    };
    console.log('最后配置', JSON.stringify(newConfig, null, 2));
    return newConfig;
};

However, what I don't understand is that it keeps throwing errors and fails to parse CSS correctly. I'm starting to doubt if anyone has successfully introduced Monaco in a similar environment.

My other configurations: nx report

 >  NX   Report complete - copy this into the issue template

   Node   : 20.15.1
   OS     : darwin-x64
   pnpm   : 9.7.0
   
   nx (global)        : 19.5.6
   nx                 : 16.10.0
   @nx/js             : 16.10.0
   @nx/jest           : 16.10.0
   @nx/linter         : 16.10.0
   @nx/workspace      : 16.10.0
   @nx/angular        : 16.10.0
   @nx/eslint-plugin  : 16.10.0
   @nx/storybook      : 16.10.0
   typescript         : 5.1.6
   ---------------------------------------
   Community plugins:
   @compodoc/compodoc : 1.1.25
   @storybook/angular : 7.6.20

package.json

{
    "name": "@yi-form/source",
    "version": "0.0.0",
    "license": "MIT",
    "scripts": {
    },
    "private": true,
    "devDependencies": {
        "@angular-builders/custom-webpack": "~16.0.1",
        "@angular-devkit/build-angular": "~16.2.15",
        "@angular-devkit/core": "~16.2.15",
        "@angular-devkit/schematics": "~16.2.15",
        "@angular-eslint/eslint-plugin": "~16.0.3",
        "@angular-eslint/eslint-plugin-template": "~16.0.3",
        "@angular-eslint/template-parser": "~16.0.3",
        "@angular/cli": "~16.2.15",
        "@angular/compiler-cli": "~16.2.12",
        "@angular/language-service": "~16.2.12",
        "@babel/plugin-proposal-class-properties": "^7.18.6",
        "@babel/plugin-proposal-decorators": "^7.24.7",
        "@babel/preset-env": "^7.25.4",
        "@babel/preset-react": "^7.24.7",
        "@babel/preset-typescript": "^7.24.7",
        "@compodoc/compodoc": "^1.1.25",
        "@nx/angular": "16.10.0",
        "@nx/eslint-plugin": "16.10.0",
        "@nx/jest": "16.10.0",
        "@nx/js": "16.10.0",
        "@nx/linter": "16.10.0",
        "@nx/storybook": "16.10.0",
        "@nx/workspace": "16.10.0",
        "@schematics/angular": "~16.2.15",
        "@storybook/addon-docs": "^8.2.9",
        "@storybook/addon-essentials": "^7.6.20",
        "@storybook/angular": "^7.6.20",
        "@storybook/builder-webpack5": "^8.2.9",
        "@storybook/core-server": "^7.6.20",
        "@storybook/react-dom-shim": "^8.2.9",
        "@types/jest": "^29.5.12",
        "@types/node": "16.11.7",
        "@types/sortablejs": "^1.15.8",
        "@typescript-eslint/eslint-plugin": "^5.62.0",
        "@typescript-eslint/parser": "^5.62.0",
        "autoprefixer": "^10.4.20",
        "babel-loader": "^9.1.3",
        "css-loader": "^7.1.2",
        "esbuild": "^0.19.12",
        "eslint": "~8.46.0",
        "eslint-config-prettier": "8.1.0",
        "file-loader": "^6.2.0",
        "html-webpack-plugin": "^5.6.0",
        "jest": "^29.7.0",
        "jest-environment-jsdom": "^29.7.0",
        "jest-preset-angular": "~13.1.6",
        "jsonc-eslint-parser": "^2.4.0",
        "mini-css-extract-plugin": "^2.9.1",
        "monaco-editor-webpack-plugin": "^7.1.0",
        "ng-packagr": "~16.2.3",
        "nx": "^16.10.0",
        "postcss": "^8.4.45",
        "postcss-import": "~14.1.0",
        "postcss-loader": "^8.1.1",
        "postcss-preset-env": "~7.5.0",
        "postcss-url": "~10.1.3",
        "prettier": "^2.8.8",
        "react": "^18.3.1",
        "react-dom": "^18.3.1",
        "sass": "1.78.0",
        "sass-loader": "^16.0.1",
        "style-loader": "^4.0.0",
        "ts-jest": "^29.2.5",
        "ts-loader": "^9.5.1",
        "ts-node": "10.9.1",
        "tsconfig-paths": "^4.2.0",
        "typescript": "~5.1.6",
        "webpack": "5"
    },
    "dependencies": {
        "@angular/animations": "~16.2.12",
        "@angular/common": "~16.2.12",
        "@angular/compiler": "~16.2.12",
        "@angular/core": "~16.2.12",
        "@angular/forms": "~16.2.12",
        "@angular/platform-browser": "~16.2.12",
        "@angular/platform-browser-dynamic": "~16.2.12",
        "@angular/router": "~16.2.12",
        "monaco-editor": "^0.50.0",
        "rxjs": "~7.8.1",
        "sortablejs": "^1.15.3",
        "tslib": "^2.7.0",
        "zone.js": "~0.13.3"
    }
}

project.json

{
    "name": "yi-form-demo",
    "$schema": "../node_modules/nx/schemas/project-schema.json",
    "projectType": "application",
    "prefix": "yi-form",
    "sourceRoot": "yi-form-demo/src",
    "tags": [],
    "targets": {
        "build": {
            "executor": "@angular-builders/custom-webpack:browser",
            "outputs": ["{options.outputPath}"],
            "options": {
                "customWebpackConfig": {
                    "path": "yi-form-demo/webpack.config.js"
                },
                "outputPath": "dist/yi-form-demo",
                "index": "yi-form-demo/src/index.html",
                "main": "yi-form-demo/src/main.ts",
                "tsConfig": "yi-form-demo/tsconfig.app.json",
                "polyfills": ["zone.js"],
                "assets": ["yi-form-demo/src/favicon.ico", "yi-form-demo/src/assets",{
                        "glob": "**/*",
                        "input": "../node_modules/monaco-editor/min/vs",
                        "output": "yi-form-demo/src/assets/monaco-editor"
                    }],
                "inlineStyleLanguage": "scss",
                "styles": ["yi-form-demo/src/styles.css"],
                "scripts": []
            },
            "configurations": {
                "production": {
                    "vendorChunk": true
                },
                "development": {
                    "buildOptimizer": false,
                    "optimization": false,
                    "vendorChunk": true,
                    "extractLicenses": false,
                    "sourceMap": true,
                    "namedChunks": true
                }
            },
            "defaultConfiguration": "production"
        },
        "serve": {
            "executor": "@angular-devkit/build-angular:dev-server",
            "configurations": {
                "production": {
                    "browserTarget": "yi-form-demo:build:production"
                },
                "development": {
                    "browserTarget": "yi-form-demo:build:development"
                }
            },
            "defaultConfiguration": "development"
        },
        "extract-i18n": {
            "executor": "@angular-devkit/build-angular:extract-i18n",
            "options": {
                "browserTarget": "yi-form-demo:build"
            }
        },
        "lint": {
            "executor": "@nx/linter:eslint",
            "outputs": ["{options.outputFile}"],
            "options": {
                "lintFilePatterns": ["yi-form-demo/**/*.ts", "yi-form-demo/**/*.html"]
            }
        },
        "test": {
            "executor": "@nx/jest:jest",
            "outputs": ["{workspaceRoot}/coverage/{projectRoot}"],
            "options": {
                "jestConfig": "yi-form-demo/jest.config.ts",
                "passWithNoTests": true
            },
            "configurations": {
                "ci": {
                    "ci": true,
                    "codeCoverage": true
                }
            }
        }
    }
}

errors:

./node_modules/.pnpm/typescript@5.1.6/node_modules/typescript/lib/typescript.js:3055:91-112 - Warning: Module not found: Error: Can't resolve 'perf_hooks' in '/Users/EternalHeart/workspace/angular/angular-saas/yi-form/node_modules/.pnpm/typescript@5.1.6/node_modules/typescript/lib'

./node_modules/.pnpm/typescript@5.1.6/node_modules/typescript/lib/typescript.js:3089:22-44 - Warning: Critical dependency: the request of a dependency is an expression

./node_modules/.pnpm/typescript@5.1.6/node_modules/typescript/lib/typescript.js:6256:35-54 - Warning: Critical dependency: the request of a dependency is an expression



./yi-form-demo/src/app/app.config.ts:4:0-45 - Error: Module not found: Error: Can't resolve 'yi-form' in '/Users/EternalHeart/workspace/angular/angular-saas/yi-form/yi-form-demo/src/app'

./yi-form-demo/src/components/asset-manager/asset-manager.component.ts:5:0-48 - Error: Module not found: Error: Can't resolve 'yi-form' in '/Users/EternalHeart/workspace/angular/angular-saas/yi-form/yi-form-demo/src/components/asset-manager'

./yi-form-demo/src/components/convert-helper/convert-helper.component.ts:5:0-49 - Error: Module not found: Error: Can't resolve 'yi-form' in '/Users/EternalHeart/workspace/angular/angular-saas/yi-form/yi-form-demo/src/components/convert-helper'

./yi-form-demo/src/components/designer/designer.component.ts:5:0-44 - Error: Module not found: Error: Can't resolve 'yi-form' in '/Users/EternalHeart/workspace/angular/angular-saas/yi-form/yi-form-demo/src/components/designer'

./yi-form-demo/src/components/manager/manager.component.ts:5:0-43 - Error: Module not found: Error: Can't resolve 'yi-form' in '/Users/EternalHeart/workspace/angular/angular-saas/yi-form/yi-form-demo/src/components/manager'

./yi-form-demo/src/components/renderer/renderer.component.ts:5:0-44 - Error: Module not found: Error: Can't resolve 'yi-form' in '/Users/EternalHeart/workspace/angular/angular-saas/yi-form/yi-form-demo/src/components/renderer'

./yi-form-demo/src/components/script-editor/script-editor.component.ts:5:0-48 - Error: Module not found: Error: Can't resolve 'yi-form' in '/Users/EternalHeart/workspace/angular/angular-saas/yi-form/yi-form-demo/src/components/script-editor'

./yi-form-demo/src/demo/demo.register.ts:1:0-54 - Error: Module not found: Error: Can't resolve 'yi-form' in '/Users/EternalHeart/workspace/angular/angular-saas/yi-form/yi-form-demo/src/demo'

./yi-form-demo/src/directives/dynamic-component.directive.ts:5:0-52 - Error: Module not found: Error: Can't resolve 'yi-form' in '/Users/EternalHeart/workspace/angular/angular-saas/yi-form/yi-form-demo/src/directives'



 ——————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————

 >  NX   Running target build for project yi-form-demo and 1 task it depends on failed

   Failed tasks:
   
   - yi-form-demo:build:production
   
   Hint: run the command with --verbose for more details.


The above is the first error, likely because the tsconfig.app.json inherits the configuration from tsconfig.base.json, and when changing the executor, the extends property becomes invalid. Even if I manually resolve this reference, there will still be the following issue:

./yi-form-demo/src/demo/demo.ts-1.css?ngResource!=!./node_modules/.pnpm/@ngtools+webpack@16.2.15_@angular+compiler-cli@16.2.12_@angular+compiler@16.2.12_@angular+cor_663oprwhopsofuxbpzvh4iaq2e/node_modules/@ngtools/webpack/src/loaders/inline-resource.js!./yi-form-demo/src/demo/demo.ts - Error: No template for dependency: CssDependency

./yi-form-demo/src/app/app.select.scss?ngResource - Error: No template for dependency: CssDependency

Error: The loader "/Users/EternalHeart/workspace/angular/angular-saas/yi-form/yi-form-demo/src/app/app.select.scss" didn't return a string.

Error: The loader "/Users/EternalHeart/workspace/angular/angular-saas/yi-form/yi-form-demo/src/demo/demo.ts-angular-inline--1.css" didn't return a string.



 ——————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————

 >  NX   Running target build for project yi-form-demo and 1 task it depends on failed


enter image description here

I want to install the Monaco project in an Angular 16 environment within an Nx project.


Solution

  • After changing the executor from @angular-devkit/build-angular:browser to @angular-devkit/build-angular:browser-esbuild, everything worked perfectly! (You can now remove all redundant webpack configurations, and you don't even need to include any plugins!)