mirror of https://github.com/CesiumGS/cesium.git
1209 lines
36 KiB
JavaScript
1209 lines
36 KiB
JavaScript
/*eslint-env node*/
|
|
import child_process from "child_process";
|
|
import { existsSync, readFileSync, statSync } from "fs";
|
|
import { readFile, writeFile } from "fs/promises";
|
|
import { EOL } from "os";
|
|
import path from "path";
|
|
import { createRequire } from "module";
|
|
|
|
import esbuild from "esbuild";
|
|
import { globby } from "globby";
|
|
import glslStripComments from "glsl-strip-comments";
|
|
import gulp from "gulp";
|
|
import rimraf from "rimraf";
|
|
import { rollup } from "rollup";
|
|
import rollupPluginStripPragma from "rollup-plugin-strip-pragma";
|
|
import { terser } from "rollup-plugin-terser";
|
|
import rollupCommonjs from "@rollup/plugin-commonjs";
|
|
import rollupResolve from "@rollup/plugin-node-resolve";
|
|
import streamToPromise from "stream-to-promise";
|
|
|
|
import mkdirp from "mkdirp";
|
|
|
|
// Determines the scope of the workspace packages. If the scope is set to cesium, the workspaces should be @cesium/engine.
|
|
// This should match the scope of the dependencies of the root level package.json.
|
|
const scope = "cesium";
|
|
|
|
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);
|
|
}
|
|
|
|
const copyrightHeaderTemplate = readFileSync(
|
|
path.join("Source", "copyrightHeader.js"),
|
|
"utf8"
|
|
);
|
|
const combinedCopyrightHeader = copyrightHeaderTemplate.replace(
|
|
"${version}",
|
|
version
|
|
);
|
|
|
|
function escapeCharacters(token) {
|
|
return token.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&");
|
|
}
|
|
|
|
function constructRegex(pragma, exclusive) {
|
|
const prefix = exclusive ? "exclude" : "include";
|
|
pragma = escapeCharacters(pragma);
|
|
|
|
const s =
|
|
`[\\t ]*\\/\\/>>\\s?${prefix}Start\\s?\\(\\s?(["'])${pragma}\\1\\s?,\\s?pragmas\\.${pragma}\\s?\\)\\s?;?` +
|
|
// multiline code block
|
|
`[\\s\\S]*?` +
|
|
// end comment
|
|
`[\\t ]*\\/\\/>>\\s?${prefix}End\\s?\\(\\s?(["'])${pragma}\\2\\s?\\)\\s?;?\\s?[\\t ]*\\n?`;
|
|
|
|
return new RegExp(s, "gm");
|
|
}
|
|
|
|
const pragmas = {
|
|
debug: false,
|
|
};
|
|
const stripPragmaPlugin = {
|
|
name: "strip-pragmas",
|
|
setup: (build) => {
|
|
build.onLoad({ filter: /\.js$/ }, async (args) => {
|
|
let source = await readFile(args.path, { encoding: "utf8" });
|
|
|
|
try {
|
|
for (const key in pragmas) {
|
|
if (pragmas.hasOwnProperty(key)) {
|
|
source = source.replace(constructRegex(key, pragmas[key]), "");
|
|
}
|
|
}
|
|
|
|
return { contents: source };
|
|
} catch (e) {
|
|
return {
|
|
errors: {
|
|
text: e.message,
|
|
},
|
|
};
|
|
}
|
|
});
|
|
},
|
|
};
|
|
|
|
// 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 const defaultESBuildOptions = () => {
|
|
return {
|
|
bundle: true,
|
|
color: true,
|
|
legalComments: `inline`,
|
|
logLimit: 0,
|
|
target: `es2020`,
|
|
};
|
|
};
|
|
|
|
export async function getFilesFromWorkspaceGlobs(workspaceGlobs) {
|
|
let files = [];
|
|
// Iterate over each workspace and generate declarations for each file.
|
|
for (const workspace of Object.keys(workspaceGlobs)) {
|
|
// Since workspace source files are provided relative to the workspace,
|
|
// the workspace path needs to be prepended.
|
|
const workspacePath = `packages/${workspace.replace(`${scope}/`, ``)}`;
|
|
const filesPaths = workspaceGlobs[workspace].map((glob) => {
|
|
if (glob.indexOf(`!`) === 0) {
|
|
return `!`.concat(workspacePath, `/`, glob.replace(`!`, ``));
|
|
}
|
|
return workspacePath.concat("/", glob);
|
|
});
|
|
|
|
files = files.concat(await globby(filesPaths));
|
|
}
|
|
return files;
|
|
}
|
|
|
|
/**
|
|
* @typedef {Object} CesiumBundles
|
|
* @property {Object} esmBundle The ESM bundle.
|
|
* @property {Object} iifeBundle The IIFE bundle, for use in browsers.
|
|
* @property {Object} nodeBundle The CommonJS bundle, for use in NodeJS.
|
|
*/
|
|
|
|
/**
|
|
* Bundles all individual modules, optionally minifying and stripping out debug pragmas.
|
|
* @param {Object} options
|
|
* @param {String} options.path Directory where build artifacts are output
|
|
* @param {Boolean} [options.minify=false] true if the output should be minified
|
|
* @param {Boolean} [options.removePragmas=false] true if the output should have debug pragmas stripped out
|
|
* @param {Boolean} [options.sourcemap=false] true if an external sourcemap should be generated
|
|
* @param {Boolean} [options.iife=false] true if an IIFE style module should be built
|
|
* @param {Boolean} [options.node=false] true if a CJS style node module should be built
|
|
* @param {Boolean} [options.incremental=false] true if build output should be cached for repeated builds
|
|
* @param {Boolean} [options.write=true] true if build output should be written to disk. If false, the files that would have been written as in-memory buffers
|
|
* @returns {Promise.<CesiumBundles>}
|
|
*/
|
|
export async function bundleCesiumJs(options) {
|
|
const buildConfig = defaultESBuildOptions();
|
|
buildConfig.entryPoints = ["Source/Cesium.js"];
|
|
buildConfig.minify = options.minify;
|
|
buildConfig.sourcemap = options.sourcemap;
|
|
buildConfig.external = ["https", "http", "url", "zlib"];
|
|
buildConfig.plugins = options.removePragmas ? [stripPragmaPlugin] : undefined;
|
|
buildConfig.incremental = options.incremental;
|
|
buildConfig.write = options.write;
|
|
buildConfig.banner = {
|
|
js: combinedCopyrightHeader,
|
|
};
|
|
// print errors immediately, and collect warnings so we can filter out known ones
|
|
buildConfig.logLevel = "info";
|
|
|
|
const bundles = {};
|
|
|
|
// Build ESM
|
|
const esmBundle = await esbuild.build({
|
|
...buildConfig,
|
|
format: "esm",
|
|
outfile: path.join(options.path, "index.js"),
|
|
});
|
|
|
|
handleBuildWarnings(esmBundle);
|
|
|
|
bundles.esmBundle = esmBundle;
|
|
|
|
// Build IIFE
|
|
if (options.iife) {
|
|
const iifeBundle = await esbuild.build({
|
|
...buildConfig,
|
|
format: "iife",
|
|
globalName: "Cesium",
|
|
outfile: path.join(options.path, "Cesium.js"),
|
|
});
|
|
|
|
handleBuildWarnings(iifeBundle);
|
|
|
|
bundles.iifeBundle = iifeBundle;
|
|
}
|
|
|
|
if (options.node) {
|
|
const nodeBundle = await esbuild.build({
|
|
...buildConfig,
|
|
format: "cjs",
|
|
platform: "node",
|
|
define: {
|
|
// TransformStream is a browser-only implementation depended on by zip.js
|
|
TransformStream: "null",
|
|
},
|
|
outfile: path.join(options.path, "index.cjs"),
|
|
});
|
|
|
|
handleBuildWarnings(nodeBundle);
|
|
bundles.nodeBundle = nodeBundle;
|
|
}
|
|
|
|
return bundles;
|
|
}
|
|
|
|
function filePathToModuleId(moduleId) {
|
|
return moduleId.substring(0, moduleId.lastIndexOf(".")).replace(/\\/g, "/");
|
|
}
|
|
|
|
const workspaceSourceFiles = {
|
|
engine: [
|
|
"packages/engine/Source/**/*.js",
|
|
"!packages/engine/Source/*.js",
|
|
"!packages/engine/Source/Workers/**",
|
|
"!packages/engine/Source/WorkersES6/**",
|
|
"packages/engine/Source/WorkersES6/createTaskProcessorWorker.js",
|
|
"!packages/engine/Source/ThirdParty/Workers/**",
|
|
"!packages/engine/Source/ThirdParty/google-earth-dbroot-parser.js",
|
|
"!packages/engine/Source/ThirdParty/_*",
|
|
],
|
|
widgets: ["packages/widgets/Source/**/*.js"],
|
|
};
|
|
|
|
/**
|
|
* Generates export declaration from a file from a workspace.
|
|
*
|
|
* @param {String} workspace The workspace the file belongs to.
|
|
* @param {String} file The file.
|
|
* @returns {String} The export declaration.
|
|
*/
|
|
function generateDeclaration(workspace, file) {
|
|
let assignmentName = path.basename(file, path.extname(file));
|
|
|
|
let moduleId = file;
|
|
moduleId = filePathToModuleId(moduleId);
|
|
|
|
if (moduleId.indexOf("Source/Shaders") > -1) {
|
|
assignmentName = `_shaders${assignmentName}`;
|
|
}
|
|
assignmentName = assignmentName.replace(/(\.|-)/g, "_");
|
|
return `export { ${assignmentName} } from '@${scope}/${workspace}';`;
|
|
}
|
|
|
|
/**
|
|
* Creates a single entry point file, Cesium.js, which imports all individual modules exported from the Cesium API.
|
|
* @returns {Buffer} contents
|
|
*/
|
|
export async function createCesiumJs() {
|
|
let contents = `export const VERSION = '${version}';\n`;
|
|
|
|
// Iterate over each workspace and generate declarations for each file.
|
|
for (const workspace of Object.keys(workspaceSourceFiles)) {
|
|
const files = await globby(workspaceSourceFiles[workspace]);
|
|
const declarations = files.map((file) =>
|
|
generateDeclaration(workspace, file)
|
|
);
|
|
contents += declarations.join(`${EOL}`);
|
|
contents += "\n";
|
|
}
|
|
await writeFile("Source/Cesium.js", contents, { encoding: "utf-8" });
|
|
|
|
return contents;
|
|
}
|
|
|
|
const workspaceSpecFiles = {
|
|
engine: ["packages/engine/Specs/**/*Spec.js"],
|
|
widgets: ["packages/widgets/Specs/**/*Spec.js"],
|
|
};
|
|
|
|
export async function createCombinedSpecList() {
|
|
let contents = `export const VERSION = '${version}';\n`;
|
|
|
|
for (const workspace of Object.keys(workspaceSpecFiles)) {
|
|
const files = await globby(workspaceSpecFiles[workspace]);
|
|
for (const file of files) {
|
|
contents += `import '../${file}';\n`;
|
|
}
|
|
}
|
|
|
|
await writeFile(path.join("Specs", "SpecList.js"), contents, {
|
|
encoding: "utf-8",
|
|
});
|
|
|
|
return contents;
|
|
}
|
|
|
|
/**
|
|
* Creates a single entry point file, SpecList.js, which imports all individual spec files.
|
|
* @returns {Buffer} contents
|
|
*/
|
|
export async function createSpecList() {
|
|
const files = await globby(["Specs/**/*Spec.js"]);
|
|
|
|
let contents = "";
|
|
files.forEach(function (file) {
|
|
contents += `import './${filePathToModuleId(file).replace(
|
|
"Specs/",
|
|
""
|
|
)}.js';\n`;
|
|
});
|
|
|
|
await writeFile(path.join("Specs", "SpecList.js"), contents, {
|
|
encoding: "utf-8",
|
|
});
|
|
|
|
return contents;
|
|
}
|
|
|
|
function rollupWarning(message) {
|
|
// Ignore eval warnings in third-party code we don't have control over
|
|
if (message.code === "EVAL" && /protobufjs/.test(message.loc.file)) {
|
|
return;
|
|
}
|
|
|
|
console.log(message);
|
|
}
|
|
|
|
/**
|
|
* @param {Object} options
|
|
* @param {boolean} [options.minify=false] true if the worker output should be minified
|
|
* @param {boolean} [options.removePragmas=false] true if debug pragma should be removed
|
|
* @param {boolean} [options.sourcemap=false] true if an external sourcemap should be generated
|
|
* @param {String} options.path output directory
|
|
*/
|
|
export async function bundleCombinedWorkers(options) {
|
|
// Bundle non ES6 workers.
|
|
const workers = await globby(["packages/engine/Source/Workers/**"]);
|
|
const workerConfig = defaultESBuildOptions();
|
|
workerConfig.bundle = false;
|
|
workerConfig.banner = {
|
|
js: combinedCopyrightHeader,
|
|
};
|
|
workerConfig.entryPoints = workers;
|
|
workerConfig.outdir = options.path;
|
|
workerConfig.minify = options.minify;
|
|
workerConfig.outbase = "packages/engine/Source";
|
|
await esbuild.build(workerConfig);
|
|
|
|
// Copy ThirdParty workers
|
|
const thirdPartyWorkers = await globby([
|
|
"packages/engine/Source/ThirdParty/Workers/**",
|
|
]);
|
|
|
|
const thirdPartyWorkerConfig = defaultESBuildOptions();
|
|
thirdPartyWorkerConfig.bundle = false;
|
|
thirdPartyWorkerConfig.entryPoints = thirdPartyWorkers;
|
|
thirdPartyWorkerConfig.outdir = options.path;
|
|
thirdPartyWorkerConfig.minify = options.minify;
|
|
thirdPartyWorkerConfig.outbase = "packages/engine/Source";
|
|
await esbuild.build(thirdPartyWorkerConfig);
|
|
|
|
// Bundle ES6 workers.
|
|
|
|
const es6Workers = await globby([`packages/engine/Source/WorkersES6/*.js`]);
|
|
const plugins = [rollupResolve({ preferBuiltins: true }), rollupCommonjs()];
|
|
|
|
if (options.removePragmas) {
|
|
plugins.push(
|
|
rollupPluginStripPragma({
|
|
pragmas: ["debug"],
|
|
})
|
|
);
|
|
}
|
|
|
|
if (options.minify) {
|
|
plugins.push(terser());
|
|
}
|
|
|
|
const bundle = await rollup({
|
|
input: es6Workers,
|
|
plugins: plugins,
|
|
onwarn: rollupWarning,
|
|
});
|
|
|
|
return bundle.write({
|
|
dir: path.join(options.path, "Workers"),
|
|
format: "amd",
|
|
// Rollup cannot generate a sourcemap when pragmas are removed
|
|
sourcemap: options.sourcemap && !options.removePragmas,
|
|
// SAMTODO: Add copyrightBanner
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Bundles the workers and outputs the result to the specified directory
|
|
* @param {Object} options
|
|
* @param {boolean} [options.minify=false] true if the worker output should be minified
|
|
* @param {boolean} [options.removePragmas=false] true if debug pragma should be removed
|
|
* @param {boolean} [options.sourcemap=false] true if an external sourcemap should be generated
|
|
* @param {Array.<String>} options.input The worker globs.
|
|
* @param {Array.<String>} options.inputES6 The ES6 worker globs.
|
|
* @param {String} options.path output directory
|
|
* @param {String} options.copyrightHeader The copyright header to add to worker bundles
|
|
* @returns {Promise.<*>}
|
|
*/
|
|
export async function bundleWorkers(options) {
|
|
// Copy existing workers
|
|
const workers = await globby(options.input);
|
|
|
|
const workerConfig = defaultESBuildOptions();
|
|
workerConfig.bundle = false;
|
|
workerConfig.banner = {
|
|
js: combinedCopyrightHeader,
|
|
};
|
|
workerConfig.entryPoints = workers;
|
|
workerConfig.outdir = options.path;
|
|
workerConfig.outbase = `packages/engine/Source`; // Maintain existing file paths
|
|
workerConfig.minify = options.minify;
|
|
await esbuild.build(workerConfig);
|
|
|
|
// Use rollup to build the workers:
|
|
// 1) They can be built as AMD style modules
|
|
// 2) They can be built using code-splitting, resulting in smaller modules
|
|
const files = await globby(options.inputES6);
|
|
const plugins = [rollupResolve({ preferBuiltins: true }), rollupCommonjs()];
|
|
|
|
if (options.removePragmas) {
|
|
plugins.push(
|
|
rollupPluginStripPragma({
|
|
pragmas: ["debug"],
|
|
})
|
|
);
|
|
}
|
|
|
|
if (options.minify) {
|
|
plugins.push(terser());
|
|
}
|
|
|
|
const bundle = await rollup({
|
|
input: files,
|
|
plugins: plugins,
|
|
onwarn: rollupWarning,
|
|
});
|
|
|
|
return bundle.write({
|
|
dir: path.join(options.path, "Workers"),
|
|
format: "amd",
|
|
// Rollup cannot generate a sourcemap when pragmas are removed
|
|
sourcemap: options.sourcemap && !options.removePragmas,
|
|
banner: options.copyrightHeader,
|
|
});
|
|
}
|
|
|
|
const shaderFiles = [
|
|
"packages/engine/Source/Shaders/**/*.glsl",
|
|
"packages/engine/Source/ThirdParty/Shaders/*.glsl",
|
|
];
|
|
export async function glslToJavaScript(minify, minifyStateFilePath, workspace) {
|
|
await writeFile(minifyStateFilePath, minify.toString());
|
|
const minifyStateFileLastModified = existsSync(minifyStateFilePath)
|
|
? statSync(minifyStateFilePath).mtime.getTime()
|
|
: 0;
|
|
|
|
// collect all currently existing JS files into a set, later we will remove the ones
|
|
// we still are using from the set, then delete any files remaining in the set.
|
|
const leftOverJsFiles = {};
|
|
|
|
const files = await globby([
|
|
`packages/${workspace}/Source/Shaders/**/*.js`,
|
|
`packages/${workspace}/Source/ThirdParty/Shaders/*.js`,
|
|
]);
|
|
files.forEach(function (file) {
|
|
leftOverJsFiles[path.normalize(file)] = true;
|
|
});
|
|
|
|
const builtinFunctions = [];
|
|
const builtinConstants = [];
|
|
const builtinStructs = [];
|
|
|
|
const glslFiles = await globby(shaderFiles);
|
|
await Promise.all(
|
|
glslFiles.map(async function (glslFile) {
|
|
glslFile = path.normalize(glslFile);
|
|
const baseName = path.basename(glslFile, ".glsl");
|
|
const jsFile = `${path.join(path.dirname(glslFile), baseName)}.js`;
|
|
|
|
// identify built in functions, structs, and constants
|
|
const baseDir = path.join(
|
|
`packages/${workspace}/`,
|
|
"Source",
|
|
"Shaders",
|
|
"Builtin"
|
|
);
|
|
if (
|
|
glslFile.indexOf(path.normalize(path.join(baseDir, "Functions"))) === 0
|
|
) {
|
|
builtinFunctions.push(baseName);
|
|
} else if (
|
|
glslFile.indexOf(path.normalize(path.join(baseDir, "Constants"))) === 0
|
|
) {
|
|
builtinConstants.push(baseName);
|
|
} else if (
|
|
glslFile.indexOf(path.normalize(path.join(baseDir, "Structs"))) === 0
|
|
) {
|
|
builtinStructs.push(baseName);
|
|
}
|
|
|
|
delete leftOverJsFiles[jsFile];
|
|
|
|
const jsFileExists = existsSync(jsFile);
|
|
const jsFileModified = jsFileExists
|
|
? statSync(jsFile).mtime.getTime()
|
|
: 0;
|
|
const glslFileModified = statSync(glslFile).mtime.getTime();
|
|
|
|
if (
|
|
jsFileExists &&
|
|
jsFileModified > glslFileModified &&
|
|
jsFileModified > minifyStateFileLastModified
|
|
) {
|
|
return;
|
|
}
|
|
|
|
let contents = await readFile(glslFile, { encoding: "utf8" });
|
|
contents = contents.replace(/\r\n/gm, "\n");
|
|
|
|
let copyrightComments = "";
|
|
const extractedCopyrightComments = contents.match(
|
|
/\/\*\*(?:[^*\/]|\*(?!\/)|\n)*?@license(?:.|\n)*?\*\//gm
|
|
);
|
|
if (extractedCopyrightComments) {
|
|
copyrightComments = `${extractedCopyrightComments.join("\n")}\n`;
|
|
}
|
|
|
|
if (minify) {
|
|
contents = glslStripComments(contents);
|
|
contents = contents
|
|
.replace(/\s+$/gm, "")
|
|
.replace(/^\s+/gm, "")
|
|
.replace(/\n+/gm, "\n");
|
|
contents += "\n";
|
|
}
|
|
|
|
contents = contents.split('"').join('\\"').replace(/\n/gm, "\\n\\\n");
|
|
contents = `${copyrightComments}\
|
|
//This file is automatically rebuilt by the Cesium build process.\n\
|
|
export default "${contents}";\n`;
|
|
|
|
return writeFile(jsFile, contents);
|
|
})
|
|
);
|
|
|
|
// delete any left over JS files from old shaders
|
|
Object.keys(leftOverJsFiles).forEach(function (filepath) {
|
|
rimraf.sync(filepath);
|
|
});
|
|
|
|
const generateBuiltinContents = function (contents, builtins, path) {
|
|
for (let i = 0; i < builtins.length; i++) {
|
|
const builtin = builtins[i];
|
|
contents.imports.push(
|
|
`import czm_${builtin} from './${path}/${builtin}.js'`
|
|
);
|
|
contents.builtinLookup.push(`czm_${builtin} : ` + `czm_${builtin}`);
|
|
}
|
|
};
|
|
|
|
//generate the JS file for Built-in GLSL Functions, Structs, and Constants
|
|
const contents = {
|
|
imports: [],
|
|
builtinLookup: [],
|
|
};
|
|
generateBuiltinContents(contents, builtinConstants, "Constants");
|
|
generateBuiltinContents(contents, builtinStructs, "Structs");
|
|
generateBuiltinContents(contents, builtinFunctions, "Functions");
|
|
|
|
const fileContents = `//This file is automatically rebuilt by the Cesium build process.\n${contents.imports.join(
|
|
"\n"
|
|
)}\n\nexport default {\n ${contents.builtinLookup.join(",\n ")}\n};\n`;
|
|
|
|
return writeFile(
|
|
path.join(
|
|
`packages/${workspace}/`,
|
|
"Source",
|
|
"Shaders",
|
|
"Builtin",
|
|
"CzmBuiltins.js"
|
|
),
|
|
fileContents
|
|
);
|
|
}
|
|
|
|
const externalResolvePlugin = {
|
|
name: "external-cesium",
|
|
setup: (build) => {
|
|
// In Specs, when we import files from the source files, we import
|
|
// them from the index.js files. This plugin replaces those imports
|
|
// with the IIFE Cesium.js bundle that's loaded in the browser
|
|
// in SpecRunner.html.
|
|
build.onResolve({ filter: new RegExp(`index\.js$`) }, () => {
|
|
return {
|
|
path: "Cesium",
|
|
namespace: "external-cesium",
|
|
};
|
|
});
|
|
|
|
build.onResolve({ filter: /@cesium/ }, () => {
|
|
return {
|
|
path: "Cesium",
|
|
namespace: "external-cesium",
|
|
};
|
|
});
|
|
|
|
build.onLoad(
|
|
{
|
|
filter: new RegExp(`^Cesium$`),
|
|
namespace: "external-cesium",
|
|
},
|
|
() => {
|
|
const contents = `module.exports = Cesium`;
|
|
return {
|
|
contents,
|
|
};
|
|
}
|
|
);
|
|
},
|
|
};
|
|
|
|
/**
|
|
* Creates a template html file in the Sandcastle app listing the gallery of demos
|
|
* @param {Boolean} [noDevelopmentGallery=false] true if the development gallery should not be included in the list
|
|
* @returns {Promise.<*>}
|
|
*/
|
|
export async function createGalleryList(noDevelopmentGallery) {
|
|
const demoObjects = [];
|
|
const demoJSONs = [];
|
|
const output = path.join("Apps", "Sandcastle", "gallery", "gallery-index.js");
|
|
|
|
const fileList = ["Apps/Sandcastle/gallery/**/*.html"];
|
|
if (noDevelopmentGallery) {
|
|
fileList.push("!Apps/Sandcastle/gallery/development/**/*.html");
|
|
}
|
|
|
|
// On travis, the version is set to something like '1.43.0-branch-name-travisBuildNumber'
|
|
// We need to extract just the Major.Minor version
|
|
const majorMinor = packageJson.version.match(/^(.*)\.(.*)\./);
|
|
const major = majorMinor[1];
|
|
const minor = Number(majorMinor[2]) - 1; // We want the last release, not current release
|
|
const tagVersion = `${major}.${minor}`;
|
|
|
|
// Get an array of demos that were added since the last release.
|
|
// This includes newly staged local demos as well.
|
|
let newDemos = [];
|
|
try {
|
|
newDemos = child_process
|
|
.execSync(
|
|
`git diff --name-only --diff-filter=A ${tagVersion} Apps/Sandcastle/gallery/*.html`,
|
|
{ stdio: ["pipe", "pipe", "ignore"] }
|
|
)
|
|
.toString()
|
|
.trim()
|
|
.split("\n");
|
|
} catch (e) {
|
|
// On a Cesium fork, tags don't exist so we can't generate the list.
|
|
}
|
|
|
|
let helloWorld;
|
|
const files = await globby(fileList);
|
|
files.forEach(function (file) {
|
|
const demo = filePathToModuleId(
|
|
path.relative("Apps/Sandcastle/gallery", file)
|
|
);
|
|
|
|
const demoObject = {
|
|
name: demo,
|
|
isNew: newDemos.includes(file),
|
|
};
|
|
|
|
if (existsSync(`${file.replace(".html", "")}.jpg`)) {
|
|
demoObject.img = `${demo}.jpg`;
|
|
}
|
|
|
|
demoObjects.push(demoObject);
|
|
|
|
if (demo === "Hello World") {
|
|
helloWorld = demoObject;
|
|
}
|
|
});
|
|
|
|
demoObjects.sort(function (a, b) {
|
|
if (a.name < b.name) {
|
|
return -1;
|
|
} else if (a.name > b.name) {
|
|
return 1;
|
|
}
|
|
return 0;
|
|
});
|
|
|
|
const helloWorldIndex = Math.max(demoObjects.indexOf(helloWorld), 0);
|
|
|
|
for (let i = 0; i < demoObjects.length; ++i) {
|
|
demoJSONs[i] = JSON.stringify(demoObjects[i], null, 2);
|
|
}
|
|
|
|
const contents = `\
|
|
// This file is automatically rebuilt by the Cesium build process.\n\
|
|
const hello_world_index = ${helloWorldIndex};\n\
|
|
const VERSION = '${version}';\n\
|
|
const gallery_demos = [${demoJSONs.join(", ")}];\n\
|
|
const has_new_gallery_demos = ${newDemos.length > 0 ? "true;" : "false;"}\n`;
|
|
|
|
await writeFile(output, contents);
|
|
|
|
// Compile CSS for Sandcastle
|
|
return esbuild.build({
|
|
entryPoints: [
|
|
path.join("Apps", "Sandcastle", "templates", "bucketRaw.css"),
|
|
],
|
|
minify: true,
|
|
banner: {
|
|
css:
|
|
"/* This file is automatically rebuilt by the Cesium build process. */\n",
|
|
},
|
|
outfile: path.join("Apps", "Sandcastle", "templates", "bucket.css"),
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Helper function to copy files.
|
|
*
|
|
* @param {Array.<String>} globs The file globs to be copied.
|
|
* @param {String} destination The path to copy the files to.
|
|
* @param {String} base The base path to omit from the globs when files are copied. Defaults to "".
|
|
* @returns {Promise.<Buffer>} A promise containing the stream output as a buffer.
|
|
*/
|
|
export async function copyFiles(globs, destination, base) {
|
|
const stream = gulp
|
|
.src(globs, { nodir: true, base: base ?? "" })
|
|
.pipe(gulp.dest(destination));
|
|
|
|
return streamToPromise(stream);
|
|
}
|
|
|
|
/**
|
|
* Copy assets from engine.
|
|
*
|
|
* @param {String} destination The path to copy files to.
|
|
* @returns {Promise.<>} A promise that completes when all assets are copied to the destination.
|
|
*/
|
|
export async function copyEngineAssets(destination) {
|
|
const engineStaticAssets = [
|
|
"packages/engine/Source/**",
|
|
"!packages/engine/Source/Workers/package.json",
|
|
"!packages/engine/Source/**/*.js",
|
|
"!packages/engine/Source/**/*.glsl",
|
|
"!packages/engine/Source/**/*.css",
|
|
"!packages/engine/Source/**/*.md",
|
|
];
|
|
|
|
await copyFiles(engineStaticAssets, destination, "packages/engine/Source");
|
|
|
|
// Since the CesiumWidget was part of the Widgets folder, the files must be manually
|
|
// copied over to the right directory.
|
|
|
|
await copyFiles(
|
|
["packages/engine/Source/Widget/**", "!packages/engine/Source/Widget/*.js"],
|
|
path.join(destination, "Widgets/CesiumWidget"),
|
|
"packages/engine/Source/Widget"
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Copy assets from widgets.
|
|
*
|
|
* @param {String} destination The path to copy files to.
|
|
* @returns {Promise.<>} A promise that completes when all assets are copied to the destination.
|
|
*/
|
|
export async function copyWidgetsAssets(destination) {
|
|
const widgetsStaticAssets = [
|
|
"packages/widgets/Source/**",
|
|
"!packages/widgets/Source/**/*.js",
|
|
"!packages/widgets/Source/**/*.css",
|
|
"!packages/widgets/Source/**/*.glsl",
|
|
"!packages/widgets/Source/**/*.md",
|
|
];
|
|
|
|
await copyFiles(widgetsStaticAssets, destination, "packages/widgets/Source");
|
|
}
|
|
|
|
/**
|
|
* Creates .jshintrc for use in Sandcastle
|
|
* @returns {Buffer} contents
|
|
*/
|
|
export async function createJsHintOptions() {
|
|
const jshintrc = JSON.parse(
|
|
await readFile(path.join("Apps", "Sandcastle", ".jshintrc"), {
|
|
encoding: "utf8",
|
|
})
|
|
);
|
|
|
|
const contents = `\
|
|
// This file is automatically rebuilt by the Cesium build process.\n\
|
|
const sandcastleJsHintOptions = ${JSON.stringify(jshintrc, null, 4)};\n`;
|
|
|
|
await writeFile(
|
|
path.join("Apps", "Sandcastle", "jsHintOptions.js"),
|
|
contents
|
|
);
|
|
|
|
return contents;
|
|
}
|
|
|
|
/**
|
|
* Bundles spec files for testing in the browser and on the command line with karma.
|
|
* @param {Object} options
|
|
* @param {Boolean} [options.incremental=false] true if the build should be cached for repeated rebuilds
|
|
* @param {Boolean} [options.write=false] true if build output should be written to disk. If false, the files that would have been written as in-memory buffers
|
|
* @returns {Promise.<*>}
|
|
*/
|
|
export function bundleCombinedSpecs(options) {
|
|
options = options || {};
|
|
|
|
return esbuild.build({
|
|
entryPoints: [
|
|
"Specs/spec-main.js",
|
|
"Specs/SpecList.js",
|
|
"Specs/karma-main.js",
|
|
],
|
|
bundle: true,
|
|
format: "esm",
|
|
sourcemap: true,
|
|
target: "es2020",
|
|
outdir: path.join("Build", "Specs"),
|
|
plugins: [externalResolvePlugin],
|
|
external: [`http`, `https`, `url`, `zlib`],
|
|
incremental: options.incremental,
|
|
write: options.write,
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Creates the index.js for a package.
|
|
*
|
|
* @param {String} workspace The workspace to create the index.js for.
|
|
* @returns
|
|
*/
|
|
async function createIndexJs(workspace) {
|
|
let contents = "";
|
|
|
|
// Iterate over all provided source files for the workspace and export the assignment based on file name.
|
|
const workspaceSources = workspaceSourceFiles[workspace];
|
|
if (!workspaceSources) {
|
|
console.error(`Unable to find source files for workspace: ${workspace}`);
|
|
process.exit(-1);
|
|
}
|
|
|
|
const files = await globby(workspaceSources);
|
|
files.forEach(function (file) {
|
|
file = path.relative(`packages/${workspace}`, file);
|
|
|
|
let moduleId = file;
|
|
moduleId = filePathToModuleId(moduleId);
|
|
|
|
// Rename shader files, such that ViewportQuadFS.glsl is exported as _shadersViewportQuadFS in JS.
|
|
|
|
let assignmentName = path.basename(file, path.extname(file));
|
|
if (moduleId.indexOf(`Source/Shaders/`) === 0) {
|
|
assignmentName = `_shaders${assignmentName}`;
|
|
}
|
|
assignmentName = assignmentName.replace(/(\.|-)/g, "_");
|
|
contents += `export { default as ${assignmentName} } from './${moduleId}.js';${EOL}`;
|
|
});
|
|
|
|
await writeFile(`packages/${workspace}/index.js`, contents, {
|
|
encoding: "utf-8",
|
|
});
|
|
|
|
return contents;
|
|
}
|
|
|
|
/**
|
|
* Creates a single entry point file by importing all individual spec files.
|
|
* @param {Array.<String>} files The individual spec files.
|
|
* @param {String} workspace The workspace.
|
|
* @param {String} outputPath The path the file is written to.
|
|
*/
|
|
async function createSpecListJs(files, workspace, outputPath) {
|
|
let contents = "";
|
|
files.forEach(function (file) {
|
|
contents += `import './${filePathToModuleId(file).replace(
|
|
`packages/${workspace}/Specs/`,
|
|
""
|
|
)}.js';\n`;
|
|
});
|
|
|
|
await writeFile(outputPath, contents, {
|
|
encoding: "utf-8",
|
|
});
|
|
|
|
return contents;
|
|
}
|
|
|
|
/**
|
|
* Bundles CSS files.
|
|
*
|
|
* @param {Object} options
|
|
* @param {Array.<String>} options.filePaths The file paths to bundle.
|
|
* @param {Boolean} options.sourcemap
|
|
* @param {Boolean} options.minify
|
|
* @param {String} options.outdir The output directory.
|
|
* @param {String} options.outbase The
|
|
*/
|
|
async function bundleCSS(options) {
|
|
// Configure options for esbuild.
|
|
const esBuildOptions = defaultESBuildOptions();
|
|
esBuildOptions.entryPoints = await globby(options.filePaths);
|
|
esBuildOptions.loader = {
|
|
".gif": "text",
|
|
".png": "text",
|
|
};
|
|
esBuildOptions.sourcemap = options.sourcemap;
|
|
esBuildOptions.minify = options.minify;
|
|
esBuildOptions.outdir = options.outdir;
|
|
esBuildOptions.outbase = options.outbase;
|
|
|
|
await esbuild.build(esBuildOptions);
|
|
}
|
|
|
|
const workspaceCssFiles = {
|
|
engine: ["packages/engine/Source/**/*.css"],
|
|
widgets: ["packages/widgets/Source/**/*.css"],
|
|
};
|
|
|
|
/**
|
|
* Bundles spec files for testing in the browser.
|
|
*
|
|
* @param {Object} options
|
|
* @param {Boolean} [options.incremental=false] True if builds should be generated incrementally.
|
|
* @param {String} options.outbase The base path the output files are relative to.
|
|
* @param {String} options.outdir The directory to place the output in.
|
|
* @param {String} options.specListFile The path to the SpecList.js file
|
|
* @param {Boolean} [options.write=true] True if bundles generated are written to files instead of in-memory buffers.
|
|
* @returns {Object} The bundle generated from Specs.
|
|
*/
|
|
async function bundleSpecs(options) {
|
|
const incremental = options.incremental ?? true;
|
|
const write = options.write ?? true;
|
|
|
|
const buildOptions = {
|
|
bundle: true,
|
|
format: "esm",
|
|
incremental: incremental,
|
|
outdir: options.outdir,
|
|
sourcemap: true,
|
|
external: ["https", "http", "zlib", "url"],
|
|
target: "es2020",
|
|
write: write,
|
|
};
|
|
|
|
// When bundling specs for a workspace, the spec-main.js and karma-main.js
|
|
// are bundled separately since they use a different outbase than the workspace's SpecList.js.
|
|
await esbuild.build({
|
|
...buildOptions,
|
|
entryPoints: ["Specs/spec-main.js", "Specs/karma-main.js"],
|
|
});
|
|
|
|
return await esbuild.build({
|
|
...buildOptions,
|
|
entryPoints: [options.specListFile],
|
|
outbase: options.outbase,
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Builds the engine workspace.
|
|
*
|
|
* @param {Object} options
|
|
* @param {Boolean} [options.incremental=false] True if builds should be generated incrementally.
|
|
* @param {Boolean} [options.minify=false] True if bundles should be minified.
|
|
* @param {Boolean} [options.write=true] True if bundles generated are written to files instead of in-memory buffers.
|
|
*/
|
|
export const buildEngine = async (options) => {
|
|
options = options || {};
|
|
|
|
const incremental = options.incremental ?? false;
|
|
const minify = options.minify ?? false;
|
|
const write = options.write ?? true;
|
|
|
|
// Create Build folder to place build artifacts.
|
|
mkdirp.sync("packages/engine/Build");
|
|
|
|
// Convert GLSL files to JavaScript modules.
|
|
await glslToJavaScript(
|
|
minify,
|
|
"packages/engine/Build/minifyShaders.state",
|
|
"engine"
|
|
);
|
|
|
|
// Create index.js
|
|
await createIndexJs("engine");
|
|
|
|
// Build workers.
|
|
await bundleWorkers({
|
|
input: ["packages/engine/Source/Workers/**"],
|
|
inputES6: ["packages/engine/Source/WorkersES6/*.js"],
|
|
path: "packages/engine/Build",
|
|
});
|
|
|
|
// Create SpecList.js
|
|
const specFiles = await globby(workspaceSpecFiles["engine"]);
|
|
const specListFile = path.join("packages/engine/Specs", "SpecList.js");
|
|
await createSpecListJs(specFiles, "engine", specListFile);
|
|
|
|
await bundleSpecs({
|
|
incremental: incremental,
|
|
outbase: "packages/engine/Specs",
|
|
outdir: "packages/engine/Build/Specs",
|
|
specListFile: specListFile,
|
|
write: write,
|
|
});
|
|
|
|
return;
|
|
};
|
|
|
|
/**
|
|
* Builds the widgets workspace.
|
|
*
|
|
* @param {Object} options
|
|
* @param {Boolean} [options.incremental=false] True if builds should be generated incrementally.
|
|
* @param {Boolean} [options.write=true] True if bundles generated are written to files instead of in-memory buffers.
|
|
*/
|
|
export const buildWidgets = async (options) => {
|
|
options = options || {};
|
|
|
|
const incremental = options.incremental ?? false;
|
|
|
|
const write = options.write ?? true;
|
|
|
|
// Generate Build folder to place build artifacts.
|
|
mkdirp.sync("packages/widgets/Build");
|
|
|
|
// Create index.js
|
|
await createIndexJs("widgets");
|
|
|
|
// Create SpecList.js
|
|
const specFiles = await globby(workspaceSpecFiles["widgets"]);
|
|
const specListFile = path.join("packages/widgets/Specs", "SpecList.js");
|
|
await createSpecListJs(specFiles, "widgets", specListFile);
|
|
|
|
await bundleSpecs({
|
|
incremental: incremental,
|
|
outbase: "packages/widgets/Specs",
|
|
outdir: "packages/widgets/Build/Specs",
|
|
specListFile: specListFile,
|
|
write: write,
|
|
});
|
|
};
|
|
|
|
/**
|
|
* Build CesiumJS.
|
|
*
|
|
* @param {Object} options
|
|
* @param {Boolean} [options.development=true] True if build is targeted for development.
|
|
* @param {Boolean} [options.iife=true] True if IIFE bundle should be generated.
|
|
* @param {Boolean} [options.incremental=true] True if builds should be generated incrementally.
|
|
* @param {Boolean} [options.minify=false] True if bundles should be minified.
|
|
* @param {Boolean} [options.node=true] True if CommonJS bundle should be generated.
|
|
* @param {Boolean} options.outputDirectory The directory where the output should go.
|
|
* @param {Boolean} [options.removePragmas=false] True if debug pragmas should be removed.
|
|
* @param {Boolean} [options.sourcemap=true] True if sourcemap should be included in the generated bundles.
|
|
* @param {Boolean} [options.write=true] True if bundles generated are written to files instead of in-memory buffers.
|
|
*/
|
|
export async function buildCesium(options) {
|
|
const development = options.development ?? true;
|
|
const iife = options.iife ?? true;
|
|
const incremental = options.incremental ?? false;
|
|
const minify = options.minify ?? false;
|
|
const node = options.node ?? true;
|
|
const removePragmas = options.removePragmas ?? false;
|
|
const sourcemap = options.sourcemap ?? true;
|
|
const write = options.write ?? true;
|
|
|
|
// Generate Build folder to place build artifacts.
|
|
mkdirp.sync("Build");
|
|
const outputDirectory =
|
|
options.outputDirectory ??
|
|
path.join("Build", `Cesium${!minify ? "Unminified" : ""}`);
|
|
rimraf.sync(outputDirectory);
|
|
|
|
await writeFile(
|
|
"Build/package.json",
|
|
JSON.stringify({
|
|
type: "commonjs",
|
|
}),
|
|
"utf8"
|
|
);
|
|
|
|
// Create Cesium.js
|
|
await createCesiumJs();
|
|
|
|
// Create SpecList.js
|
|
await createCombinedSpecList();
|
|
|
|
// Bundle ThirdParty files.
|
|
await bundleCSS({
|
|
filePaths: [
|
|
"packages/engine/Source/ThirdParty/google-earth-dbroot-parser.js",
|
|
],
|
|
minify: minify,
|
|
sourcemap: sourcemap,
|
|
outdir: outputDirectory,
|
|
outbase: "packages/engine/Source",
|
|
});
|
|
|
|
// Bundle CSS files.
|
|
await bundleCSS({
|
|
filePaths: workspaceCssFiles[`engine`],
|
|
outdir: path.join(outputDirectory, "Widgets/CesiumWidget"),
|
|
outbase: "packages/engine/Source/Widget",
|
|
});
|
|
await bundleCSS({
|
|
filePaths: workspaceCssFiles[`widgets`],
|
|
outdir: path.join(outputDirectory, "Widgets"),
|
|
outbase: "packages/widgets/Source",
|
|
});
|
|
|
|
// Generate bundles.
|
|
const bundles = await bundleCesiumJs({
|
|
minify: minify,
|
|
iife: iife,
|
|
incremental: incremental,
|
|
sourcemap: sourcemap,
|
|
removePragmas: removePragmas,
|
|
path: outputDirectory,
|
|
node: node,
|
|
write: write,
|
|
});
|
|
|
|
await Promise.all([
|
|
createJsHintOptions(),
|
|
bundleCombinedWorkers({
|
|
minify: minify,
|
|
sourcemap: sourcemap,
|
|
path: outputDirectory,
|
|
removePragmas: removePragmas,
|
|
}),
|
|
createGalleryList(!development),
|
|
]);
|
|
|
|
// Generate Specs bundle.
|
|
const specsBundle = await bundleCombinedSpecs({
|
|
incremental: incremental,
|
|
write: write,
|
|
});
|
|
|
|
// Copy static assets to the Build folder.
|
|
|
|
await copyEngineAssets(outputDirectory);
|
|
await copyWidgetsAssets(path.join(outputDirectory, "Widgets"));
|
|
|
|
// Copy static assets to Source folder.
|
|
|
|
await copyEngineAssets("Source");
|
|
await copyFiles(
|
|
["packages/engine/Source/ThirdParty/**/*.js"],
|
|
"Source/ThirdParty",
|
|
"packages/engine/Source/ThirdParty"
|
|
);
|
|
|
|
await copyWidgetsAssets("Source/Widgets");
|
|
await copyFiles(
|
|
["packages/widgets/Source/**/*.css"],
|
|
"Source/Widgets",
|
|
"packages/widgets/Source"
|
|
);
|
|
|
|
// WORKAROUND:
|
|
// Since CesiumWidget was originally part of the Widgets folder, we need to fix up any
|
|
// references to it when we put it back in the Widgets folder, as expected by the
|
|
// combined CesiumJS structure.
|
|
const widgetsCssBuffer = await readFile("Source/Widgets/widgets.css");
|
|
const widgetsCssContents = widgetsCssBuffer
|
|
.toString()
|
|
.replace("../../engine/Source/Widget", "./CesiumWidget");
|
|
await writeFile("Source/Widgets/widgets.css", widgetsCssContents);
|
|
|
|
const lighterCssBuffer = await readFile("Source/Widgets/lighter.css");
|
|
const lighterCssContents = lighterCssBuffer
|
|
.toString()
|
|
.replace("../../engine/Source/Widget", "./CesiumWidget");
|
|
await writeFile("Source/Widgets/lighter.css", lighterCssContents);
|
|
|
|
return {
|
|
...bundles,
|
|
specsBundle: specsBundle,
|
|
};
|
|
}
|