From c5be00c77d27b3ea1ea46b598787ce8f4aa06b41 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tobias=20=C3=85str=C3=B6m?= Date: Fri, 13 Mar 2026 08:47:13 +0100 Subject: [PATCH] feat: migrate all commands to esm (#1932) * feat: migrate all commands to esm * chore: fix server build * chore: fix serve * chore: adjust serve * chore: adjust serve * chore: serve esm only * chore: serve esm only --- commands/build/command.js | 8 +- commands/build/lib/build.js | 20 ++-- commands/build/lib/config.js | 16 ++- commands/build/lib/init-config.js | 7 +- commands/build/lib/resolveNative.js | 6 +- commands/build/lib/systemjs.js | 2 +- commands/build/lib/watch.js | 19 +-- commands/build/package.json | 3 +- commands/cli/lib/checkNodeVersion.js | 2 +- commands/cli/lib/index.js | 54 ++++++--- commands/cli/package.json | 1 + commands/create/command.js | 5 +- commands/create/lib/create.js | 23 ++-- commands/create/package.json | 1 + commands/sense/command.js | 7 +- commands/sense/lib/api-entry.js | 11 +- commands/sense/lib/build.js | 31 +++-- commands/sense/lib/init-config.js | 2 +- commands/sense/lib/sense.js | 5 +- commands/sense/package.json | 1 + commands/serve/command.js | 7 +- commands/serve/lib/fixtures.js | 7 +- commands/serve/lib/init-config.js | 7 +- commands/serve/lib/oauth-router.js | 10 +- commands/serve/lib/serve.js | 27 +++-- commands/serve/lib/snapshot-router.js | 8 +- commands/serve/lib/snapshot-server.js | 5 +- commands/serve/lib/webpack.build.js | 53 +++++---- commands/serve/lib/webpack.prod.js | 24 +++- commands/serve/lib/webpack.serve.js | 42 +++---- commands/serve/package.json | 6 + .../hooks/useDeauthorizePrevOAuthInstance.js | 3 +- rollup.config.js | 3 + test/component/barchart/barchart.test.cjs | 3 +- test/component/hooks/hooks.test.cjs | 3 +- test/component/object/sn.test.cjs | 3 +- test/integration/table/table.test.cjs | 3 +- test/mashup/server.cjs | 4 +- yarn.lock | 112 +++++++++++++++--- 39 files changed, 368 insertions(+), 186 deletions(-) diff --git a/commands/build/command.js b/commands/build/command.js index 60a07db98..644e80342 100644 --- a/commands/build/command.js +++ b/commands/build/command.js @@ -1,8 +1,8 @@ -const build = require('./lib/build'); +/* eslint-disable import/extensions */ +import build from './lib/build.js'; +import init from './lib/init-config.js'; -const init = require('./lib/init-config'); - -module.exports = { +export default { command: 'build', desc: 'Build visualization', builder(yargs) { diff --git a/commands/build/lib/build.js b/commands/build/lib/build.js index bbaa14c7f..91dd45144 100644 --- a/commands/build/lib/build.js +++ b/commands/build/lib/build.js @@ -1,13 +1,13 @@ -/* eslint-disable no-console */ -const path = require('path'); -const extend = require('extend'); -const yargs = require('yargs'); -const rollup = require('rollup'); +/* eslint-disable no-console, import/extensions */ +import path from 'path'; +import extend from 'extend'; +import yargs from 'yargs/yargs'; +import * as rollup from 'rollup'; -const initConfig = require('./init-config'); -const config = require('./config'); -const watch = require('./watch'); -const systemjsBehaviours = require('./systemjs'); +import initConfig from './init-config.js'; +import config from './config.js'; +import watch from './watch.js'; +import systemjsBehaviours from './systemjs.js'; const umd = async (argv) => { const c = config({ @@ -89,4 +89,4 @@ async function build(argv = {}) { return undefined; } -module.exports = build; +export default build; diff --git a/commands/build/lib/config.js b/commands/build/lib/config.js index 4d00a1155..c15cc65e3 100644 --- a/commands/build/lib/config.js +++ b/commands/build/lib/config.js @@ -1,5 +1,12 @@ -const fs = require('fs'); -const path = require('path'); +/* eslint-disable import/extensions */ +import fs from 'fs'; +import path from 'path'; +import { createRequire } from 'module'; +import { visualizer } from 'rollup-plugin-visualizer'; + +import resolveNative from './resolveNative.js'; + +const require = createRequire(import.meta.url); const babel = require('@rollup/plugin-babel'); const postcss = require('rollup-plugin-postcss'); const replace = require('@rollup/plugin-replace'); @@ -8,13 +15,10 @@ const { nodeResolve } = require('@rollup/plugin-node-resolve'); const commonjs = require('@rollup/plugin-commonjs'); const terser = require('@rollup/plugin-terser'); const babelPreset = require('@babel/preset-env'); -const { visualizer } = require('rollup-plugin-visualizer'); const browsersList = require('@qlik/browserslist-config'); const babelPresetReact = require('@babel/preset-react'); const babelPresetTypescript = require('@babel/preset-typescript'); -const resolveNative = require('./resolveNative'); - const resolveReplacementStrings = (replacementStrings) => { if (typeof replacementStrings !== 'object') { throw new Error('replacementStrings should be an object with key value pairs of strings!'); @@ -238,4 +242,4 @@ const config = ({ }; }; -module.exports = config; +export default config; diff --git a/commands/build/lib/init-config.js b/commands/build/lib/init-config.js index ac52cc453..d817c5740 100644 --- a/commands/build/lib/init-config.js +++ b/commands/build/lib/init-config.js @@ -1,5 +1,8 @@ /* eslint global-require: 0, no-param-reassign: 0 */ -const fs = require('fs'); +import fs from 'fs'; +import { createRequire } from 'module'; + +const require = createRequire(import.meta.url); const defaultFilename = 'nebula.config.js'; const RX = new RegExp(`${defaultFilename.replace(/\./g, '\\.')}$`); @@ -86,7 +89,7 @@ const watchMiddleware = (argv) => { return argv; }; -module.exports = (yargs) => { +export default (yargs) => { yargs.parserConfiguration({ 'dot-notation': false, // To avoid parsing "replacementStrings" with dot-notation into objects }); diff --git a/commands/build/lib/resolveNative.js b/commands/build/lib/resolveNative.js index 1d3d24f9a..ffdc1b592 100644 --- a/commands/build/lib/resolveNative.js +++ b/commands/build/lib/resolveNative.js @@ -1,10 +1,10 @@ /* eslint-disable no-console */ -const path = require('path'); -const fs = require('fs'); +import path from 'path'; +import fs from 'fs'; // this plugin will check if there is a corresponding .native file for the input file and output the contents of the native file // This plugin should be loaded first. -module.exports = ({ reactNative }) => ({ +export default ({ reactNative }) => ({ async load(id) { if (!reactNative) { return null; diff --git a/commands/build/lib/systemjs.js b/commands/build/lib/systemjs.js index b0d228ff5..45c2f3601 100644 --- a/commands/build/lib/systemjs.js +++ b/commands/build/lib/systemjs.js @@ -17,4 +17,4 @@ const systemjsBehaviours = { enabled: ({ pkg }) => !!pkg.systemjs, }; -module.exports = systemjsBehaviours; +export default systemjsBehaviours; diff --git a/commands/build/lib/watch.js b/commands/build/lib/watch.js index 58ec51586..2dc3a0a8e 100644 --- a/commands/build/lib/watch.js +++ b/commands/build/lib/watch.js @@ -1,11 +1,14 @@ -/* eslint-disable no-console */ -const path = require('path'); -const readline = require('readline'); -const chalk = require('chalk'); -const rollup = require('rollup'); +/* eslint-disable no-console, import/extensions */ +import path from 'path'; +import readline from 'readline'; +import { createRequire } from 'module'; +import chalk from 'chalk'; +import * as rollup from 'rollup'; -const config = require('./config'); -const systemjsBehaviours = require('./systemjs'); +import config from './config.js'; +import systemjsBehaviours from './systemjs.js'; + +const require = createRequire(import.meta.url); const getPackage = (argv, cwd = process.cwd()) => require(path.resolve(argv.cwd || cwd, 'package.json')); // eslint-disable-line @@ -119,4 +122,4 @@ const watch = async (argv) => { }); }; -module.exports = watch; +export default watch; diff --git a/commands/build/package.json b/commands/build/package.json index 305f489cb..edd368b43 100644 --- a/commands/build/package.json +++ b/commands/build/package.json @@ -2,6 +2,7 @@ "name": "@nebula.js/cli-build", "version": "6.7.0", "description": "", + "type": "module", "license": "MIT", "author": "QlikTech International AB", "keywords": [], @@ -38,7 +39,7 @@ "postcss": "^8.5.8", "rollup": "4.59.0", "rollup-plugin-postcss": "4.0.2", - "rollup-plugin-visualizer": "6.0.11", + "rollup-plugin-visualizer": "7.0.1", "yargs": "17.7.2" }, "devDependencies": { diff --git a/commands/cli/lib/checkNodeVersion.js b/commands/cli/lib/checkNodeVersion.js index 3e4ad7621..5ce4c2a87 100644 --- a/commands/cli/lib/checkNodeVersion.js +++ b/commands/cli/lib/checkNodeVersion.js @@ -25,4 +25,4 @@ function checkNodeVersion(pkg) { } } -module.exports = checkNodeVersion; +export default checkNodeVersion; diff --git a/commands/cli/lib/index.js b/commands/cli/lib/index.js index d48bb2aaf..81d7a3c48 100755 --- a/commands/cli/lib/index.js +++ b/commands/cli/lib/index.js @@ -1,26 +1,45 @@ #!/usr/bin/env node -/* eslint-disable no-console */ -const yargs = require('yargs'); -const importCwd = require('import-cwd'); -const checkNodeVersion = require('./checkNodeVersion'); +/* eslint-disable no-console, import/extensions */ +import path from 'path'; +import { createRequire } from 'module'; +import { pathToFileURL } from 'url'; +import yargs from 'yargs/yargs'; +import { hideBin } from 'yargs/helpers'; + +import checkNodeVersion from './checkNodeVersion.js'; + +const require = createRequire(import.meta.url); const pkg = require('../package.json'); checkNodeVersion(pkg); -yargs.usage('nebula [options]'); +const cli = yargs(hideBin(process.argv)); -const tryAddCommand = (m) => { +cli.usage('nebula [options]'); + +const getCommand = async (specifier) => { + const mod = await import(specifier); + return mod.default || mod; +}; + +const tryAddCommand = async (m) => { let cmd; let error; try { - cmd = require(`${m}/command`); // eslint-disable-line + cmd = await getCommand(`${m}/command.js`); } catch (e) { error = e; - cmd = importCwd.silent(`${m}/command`); + try { + const cwdRequire = createRequire(path.join(process.cwd(), 'package.json')); + const resolved = cwdRequire.resolve(`${m}/command`); + cmd = await getCommand(pathToFileURL(resolved).href); + } catch (ee) { + error = ee; + } } if (cmd) { - yargs.command(cmd); + cli.command(cmd); } else { // Print the error if (error) { @@ -28,7 +47,7 @@ const tryAddCommand = (m) => { } // add dummy command in order to instruct user how to install missing package const comm = m.split('-')[1]; - yargs.command({ + cli.command({ command: comm, handler() { throw new Error( @@ -39,8 +58,15 @@ const tryAddCommand = (m) => { } }; -['@nebula.js/cli-create', '@nebula.js/cli-build', '@nebula.js/cli-serve', '@nebula.js/cli-sense'].forEach( - tryAddCommand -); +const run = async () => { + await ['@nebula.js/cli-create', '@nebula.js/cli-build', '@nebula.js/cli-serve', '@nebula.js/cli-sense'].reduce( + (chain, commandPackage) => + // Preserve deterministic command registration order. + chain.then(() => tryAddCommand(commandPackage)), + Promise.resolve() + ); -yargs.demandCommand().alias('h', 'help').wrap(Math.min(80, yargs.terminalWidth())).argv; + cli.demandCommand().alias('h', 'help').wrap(Math.min(80, cli.terminalWidth())).argv; +}; + +run(); diff --git a/commands/cli/package.json b/commands/cli/package.json index 498725208..f08bc2ccf 100644 --- a/commands/cli/package.json +++ b/commands/cli/package.json @@ -2,6 +2,7 @@ "name": "@nebula.js/cli", "version": "6.7.0", "description": "", + "type": "module", "license": "MIT", "author": "QlikTech International AB", "keywords": [], diff --git a/commands/create/command.js b/commands/create/command.js index 1b24f9f21..b9ce351d9 100644 --- a/commands/create/command.js +++ b/commands/create/command.js @@ -1,4 +1,5 @@ -const create = require('./lib/create'); +/* eslint-disable import/extensions */ +import create from './lib/create.js'; const mashup = { command: 'mashup ', @@ -14,7 +15,7 @@ const mashup = { }, }; -module.exports = { +export default { command: 'create ', desc: 'Create a visualization', builder(yargs) { diff --git a/commands/create/lib/create.js b/commands/create/lib/create.js index 37f3a3cb5..f840834da 100644 --- a/commands/create/lib/create.js +++ b/commands/create/lib/create.js @@ -1,14 +1,19 @@ /* eslint-disable no-console */ -const path = require('path'); -const fs = require('fs'); -const { execSync } = require('child_process'); -const chalk = require('chalk'); -const fse = require('fs-extra'); -const ejs = require('ejs'); -const inquirer = require('inquirer'); +import path from 'path'; +import fs from 'fs'; +import { execSync } from 'child_process'; +import { createRequire } from 'module'; +import { fileURLToPath } from 'url'; +import chalk from 'chalk'; +import fse from 'fs-extra'; +import ejs from 'ejs'; +import inquirer from 'inquirer'; +const require = createRequire(import.meta.url); const pkg = require('../package.json'); +const moduleDir = path.dirname(fileURLToPath(import.meta.url)); + const hasYarn = () => { try { execSync('yarnpkg --version', { stdio: 'ignore' }); @@ -63,7 +68,7 @@ const create = async (argv) => { const packageName = name.split('/').slice(-1)[0]; const cwd = process.cwd(); - const templatesRoot = path.resolve(__dirname, '..', 'templates'); + const templatesRoot = path.resolve(moduleDir, '..', 'templates'); const destination = path.resolve(cwd, projectFolder); let options = { @@ -190,4 +195,4 @@ const create = async (argv) => { await end(); }; -module.exports = create; +export default create; diff --git a/commands/create/package.json b/commands/create/package.json index 7b73b151f..b3dbb29cc 100644 --- a/commands/create/package.json +++ b/commands/create/package.json @@ -2,6 +2,7 @@ "name": "@nebula.js/cli-create", "version": "6.7.0", "description": "", + "type": "module", "license": "MIT", "author": "QlikTech International AB", "keywords": [], diff --git a/commands/sense/command.js b/commands/sense/command.js index 8e36722e3..8a744f9f7 100644 --- a/commands/sense/command.js +++ b/commands/sense/command.js @@ -1,7 +1,8 @@ -const init = require('./lib/init-config'); -const sense = require('./lib/sense'); +/* eslint-disable import/extensions */ +import init from './lib/init-config.js'; +import sense from './lib/sense.js'; -module.exports = { +export default { command: 'sense', desc: 'Build a nebula visualization as a Qlik Sense extension', builder(yargs) { diff --git a/commands/sense/lib/api-entry.js b/commands/sense/lib/api-entry.js index c430218af..15b731086 100644 --- a/commands/sense/lib/api-entry.js +++ b/commands/sense/lib/api-entry.js @@ -1,9 +1,10 @@ -const extend = require('extend'); -const yargs = require('yargs'); -const initConfig = require('./init-config'); -const sense = require('./sense'); +/* eslint-disable import/extensions */ +import extend from 'extend'; +import yargs from 'yargs/yargs'; +import initConfig from './init-config.js'; +import sense from './sense.js'; -module.exports = (argv) => { +export default (argv) => { // not runnning via command line, run the config to inject default values const defaultBuildConfig = initConfig(yargs([])).argv; const senseConfig = extend(true, {}, defaultBuildConfig, argv); diff --git a/commands/sense/lib/build.js b/commands/sense/lib/build.js index b52a08a63..01846d087 100644 --- a/commands/sense/lib/build.js +++ b/commands/sense/lib/build.js @@ -1,19 +1,24 @@ -/* eslint-disable no-console */ -const path = require('path'); -const fs = require('fs-extra'); +/* eslint-disable no-console, import/extensions */ +import path from 'path'; +import { fileURLToPath } from 'url'; +import { createRequire } from 'module'; +import fs from 'fs-extra'; -const rollup = require('rollup'); -const { nodeResolve } = require('@rollup/plugin-node-resolve'); -const common = require('@rollup/plugin-commonjs'); -const babel = require('@rollup/plugin-babel'); -const terser = require('@rollup/plugin-terser'); +import * as rollup from 'rollup'; +import { nodeResolve } from '@rollup/plugin-node-resolve'; +import common from '@rollup/plugin-commonjs'; +import babel from '@rollup/plugin-babel'; +import terser from '@rollup/plugin-terser'; + +const require = createRequire(import.meta.url); +const moduleDir = path.dirname(fileURLToPath(import.meta.url)); async function build(argv) { const cwd = process.cwd(); - const supernovaPkg = require(path.resolve(cwd, 'package.json')); // eslint-disable-line + const supernovaPkg = require(path.resolve(cwd, 'package.json')); - let extName = supernovaPkg.name.split('/').reverse()[0]; // replace(/\//, '-').replace('@', ''); + let extName = supernovaPkg.name.split('/').reverse()[0]; const { main } = supernovaPkg; @@ -32,7 +37,7 @@ async function build(argv) { const createQextFiles = () => { const qext = supernovaPkg.qext || {}; if (argv.meta) { - const meta = require(path.resolve(cwd, argv.meta)); // eslint-disable-line + const meta = require(path.resolve(cwd, argv.meta)); Object.assign(qext, meta); } const contents = { @@ -46,7 +51,7 @@ async function build(argv) { supernova: true, }; - let qextjs = fs.readFileSync(path.resolve(__dirname, extDefinition ? '../src/ext.js' : '../src/empty-ext.js'), { + let qextjs = fs.readFileSync(path.resolve(moduleDir, extDefinition ? '../src/ext.js' : '../src/empty-ext.js'), { encoding: 'utf8', }); qextjs = qextjs.replace('{{DIST}}', `./${main.replace(/^[./]*/, '').replace(/\.js$/, '')}`); @@ -129,4 +134,4 @@ async function build(argv) { createQextFiles(); } -module.exports = build; +export default build; diff --git a/commands/sense/lib/init-config.js b/commands/sense/lib/init-config.js index 8198f9084..781e6f9f2 100644 --- a/commands/sense/lib/init-config.js +++ b/commands/sense/lib/init-config.js @@ -29,4 +29,4 @@ const options = { }, }; -module.exports = (yargs) => yargs.options(options); +export default (yargs) => yargs.options(options); diff --git a/commands/sense/lib/sense.js b/commands/sense/lib/sense.js index 1a939654f..a13376074 100644 --- a/commands/sense/lib/sense.js +++ b/commands/sense/lib/sense.js @@ -1,4 +1,5 @@ -const build = require('./build'); +/* eslint-disable import/extensions */ +import build from './build.js'; function sense(argv) { if (argv.legacy) { @@ -7,4 +8,4 @@ function sense(argv) { } return build(argv); } -module.exports = sense; +export default sense; diff --git a/commands/sense/package.json b/commands/sense/package.json index 40e6d04b8..d24d6ff0f 100644 --- a/commands/sense/package.json +++ b/commands/sense/package.json @@ -2,6 +2,7 @@ "name": "@nebula.js/cli-sense", "version": "6.7.0", "description": "Build a supernova as a Qlik Sense extension", + "type": "module", "license": "MIT", "author": "QlikTech International AB", "keywords": [ diff --git a/commands/serve/command.js b/commands/serve/command.js index d69364553..bf3624316 100644 --- a/commands/serve/command.js +++ b/commands/serve/command.js @@ -1,7 +1,8 @@ -const serve = require('./lib/serve'); -const init = require('./lib/init-config'); +/* eslint-disable import/extensions */ +import serve from './lib/serve.js'; +import init from './lib/init-config.js'; -module.exports = { +export default { command: 'serve', desc: 'Start a development server', builder(yargs) { diff --git a/commands/serve/lib/fixtures.js b/commands/serve/lib/fixtures.js index 0b9c5dbe4..402ccb280 100644 --- a/commands/serve/lib/fixtures.js +++ b/commands/serve/lib/fixtures.js @@ -9,8 +9,11 @@ window.serveFixtures = { const k = ['/', './'].some((v) => key.startsWith(v)) ? key : `./${key}`; let context; try { - // see: https://webpack.js.org/guides/dependency-management/#requirecontext - context = require.context('fixtures', true, /\.fix\.js$/); + // ESM-safe context API in webpack 5 + context = import.meta.webpackContext('fixtures', { + recursive: true, + regExp: /\.fix\.js$/, + }); } catch (_) { throw new Error('Specified "--fixturePath" does not exist'); } diff --git a/commands/serve/lib/init-config.js b/commands/serve/lib/init-config.js index 7e12319d5..73a0d47d0 100644 --- a/commands/serve/lib/init-config.js +++ b/commands/serve/lib/init-config.js @@ -1,6 +1,9 @@ /* eslint global-require: 0 */ -const fs = require('fs'); +import fs from 'fs'; +import { createRequire } from 'module'; + +const require = createRequire(import.meta.url); const defaultFilename = 'nebula.config.js'; const RX = new RegExp(`${defaultFilename.replace(/\./g, '\\.')}$`); @@ -96,7 +99,7 @@ const options = { }, }; -module.exports = (yargs) => +export default (yargs) => yargs.options(options).config('config', (configPath) => { if (configPath === null) { return {}; diff --git a/commands/serve/lib/oauth-router.js b/commands/serve/lib/oauth-router.js index e4f97e497..94fe649ce 100644 --- a/commands/serve/lib/oauth-router.js +++ b/commands/serve/lib/oauth-router.js @@ -1,5 +1,5 @@ -const express = require('express'); -const { Auth, AuthType } = require('@qlik/sdk'); +import express from 'express'; +import { Auth, AuthType } from '@qlik/sdk'; let prevHost = null; let prevClientId = null; @@ -22,7 +22,7 @@ const getAuthInstance = (returnToOrigin, host, clientId) => { return authInstance; }; -const OAuthRouter = ({ originUrl }) => { +export const OAuthRouter = ({ originUrl }) => { const router = express.Router(); let cachedHost = null; @@ -116,6 +116,4 @@ const OAuthRouter = ({ originUrl }) => { return router; }; -const getAvailableAuthInstance = () => authInstance; - -module.exports = { OAuthRouter, getAvailableAuthInstance }; +export const getAvailableAuthInstance = () => authInstance; diff --git a/commands/serve/lib/serve.js b/commands/serve/lib/serve.js index 58895de91..ce31332ee 100644 --- a/commands/serve/lib/serve.js +++ b/commands/serve/lib/serve.js @@ -1,15 +1,18 @@ -const path = require('path'); -const fs = require('fs'); -const portfinder = require('portfinder'); -const extend = require('extend'); -const yargs = require('yargs'); -const WebSocket = require('ws').Server; -const chokidar = require('chokidar'); -const build = require('@nebula.js/cli-build'); +/* eslint-disable import/extensions */ +import path from 'path'; +import fs from 'fs'; +import { createRequire } from 'module'; +import portfinder from 'portfinder'; +import extend from 'extend'; +import yargs from 'yargs/yargs'; +import { WebSocketServer } from 'ws'; +import chokidar from 'chokidar'; +import build from '@nebula.js/cli-build'; -const initConfig = require('./init-config'); +import initConfig from './init-config.js'; +import webpackServe from './webpack.serve.js'; -const webpackServe = require('./webpack.serve'); +const require = createRequire(import.meta.url); const initiateWatch = async ({ snPath = '', snName, host }) => { // TODO - timeout @@ -20,7 +23,7 @@ const initiateWatch = async ({ snPath = '', snName, host }) => { const wsPort = await portfinder.getPortPromise({ port: 5001, host }); - const ws = new WebSocket({ + const ws = new WebSocketServer({ port: wsPort, clientTracking: true, }); @@ -92,7 +95,7 @@ const initiateWatch = async ({ snPath = '', snName, host }) => { }; }; -module.exports = async (argv) => { +export default async (argv) => { let context = process.cwd(); let defaultServeConfig = {}; let runFromDirectory = false; diff --git a/commands/serve/lib/snapshot-router.js b/commands/serve/lib/snapshot-router.js index e362a8de9..7dc85d23e 100644 --- a/commands/serve/lib/snapshot-router.js +++ b/commands/serve/lib/snapshot-router.js @@ -1,7 +1,7 @@ -const express = require('express'); -const bodyParser = require('body-parser'); +import express from 'express'; +import bodyParser from 'body-parser'; -module.exports = function router({ base, snapshotUrl, snapshooter }) { +export default function router({ base, snapshotUrl, snapshooter }) { const r = express.Router(); r.use( @@ -55,4 +55,4 @@ module.exports = function router({ base, snapshotUrl, snapshooter }) { }); return r; -}; +} diff --git a/commands/serve/lib/snapshot-server.js b/commands/serve/lib/snapshot-server.js index 13c25dbb4..8fa6b38c8 100644 --- a/commands/serve/lib/snapshot-server.js +++ b/commands/serve/lib/snapshot-server.js @@ -1,6 +1,6 @@ /* eslint no-param-reassign: 0 */ -const puppeteer = require('puppeteer'); +import puppeteer from 'puppeteer'; function snapshooter({ snapshotUrl, chrome = {} } = {}) { const snapshots = {}; @@ -80,4 +80,5 @@ function snapshooter({ snapshotUrl, chrome = {} } = {}) { }, }; } -module.exports = snapshooter; + +export default snapshooter; diff --git a/commands/serve/lib/webpack.build.js b/commands/serve/lib/webpack.build.js index 0f6b09bf6..7f03d6b32 100644 --- a/commands/serve/lib/webpack.build.js +++ b/commands/serve/lib/webpack.build.js @@ -1,14 +1,19 @@ -const path = require('path'); -const crypto = require('crypto'); -const webpack = require('webpack'); -const HtmlWebpackPlugin = require('html-webpack-plugin'); +import path from 'path'; +import crypto from 'crypto'; +import webpack from 'webpack'; +import HtmlWebpackPlugin from 'html-webpack-plugin'; +import { createRequire } from 'module'; +import { fileURLToPath } from 'url'; + +const require = createRequire(import.meta.url); +const moduleDir = path.dirname(fileURLToPath(import.meta.url)); const babelPath = require.resolve('babel-loader'); const babelPresetEnvPath = require.resolve('@babel/preset-env'); const babelPresetReactPath = require.resolve('@babel/preset-react'); const sourceMapLoaderPath = require.resolve('source-map-loader'); -const favicon = path.resolve(__dirname, '../../../docs/assets/njs.png'); +const favicon = path.resolve(moduleDir, '../../../docs/assets/njs.png'); const { version } = require('../package.json'); @@ -20,7 +25,7 @@ const cfg = ({ srcDir, distDir, dev = false, serveConfig = {} }) => { entry: { eRender: [path.resolve(srcDir, 'eRender')], eHub: [path.resolve(srcDir, 'eHub')], - fixtures: [path.resolve(__dirname, './fixtures.js')], + fixtures: [path.resolve(moduleDir, './fixtures.js')], }, devtool: 'source-map', output: { @@ -30,18 +35,24 @@ const cfg = ({ srcDir, distDir, dev = false, serveConfig = {} }) => { }, resolve: { alias: { - '@nebula.js/stardust': path.resolve(__dirname, '../../../apis/stardust/src'), - '@nebula.js/snapshooter/client': path.resolve(__dirname, '../../../apis/snapshooter/src/renderer'), - '@nebula.js/theme': path.resolve(__dirname, '../../../apis/theme/src'), - '@nebula.js/conversion': path.resolve(__dirname, '../../../apis/conversion/src'), - '@nebula.js/locale/all.json$': path.resolve(__dirname, '../../../apis/locale/all.json'), - '@nebula.js/locale': path.resolve(__dirname, '../../../apis/locale/src'), + '@nebula.js/stardust': path.resolve(moduleDir, '../../../apis/stardust/src'), + '@nebula.js/snapshooter/client': path.resolve(moduleDir, '../../../apis/snapshooter/src/renderer'), + '@nebula.js/theme': path.resolve(moduleDir, '../../../apis/theme/src'), + '@nebula.js/conversion': path.resolve(moduleDir, '../../../apis/conversion/src'), + '@nebula.js/locale/all.json$': path.resolve(moduleDir, '../../../apis/locale/all.json'), + '@nebula.js/locale': path.resolve(moduleDir, '../../../apis/locale/src'), fixtures: path.resolve(process.cwd(), serveConfig.fixturePath || ''), }, extensions: ['.dev.js', '.js', '.jsx'], }, module: { rules: [ + { + test: /\.m?js$/, + resolve: { + fullySpecified: false, + }, + }, { test: /\.css$/, use: ['style-loader', 'css-loader'], @@ -107,12 +118,12 @@ const cfg = ({ srcDir, distDir, dev = false, serveConfig = {} }) => { return config; }; -if (!process.env.DEFAULTS) { - module.exports = cfg; -} else { - module.exports = cfg({ - srcDir: path.resolve(__dirname, '../web'), - distDir: path.resolve(__dirname, '../dist'), - dev: process.env.NODE_ENV !== 'production', - }); -} +const defaultExport = !process.env.DEFAULTS + ? cfg + : cfg({ + srcDir: path.resolve(moduleDir, '../web'), + distDir: path.resolve(moduleDir, '../dist'), + dev: process.env.NODE_ENV !== 'production', + }); + +export default defaultExport; diff --git a/commands/serve/lib/webpack.prod.js b/commands/serve/lib/webpack.prod.js index ad64f48ac..367b256f4 100644 --- a/commands/serve/lib/webpack.prod.js +++ b/commands/serve/lib/webpack.prod.js @@ -1,18 +1,22 @@ -const path = require('path'); -const HtmlWebpackPlugin = require('html-webpack-plugin'); +/* eslint-disable import/extensions */ +import path from 'path'; +import { fileURLToPath } from 'url'; +import HtmlWebpackPlugin from 'html-webpack-plugin'; + +const moduleDir = path.dirname(fileURLToPath(import.meta.url)); const isSrc = /^([.]{2}\/)/; const namespace = /^webpack:\/\/([^/]+)\//; const NM = /node_modules/; const WP = /\/\(?webpack\)?/; -const cfg = ({ srcDir = path.resolve(__dirname, '../dist'), serveConfig = {} }) => { +const cfg = ({ srcDir = path.resolve(moduleDir, '../dist'), serveConfig = {} }) => { const folderName = process.cwd().split('/').slice(-1)[0]; const config = { mode: 'development', entry: { - fixtures: [path.resolve(__dirname, './fixtures.js')], + fixtures: [path.resolve(moduleDir, './fixtures.js')], }, devtool: false, infrastructureLogging: { @@ -49,6 +53,16 @@ const cfg = ({ srcDir = path.resolve(__dirname, '../dist'), serveConfig = {} }) fixtures: path.resolve(process.cwd(), serveConfig.fixturePath), }, }, + module: { + rules: [ + { + test: /\.m?js$/, + resolve: { + fullySpecified: false, + }, + }, + ], + }, plugins: [ new HtmlWebpackPlugin({ template: path.resolve(srcDir, 'eRender.html'), @@ -64,4 +78,4 @@ const cfg = ({ srcDir = path.resolve(__dirname, '../dist'), serveConfig = {} }) return config; }; -module.exports = cfg; +export default cfg; diff --git a/commands/serve/lib/webpack.serve.js b/commands/serve/lib/webpack.serve.js index 513442d2c..d1c55393f 100644 --- a/commands/serve/lib/webpack.serve.js +++ b/commands/serve/lib/webpack.serve.js @@ -1,22 +1,25 @@ -/* eslint-disable global-require, no-console */ -const path = require('path'); -const fs = require('fs'); -const homedir = require('os').homedir(); -const chalk = require('chalk'); -const express = require('express'); +/* eslint-disable no-console, import/extensions */ +import path from 'path'; +import fs from 'fs'; +import { homedir } from 'os'; +import { fileURLToPath } from 'url'; +import chalk from 'chalk'; +import express from 'express'; -const webpack = require('webpack'); -const WebpackDevServer = require('webpack-dev-server'); +import webpack from 'webpack'; +import WebpackDevServer from 'webpack-dev-server'; -const snapshooterFn = require('./snapshot-server'); -const snapshotRouter = require('./snapshot-router'); +import snapshooterFn from './snapshot-server.js'; +import snapshotRouter from './snapshot-router.js'; +import { OAuthRouter, getAvailableAuthInstance } from './oauth-router.js'; -const { OAuthRouter, getAvailableAuthInstance } = require('./oauth-router'); +const moduleDir = path.dirname(fileURLToPath(import.meta.url)); +const homeDir = homedir(); -const httpsKeyPath = path.join(homedir, '.certs/key.pem'); -const httpsCertPath = path.join(homedir, '.certs/cert.pem'); +const httpsKeyPath = path.join(homeDir, '.certs/key.pem'); +const httpsCertPath = path.join(homeDir, '.certs/cert.pem'); -module.exports = async ({ +export default async ({ host, port, disableHostCheck, @@ -64,8 +67,8 @@ module.exports = async ({ const renderConfigs = serveConfig.renderConfigs || []; if (dev) { - const webpackConfig = require('./webpack.build'); - const srcDir = path.resolve(__dirname, '../web'); + const webpackConfig = (await import('./webpack.build.js')).default; + const srcDir = path.resolve(moduleDir, '../web'); const distDir = path.resolve(srcDir, '../dist'); contentBase = distDir; config = webpackConfig({ @@ -75,8 +78,8 @@ module.exports = async ({ serveConfig, }); } else { - const webpackConfig = require('./webpack.prod'); - const srcDir = path.resolve(__dirname, '../dist'); + const webpackConfig = (await import('./webpack.prod.js')).default; + const srcDir = path.resolve(moduleDir, '../dist'); contentBase = srcDir; config = webpackConfig({ srcDir, @@ -180,8 +183,7 @@ module.exports = async ({ app.use('/resources', express.static(serveConfig.resources)); } - app.use('/assets', express.static(path.resolve(__dirname, '../assets'))); - + app.use('/assets', express.static(path.resolve(moduleDir, '../assets'))); return middlewares; }, proxy: [ diff --git a/commands/serve/package.json b/commands/serve/package.json index b9797cdcc..25d9b856d 100644 --- a/commands/serve/package.json +++ b/commands/serve/package.json @@ -2,6 +2,7 @@ "name": "@nebula.js/cli-serve", "version": "6.7.0", "description": "", + "type": "module", "license": "MIT", "author": "QlikTech International AB", "keywords": [], @@ -13,6 +14,11 @@ "url": "https://github.com/qlik-oss/nebula.js.git" }, "main": "lib/serve.js", + "exports": { + ".": "./lib/serve.js", + "./command.js": "./command.js", + "./lib/snapshot-server": "./lib/snapshot-server.js" + }, "files": [ "assets", "command.js", diff --git a/commands/serve/web/hooks/useDeauthorizePrevOAuthInstance.js b/commands/serve/web/hooks/useDeauthorizePrevOAuthInstance.js index ebd8a8101..8151cb56c 100644 --- a/commands/serve/web/hooks/useDeauthorizePrevOAuthInstance.js +++ b/commands/serve/web/hooks/useDeauthorizePrevOAuthInstance.js @@ -1,7 +1,6 @@ +import { useEffect } from 'react'; import { useRootContext } from '../contexts/RootContext'; -const { useEffect } = require('react'); - export const useDeauthorizePrevOAuthInstance = () => { const { cachedConnectionsData } = useRootContext(); diff --git a/rollup.config.js b/rollup.config.js index 32c472f53..d04c278a7 100644 --- a/rollup.config.js +++ b/rollup.config.js @@ -121,6 +121,9 @@ const config = ({ format = 'umd', debug = false, file, targetPkg }) => { if (warning.code === 'CIRCULAR_DEPENDENCY' && warning.message.includes(`node_modules/semver`)) { return; } + if (warning.code === 'CIRCULAR_DEPENDENCY' && warning.message.includes(`node_modules/@qlik/api`)) { + return; + } warn(warning); }, input: path.resolve(cwd, 'src', 'index'), diff --git a/test/component/barchart/barchart.test.cjs b/test/component/barchart/barchart.test.cjs index b791b0910..d4ada424e 100644 --- a/test/component/barchart/barchart.test.cjs +++ b/test/component/barchart/barchart.test.cjs @@ -1,13 +1,14 @@ const path = require('path'); -const serve = require('@nebula.js/cli-serve'); const { test, expect } = require('@playwright/test'); const snSelector = '.njs-viz'; test.describe('bar chart', () => { let s; + let serve; test.beforeAll(async () => { + ({ default: serve } = await import('@nebula.js/cli-serve')); s = await serve({ entry: path.resolve(__dirname, 'sn-barchart'), config: 'nebula.config.cjs', diff --git a/test/component/hooks/hooks.test.cjs b/test/component/hooks/hooks.test.cjs index 7269a4a54..2203056c1 100644 --- a/test/component/hooks/hooks.test.cjs +++ b/test/component/hooks/hooks.test.cjs @@ -1,5 +1,4 @@ const path = require('path'); -const serve = require('@nebula.js/cli-serve'); const { test, expect } = require('@playwright/test'); const snSelector = '.njs-viz'; @@ -7,8 +6,10 @@ const snSelector = '.njs-viz'; test.describe('hooks', () => { let s; let page; + let serve; test.beforeAll(async ({ browser }) => { + ({ default: serve } = await import('@nebula.js/cli-serve')); s = await serve({ entry: path.resolve(__dirname, 'sn-hooks'), config: 'nebula.config.cjs', diff --git a/test/component/object/sn.test.cjs b/test/component/object/sn.test.cjs index 50da94755..3d9fc7222 100644 --- a/test/component/object/sn.test.cjs +++ b/test/component/object/sn.test.cjs @@ -1,4 +1,3 @@ -const serve = require('@nebula.js/cli-serve'); const { test, expect } = require('@playwright/test'); const snSelector = '.njs-viz'; @@ -6,8 +5,10 @@ const errorSelector = '.njs-cell [data-tid="error-title"]'; test.describe('sn', () => { let s; + let serve; test.beforeAll(async () => { + ({ default: serve } = await import('@nebula.js/cli-serve')); s = await serve({ open: false, config: 'nebula.config.cjs', diff --git a/test/integration/table/table.test.cjs b/test/integration/table/table.test.cjs index fa664f929..8563690e3 100644 --- a/test/integration/table/table.test.cjs +++ b/test/integration/table/table.test.cjs @@ -1,13 +1,14 @@ const path = require('path'); -const serve = require('@nebula.js/cli-serve'); const { test, expect } = require('@playwright/test'); const content = '.simple-table'; test.describe('Table visualization', () => { let s; + let serve; test.beforeAll(async () => { + ({ default: serve } = await import('@nebula.js/cli-serve')); s = await serve({ entry: path.resolve(__dirname, 'sn-table'), config: 'nebula.config.cjs', diff --git a/test/mashup/server.cjs b/test/mashup/server.cjs index b2a3457a2..6ef28f232 100644 --- a/test/mashup/server.cjs +++ b/test/mashup/server.cjs @@ -13,8 +13,8 @@ module.exports = async () => { const args = yargs(process.argv.slice(2)).argv; - // eslint-disable-next-line - snapshooter = require('@nebula.js/cli-serve/lib/snapshot-server')({ + const { default: createSnapshooter } = await import('@nebula.js/cli-serve/lib/snapshot-server'); + snapshooter = createSnapshooter({ snapshotUrl: `${url}/snaps/single.html`, chrome: args.chrome || {}, }); diff --git a/yarn.lock b/yarn.lock index 2a6eaccf6..58e335d5b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4775,7 +4775,7 @@ __metadata: postcss: "npm:^8.5.8" rollup: "npm:4.59.0" rollup-plugin-postcss: "npm:4.0.2" - rollup-plugin-visualizer: "npm:6.0.11" + rollup-plugin-visualizer: "npm:7.0.1" tslib: "npm:*" typescript: "npm:>=5.9.3" yargs: "npm:17.7.2" @@ -9084,6 +9084,17 @@ __metadata: languageName: node linkType: hard +"cliui@npm:^9.0.1": + version: 9.0.1 + resolution: "cliui@npm:9.0.1" + dependencies: + string-width: "npm:^7.2.0" + strip-ansi: "npm:^7.1.0" + wrap-ansi: "npm:^9.0.0" + checksum: 10c0/13441832e9efe7c7a76bd2b8e683555c478d461a9f249dc5db9b17fe8d4b47fa9277b503914b90bd00e4a151abb6b9b02b2288972ffe2e5e3ca40bcb1c2330d3 + languageName: node + linkType: hard + "clone-deep@npm:^4.0.1": version: 4.0.1 resolution: "clone-deep@npm:4.0.1" @@ -10117,6 +10128,16 @@ __metadata: languageName: node linkType: hard +"default-browser@npm:^5.4.0": + version: 5.5.0 + resolution: "default-browser@npm:5.5.0" + dependencies: + bundle-name: "npm:^4.1.0" + default-browser-id: "npm:^5.0.0" + checksum: 10c0/576593b617b17a7223014b4571bfe1c06a2581a4eb8b130985d90d253afa3f40999caec70eb0e5776e80d4af6a41cce91018cd3f86e57ad578bf59e46fb19abe + languageName: node + linkType: hard + "defaults@npm:^1.0.3": version: 1.0.3 resolution: "defaults@npm:1.0.3" @@ -14003,6 +14024,13 @@ __metadata: languageName: node linkType: hard +"is-in-ssh@npm:^1.0.0": + version: 1.0.0 + resolution: "is-in-ssh@npm:1.0.0" + checksum: 10c0/fbb4c25d85c543df09997fbe7aeca410ae0c839c5825bba2d4c672df765e9ce0e7558e781b371c0a21d6ef9bbac39b31875617a68eaaea5504438d07db9a2ffa + languageName: node + linkType: hard + "is-inside-container@npm:^1.0.0": version: 1.0.0 resolution: "is-inside-container@npm:1.0.0" @@ -17360,14 +17388,17 @@ __metadata: languageName: node linkType: hard -"open@npm:^8.0.0": - version: 8.4.2 - resolution: "open@npm:8.4.2" +"open@npm:^11.0.0": + version: 11.0.0 + resolution: "open@npm:11.0.0" dependencies: - define-lazy-prop: "npm:^2.0.0" - is-docker: "npm:^2.1.1" - is-wsl: "npm:^2.2.0" - checksum: 10c0/bb6b3a58401dacdb0aad14360626faf3fb7fba4b77816b373495988b724fb48941cad80c1b65d62bb31a17609b2cd91c41a181602caea597ca80dfbcc27e84c9 + default-browser: "npm:^5.4.0" + define-lazy-prop: "npm:^3.0.0" + is-in-ssh: "npm:^1.0.0" + is-inside-container: "npm:^1.0.0" + powershell-utils: "npm:^0.1.0" + wsl-utils: "npm:^0.3.0" + checksum: 10c0/7aeeda4131268ed90f90e7728dda5c46bb0c6205b27a4be3e86ea33593e30dd393423e20e31c00802a8e635ef59becaee33ef9749a8ceb027567cd253e9e7b1e languageName: node linkType: hard @@ -18587,6 +18618,13 @@ __metadata: languageName: node linkType: hard +"powershell-utils@npm:^0.1.0": + version: 0.1.0 + resolution: "powershell-utils@npm:0.1.0" + checksum: 10c0/a64713cf3583259c9ed6be211c06b4b19e8608bcb0f7f6287ffac0a95b8c7582b6b662eea0e201fd659492c8e9f9c5fd0bfc4579645c5add9c1a600075621c95 + languageName: node + linkType: hard + "prelude-ls@npm:^1.2.1": version: 1.2.1 resolution: "prelude-ls@npm:1.2.1" @@ -19747,16 +19785,16 @@ __metadata: languageName: node linkType: hard -"rollup-plugin-visualizer@npm:6.0.11": - version: 6.0.11 - resolution: "rollup-plugin-visualizer@npm:6.0.11" +"rollup-plugin-visualizer@npm:7.0.1": + version: 7.0.1 + resolution: "rollup-plugin-visualizer@npm:7.0.1" dependencies: - open: "npm:^8.0.0" + open: "npm:^11.0.0" picomatch: "npm:^4.0.2" source-map: "npm:^0.7.4" - yargs: "npm:^17.5.1" + yargs: "npm:^18.0.0" peerDependencies: - rolldown: 1.x || ^1.0.0-beta + rolldown: 1.x || ^1.0.0-beta || ^1.0.0-rc rollup: 2.x || 3.x || 4.x peerDependenciesMeta: rolldown: @@ -19765,7 +19803,7 @@ __metadata: optional: true bin: rollup-plugin-visualizer: dist/bin/cli.js - checksum: 10c0/a8461e3b1178791e5834617c0e59b89a2832c0a371632e45c8c6934d17baa39f597e74cece5eaecd244f5b3dd0fab14c695f5860de3f3b0ac25e50a221442817 + checksum: 10c0/8ca591a465554d7a4a348538b35acd8eb796156fe24fb7252457640fa49a5aa399962e2cabb542ae8590a391918ae2927ed492081e5598977f3a69b91d0042f4 languageName: node linkType: hard @@ -21042,6 +21080,17 @@ __metadata: languageName: node linkType: hard +"string-width@npm:^7.2.0": + version: 7.2.0 + resolution: "string-width@npm:7.2.0" + dependencies: + emoji-regex: "npm:^10.3.0" + get-east-asian-width: "npm:^1.0.0" + strip-ansi: "npm:^7.1.0" + checksum: 10c0/eb0430dd43f3199c7a46dcbf7a0b34539c76fe3aa62763d0b0655acdcbdf360b3f66f3d58ca25ba0205f42ea3491fa00f09426d3b7d3040e506878fc7664c9b9 + languageName: node + linkType: hard + "string-width@npm:^8.0.0": version: 8.1.0 resolution: "string-width@npm:8.1.0" @@ -23098,6 +23147,16 @@ __metadata: languageName: node linkType: hard +"wsl-utils@npm:^0.3.0": + version: 0.3.1 + resolution: "wsl-utils@npm:0.3.1" + dependencies: + is-wsl: "npm:^3.1.0" + powershell-utils: "npm:^0.1.0" + checksum: 10c0/b3ba99cc6b71f66457eef598d529beeb8cb57a72646877fe25993894b808c60b82f6d47df5463f0b6e54632272f62f5eaea105c12784fd09b06f500f3f53aa2e + languageName: node + linkType: hard + "xml-name-validator@npm:^5.0.0": version: 5.0.0 resolution: "xml-name-validator@npm:5.0.0" @@ -23231,7 +23290,14 @@ __metadata: languageName: node linkType: hard -"yargs@npm:17.7.2, yargs@npm:^17.5.1, yargs@npm:^17.7.2": +"yargs-parser@npm:^22.0.0": + version: 22.0.0 + resolution: "yargs-parser@npm:22.0.0" + checksum: 10c0/cb7ef81759c4271cb1d96b9351dbbc9a9ce35d3e1122d2b739bf6c432603824fa02c67cc12dcef6ea80283379d63495686e8f41cc7b06c6576e792aba4d33e1c + languageName: node + linkType: hard + +"yargs@npm:17.7.2, yargs@npm:^17.7.2": version: 17.7.2 resolution: "yargs@npm:17.7.2" dependencies: @@ -23291,6 +23357,20 @@ __metadata: languageName: node linkType: hard +"yargs@npm:^18.0.0": + version: 18.0.0 + resolution: "yargs@npm:18.0.0" + dependencies: + cliui: "npm:^9.0.1" + escalade: "npm:^3.1.1" + get-caller-file: "npm:^2.0.5" + string-width: "npm:^7.2.0" + y18n: "npm:^5.0.5" + yargs-parser: "npm:^22.0.0" + checksum: 10c0/bf290e4723876ea9c638c786a5c42ac28e03c9ca2325e1424bf43b94e5876456292d3ed905b853ebbba6daf43ed29e772ac2a6b3c5fb1b16533245d6211778f3 + languageName: node + linkType: hard + "yauzl@npm:^2.10.0": version: 2.10.0 resolution: "yauzl@npm:2.10.0"