Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ESM and CSS loading #886

Open
oldrich-s opened this issue May 18, 2018 · 48 comments
Open

ESM and CSS loading #886

oldrich-s opened this issue May 18, 2018 · 48 comments
Labels
ESM ECMAScript modules feature-request Request for new features or functionality integration
Milestone

Comments

@oldrich-s
Copy link

In the ESM (EcmaScript Modules) version, css files are being imported like:

import './actionbar.css';

I have looked into the ECMA-262, TC39 proposals etc. but nowhere I could find that importing CSS files or other assets is allowed this way. Do any browsers support importing CSS files that way? Do I miss anything obvious? To me the ESM version is not ESM ;).

@flash-me
Copy link

flash-me commented May 22, 2018

CSS files are handled by webpack, the typical bundler when choosing ESM. This has nothing to do neither with ECMA nor with the browsers. For more information about webpack and loading CSS: https://webpack.js.org/guides/asset-management/#loading-css

@oldrich-s
Copy link
Author

oldrich-s commented May 22, 2018

To me it is a mistake to introduce non-standard features to esm modules. How about if I want to use the unbundled version directly in the browser together with http2? As it is now, it is impossible without preprocessing.

@flash-me
Copy link

flash-me commented May 22, 2018

Sorry for the disappointment for not fitting in your expectation. I think the ESM Version is thought to be bundled with webpack in this project, I'm quite sure that bundled/optimizied/tree-shaked code is still faster than HTTP2 with multiplexing etc... But hey, there is still an AMD Version that you can use.

@alexdima
Copy link
Member

@czb I agree.

I explicitly tried to avoid any webpack assumptions in the esm distribution, that's why the esm distribution can be loaded by other loaders.

But I could not figure out what to do with the CSS imports. Our CSS is grouped neatly with the corresponding feature. i.e. we don't have a large CSS file which is a "take it all or leave it all", but rather we have written the CSS next to the JS, so each JS file can indicate that it wants certain CSS.

You are correct, the source code cannot be loaded in its current shape via a <script type="module> tag without a minimal preprocessing to strip the CSS imports. But, I wonder, what is the alternative? What would you recommend? My plan is to wait for the committee to figure out what they're gonna do about CSS loading and adopt that once that is finalised.

@alexdima alexdima self-assigned this May 28, 2018
@oldrich-s
Copy link
Author

oldrich-s commented May 28, 2018

Personally, I load css dynamically:

export function injectFile(href: string) {
    return new Promise<void>((resolve, reject) => {
        const link = document.createElement('link')
        link.setAttribute("rel", "stylesheet")
        link.setAttribute("type", "text/css")
        link.onload = () => resolve()
        link.onerror = () => reject()
        link.setAttribute("href", href)
        document.head.appendChild(link)
    })
}

export function injectText(content: string) {
    const css = document.createElement("style")
    css.type = "text/css"
    css.innerHTML = content
    document.head.appendChild(css)
}

I guess it is equivalent to dynamic import.

@alexdima
Copy link
Member

@czb Yes, that is what I also do in the AMD loader I wrote 5 years ago :). But I don't want to do that in each and every JS file where I use some CSS. I also want the ability to be able to collect all used CSS into a single style-sheet at packaging time, so that's why I left .css imports in JS files.

@oldrich-s
Copy link
Author

I could not find any signs of css imports discussions which probably means they will not arrive in a near future.

To me, any solution is better than extending javascript by non standard features that are not even at stage 1.

@alexdima
Copy link
Member

@czb I am not familiar with the maze of github repositories used by whatwg or w3c nor their politics.

I don't expect that ECMA-262 (which standardises ECMAScript) would concern itself with importing CSS in a web browser.

Do you happen to know where we can ask someone about the state of affairs for importing CSS ?

@justinfagnani
Copy link

I happen to be part of the group proposing HTML Modules - the ability to import HTML files into JavaScript modules or via <script type=module. CSS Modules are on our radar, but not proposed yet.

