diff --git a/NOTICE.txt b/NOTICE.txt deleted file mode 100644 index efa95dd4cbf..00000000000 --- a/NOTICE.txt +++ /dev/null @@ -1 +0,0 @@ -Copyright (c) 2018-2019 SAP SE or an SAP affiliate company. All rights reserved. diff --git a/docs/SAP Corporate Contributor License Agreement.pdf b/docs/SAP Corporate Contributor License Agreement.pdf deleted file mode 100644 index f88845e809f..00000000000 Binary files a/docs/SAP Corporate Contributor License Agreement.pdf and /dev/null differ diff --git a/package.json b/package.json index e5b6142fb75..ed335d94b5d 100644 --- a/package.json +++ b/package.json @@ -6,10 +6,9 @@ "license": "Apache-2.0", "scripts": { "start": "lerna run build:i18n && start-storybook -p 6006 -c .storybook", - "build": "npm-run-all -s build:clean build:esm build:cjs", + "build": "npm-run-all -s build:clean build:bundle", "build:clean": "yarn clean", - "build:esm": "lerna run build --stream", - "build:cjs": "node ./scripts/rollup/build.js", + "build:bundle": "lerna run build --stream", "build:storybook": "lerna run build:i18n && build-storybook -c .storybook -o .out", "pretest": "rimraf coverage && lerna run build:i18n", "test": "jest --config=config/jest.config.js --coverage", @@ -62,7 +61,6 @@ "@typescript-eslint/eslint-plugin": "^3.7.0", "@typescript-eslint/parser": "^3.7.0", "@ui5/webcomponents-tools": "^1.0.0-rc.8", - "babel-code-frame": "^6.26.0", "babel-jest": "^26.2.2", "babel-loader": "^8.1.0", "chalk": "^4.0.0", @@ -78,7 +76,6 @@ "eslint-plugin-react": "^7.20.3", "eslint-plugin-react-hooks": "^4.0.8", "glob": "^7.1.6", - "google-closure-compiler": "^20200406.0.0", "husky": "^4.2.5", "identity-obj-proxy": "^3.0.0", "jest": "^26.2.2", @@ -96,7 +93,7 @@ "react-app-polyfill": "^1.0.6", "rimraf": "^3.0.1", "rollup": "^2.23.0", - "rollup-plugin-strip-banner": "^2.0.0", + "rollup-plugin-terser": "^6.1.0", "shelljs": "^0.8.3", "sinon": "^9.0.2", "targz": "^1.0.1", diff --git a/scripts/rollup/build.js b/scripts/rollup/build.js deleted file mode 100644 index f1956121971..00000000000 --- a/scripts/rollup/build.js +++ /dev/null @@ -1,281 +0,0 @@ -const { rollup } = require('rollup'); -const stripBanner = require('rollup-plugin-strip-banner'); -const { babel } = require('@rollup/plugin-babel'); -const replace = require('@rollup/plugin-replace'); -const { nodeResolve } = require('@rollup/plugin-node-resolve'); -const json = require('@rollup/plugin-json'); -const closure = require('./plugins/closure-plugin'); -const stripUnusedImports = require('./plugins/strip-unused-imports'); -const Bundles = require('./bundles'); -const codeFrame = require('babel-code-frame'); -const chalk = require('chalk'); -const path = require('path'); -const fs = require('fs'); -const Packaging = require('./packaging'); -const Modules = require('./modules'); -const { createDeclarationFiles } = require('./declarations'); -const PATHS = require('../../config/paths'); - -const argv = require('minimist')(process.argv.slice(2)); -const forcePrettyOutput = argv.pretty; -const shouldExtractErrors = argv['extract-errors']; - -process.env.NODE_ENV = 'production'; - -// Errors in promises should be fatal. -let loggedErrors = new Set(); -process.on('unhandledRejection', (err) => { - if (loggedErrors.has(err)) { - // No need to print it twice. - process.exit(1); - } - throw err; -}); - -const { NODE_DEV, NODE_PROD } = Bundles.bundleTypes; - -const closureOptions = { - compilation_level: 'SIMPLE', - language_in: 'ECMASCRIPT5_STRICT', - language_out: 'ECMASCRIPT5_STRICT', - env: 'CUSTOM', - warning_level: 'QUIET', - apply_input_source_maps: false, - use_types_for_optimization: false, - process_common_js_modules: false, - rewrite_polyfills: false -}; - -function handleRollupWarning(warning) { - if (warning.code === 'UNUSED_EXTERNAL_IMPORT') { - const match = warning.message.match(/external module '([^']+)'/); - if (!match || typeof match[1] !== 'string') { - throw new Error('Could not parse a Rollup warning. ' + 'Fix this method.'); - } - // Don't warn. We will remove side effectless require() in a later pass. - return; - } - - if (typeof warning.code === 'string') { - // This is a warning coming from Rollup itself. - // These tend to be important (e.g. clashes in namespaced exports) - // so we'll fail the build on any of them. - console.error(); - console.error(warning.message || warning); - console.error(); - // process.exit(1); - } else { - // The warning is from one of the plugins. - // Maybe it's not important, so just print it. - console.warn(warning.message || warning); - } -} - -function handleRollupError(error) { - loggedErrors.add(error); - if (!error.code) { - console.error(error); - return; - } - console.error(`\x1b[31m-- ${error.code}${error.plugin ? ` (${error.plugin})` : ''} --`); - console.error(error.stack); - if (error.loc && error.loc.file) { - const { file, line, column } = error.loc; - // This looks like an error from Rollup, e.g. missing export. - // We'll use the accurate line numbers provided by Rollup but - // use Babel code frame because it looks nicer. - const rawLines = fs.readFileSync(file, 'utf-8'); - // column + 1 is required due to rollup counting column start position from 0 - // whereas babel-code-frame counts from 1 - const frame = codeFrame(rawLines, line, column + 1, { - highlightCode: true - }); - console.error(frame); - } else if (error.codeFrame) { - // This looks like an error from a plugin (e.g. Babel). - // In this case we'll resort to displaying the provided code frame - // because we can't be sure the reported location is accurate. - console.error(error.codeFrame); - } -} - -function getFilename(name, bundleType) { - // we do this to replace / to -, for react-dom/server - name = name.replace('/', '-'); - switch (bundleType) { - case NODE_DEV: - return `${name}.development.js`; - case NODE_PROD: - return `${name}.production.min.js`; - } -} - -function getFormat(bundleType) { - switch (bundleType) { - case NODE_DEV: - case NODE_PROD: - return `cjs`; - } -} - -function isProductionBundleType(bundleType) { - switch (bundleType) { - case NODE_DEV: - case NODE_PROD: - return true; - default: - throw new Error(`Unknown type: ${bundleType}`); - } -} - -function getPlugins(entry, externals, updateBabelOptions, filename, packageName, bundleType) { - const isProduction = isProductionBundleType(bundleType); - const shouldStayReadable = forcePrettyOutput; - return [ - nodeResolve({ - extensions: ['.js', '.jsx', '.ts', '.tsx', '.json'] - }), - // Remove license headers from individual modules - stripBanner({ - exclude: 'node_modules/**/*' - }), - json(), - // Compile to ES5. - babel(getBabelConfig(updateBabelOptions, bundleType)), - // Turn __DEV__ and process.env checks into constants. - replace({ - exclude: 'node_modules/**', - values: { - __DEV__: isProduction ? 'false' : 'true', - 'process.env.NODE_ENV': isProduction ? "'production'" : "'development'" - } - }), - // Apply dead code elimination and/or minification. - isProduction && - closure( - Object.assign({}, closureOptions, { - // Don't let it create global variables in the browser. - // https://github.com/facebook/react/issues/10909 - assume_function_wrapper: true, - // Works because `google-closure-compiler-js` is forked in Yarn lockfile. - // We can remove this if GCC merges my PR: - // https://github.com/google/closure-compiler/pull/2707 - // and then the compiled version is released via `google-closure-compiler-js`. - renaming: !shouldStayReadable - }) - ), - // HACK to work around the fact that Rollup isn't removing unused, pure-module imports. - // Note that this plugin must be called after closure applies DCE. - isProduction && stripUnusedImports([]) - ].filter(Boolean); -} - -function shouldSkipBundle(bundle, bundleType) { - return bundle.bundleTypes.indexOf(bundleType) === -1; -} - -function getBabelConfig(updateBabelOptions, bundleType, filename) { - let options = { - configFile: path.resolve(PATHS.root, 'babel.config.js'), - babelHelpers: 'runtime', - extensions: ['.js', '.jsx', '.ts', '.tsx', '.json'] - }; - if (updateBabelOptions) { - options = updateBabelOptions(options); - } - - return options; -} - -function getRollupOutputOptions(outputPath, format, globals, bundleType) { - const isProduction = isProductionBundleType(bundleType); - - return Object.assign( - {}, - { - file: outputPath, - format, - globals, - freeze: !isProduction, - interop: false, - sourcemap: false - } - ); -} - -async function createBundle(bundle, bundleType) { - if (shouldSkipBundle(bundle, bundleType)) { - return; - } - - const filename = getFilename(bundle.entry, bundleType); - const logKey = chalk.white.bold(filename) + chalk.dim(` (${bundleType.toLowerCase()})`); - const format = getFormat(bundleType); - const packageName = Packaging.getPackageName(bundle.entry); - - let resolvedEntry = path.resolve(__dirname, '..', '..', 'packages', bundle.entry, 'src', 'index.ts'); //require.resolve(bundle.entry); - - const peerGlobals = Modules.getPeerGlobals(bundle.externals, bundleType); - let externals = Object.keys(peerGlobals); - const deps = Modules.getDependencies(bundleType, bundle.entry); - externals = externals.concat(deps); - - const rollupConfig = { - input: resolvedEntry, - external(id) { - const containsThisModule = (pkg) => id === pkg || id.startsWith(pkg + '/'); - const isProvidedByDependency = externals.some(containsThisModule); - if (isProvidedByDependency) { - return true; - } - return !!peerGlobals[id]; - }, - onwarn: handleRollupWarning, - plugins: getPlugins( - bundle.entry, - externals, - bundle.babel, - filename, - packageName, - bundleType, - bundle.moduleType, - bundle.modulesToStub - ) - }; - const mainOutputPath = Packaging.getBundleOutputPaths(bundleType, filename, packageName); - const rollupOutputOptions = getRollupOutputOptions(mainOutputPath, format, peerGlobals, bundleType); - - console.log(`${chalk.bgYellow.black(' BUILDING ')} ${logKey}`); - try { - const result = await rollup(rollupConfig); - await result.write(rollupOutputOptions); - } catch (error) { - console.log(`${chalk.bgRed.black(' OH NOES! ')} ${logKey}\n`); - handleRollupError(error); - throw error; - } - console.log(`${chalk.bgGreen.black(' COMPLETE ')} ${logKey}\n`); -} - -async function buildEverything() { - // Run them serially for better console output - // and to avoid any potential race conditions. - // eslint-disable-next-line no-for-of-loops/no-for-of-loops - for (const bundle of Bundles.bundles) { - await createBundle(bundle, NODE_DEV); - await createBundle(bundle, NODE_PROD); - createDeclarationFiles(bundle); - } - - await Packaging.prepareNpmPackages(); - - if (shouldExtractErrors) { - console.warn( - '\nWarning: this build was created with --extract-errors enabled.\n' + - 'this will result in extremely slow builds and should only be\n' + - 'used when the error map needs to be rebuilt.\n' - ); - } -} - -buildEverything(); diff --git a/scripts/rollup/bundles.js b/scripts/rollup/bundles.js deleted file mode 100644 index bc865ed51f3..00000000000 --- a/scripts/rollup/bundles.js +++ /dev/null @@ -1,49 +0,0 @@ -const bundleTypes = { - NODE_DEV: 'NODE_DEV', - NODE_PROD: 'NODE_PROD' -}; - -const bundles = [ - { - label: 'main', - bundleTypes: [bundleTypes.NODE_DEV, bundleTypes.NODE_PROD], - entry: 'main', - externals: ['@ui5/webcomponents-react', '@ui5/webcomponents-base'] - }, - { - label: 'base', - bundleTypes: [bundleTypes.NODE_DEV, bundleTypes.NODE_PROD], - entry: 'base', - externals: ['@ui5/webcomponents-react-base', '@ui5/webcomponents-base'] - }, - { - label: 'charts', - bundleTypes: [bundleTypes.NODE_DEV, bundleTypes.NODE_PROD], - entry: 'charts', - externals: ['@ui5/webcomponents-react-charts', '@ui5/webcomponents', '@ui5/webcomponents-base'] - } -]; - -// Copied from facebook/react build setup -// Based on deep-freeze by substack (public domain) -function deepFreeze(o) { - Object.freeze(o); - Object.getOwnPropertyNames(o).forEach(function (prop) { - if ( - o[prop] !== null && - (typeof o[prop] === 'object' || typeof o[prop] === 'function') && - !Object.isFrozen(o[prop]) - ) { - deepFreeze(o[prop]); - } - }); - return o; -} - -// Don't accidentally mutate config as part of the build -deepFreeze(bundles); - -module.exports = { - bundleTypes, - bundles -}; diff --git a/scripts/rollup/configFactory.js b/scripts/rollup/configFactory.js index c61d5186715..d9c72d35ae0 100644 --- a/scripts/rollup/configFactory.js +++ b/scripts/rollup/configFactory.js @@ -7,20 +7,23 @@ const json = require('@rollup/plugin-json'); const micromatch = require('micromatch'); const PATHS = require('../../config/paths'); const { asyncCopyTo, highlightLog } = require('../utils'); +const replace = require('@rollup/plugin-replace'); const glob = require('glob'); +const { terser } = require('rollup-plugin-terser'); +const sh = require('shelljs'); +const { spawnSync } = require('child_process'); process.env.BABEL_ENV = 'production'; process.env.NODE_ENV = 'production'; const rollupConfigFactory = (pkgName, externals = []) => { - const LIB_BASE_PATH = path.resolve(PATHS.packages, pkgName, 'src', 'lib'); + const PKG_BASE_PATH = path.resolve(PATHS.packages, pkgName); + const LIB_BASE_PATH = path.resolve(PKG_BASE_PATH, 'src', 'lib'); const allFilesAndFolders = glob.sync(`${LIB_BASE_PATH}/**/*`); const allLibFiles = allFilesAndFolders.filter((file) => fs.statSync(file).isFile()); - console.log(require('@babel/runtime/package.json').version); - const plugins = [ nodeResolve({ extensions: ['.mjs', '.js', '.json', '.node', '.jsx', '.ts', '.tsx'] @@ -31,10 +34,18 @@ const rollupConfigFactory = (pkgName, externals = []) => { extensions: ['.js', '.jsx', '.ts', '.tsx'], babelHelpers: 'runtime', configFile: path.resolve(PATHS.root, 'babel.config.js') + }), + // Turn __DEV__ and process.env checks into constants. + replace({ + exclude: 'node_modules/**', + values: { + __DEV__: 'false', + 'process.env.NODE_ENV': "'production'" + } }) ]; - const packageJson = require(path.resolve(PATHS.packages, pkgName, 'package.json')); + const packageJson = require(path.resolve(PKG_BASE_PATH, 'package.json')); const externalModules = Array.from( new Set([ 'react', @@ -49,36 +60,75 @@ const rollupConfigFactory = (pkgName, externals = []) => { highlightLog(`Build lib folder for ${pkgName}`); + console.info('Copy License'); + fs.copyFileSync(path.resolve(PATHS.root, 'LICENSE'), path.resolve(PKG_BASE_PATH, `LICENSE`)); + console.info('Copy index file'); - asyncCopyTo( - path.resolve(PATHS.packages, pkgName, 'src', 'index.ts'), - path.resolve(PATHS.packages, pkgName, `index.esm.js`) - ); + asyncCopyTo(path.resolve(PKG_BASE_PATH, 'src', 'index.ts'), path.resolve(PKG_BASE_PATH, `index.esm.js`)); - return allLibFiles.map((file) => ({ - input: file, - external: (id) => { - const containsThisModule = (pkg) => id === pkg || id.startsWith(pkg + '/'); - return externalModules.some(containsThisModule); - }, - treeshake: { - moduleSideEffects: - packageJson.sideEffects === false ? false : (id) => micromatch.isMatch(id, packageJson.sideEffects) - }, - output: [ - { - file: path.resolve( - PATHS.packages, - pkgName, - 'lib', - file.replace(`${LIB_BASE_PATH}${path.sep}`, '').replace(/\.ts$/, '.js') - ), - format: 'es', - sourcemap: true - } - ], - plugins - })); + console.info('Create TS Types'); + const tsConfigPath = path.resolve(PKG_BASE_PATH, 'tsconfig.json'); + if (fs.existsSync(tsConfigPath)) { + spawnSync( + path.resolve(PATHS.nodeModules, '.bin', 'tsc'), + [ + '--project', + tsConfigPath, + '--declaration', + '--emitDeclarationOnly', + '--declarationDir', + PKG_BASE_PATH, + '--removeComments', + 'false' + ], + { stdio: 'inherit' } + ); + } + + const external = (id) => { + const containsThisModule = (pkg) => id === pkg || id.startsWith(pkg + '/'); + return externalModules.some(containsThisModule); + }; + const treeshake = { + moduleSideEffects: + packageJson.sideEffects === false ? false : (id) => micromatch.isMatch(id, packageJson.sideEffects) + }; + return [ + ...allLibFiles.map((file) => ({ + input: file, + external, + treeshake, + output: [ + { + file: path.resolve( + PKG_BASE_PATH, + 'lib', + file.replace(`${LIB_BASE_PATH}${path.sep}`, '').replace(/\.ts$/, '.js') + ), + format: 'es', + sourcemap: true + } + ], + plugins + })), + { + input: path.resolve(PKG_BASE_PATH, 'src', 'index.ts'), + external, + treeshake, + plugins, + output: [ + { + file: path.resolve(PKG_BASE_PATH, 'cjs', `${pkgName}.development.js`), + format: 'cjs' + }, + { + file: path.resolve(PKG_BASE_PATH, 'cjs', `${pkgName}.production.min.js`), + format: 'cjs', + plugins: [terser()] + } + ] + } + ]; }; module.exports = rollupConfigFactory; diff --git a/scripts/rollup/declarations.js b/scripts/rollup/declarations.js deleted file mode 100644 index 5775348c8e4..00000000000 --- a/scripts/rollup/declarations.js +++ /dev/null @@ -1,13 +0,0 @@ -const sh = require('shelljs'); - -const createDeclarationFiles = (bundle) => { - if (sh.test('-e', `packages/${bundle.entry}/tsconfig.json`)) { - sh.exec( - `node_modules/.bin/tsc --project packages/${bundle.entry}/tsconfig.json --declaration --emitDeclarationOnly --declarationDir "packages/${bundle.entry}/" --removeComments false` - ); - } -}; - -module.exports = { - createDeclarationFiles -}; diff --git a/scripts/rollup/modules.js b/scripts/rollup/modules.js deleted file mode 100755 index e96f872507e..00000000000 --- a/scripts/rollup/modules.js +++ /dev/null @@ -1,36 +0,0 @@ -'use strict'; - -// For any external that is used in a DEV-only condition, explicitly -// specify whether it has side effects during import or not. This lets -// us know whether we can safely omit them when they are unused. -// Bundles exporting globals that other modules rely on. -const knownGlobals = Object.freeze({ - react: 'React', - 'react-dom': 'ReactDOM' -}); - -// Given ['react'] in bundle externals, returns { 'react': 'React' }. -function getPeerGlobals(externals, bundleType) { - const peerGlobals = {}; - externals.forEach((name) => { - peerGlobals[name] = knownGlobals[name]; - }); - return peerGlobals; -} - -// Determines node_modules packages that are safe to assume will exist. -function getDependencies(bundleType, entry) { - // Replaces any part of the entry that follow the package name (like - // "/server" in "react-dom/server") by the path to the package settings - // const packageJson = require(entry.replace(/(\/.*)?$/, '/package.json')); - const packageJson = require(`../../packages/${entry}/package.json`); - // Both deps and peerDeps are assumed as accessible. - return Array.from( - new Set([...Object.keys(packageJson.dependencies || {}), ...Object.keys(packageJson.peerDependencies || {})]) - ); -} - -module.exports = { - getPeerGlobals, - getDependencies -}; diff --git a/scripts/rollup/packaging.js b/scripts/rollup/packaging.js deleted file mode 100755 index 22b05ea9d7a..00000000000 --- a/scripts/rollup/packaging.js +++ /dev/null @@ -1,39 +0,0 @@ -'use strict'; - -const { existsSync, readdirSync, unlinkSync } = require('fs'); -const Bundles = require('./bundles'); -const { asyncCopyTo, asyncExecuteCommand, asyncExtractTar, asyncRimRaf } = require('../utils'); - -const { NODE_DEV, NODE_PROD } = Bundles.bundleTypes; - -function getPackageName(name) { - return name; -} - -function getBundleOutputPaths(bundleType, filename, packageName) { - switch (bundleType) { - case NODE_DEV: - case NODE_PROD: - return `packages/${packageName}/cjs/${filename}`; - default: - throw new Error('Unknown bundle type.'); - } -} - -async function prepareNpmPackage(name) { - await Promise.all([ - asyncCopyTo('LICENSE', `packages/${name}/LICENSE`), - asyncCopyTo('NOTICE.txt', `packages/${name}/NOTICE.txt`) - ]); -} - -async function prepareNpmPackages() { - const builtPackageFolders = readdirSync('packages').filter((dir) => dir.charAt(0) !== '.'); - await Promise.all(builtPackageFolders.map(prepareNpmPackage)); -} - -module.exports = { - getPackageName, - getBundleOutputPaths, - prepareNpmPackages -}; diff --git a/scripts/rollup/plugins/closure-plugin.js b/scripts/rollup/plugins/closure-plugin.js deleted file mode 100755 index 0bce578a922..00000000000 --- a/scripts/rollup/plugins/closure-plugin.js +++ /dev/null @@ -1,35 +0,0 @@ -'use strict'; - -const ClosureCompiler = require('google-closure-compiler').compiler; -const { promisify } = require('util'); -const fs = require('fs'); -const tmp = require('tmp'); -const writeFileAsync = promisify(fs.writeFile); - -function compile(flags) { - return new Promise((resolve, reject) => { - const closureCompiler = new ClosureCompiler(flags); - closureCompiler.run(function(exitCode, stdOut, stdErr) { - if (!stdErr) { - resolve(stdOut); - } else { - reject(new Error(stdErr)); - } - }); - }); -} - -module.exports = function closure(flags = {}) { - return { - name: 'scripts/rollup/plugins/closure-plugin', - async renderChunk(code) { - const inputFile = tmp.fileSync(); - const tempPath = inputFile.name; - flags = Object.assign({}, flags, { js: tempPath }); - await writeFileAsync(tempPath, code, 'utf8'); - const compiledCode = await compile(flags); - inputFile.removeCallback(); - return { code: compiledCode }; - } - }; -}; diff --git a/scripts/rollup/plugins/strip-unused-imports.js b/scripts/rollup/plugins/strip-unused-imports.js deleted file mode 100755 index 66d27e36ace..00000000000 --- a/scripts/rollup/plugins/strip-unused-imports.js +++ /dev/null @@ -1,26 +0,0 @@ -/** - * Copyright (c) Facebook, Inc. and its affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ -'use strict'; - -module.exports = function stripUnusedImports(pureExternalModules) { - return { - name: 'scripts/rollup/plugins/strip-unused-imports', - renderChunk(code) { - pureExternalModules.forEach((module) => { - // Ideally this would use a negative lookbehind: (?