@@ -0,0 +1,55 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2018 SonarSource SA | |||
* mailto:info AT sonarsource DOT com | |||
* | |||
* This program is free software; you can redistribute it and/or | |||
* modify it under the terms of the GNU Lesser General Public | |||
* License as published by the Free Software Foundation; either | |||
* version 3 of the License, or (at your option) any later version. | |||
* | |||
* This program is distributed in the hope that it will be useful, | |||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |||
* Lesser General Public License for more details. | |||
* | |||
* You should have received a copy of the GNU Lesser General Public License | |||
* along with this program; if not, write to the Free Software Foundation, | |||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
*/ | |||
// Copied from https://github.com/facebook/create-react-app/blob/master/packages/react-dev-utils/InterpolateHtmlPlugin.js | |||
// This Webpack plugin lets us interpolate custom variables into `index.html`. | |||
// Usage: `new InterpolateHtmlPlugin({ 'MY_VARIABLE': 42 })` | |||
// Then, you can use %MY_VARIABLE% in your `index.html`. | |||
// It works in tandem with HtmlWebpackPlugin. | |||
// Learn more about creating plugins like this: | |||
// https://github.com/ampedandwired/html-webpack-plugin#events | |||
'use strict'; | |||
const escapeStringRegexp = require('escape-string-regexp'); | |||
class InterpolateHtmlPlugin { | |||
constructor(replacements) { | |||
this.replacements = replacements; | |||
} | |||
apply(compiler) { | |||
compiler.hooks.compilation.tap('InterpolateHtmlPlugin', compilation => { | |||
compilation.hooks.htmlWebpackPluginBeforeHtmlProcessing.tap('InterpolateHtmlPlugin', data => { | |||
// Run HTML through a series of user-specified string replacements. | |||
Object.keys(this.replacements).forEach(key => { | |||
const value = this.replacements[key]; | |||
data.html = data.html.replace( | |||
new RegExp('%' + escapeStringRegexp(key) + '%', 'g'), | |||
value | |||
); | |||
}); | |||
}); | |||
}); | |||
} | |||
} | |||
module.exports = InterpolateHtmlPlugin; |
@@ -18,42 +18,9 @@ | |||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
*/ | |||
const path = require('path'); | |||
const fs = require('fs'); | |||
// Make sure any symlinks in the project folder are resolved: | |||
// https://github.com/facebookincubator/create-react-app/issues/637 | |||
const appDirectory = fs.realpathSync(process.cwd()); | |||
function resolveApp(relativePath) { | |||
return path.resolve(appDirectory, relativePath); | |||
} | |||
// We support resolving modules according to `NODE_PATH`. | |||
// This lets you use absolute paths in imports inside large monorepos: | |||
// https://github.com/facebookincubator/create-react-app/issues/253. | |||
// It works similar to `NODE_PATH` in Node itself: | |||
// https://nodejs.org/api/modules.html#modules_loading_from_the_global_folders | |||
// We will export `nodePaths` as an array of absolute paths. | |||
// It will then be used by Webpack configs. | |||
// Jest doesn’t need this because it already handles `NODE_PATH` out of the box. | |||
const nodePaths = (process.env.NODE_PATH || '') | |||
.split(process.platform === 'win32' ? ';' : ':') | |||
.filter(Boolean) | |||
.map(resolveApp); | |||
// config after eject: we're in ./config/ | |||
module.exports = { | |||
appBuild: resolveApp('build/webapp'), | |||
appPublic: resolveApp('public'), | |||
appHtml: resolveApp('public/index.html'), | |||
appPackageJson: resolveApp('package.json'), | |||
appSrc: resolveApp('src/main/js'), | |||
jsBuild: resolveApp('build/webapp/js'), | |||
cssBuild: resolveApp('build/webapp/css'), | |||
htmlBuild: resolveApp('build/webapp/index.html'), | |||
appNodeModules: resolveApp('node_modules'), | |||
ownNodeModules: resolveApp('node_modules'), | |||
nodePaths | |||
appBuild: path.join(__dirname, '../build/webapp'), | |||
appPublic: path.join(__dirname, '../public'), | |||
appHtml: path.join(__dirname, '../public/index.html') | |||
}; |
@@ -1,20 +1,41 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2018 SonarSource SA | |||
* mailto:info AT sonarsource DOT com | |||
* | |||
* This program is free software; you can redistribute it and/or | |||
* modify it under the terms of the GNU Lesser General Public | |||
* License as published by the Free Software Foundation; either | |||
* version 3 of the License, or (at your option) any later version. | |||
* | |||
* This program is distributed in the hope that it will be useful, | |||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |||
* Lesser General Public License for more details. | |||
* | |||
* You should have received a copy of the GNU Lesser General Public License | |||
* along with this program; if not, write to the Free Software Foundation, | |||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
*/ | |||
/* eslint-disable import/no-extraneous-dependencies */ | |||
const path = require('path'); | |||
const ExtractTextPlugin = require('extract-text-webpack-plugin'); | |||
const CleanWebpackPlugin = require('clean-webpack-plugin'); | |||
const CopyWebpackPlugin = require('copy-webpack-plugin'); | |||
const MiniCssExtractPlugin = require('mini-css-extract-plugin'); | |||
const HtmlWebpackPlugin = require('html-webpack-plugin'); | |||
const InterpolateHtmlPlugin = require('react-dev-utils/InterpolateHtmlPlugin'); | |||
const webpack = require('webpack'); | |||
const InterpolateHtmlPlugin = require('./InterpolateHtmlPlugin'); | |||
const paths = require('./paths'); | |||
const cssMinimizeOptions = { | |||
discardComments: { removeAll: true } | |||
}; | |||
const cssLoader = ({ production, fast }) => ({ | |||
const cssLoader = ({ production }) => ({ | |||
loader: 'css-loader', | |||
options: { | |||
importLoaders: 1, | |||
minimize: production && !fast && cssMinimizeOptions, | |||
minimize: production && cssMinimizeOptions, | |||
url: false | |||
} | |||
}); | |||
@@ -33,35 +54,20 @@ const postcssLoader = () => ({ | |||
} | |||
}); | |||
module.exports = ({ production = true, fast = false }) => ({ | |||
bail: production, | |||
devtool: production ? (fast ? false : 'source-map') : 'cheap-module-source-map', | |||
module.exports = ({ production = true }) => ({ | |||
mode: production ? 'production' : 'development', | |||
devtool: production ? 'source-map' : 'cheap-module-source-map', | |||
resolve: { | |||
// Add '.ts' and '.tsx' as resolvable extensions. | |||
extensions: ['.ts', '.tsx', '.js', '.json'] | |||
}, | |||
entry: { | |||
vendor: [ | |||
!production && require.resolve('react-dev-utils/webpackHotDevClient'), | |||
require.resolve('./polyfills'), | |||
!production && require.resolve('react-error-overlay'), | |||
'lodash', | |||
'd3-array', | |||
'd3-hierarchy', | |||
'd3-scale', | |||
'd3-selection', | |||
'd3-shape', | |||
'react', | |||
'react-dom' | |||
].filter(Boolean), | |||
app: [ | |||
'./src/main/js/app/utils/setPublicPath.js', | |||
'./src/main/js/app/index.js', | |||
'./src/main/js/components/SourceViewer/SourceViewer' | |||
] | |||
}, | |||
entry: [ | |||
!production && require.resolve('react-dev-utils/webpackHotDevClient'), | |||
require.resolve('./polyfills'), | |||
!production && require.resolve('react-error-overlay'), | |||
'./src/main/js/app/utils/setPublicPath.js', | |||
'./src/main/js/app/index.js' | |||
].filter(Boolean), | |||
output: { | |||
path: paths.appBuild, | |||
pathinfo: !production, | |||
@@ -71,88 +77,71 @@ module.exports = ({ production = true, fast = false }) => ({ | |||
module: { | |||
rules: [ | |||
{ | |||
test: /\.js$/, | |||
loader: 'babel-loader', | |||
exclude: /(node_modules|libs)/ | |||
}, | |||
{ | |||
test: /\.tsx?$/, | |||
test: /(\.js$|\.ts(x?)$)/, | |||
exclude: /(node_modules|libs)/, | |||
use: [ | |||
{ loader: 'babel-loader' }, | |||
{ | |||
loader: 'awesome-typescript-loader', | |||
options: { | |||
transpileOnly: true, | |||
useBabel: true, | |||
useCache: true | |||
} | |||
loader: 'ts-loader', | |||
options: { transpileOnly: true } | |||
} | |||
] | |||
}, | |||
production | |||
? { | |||
test: /\.css$/, | |||
loader: ExtractTextPlugin.extract({ | |||
fallback: 'style-loader', | |||
use: [cssLoader({ production, fast }), postcssLoader()] | |||
}) | |||
} | |||
: { | |||
test: /\.css$/, | |||
use: ['style-loader', cssLoader({ production, fast }), postcssLoader()] | |||
}, | |||
{ | |||
test: /\.css$/, | |||
use: [ | |||
production ? MiniCssExtractPlugin.loader : 'style-loader', | |||
cssLoader({ production }), | |||
postcssLoader() | |||
].filter(Boolean) | |||
}, | |||
{ test: require.resolve('lodash'), loader: 'expose-loader?_' }, | |||
{ test: require.resolve('react'), loader: 'expose-loader?React' }, | |||
{ test: require.resolve('react-dom'), loader: 'expose-loader?ReactDOM' } | |||
].filter(Boolean) | |||
}, | |||
plugins: [ | |||
new webpack.optimize.CommonsChunkPlugin({ name: 'vendor' }), | |||
// `allowExternal: true` to remove files outside of the current dir | |||
production && new CleanWebpackPlugin([paths.appBuild], { allowExternal: true, verbose: false }), | |||
production && | |||
new ExtractTextPlugin({ | |||
filename: production ? 'css/sonar.[chunkhash:8].css' : 'css/sonar.css' | |||
}), | |||
new CopyWebpackPlugin([ | |||
{ | |||
from: paths.appPublic, | |||
to: paths.appBuild, | |||
ignore: [paths.appHtml] | |||
} | |||
]), | |||
!production && new InterpolateHtmlPlugin({ WEB_CONTEXT: '' }), | |||
production && | |||
new MiniCssExtractPlugin({ | |||
filename: 'css/[name].[chunkhash:8].css', | |||
chunkFilename: 'css/[name].[chunkhash:8].chunk.css' | |||
}), | |||
new HtmlWebpackPlugin({ | |||
inject: false, | |||
template: paths.appHtml, | |||
minify: production && | |||
!fast && { | |||
removeComments: true, | |||
collapseWhitespace: true, | |||
removeRedundantAttributes: true, | |||
useShortDoctype: true, | |||
removeEmptyAttributes: true, | |||
removeStyleLinkTypeAttributes: true, | |||
keepClosingSlash: true, | |||
minifyJS: true, | |||
minifyCSS: true, | |||
minifyURLs: true | |||
} | |||
minify: production && { | |||
removeComments: true, | |||
collapseWhitespace: true, | |||
removeRedundantAttributes: true, | |||
useShortDoctype: true, | |||
removeEmptyAttributes: true, | |||
removeStyleLinkTypeAttributes: true, | |||
keepClosingSlash: true, | |||
minifyJS: true, | |||
minifyCSS: true, | |||
minifyURLs: true | |||
} | |||
}), | |||
new webpack.DefinePlugin({ | |||
'process.env.NODE_ENV': JSON.stringify(production ? 'production' : 'development') | |||
}), | |||
production && | |||
!fast && | |||
new webpack.optimize.UglifyJsPlugin({ | |||
sourceMap: true, | |||
compress: { screw_ie8: true, warnings: false }, | |||
mangle: { screw_ie8: true }, | |||
output: { comments: false, screw_ie8: true } | |||
}), | |||
// keep `InterpolateHtmlPlugin` after `HtmlWebpackPlugin` | |||
!production && new InterpolateHtmlPlugin({ WEB_CONTEXT: '' }), | |||
!production && new webpack.HotModuleReplacementPlugin() | |||
].filter(Boolean), | |||
// Some libraries import Node modules but don't use them in the browser. | |||
// Tell Webpack to provide empty mocks for them so importing them works. | |||
node: { | |||
fs: 'empty', | |||
net: 'empty', | |||
tls: 'empty' | |||
optimization: { | |||
splitChunks: { chunks: 'all' } | |||
} | |||
}); |
@@ -57,10 +57,9 @@ | |||
"@types/react-router": "3.0.13", | |||
"@types/react-select": "1.0.59", | |||
"autoprefixer": "7.1.6", | |||
"awesome-typescript-loader": "3.2.3", | |||
"babel-core": "6.26.0", | |||
"babel-jest": "22.0.6", | |||
"babel-loader": "7.1.2", | |||
"babel-loader": "7.1.4", | |||
"babel-plugin-dynamic-import-node": "1.1.0", | |||
"babel-plugin-syntax-dynamic-import": "6.18.0", | |||
"babel-plugin-transform-class-properties": "^6.22.0", | |||
@@ -71,40 +70,42 @@ | |||
"babel-preset-env": "1.6.1", | |||
"babel-preset-react": "^6.22.0", | |||
"chalk": "2.3.0", | |||
"css-loader": "0.28.7", | |||
"clean-webpack-plugin": "0.1.19", | |||
"copy-webpack-plugin": "4.5.1", | |||
"css-loader": "0.28.11", | |||
"enzyme": "3.3.0", | |||
"enzyme-adapter-react-16": "1.1.1", | |||
"enzyme-to-json": "3.3.0", | |||
"escape-string-regexp": "1.0.5", | |||
"eslint": "4.17.0", | |||
"eslint-plugin-import": "2.8.0", | |||
"eslint-plugin-jsx-a11y": "6.0.2", | |||
"eslint-plugin-promise": "3.6.0", | |||
"eslint-plugin-react": "7.6.1", | |||
"expose-loader": "0.7.3", | |||
"extract-text-webpack-plugin": "3.0.1", | |||
"expose-loader": "0.7.5", | |||
"flow-bin": "^0.52.0", | |||
"fs-extra": "0.30.0", | |||
"html-webpack-plugin": "2.30.1", | |||
"html-webpack-plugin": "3.0.6", | |||
"jest": "22.0.6", | |||
"lint-staged": "4.3.0", | |||
"mini-css-extract-plugin": "0.2.0", | |||
"postcss-calc": "6.0.1", | |||
"postcss-custom-properties": "6.2.0", | |||
"postcss-loader": "2.0.8", | |||
"postcss-loader": "2.1.1", | |||
"prettier": "1.10.2", | |||
"react-dev-utils": "5.0.0", | |||
"react-error-overlay": "1.0.7", | |||
"react-test-renderer": "16.2.0", | |||
"rimraf": "2.6.2", | |||
"style-loader": "0.19.0", | |||
"style-loader": "0.20.3", | |||
"ts-jest": "22.0.1", | |||
"ts-loader": "4.1.0", | |||
"typescript": "2.7.1", | |||
"typescript-eslint-parser": "13.0.0", | |||
"webpack": "3.8.1", | |||
"webpack-bundle-analyzer": "2.9.0", | |||
"webpack-dev-server": "2.9.3" | |||
"webpack": "4.1.1", | |||
"webpack-bundle-analyzer": "2.11.1", | |||
"webpack-dev-server": "3.1.1" | |||
}, | |||
"scripts": { | |||
"start": "node scripts/start.js", | |||
"build-fast": "node scripts/build.js --fast", | |||
"build": "node scripts/build.js", | |||
"test": "node scripts/test.js", | |||
"coverage": "npm test -- --coverage", |
@@ -21,34 +21,15 @@ | |||
process.env.NODE_ENV = 'production'; | |||
const chalk = require('chalk'); | |||
const fs = require('fs-extra'); | |||
const rimrafSync = require('rimraf').sync; | |||
const webpack = require('webpack'); | |||
const paths = require('../config/paths'); | |||
const sortBy = require('lodash/sortBy'); | |||
const formatSize = require('./utils/formatSize'); | |||
const getConfig = require('../config/webpack.config'); | |||
const fast = process.argv.some(arg => arg.indexOf('--fast') > -1); | |||
const config = getConfig({ fast, production: true }); | |||
function clean() { | |||
// Remove all content but keep the directory so that | |||
// if you're in it, you don't end up in Trash | |||
console.log(chalk.cyan.bold('Cleaning output directories and files...')); | |||
console.log(paths.appBuild + '/*'); | |||
rimrafSync(paths.appBuild + '/*'); | |||
console.log(); | |||
} | |||
const config = getConfig({ production: true }); | |||
function build() { | |||
if (fast) { | |||
console.log(chalk.magenta.bold('Running fast build...')); | |||
} else { | |||
console.log(chalk.cyan.bold('Creating optimized production build...')); | |||
} | |||
console.log(chalk.cyan.bold('Creating optimized production build...')); | |||
console.log(); | |||
webpack(config, (err, stats) => { | |||
@@ -65,16 +46,17 @@ function build() { | |||
} | |||
const jsonStats = stats.toJson(); | |||
const withoutSourceMaps = jsonStats.assets.filter(asset => !asset.name.endsWith('.map')); | |||
console.log('Assets:'); | |||
const assets = jsonStats.assets.slice(); | |||
assets.sort((a, b) => b.size - a.size); | |||
assets.forEach(asset => { | |||
let sizeLabel = formatSize(asset.size); | |||
const leftPadding = ' '.repeat(Math.max(0, 8 - sizeLabel.length)); | |||
sizeLabel = leftPadding + sizeLabel; | |||
console.log('', chalk.yellow(sizeLabel), asset.name); | |||
}); | |||
console.log(`Biggest assets (${withoutSourceMaps.length} total):`); | |||
sortBy(withoutSourceMaps, asset => -asset.size) | |||
.slice(0, 5) | |||
.forEach(asset => { | |||
let sizeLabel = formatSize(asset.size); | |||
const leftPadding = ' '.repeat(Math.max(0, 8 - sizeLabel.length)); | |||
sizeLabel = leftPadding + sizeLabel; | |||
console.log('', chalk.yellow(sizeLabel), asset.name); | |||
}); | |||
console.log(); | |||
const seconds = jsonStats.time / 1000; | |||
@@ -85,13 +67,4 @@ function build() { | |||
}); | |||
} | |||
function copyPublicFolder() { | |||
fs.copySync(paths.appPublic, paths.appBuild, { | |||
dereference: true, | |||
filter: file => file !== paths.appHtml | |||
}); | |||
} | |||
clean(); | |||
build(); | |||
copyPublicFolder(); |
@@ -24,5 +24,5 @@ module.exports = function(bytes) { | |||
const k = 1000; // or 1024 for binary | |||
const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB']; | |||
const i = Math.floor(Math.log(bytes) / Math.log(k)); | |||
return parseFloat((bytes / Math.pow(k, i)).toFixed(1)) + ' ' + sizes[i]; | |||
return parseFloat((bytes / Math.pow(k, i)).toFixed(0)) + ' ' + sizes[i]; | |||
}; |
@@ -12,7 +12,8 @@ | |||
"lib": ["es2017", "dom"], | |||
"module": "esnext", | |||
"moduleResolution": "node", | |||
"typeRoots": ["./src/main/js/typings", "./node_modules/@types"] | |||
"typeRoots": ["./src/main/js/typings", "./node_modules/@types"], | |||
"sourceMap": true | |||
}, | |||
"include": ["./src/main/js/**/*"] | |||
} |
@@ -123,7 +123,7 @@ zip.doFirst { | |||
} | |||
// Check the size of the archive | |||
zip.doLast { | |||
def minLength = 155000000 | |||
def minLength = 150000000 | |||
def maxLength = 170000000 | |||
def length = new File(distsDir, archiveName).length() | |||
if (length < minLength) |