diff --git a/build/ci/postInstall.js b/build/ci/postInstall.js index dd17580bab1..981bd60917c 100644 --- a/build/ci/postInstall.js +++ b/build/ci/postInstall.js @@ -161,9 +161,42 @@ function fixUIFabricForTS49() { }); } +/** + * Ensures that moment is not used by any other npm package other than @jupyterlab/coreutils. + * See comments here build/webpack/moment.js + */ +function verifyMomentIsOnlyUsedByJupyterLabCoreUtils() { + const packageLock = require(path.join(__dirname, '..', '..', 'package-lock.json')); + const packagesAllowedToUseMoment = ['node_modules/@jupyterlab/coreutils', '@jupyterlab/coreutils']; + const otherPackagesUsingMoment = []; + ['packages', 'dependencies'].forEach((key) => { + if (!(key in packageLock)) { + throw new Error(`Invalid package-lock.json, as it does not contain the key '${key}'`); + } + const packages = packageLock[key]; + Object.keys(packages).forEach((packageName) => { + if (packagesAllowedToUseMoment.includes(packageName)) { + return; + } + ['dependencies', 'requires'].forEach((dependencyKey) => { + if (dependencyKey in packages[packageName]) { + const dependenciesOfPackage = packages[packageName][dependencyKey]; + if ('moment' in dependenciesOfPackage) { + otherPackagesUsingMoment.push(`${key}.${dependencyKey}.${packageName}`); + } + } + }); + }); + }); + if (otherPackagesUsingMoment.length > 0) { + throw new Error(`Moment is being used by another package (${otherPackagesUsingMoment.join(', ')}).`); + } +} + fixUIFabricForTS49(); fixJupyterLabRenderers(); makeVariableExplorerAlwaysSorted(); createJupyterKernelWithoutSerialization(); updateJSDomTypeDefinition(); fixStripComments(); +verifyMomentIsOnlyUsedByJupyterLabCoreUtils(); diff --git a/build/webpack/moment.js b/build/webpack/moment.js new file mode 100644 index 00000000000..8080e13640f --- /dev/null +++ b/build/webpack/moment.js @@ -0,0 +1,27 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +// This file has been handcrafted to override the use of moment.js. +// Moment is only used in @jupyterlab/coreutils/lib/time.js +// & that code is not used anywhere in any code path. +// Hence we create a dummy moment object that does nothing. +// & we have checks on CI that ensures no other npm package or other code does not in-directly or directly use moment. + +export default function (dateTime) { + return { + formatNow: () => { + try { + return dateTime.toLocaleString(); + } catch { + return `${dateTime}`; + } + }, + format: () => { + try { + return dateTime.toLocaleTimeString(); + } catch { + return `${dateTime}`; + } + } + }; +} diff --git a/build/webpack/webpack.extension.node.config.js b/build/webpack/webpack.extension.node.config.js index 1fc01c20fc2..2506ddacfe2 100644 --- a/build/webpack/webpack.extension.node.config.js +++ b/build/webpack/webpack.extension.node.config.js @@ -130,10 +130,6 @@ const config = { new copyWebpackPlugin({ patterns: [{ from: './node_modules/zeromq/**/*.json' }] }), new copyWebpackPlugin({ patterns: [{ from: './node_modules/node-gyp-build/**/*' }] }), new copyWebpackPlugin({ patterns: [{ from: './node_modules/@vscode/jupyter-ipywidgets7/dist/*.js' }] }), - new webpack.IgnorePlugin({ - resourceRegExp: /^\.\/locale$/, - contextRegExp: /moment$/ - }), new webpack.DefinePlugin({ IS_PRE_RELEASE_VERSION_OF_JUPYTER_EXTENSION: JSON.stringify( typeof process.env.IS_PRE_RELEASE_VERSION_OF_JUPYTER_EXTENSION === 'string' @@ -147,7 +143,8 @@ const config = { // Pointing pdfkit to a dummy js file so webpack doesn't fall over. // Since pdfkit has been externalized (it gets updated with the valid code by copying the pdfkit files // into the right destination). - pdfkit: path.resolve(__dirname, 'pdfkit.js') + pdfkit: path.resolve(__dirname, 'pdfkit.js'), + moment: path.join(__dirname, 'moment.js') }, extensions: ['.ts', '.js'], plugins: [new tsconfig_paths_webpack_plugin.TsconfigPathsPlugin({ configFile: configFileName })], diff --git a/build/webpack/webpack.extension.web.config.js b/build/webpack/webpack.extension.web.config.js index a514eb7b6fa..9da51766451 100644 --- a/build/webpack/webpack.extension.web.config.js +++ b/build/webpack/webpack.extension.web.config.js @@ -114,10 +114,6 @@ const config = { ) }), new CleanTerminalPlugin(), - new webpack.IgnorePlugin({ - resourceRegExp: /^\.\/locale$/, - contextRegExp: /moment$/ - }), new webpack.optimize.LimitChunkCountPlugin({ maxChunks: 1 }) @@ -130,7 +126,8 @@ const config = { ], alias: { // provides alternate implementation for node module and source files - fs: './fs-empty.js' + fs: './fs-empty.js', + moment: path.join(__dirname, 'moment.js') }, fallback: { os: require.resolve('os-browserify')