zarbis
Version:
Configuration-less build tool
425 lines • 16.9 kB
JavaScript
import { join, normalize } from 'node:path';
import ReactRefreshWebpackPlugin from '@pmmmwh/react-refresh-webpack-plugin';
import CompressionPlugin from 'compression-webpack-plugin';
import UglifyJsPlugin from "terser-webpack-plugin";
import * as webpack from 'webpack';
import { BundleAnalyzerPlugin } from 'webpack-bundle-analyzer';
import PackageJsonGenerator from './customPlugins/packageJsonGenerator.js';
import PackerPlugin from './customPlugins/PackerPlugin.js';
import ErrorFormatter from "./ErrorFormatter.js";
import { createRequire } from 'node:module';
const { DefinePlugin, HotModuleReplacementPlugin, NormalModuleReplacementPlugin, optimize, ProvidePlugin } = webpack.default;
const NONE = Symbol('none');
function clearObj(obj) {
let resObj = {};
for (let key in obj) {
let value = obj[key];
if (value !== NONE)
resObj[key] = value;
}
return resObj;
}
const resolve = (path) => {
const require = createRequire(import.meta.url);
const pathName = require.resolve(path);
return pathName;
};
export const generateBabelConfig = async (shitSupport, env) => {
let data = {
"presets": [[resolve('@babel/preset-env'), shitSupport ? {
"targets": ">100%",
modules: false,
useBuiltIns: 'usage',
corejs: 3,
} : {
"targets": {
"browsers": ">50%",
"node": "current"
},
shippedProposals: true,
modules: false
}]],
"plugins": [],
babelrc: false,
};
const plugins = data.plugins;
plugins.push(resolve('@babel/plugin-syntax-dynamic-import'));
plugins.push([resolve('@babel/plugin-transform-typescript'), {
isTSX: true
}]);
plugins.push(resolve('@babel/plugin-proposal-object-rest-spread'));
plugins.push(resolve('@babel/plugin-proposal-do-expressions'));
plugins.push([resolve('@babel/plugin-proposal-decorators'), {
legacy: true
}]);
plugins.push(resolve('@babel/plugin-proposal-class-properties'));
plugins.push(resolve('@babel/plugin-proposal-nullish-coalescing-operator'));
plugins.push(resolve('@babel/plugin-proposal-optional-chaining'));
plugins.push([resolve('@babel/plugin-transform-react-jsx'), {
throwIfNamespace: false,
useSpread: true,
runtime: 'automatic',
}]);
if (env === 'development') {
plugins.push(resolve('react-refresh/babel'));
}
return data;
};
export default async function mkConfig(logger, targetName, env, projectBaseDir, outputDirPath, config) {
let aliases = {
...config.quirks?.moduleAliases
};
if (config.for === 'web') {
aliases = {
...aliases,
'crypto': 'crypto-browserify',
'stream': 'stream-browserify',
'vm': 'vm-browserify',
'path': 'path-browserify',
'http': 'stream-http',
'process': 'node-process',
'fs': resolve('./stubs/fs.js'),
'child_process': resolve('./stubs/child_process.js'),
};
}
let ret = {
// Web target is selected for extensions
target: config.for,
node: {
__dirname: false,
__filename: false,
},
module: {
rules: [],
unknownContextCritical: false
},
experiments: {
asyncWebAssembly: true,
topLevelAwait: true,
},
devtool: env === 'development' ? 'source-map' : 'hidden-source-map',
entry: {},
resolve: {
alias: aliases,
extensions: [
'.tsx', '.ts', '.mjs', '.jsx', '.js'
],
modules: [...[
join(projectBaseDir, 'node_modules'),
(config.for === 'web' || config.for === 'webworker') ? join(import.meta.url, '../browserLibs/modules/') : null,
join(import.meta.url, '../commonLibs/modules/'),
].filter(m => m !== null).map(m => m).map(normalize), 'node_modules'],
mainFields: [],
symlinks: true,
},
output: clearObj({
sourcePrefix: '',
filename: config.quirks?.singleChunk ? 'main.js' : (env === 'development' ? NONE : '[chunkhash].js'),
sourceMapFilename: env === 'development' ? NONE : '[file].map[query]',
path: outputDirPath,
chunkFilename: env === 'development' ? NONE : 'c.[chunkhash].js',
// jsonpFunction: env === 'development' ? NONE : 'zarbisOnLoad',
pathinfo: env === 'development',
devtoolModuleFilenameTemplate: '[absolute-resource-path]',
devtoolFallbackModuleFilenameTemplate: `[absolute-resource-path]?[loaders]`,
libraryTarget: (config.for === 'node' ? 'commonjs2' : NONE),
publicPath: config.for === 'web' ? '/' : NONE,
crossOriginLoading: 'anonymous'
}),
plugins: [],
externals: [
'zarbis'
],
stats: {
errorDetails: true
},
cache: true,
parallelism: 7,
mode: 'none',
optimization: {
chunkIds: env === 'production' ? 'natural' : 'named',
moduleIds: env === 'production' ? 'natural' : 'named',
mangleExports: env === 'production',
removeAvailableModules: env === 'production',
removeEmptyChunks: env === 'production',
mergeDuplicateChunks: env === 'production',
flagIncludedChunks: env === 'production',
sideEffects: env === 'production',
providedExports: env === 'production',
usedExports: env === 'production',
concatenateModules: env === 'production',
noEmitOnErrors: false,
portableRecords: true,
minimize: !config.quirks?.skipTerser && env === 'production',
minimizer: config.quirks?.skipTerser ? [] : [
new UglifyJsPlugin({
extractComments: {
condition: false,
},
terserOptions: {
parse: {
bare_returns: false,
html5_comments: true,
shebang: true,
},
compress: {
arrows: true,
booleans: true,
collapse_vars: true,
comparisons: true,
computed_props: true,
conditionals: true,
dead_code: true,
drop_console: false,
drop_debugger: true,
evaluate: true,
expression: false,
global_defs: {},
hoist_funs: false,
hoist_props: true,
hoist_vars: false,
ie8: false,
if_return: true,
inline: true,
join_vars: true,
keep_classnames: false,
keep_fargs: true,
keep_fnames: false,
keep_infinity: false,
loops: true,
negate_iife: true,
passes: 1,
properties: true,
pure_getters: "strict",
reduce_funcs: true,
reduce_vars: true,
sequences: true,
side_effects: true,
switches: true,
top_retain: null,
toplevel: false,
typeofs: true,
unsafe: false,
unsafe_arrows: false,
unsafe_comps: false,
unsafe_Function: false,
unsafe_math: false,
unsafe_methods: false,
unsafe_proto: false,
unsafe_regexp: false,
unsafe_undefined: false,
unused: true,
},
mangle: {
eval: false,
keep_classnames: false,
keep_fnames: false,
properties: false,
safari10: false,
toplevel: false
},
output: {
ascii_only: false,
beautify: false,
comments: false,
ie8: false,
indent_level: 4,
indent_start: 0,
inline_script: true,
keep_quoted_props: false,
max_line_len: false,
quote_keys: false,
quote_style: 0,
safari10: false,
semicolons: true,
shebang: true,
webkit: false,
width: 80,
wrap_iife: false
},
}
})
],
splitChunks: {
chunks: "all", maxInitialRequests: 30, maxAsyncRequests: 30, maxSize: 100000
},
}
};
// Main fields
{
let mainFields = ['module', 'main'];
if (env === 'development')
mainFields = ['dev:module', ...mainFields];
if (config.for !== 'node') {
mainFields = ['browser', ...mainFields];
if (env === 'development')
mainFields = ['dev:browser', ...mainFields];
}
mainFields = [...mainFields];
ret.resolve.mainFields = mainFields;
}
// Module rules
{
let rules = ret.module.rules;
rules.push({
test: /node_modules[\/\\](iconv-lite)[\/\\].+/,
resolve: {
aliasFields: ['main']
}
});
rules.push({
type: 'javascript/auto',
test: /\.mjs$/,
use: []
});
// Because node doesn't supports css, and we need only to extract real class names for SSR
rules.push({
test: /\.((c|le)ss)$/,
loader: resolve('./customPlugins/isomorphicStyleLoader')
});
rules.push({
test: /\.(c|le)ss$/,
loader: resolve('css-loader'),
options: {
modules: {
mode: 'local',
exportGlobals: true,
namedExport: true,
localIdentName: env === 'development' ? '[path][name]__[local]--[hash:base64:5]' : '[hash:base64:12]',
},
sourceMap: env === 'development',
}
});
if (env === 'production') {
rules.push({
test: /\.(c|le)ss$/,
loader: resolve('postcss-loader'),
options: {
plugins(loader) {
return [
require('postcss-preset-env')(),
require('cssnano')(),
];
}
},
});
}
rules.push({
test: /\.less$/,
loader: resolve("less-loader")
});
// Files
rules.push({
test: /\.(txt|png|jpg|otf|gif|jpeg|svg|ttf|woff|eot|woff2)$/,
loader: resolve('url-loader'),
options: {
esModule: false,
fallback: resolve('file-loader'),
limit: config.quirks?.singleChunk ? Infinity : 8192,
name: env === 'development' ? '[name].[sha512:hash:hex:20].[ext]' : '[sha512:hash:hex:20].[ext]'
}
});
// Just js/jsx
rules.push({
test: /\.(:?m?[jt]sx?)$/i,
loader: resolve('babel-loader'),
exclude: /\.min\.js$/,
options: await generateBabelConfig(!!config.quirks?.oldBrowser, env),
});
}
// Entry points
for (let key of Object.keys(config.entrypoints)) {
let entry = ret.entry;
if (env === 'development')
entry[key] = [resolve('./customLoaders/hmrApplier.js'), ...config.entrypoints[key]];
else
entry[key] = [...config.entrypoints[key]];
if (config.for === 'node')
entry[key] = [resolve('./customLoaders/nodeSupport.js'), ...entry[key]];
}
// Plugins
{
let plugins = ret.plugins;
if (env === 'development') {
plugins.push(new ReactRefreshWebpackPlugin({
forceEnable: true,
esModule: false,
}));
}
plugins.push(new ErrorFormatter(logger, false, false));
if (config.quirks?.packer) {
plugins.push(new PackerPlugin());
}
plugins.push(new DefinePlugin({
'process.browser': config.for === 'web',
'process.env.ELECTRON': config.for.startsWith('electron'),
'process.env.BROWSER': config.for === 'web',
'process.env.NODE': config.for === 'node' || config.for.startsWith('electron'),
'process.env.NODE_ENV': JSON.stringify(env),
'process.env.ENV': JSON.stringify(env),
'process.env.ZARBIS_ALIASES': JSON.stringify(aliases)
}));
if (config.for === 'node' || config.for.startsWith('electron')) {
plugins.push(new PackageJsonGenerator({
name: targetName
}, projectBaseDir));
}
(config.for === 'web' || config.for === 'webworker') && plugins.push(new NormalModuleReplacementPlugin(/^http2$/, resolve('./stubs/http2.js')), new NormalModuleReplacementPlugin(/^cluster$/, resolve('./stubs/cluster.js')), new NormalModuleReplacementPlugin(/^fs$/, resolve('./stubs/fs.js')));
for (let from of Object.keys(aliases)) {
const regexp = new RegExp('^' + from + '(:?$|\\/)');
plugins.push(new NormalModuleReplacementPlugin(regexp, function (resource) {
resource.request = resource.request.replace(regexp, aliases[from]);
}));
}
if (env === 'development') {
plugins.push(new HotModuleReplacementPlugin({
multiStep: false,
}));
}
if (config.for === 'web') {
plugins.push(new ProvidePlugin({
'process': 'process',
'Buffer': ['buffer', 'Buffer']
}));
}
if (env === 'production' && config.for === 'web')
plugins.push(new CompressionPlugin({
minRatio: 1,
threshold: 0,
filename: '[path].gz',
compressionOptions: {
level: 9,
},
}));
if (env === 'production') {
plugins.push(new BundleAnalyzerPlugin({
analyzerMode: 'static',
defaultSizes: 'gzip',
openAnalyzer: false
}));
}
if (config.quirks?.singleChunk) {
plugins.push(new optimize.LimitChunkCountPlugin({
maxChunks: 1,
}));
}
}
// Externals
// const externals = [/^(:?@[a-z\-0-9]+\/)?[a-z\-0-9]+\/?/, ...config.quirks?.externals ?? []];
// const internals = config.quirks?.internals ?? [];
config.for === 'node' && ret.externals.push('http2', (/*{ context, request }*/ _ctx, callback) => {
// if (internals.some(r => r.test(request))) {
// return callback();
// } else if (externals.some(r => r.test(request))) {
// return callback(null, `commonjs ${request}`);
// } else {
// return callback();
// }
return callback();
});
for (const modifier of config.modifiers ?? []) {
modifier(ret);
}
return ret;
}
//# sourceMappingURL=webpackConfigGenerator.js.map