I can tell you that the semantics would likely be oriented around a CSS module exporting a Constructible Stylesheet: https://wicg.github.io/construct-stylesheets/index.html

ie:

import stylesheet from './styles.css';

stylesheet instanceof CSSStyleSheet; // true

// attach to global
document.moreStyleSheets.push(stylesheet);

// or to a ShadowRoot
class MyElement extends HTMLElement {
  constructor() {
    this.attachShadow({mode: 'open'}).moreStyleSheets.push(stylesheet);
  }
}

If you wanted to be future-facing to such a spec, the most proper thing to do would be to create a JS modules that has no side-effects and returns a style sheet object from a JS module, ie:

styles.css.js:

// create a container and scope to hold a style sheet:
const container = document.createElement('div');
const shadowRoot = container.attachShadow({mode: 'open'});

// create a <style> element to add css text to
let styleElement = document.createElement('style');

// add the styles
styleElement.textContent = `
  .monaco-editor .accessibilityHelpWidget {
	padding: 10px;
	vertical-align: middle;
	overflow: scroll;
  }
`;
// add the <style> to the document so it creates a style sheet
shadowRoot.appendChild(styleElement);

export default let stylesheet = styleElement.sheet;

However, because this has no side-effects, this would require rewriting the importing code. Which is kind of ugly without the moreStyleSheets property of the Constructable StyleSheets proposal.

So for now, because I don't think you're using Shadow DOM yet, I think the best option is to build .css files into JS modules, that attach the CSS to the main document:

export default styleElement = document.createElement('style');
styleElement.textContent = `
.monaco-editor .accessibilityHelpWidget {
	padding: 10px;
	vertical-align: middle;
	overflow: scroll;
}
document.head.appendChild(styleElement);
`;

Then your import sites can remain the same as the are now.

@alexdima
Copy link
Member

Thank you for the explanation @justinfagnani

I'm going to think about this for a while, I was hoping to end up in a situation with the /esm/ folder in our distribution where its contents are both future-proof and consumable today via loaders such as webpack or rollup.

I don't believe webpack nor rollup would understand the creation of <style> elements code pattern and be able to extract the CSS to a separate CSS file.

I also don't think browsers will really like this creation of hundreds of <style> elements (speed-wise). Even if the pattern would change to create a single <style> element and keep appending to it, things might not be as good as extracting the CSS to a single file... I think browsers prioritise downloading CSS over other resources like images...

@justinfagnani
Copy link

Hundreds of stylesheets will be fine. We have lots of experience with styles directly embedded into HTML imports and JS modules on the Polymer team. The future is styles and templates packaged with components, and browsers are well positioned for that. Because modules are statically imported, they can be prefetched as discovered and with http/2 you can get lots of loading in parallel. The tools can analyze the dependency graph and add <link rel=modulepreload> tags to head which causees fetches before the browser even discovering the whole graph.

If the distribution stays dependent on WebPack, can you at least rename the folder and documentation from "esm" to WebPack? It's very misleading that you can't actually load the modules in the browser, IMO.

@justinfagnani
Copy link

So I modified the gulpfile to build .css into .css.js and this gets things closer to working, but there was an exception about require not being defined:

languageFeatures.js:192 ReferenceError: require is not defined
    at editorSimpleWorker.js:522
    at new Promise_ctor (winjs.base.js:1822)
    at EditorSimpleWorkerImpl.BaseEditorSimpleWorker.loadForeignModule (editorSimpleWorker.js:521)
    at webWorker.js:57
    at ShallowCancelThenPromise.CompletePromise_then [as then] (winjs.base.js:1743)
    at MonacoWebWorkerImpl._getForeignProxy (webWorker.js:56)
    at MonacoWebWorkerImpl.getProxy (webWorker.js:87)
    at WorkerManager._getClient (workerManager.js:76)
    at WorkerManager.getLanguageServiceWorker (workerManager.js:105)
    at DiagnostcsAdapter.worker [as _worker] (tsMode.js:47)

@zevisert
Copy link

