webpack-builder-react-xd
Version:
小盾安全前端团队构建器
362 lines (348 loc) • 11.3 kB
JavaScript
const webpack = require('webpack');
const chalk = require('chalk');
const fs = require('fs');
const CopyWebpackPlugin = require('copy-webpack-plugin');
const ProgressBarPlugin = require('progress-bar-webpack-plugin');
const CssUrlRelativePlugin = require('css-url-relative-plugin');
const CaseSensitivePathsPlugin = require('case-sensitive-paths-webpack-plugin');
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
const MinniCssExtractPlugin = require('mini-css-extract-plugin');
const ForkTsCheckerWebpackPlugin = require('fork-ts-checker-webpack-plugin');
const ReactRefreshWebpackPlugin = require('@pmmmwh/react-refresh-webpack-plugin');
const CopyFiles = require('./plugins/CopyFiles');
const paths = require('./paths');
const babelConfig = require('./babelConfig');
const { getEntrys } = require('./entrys');
const clientEnvironment = require('./env');
const { getAlais, getPublicPath, getBuilderConfig, createEnvironmentHash } = require('./hlper');
const { LOG_VALUE_COLOR, LOG_LABEL_COLOR } = require('./constant');
const builderConfig = getBuilderConfig();
const env = clientEnvironment();
const publicPath = getPublicPath();
const isEnvDevelopment = env.raw.NODE_ENV === 'development';
const isEnvProduction = env.raw.NODE_ENV === 'production';
// 生产环境将css提取到单独的文件,生产环境和足强架子微前端需要,不能和style-loader一起使用
const doExtract = isEnvProduction || process.env.MICRO;
const getStyleLoaders = (cssOptions, preProcessor) => {
return [
doExtract && MinniCssExtractPlugin.loader,
!doExtract && require.resolve('style-loader'),
// builderConfig.typescript && require.resolve('css-modules-typescript-loader'),
{
loader: require.resolve('css-loader'),
options: cssOptions,
},
{
loader: require.resolve('postcss-loader'),
options: {
postcssOptions: {
ident: 'postcss',
config: false,
plugins: [
'postcss-flexbugs-fixes',
'postcss-nested',
[
'postcss-preset-env',
{
autoprefixer: {
flexbox: 'no-2009',
},
stage: 3,
},
],
'postcss-normalize',
],
},
},
},
preProcessor,
].filter(Boolean);
};
/** @type {import('webpack').Configuration} */
const config = {
entry: paths.appIndex,
mode: env.raw.NODE_ENV,
cache: {
type: 'filesystem',
version: createEnvironmentHash(env.raw),
cacheDirectory: paths.appWebpackCache,
store: 'pack',
buildDependencies: {
defaultWebpack: ['webpack/lib/'],
config: [__filename],
tsconfig: [paths.appTsConfig].filter(f =>
fs.existsSync(f)
),
},
},
output: {
path: paths.appDist,
filename: 'js/[name].js',
chunkFilename: 'js/[name].js',
assetModuleFilename: 'media/[name]-[hash:6][ext]',
publicPath,
},
resolve: {
// 解析模块的可选项
extensions: ['.mjs', '.js', '.jsx', '.json', '.ts', '.tsx', '.less'], // 用到文件的扩展名
// 优化模块查找路径,src优先于 node_modules
modules: [paths.appSrc, 'node_modules'],
alias: getAlais(),
},
module: {
rules: [{
// 使用oneOf, 会依次配置loader, 如果未匹配到的会默认使用最后的 file-loader
oneOf: [
// TODO: Merge this config once `image/avif` is in the mime-db
// https://github.com/jshttp/mime-db
{
test: [/\.avif$/],
type: 'asset',
mimetype: 'image/avif',
},
// 加载图片
{
test: [/\.bmp$/, /\.gif$/, /\.jpe?g$/, /\.png$/],
type: 'asset/resource',
generator: {
filename: 'images/[name]_[hash:6][ext]'
}
},
{
test: /\.svg$/,
use: [
{
loader: require.resolve('@svgr/webpack'),
options: {
prettier: false,
svgo: false,
svgoConfig: {
plugins: [{ removeViewBox: false }],
},
titleProp: true,
ref: true,
},
},
{
loader: require.resolve('file-loader'),
options: {
name: 'media/[name].[ext]',
},
},
],
issuer: {
and: [/\.(ts|tsx|js|jsx|md|mdx)$/],
},
},
// 加载js
{
test: /\.(js|jsx|ts|tsx)$/,
exclude: /\/node_modules\//,
include: paths.appSrc, // 精确指定要处理的目录
loader: require.resolve('babel-loader'),
options: babelConfig,
},
// 支持引用html模板
{
test: /\.(html|htm|tpl)$/,
loader: require.resolve('html-loader'),
},
// src/*.css -> css module
{
test: /\.css$/,
exclude: /\/node_modules\//,
use: getStyleLoaders(
{
importLoaders: 1,
modules: {
mode: 'local',
exportLocalsConvention: 'camelCase',
localIdentName: '[name]__[local]__[hash:base64:5]',
},
}
),
},
// ./node_modules/*.css -> 全局css
// 没有被上面 css 匹配上的 src/*.css -> 全局css
{
test: /\.css$/,
include: [paths.appNodeModules],
use: getStyleLoaders({
importLoaders: 1,
}),
// Don't consider CSS imports dead code even if the
// containing package claims to have no side effects.
// Remove this when webpack adds a warning or an error for this.
// See https://github.com/webpack/webpack/issues/6571
sideEffects: true,
},
{
test: /\.less$/,
exclude: /\/node_modules\//,
use: getStyleLoaders(
{
importLoaders: 1,
modules: {
mode: 'local',
exportLocalsConvention: 'camelCase',
localIdentName: '[name]__[local]__[hash:base64:5]',
},
},
{
loader: require.resolve('less-loader'),
options: {
lessOptions: {
javascriptEnabled: true,
},
},
}
),
},
// ./node_modules/*.less -> 全局less
{
test: /\.less$/,
include: [paths.appNodeModules],
use: getStyleLoaders(
{
importLoaders: 1,
},
{
loader: require.resolve('less-loader'),
options: {
lessOptions: {
modifyVars: {
'primary-color': '#244ba8',
'link-color': '#244ba8',
'font-size-base': '12px',
'text-color': 'rgba(0,0,0,.65)',
},
javascriptEnabled: true,
},
},
}
),
sideEffects: true,
},
{
exclude: [/^$/, /\.(js|mjs|jsx|ts|tsx)$/, /\.html$/, /\.json$/],
type: 'asset/resource',
},
],
}],
},
plugins: [
new webpack.DefinePlugin(env.stringified),
// 处理css路径问题
new CssUrlRelativePlugin(),
// 显示构建进度
new ProgressBarPlugin(),
// 处理路径大小写问题
new CaseSensitivePathsPlugin(),
// 处理 .locale文件
// 先注释起来吧,要支持国际化
// new webpack.IgnorePlugin({
// resourceRegExp: /^\.\/locale$/,
// contextRegExp: /moment$/,
// }),
// 静态资源输出
new CopyWebpackPlugin({
patterns: [
{ from: paths.appAssets, to: paths.appDistAssets },
],
}),
].filter(Boolean),
// 定义sourcemap 配置
devtool: 'cheap-module-source-map',
// 关闭性能提示
performance: {
hints: false,
},
};
console.log(chalk.hex(LOG_LABEL_COLOR).bold('INFO:'), `当前构建模式为 ${chalk.hex(LOG_VALUE_COLOR).bold(env.raw.NODE_ENV)} 模式`);
if (doExtract) {
config.plugins.push(
new MinniCssExtractPlugin({
filename: 'css/[name].css',
ignoreOrder: true,
})
);
}
if (isEnvDevelopment) {
// 热更新
config.plugins.push(new ReactRefreshWebpackPlugin({
overlay: false,
}));
}
if (isEnvProduction) {
const TerserPlugin = require('terser-webpack-plugin');
const CssMinimizerPlugin = require('css-minimizer-webpack-plugin');
config.optimization = {
minimize: true,
minimizer: [
new TerserPlugin(
{
parallel: true,
terserOptions: {
// https://github.com/webpack-contrib/terser-webpack-plugin#terseroptions
compress: {
comparisons: false,
},
safari10: true,
format: {
comments: false,
},
},
extractComments: false,
}
),
new CssMinimizerPlugin(),
],
};
// 开启性能提示
config.performance = {};
// sourceMap 开启nosources
config.devtool = 'nosources-source-map';
}
// 支持ts
if (builderConfig.typescript) {
console.log(chalk.hex(LOG_LABEL_COLOR).bold('INFO:'), `当前构建已开启 ${chalk.hex(LOG_VALUE_COLOR).bold('TypeScript')} 支持`);
// eslint 检查
config.plugins.push(
new ForkTsCheckerWebpackPlugin({
async: true,
})
);
}
// 开启 profile 分析
if (process.env.PROFILE) {
console.log(chalk.hex(LOG_LABEL_COLOR).bold('INFO:'), `当前构建已开启 ${chalk.hex(LOG_VALUE_COLOR).bold('profile')} 分析`);
config.plugins.push(new BundleAnalyzerPlugin());
}
// 开启微前端构建
if (env.raw.MICRO) {
if (process.MICRO_CONFIG.mode === 'SINGLE') {
console.log(chalk.hex(LOG_LABEL_COLOR).bold('INFO:'), `当前构建已开启 微前端 ${chalk.hex(LOG_VALUE_COLOR).bold('SINGLE(单一入口)')} 构建模式`);
getEntrys(config);
} else {
console.log(chalk.hex(LOG_LABEL_COLOR).bold('INFO:'), `当前构建已开启 微前端 ${chalk.hex(LOG_VALUE_COLOR).bold('UNITY(统一入口)')} 构建模式`);
getEntrys(config);
}
if (isEnvProduction) {
config.plugins.push(new CopyFiles());
}
} else if (builderConfig.entry === 'MPA') {
console.log(chalk.hex(LOG_LABEL_COLOR).bold('INFO:'), `当前构建已开启 ${chalk.hex(LOG_VALUE_COLOR).bold('MPA')} 模式`);
// 自动生成html模板
getEntrys(config, 'MPA');
} else {
console.log(chalk.hex(LOG_LABEL_COLOR).bold('INFO:'), `当前构建已开启 ${chalk.hex(LOG_VALUE_COLOR).bold('SPA')} 模式`);
getEntrys(config, 'SPA');
}
const entryInfo = !(env.raw.MICRO && process.MICRO_CONFIG.mode === 'UNITY') ? JSON.stringify(config.entry) :
JSON.stringify(config.entry)
.split(',')
.join('\n ')
.replace('{', '\n ')
.replace('}', '');
console.log(chalk.hex(LOG_LABEL_COLOR).bold('INFO:'), `当前构建入口为 ${chalk.hex(LOG_VALUE_COLOR).bold(entryInfo)}`);
module.exports = config;