diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index a654eae9b3b0..b13d2a07579d 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -2,7 +2,7 @@ name: deploy on: push: branches-ignore: - - 'cesium.com' + - "cesium.com" - production concurrency: group: deploy-${{ github.ref }} @@ -22,14 +22,13 @@ jobs: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_REPO: ${{ github.repository }} GITHUB_SHA: ${{ github.sha }} - BASE_URL: /cesium/${{ github.ref_name }}/ DEPLOYED_URL: https://ci-builds.cesium.com/cesium/${{ github.ref_name }}/ steps: - uses: actions/checkout@v5 - name: install node 22 uses: actions/setup-node@v6 with: - node-version: '22' + node-version: "22" - name: npm install run: npm install - name: set the version in package.json @@ -42,8 +41,6 @@ jobs: run: npm pack --workspaces &> /dev/null - name: build apps run: npm run build-apps - - name: build sandcastle v2 - run: npm run build-ci -w packages/sandcastle -- -l warn - uses: ./.github/actions/verify-package - name: deploy to s3 if: ${{ env.AWS_ACCESS_KEY_ID != '' }} diff --git a/.github/workflows/prod.yml b/.github/workflows/prod.yml index a55af6b0a458..abc62b7c734e 100644 --- a/.github/workflows/prod.yml +++ b/.github/workflows/prod.yml @@ -41,14 +41,17 @@ jobs: run: npm install - name: build website release run: npm run website-release - - name: build apps - run: npm run build-apps - name: build types run: npm run build-ts - - name: build prod sandcastle - run: npm run build-prod -w packages/sandcastle -- -l warn + - name: build apps + run: npm run build-apps - name: deploy to cesium.com if: ${{ env.AWS_ACCESS_KEY_ID != '' }} + # Download zip from the Github release and unzip to Build/release/ + # Publish that unzipped code to the bucket for https://cesium.com/downloads/cesiumjs/releases/[version]/... urls + # Publish the documentation files to the bucket for https://cesium.com/learn/cesiumjs/ref-doc/ + # Publish the simple viewer app to the bucket for https://cesium.com/cesiumjs/cesium-viewer/ + # Publish sandcastle to the bucket for https://sandcastle.cesium.com/ run: | curl -LO $(curl https://api.github.com/repos/CesiumGS/cesium/releases/latest -H "Authorization: ${GITHUB_TOKEN}" | jq -r '.assets[0].browser_download_url') unzip Cesium-$(cat package.json | jq -r '.version' | sed 's/\.0$//').zip -d Build/release/ -x "Apps" diff --git a/.github/workflows/sandcastle-dev.yml b/.github/workflows/sandcastle-dev.yml index 522b39349dce..7455552080b7 100644 --- a/.github/workflows/sandcastle-dev.yml +++ b/.github/workflows/sandcastle-dev.yml @@ -2,7 +2,7 @@ name: sandcastle-dev on: push: branches: - - 'cesium.com' + - "main" jobs: deploy: runs-on: ubuntu-latest @@ -20,7 +20,7 @@ jobs: - name: install node 22 uses: actions/setup-node@v6 with: - node-version: '22' + node-version: "22" - name: npm install run: npm install - name: build website release @@ -28,7 +28,7 @@ jobs: - name: build types run: npm run build-ts - name: build prod sandcastle - run: npm run build-prod -w packages/sandcastle -- -l warn + run: npm run build-sandcastle - name: deploy to dev-sandcastle.cesium.com if: ${{ env.AWS_ACCESS_KEY_ID != '' }} run: | diff --git a/.npmignore b/.npmignore index c5ee4a9faf67..627f0a708416 100644 --- a/.npmignore +++ b/.npmignore @@ -23,6 +23,8 @@ /scripts/ /favicon.ico /gulpfile.js +/gulpfile.apps.js +/gulpfile.makezip.js /index.html /index.release.html /launches diff --git a/Documentation/Contributors/BuildGuide/README.md b/Documentation/Contributors/BuildGuide/README.md index 3471d2d628d2..89c45b3cd5a8 100644 --- a/Documentation/Contributors/BuildGuide/README.md +++ b/Documentation/Contributors/BuildGuide/README.md @@ -77,7 +77,7 @@ npm start Then browse to [http://localhost:8080/](http://localhost:8080/). The landing page includes apps and tools commonly used during development, including: - **Hello World** : an example for how to create a 3D globe. [Tutorial here](https://cesium.com/learn/cesiumjs-learn/cesiumjs-quickstart/) -- **Sandcastle** : an app for viewing and creating [code examples](https://sandcastle.cesium.com?src=Hello%20World.html&label=Showcases), complete with a live preview +- **Sandcastle** : an app for viewing and creating [code examples](https://sandcastle.cesium.com), complete with a live preview - **Test Suites** : tests using [Jasmine](https://jasmine.github.io/). [Testing guide here.](https://github.com/CesiumGS/cesium/blob/main/Documentation/Contributors/TestingGuide/README.md#testing-guide) - **Documentation** : reference documentation built from source. [Documentation guide here.](https://github.com/CesiumGS/cesium/blob/main/Documentation/Contributors/DocumentationGuide/README.md#documentation-guide) diff --git a/eslint.config.js b/eslint.config.js index e6f511e410b5..70c1a4e81fd2 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -48,6 +48,8 @@ export default [ "scripts/**/*.js", "packages/sandcastle/scripts/**/*.js", "gulpfile.js", + "gulpfile.apps.js", + "gulpfile.makezip.js", "server.js", ], ...configCesium.configs.node, diff --git a/gulpfile.apps.js b/gulpfile.apps.js new file mode 100644 index 000000000000..ad8cef320ede --- /dev/null +++ b/gulpfile.apps.js @@ -0,0 +1,252 @@ +import { join } from "path"; +import { finished } from "stream/promises"; + +import gulp from "gulp"; +import gulpReplace from "gulp-replace"; +import { buildSandcastleApp } from "./scripts/buildSandcastle.js"; +import { mkdirp } from "mkdirp"; +import { bundleWorkers, defaultESBuildOptions } from "./scripts/build.js"; +import { build as esbuild } from "esbuild"; + +const isProduction = process.env.PROD === "true"; + +// Print an esbuild warning +function printBuildWarning({ location, text }) { + const { column, file, line, lineText, suggestion } = location; + + let message = `\n + > ${file}:${line}:${column}: warning: ${text} + ${lineText} + `; + + if (suggestion && suggestion !== "") { + message += `\n${suggestion}`; + } + + console.log(message); +} + +// Ignore `eval` warnings in third-party code we don't have control over +function handleBuildWarnings(result) { + for (const warning of result.warnings) { + if ( + !warning.location.file.includes("protobufjs.js") && + !warning.location.file.includes("Build/Cesium") + ) { + printBuildWarning(warning); + } + } +} + +async function buildLegacySandcastle() { + const streams = []; + let appStream = gulp.src( + [ + "Apps/Sandcastle/**", + "!Apps/Sandcastle/load-cesium-es6.js", + "!Apps/Sandcastle/images/**", + "!Apps/Sandcastle/gallery/**.jpg", + ], + { + encoding: false, + }, + ); + + if (isProduction) { + // Remove swap out ESM modules for the IIFE build + appStream = appStream + .pipe( + gulpReplace( + ' ', + ' \n' + + ' ', + ), + ) + .pipe( + gulpReplace( + ' ', + ' \n' + + ' ', + ), + ) + // Fix relative paths for new location + .pipe(gulpReplace("../../../Build", "..")) + .pipe(gulpReplace("../../../Source", "../CesiumUnminified")) + .pipe(gulpReplace("../../Source", ".")) + .pipe(gulpReplace("../../../ThirdParty", "./ThirdParty")) + .pipe(gulpReplace("../../ThirdParty", "./ThirdParty")) + .pipe(gulpReplace("../ThirdParty", "./ThirdParty")) + .pipe(gulpReplace("../Apps/Sandcastle", ".")) + .pipe(gulpReplace("../../SampleData", "../SampleData")) + .pipe( + gulpReplace("../../Build/Documentation", "/learn/cesiumjs/ref-doc/"), + ) + .pipe(gulp.dest("Build/Sandcastle")); + } else { + // Remove swap out ESM modules for the IIFE build + appStream = appStream + .pipe( + gulpReplace( + ' ', + ' \n' + + ' ', + ), + ) + .pipe( + gulpReplace( + ' ', + ' \n' + + ' ', + ), + ) + // Fix relative paths for new location + .pipe(gulpReplace("../../../Build", "../../..")) + .pipe(gulpReplace("../../Source", "../../../Source")) + .pipe(gulpReplace("../../ThirdParty", "../../../ThirdParty")) + .pipe(gulpReplace("../../SampleData", "../../../../Apps/SampleData")) + .pipe(gulpReplace("Build/Documentation", "Documentation")) + .pipe(gulp.dest("Build/Apps/Sandcastle")); + } + streams.push(appStream); + + let imageStream = gulp.src( + ["Apps/Sandcastle/gallery/**.jpg", "Apps/Sandcastle/images/**"], + { + base: "Apps/Sandcastle", + encoding: false, + }, + ); + if (isProduction) { + imageStream = imageStream.pipe(gulp.dest("Build/Sandcastle")); + } else { + imageStream = imageStream.pipe(gulp.dest("Build/Apps/Sandcastle")); + } + streams.push(imageStream); + + if (isProduction) { + const fileStream = gulp + .src(["ThirdParty/**"], { encoding: false }) + .pipe(gulp.dest("Build/Sandcastle/ThirdParty")); + streams.push(fileStream); + + const dataStream = gulp + .src(["Apps/SampleData/**"], { encoding: false }) + .pipe(gulp.dest("Build/Sandcastle/SampleData")); + streams.push(dataStream); + } + + let standaloneStream = gulp + .src(["Apps/Sandcastle/standalone.html"]) + .pipe(gulpReplace("../../../", ".")) + .pipe( + gulpReplace( + ' ', + ' \n' + + ' ', + ), + ) + .pipe(gulpReplace("../../Build", ".")); + if (isProduction) { + standaloneStream = standaloneStream.pipe(gulp.dest("Build/Sandcastle")); + } else { + standaloneStream = standaloneStream.pipe( + gulp.dest("Build/Apps/Sandcastle"), + ); + } + streams.push(standaloneStream); + + return Promise.all(streams.map((s) => finished(s))); +} + +async function buildCesiumViewer() { + const cesiumViewerOutputDirectory = isProduction + ? "Build/CesiumViewer" + : "Build/Apps/CesiumViewer"; + mkdirp.sync(cesiumViewerOutputDirectory); + + const config = defaultESBuildOptions(); + config.entryPoints = [ + "Apps/CesiumViewer/CesiumViewer.js", + "Apps/CesiumViewer/CesiumViewer.css", + ]; + config.bundle = true; // Tree-shaking is enabled automatically + config.minify = true; + config.loader = { + ".gif": "text", + ".png": "text", + }; + config.format = "iife"; + // Configure Cesium base path to use built + config.define = { CESIUM_BASE_URL: `"."` }; + config.outdir = cesiumViewerOutputDirectory; + config.outbase = "Apps/CesiumViewer"; + config.logLevel = "error"; // print errors immediately, and collect warnings so we can filter out known ones + const result = await esbuild(config); + + handleBuildWarnings(result); + + await esbuild({ + entryPoints: ["packages/widgets/Source/InfoBox/InfoBoxDescription.css"], + minify: true, + bundle: true, + loader: { + ".gif": "text", + ".png": "text", + }, + outdir: join(cesiumViewerOutputDirectory, "Widgets"), + outbase: "packages/widgets/Source/", + }); + + await bundleWorkers({ + minify: true, + removePragmas: true, + path: cesiumViewerOutputDirectory, + }); + + const stream = gulp + .src( + [ + "Apps/CesiumViewer/**", + "!Apps/CesiumViewer/Images", + "!Apps/CesiumViewer/**/*.js", + "!Apps/CesiumViewer/**/*.css", + ], + { + encoding: false, + }, + ) + .pipe( + gulp.src( + [ + "Build/Cesium/Assets/**", + "Build/Cesium/Workers/**", + "Build/Cesium/ThirdParty/**", + "Build/Cesium/Widgets/**", + "!Build/Cesium/Widgets/**/*.css", + ], + { + base: "Build/Cesium", + nodir: true, + encoding: false, + }, + ), + ) + .pipe(gulp.src(["web.config"])) + .pipe(gulp.dest(cesiumViewerOutputDirectory)); + + await finished(stream); + return stream; +} + +export async function buildSandcastle() { + return buildSandcastleApp({ + outputToBuildDir: isProduction, + includeDevelopment: !isProduction, + }); +} + +export const buildApps = gulp.parallel( + buildCesiumViewer, + buildLegacySandcastle, + buildSandcastle, +); diff --git a/gulpfile.js b/gulpfile.js index 28297313b758..e24d11e6b22d 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -7,14 +7,9 @@ import { createRequire } from "module"; import { finished } from "stream/promises"; import gulp from "gulp"; -import gulpTap from "gulp-tap"; -import gulpZip from "gulp-zip"; -import gulpRename from "gulp-rename"; -import gulpReplace from "gulp-replace"; import { globby } from "globby"; import open from "open"; import { rimraf } from "rimraf"; -import { mkdirp } from "mkdirp"; import karma from "karma"; import yargs from "yargs"; import typeScript from "typescript"; @@ -29,7 +24,6 @@ import { glslToJavaScript, createCombinedSpecList, createJsHintOptions, - defaultESBuildOptions, } from "./scripts/build.js"; // Determines the scope of the workspace packages. If the scope is set to cesium, the workspaces should be @cesium/engine. @@ -55,7 +49,6 @@ function getWorkspaces(onlyDependencies = false) { } const devDeployUrl = process.env.DEPLOYED_URL; -const isProduction = process.env.PROD; //Gulp doesn't seem to have a way to get the currently running tasks for setting //per-task variables. We use the command line argument here to detect which task is being run. @@ -92,34 +85,6 @@ const shaderFiles = [ "packages/engine/Source/ThirdParty/Shaders/*.glsl", ]; -// Print an esbuild warning -function printBuildWarning({ location, text }) { - const { column, file, line, lineText, suggestion } = location; - - let message = `\n - > ${file}:${line}:${column}: warning: ${text} - ${lineText} - `; - - if (suggestion && suggestion !== "") { - message += `\n${suggestion}`; - } - - console.log(message); -} - -// Ignore `eval` warnings in third-party code we don't have control over -function handleBuildWarnings(result) { - for (const warning of result.warnings) { - if ( - !warning.location.file.includes("protobufjs.js") && - !warning.location.file.includes("Build/Cesium") - ) { - printBuildWarning(warning); - } - } -} - export async function build() { // Configure build options from command line arguments. const minify = argv.minify ?? false; @@ -287,10 +252,6 @@ export async function buildTs() { await createTypeScriptDefinitions(); } -export function buildApps() { - return Promise.all([buildCesiumViewer(), buildSandcastle()]); -} - const filesToClean = [ "Source/Cesium.js", "Source/Shaders/**/*.js", @@ -520,184 +481,6 @@ export const postversion = async function () { return Promise.all(promises); }; -/** - * Removes scripts from package.json files to ensure that - * they still work when run from within the ZIP file. - * - * @param {string} packageJsonPath The path to the package.json. - * @returns {WritableStream} A stream that writes to the updated package.json file. - */ -async function pruneScriptsForZip(packageJsonPath) { - // Read the contents of the file. - const contents = await readFile(packageJsonPath); - const contentsJson = JSON.parse(contents); - - const scripts = contentsJson.scripts; - - // Remove prepare step from package.json to avoid running "prepare" an extra time. - delete scripts.prepare; - - // Remove build and transform tasks since they do not function as intended from within the release zip - delete scripts.build; - delete scripts["build-release"]; - delete scripts["build-watch"]; - delete scripts["build-ts"]; - delete scripts["build-third-party"]; - delete scripts["build-apps"]; - delete scripts["build-sandcastle"]; - delete scripts.clean; - delete scripts.cloc; - delete scripts["build-docs"]; - delete scripts["build-docs-watch"]; - delete scripts["make-zip"]; - delete scripts.release; - delete scripts.prettier; - - // Remove deploy tasks - delete scripts["deploy-status"]; - delete scripts["deploy-set-version"]; - delete scripts["website-release"]; - - // Set server tasks to use production flag - scripts["start"] = "node server.js --production"; - scripts["start-public"] = "node server.js --public --production"; - scripts["start-public"] = "node server.js --public --production"; - scripts["test"] = "gulp test --production"; - scripts["test-all"] = "gulp test --all --production"; - scripts["test-webgl"] = "gulp test --include WebGL --production"; - scripts["test-non-webgl"] = "gulp test --exclude WebGL --production"; - scripts["test-webgl-validation"] = "gulp test --webglValidation --production"; - scripts["test-webgl-stub"] = "gulp test --webglStub --production"; - scripts["test-release"] = "gulp test --release --production"; - - // Write to a temporary package.json file. - const noPreparePackageJson = join( - dirname(packageJsonPath), - "package.noprepare.json", - ); - await writeFile(noPreparePackageJson, JSON.stringify(contentsJson, null, 2)); - - return gulp.src(noPreparePackageJson, { - base: ".", - }); -} - -export const makeZip = gulp.series(release, async function createZipFile() { - //For now we regenerate the JS glsl to force it to be unminified in the release zip - //See https://github.com/CesiumGS/cesium/pull/3106#discussion_r42793558 for discussion. - await glslToJavaScript(false, "Build/minifyShaders.state", "engine"); - - const packageJsonSrc = await pruneScriptsForZip("package.json"); - const enginePackageJsonSrc = await pruneScriptsForZip( - "packages/engine/package.json", - ); - const widgetsPackageJsonSrc = await pruneScriptsForZip( - "packages/widgets/package.json", - ); - - const src = gulp - .src("index.release.html") - .pipe( - gulpRename((file) => { - if (file.basename === "index.release") { - file.basename = "index"; - } - }), - ) - .pipe(enginePackageJsonSrc) - .pipe(widgetsPackageJsonSrc) - .pipe(packageJsonSrc) - .pipe( - gulpRename((file) => { - if (file.basename === "package.noprepare") { - file.basename = "package"; - } - }), - ) - .pipe( - gulp.src( - [ - "Build/Cesium/**", - "Build/CesiumUnminified/**", - "Build/Documentation/**", - "Build/Specs/**", - "Build/package.json", - "packages/engine/Build/**", - "packages/widgets/Build/**", - "!Build/Specs/e2e/**", - "!Build/InlineWorkers.js", - "!packages/engine/Build/Specs/**", - "!packages/widgets/Build/Specs/**", - "!packages/engine/Build/minifyShaders.state", - ], - { - encoding: false, - base: ".", - }, - ), - ) - .pipe( - gulp.src( - [ - "Apps/**", - "Apps/Sandcastle/.jshintrc", - "packages/engine/index.js", - "packages/engine/index.d.ts", - "packages/engine/LICENSE.md", - "packages/engine/README.md", - "packages/engine/Source/**", - "packages/widgets/index.js", - "packages/widgets/index.d.ts", - "packages/widgets/LICENSE.md", - "packages/widgets/README.md", - "packages/widgets/Source/**", - "Source/**", - "Specs/**", - "ThirdParty/**", - "scripts/**", - "favicon.ico", - ".prettierignore", - "eslint.config.js", - "gulpfile.js", - "server.js", - "index.cjs", - "LICENSE.md", - "CHANGES.md", - "README.md", - "web.config", - "!**/*.gitignore", - "!Specs/e2e/*-snapshots/**", - "!Apps/Sandcastle/gallery/development/**", - "!Apps/Sandcastle2/**", - ], - { - encoding: false, - base: ".", - }, - ), - ) - .pipe( - gulpTap(function (file) { - // Work around an issue with gulp-zip where archives generated on Windows do - // not properly have their directory executable mode set. - // see https://github.com/sindresorhus/gulp-zip/issues/64#issuecomment-205324031 - if (file.isDirectory()) { - file.stat.mode = parseInt("40777", 8); - } - }), - ) - .pipe(gulpZip(`Cesium-${version}.zip`)) - .pipe(gulp.dest(".")); - - await finished(src); - - rimraf.sync("./package.noprepare.json"); - rimraf.sync("./packages/engine/package.noprepare.json"); - rimraf.sync("./packages/widgets/package.noprepare.json"); - - return src; -}); - export async function deploySetVersion() { const buildVersion = argv.buildVersion; if (buildVersion) { @@ -1239,13 +1022,6 @@ function generateTypeScriptDefinitions( "raiseEvent(...arguments: Parameters): void;", ); - // Wrap the source to actually be inside of a declared cesium module - // and add any workaround and private utility types. - source = `declare module "@${scope}/${workspaceName}" { -${source} -} -`; - if (importModules) { let imports = ""; Object.keys(importModules).forEach((workspace) => { @@ -1259,6 +1035,13 @@ ${source} source = imports + source; } + // Wrap the source to actually be inside of a declared cesium module + // and add any workaround and private utility types. + source = `declare module "@${scope}/${workspaceName}" { +${source} +} +`; + // Write the final source file back out writeFileSync(definitionsPath, source); @@ -1630,203 +1413,3 @@ export async function buildThirdParty() { return writeFile("ThirdParty.json", JSON.stringify(licenseJson, null, 2)); } - -async function buildSandcastle() { - const streams = []; - let appStream = gulp.src( - [ - "Apps/Sandcastle/**", - "!Apps/Sandcastle/load-cesium-es6.js", - "!Apps/Sandcastle/images/**", - "!Apps/Sandcastle/gallery/**.jpg", - ], - { - encoding: false, - }, - ); - - if (isProduction) { - // Remove swap out ESM modules for the IIFE build - appStream = appStream - .pipe( - gulpReplace( - ' ', - ' \n' + - ' ', - ), - ) - .pipe( - gulpReplace( - ' ', - ' \n' + - ' ', - ), - ) - // Fix relative paths for new location - .pipe(gulpReplace("../../../Build", "..")) - .pipe(gulpReplace("../../../Source", "../CesiumUnminified")) - .pipe(gulpReplace("../../Source", ".")) - .pipe(gulpReplace("../../../ThirdParty", "./ThirdParty")) - .pipe(gulpReplace("../../ThirdParty", "./ThirdParty")) - .pipe(gulpReplace("../ThirdParty", "./ThirdParty")) - .pipe(gulpReplace("../Apps/Sandcastle", ".")) - .pipe(gulpReplace("../../SampleData", "../SampleData")) - .pipe( - gulpReplace("../../Build/Documentation", "/learn/cesiumjs/ref-doc/"), - ) - .pipe(gulp.dest("Build/Sandcastle")); - } else { - // Remove swap out ESM modules for the IIFE build - appStream = appStream - .pipe( - gulpReplace( - ' ', - ' \n' + - ' ', - ), - ) - .pipe( - gulpReplace( - ' ', - ' \n' + - ' ', - ), - ) - // Fix relative paths for new location - .pipe(gulpReplace("../../../Build", "../../..")) - .pipe(gulpReplace("../../Source", "../../../Source")) - .pipe(gulpReplace("../../ThirdParty", "../../../ThirdParty")) - .pipe(gulpReplace("../../SampleData", "../../../../Apps/SampleData")) - .pipe(gulpReplace("Build/Documentation", "Documentation")) - .pipe(gulp.dest("Build/Apps/Sandcastle")); - } - streams.push(appStream); - - let imageStream = gulp.src( - ["Apps/Sandcastle/gallery/**.jpg", "Apps/Sandcastle/images/**"], - { - base: "Apps/Sandcastle", - encoding: false, - }, - ); - if (isProduction) { - imageStream = imageStream.pipe(gulp.dest("Build/Sandcastle")); - } else { - imageStream = imageStream.pipe(gulp.dest("Build/Apps/Sandcastle")); - } - streams.push(imageStream); - - if (isProduction) { - const fileStream = gulp - .src(["ThirdParty/**"], { encoding: false }) - .pipe(gulp.dest("Build/Sandcastle/ThirdParty")); - streams.push(fileStream); - - const dataStream = gulp - .src(["Apps/SampleData/**"], { encoding: false }) - .pipe(gulp.dest("Build/Sandcastle/SampleData")); - streams.push(dataStream); - } - - let standaloneStream = gulp - .src(["Apps/Sandcastle/standalone.html"]) - .pipe(gulpReplace("../../../", ".")) - .pipe( - gulpReplace( - ' ', - ' \n' + - ' ', - ), - ) - .pipe(gulpReplace("../../Build", ".")); - if (isProduction) { - standaloneStream = standaloneStream.pipe(gulp.dest("Build/Sandcastle")); - } else { - standaloneStream = standaloneStream.pipe( - gulp.dest("Build/Apps/Sandcastle"), - ); - } - streams.push(standaloneStream); - - return Promise.all(streams.map((s) => finished(s))); -} - -async function buildCesiumViewer() { - const cesiumViewerOutputDirectory = isProduction - ? "Build/CesiumViewer" - : "Build/Apps/CesiumViewer"; - mkdirp.sync(cesiumViewerOutputDirectory); - - const config = defaultESBuildOptions(); - config.entryPoints = [ - "Apps/CesiumViewer/CesiumViewer.js", - "Apps/CesiumViewer/CesiumViewer.css", - ]; - config.bundle = true; // Tree-shaking is enabled automatically - config.minify = true; - config.loader = { - ".gif": "text", - ".png": "text", - }; - config.format = "iife"; - // Configure Cesium base path to use built - config.define = { CESIUM_BASE_URL: `"."` }; - config.outdir = cesiumViewerOutputDirectory; - config.outbase = "Apps/CesiumViewer"; - config.logLevel = "error"; // print errors immediately, and collect warnings so we can filter out known ones - const result = await esbuild(config); - - handleBuildWarnings(result); - - await esbuild({ - entryPoints: ["packages/widgets/Source/InfoBox/InfoBoxDescription.css"], - minify: true, - bundle: true, - loader: { - ".gif": "text", - ".png": "text", - }, - outdir: join(cesiumViewerOutputDirectory, "Widgets"), - outbase: "packages/widgets/Source/", - }); - - await bundleWorkers({ - minify: true, - removePragmas: true, - path: cesiumViewerOutputDirectory, - }); - - const stream = gulp - .src( - [ - "Apps/CesiumViewer/**", - "!Apps/CesiumViewer/Images", - "!Apps/CesiumViewer/**/*.js", - "!Apps/CesiumViewer/**/*.css", - ], - { - encoding: false, - }, - ) - .pipe( - gulp.src( - [ - "Build/Cesium/Assets/**", - "Build/Cesium/Workers/**", - "Build/Cesium/ThirdParty/**", - "Build/Cesium/Widgets/**", - "!Build/Cesium/Widgets/**/*.css", - ], - { - base: "Build/Cesium", - nodir: true, - encoding: false, - }, - ), - ) - .pipe(gulp.src(["web.config"])) - .pipe(gulp.dest(cesiumViewerOutputDirectory)); - - await finished(stream); - return stream; -} diff --git a/gulpfile.makezip.js b/gulpfile.makezip.js new file mode 100644 index 000000000000..3d136e609ed7 --- /dev/null +++ b/gulpfile.makezip.js @@ -0,0 +1,206 @@ +import { dirname, join } from "path"; +import gulp from "gulp"; +import gulpTap from "gulp-tap"; +import gulpZip from "gulp-zip"; +import gulpRename from "gulp-rename"; +import { glslToJavaScript } from "./scripts/build.js"; +import { release } from "./gulpfile.js"; +import { readFile, writeFile } from "fs/promises"; +import { rimraf } from "rimraf"; +import { finished } from "stream/promises"; +import { createRequire } from "module"; +import { buildSandcastleApp } from "./scripts/buildSandcastle.js"; + +const require = createRequire(import.meta.url); +const packageJson = require("./package.json"); +let version = packageJson.version; +if (/\.0$/.test(version)) { + version = version.substring(0, version.length - 2); +} + +/** + * Removes scripts from package.json files to ensure that + * they still work when run from within the ZIP file. + * + * @param {string} packageJsonPath The path to the package.json. + * @returns {WritableStream} A stream that writes to the updated package.json file. + */ +async function pruneScriptsForZip(packageJsonPath) { + // Read the contents of the file. + const contents = await readFile(packageJsonPath); + const contentsJson = JSON.parse(contents); + + const scripts = contentsJson.scripts; + + // Remove prepare step from package.json to avoid running "prepare" an extra time. + delete scripts.prepare; + + // Remove build and transform tasks since they do not function as intended from within the release zip + delete scripts.build; + delete scripts["build-release"]; + delete scripts["build-watch"]; + delete scripts["build-ts"]; + delete scripts["build-third-party"]; + delete scripts["build-apps"]; + delete scripts["build-sandcastle"]; + delete scripts.clean; + delete scripts.cloc; + delete scripts["build-docs"]; + delete scripts["build-docs-watch"]; + delete scripts["make-zip"]; + delete scripts.release; + delete scripts.prettier; + + // Remove deploy tasks + delete scripts["deploy-status"]; + delete scripts["deploy-set-version"]; + delete scripts["website-release"]; + + // Set server tasks to use production flag + scripts["start"] = "node server.js --production"; + scripts["start-public"] = "node server.js --public --production"; + scripts["start-public"] = "node server.js --public --production"; + scripts["test"] = "gulp test --production"; + scripts["test-all"] = "gulp test --all --production"; + scripts["test-webgl"] = "gulp test --include WebGL --production"; + scripts["test-non-webgl"] = "gulp test --exclude WebGL --production"; + scripts["test-webgl-validation"] = "gulp test --webglValidation --production"; + scripts["test-webgl-stub"] = "gulp test --webglStub --production"; + scripts["test-release"] = "gulp test --release --production"; + + // Write to a temporary package.json file. + const noPreparePackageJson = join( + dirname(packageJsonPath), + "package.noprepare.json", + ); + await writeFile(noPreparePackageJson, JSON.stringify(contentsJson, null, 2)); + + return gulp.src(noPreparePackageJson, { + base: ".", + }); +} + +export const makeZip = gulp.series( + release, + async function buildSandcastleStep() { + return buildSandcastleApp({ + outputToBuildDir: false, + includeDevelopment: false, + }); + }, + async function createZipFile() { + //For now we regenerate the JS glsl to force it to be unminified in the release zip + //See https://github.com/CesiumGS/cesium/pull/3106#discussion_r42793558 for discussion. + await glslToJavaScript(false, "Build/minifyShaders.state", "engine"); + + const packageJsonSrc = await pruneScriptsForZip("package.json"); + const enginePackageJsonSrc = await pruneScriptsForZip( + "packages/engine/package.json", + ); + const widgetsPackageJsonSrc = await pruneScriptsForZip( + "packages/widgets/package.json", + ); + + const src = gulp + .src("index.release.html") + .pipe( + gulpRename((file) => { + if (file.basename === "index.release") { + file.basename = "index"; + } + }), + ) + .pipe(enginePackageJsonSrc) + .pipe(widgetsPackageJsonSrc) + .pipe(packageJsonSrc) + .pipe( + gulpRename((file) => { + if (file.basename === "package.noprepare") { + file.basename = "package"; + } + }), + ) + .pipe( + gulp.src( + [ + "Build/Cesium/**", + "Build/CesiumUnminified/**", + "Build/Documentation/**", + "Build/Specs/**", + "Build/package.json", + "packages/engine/Build/**", + "packages/widgets/Build/**", + "!Build/Specs/e2e/**", + "!Build/InlineWorkers.js", + "!packages/engine/Build/Specs/**", + "!packages/widgets/Build/Specs/**", + "!packages/engine/Build/minifyShaders.state", + ], + { + encoding: false, + base: ".", + }, + ), + ) + .pipe( + gulp.src( + [ + "Apps/**", + "Apps/Sandcastle/.jshintrc", + "packages/engine/index.js", + "packages/engine/index.d.ts", + "packages/engine/LICENSE.md", + "packages/engine/README.md", + "packages/engine/Source/**", + "packages/widgets/index.js", + "packages/widgets/index.d.ts", + "packages/widgets/LICENSE.md", + "packages/widgets/README.md", + "packages/widgets/Source/**", + "Source/**", + "Specs/**", + "ThirdParty/**", + "scripts/**", + "favicon.ico", + ".prettierignore", + "eslint.config.js", + "gulpfile.js", + "server.js", + "index.cjs", + "LICENSE.md", + "CHANGES.md", + "README.md", + "web.config", + "!scripts/buildSandcastle.js", + "!**/*.gitignore", + "!Specs/e2e/*-snapshots/**", + "!Apps/Sandcastle/gallery/development/**", + ], + { + encoding: false, + base: ".", + }, + ), + ) + .pipe( + gulpTap(function (file) { + // Work around an issue with gulp-zip where archives generated on Windows do + // not properly have their directory executable mode set. + // see https://github.com/sindresorhus/gulp-zip/issues/64#issuecomment-205324031 + if (file.isDirectory()) { + file.stat.mode = parseInt("40777", 8); + } + }), + ) + .pipe(gulpZip(`Cesium-${version}.zip`)) + .pipe(gulp.dest(".")); + + await finished(src); + + rimraf.sync("./package.noprepare.json"); + rimraf.sync("./packages/engine/package.noprepare.json"); + rimraf.sync("./packages/widgets/package.noprepare.json"); + + return src; + }, +); diff --git a/index.release.html b/index.release.html index 0cf60e9f9de2..ef8ad8a45484 100644 --- a/index.release.html +++ b/index.release.html @@ -90,7 +90,7 @@