Any updates on this? Is it just that we're waiting on ECMA for new CSS loading spec? If so, it would be great to have a ESM spec compliant version using one of the solutions @justinfagnani put forward for the time being!

@staxmanade
Copy link

I'd love to be able to run the following in the browser (just prototyping myself) - and have it load the editor:

<!doctype html>
<html>
  <meta charset="utf-8">
<body>
  <div id="container">
</body>
<script type="module">

  import monaco from '//dev.jspm.io/monaco-editor/esm/vs/editor/editor.main.js';
  console.log(monaco);

</script>
</html>

Currently it does not load and I see a large number of these errors in the console

Failed to load module script: The server responded with a non-JavaScript MIME type of "text/css". Strict MIME type checking is enforced for module scripts per HTML spec.

Here's an example plnkr I was hoping to be able to hack on using the monaco editor in the browser https://plnkr.co/edit/0fMrMAjNeH2PVGAh206m?p=preview to prototype something...

@JosefJezek
Copy link

@alexandrudima @kqadem Any news?

@paulvarache
Copy link

I wanted to prototype earlier on and faced a similar issue. My temporary solution was to make my local server transform the css files into javascript that append a stylesheet in the head. Here is my server for whoever needs it:

const connect = require('connect');
const http = require('http');
const static = require('serve-static');
const transform = require('connect-static-transform');

const css = transform({
    root: __dirname,
    match: /(.+)\.css/,
    transform: (path, content, send) => {
        const c = `
        const css = document.createElement('style');
        css.type = "text/css";
        css.textContent = \`${content}\`;
        document.head.appendChild(css);
        `;
        send(c, {'Content-Type': 'application/javascript'});
    },
});

const app = connect().use(css).use(static(__dirname));

http.createServer(app).listen(8000);

@flash-me
Copy link

flash-me commented Jan 19, 2019

@JosefJezek

Didn't realize I'm still subscribed to this. Never intended to participate in such discussion. Personally I don't even know where the problem is. If you want ESM spec compliance, you can either

  • provide a single .css file containing all styles
  • provide seperate .css file for each module in the same directory to have an association between them (or any other convention, e.g. same file name pattern etc..)
  • make use of CSSOM within the JavaScript

thats it from your side as the 'providing side'. The question of how the consumer works with it in the end shouldn't be your concern as long as your stuff comply the specs (imho)

@e111077
Copy link

e111077 commented May 29, 2019

Hello, is there any update on distributing a browser/spec-friendly ESM build?

I think the suggestions given above are all quite reasonable:

  • Distribute your css as .css.js stylesheet modules
  • Distributing a js module and a bundled stylesheet

Browser-friendly ES modules with minimal side-effects per module are an incredibly useful tool for publishing future-proof packages. As of now, I can't really ship a browser-runnable package without forcing the user into webpack or parcel.

Alternatively, Rollup support would also make this transformation much easier.

@webmetal
Copy link

I can't believe we are still not on standard here.
Is there any effort being made to resolve this yet?
In this day and age staxmanade example is the reasonable choice.

@alexdima alexdima added feature-request Request for new features or functionality integration labels Dec 12, 2019
@alexdima alexdima added this to the Backlog milestone Dec 12, 2019
@alexdima alexdima removed their assignment Dec 16, 2019
@justinfagnani
Copy link

I forgot about Monaco not being standards compliant here after being away from two years, and hit this roadblock again when trying to load code directly into a browser.

Again, can the ESM distribution be renamed to "webpack" or something? It's just misleading at this point. If the library really is distributed as modules then typing import('https://unpkg.com/[email protected]/esm/vs/editor/editor.all.js?module'); in devtools should work without errors. Monaco currently has dozens of errors.

Then, can the CSS just be built and distributed a different way? Many libraries that use standard modules these days publish CSS inside of JS modules, sometimes via a build step. Vexflow and CodeMirror 6 are two I've seen recently. Everything made with LitElement works this way. The benefit is that the CSS is part of the library's module graph and you don't need any bundler to get the right files loaded.

@sombreroEnPuntas
Copy link

