@procore/core-scripts
Version:
A CLI to enhance your development experience
532 lines • 21.9 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.normalizePublicPath = void 0;
exports.setupWebpack = setupWebpack;
const tslib_1 = require("tslib");
const cdn_1 = tslib_1.__importDefault(require("@procore/cdn"));
const case_sensitive_paths_webpack_plugin_1 = tslib_1.__importDefault(require("case-sensitive-paths-webpack-plugin"));
const fs_1 = tslib_1.__importDefault(require("fs"));
const html_webpack_plugin_1 = tslib_1.__importDefault(require("html-webpack-plugin"));
const mini_css_extract_plugin_1 = tslib_1.__importDefault(require("mini-css-extract-plugin"));
const webpack_bugsnag_plugins_1 = require("webpack-bugsnag-plugins");
const css_minimizer_webpack_plugin_1 = tslib_1.__importDefault(require("css-minimizer-webpack-plugin"));
const path_1 = tslib_1.__importDefault(require("path"));
const postcss_flexbugs_fixes_1 = tslib_1.__importDefault(require("postcss-flexbugs-fixes"));
const postcss_normalize_1 = tslib_1.__importDefault(require("postcss-normalize"));
const postcss_preset_env_1 = tslib_1.__importDefault(require("postcss-preset-env"));
const ForkTsCheckerWebpackPlugin_1 = tslib_1.__importDefault(require("react-dev-utils/ForkTsCheckerWebpackPlugin"));
const getCSSModuleLocalIdent_1 = tslib_1.__importDefault(require("react-dev-utils/getCSSModuleLocalIdent"));
const InlineChunkHtmlPlugin_1 = tslib_1.__importDefault(require("react-dev-utils/InlineChunkHtmlPlugin"));
const InterpolateHtmlPlugin_1 = tslib_1.__importDefault(require("react-dev-utils/InterpolateHtmlPlugin"));
const ModuleNotFoundPlugin_1 = tslib_1.__importDefault(require("react-dev-utils/ModuleNotFoundPlugin"));
const resolve_1 = tslib_1.__importDefault(require("resolve"));
const terser_webpack_plugin_1 = tslib_1.__importDefault(require("terser-webpack-plugin"));
const webpack_1 = tslib_1.__importDefault(require("webpack"));
const webpack_bundle_analyzer_1 = require("webpack-bundle-analyzer");
const webpack_manifest_plugin_1 = require("webpack-manifest-plugin");
const workbox_webpack_plugin_1 = tslib_1.__importDefault(require("workbox-webpack-plugin"));
const config_1 = require("../../babel/config");
const environment_1 = require("../../environment");
const moduleFederationDecorator_1 = require("./pluginDecorators/moduleFederationDecorator");
/**
* Passing `https://assets%d.procore.com` as the public path breaks the chunks
* since they are being injected by JavaScript.
*
* We need to replace `%d` with a valid value since this is done in
* `Hydra::AssetPath`.
*/
const normalizePublicPath = (publicPath) => publicPath.replace(/%d/, '0');
exports.normalizePublicPath = normalizePublicPath;
function getIndexHtmlPath(workspace) {
const appTemplate = workspace.resolve('public', 'index.html');
const defaultTemplate = path_1.default.resolve(__dirname, '..', '..', '..', 'public', 'index.html');
return fs_1.default.existsSync(appTemplate) ? appTemplate : defaultTemplate;
}
const resolveCdn = (0, cdn_1.default)(4);
const BROWSER_LIST = [
'last 2 chrome versions',
'last 2 firefox versions',
'last 2 safari versions',
'last 2 edge versions',
'ie 11',
];
function isEnvValueTrue(envValue) {
if (envValue) {
return envValue.toLowerCase() === 'true';
}
return false;
}
function setupWebpack(config, opts) {
var _a, _b, _c, _d;
const options = {
shouldInlineRuntimeChunk: false,
shouldUseSourceMap: true,
...opts,
};
const isEnvDevelopment = options.env === 'development';
const isEnvProduction = options.env === 'production';
const appTsConfigPath = opts.workspace.resolve('tsconfig.json');
const appBuildPath = opts.workspace.resolve('build');
const appSrcPath = opts.workspace.resolve('src');
// const appPackageJsonPath = opts.workspace.resolve('package.json')
const appNodeModulesPath = opts.workspace.resolve('node_modules');
const pkgJson = options.workspace.packageJson;
const appIndexHtmlPath = getIndexHtmlPath(opts.workspace);
const useTypeScript = fs_1.default.existsSync(appTsConfigPath);
const shouldUseRelativeAssetPaths = options.publicPath === './';
const shouldUploadSourcemaps = opts.env === 'production'
? isEnvValueTrue(process.env.BUGSNAG_UPLOAD_SOURCEMAPS) || false
: false;
const suffix = Buffer.from(pkgJson.name + pkgJson.version)
.toString('base64')
.slice(0, 5);
const envVariables = (0, environment_1.getEnvVariables)({
WDS_SOCKET_HOST: (_a = options.hmr) === null || _a === void 0 ? void 0 : _a.host,
WDS_SOCKET_PORT: (_b = options.hmr) === null || _b === void 0 ? void 0 : _b.port,
WDS_SOCKET_PATH: (_c = options.hmr) === null || _c === void 0 ? void 0 : _c.socketPath,
NODE_ENV: opts.env,
PUBLIC_URL: (0, exports.normalizePublicPath)(options.publicUrl),
RAILS_MODE: opts.railsMode,
});
const hasModuleFederation = ((_d = options.workspace.procoreConfig.app) === null || _d === void 0 ? void 0 : _d.moduleFederation) !== undefined;
function injectStyleLoaders(rule, cssOptions, preProcessor) {
rule.when(isEnvDevelopment, (rule) => {
rule
.use('style')
.loader(require.resolve('style-loader'))
.options({ esModule: false });
}, (rule) => {
rule
.use('cssExtract')
.loader(mini_css_extract_plugin_1.default.loader)
.options(shouldUseRelativeAssetPaths
? { esModule: false, publicPath: '../../' }
: { esModule: false });
});
rule
.use('css')
.loader(require.resolve('css-loader'))
.options({ ...cssOptions, esModule: false });
rule
.use('postcss')
.loader(require.resolve('postcss-loader'))
.options({
postcssOptions: {
ident: 'postcss',
plugins: [
postcss_flexbugs_fixes_1.default,
(0, postcss_preset_env_1.default)({
autoprefixer: {
flexbox: 'no-2009',
},
browsers: BROWSER_LIST,
stage: 3,
}),
(0, postcss_normalize_1.default)(),
],
},
sourceMap: isEnvProduction && options.shouldUseSourceMap,
});
rule.when(preProcessor, (rule) => {
rule
.use('resolveUrl')
.loader(require.resolve('resolve-url-loader'))
.options({
sourceMap: isEnvProduction && options.shouldUseSourceMap,
});
rule.use(preProcessor).loader(require.resolve(preProcessor)).options({
sourceMap: true,
});
});
}
config.performance
.hints(false)
.maxEntrypointSize(Infinity)
.maxAssetSize(Infinity);
config
.mode(options.env)
.bail(isEnvProduction)
.context(opts.workspace.context)
.when(isEnvProduction && options.shouldUseSourceMap, (config) => config.devtool('source-map'), (config) => config.devtool('cheap-module-source-map'));
Object.entries(options.entries).forEach((entry) => {
const entryName = `${entry[0]}.bundle`;
const entryPath = opts.workspace.resolve('src', entry[1]);
config
.entry(entryName)
.when(isEnvDevelopment, (entry) => entry.add(require.resolve('react-dev-utils/webpackHotDevClient')))
.add(entryPath);
});
// webpack5 drops built in polyfills, add them back in
config.merge({
target: ['web', 'es5'],
resolve: {
fallback: {
assert: 'assert',
buffer: 'buffer',
console: 'console-browserify',
constants: 'constants-browserify',
crypto: 'crypto-browserify',
domain: 'domain-browser',
events: 'events',
http: 'stream-http',
https: 'https-browserify',
os: 'os-browserify/browser',
path: 'path-browserify',
punycode: 'punycode',
process: 'process/browser',
querystring: 'querystring-es3',
stream: 'stream-browserify',
_stream_duplex: 'readable-stream/duplex',
_stream_passthrough: 'readable-stream/passthrough',
_stream_readable: 'readable-stream/readable',
_stream_transform: 'readable-stream/transform',
_stream_writable: 'readable-stream/writable',
string_decoder: 'string_decoder',
sys: 'util',
timers: 'timers-browserify',
tty: 'tty-browserify',
url: 'url',
util: 'util',
vm: 'vm-browserify',
zlib: 'browserify-zlib',
},
},
});
config.output.merge({
// TODO: Use `static/js/bundle.js` for filename
chunkFilename: 'static/js/[name].chunk.js',
crossOriginLoading: false,
devtoolModuleFilenameTemplate: (info) => path_1.default.resolve(info.absoluteResourcePath).replace(/\\/g, '/'),
filename: '[name].js',
globalObject: 'self',
hashFunction: 'xxhash64',
path: '/',
pathinfo: isEnvDevelopment,
publicPath: (0, exports.normalizePublicPath)(options.publicPath),
});
config.output.when(isEnvProduction, (output) => {
output.merge({
path: appBuildPath,
chunkFilename: 'static/js/[name].[contenthash:8].chunk.js',
filename: 'static/js/[name].[contenthash:8].js',
devtoolModuleFilenameTemplate: (info) => path_1.default
.relative(appSrcPath, info.absoluteResourcePath)
.replace(/\\/g, '/'),
});
});
config.optimization.minimize(isEnvProduction).runtimeChunk(false);
config.optimization.minimizer('terser').use(terser_webpack_plugin_1.default, [
{
terserOptions: {
parse: {
ecma: 8,
},
compress: {
ecma: 5,
warnings: false,
comparisons: false,
inline: 2,
},
// TODO: investigate this config
// mangle: {
// safari10: true,
// },
mangle: true,
keep_classnames: options.profile,
// TODO: investigate this config
// keep_fnames: options.profile,
keep_fnames: options.profile ? options.profile : /./,
output: {
ecma: 5,
comments: false,
ascii_only: true,
},
},
parallel: true,
},
]);
config.optimization.minimizer('optimizeCSS').use(css_minimizer_webpack_plugin_1.default);
config.resolve.modules.add('node_modules').add(appNodeModulesPath);
config.resolve.alias
.set('@', appSrcPath)
.set('react-native', 'react-native-web')
.when(options.profile, (alias) => alias.merge({
'react-dom$': 'react-dom/profiling',
'scheduler/tracing': 'scheduler/tracing-profiling',
}));
['cjs', 'mjs', 'js', 'ts', 'tsx', 'json', 'jsx']
.map((ext) => `.${ext}`)
.map((ext) => config.resolve.extensions.add(ext));
// config.resolve
// .plugin('moduleScope')
// .use(ModuleScopePlugin, [appSrcPath, [appPackageJsonPath]])
config.module
.rule('requireEnsure')
.test(/\.[cm]?js$/)
.parser({ requireEnsure: false });
config.module
.rule('compiler')
.oneOf('url')
.test([/\.bmp$/, /\.gif$/, /\.jpe?g$/, /\.png$/])
.use('url')
.loader(require.resolve('url-loader'))
.options({
limit: 10000,
name: 'static/media/[name].[hash:8].[ext]',
});
config.module
.rule('compiler')
.oneOf('worker')
.test(/\.worker\.(js|jsx|ts|tsx)$/)
.include.add(appSrcPath)
.end()
.use('worker')
.options({ inline: 'fallback' })
.loader(require.resolve('worker-loader'))
.end()
.use('babel')
.loader(require.resolve('babel-loader'))
.options((0, config_1.createConfigForReact)({ env: opts.env }));
config.module
.rule('compiler')
.oneOf('babel')
.test(/\.(js|cjs|mjs|jsx|ts|tsx)$/)
.include.add(appSrcPath)
.end()
.exclude.add(appNodeModulesPath)
.end()
.use('babel')
.loader(require.resolve('babel-loader'))
.options((0, config_1.createConfigForReact)({ env: opts.env }));
config.module
.rule('compiler')
.oneOf('babelDeps')
.test(/\.(js|cjs|mjs)$/)
.resolve.set('fullySpecified', false)
.end()
.exclude.add(/@babel(?:\/|\\{1,2})runtime/)
.add(/core-js/)
.add(appSrcPath)
.end()
.use('babel')
.loader(require.resolve('babel-loader'))
.options((0, config_1.createConfigForDependencies)({
env: opts.env,
shouldUseSourceMap: options.shouldUseSourceMap,
}));
config.module
.rule('compiler')
.oneOf('babepDeps')
.test(/\.(js|cjs|mjs)$/)
.include.add(/@babel(?:\/|\\{1,2})runtime/)
.end()
.resolve.set('fullySpecified', false);
config.module
.rule('compiler')
.oneOf('cssModules')
.test(/\.css$/);
injectStyleLoaders(config.module.rule('compiler').oneOf('cssModules'), {
importLoaders: 1,
sourceMap: isEnvProduction && options.shouldUseSourceMap,
modules: {
getLocalIdent: getCSSModuleLocalIdent_1.default,
},
});
config.module
.rule('compiler')
.oneOf('sassModules')
.test(/\.(scss|sass)$/)
.exclude.add(/core-css(.*)\.scss/)
.end();
injectStyleLoaders(config.module.rule('compiler').oneOf('sassModules'), {
importLoaders: 2,
sourceMap: isEnvProduction && options.shouldUseSourceMap,
modules: {
getLocalIdent: getCSSModuleLocalIdent_1.default,
},
}, 'sass-loader');
config.module
.rule('compiler')
.oneOf('coreCssModules')
.test(/core-css(.*)\.scss$/)
.end();
injectStyleLoaders(config.module.rule('compiler').oneOf('coreCssModules'), {
importLoaders: 2,
sourceMap: isEnvProduction && options.shouldUseSourceMap,
modules: {
getLocalIdent: (...args) => {
return `${(0, getCSSModuleLocalIdent_1.default)(...args)}_${suffix}`;
},
},
}, 'sass-loader');
config.module
.rule('compiler')
.oneOf('file')
.exclude.add(/\.(js|cjs|mjs|jsx|ts|tsx|html|json)$/)
.end()
.use('file')
.loader(require.resolve('file-loader'))
.options({
name: 'static/media/[name].[hash:8].[ext]',
});
config.plugin('html').use(html_webpack_plugin_1.default, [
{
inject: true,
template: appIndexHtmlPath,
minify: isEnvProduction
? {
removeComments: true,
collapseWhitespace: true,
removeRedundantAttributes: true,
useShortDoctype: true,
removeEmptyAttributes: true,
removeStyleLinkTypeAttributes: true,
keepClosingSlash: true,
minifyJS: true,
minifyCSS: true,
minifyURLs: true,
}
: false,
},
]);
config.when(isEnvProduction && options.shouldInlineRuntimeChunk, () => {
config
.plugin('inlineRuntimeChunk')
.use(InlineChunkHtmlPlugin_1.default, [html_webpack_plugin_1.default, [/runtime-.+[.]js/]]);
});
config
.plugin('interpolateHtml')
.use(InterpolateHtmlPlugin_1.default, [html_webpack_plugin_1.default, envVariables]);
config
.plugin('moduleNotFound')
.use(ModuleNotFoundPlugin_1.default, [opts.workspace.context]);
config
.plugin('injectEnvVariables')
.use(webpack_1.default.DefinePlugin, [(0, environment_1.stringifyEnvironment)(envVariables)]);
config.when(isEnvDevelopment, (config) => {
config
.plugin('hotModuleReplacement')
.use(webpack_1.default.HotModuleReplacementPlugin);
});
config.when(isEnvDevelopment, (config) => {
config.plugin('caseSensitivePaths').use(case_sensitive_paths_webpack_plugin_1.default);
});
config.when(isEnvProduction, (config) => {
config.plugin('extractCss').use(mini_css_extract_plugin_1.default, [
{
filename: 'static/css/[name].[contenthash:8].css',
chunkFilename: 'static/css/[name].[contenthash:8].chunk.css',
},
]);
});
config.when(shouldUploadSourcemaps, (config) => {
config.plugin('bugsnag').use(webpack_bugsnag_plugins_1.BugsnagSourceMapUploaderPlugin, [
{
apiKey: envVariables.PROCORE_HYDRA_BUGSNAG_API_KEY,
publicPath: process.env.BUGSNAG_SOURCEMAP_URL || 'https://assets*.procore.com',
overwrite: true,
},
]);
});
config.when(Boolean(opts.analyze), () => {
config.plugin('bundleAnalyzer').use(webpack_bundle_analyzer_1.BundleAnalyzerPlugin);
});
config.plugin('minifest').use(webpack_manifest_plugin_1.WebpackManifestPlugin, [
{
fileName: 'asset-manifest.json',
publicPath: options.publicPath,
generate: (seed, files,
// eslint-disable-next-line @typescript-eslint/no-unused-vars
_entrypoints) => {
function addToManifest(manifestSeed, file) {
return {
...manifestSeed,
[file.name]: resolveCdn(file.path),
};
}
const manifestFiles = files.reduce(addToManifest, seed);
// const entrypointFiles = entrypoints.main.filter(
// (fileName) => !fileName.endsWith('.map')
// );
// TODO: break change once Hydra gem is fixed.
// return {
// files: manifestFiles,
// entrypoints: entrypointFiles,
// };
return manifestFiles;
},
},
]);
config.plugin('ignore').use(webpack_1.default.IgnorePlugin, [
{
resourceRegExp: /^\.\/locale$/,
contextRegExp: /moment$/,
},
]);
config.when(isEnvProduction, (config) => {
config.plugin('serviceWorker').use(workbox_webpack_plugin_1.default.GenerateSW, [
{
clientsClaim: true,
exclude: [/\.map$/, /asset-manifest\.json$/],
navigateFallback: `${options.publicUrl}/index.html`,
navigateFallbackDenylist: [
// eslint-disable-next-line prefer-regex-literals
new RegExp('^/_'),
// eslint-disable-next-line prefer-regex-literals
new RegExp('/[^/?]+\\.[^/]+$'),
],
},
]);
});
config.when(useTypeScript, (config) => {
config.plugin('typescriptCheck').use(ForkTsCheckerWebpackPlugin_1.default, [
{
async: isEnvDevelopment,
typescript: {
typescriptPath: resolve_1.default.sync('typescript', {
basedir: appNodeModulesPath,
}),
configOverride: {
compilerOptions: {
sourceMap: isEnvProduction
? options.shouldUseSourceMap
: isEnvDevelopment,
skipLibCheck: true,
inlineSourceMap: false,
declarationMap: false,
noEmit: true,
incremental: true,
tsBuildInfoFile: opts.workspace.resolve('node_modules', '.cache', 'tsconfig.tsbuildinfo'),
},
},
context: opts.workspace.context,
diagnosticOptions: {
syntactic: true,
},
mode: 'write-references',
// profile: true,
},
issue: {
include: [
{ file: '../**/src/**/*.{ts,tsx}' },
{ file: '**/src/**/*.{ts,tsx}' },
],
exclude: [
{ file: '**/src/**/__tests__/**' },
{ file: '**/src/**/?(*.){spec|test}.*' },
{ file: '**/src/setupProxy.*' },
{ file: '**/src/setupTests.*' },
],
},
logger: {
infrastructure: 'silent',
},
},
]);
});
config.when(hasModuleFederation, (config) => {
(0, moduleFederationDecorator_1.setupModuleFederation)(config, options);
});
return config;
}
//# sourceMappingURL=setupWebpack.js.map