getWebpackConfig.js (162 lines of code) (raw):
const fs = require('fs')
const path = require('path')
const webpack = require('webpack')
const ringUiConfig = require('@jetbrains/ring-ui/webpack.config')
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin
const processPath = process.cwd()
const tsConfigExists = fs.existsSync(path.join(processPath, './tsconfig.json'))
const postcssConfigExists = fs.existsSync(path.join(processPath, './postcss.config.js'))
const babelConfigExists = fs.existsSync(path.join(processPath, './babel.config.js'))
/**
* This function returns ready-to-use webpack configuration for creating frontend plugins for TeamCity
* @param {Object} options - options for generating webpack.config.js
* @param {boolean | undefined} options.useTypeScript - set to true if you want to write code in TypeScript
* @param {string} options.srcPath - Path to the source directory. For example: path.join(__dirname, 'src')
* @param {string} options.outputPath - Path to write the compiled files to disk. For example: path.resolve(__dirname, '../demoPlugin-server/src/main/resources/buildServerResources')
* @param {string} options.entry - The point or points where to start the application bundling process. For example: './src/index.ts'.
* @param {string | undefined} options.cssPrefix - This prefix will be added to the beginning of every css class to avoid conflicts with other plugins.
* @returns {Function} webpack configuration
*/
module.exports = function getWebpackConfig(options) {
const {useTypeScript, useFlow, srcPath, outputPath, entry} = options
if (useFlow) {
printWarning('Flow support is deprecated and will be removed soon. Use the TypeScript instead.')
}
let packageName
if (options.cssPrefix == null || options.cssPrefix.length === 0) {
let packageJSON
try {
const file = fs.readFileSync(path.join(processPath, './package.json'), {encoding: 'UTF-8'})
packageJSON = JSON.parse(file)
} catch (e) {
printWarning(
"The package.json file was not found. Please pass the 'cssPrefix' option to the getWebpackConfig() function.",
)
}
if (packageJSON != null) {
if (packageJSON.name != null) {
packageName = packageJSON.name
} else {
printWarning(
"The 'name' property in package.json was not found. " +
"Please either specify it or pass the 'cssPrefix' option to the getWebpackConfig() function.",
)
}
}
}
ringUiConfig.loaders.cssLoader.include = [...ringUiConfig.loaders.cssLoader.include, srcPath]
ringUiConfig.loaders.cssLoader.use.forEach(item => {
if (item.loader != null) {
if (!postcssConfigExists && item.loader.includes('/postcss-loader')) {
item.options = {
...item.options,
postcssOptions: {
config: path.join(__dirname, './postcss.config.js'),
},
}
} else if (
item.loader.includes('/css-loader') &&
item.options != null &&
item.options.modules != null
) {
item.options.modules.localIdentName = `${options.cssPrefix != null ? `${options.cssPrefix}_` : ''}[local]_[hash:base64:4]`
if (packageName != null) {
item.options.modules.localIdentHashSalt = packageName
}
}
}
})
const babelLoader = {
loader: 'babel-loader',
options: {
cacheDirectory: true,
babelrc: false,
extends: babelConfigExists ? './babel.config.js' : path.join(__dirname, './babel.config.js'),
},
}
Object.assign(ringUiConfig.loaders.babelLoader, babelLoader)
return (env = {}) => ({
mode: env.production ? 'production' : 'development',
bail: !env.devserver,
entry,
output: {
path: outputPath,
filename: 'bundle.js',
},
resolve: useTypeScript
? {
extensions: ['.ts', '.tsx', '.js', '.css'],
}
: undefined,
module: {
rules: [
...ringUiConfig.config.module.rules,
{
test: /.css$/,
include: /node_modules\/@jetbrains\/(ring-ui-built|teamcity-ui)/,
use: ['style-loader', 'css-loader'],
},
useTypeScript && {
test: /\.ts(x?)$/,
exclude: /node_modules/,
use: [
{
loader: 'babel-loader',
options: !tsConfigExists
? {
configFile: path.join(__dirname, './plugin.tsconfig.json'),
}
: undefined,
},
],
},
{
test: /\.js$/,
include: [srcPath],
exclude: [/node_modules/],
use: [babelLoader],
},
{
test: /\.svg$/,
include: [/node_modules\/@jetbrains\/(icons|logos)/, srcPath],
use: [
babelLoader,
{
loader: '@svgr/webpack',
options: {
babel: false,
svgoConfig: {
plugins: [
{
name: 'preset-default',
params: {
overrides: {removeViewBox: false},
},
},
{name: 'prefixIds', params: {prefixClassNames: false}},
],
},
template: ({imports, componentName, props, jsx}, {tpl}) =>
tpl`${imports}
export default React.memo(function ${componentName}(${props}) {
return ${jsx}
})
`,
},
},
],
},
].filter(Boolean),
},
devServer: {
hot: true,
port: env.port,
host: env.host,
},
plugins: [
new webpack.IgnorePlugin({
resourceRegExp: /^\.\/locale$/,
contextRegExp: /moment$/,
}),
env.analyze &&
new BundleAnalyzerPlugin({
analyzerMode: 'static',
reportFilename: 'bundle-report.html',
openAnalyzer: false,
}),
].filter(Boolean),
})
}
function printWarning(message) {
console.warn(`\x1b[33m${message}\x1b[0m`)
}