cesium
cesium copied to clipboard
Can not import widgets.css file not exported from package.json
I am working on https://github.com/CesiumGS/cesium-webpack-example. This is very simple web pack configured web app.
It works well with webpack 4(original version). But after I update web pack version, it shows following error.
Module not found: Error: Package path ./Build/Cesium/Widgets/widgets.css is not exported from package E:\MyResearch\CesiumJsGIS\node_modules\cesium (see exports fiel d in E:\MyResearch\CesiumJsGIS\node_modules\cesium\package.json)
In a short I could not import css file from node_module.
hope you export widgets.css in package.json.
Hey @3DGISKing I think it would make sense to file an issue to the cesium-webpack-example repository instead 😉
this is actually related with this repo.
@3DGISKing I am not sure if it is indeed related to this repo, (it may have to do with the webpack configuration) but I think that reporting it there would be a good start for filing the issue 😃 . However, either way, looks valid to me 👍
please have a look at this.
https://github.com/webpack/webpack/issues/11787
@3DGISKing i do not know if it is ok to export CSS files through package.json
, the maintainers of Cesium can tell that, but you have always the option of the copy-webpack-plugin
Closing this issue as we have not received a response from the author in a while. Please re-open if there is additional discussion to be made.
Please re-open this issue.
I've never had to set up the exports
field in a package.json
before but apparently it's a pretty big deal and can break a lot of things, especially now that Webpack 5 fully respects it. I'm going by the webpack docs which refer to the node docs here. (cc @kring because he created the current version )
Once you create an exports
field, it has to contain an entry for anything you might ever want to import from the package:
Warning: Introducing the "exports" field prevents consumers of a package from using any entry points that are not defined
This includes if your consumer's build tools need to grab stylesheets (as in the OP), images, JSON data, web workers, anything like that. I haven't spent a lot of time with it but I think at a minimum it should have entries for assets: "./Build/Assets/"
and widgets: "./Build/Widgets/"
. The team might have some ideas about other things "export" explicitly.
Is it possible to add some sort of wildcard export that allows any file in CesiumJS to be imported if desired?
I've been playing with this for the last half-hour or so. I think this might cover everybody:
"exports": {
"./package.json": "./package.json",
"./Assets/": "./Build/Cesium/Assets/",
"./ThirdParty/": "./Build/Cesium/ThirdParty/",
"./Widgets/": "./Build/Cesium/Widgets/",
"./Workers/": "./Build/Cesium/Workers/",
".": {
"require": "./index.cjs",
"import": "./Source/Cesium.js"
}
},
but you could also use "./": "./"
as a fallback, I think. I don't know whether it's better to just expose every file in the package, or carefully consider what you want to expose as the public API of the package. Pros and cons on both sides, I guess.
Also note, though -- Node has deprecated "subpath folder mappings" (like "./path/"
) in favor of "subpath patterns" (like "./path/*"
) but webpack doesn't support that yet.
Hmm interesting. Some apps import Cesium source files directly, too. i.e. right from the Source directory, not from Build. Maybe we just need to add a ./Source/
export to cover that case as well?
This is a tricky question. The package file format has evolved over a relatively long time, which means now you're supporting CJS imports via the main
field, older ESM imports using module
, and newer consumers from recent Node or Webpack 5+ using exports
. I think anybody using the exports
field should be able to support ESM modules, though they might mix in require()
as well, see the dual package hazard section in the Node docs.
If I'm right, it would mean that consumers using import
should always get scripts out of Source, because that's the ESM format they want, so exports
should only provide code out of ./Source/
. (That's only about code, though. The entries I proposed above are all assets -- JSON data, images, CSS -- so getting the possibly-minified version of the asset out of Build makes sense.)
While we're thinking about this: have you considered distributing a processed/minified version of ES modules instead of Source
? Using a structure like that, you could expose individual modules for import like this:
"exports": {
"./package.json": "./package.json",
"./Assets/": "./Build/Cesium/Assets/",
"./Workers/": "./Build/Cesium/Workers/",
"./": "./Build/esm/",
".": {
"require": "./index.cjs",
"import": "./Build/esm/Cesium.js"
}
},
This would let consumers write import Viewer from "cesium/Widgets/Viewer"
and get a tree-shakable minified ESM version of Viewer (and its dependencies).
For what it is worth I agree with @thw0rted above, that config works, and the proposal of distributing a minified version of each ES module is exactly what is needed.
Just for kicks I upgraded my example with c137.js here to Webpack 5 and it still works without a hitch.
Another interesting finding came up just now. Your ./index.cjs
is basically a conditional redirect to give CJS consumers a minified build in a production environment or an unminified build otherwise. In Webpack, you have access to conditional matchers for production
and development
. I believe that means you can use
".": {
"production": {
"require": "./Build/Cesium/Cesium.js",
"import": "./Source/Cesium.js"
},
"development": {
"require": "./Build/CesiumUnminified/Cesium.js",
"import": "./Source/Cesium.js"
},
"node": {
"require": "./index.cjs",
"import": "./Source/Cesium.js"
}
}
},
This is good because it cuts out a runtime decision (i.e. the return value of path.join(...)
in index.cjs
), which is bad for webpack's require
because it's forced to create a context
for dynamic resolution. (TBH I think it's unusual for a package to provide different code based on NODE_ENV
but in my above example you could preserve this behavior, if you like.)
This would let consumers write import Viewer from "cesium/Widgets/Viewer" and get a tree-shakable minified ESM version of Viewer (and its dependencies).
I think this is already possible, at least in Webpack 4. For example: https://github.com/TerriaJS/terriajs/blob/next/lib/ModelMixins/Cesium3dTilesMixin.ts
(the Cesium module is called terriajs-cesium here for... reasons, but it's a fork of Cesium without any major changes).
But there are other environments to consider, like Node.js.
The number of different environments where folks might want to use CesiumJS, some of which seem to be mutually incompatible, has really exploded recently, and I feel I'd need to spend quite a lot of time understanding them all in order to make good decisions here. I don't think I can take that time at the moment, but I'm happy to take pull requests that improve things and demonstrate that the important environments still work. Off the top of my head, some of the axes are:
- CesiumJS release ZIP /
cesium
package on npm / CesiumJS installed by npm from the github repo. - Webpack 4 / Webpack 5 / Rollup / probably a hundred other (less important?) ones.
- Babel / TypeScript / No compiler
- Node.js 10 / Node.js 12 / Node.js 14
-
require
(CommonJS) /import
(ESM) - Requiring or importing
"cesium"
/ Requiring or importing individual source files - What else have I missed?
It's definitely already possible to import a tree-shakable module directly -- the key word in my post was "minified". As far as I can tell, Cesium does not distribute processed ESM yet. To be honest, though, I don't when that's desirable, because as you say there are dozens of permutations of use cases.
If I'm importing ESM but it's going to go through a minification process as part of a (browser-targeted?) build toolchain, I might not care if the package ships minified code, or I might prefer to minify for myself from the original sources. However, if it's available pre-minified, I might be able to reduce my own build time by excluding Cesium from the minification process, which could be a big win.
If I'm importing ESM from a recent version of Node, maybe I don't care about the size of the source because it doesn't have to traverse the network. In that case my only concern about size is total package size (for faster npm install
), which is actually larger if you're going to ship unminified ESM source anyway, plus minified (and unminified?) CJS, and now we're talking about adding a second (minified) copy.
At any rate, I agree that it would take a lot of time to make good decisions, as you say. In the meantime, I think there are non-breaking changes that could be made to better support Webpack 5 specifically, while following the intent of the Bare Module Resolution spec to best convey which version of the code is best for which consumer.
@thw0rted @kring This is the type of use case I created c137.js to address, as a single-file, minified distro. Try it out and let me know what you think.
TJ, I noticed the NPM page for your package does not link to a source repo. Your fork of the cesium repo hasn't been updated in a while. Where is the source for the package hosted?
@thw0rted The source is literally just the Cesium source, the build process is (so far) proprietary.
I do have other source that I use on Mapshot for performance reasons.
Thanks, I might take a look at it at some point, but I'm not planning to tie our build to anything proprietary at this time. For one thing, I'm here on the tracker at least once a week, and I don't want to have to worry about whether the issues I'm having stem from Cesium's original source or from your build process (nothing personal!).
@thw0rted We are always open to contracts / partnerships so you can see the source.
BTW, we do run all the Cesium tests on it and it passes, you can do the same.
Hi @dzungpng , I just came here from #8471 and realized this issue never got re-opened. I believe the exports
field is still not correct and won't work with Webpack 5 until it's updated as I described previously.
Hi @3DGISKing , was this closed because it's been fixed? I haven't seen this issue mentioned in any recent PRs.
I also encountered the same problem when using vite
to package the vanillajs project.
Which the report is:
[vite] Internal server error: Missing "./Source/Widgets/widgets.css" export in "cesium" package
code in main.js:
import { Viewer } from 'cesium'
import './style.css'
import 'cesium/Source/Widgets/widgets.css'
let viewer
const main = () => {
const dom = document.getElementById('app')
viewer = new Viewer(dom)
}
document.addEventListener('DOMContentLoaded', () => {
main()
})
The code above if remove import widgets.css
, which can create viewer, but without style.
Btw, using vite
with reactjs
or vuejs
can import widgets.css
successfully.
I don't think I ever posted this in the rest of the conversation, but the workaround I've been using while I wait for this issue to be resolved is to set Webpack's resolve.exportsFields
to an empty array, which basically causes Webpack to totally ignore the exports
field in every package.json, and fall back to previous behavior. Obviously this has big downsides, but it lets me keep using the stock cesium
until they fix it.
Speaking of which: it looks like, at the end of last year, @kring wanted to have a bit of a think about exactly what needs to be exported. For the intervening ~8 months, the field has remained -- and I don't mean to be a jerk about it! -- wrong. The Cesium-provided example of use via Webpack will not work in Webpack v5 because of this, and while I can't find documented lifecycle support commitments for v4, I wouldn't expect it to be maintained much longer. (The most recent update I can find is a bugfix that got backported in April.)
So: @3DGISKing @lilleyse @kring , would you accept a PR that improves the exports
field as described in the conversation we had last December? I know a lot of your time goes to Unreal stuff these days, but those of us on the web side have been using workarounds for a pretty long time now.
So times, I usually use cesiumjs by cdn but not bundle.
If the functionality in c137.js works for everyone’s use cases, I will see what I can do about open-sourcing the build process. Please let me know if it does what you need it to do.
Method 1
WEBPACK5 Just DELETE exportsFields in cesium/package.json webpack5's subModule enhanced-resolve will follow the rules which cesium set in package.json => exportsFields. And it failed in enhanced-resolve/lib/ExportsFieldPlugin.js
in Webpack4 subModule enhanced-resolve has no ExportsFieldPlugin.js, so guess webpack4 will not fail
Method 2
If you really want to use exportsFields in cesium/package.json for WEBPACK5 You should do like below
import from cesium
import Color from "cesium/Source/Core/Color";
import "cesium/Source/Widgets/widgets.css";
change cesium package.json exports
# package.json
{
"exports": {
"./package.json": "./package.json",
".": {
"require": "./index.cjs",
"import": "./Source/Cesium.js"
},
# add below
"./Source/": "./Source/", ## expose all below Source folder
"./Source/Core": "./Source/Core/", ## expose all below Source/Core
"./Source/Core/Color": "./Source/Core/Color.js", ## just expose Source/Core/Color.js
"./Source/Widgets/widgets.css": "./Source/Widgets/widgets.css" ## just expose widgets.css
}
}
For anyone still struggling with this and wanting to keep Cesium installations vanilla, this was my solution.
My package.json file:
...
"scripts": {
"postinstall": "node ./scripts/cesium-fix.js",
This script can be added to any step as well, such as before running your dev server or prod build for safety.
For ex: "build": "node ./scripts/cesium-fix.js && quasar build"
Fix file ./scripts/cesium-fix.js
:
const fs = require('fs');
const fileName = require.resolve('cesium/package.json');
// try to load the file
try {
const jsonString = fs.readFileSync(fileName);
const file = JSON.parse(jsonString);
// add new field for proper exporting widgets.css
file.exports["./Source/Widgets/widgets.css"] = "./Source/Widgets/widgets.css";
// write the file
fs.writeFile(fileName, JSON.stringify(file), function writeJSON(err) {
if (err) return console.log(err);
console.log('writing to ' + fileName);
});
} catch (err) {
console.log(err);
return;
};
Then in my component file or wherever I'm loading cesium and cesium widgets:
import 'cesium/Source/Widgets/widgets.css';
Hope this helps!
C137.js has all this integrated, and is open source. All you have to do is:
import Cesium from “c137.js”
Agree @prophetw
When I remove "exports"
field in node_modules/cesium/package.json
, import style with import 'cesium/Source/Widgets/widgets.css'
work.
Or when I add some options to "exports"
as below:
{
"exports": {
"./package.json": "./package.json",
".": {
"require": "./index.cjs",
"import": "./Source/Cesium.js"
},
+ "./index.css": "./Source/Widgets/widgets.css"
}
}
I can simply import styles with import 'cesium/index.css'
.
environment
- NodeJS 16.14
- pnpm 6.31.0
- vite 2.8
Using vanillajs.
Hope Cesium team can improve this by changing the "exports"
in package.json.
Adding "./Source/Widgets/widgets.css": "./Source/Widgets/widgets.css"
to cesium
's package.json
in node_modules does the trick.
diff --git a/node_modules/cesium/package.json b/node_modules/cesium/package.json
index d0adea6..144b9c6 100644
--- a/node_modules/cesium/package.json
+++ b/node_modules/cesium/package.json
@@ -37,7 +37,8 @@
".": {
"require": "./index.cjs",
"import": "./Source/Cesium.js"
- }
+ },
+ "./Source/Widgets/widgets.css": "./Source/Widgets/widgets.css"
},
"type": "module",
"devDependencies": {
I used the patch-package
to record and commit the patch locally.
The TLDR is that cesium
needs to update to modern ESM standards.
Hey y'all, I just spent my whole day on what turned out to be an exports
issue so I thought I'd share some findings that might help others (or me!) in the future. The short version is that my tests broke when I did some recent dependency updates, including (among others) major updates to Cesium and Angular. I went down a bunch of blind alleys -- thought it was due to the esbuild
overhaul that shipped recently, that sort of thing -- but it turned out to be a version of the dual-package hazard. (Sorry in advance for the length. Feel free to skip, only really pertinent if you're trying to use CJS in the browser.)
Basically, I consume Cesium classes from Typescript, using ESM-style import {Foo} from "cesium"
. Typescript transpiles this down to an identical ESM import statement in my dev and prod build processes, but tests are different. I run tests through karma+Jasmine, and for historical reasons these are configured to use CJS modules. The problem is that Cesium does not appear to support CommonJS in the browser, probably since the ES6 rewrite back in 2019.
If I run my tests with the tsconfig compiler option module: "CommonJS"
, the import
statements get converted to require
calls. Now, before my last round of updates, I used a Webpack option resolve.exportsFields: []
, to ignore the Cesium exports
field, but I had to remove that workaround. Now, with Webpack respecting the exports["."]["require"]
value, Cesium's index.cjs
is used instead of Source/Cesium.js
. This in turn tries to require ./Build/CesiumUnminified/Cesium.js
but using the Node-specific __dirname
token. I found another workaround for this, only to encounter more Node-isms in the CJS build -- require
s for url
, https
, zlib
, etc. -- so I gave up and realized I had to avoid the CJS package in the first place.
Fortunately, the Webpack docs included a helpful comment: imports with an absolute path, rather than a Node-style package identifier, always resolve according to the path and ignore package.json
completely (including the exports
field). So I set resolve.alias
in my Webpack config to include cesium: resolve("node_modules", "cesium", "Source", "Cesium.js")
. This effectively rewrites import ... from "cesium"
to import ... from "/absolute/path/to/node_modules/cesium/Source/Cesium.js"
, which survives the conversion to require
when emitting with CJS packaging.
So: if you're using CJS in-browser for any reason, the exports
field will try to give you a Node-specific build. Either stop using CJS, or load the browser version (from Source/Cesium.js
) via absolute path.
Reviewing this in light of recent updates (which actually made the requirements around environments bit stricter), here are some thoughts.
For those bundling an app through tools such as Webpack or rollup, Widget
CSS files could be imported from either the Source
folder, or the Build
folder. This depends on whether there is a CSS bundler in place. If so, importing from Source
will give the most flexibility. If not, importing from Build/Cesium
will give the pre-bundled CSS which will "just work" as is. So I think it make sense to support both paths:
"exports": {
"./package.json": "./package.json",
"./Source/*": "./Source/*",
"./Build/*": "./Build/*",
".": {
"require": "./index.cjs",
"import": "./Source/Cesium.js"
}
},
Generally speaking, Assets
, ThirdParty
, and Workers
should only be imported or loaded indirectly by CesiumJS, and not directly into the application. But, if we apply a general pattern like in the above snippet, I don't see why they shouldn't be exposed via a Source
or Build
subpath.
Given that the minimum NodeJS version officially supported is now 14, and that a bundler is now officially required when using Source
JS files directly, the above snippet should work for the JS files in the Source
directory under the each of the environments suggested above. The only exception would be that in CJS, Node will warn about using require
instead of a dynamic import
on Source
modules (which is a good thing in this case because it lets the user know which style to use).
However, loading JS files from the build directory would require a bit of know-how if attempting to use direct paths to certain JS files. For instance, when using CJS, the built .cjs
files should be used instead of .js
. However, these use cases are detailed in the Build Guide, so it may be worth the flexibility here.
If we don't want to allow that flexibility, and we want to narrow this down to just the CSS files for the sake of the original use case in this issue, this could become:
"exports": {
"./package.json": "./package.json",
"./Source/*": "./Source/*",
"./Build/*.css": "./Build/*.css",
".": {
"require": "./index.cjs",
"import": "./Source/Cesium.js"
}
},
Am I missing anything?
On a slightly tangential note, I think based on the node JS documentation, to avoid the runtime logic in the root index.cjs
, we can change the root export to the following:
".": {
"production": {
"require": "./Build/Cesium/index.cjs",
"import": "./Source/Cesium.js"
},
"require": "./Build/CesiumUnminified/index.cjs",
"import": "./Source/Cesium.js"
}
@thw0rted For your particular use case, it sounds like setting the exports.browser
field would help, but I'm not sure if that has other implications outside of webpack.
First, I like the idea of user-conditions to get rid of index.cjs, but while we're at it, why have the unminified option in the first place? Does anybody consuming this lack support for sourcemaps? Would it be possible / desirable to just have one Build directory that includes minified JS plus a sourcemap for everything? Probably an issue for another ticket.
Also under User Conditions, it says that you can specify
{
"/Source/*": {
"require": null,
"import": "./Source/*"
}
}
which would result in require("cesium/Source/Foo.js")
failing immediately with ERR_PACKAGE_PATH_NOT_EXPORTED
, rather than a warning.
Second, Typescript recently made a big, somewhat controversial change to how they handle module resolution. They now always look for type definitions according to module-resolution logic. So if import ... from "cesium"
would use exports["."]["import"]
to resolve to ./Source/Cesium.js
, I believe then TS will look for exports["."]["types"]
-- critically, it pays no attention at all to the top level "types" key, which is now only used to match top level main
/ module
. If there's no sibling types
key under the matched exports
field, it will fall back to looking for a sibling of the file that the matching exports.foo.import
key points to (in the example, ./Source/Cesium.d.ts
) automatically. (TS would like you to include a types
key, and if you do it must come first.)
This means your proposal would work fine for a top level import ... from "cesium"
, but, but I'm not sure how it would handle import Foo from "cesium/Source/Foo.js"
. As I understand the logic, it would look for exports["./Source/*"]["types"]
, if the Source/*
wildcard had an object value instead of a string. Failing that, it would resolve exports["./Source/*"]["import"]
, then look for the sibling ./Source/Foo.d.ts
, which is not created by the current type generation process. I don't know if it would ever try to fall back to the ambient module declaration in ./Source/Cesium.d.ts
. Maybe just parsing the JSDoc it finds in Foo.js
would be sufficient? I'd want to test that behavior to be sure.
Re: exports.browser
, I just tested the below and it does fix my problem:
".": {
"browser": {
"require": "./Source/Cesium.js"
},
"require": "./index.cjs",
"import": "./Source/Cesium.js"
}
As you say, this may have ramifications for other build tools that support CJS but don't support ESM interop the way Webpack does. I just tried it with webpack
instead of browser
, and that works as well, so you have the option to limit this behavior to one specific tool -- of course, there are pros and cons to that. (Does somebody run tests in browser via CJS, with an intermediate esbuild
or rollup
step?)
Thanks for pointing that out @thw0rted. To side step the types
issue, we could restrict the exports to just CSS files for now since that seems to be the core of this issue.
I guess as a first pass that makes sense, but I thought part of the point of repackaging all the source in ESM was that people can import directly from cesium/Source/Foo.js
, and thus benefit from tree-shaking? If you don't expose your ESM source files via exports
, that won't be possible (with modern tooling).
This goes back to some old posts upthread -- what kind of public API does Cesium really want to provide? Are you going to have docs with examples that import
from Source?
I thought part of the point of repackaging all the source in ESM was that people can import directly from cesium/Source/Foo.js, and thus benefit from tree-shaking? If you don't expose your ESM source files via exports , that won't be possible (with modern tooling).
Since all it does is export other modules, you can get the benefit of tree shaking by using Source/Cesium.js
(the default) as is as long as you are importing the individual modules, like:
import { Viewer } from "cesium";
Webpack specifically looks for the sideEffects
field in package.json
. So for this specific case, we could potentially add
"sideEffects": ["./Source/Assets/*", "./Source/ThirdParty/*", "./Source/Widgets/*", "./Source/Workers/*"]
but I think that is out of scope for this particular issue.
This goes back to some old posts upthread -- what kind of public API does Cesium really want to provide? Are you going to have docs with examples that import from Source?
At the moment all of our documentation uses IIFE-style global variables (Cesium.Viewer
) for ease-of-use with a minimal, non-bundler setup. It works in Sandcastle and for folks using a CDN via script tag. In the future, we may explore multiple include styles (for instance, how the Node documentation has an ESM/CJS toggle switch) but again, I think that is out of scope of this issue.