diff --git a/examples/jsconfig.json b/examples/jsconfig.json index 857eb430977..6fd2d97d2fc 100644 --- a/examples/jsconfig.json +++ b/examples/jsconfig.json @@ -16,6 +16,6 @@ "esModuleInterop" : true, "moduleResolution" : "node" }, - "include": ["src", "scripts", "iframe", "examples.config.mjs"], + "include": ["src", "scripts", "iframe", "utils.mjs"], "exclude": ["node_modules", "src/lib"] } diff --git a/examples/package.json b/examples/package.json index 0a96f36f5fb..fa3d8ba75d6 100644 --- a/examples/package.json +++ b/examples/package.json @@ -5,8 +5,8 @@ "main": "index.js", "type": "module", "scripts": { - "prebuild": "npm run -s build:metadata && npm run -s build:standalone && npm run -s build:sharing", "build": "cross-env NODE_ENV=production rollup -c", + "build:pre": "npm run -s build:metadata && npm run -s build:standalone && npm run -s build:sharing", "build:metadata": "node ./scripts/metadata.mjs", "build:sharing": "node ./scripts/sharing-html.mjs", "build:standalone": "node ./scripts/standalone-html.mjs", @@ -14,7 +14,7 @@ "rollup:watch": "cross-env NODE_ENV=development rollup -c -w", "serve": "serve dist -l 5000 --no-request-logging --config ../serve.json", "lint": "eslint . --ext .js,.ts,.tsx", - "watch": "npm run prebuild && cross-env concurrently --kill-others \"npm run rollup:watch\"", + "watch": "cross-env concurrently --kill-others \"npm run rollup:watch\"", "watch:debug": "cross-env ENGINE_PATH=../build/playcanvas.dbg.js npm run watch", "watch:profiler": "cross-env ENGINE_PATH=../build/playcanvas.prf.js npm run watch", "develop": "cross-env NODE_ENV=development concurrently --kill-others \"npm run watch\" \"npm run serve\"", diff --git a/examples/rollup.config.js b/examples/rollup.config.js index 718afbc5dc4..defb8e7460a 100644 --- a/examples/rollup.config.js +++ b/examples/rollup.config.js @@ -3,7 +3,7 @@ import * as fs from 'node:fs'; import fse from 'fs-extra'; import { fileURLToPath } from 'node:url'; import path from 'node:path'; -import { exec } from 'node:child_process'; +import { execSync } from 'node:child_process'; // 1st party Rollup plugins import alias from '@rollup/plugin-alias'; @@ -12,19 +12,29 @@ import replace from '@rollup/plugin-replace'; import resolve from "@rollup/plugin-node-resolve"; import terser from '@rollup/plugin-terser'; +// engine rollup utils import { buildTarget } from '../utils/rollup-build-target.mjs'; import { scriptTargetEs6 } from '../utils/rollup-script-target-es6.mjs'; +// util functions +import { isModuleWithExternalDependencies } from './utils.mjs'; + /** @typedef {import('rollup').RollupOptions} RollupOptions */ /** @typedef {import('rollup').Plugin} RollupPlugin */ const __dirname = path.dirname(fileURLToPath(import.meta.url)); +const NODE_ENV = process.env.NODE_ENV ?? ''; +const ENGINE_PATH = !process.env.ENGINE_PATH && NODE_ENV === 'development' ? + '../src/index.js' : + process.env.ENGINE_PATH ?? ''; + const PCUI_PATH = process.env.PCUI_PATH || 'node_modules/@playcanvas/pcui'; const PCUI_REACT_PATH = path.resolve(PCUI_PATH, 'react'); const PCUI_STYLES_PATH = path.resolve(PCUI_PATH, 'styles'); -const staticFiles = [ + +const STATIC_FILES = [ // static main page src { src: './src/static', dest: 'dist/' }, @@ -51,59 +61,36 @@ const staticFiles = [ // modules (N.B. destination folder is 'modules' as 'node_modules' are automatically excluded by git pages) { src: './node_modules/monaco-editor/min/vs', dest: 'dist/modules/monaco-editor/min/vs' }, - // N.B. fflate will not be needed once extras module is rolled up - { src: '../node_modules/fflate/esm/', dest: 'dist/modules/fflate/esm' } -]; -const regexpExportStarFrom = /^\s*export\s*\*\s*from\s*.+\s*;\s*$/gm; -const regexpExportFrom = /^\s*export\s*{.*}\s*from\s*.+\s*;\s*$/gm; -const regexpImport = /^\s*import\s*.+\s*;\s*$/gm; -/** - * If one of this RegExp's match, it's likely an ESM with external dependencies. - * @example - * isModuleWithExternalDependencies(` - * // Testing variants: - * export * from './index.mjs'; - * export { Ray } from './core/shape/ray.js'; - * import './polyfill/OESVertexArrayObject.js'; - *`); - * @param {string} content - The file content to test. - * @returns {boolean} Whether content is a module. - */ -function isModuleWithExternalDependencies(content) { - const a = regexpExportStarFrom.test(content); - const b = regexpExportFrom.test(content); - const c = regexpImport.test(content); - // console.log('isModuleWithExternalDependencies', { a, b, c }); - return a || b || c; -} + // TODO: fflate will not be needed once extras module is rolled up + { src: '../node_modules/fflate/esm/', dest: 'dist/modules/fflate/esm' }, -const { NODE_ENV = '' } = process.env; -let { ENGINE_PATH = '' } = process.env; + // engine path + ...getEnginePathFiles() +]; -// If we don't set ENGINE_PATH and NODE_ENV is 'development', we use ../src/index.js, which -// requires no additional build shells. -if (!ENGINE_PATH && NODE_ENV === 'development') { - ENGINE_PATH = '../src/index.js'; -} +function getEnginePathFiles() { + if (!ENGINE_PATH) { + return []; + } -if (ENGINE_PATH) { const src = path.resolve(ENGINE_PATH); const content = fs.readFileSync(src, 'utf8'); - const copyDir = isModuleWithExternalDependencies(content); - if (copyDir) { - // Copy entire folder for MJS versions with external dependencies + const isUnpacked = isModuleWithExternalDependencies(content); + if (isUnpacked) { const srcDir = path.dirname(src); const dest = 'dist/iframe/ENGINE_PATH'; - staticFiles.push({ src: srcDir, dest }); - } else { - // This can be both UMD/ESM as a single file - const entryPoint = ENGINE_PATH.split("/").pop(); - const dest = 'dist/iframe/ENGINE_PATH/' + entryPoint; - staticFiles.push({ src, dest }); + return [{ src: srcDir, dest }]; } + + // packed module builds + const dest = 'dist/iframe/ENGINE_PATH/index.js'; + return [{ src, dest }]; } +/** + * @returns {RollupPlugin} The plugin. + */ function timestamp() { return { name: 'timestamp', @@ -114,7 +101,7 @@ function timestamp() { } /** - * @param {import('rollup').Plugin} plugin - The Rollup plugin. + * @param {RollupPlugin} plugin - The Rollup plugin. * @param {string} src - File or path to watch. */ function watch(plugin, src) { @@ -138,7 +125,7 @@ function watch(plugin, src) { /** * This plugin copies static files from source to destination. * - * @param {staticFiles} targets - Array of source and destination objects. + * @param {STATIC_FILES} targets - Array of source and destination objects. * @returns {RollupPlugin} The plugin. */ function copyStaticFiles(targets) { @@ -181,21 +168,45 @@ function buildAndWatchStandaloneExamples() { } }, generateBundle() { - const cmd = `cross-env NODE_ENV=${NODE_ENV} ENGINE_PATH=${ENGINE_PATH} npm run build:standalone`; + const cmd = `cross-env NODE_ENV=${NODE_ENV} ENGINE_PATH=${ENGINE_PATH} npm run build:pre`; console.log(cmd); - exec(cmd); + execSync(cmd, { stdio: 'inherit' }); } }; } -// define supported module overrides -const aliasEntries = { - '@playcanvas/pcui/react': PCUI_REACT_PATH, - '@playcanvas/pcui/styles': PCUI_STYLES_PATH -}; +function getEngineTargets() { + const targets = [ + // Outputs: dist/iframe/playcanvas-extras.mjs + scriptTargetEs6('pcx', '../extras/index.js', 'dist/iframe/playcanvas-extras.mjs') + ]; + if (NODE_ENV === 'production') { + // Outputs: dist/iframe/playcanvas.mjs + targets.push(buildTarget('release', 'es6', '../src/index.js', 'dist/iframe')); + } + if (NODE_ENV === 'production' || NODE_ENV === 'development') { + // Outputs: dist/iframe/playcanvas.dbg.mjs + targets.push(buildTarget('debug', 'es6', '../src/index.js', 'dist/iframe')); + } + if (NODE_ENV === 'production' || NODE_ENV === 'profiler') { + // Outputs: dist/iframe/playcanvas.prf.mjs + targets.push(buildTarget('profiler', 'es6', '../src/index.js', 'dist/iframe')); + } + return targets; +} -/** @type {RollupOptions[]} */ -const targets = [ +export default [ + { + input: 'src/static/index.html', + output: { + file: `dist/copy.tmp` + }, + plugins: [ + buildAndWatchStandaloneExamples(), + copyStaticFiles(STATIC_FILES), + timestamp() + ] + }, { // A debug build is ~2.3MB and a release build ~0.6MB input: 'src/app/index.mjs', @@ -204,7 +215,13 @@ const targets = [ format: 'umd' }, plugins: [ - alias({ entries: aliasEntries }), + alias({ + entries: { + // define supported module overrides + '@playcanvas/pcui/react': PCUI_REACT_PATH, + '@playcanvas/pcui/styles': PCUI_STYLES_PATH + } + }), commonjs(), resolve(), replace({ @@ -217,43 +234,5 @@ const targets = [ timestamp() ] }, - { - input: 'src/static/index.html', - output: { - file: `dist/copy.tmp` - }, - plugins: [ - copyStaticFiles(staticFiles), - buildAndWatchStandaloneExamples(), - timestamp() - ] - }, - scriptTargetEs6('pcx', '../extras/index.js', 'dist/iframe/playcanvas-extras.mjs') + ...getEngineTargets() ]; - -// We skip building PlayCanvas ourselves when ENGINE_PATH is given. -// In that case we have a watcher which copies all necessary files. -if (ENGINE_PATH === '') { - /** @type {buildTarget} */ - const pushTarget = (...args) => { - targets.push(buildTarget(...args)); - }; - if (NODE_ENV === 'production') { - // Outputs: dist/iframe/playcanvas.mjs - pushTarget('release', 'es6', '../src/index.js', 'dist/iframe'); - // Outputs: dist/iframe/playcanvas.dbg.mjs - pushTarget('debug', 'es6', '../src/index.js', 'dist/iframe'); - // Outputs: dist/iframe/playcanvas.prf.mjs - pushTarget('profiler', 'es6', '../src/index.js', 'dist/iframe'); - } else if (NODE_ENV === 'development') { - // Outputs: dist/iframe/playcanvas.dbg.mjs - pushTarget('debug', 'es6', '../src/index.js', 'dist/iframe'); - } else if (NODE_ENV === 'profiler') { - // Outputs: dist/iframe/playcanvas.prf.mjs - pushTarget('profiler', 'es6', '../src/index.js', 'dist/iframe'); - } else { - console.warn("NODE_ENV is neither production, development nor profiler."); - } -} - -export default targets; diff --git a/examples/scripts/standalone-html.mjs b/examples/scripts/standalone-html.mjs index d100ce282f0..40979190610 100644 --- a/examples/scripts/standalone-html.mjs +++ b/examples/scripts/standalone-html.mjs @@ -33,11 +33,11 @@ function engineFor(type) { case 'DEVELOPMENT': return './ENGINE_PATH/index.js'; case 'PERFORMANCE': - return './playcanvas.prf.mjs/index.js'; + return './playcanvas.prf.mjs'; case 'DEBUG': - return './playcanvas.dbg.mjs/index.js'; + return './playcanvas.dbg.mjs'; } - return './playcanvas.mjs/index.js'; + return './playcanvas.mjs'; } /** diff --git a/examples/utils.mjs b/examples/utils.mjs new file mode 100644 index 00000000000..37940885d4d --- /dev/null +++ b/examples/utils.mjs @@ -0,0 +1,21 @@ +const regexPatterns = [ + /^\s*export\s*\*\s*from\s*.+\s*;\s*$/gm, + /^\s*export\s*{.*}\s*from\s*.+\s*;\s*$/gm, + /^\s*import\s*.+\s*;\s*$/gm, +]; + +/** + * Checks if the provided content matches any of a set of patterns indicative of an ES Module with external dependencies. + * Patterns checked include certain export and import statement formats. + * + * @param {string} content The file content to test. + * @returns {boolean} Whether the content is likely an ES Module with external dependencies. + * @example + * isModuleWithExternalDependencies(` + * // Testing variants: + * export * from './index.mjs'; + * export { Ray } from './core/shape/ray.js'; + * import './polyfill/OESVertexArrayObject.js'; + * `); + */ +export const isModuleWithExternalDependencies = (content) => regexPatterns.some(pattern => pattern.test(content));