dev-utils/build.js (229 lines of code) (raw):

/** * MIT License * * Copyright (c) 2017-present, Elasticsearch BV * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. * */ const { join } = require('path') const { DefinePlugin, EnvironmentPlugin, ProvidePlugin } = require('webpack') const TerserPlugin = require('terser-webpack-plugin') const { VueLoaderPlugin } = require('vue-loader') const { BundleAnalyzerPlugin } = require('webpack-bundle-analyzer') const { getTestEnvironmentVariables } = require('./test-config') const BUNDLE_TYPES = { NODE_DEV: 'NODE_DEV', NODE_PROD: 'NODE_PROD', NODE_ESM_PROD: 'NODE_ESM_PROD', BROWSER_DEV: 'BROWSER_DEV', BROWSER_PROD: 'BROWSER_PROD', BROWSER_ESM_PROD: 'BROWSER_ESM_PROD', BROWSER_ESM_ES2015: 'BROWSER_ESM_ES2015' } const { NODE_PROD, NODE_ESM_PROD, BROWSER_DEV, BROWSER_PROD, BROWSER_ESM_PROD, BROWSER_ESM_ES2015 } = BUNDLE_TYPES const PACKAGE_TYPES = { DEFAULT: 'DEFAULT', REACT: 'REACT', ANGULAR: 'ANGULAR', VUE: 'VUE' } // the default webpack hash doesn't work properly with Node >= 17 // there will no zero-config solution until webpack 6 // please, see more info here https://github.com/webpack/webpack/issues/14532#issuecomment-947525539 const WEBPACK_HASH_FN = 'xxhash64' // We must exclude `parse5` from the packages we ignore // because otherwise it would not be transpiled. Therefore, // it would break IE11. const babelExclusionRule = /node_modules\/(?!parse5).+/ function getBabelPresetEnv(bundleType) { const isBrowser = [ BROWSER_DEV, BROWSER_PROD, BROWSER_ESM_PROD, BROWSER_ESM_ES2015 ].includes(bundleType) /** * Decides transformation of ES6 module syntax to another module type. */ const shipESModule = [ NODE_ESM_PROD, BROWSER_ESM_PROD, BROWSER_ESM_ES2015 ].includes(bundleType) /** * By default RUM agent support starts from IE 11 and we do not want every users to run * babel on thier dependencies to support older browser versions. However, * advanced users can always use webpack resolve.mainFields set to `source` * and target their audience. */ let targets = { ie: '11' } /** * Angular CLI uses the target `es2015` by default which is based on the * Angular Packaging Format specification and uses it for differential * loading (module/nomodule) * Info - https://angular.io/guide/deployment#configuring-differential-loading * * Babel already supports browsers targetting ES Modules via `esmodules` flag * https://babeljs.io/docs/en/babel-preset-env#targetsesmodules */ if (isBrowser && bundleType === BROWSER_ESM_ES2015) { targets = { esmodules: true } } else if (!isBrowser) { targets = { node: true } } return [ [ '@babel/preset-env', { targets, modules: shipESModule ? false : 'auto', loose: true } ] ] } function getAngularConfig(options) { return Object.assign({}, options, { presets: options.presets.concat(['@babel/preset-typescript']), plugins: options.plugins.concat([ /** * Angular dependency injection will not work * if we dont have this plugin enabled */ 'babel-plugin-transform-typescript-metadata', ['@babel/plugin-proposal-decorators', { legacy: true }], ['@babel/plugin-proposal-class-properties', { loose: true }] ]) }) } function getReactConfig(options) { return Object.assign({}, options, { presets: options.presets.concat(['@babel/react']), plugins: options.plugins.concat([ '@babel/plugin-transform-destructuring', '@babel/plugin-syntax-dynamic-import' ]) }) } function getVueConfig(options) { return Object.assign({}, options, { plugins: options.plugins.concat(['@babel/plugin-syntax-dynamic-import']) }) } function getOptions(options, packageType) { if (packageType === PACKAGE_TYPES.REACT) { return getReactConfig(options) } else if (packageType === PACKAGE_TYPES.ANGULAR) { return getAngularConfig(options) } else if (packageType === PACKAGE_TYPES.VUE) { return getVueConfig(options) } return options } function getBabelConfig(bundleType, packageType) { let options = { comments: false, babelrc: false, exclude: babelExclusionRule, presets: getBabelPresetEnv(bundleType), plugins: [] } return getOptions(options, packageType) } /** * Used for injecting process.env across webpack bundles for testing */ function getWebpackEnv(env = 'development') { const { serverUrl, stackVersion } = getTestEnvironmentVariables() return { APM_SERVER_URL: serverUrl, STACK_VERSION: stackVersion, NODE_ENV: env } } function isProduction(bundleType) { const isEnvProduction = [ NODE_PROD, NODE_ESM_PROD, BROWSER_PROD, BROWSER_ESM_PROD ].includes(bundleType) return isEnvProduction } function getCommonWebpackConfig(bundleType, packageType) { const isEnvProduction = isProduction(bundleType) const mode = isEnvProduction ? 'production' : 'development' return { devtool: isEnvProduction ? 'source-map' : 'inline-cheap-module-source-map', mode, stats: { colors: true, assets: true, modules: false }, performance: { hints: false }, module: { rules: [ { test: /\.(js|jsx|ts)$/, exclude: babelExclusionRule, loader: 'babel-loader', options: getBabelConfig(bundleType, packageType) } ] }, plugins: [ new EnvironmentPlugin(getWebpackEnv(mode)), new ProvidePlugin({ process: 'process/browser' }) ], ...(isEnvProduction ? { optimization: { minimize: true, minimizer: [ new TerserPlugin({ sourceMap: true, extractComments: true }) ] } } : {}) } } /** * Webpack config that are used across Unit, Integration and E2E Tests */ function getWebpackConfig(bundleType, packageType) { const config = { ...getCommonWebpackConfig(bundleType, packageType), ...{ resolve: { mainFields: ['source', 'browser', 'module', 'main'], extensions: ['.js', '.jsx', '.ts'], fallback: { stream: require.resolve('stream-browserify'), util: require.resolve('util/') } } } } if (packageType === PACKAGE_TYPES.VUE) { config.module.rules.push({ test: /\.vue$/, use: 'vue-loader' }) config.plugins.push(new VueLoaderPlugin()) config.plugins.push( new DefinePlugin({ __VUE_OPTIONS_API__: true, __VUE_PROD_DEVTOOLS__: false }) ) config.resolve.alias = { vue$: 'vue/dist/vue.esm-bundler.js' } } return config } /** * Webpack config which handles how the RUM and Opentracing bundles * are built for releases */ function getWebpackReleaseConfig(bundleType, name) { const isEnvProduction = isProduction(bundleType) const REPORTS_DIR = join(__dirname, '..', 'packages', 'rum', 'reports') const config = { ...getCommonWebpackConfig(bundleType) } if (isEnvProduction) { /** * Warns if the ungzipped bundle size is more than 60 kB */ config.performance = { hints: 'warning', maxAssetSize: 60 * 1024 // 60 kB } config.plugins.push( new BundleAnalyzerPlugin({ analyzerMode: 'static', reportFilename: join(REPORTS_DIR, `${name}-report.html`), generateStatsFile: true, statsFilename: join(REPORTS_DIR, `${name}-stats.json`), openAnalyzer: false }) ) } return config } module.exports = { getBabelConfig, getWebpackConfig, getWebpackReleaseConfig, BUNDLE_TYPES, PACKAGE_TYPES, WEBPACK_HASH_FN }