UNPKG

zarbis

Version:

Configuration-less build tool

425 lines 16.9 kB
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