Local links

- Sandcastle + Sandcastle Cesium's live code editor and example gallery. Browse examples highlighting features of the Cesium API and edit and run them diff --git a/package.json b/package.json index 99ce5e90b601..1e79c6b745ef 100644 --- a/package.json +++ b/package.json @@ -114,15 +114,15 @@ "build-watch": "gulp buildWatch", "build-ts": "gulp buildTs", "build-third-party": "gulp buildThirdParty", - "build-apps": "gulp buildApps", - "build-sandcastle": "npm run build-app --workspace packages/sandcastle", + "build-apps": "gulp -f gulpfile.apps.js buildApps", + "build-sandcastle": "gulp -f gulpfile.apps.js buildSandcastle", "clean": "gulp clean", "cloc": "gulp cloc", "coverage": "gulp coverage", "build-docs": "gulp buildDocs", "build-docs-watch": "gulp buildDocsWatch", "eslint": "eslint \"./**/*.*js\" \"./**/*.*ts*\" \"./**/*.html\" --cache --quiet", - "make-zip": "gulp makeZip", + "make-zip": "gulp -f gulpfile.makezip.js makeZip", "markdownlint": "markdownlint \"**/*.md\"", "release": "gulp release", "website-release": "gulp websiteRelease", diff --git a/packages/sandcastle/README.md b/packages/sandcastle/README.md index 6c14ae2a5daf..7f7c88147595 100644 --- a/packages/sandcastle/README.md +++ b/packages/sandcastle/README.md @@ -5,12 +5,23 @@ This package is the application for Sandcastle. ## Running/Building - `npm run dev`: run the development server -- `npm run build`: alias for `npm run build-app` - `npm run build-app`: build to static files in `/Apps/Sandcastle2` for hosting/access from the root cesium dev server -- `npm run build-ci`: build to static files in `/Apps/Sandcastle2` and configure paths as needed for CI deployment Linting and style is managed under the project root's scripts. +## Building Sandcastle + +There are 2 main conceptual ways that Sandcastle gets built which mostly revolve around how to access CesiumJS resources: + +1. Sandcastle points to "external" paths for CesiumJS resources +2. Sandcastle is built to 1 static location that is co-located with all CesiumJS files. ie they're all copied into the built location + +The first method is useful and desired when developing the project locally and you want to refer to the actively built and updated CesiumJS files as you do other work. This is how the Sandcastle development server (`npm run dev`) and the local static version at `/Apps/Sandcastle2` are built. + +The second method is used when building Sandcastle to be deployed to the website or other static location. You can think of this as "bundling" all the necessary files needed for Sandcastle into 1 single directory. + +Regardless the method you want to use Sandcastle is always built using the exported `buildStatic`, `createSandcastleConfig` and `buildGalleryList` functions. + ## Gallery structure The gallery for Sandcastle is located in the `gallery` directory. A "single sandcastle" consists of 4 files which should be contained in a sub-directory that matches the id of the sandcastle. @@ -48,6 +59,10 @@ thumbnail: thumbnail.jpg development: false ``` +### Thumbnails + +Thumbnails should be any image that represents what the sandcastle does. Often this will just be the Viewer with or without any Sandcastle interaction buttons. Thumbnail files should be limited in size to help save on bandwidth. Currently most are around 225px in width. + ## Expanding the ESLint configuration diff --git a/packages/sandcastle/gallery/cesium-inspector/sandcastle.yaml b/packages/sandcastle/gallery/cesium-inspector/sandcastle.yaml index 4c7f67aecd8d..5c3e6d029227 100644 --- a/packages/sandcastle/gallery/cesium-inspector/sandcastle.yaml +++ b/packages/sandcastle/gallery/cesium-inspector/sandcastle.yaml @@ -5,3 +5,4 @@ labels: - Development - Entities thumbnail: thumbnail.jpg +development: true diff --git a/packages/sandcastle/gallery/fog/sandcastle.yaml b/packages/sandcastle/gallery/fog/sandcastle.yaml index c8910f10cad0..deb0d5c6dd64 100644 --- a/packages/sandcastle/gallery/fog/sandcastle.yaml +++ b/packages/sandcastle/gallery/fog/sandcastle.yaml @@ -4,3 +4,4 @@ description: Control fog parameters. labels: - Development thumbnail: thumbnail.jpg +development: true diff --git a/packages/sandcastle/index.html b/packages/sandcastle/index.html index 9e8ca7c615e0..aa91a42847a0 100644 --- a/packages/sandcastle/index.html +++ b/packages/sandcastle/index.html @@ -24,7 +24,7 @@ } - +
diff --git a/packages/sandcastle/index.js b/packages/sandcastle/index.js new file mode 100644 index 000000000000..066e014278c9 --- /dev/null +++ b/packages/sandcastle/index.js @@ -0,0 +1,2 @@ +export { buildGalleryList } from "./scripts/buildGallery.js"; +export { buildStatic, createSandcastleConfig } from "./scripts/buildStatic.js"; diff --git a/packages/sandcastle/package.json b/packages/sandcastle/package.json index c6490514726c..f7a9c47add74 100644 --- a/packages/sandcastle/package.json +++ b/packages/sandcastle/package.json @@ -3,15 +3,10 @@ "private": true, "version": "0.0.4", "type": "module", - "files": [ - "scripts/buildGallery.js" - ], + "main": "index.js", "scripts": { "dev": "npm run build-gallery && vite --config vite.config.dev.ts", - "build": "npm run build-app", - "build-app": "tsc -b && npm run build-gallery && vite build --config vite.config.app.ts", - "build-ci": "tsc -b && npm run build-gallery && vite build --config vite.config.ci.ts", - "build-prod": "tsc -b && npm run build-gallery && vite build --config vite.config.prod.ts", + "build": "echo 'Sandcastle cannot be built directly. Use the exported buildStatic() function instead'", "build-gallery": "node scripts/buildGallery.js" }, "dependencies": { @@ -30,6 +25,7 @@ "react-dom": "^19.0.0", "react-stay-scrolled": "^9.0.0", "react-use": "^17.6.0", + "typescript": "^5.9.3", "yargs": "^18.0.0" }, "devDependencies": { @@ -43,7 +39,6 @@ "pagefind": "^1.3.0", "rimraf": "^6.0.1", "slugify": "^1.6.6", - "typescript": "^5.9.3", "vite": "^7.1.9", "vite-plugin-static-copy": "^3.1.3", "yaml": "^2.8.0" diff --git a/packages/sandcastle/sandcastle.config.js b/packages/sandcastle/sandcastle.config.js index a9e6103e4bdc..ee6778e1b516 100644 --- a/packages/sandcastle/sandcastle.config.js +++ b/packages/sandcastle/sandcastle.config.js @@ -3,7 +3,7 @@ import process from "process"; const config = { root: ".", sourceUrl: "https://github.com/CesiumGS/cesium/blob/main/packages/sandcastle", - publicDir: "./public", + publicDirectory: "./public", gallery: { files: ["gallery"], searchOptions: { diff --git a/packages/sandcastle/scripts/buildGallery.js b/packages/sandcastle/scripts/buildGallery.js index f03d0caa2037..eaa1dceeafb7 100644 --- a/packages/sandcastle/scripts/buildGallery.js +++ b/packages/sandcastle/scripts/buildGallery.js @@ -174,7 +174,11 @@ export async function buildGalleryList(options = {}) { if ( check(!/^[a-zA-Z0-9-.]+$/.test(slug), `"${slug}" is not a valid slug`) || check(!title, `${slug} - Missing title`) || - check(!description, `${slug} - Missing description`) + check(!description, `${slug} - Missing description`) || + check( + !development && labels.includes("Development"), + `${slug} has Development label but not marked as development sandcastle`, + ) ) { continue; } @@ -300,7 +304,7 @@ if (import.meta.url.endsWith(`${pathToFileURL(process.argv[1])}`)) { try { const config = await import(pathToFileURL(configPath).href); - const { root, publicDir, gallery, sourceUrl } = config.default; + const { root, publicDirectory, gallery, sourceUrl } = config.default; // Paths are specified relative to the config file const configDir = dirname(configPath); @@ -316,7 +320,7 @@ if (import.meta.url.endsWith(`${pathToFileURL(process.argv[1])}`)) { buildGalleryOptions = { rootDirectory: configRoot, - publicDirectory: publicDir, + publicDirectory: publicDirectory, galleryFiles: files, sourceUrl, defaultThumbnail, diff --git a/packages/sandcastle/scripts/buildStatic.js b/packages/sandcastle/scripts/buildStatic.js new file mode 100644 index 000000000000..2089fe33abee --- /dev/null +++ b/packages/sandcastle/scripts/buildStatic.js @@ -0,0 +1,152 @@ +import { build, defineConfig } from "vite"; +import baseConfig from "../vite.config.js"; +import { fileURLToPath } from "url"; +import { viteStaticCopy } from "vite-plugin-static-copy"; +import { dirname, join } from "path"; +import { cesiumPathReplace, insertImportMap } from "../vite-plugins.js"; +import typescriptCompile from "./typescriptCompile.js"; + +/** @import { UserConfig, LogLevel } from 'vite' */ + +/** + * @typedef {Object} ImportObject + * @property {string} path The path to use for the import map. ie the path the app can expect to find this at + * @property {string} typesPath The path to use for intellisense types in monaco + */ + +/** + * @typedef {Object} ImportList + */ + +/** + * Check if the given key is in the imports list and throw an error if not + * @param {ImportList} imports + * @param {string} name + */ +function checkForImport(imports, name) { + if (!imports[name]) { + throw new Error(`Missing import for ${name}`); + } +} + +const __dirname = dirname(fileURLToPath(import.meta.url)); + +/** + * Create the Vite configuration for building Sandcastle. + * Set where it should build to and the base path for vite and CesiumJS files. + * + * Most importantly specify the paths the app can find the library imports. + * + * If you are copying files to the built directory ensure the source files exist BEFORE attempting to build Sandcastle + * + * @param {object} options + * @param {string} options.outDir Path to build files into + * @param {string} options.basePath Base path for files/routes + * @param {string} options.cesiumBaseUrl Base path for CesiumJS. This should include the CesiumJS assets and workers etc. + * @param {string} options.cesiumVersion CesiumJS version to display in the top right + * @param {string} [options.commitSha] Optional commit hash to display in the top right of the application + * @param {ImportList} options.imports Set of imports to add to the import map for the iframe and standalone html pages. These paths should match the URL where it can be accessed within the current environment. + * @param {{src: string, dest: string}[]} [options.copyExtraFiles] Extra paths passed to viteStaticCopy. Use this to consolidate files for a singular static deployment (ie during production). Source paths should be absolute, dest paths should be relative to the page root. It is up to you to ensure these files exist BEFORE building sandcastle. + */ +export function createSandcastleConfig({ + outDir, + basePath, + cesiumBaseUrl, + cesiumVersion, + commitSha, + imports, + copyExtraFiles = [], +}) { + if (!cesiumVersion || cesiumVersion === "") { + throw new Error("Must provide a CesiumJS version"); + } + + /** @type {UserConfig} */ + const config = { ...baseConfig }; + + config.base = basePath; + + config.build = { + ...config.build, + outDir: outDir, + }; + + const copyPlugin = viteStaticCopy({ + targets: [ + { src: "templates/Sandcastle.(d.ts|js)", dest: "templates" }, + ...copyExtraFiles, + ], + }); + + checkForImport(imports, "cesium"); + checkForImport(imports, "@cesium/engine"); + checkForImport(imports, "@cesium/widgets"); + if (imports["Sandcastle"]) { + throw new Error( + "Don't specify the Sandcastle import this is taken care of internally", + ); + } + + /** @type {Object} */ + const importMap = { + Sandcastle: "../templates/Sandcastle.js", + }; + /** @type {Object} */ + const typePaths = { + Sandcastle: "../templates/Sandcastle.d.ts", + }; + for (const [key, value] of Object.entries(imports)) { + importMap[key] = value.path; + typePaths[key] = value.typesPath; + } + + config.define = { + ...config.define, + __VITE_TYPE_IMPORT_PATHS__: JSON.stringify(typePaths), + __CESIUM_VERSION__: JSON.stringify(`Cesium ${cesiumVersion}`), + __COMMIT_SHA__: JSON.stringify(commitSha ?? undefined), + }; + + const plugins = config.plugins ?? []; + config.plugins = [ + ...plugins, + copyPlugin, + cesiumPathReplace(cesiumBaseUrl), + insertImportMap(importMap, ["bucket.html", "standalone.html"]), + ]; + + return defineConfig(config); +} + +/** + * Build Sandcastle out to a specified location as static files. + * The config should be generated with the createSandcastleConfig function. + * + * The build will only set up the paths for "external" resources from the app. + * If you are copying files to the built directory ensure the source files exist BEFORE attempting to build Sandcastle + * + * @param {UserConfig} config + * @param {LogLevel} logLevel + * @returns {Promise} + */ +export async function buildStatic(config, logLevel = "warn") { + // We have to do the compile for the Sandcastle API outside of the vite build + // because we need to reference the js file and types directly from the app + // and we don't want them bundled with the rest of the code + const exitCode = await typescriptCompile( + join(__dirname, "../templates/tsconfig.lib.json"), + ); + + if (exitCode === 0) { + console.log(`Sandcastle typescript build complete`); + } else { + throw new Error("Sandcastle typescript build failed"); + } + + console.log("Building Sandcastle with Vite"); + await build({ + ...config, + root: join(__dirname, "../"), + logLevel, + }); +} diff --git a/packages/sandcastle/scripts/typescriptCompile.js b/packages/sandcastle/scripts/typescriptCompile.js new file mode 100644 index 000000000000..22d667f485e2 --- /dev/null +++ b/packages/sandcastle/scripts/typescriptCompile.js @@ -0,0 +1,33 @@ +import { spawn } from "node:child_process"; +import { join } from "node:path"; +import { fileURLToPath } from "node:url"; + +/** + * Compile a typescript project from it's config file using the tsc CLI + * + * @param {string} configPath Absolute path to the config file to build + * @returns {number} exit code from the tsc command + */ +export default async function typescriptCompile(configPath) { + const tsPath = import.meta.resolve("typescript"); + const binPath = fileURLToPath(join(tsPath, "../../bin/tsc")); + return new Promise((resolve, reject) => { + const ls = spawn(binPath, ["-p", configPath]); + + ls.stdout.on("data", (data) => { + console.log(`stdout: ${data}`); + }); + + ls.stderr.on("data", (data) => { + console.error(`stderr: ${data}`); + }); + + ls.on("close", (code) => { + if (code === 0) { + resolve(code); + } else { + reject(code); + } + }); + }); +} diff --git a/packages/sandcastle/src/SandcastleEditor.tsx b/packages/sandcastle/src/SandcastleEditor.tsx index be7ea1bf8a1b..38bd83dad378 100644 --- a/packages/sandcastle/src/SandcastleEditor.tsx +++ b/packages/sandcastle/src/SandcastleEditor.tsx @@ -51,9 +51,6 @@ self.MonacoEnvironment = { // open network access loader.config({ monaco }); -const TYPES_URL = `${__PAGE_BASE_URL__}Source/Cesium.d.ts`; -const SANDCASTLE_TYPES_URL = `templates/Sandcastle.d.ts`; - export type SandcastleEditorRef = { formatCode(): void; }; @@ -210,25 +207,63 @@ function SandcastleEditor({ async function setTypes(monaco: Monaco) { // https://microsoft.github.io/monaco-editor/playground.html?source=v0.52.2#example-extending-language-services-configure-javascript-defaults - const cesiumTypes = await (await fetch(TYPES_URL)).text(); - // define a "global" variable so types work even with out the import statement - const cesiumTypesWithGlobal = `${cesiumTypes}\nvar Cesium: typeof import('cesium');`; - monaco.languages.typescript.javascriptDefaults.addExtraLib( - cesiumTypesWithGlobal, - "ts:cesium.d.ts", + const typeImportPaths = __VITE_TYPE_IMPORT_PATHS__ ?? {}; + + const typeImports: { + url: string; + filename: string; + transformTypes?: (typesContent: string) => string; + }[] = [ + { + url: typeImportPaths["cesium"], + filename: "ts:cesium.d.ts", + transformTypes(typesContent: string) { + // define a "global" variable so types work even with out the import statement + return `${typesContent}\nvar Cesium: typeof import('cesium');`; + }, + }, + { + url: typeImportPaths["Sandcastle"], + filename: "ts:sandcastle.d.ts", + transformTypes(typesContent: string) { + return `declare module 'Sandcastle' { + ${typesContent} + } + var Sandcastle: typeof import('Sandcastle').default;`; + }, + }, + ]; + + const extraImportNames = Object.keys(typeImportPaths).filter( + (name) => !["cesium", "Sandcastle"].includes(name), ); + for (const extraName of extraImportNames) { + typeImports.push({ + url: typeImportPaths[extraName], + filename: `ts:${extraName.replace(/@\//, "-")}.d.ts`, + }); + } - const sandcastleTypes = await (await fetch(SANDCASTLE_TYPES_URL)).text(); - // surround in a module so the import statement works nicely - // also define a "global" so types show even if you don't have the import - const sandcastleModuleTypes = `declare module 'Sandcastle' { - ${sandcastleTypes} - } - var Sandcastle: typeof import('Sandcastle').default;`; - - monaco.languages.typescript.javascriptDefaults.addExtraLib( - sandcastleModuleTypes, - "ts:sandcastle.d.ts", + await Promise.allSettled( + typeImports.map(async (typeImport) => { + const { url, transformTypes, filename } = typeImport; + if (!url) { + return; + } + try { + const responseText = await (await fetch(url)).text(); + const typesContent = transformTypes + ? transformTypes(responseText) + : responseText; + monaco.languages.typescript.javascriptDefaults.addExtraLib( + typesContent, + filename, + ); + } catch (error) { + console.error(`Unable to load types for ${filename} at ${url}`); + console.error(error); + } + }), ); } diff --git a/packages/sandcastle/standalone.html b/packages/sandcastle/standalone.html index a6694686596d..26aea087d302 100644 --- a/packages/sandcastle/standalone.html +++ b/packages/sandcastle/standalone.html @@ -8,14 +8,9 @@ content="width=device-width, initial-scale=1, maximum-scale=1, minimum-scale=1, user-scalable=no" /> Cesium Demo - + + @@ -104,7 +99,11 @@ unstable_loadStyles(document); - + diff --git a/packages/sandcastle/templates/bucket.html b/packages/sandcastle/templates/bucket.html index e56946aca7bf..99ee48356fbf 100644 --- a/packages/sandcastle/templates/bucket.html +++ b/packages/sandcastle/templates/bucket.html @@ -8,14 +8,6 @@ content="width=device-width, initial-scale=1, maximum-scale=1, minimum-scale=1, user-scalable=no" /> Cesium Demo -