diff --git a/maintenance/projects/js-toolkit/packages/babel-plugin-namespace-modules/src/index.js b/maintenance/projects/js-toolkit/packages/babel-plugin-namespace-modules/src/index.js index df40fa8d17..6819012432 100644 --- a/maintenance/projects/js-toolkit/packages/babel-plugin-namespace-modules/src/index.js +++ b/maintenance/projects/js-toolkit/packages/babel-plugin-namespace-modules/src/index.js @@ -263,7 +263,7 @@ export default function ({types: t}) { /** * Add namespace to a module's dependency * @param {String} moduleName dependency module name - * @param {String} namespacePkg package name to use as namespace + * @param {Object} namespacePkg package to use as namespace name * @param {Object} unrolledImports unrolled imports section of .npmbundlerrc file * @return {String} the namespaced module */ diff --git a/maintenance/projects/js-toolkit/packages/liferay-npm-build-tools-common/src/__tests__/namespace.test.js b/maintenance/projects/js-toolkit/packages/liferay-npm-build-tools-common/src/__tests__/namespace.test.js index 01bcd2d23b..74f2f56608 100644 --- a/maintenance/projects/js-toolkit/packages/liferay-npm-build-tools-common/src/__tests__/namespace.test.js +++ b/maintenance/projects/js-toolkit/packages/liferay-npm-build-tools-common/src/__tests__/namespace.test.js @@ -8,9 +8,14 @@ import * as ns from '../namespace'; const pkg = { name: 'a-package', }; +const scopedPkg = { + name: '@scoped/a-package', +}; it('makeNamespace works', () => { expect(ns.makeNamespace(pkg)).toBe('a-package$'); + + expect(ns.makeNamespace(scopedPkg)).toBe('scoped!a-package$'); }); describe('when using regular packages', () => { @@ -18,11 +23,15 @@ describe('when using regular packages', () => { // Package alone + expect(ns.isNamespaced('scoped!a-package$b-package')).toBe(true); expect(ns.isNamespaced('a-package$b-package')).toBe(true); expect(ns.isNamespaced('b-package')).toBe(false); // Package and module + expect(ns.isNamespaced('scoped!a-package$b-package/a-module')).toBe( + true + ); expect(ns.isNamespaced('a-package$b-package/a-module')).toBe(true); expect(ns.isNamespaced('b-package/a-module')).toBe(false); expect(ns.isNamespaced('b-package/a-module/$.a-function')).toBe(false); @@ -32,11 +41,17 @@ describe('when using regular packages', () => { // Package alone + expect(ns.getNamespace('scoped!a-package$b-package')).toBe( + 'scoped!a-package$' + ); expect(ns.getNamespace('a-package$b-package')).toBe('a-package$'); expect(ns.getNamespace('b-package')).toBeNull(); // Package and module + expect(ns.getNamespace('scoped!a-package$b-package/a-module')).toBe( + 'scoped!a-package$' + ); expect(ns.getNamespace('a-package$b-package/a-module')).toBe( 'a-package$' ); @@ -48,16 +63,28 @@ describe('when using regular packages', () => { // Package alone + expect(ns.addNamespace('scoped!a-package$b-package', scopedPkg)).toBe( + 'scoped!a-package$b-package' + ); expect(ns.addNamespace('a-package$b-package', pkg)).toBe( 'a-package$b-package' ); + expect(ns.addNamespace('b-package', scopedPkg)).toBe( + 'scoped!a-package$b-package' + ); expect(ns.addNamespace('b-package', pkg)).toBe('a-package$b-package'); // Package and module + expect( + ns.addNamespace('scoped!a-package$b-package/a-module', scopedPkg) + ).toBe('scoped!a-package$b-package/a-module'); expect(ns.addNamespace('a-package$b-package/a-module', pkg)).toBe( 'a-package$b-package/a-module' ); + expect(ns.addNamespace('b-package/a-module', scopedPkg)).toBe( + 'scoped!a-package$b-package/a-module' + ); expect(ns.addNamespace('b-package/a-module', pkg)).toBe( 'a-package$b-package/a-module' ); @@ -67,10 +94,19 @@ describe('when using regular packages', () => { // Package alone + expect(() => + ns.addNamespace('scoped!other-package$b-package', scopedPkg) + ).toThrow(); expect(() => ns.addNamespace('other-package$b-package', pkg)).toThrow(); // Package and module + expect(() => + ns.addNamespace( + 'scoped!other-package$b-package/a-module', + scopedPkg + ) + ).toThrow(); expect(() => ns.addNamespace('other-package$b-package/a-module', pkg) ).toThrow(); @@ -80,6 +116,11 @@ describe('when using regular packages', () => { // Package alone + expect( + ns.addNamespace('scoped!other-package$b-package', scopedPkg, { + allowOverride: true, + }) + ).toBe('scoped!a-package$b-package'); expect( ns.addNamespace('other-package$b-package', pkg, { allowOverride: true, @@ -88,6 +129,15 @@ describe('when using regular packages', () => { // Package and module + expect( + ns.addNamespace( + 'scoped!other-package$b-package/a-module', + scopedPkg, + { + allowOverride: true, + } + ) + ).toBe('scoped!a-package$b-package/a-module'); expect( ns.addNamespace('other-package$b-package/a-module', pkg, { allowOverride: true, @@ -113,11 +163,17 @@ describe('when using regular packages', () => { // Package alone + expect(ns.removeNamespace('scoped!a-package$b-package')).toBe( + 'b-package' + ); expect(ns.removeNamespace('a-package$b-package')).toBe('b-package'); expect(ns.removeNamespace('b-package')).toBe('b-package'); // Package and module + expect(ns.removeNamespace('scoped!a-package$b-package/a-module')).toBe( + 'b-package/a-module' + ); expect(ns.removeNamespace('a-package$b-package/a-module')).toBe( 'b-package/a-module' ); @@ -135,16 +191,21 @@ describe('when using scoped packages', () => { // Scope alone + expect(ns.isNamespaced('@scoped!a-package$scope')).toBe(true); expect(ns.isNamespaced('@a-package$scope')).toBe(true); expect(ns.isNamespaced('@scope')).toBe(false); // Scope and package + expect(ns.isNamespaced('@scoped!a-package$scope/b-package')).toBe(true); expect(ns.isNamespaced('@a-package$scope/b-package')).toBe(true); expect(ns.isNamespaced('@scope/b-package')).toBe(false); // Scope, package and module + expect( + ns.isNamespaced('@scoped!a-package$scope/b-package/a-module') + ).toBe(true); expect(ns.isNamespaced('@a-package$scope/b-package/a-module')).toBe( true ); @@ -158,6 +219,9 @@ describe('when using scoped packages', () => { // Package alone + expect(ns.getNamespace('@scoped!a-package$scope/b-package')).toBe( + 'scoped!a-package$' + ); expect(ns.getNamespace('@a-package$scope/b-package')).toBe( 'a-package$' ); @@ -165,6 +229,9 @@ describe('when using scoped packages', () => { // Package and module + expect( + ns.getNamespace('@scoped!a-package$scope/b-package/a-module') + ).toBe('scoped!a-package$'); expect(ns.getNamespace('@a-package$scope/b-package/a-module')).toBe( 'a-package$' ); @@ -178,25 +245,46 @@ describe('when using scoped packages', () => { // Scope alone + expect(ns.addNamespace('@scoped!a-package$scope', scopedPkg)).toBe( + '@scoped!a-package$scope' + ); expect(ns.addNamespace('@a-package$scope', pkg)).toBe( '@a-package$scope' ); + expect(ns.addNamespace('@scope', scopedPkg)).toBe( + '@scoped!a-package$scope' + ); expect(ns.addNamespace('@scope', pkg)).toBe('@a-package$scope'); // Scope and package + expect( + ns.addNamespace('@scoped!a-package$scope/b-package', scopedPkg) + ).toBe('@scoped!a-package$scope/b-package'); expect(ns.addNamespace('@a-package$scope/b-package', pkg)).toBe( '@a-package$scope/b-package' ); + expect(ns.addNamespace('@scope/b-package', scopedPkg)).toBe( + '@scoped!a-package$scope/b-package' + ); expect(ns.addNamespace('@scope/b-package', pkg)).toBe( '@a-package$scope/b-package' ); // Scope, package and module + expect( + ns.addNamespace( + '@scoped!a-package$scope/b-package/a-module', + scopedPkg + ) + ).toBe('@scoped!a-package$scope/b-package/a-module'); expect( ns.addNamespace('@a-package$scope/b-package/a-module', pkg) ).toBe('@a-package$scope/b-package/a-module'); + expect(ns.addNamespace('@scope/b-package/a-module', scopedPkg)).toBe( + '@scoped!a-package$scope/b-package/a-module' + ); expect(ns.addNamespace('@scope/b-package/a-module', pkg)).toBe( '@a-package$scope/b-package/a-module' ); @@ -206,12 +294,21 @@ describe('when using scoped packages', () => { // Package alone + expect(() => + ns.addNamespace('@scoped!other-package$scope/b-package', scopedPkg) + ).toThrow(); expect(() => ns.addNamespace('@other-package$scope/b-package', pkg) ).toThrow(); // Package and module + expect(() => + ns.addNamespace( + '@scoped!other-package$scope/b-package/a-module', + scopedPkg + ) + ).toThrow(); expect(() => ns.addNamespace('@other-package$scope/b-package/a-module', pkg) ).toThrow(); @@ -221,6 +318,15 @@ describe('when using scoped packages', () => { // Package alone + expect( + ns.addNamespace( + '@scoped!other-package$scope/b-package', + scopedPkg, + { + allowOverride: true, + } + ) + ).toBe('@scoped!a-package$scope/b-package'); expect( ns.addNamespace('@other-package$scope/b-package', pkg, { allowOverride: true, @@ -229,6 +335,15 @@ describe('when using scoped packages', () => { // Package and module + expect( + ns.addNamespace( + '@scoped!other-package$scope/b-package/a-module', + scopedPkg, + { + allowOverride: true, + } + ) + ).toBe('@scoped!a-package$scope/b-package/a-module'); expect( ns.addNamespace('@other-package$scope/b-package/a-module', pkg, { allowOverride: true, @@ -256,11 +371,15 @@ describe('when using scoped packages', () => { // Scope alone + expect(ns.removeNamespace('@scoped!a-package$scope')).toBe('@scope'); expect(ns.removeNamespace('@a-package$scope')).toBe('@scope'); expect(ns.removeNamespace('@scope')).toBe('@scope'); // Scope and package + expect(ns.removeNamespace('@scoped!a-package$scope/b-package')).toBe( + '@scope/b-package' + ); expect(ns.removeNamespace('@a-package$scope/b-package')).toBe( '@scope/b-package' ); @@ -268,6 +387,9 @@ describe('when using scoped packages', () => { // Scope, package and module + expect( + ns.removeNamespace('@scoped!a-package$scope/b-package/a-module') + ).toBe('@scope/b-package/a-module'); expect(ns.removeNamespace('@a-package$scope/b-package/a-module')).toBe( '@scope/b-package/a-module' ); diff --git a/maintenance/projects/js-toolkit/packages/liferay-npm-build-tools-common/src/namespace.ts b/maintenance/projects/js-toolkit/packages/liferay-npm-build-tools-common/src/namespace.ts index 2d2cc7c065..99094e965a 100644 --- a/maintenance/projects/js-toolkit/packages/liferay-npm-build-tools-common/src/namespace.ts +++ b/maintenance/projects/js-toolkit/packages/liferay-npm-build-tools-common/src/namespace.ts @@ -109,5 +109,20 @@ export function getNamespace(moduleName: string): string { * @return the namespace for modules */ export function makeNamespace({name}: {name: string}): string { + + // Convert `@liferay/frontend-js-web` to `liferay!frontend-js-web`. + // + // `/` would confuse the legacy AMD code and would break backward + // compatibility, so we need to pick a substitute that is legal in URLs and + // filesystems, but not allowed in npm package names (to avoid collisions + // with valid non-scoped package names). + // + // We use ! for scopes in the namespacing package, and $ for scopes in the + // namespaced package so that it is easier to understand and parse. + + if (name.startsWith('@')) { + name = name.substr(1).replace('/', '!'); + } + return name + '$'; } diff --git a/projects/npm-tools/packages/npm-bundler-preset-liferay-dev/config.json b/projects/npm-tools/packages/npm-bundler-preset-liferay-dev/config.json index ff3a493b9d..6572884e47 100644 --- a/projects/npm-tools/packages/npm-bundler-preset-liferay-dev/config.json +++ b/projects/npm-tools/packages/npm-bundler-preset-liferay-dev/config.json @@ -81,7 +81,7 @@ "util": ">=0.10.3", "vm-browserify": ">=0.0.4" }, - "frontend-js-react-web": { + "@liferay/frontend-js-react-web": { "/": ">=1.0.0", "classnames": ">=2.2.6", "formik": ">=1.4.3", @@ -105,7 +105,7 @@ "svg4everybody": ">=2.1.9", "uuid": ">=3.3.2" }, - "frontend-taglib": { + "@liferay/frontend-taglib": { "/": ">=1.0.0" }, "frontend-taglib-chart": { diff --git a/projects/npm-tools/packages/npm-scripts/src/utils/SignalHandler.js b/projects/npm-tools/packages/npm-scripts/src/utils/SignalHandler.js index 4df023bbf9..c73688bdc9 100644 --- a/projects/npm-tools/packages/npm-scripts/src/utils/SignalHandler.js +++ b/projects/npm-tools/packages/npm-scripts/src/utils/SignalHandler.js @@ -16,6 +16,7 @@ const SIGNALS = { SIGINT: 2, SIGQUIT: 3, SIGTERM: 15, + exit: -1, }; const SignalHandler = { @@ -60,7 +61,9 @@ function handleSignal(signal) { } } - process.exit(128 + SIGNALS[signal]); + if (signal !== -1) { + process.exit(128 + SIGNALS[signal]); + } } module.exports = SignalHandler; diff --git a/projects/npm-tools/packages/npm-scripts/src/utils/bundlerNamespace.js b/projects/npm-tools/packages/npm-scripts/src/utils/bundlerNamespace.js new file mode 100644 index 0000000000..bb33ca1c52 --- /dev/null +++ b/projects/npm-tools/packages/npm-scripts/src/utils/bundlerNamespace.js @@ -0,0 +1,30 @@ +/** + * SPDX-FileCopyrightText: © 2019 Liferay, Inc. + * SPDX-License-Identifier: BSD-3-Clause + */ + +function addNamespace(packageName, namespacingPackageName) { + const namespace = makeNamespace(namespacingPackageName); + + if (packageName.indexOf('@') === 0) { + return `@${namespace}$${packageName.substring(1)}`; + } + else { + return `${namespace}$${packageName}`; + } +} + +function makeNamespace(namespacingPackageName) { + let namespace = namespacingPackageName; + + if (namespace.startsWith('@')) { + namespace = namespace.substring(1).replace('/', '!'); + } + + return namespace; +} + +module.exports = { + addNamespace, + makeNamespace, +}; diff --git a/projects/npm-tools/packages/npm-scripts/src/utils/createBridges.js b/projects/npm-tools/packages/npm-scripts/src/utils/createBridges.js index f2e60a9e15..55c7c27c1a 100644 --- a/projects/npm-tools/packages/npm-scripts/src/utils/createBridges.js +++ b/projects/npm-tools/packages/npm-scripts/src/utils/createBridges.js @@ -3,10 +3,10 @@ * SPDX-License-Identifier: BSD-3-Clause */ -const {addNamespace} = require('@liferay/js-toolkit-core'); const fs = require('fs'); const path = require('path'); +const {addNamespace} = require('./bundlerNamespace'); const getNamespacedVersionedPackageName = require('./getNamespacedVersionedPackageName'); const getProjectMainModuleFilePath = require('./getProjectMainModuleFilePath'); const writeBridge = require('./writeBridge'); @@ -76,7 +76,10 @@ module.exports = function (bridges, dir) { JSON.stringify( { ...packageJson, - name: addNamespace(packageJson.name, projectPackageJson), + name: addNamespace( + packageJson.name, + projectPackageJson.name + ), }, null, '\t' diff --git a/projects/npm-tools/packages/npm-scripts/src/utils/createFederationConfig.js b/projects/npm-tools/packages/npm-scripts/src/utils/createFederationConfig.js index f24188b8f9..52b8b612d9 100644 --- a/projects/npm-tools/packages/npm-scripts/src/utils/createFederationConfig.js +++ b/projects/npm-tools/packages/npm-scripts/src/utils/createFederationConfig.js @@ -8,6 +8,7 @@ const { container: {ModuleFederationPlugin}, } = require('webpack'); +const {makeNamespace} = require('./bundlerNamespace'); const createTempFile = require('./createTempFile'); const getMergedConfig = require('./getMergedConfig'); const parseBnd = require('./parseBnd'); @@ -26,10 +27,16 @@ const writeWebpackFederationEntryPoint = require('./writeWebpackFederationEntryP const DEFAULT_REMOTES = [ 'frontend-js-components-web', 'frontend-js-metal-web', - 'frontend-js-react-web', + { + name: '@liferay/frontend-js-react-web', + webContextPath: 'frontend-js-react-web', + }, 'frontend-js-spa-web', 'frontend-js-web', - 'frontend-taglib', + { + name: '@liferay/frontend-taglib', + webContextPath: 'frontend-taglib', + }, 'frontend-taglib-chart', 'frontend-taglib-clay', ]; @@ -189,6 +196,57 @@ module.exports = async function () { remotes = remotes || []; shared = shared || []; + // Prevent webpack from choking on scopes + + const libraryName = makeNamespace(name); + + const federationPluginConfig = { + exposes: { + ...transformExposes(exposes, build.input), + '.': mainFilePath, + }, + filename: 'container.js', + library: { + name: `window[Symbol.for("__LIFERAY_WEBPACK_CONTAINERS__")]["${libraryName}"]`, + type: 'assign', + }, + name, + remoteType: 'script', + remotes: [...DEFAULT_REMOTES, ...remotes].reduce((remotes, remote) => { + let webContextPath; + + if (typeof remote === 'string') { + webContextPath = remote; + } + else { + webContextPath = remote.webContextPath; + remote = remote.name; + } + + // Prevent webpack from choking on scopes + + const libraryName = makeNamespace(remote); + + remotes[remote] = + `window[Symbol.for("__LIFERAY_WEBPACK_CONTAINERS__")]` + + `["${libraryName}"]` + + `@/o/${webContextPath}/__generated__/container.js`; + + return remotes; + }, {}), + shared: [...DEFAULT_SHARED, ...shared].reduce((shared, name) => { + shared[name] = {singleton: true}; + + return shared; + }, {}), + }; + + createTempFile( + 'federation.plugin.config.json', + JSON.stringify(federationPluginConfig, null, 2), + {autoDelete: false} + ); + return { context: process.cwd(), devtool: 'source-map', @@ -222,39 +280,7 @@ module.exports = async function () { ), publicPath: `/o${webContextPath}/__generated__/`, }, - plugins: [ - new ModuleFederationPlugin({ - exposes: { - ...transformExposes(exposes, build.input), - '.': mainFilePath, - }, - filename: 'container.js', - library: { - name: `window[Symbol.for("__LIFERAY_WEBPACK_CONTAINERS__")]["${name}"]`, - type: 'assign', - }, - name, - remoteType: 'script', - remotes: [...DEFAULT_REMOTES, ...remotes].reduce( - (remotes, name) => { - remotes[ - name - ] = `window[Symbol.for("__LIFERAY_WEBPACK_CONTAINERS__")]["${name}"]@/o/${name}/__generated__/container.js`; - - return remotes; - }, - {} - ), - shared: [...DEFAULT_SHARED, ...shared].reduce( - (shared, name) => { - shared[name] = {singleton: true}; - - return shared; - }, - {} - ), - }), - ], + plugins: [new ModuleFederationPlugin(federationPluginConfig)], resolve: { fallback: { path: require.resolve('path-browserify'), diff --git a/projects/npm-tools/packages/npm-scripts/src/utils/createTempFile.js b/projects/npm-tools/packages/npm-scripts/src/utils/createTempFile.js index 32756944d5..2d458617e6 100644 --- a/projects/npm-tools/packages/npm-scripts/src/utils/createTempFile.js +++ b/projects/npm-tools/packages/npm-scripts/src/utils/createTempFile.js @@ -16,12 +16,16 @@ const BUILD_CONFIG = getMergedConfig('npmscripts', 'build'); * * @param {string} filename the name of the file * @param {string|Buffer} content content of the file + * @param {object} options + * Pass {autoDelete:false} to prevent the file from being removed at exit. + * + * The default is {autoDelete:true}. * * @return {object} * An object with the `dispose` function (in case the caller wants to invoke it * before the end of the process) and the `filePath`. */ -function createTempFile(filename, content) { +function createTempFile(filename, content, {autoDelete = true} = {}) { const tempDirPath = path.join(BUILD_CONFIG.temp, 'tmp'); fs.ensureDirSync(tempDirPath); @@ -31,7 +35,9 @@ function createTempFile(filename, content) { fs.writeFileSync(tempFilePath, content); const {dispose} = SignalHandler.onExit(() => { - fs.unlinkSync(tempFilePath); + if (autoDelete) { + fs.unlinkSync(tempFilePath); + } }); return { diff --git a/projects/npm-tools/packages/npm-scripts/src/utils/getNamespacedVersionedPackageName.js b/projects/npm-tools/packages/npm-scripts/src/utils/getNamespacedVersionedPackageName.js index aa6959e663..2c9a1d038f 100644 --- a/projects/npm-tools/packages/npm-scripts/src/utils/getNamespacedVersionedPackageName.js +++ b/projects/npm-tools/packages/npm-scripts/src/utils/getNamespacedVersionedPackageName.js @@ -3,7 +3,7 @@ * SPDX-License-Identifier: BSD-3-Clause */ -const {addNamespace} = require('@liferay/js-toolkit-core'); +const {addNamespace} = require('./bundlerNamespace'); /** * Get the namespaced and versioned package name of a project's dependency as it @@ -19,10 +19,12 @@ const {addNamespace} = require('@liferay/js-toolkit-core'); */ module.exports = function (projectPackageJson, packageJson) { if (packageJson) { - return addNamespace( - `${packageJson.name}@${packageJson.version}`, - projectPackageJson + const namespacedName = addNamespace( + packageJson.name, + projectPackageJson.name ); + + return `${namespacedName}@${packageJson.version}`; } else { return `${projectPackageJson.name}@${projectPackageJson.version}`;