For anyone else struggling with this: yes. This library is not exported as a valid ESM module.

If you need to use it on a real-life project (specially if you rely on popular abstractions like create-react-app or next-js), it needs to be transpiled and loaded with a separate webpack rule.

Here's a few things that worked on my current stack, but each toolchain will have it's entry points to allow webpack tweaks (or the eject button, which leaves you with plain webpack) :)

  • webpack plugin for monaco editor to write less configs 📜
  • react monaco editor to skip writing the react bindings ---> but still uses monaco-editor --> so it inherits the same ESM issues :0
  • I use next-js, luckily they expose a lot of entry points to play with webpack config. Depending on your choice of styling (css/css-modules/sass/css-in-js) you'll need to set a special loader rule for the monaco styles and ttf fonts! Here's a hint on how it could look, but it depends on your css flavor
// pseudo-code from the docs here: 
// https:/react-monaco-editor/react-monaco-editor#using-with-webpack
// Specify separate paths
const path = require('path');
const APP_DIR = path.resolve(__dirname, './src');
const MONACO_DIR = path.resolve(__dirname, './node_modules/monaco-editor');

// pass webpack module.rules
{
  test: /\.css$/,
  include: APP_DIR,
  use: [{
    loader: 'style-loader', // or whatever you use
  }, {
    loader: 'css-loader', // or whatever you use
    options: {
      modules: true,
      namedExport: true,
    },
  }],
}, {
  test: /\.css$/,
  include: MONACO_DIR,
  use: ['style-loader', 'css-loader'],
},
, {
  test: /\.ttf$/,
  include: MONACO_DIR,
  use: ['file-loader'],
}
  • next-transpile-modules will transpile the nasty code!!! Could also be written on webpack or as a prebuild step I guess? But it needs to be transpiled at some point otherwise it will just not work :)
  • if using ssr we get a node-ish env on the first render... so don't forget to make it a dynamic import to avoid missing browser globals!
import dynamic from 'next/dynamic'

const MonacoEditor = dynamic(import('react-monaco-editor'), { ssr: false })

<MonacoEditor ... />
  • web workers need to be handled by nextjs as static content to be available on the final bundle 🤖

Hope this list of hints helps others trying to consume it on their projects... but also highlights how much effort/knowledge/fiddling is needed to actually use the library :)

@klebba
Copy link

klebba commented Jul 14, 2020

Reiterating what @justinfagnani has said above; calling this esm is misleading at best. At worst a casual adopter will install the library and attempt to import it natively only to find that this does not work, wasting time. If a bundler like webpack is necessary this obviates the benefits of language-level, dependency-free imports. Monaco is awesome, and I hope that this can be addressed with more urgency. Thanks!

@alexdima
Copy link
Member

https://web.dev/css-module-scripts/ indicates a possible way forward:

import sheet from './styles.css' assert { type: 'css' };
document.adoptedStyleSheets = [sheet];
shadowRoot.adoptedStyleSheets = [sheet];

or the async variant

const cssModule = await import('./style.css', {
  assert: { type: 'css' }
});
document.adoptedStyleSheets = [cssModule.default];

@alexdima alexdima added the ESM ECMAScript modules label Dec 20, 2021
@jogibear9988
Copy link

@alexdima does this mean you will change the imports to standard ones?
and will then the esm folder contain a version wich could directly be loaded from browser?

@jogibear9988
Copy link

@alexdima
do you work on this? is it possible to help with something?

@evtk
Copy link

evtk commented Jul 14, 2022

Checking in to raise awareness of this still being an issue and it rises to the surface for all Angular 14 users, as the latest release of Angular 14 has a significant change on this topic:

We now issue a build time error since importing a CSS file as an ECMA module is non standard Webpack specific feature, which is not supported by the Angular CLI.

This feature was never truly supported by the Angular CLI, but has as such for visibility.

See also: angular/angular-cli#23273

@evtk
Copy link

evtk commented Jul 19, 2022

