Open oldrich-s opened 6 years ago
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
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.
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.
@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.
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.
@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.
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.
@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 ?
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.
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...
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.
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)
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!
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...
@alexandrudima @kqadem Any news?
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);
@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
.css
file containing all styles.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..)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)
Hello, is there any update on distributing a browser/spec-friendly ESM build?
I think the suggestions given above are all quite reasonable:
.css.js
stylesheet modulesBrowser-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.
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.
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/monaco-editor@0.20.0/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.
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) :)
monaco-editor
--> so it inherits the same ESM issues :0ttf
fonts! Here's a hint on how it could look, but it depends on your css flavor
// pseudo-code from the docs here:
// https://github.com/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](https://github.com/martpie/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!
```js
import dynamic from 'next/dynamic'
const MonacoEditor = dynamic(import('react-monaco-editor'), { ssr: false })
<MonacoEditor ... />
static
content to be available on the final bundle :robot: 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 :)
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!
To save time for y'all:
Unfortunately, the ESM folder does NOT contain an ESM distribution
The ESM folder gives you the impression that it contains an ESM implementation of Monaco. It turns out that it tries to load CSS using import statements. Thus, modern browsers supporting ESM will not be able to import Monaco using ESM.
Thanks all; I got here after trying to get some node.js-based mocha tests that tried to test monarch syntax highlighting in an ESM project. I can't see a way around this, other than webpack-ing up my tests and letting the transpiler do its magic; which is a layer of complexity I was avoiding til now (and annoyingly the mocha<->webpack stuff's all broken too).
@oldrich-s You are right, moreover, that import './actionbar.css'
is one of those custom hacky patches that reach consensus among devs-on-the-edge, to inject text as a stylesheet on the fly, so nothing about it anything ressembling a 'module' XD
meanwhile at ECMALabs, 2 years later, they have come up with this thing, and hit a roadblock there:
import "https://example.com/module";
import "https://example.com/module" assert { type: "css" };
source: whatpr.org ramblings
Oh well, can we blame people though for just wanting to move forward while standards are being discussed XD
If you face this issue:
Personally I would take that import and put it where it on another css, since you are not getting any 'css scoping' benefit anyway, and this is actually compliant since ages ago:
import "./deezstyles.css";
.my-already-cascading-styleshits {
}
Or... you could also instantiate some DOM wrappers and stick them a shadowDOM so then you can assign it an instance of a CSSStytleSheet with your CSSRule, which effectively isolates your css classname and it doesnt pollute the global scope.
Because that's the spirit! 😂
There should be an official solution, but I've found some luck using Skypack which bundles npm packages to ESM on-demand.
import * as monaco from 'https://cdn.skypack.dev/monaco-editor@0.21.2';
This is also very fast to load because it bundles the entry-point and eliminates the waterfall effect.
There's a limitation with resolving assets within CSS though, and fails to load codicon.ttf
.
Not the best solution but I worked around this using a service worker:
self.addEventListener('activate', event => {
event.waitUntil(clients.claim());
});
self.addEventListener('fetch', (event) => {
if (event.request.url.endsWith('codicon.ttf')) {
event.respondWith(
fetch('https://unpkg.com/monaco-editor@0.21.2/esm/vs/base/browser/ui/codicons/codicon/codicon.ttf')
);
}
});
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 (especially 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://github.com/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 :)
This is such a great write up but didn't work for me on Nextjs 10.
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 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?
@alexdima do you work on this? is it possible to help with something?
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: https://github.com/angular/angular-cli/issues/23273
For those of you ending up here finding a solution to have Monaco Editor ESM style work with Angular 14, this might help:
@angular-builders/custom-webpack
to be able to manipulate webpack configcustom-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,
},
}],
},
],
},
};
@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",
@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
For those of you ending up here finding a solution to have Monaco Editor ESM style work with Angular 14, this might help:
- Use
@angular-builders/custom-webpack
to be able to manipulate webpack config- 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 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.
@evtk You can also use "prebuild" and then import as ES module.
Just use this https://github.com/bultas/monaco-component
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.
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://github.com/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"
}
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.
@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.
As Import Attributes are now at stage 3 (again), would it be possible to switch monaco to use valid ES6 Code?
related to #4175
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!
@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 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.
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,
...
In the ESM (EcmaScript Modules) version, css files are being imported like:
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 ;).