For those of you ending up here finding a solution to have Monaco Editor ESM style work with Angular 14, this might help:

  1. Use @angular-builders/custom-webpack to be able to manipulate webpack config
  2. Setup like below in your custom-webpack.config.js
const webpack = require("webpack");
const path = require("path");
const MONACO_DIR = path.join(__dirname, "..", "node_modules/monaco-editor");

module.exports = {
  module: {
    rules: [
      {
        test: /\.css$/,
        include: MONACO_DIR,
        use: ["style-loader", {
          "loader": "css-loader",
          "options": {
            "url": false,
          },
        }],
      },
    ],
  },
};

@Maksclub
Copy link

Maksclub commented Sep 18, 2022

@evtk thnx, but not working :(

const MONACO_DIR = path.join(__dirname, "..", "node_modules/monaco-editor");

module.exports = {
  module: {
    rules: [
      {
        test: /\.css$/,
        include: MONACO_DIR,
        use: ["style-loader", {
          "loader": "css-loader",
          "options": {
            "url": false,
          },
        }],
      },
    ],
  },
};```

  "architect": {
    "build": {
      "builder": "@angular-builders/custom-webpack:browser",
      "options": {
        "customWebpackConfig": {
          "path": "./custom-webpack.config.js",
          "mergeRules": {
            "externals": "replace"
          }
        },

"dependencies": {
"@angular-builders/custom-webpack": "^14.0.1",
"@angular/animations": "~14.2.2",
"@angular/cdk": "^13.3.9",
"@angular/common": "~14.2.2",
"@angular/compiler": "~14.2.2",
"@angular/core": "~14.2.2",
"@angular/forms": "~14.2.2",
"@angular/platform-browser": "~14.2.2",
"@angular/platform-browser-dynamic": "~14.2.2",
"@angular/router": "~14.2.2",

@Shahar-Galukman
Copy link

Shahar-Galukman commented Sep 18, 2022

@Maksclub It does work, just had this same issue with 14.2.2 as well.
Just make sure the MONACO_DIR path is correct corresponding your project structure.
my case was removing the ..
const MONACO_DIR = path.join(__dirname, "node_modules/monaco-editor");

Big thanks @evtk for the elegant solution

@neodescis
Copy link

neodescis commented Oct 10, 2022

For those of you ending up here finding a solution to have Monaco Editor ESM style work with Angular 14, this might help:

  1. Use @angular-builders/custom-webpack to be able to manipulate webpack config
  2. Setup like below in your custom-webpack.config.js
const webpack = require("webpack");
const path = require("path");
const MONACO_DIR = path.join(__dirname, "..", "node_modules/monaco-editor");

module.exports = {
  module: {
    rules: [
      {
        test: /\.css$/,
        include: MONACO_DIR,
        use: ["style-loader", {
          "loader": "css-loader",
          "options": {
            "url": false,
          },
        }],
      },
    ],
  },
};

Thanks for this. It's painful to need a custom webpack config, but it worked like a charm. However, I now have a bunch of nonsensical warnings in my Angular build, e.g.:

Warning: /...../node_modules/monaco-editor/esm/vs/editor/standalone/browser/iPadShowKeyboard/iPadShowKeyboard.css depends on '!../../../../../../../style-loader/dist/runtime/styleDomAPI.js'. CommonJS or AMD dependencies can cause optimization bailouts.

The irony of it complaining about these being "CommonJS or AMD dependencies" is not lost on me. I don't see how to disable the warnings though, given that they are coming from style-loader itself. Whitelisting style-loader in angular.json using allowedCommonJsDependencies has no effect. They are just warnings, but if anyone knows how to get rid of them, please post.

@codeStryke
Copy link

Maybe this helps someone, I created an ESM bundle of Monaco here. There are a couple of examples of using it in the test directory.

@evtk
Copy link

evtk commented Nov 26, 2022

@codeStryke without diving too much into that bundle: would it be possible to use Monaco without all the needed webpack config? I would love to remove all that custom config and the need for the custom builder for our Angular app.

@bultas
Copy link

bultas commented Nov 26, 2022

@evtk You can also use "prebuild" and then import as ES module.

Just use this
https:/bultas/monaco-component

@evtk
Copy link

evtk commented Nov 28, 2022

Thanks for the input @codeStryke and @bultas. The thing is I need to apply an additional worker on it and so far I haven't managed to apply that additional configuration to the monaco-editor and then export it to be used as ES module.

@evtk
Copy link

evtk commented Nov 30, 2022

I managed to build an ESM library out of the monaco-editor which includes a default configuration for YAML (only). It is pretty tailor made (added specific exports in the module), but thought it was worth posting here to help others.

https:/evtk/monaco-editor-yaml-esm

For those coming here, looking for an answer how to use the Monaco Editor with Angular. Besides that webpack based solution I posted earlier on, you can also use this package or your own fork of it. It removes the need to use custom angular builders, which i wanted to get rid of.

Instead of installing the monaco-editor use the bundle I created and make sure to add the following glob to your angular.json assets config:

              {
                "glob": "**/*",
                "input": "node_modules/monaco-editor-yaml-esm/dist/assets",
                "output": "assets"
              }

@poucet
Copy link

poucet commented Feb 16, 2023

For those of us that use web-dev-server and typescript, what is the suggested solution. I'm currently running into this problem with this change:
https://gerrit-review.googlesource.com/c/gerrit/+/358615

The challenge with monaco-component is that you lose the typings.

@e111077
Copy link

e111077 commented Feb 16, 2023

@poucet Web Dev Server can accept arbitrary rollup plugins to serve these kinds of things, but the problem is that I'm not sure how monaco expects those CSS imports to be handled, hence why monaco should remove them from their distribution bundle. Here is an example of using WDS + a random CSS rollup plugin I found (not sure if this is how monaco expects it to be used):

https://stackblitz.com/edit/node-tfl4jq?file=types.d.ts

But, doing this compilation at serve time is probably not what you want because it can cause server latency. To make TS not crash, you should include a declare module '*.css' {} so TSC can compile, and then use a build tool like rollup / esbuild / webpack to process the bundle and handle those CSS files in the way monaco expects and then serve that resultant bundle.

@jogibear9988
Copy link

As Import Attributes are now at stage 3 (again), would it be possible to switch monaco to use valid ES6 Code?

https:/tc39/proposal-import-attributes

@jogibear9988
Copy link

related to #4175

@neodescis
Copy link

Another option for Angular, if you don't want to use the custom webpack builder listed above, is to do something like this somewhere in the build or postinstall:

npx replace "^import '.*\.css';$" "" --recursive ./node_modules/monaco-editor/esm/*

And then add this to your global styles list in angular.json:

node_modules/monaco-editor/min/vs/editor/editor.main.css

This works better for my particular use case, where I am publishing a reusable monaco-editor component. I've added the npx script to the component library's postinstall, and I no longer have to force users of the component/library to use the custom webpack builder. It's a bit ugly, but so are the .css imports being removed!

@lppedd
Copy link

lppedd commented Mar 5, 2024

@neodescis did you ever find a solution for those warnings?
I mean, not a big deal, but since we know that they're redundant, it would be better to get rid of them.

@neodescis
Copy link

neodescis commented Mar 5, 2024

@neodescis did you ever find a solution for those warnings? I mean, not a big deal, but since we know that they're redundant, it would be better to get rid of them.

@lppedd , if you use the solution I posted just above, instead of the custom webpack builder, you shouldn't see any warnings.

@lppedd
Copy link

lppedd commented Mar 5, 2024

I'm not sure if it can work for my requirement: I need to have the smallest bundle possible.
I'm already using monaco-editor-webpack-plugin to remove stuff that I don't need.
Although maybe I can use both of them, need to try.

OT for posterity. I had to disable Webpack caching to get the plugin's loader to work.

const MonacoWebpackPlugin = require("monaco-editor-webpack-plugin");

module.exports = {
  cache: false,
  ...

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
ESM ECMAScript modules feature-request Request for new features or functionality integration
Projects
None yet
Development

No branches or pull requests